0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-30 16:41:20 -05:00

Adjustments to svgclean

This commit is contained in:
alonso.torres 2021-03-04 07:44:46 +01:00 committed by Andrey Antukh
parent d3345c0fa6
commit 7482122964
11 changed files with 301 additions and 76 deletions

File diff suppressed because one or more lines are too long

View file

@ -132,8 +132,9 @@
"Return a map without the keys provided
in the `keys` parameter."
[data keys]
(persistent!
(reduce #(dissoc! %1 %2) (transient data) keys)))
(when data
(persistent!
(reduce #(dissoc! %1 %2) (transient data) keys))))
(defn remove-at-index
[v index]

View file

@ -285,6 +285,24 @@
(dissoc :modifiers)))
shape)))
(defn update-group-viewbox
"Updates the viewbox for groups imported from SVG's"
[{:keys [selrect svg-viewbox] :as group} new-selrect]
(let [;; Gets deltas for the selrect to update the svg-viewbox (for svg-imports)
deltas {:x (- (:x new-selrect) (:x selrect))
:y (- (:y new-selrect) (:y selrect))
:width (- (:width new-selrect) (:width selrect))
:height (- (:height new-selrect) (:height selrect))}]
(cond-> group
svg-viewbox
(update :svg-viewbox
#(-> %
(update :x + (:x deltas))
(update :y + (:y deltas))
(update :width + (:width deltas))
(update :height + (:height deltas)))))))
(defn update-group-selrect [group children]
(let [shape-center (gco/center-shape group)
transform (:transform group (gmt/matrix))
@ -306,6 +324,7 @@
;; Updates the shape and the applytransform-rect will update the other properties
(-> group
(update-group-viewbox new-selrect)
(assoc :selrect new-selrect)
(assoc :points new-points)

File diff suppressed because one or more lines are too long

View file

@ -35,7 +35,8 @@
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[potok.core :as ptk]))
[potok.core :as ptk]
[promesa.core :as p]))
(declare persist-changes)
(declare persist-sychronous-changes)
@ -392,7 +393,7 @@
(or (contains? props :data)
(contains? props :uris)))))
(defn parse-svg [text]
(defn parse-svg [[name text]]
(->> (http/send! {:method :post
:uri "/api/svg"
:headers {"content-type" "image/svg+xml"}
@ -400,13 +401,9 @@
(rx/map (fn [{:keys [status body]}]
(let [result (t/decode body)]
(if (= status 200)
result
(assoc result :name name)
(throw result)))))))
(defn fetch-svg [uri]
(->> (http/send! {:method :get :uri uri})
(rx/map :body)))
(defn url-name [url]
(let [query-idx (str/last-index-of url "?")
url (if (> query-idx 0) (subs url 0 query-idx) url)
@ -414,6 +411,12 @@
ext-idx (str/last-index-of filename ".")]
(if (> ext-idx 0) (subs filename 0 ext-idx) filename)))
(defn fetch-svg [name uri]
(->> (http/send! {:method :get :uri uri})
(rx/map #(vector
(or name (url-name uri))
(:body %)))))
(defn- handle-upload-error [on-error stream]
(->> stream
(rx/catch
@ -467,10 +470,8 @@
(->> (rx/from uris)
(rx/filter svg-url?)
(rx/merge-map fetch-svg)
(rx/merge-map (partial fetch-svg name))
(rx/merge-map parse-svg)
(rx/with-latest vector uris)
(rx/map #(assoc (first %) :name (or name (url-name (second %)))))
(rx/do on-svg)))))
(defn- upload-data [file-id local? name data force-media on-image on-svg]
@ -485,6 +486,12 @@
:is-local local?
:content blob}))
extract-content
(fn [blob]
(let [name (or name (.-name blob))]
(-> (.text blob)
(p/then #(vector name %)))))
file-stream (->> (rx/from data)
(rx/map di/validate-file))]
(rx/merge
@ -496,10 +503,8 @@
(->> file-stream
(rx/filter svg-blob?)
(rx/merge-map #(.text %))
(rx/merge-map extract-content)
(rx/merge-map parse-svg)
(rx/with-latest vector file-stream)
(rx/map #(assoc (first %) :name (.-name (second %))))
(rx/do on-svg)))))
(defn- upload-media-objects

View file

@ -89,7 +89,7 @@
shape)))
(defn create-raw-svg [name frame-id svg-data {:keys [attrs] :as data}]
(let [{:keys [x y width height]} svg-data]
(let [{:keys [x y width height offset-x offset-y]} svg-data]
(-> {:id (uuid/next)
:type :svg-raw
:name name
@ -101,7 +101,8 @@
:content (cond-> data
(map? data) (update :attrs usvg/clean-attrs))}
(assoc :svg-attrs attrs)
(assoc :svg-viewbox (select-keys svg-data [0 0 :width :height]))
(assoc :svg-viewbox (-> (select-keys svg-data [:width :height])
(assoc :x offset-x :y offset-y)))
(gsh/setup-selrect))))
(defn create-svg-root [frame-id svg-data]
@ -119,7 +120,8 @@
(dissoc :viewBox :xmlns))))))
(defn create-group [name frame-id svg-data {:keys [attrs]}]
(let [{:keys [x y width height offset-x offset-y]} svg-data]
(let [svg-transform (usvg/parse-transform (:transform attrs))
{:keys [x y width height offset-x offset-y]} svg-data]
(-> {:id (uuid/next)
:type :group
:name name
@ -128,8 +130,10 @@
:y (+ y offset-y)
:width width
:height height}
(assoc :svg-attrs (dissoc attrs :transform))
(assoc :svg-viewbox (select-keys svg-data [:x :y :width :height]))
(assoc :svg-transform svg-transform)
(assoc :svg-attrs (d/without-keys attrs usvg/inheritable-props))
(assoc :svg-viewbox (-> (select-keys svg-data [:width :height])
(assoc :x offset-x :y offset-y)))
(gsh/setup-selrect))))
(defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}]
@ -220,7 +224,7 @@
(contains? attrs :ry) (assoc :ry (d/parse-double (:ry attrs))))
(merge metadata)
(assoc :svg-transform transform)
#_(assoc :svg-transform transform)
(assoc :svg-viewbox (select-keys rect [:x :y :width :height]))
(assoc :svg-attrs (dissoc attrs :x :y :width :height :rx :ry :transform)))))
@ -257,20 +261,10 @@
:frame-id frame-id}
(merge metadata)
(assoc :svg-transform transform)
#_(assoc :svg-transform transform)
(assoc :svg-viewbox (select-keys rect [:x :y :width :height]))
(assoc :svg-attrs (dissoc attrs :cx :cy :r :rx :ry :transform)))))
(defn add-transform [transform node]
(letfn [(append-transform [old-transform]
(if (or (nil? old-transform) (empty? old-transform))
transform
(str old-transform " " transform)))]
(cond-> node
transform
(update-in [:attrs :transform] append-transform))))
(defn parse-svg-element [frame-id svg-data element-data unames]
(let [{:keys [tag attrs]} element-data
attrs (usvg/format-styles attrs)
@ -286,14 +280,17 @@
use-tag? (and (= :use tag) (contains? defs href-id))]
(if use-tag?
;; TODO: If the child is a symbol we've to take the width/height into account
(let [use-data (get defs href-id)
translate (gpt/point (:x attrs 0) (:y attrs 0))
attrs' (dissoc attrs :x :y :width :height :href :xlink:href)
;; TODO: If the child is a symbol we've to take the width/height into account
use-data (update use-data :attrs #(let [attrs (usvg/format-styles %)]
(d/deep-merge attrs' attrs)))
[shape children] (parse-svg-element frame-id svg-data use-data unames)]
[(-> shape (gsh/move translate)) children])
displacement (gpt/point (d/parse-double (:x attrs "0")) (d/parse-double (:y attrs "0")))
disp-matrix (str (gmt/translate-matrix displacement))
element-data (-> element-data
(assoc :tag :g)
(update :attrs dissoc :x :y :width :height :href :xlink:href)
(update :attrs usvg/add-transform disp-matrix)
(assoc :content [use-data]))]
(parse-svg-element frame-id svg-data element-data unames))
;; SVG graphic elements
;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use
@ -304,7 +301,8 @@
:ellipse) (create-circle-shape name frame-id svg-data element-data)
:path (create-path-shape name frame-id svg-data element-data)
:polyline (create-path-shape name frame-id svg-data (-> element-data usvg/polyline->path))
:polygon (create-path-shape name frame-id svg-data (-> element-data usvg/polygon->path))
:polygo (create-path-shape name frame-id svg-data (-> element-data usvg/polygon->path))
:line (create-path-shape name frame-id svg-data (-> element-data usvg/line->path))
#_other (create-raw-svg name frame-id svg-data element-data))
(assoc :svg-defs (select-keys (:defs svg-data) references))
@ -313,7 +311,7 @@
children (cond->> (:content element-data)
(= tag :g)
(mapv #(add-transform (:transform attrs) %)))]
(mapv #(usvg/inherit-attributes attrs %)))]
[shape children]))))
(defn add-svg-child-changes [page-id objects selected frame-id parent-id svg-data [unames [rchs uchs]] [index data]]
@ -365,20 +363,24 @@
:height vb-height
:name svg-name))
[def-nodes svg-data] (usvg/extract-defs svg-data)
[def-nodes svg-data] (-> svg-data
(usvg/fix-default-values)
(usvg/fix-percents)
(usvg/extract-defs))
svg-data (assoc svg-data :defs def-nodes)
root-shape (create-svg-root frame-id svg-data)
root-id (:id root-shape)
changes (dwc/add-shape-changes page-id objects selected root-shape)
changes (dwc/add-shape-changes page-id objects selected root-shape false)
reducer-fn (partial add-svg-child-changes page-id objects selected frame-id root-id svg-data)
[_ [rchanges uchanges]] (reduce reducer-fn [unames changes] (d/enumerate (:content svg-data)))
reg-objects-action {:type :reg-objects
:page-id page-id
:shapes (->> rchanges (map :id) (remove nil?) (into #{root-id}) vec)}
:shapes (->> rchanges (filter #(= :add-obj (:type %))) (map :id) reverse vec)}
rchanges (conj rchanges reg-objects-action)]

View file

@ -34,8 +34,7 @@
props (-> (attrs/extract-style-attrs shape)
(obj/merge!
#js {:className "group"
:pointerEvents pointer-events
#js {:pointerEvents pointer-events
:mask (when (and mask (not expand-mask)) (mask-str mask))}))]
[:> :g props

View file

@ -48,23 +48,17 @@
:else
(let [{:keys [tag attrs content]} node
transform-gradient? (and (#{:linearGradient :radialGradient} tag)
(= "userSpaceOnUse" (get attrs :gradientUnits "userSpaceOnUse")))
transform-gradient? (and (contains? usvg/gradient-tags tag)
(= "userSpaceOnUse" (get attrs :gradientUnits "objectBoundingBox")))
transform-pattern? (and (= :pattern tag)
(every? d/num-string? [(:x attrs "0") (:y attrs "0") (:width attrs "0") (:height attrs "0")])
(= "userSpaceOnUse" (get attrs :patternUnits "userSpaceOnUse")))
transform-clippath? (and (= :clipPath tag)
(= "userSpaceOnUse" (get attrs :clipPathUnits "userSpaceOnUse")))
transform-filter? (and (= #{:filter
;; Filter primitives. We need to remap subregions
:feBlend :feColorMatrix :feComponentTransfer :feComposite :feConvolveMatrix
:feDiffuseLighting :feDisplacementMap :feFlood :feGaussianBlur
:feImage :feMerge :feMorphology :feOffset
:feSpecularLighting :feTile :feTurbulence} tag)
(= "userSpaceOnUse" (get attrs :filterUnits "userSpaceOnUse")))
transform-filter? (and (contains? usvg/filter-tags tag)
(= "userSpaceOnUse" (get attrs :filterUnits "objectBoundingBox")))
attrs (-> attrs
(usvg/update-attr-ids prefix-id)
@ -88,8 +82,6 @@
(mf/defc svg-defs [{:keys [shape render-id]}]
(let [svg-defs (:svg-defs shape)
;;_ (when (:svg-transform shape)
;; (.log js/console (:name shape) (:old-transform shape) (str (:svg-transform shape))))
transform (mf/use-memo
(mf/deps shape)
@ -98,7 +90,7 @@
(usvg/svg-transform-matrix shape)))
;; Paths doesn't have transform so we have to transform its gradients
transform (if (and (= :path (:type shape)) (contains? shape :svg-transform))
transform (if (contains? shape :svg-transform)
(gmt/multiply transform (or (:svg-transform shape) (gmt/matrix)))
transform)

View file

@ -14,6 +14,7 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[cuerdas.core :as str]))
(defonce replace-regex #"#([^\W]+)")
@ -99,7 +100,8 @@
:else
(let [replace-id
(fn [result it]
(str/replace result it (replace-fn it)))]
(let [to-replace (replace-fn it)]
(str/replace result (str "#" it) (str "#" to-replace))))]
(reduce replace-id val (extract-ids val)))))]
(d/mapm update-ids attrs)))
@ -119,11 +121,13 @@
(reduce visit-node result (:content node))))]
(visit-node {} content)))
(def remove-tags #{:defs :linearGradient})
(defn extract-defs [{:keys [tag attrs content] :as node}]
(if-not (map? node)
[{} node]
(let [remove-node? (fn [{:keys [tag]}] (= tag :defs))
(let [remove-node? (fn [{:keys [tag]}] (contains? remove-tags tag))
rec-result (->> (:content node) (map extract-defs))
node (assoc node :content (->> rec-result (map second) (filterv (comp not remove-node?))))
@ -218,13 +222,13 @@
(defn format-scale-params [params]
(assert (or (= (count params) 1) (= (count params) 2)))
(if (= (count params) 1)
[(gpt/point (nth params 0))]
[(gpt/point (mth/abs (nth params 0)))]
[(gpt/point (nth params 0) (nth params 1))]))
(defn format-rotate-params [params]
(assert (or (= (count params) 1) (= (count params) 3)) (str "??" (count params)))
(if (= (count params) 1)
[(nth params 0)]
[(nth params 0) (gpt/point 0 0)]
[(nth params 0) (gpt/point (nth params 1) (nth params 2))]))
(defn format-skew-x-params [params]
@ -291,3 +295,207 @@
(dissoc :points)
(assoc :d (str (points->path (:points attrs)) "Z")))]
(assoc node :attrs attrs :tag tag)))
(defn line->path [{:keys [attrs tag] :as node}]
(let [tag :path
{:keys [x1 y1 x2 y2]} attrs
attrs (-> attrs
(dissoc :x1 :x2 :y1 :y2)
(assoc :d (str "M" x1 "," y1 " L" x2 "," y2)))]
(assoc node :attrs attrs :tag tag)))
(defn add-transform [attrs transform]
(letfn [(append-transform [old-transform]
(if (or (nil? old-transform) (empty? old-transform))
transform
(str transform " " old-transform)))]
(cond-> attrs
transform
(update :transform append-transform))))
(defonce inheritable-props
[:clip-rule
:color
:color-interpolation
:color-interpolation-filters
:color-profile
:color-rendering
:cursor
:direction
:dominant-baseline
:fill
:fill-opacity
:fill-rule
:font
:font-family
:font-size
:font-size-adjust
:font-stretch
:font-style
:font-variant
:font-weight
:glyph-orientation-horizontal
:glyph-orientation-vertical
:image-rendering
:letter-spacing
:marker
:marker-end
:marker-mid
:marker-start
:paint-order
:pointer-events
:shape-rendering
:stroke
:stroke-dasharray
:stroke-dashoffset
:stroke-linecap
:stroke-linejoin
:stroke-miterlimit
:stroke-opacity
:stroke-width
:text-anchor
:text-rendering
:transform
:visibility
:word-spacing
:writing-mode])
(defonce gradient-tags
#{:linearGradient
:radialGradient})
(defonce filter-tags
#{:filter
:feBlend
:feColorMatrix
:feComponentTransfer
:feComposite
:feConvolveMatrix
:feDiffuseLighting
:feDisplacementMap
:feFlood
:feGaussianBlur
:feImage
:feMerge
:feMorphology
:feOffset
:feSpecularLighting
:feTile
:feTurbulence})
(defn inherit-attributes [group-attrs {:keys [attrs] :as node}]
(if (map? node)
(let [attrs (-> (format-styles attrs)
(add-transform (:transform group-attrs)))
attrs (d/deep-merge (select-keys group-attrs inheritable-props) attrs)]
(assoc node :attrs attrs))
node))
(defn map-nodes [mapfn node]
(let [update-content
(fn [content] (cond->> content
(vector? content)
(mapv (partial map-nodes mapfn))))]
(cond-> node
(map? node)
(-> (mapfn)
(d/update-when :content update-content)))))
;; Defaults for some tags per spec https://www.w3.org/TR/SVG11/single-page.html
;; they are basicaly the defaults that can be percents and we need to replace because
;; otherwise won't work as expected in the workspace
(defonce svg-tag-defaults
(let [filter-default {:units :filterUnits
:default "objectBoundingBox"
"objectBoundingBox" {}
"userSpaceOnUse" {:x "-10%" :y "-10%" :width "120%" :height "120%"}}
filter-values (->> filter-tags
(reduce #(merge %1 (hash-map %2 filter-default)) {}))]
(merge {:linearGradient {:units :gradientUnits
:default "objectBoundingBox"
"objectBoundingBox" {}
"userSpaceOnUse" {:x1 "0%" :y1 "0%" :x2 "100%" :y2 "0%"}}
:radialGradient {:units :gradientUnits
:default "objectBoundingBox"
"objectBoundingBox" {}
"userSpaceOnUse" {:cx "50%" :cy "50%" :r "50%"}}
:mask {:units :maskUnits
:default "userSpaceOnUse"
"objectBoundingBox" {}
"userSpaceOnUse" {:x "-10%" :y "-10%" :width "120%" :height "120%"}}}
filter-values)))
(defn fix-default-values
"Gives values to some SVG elements which defaults won't work when imported into the platform"
[svg-data]
(let [add-defaults
(fn [{:keys [tag attrs] :as node}]
(let [prop (get-in svg-tag-defaults [tag :units])
default-units (get-in svg-tag-defaults [tag :default])
units (get attrs prop default-units)
tag-default (get-in svg-tag-defaults [tag units])]
(d/update-when node :attrs #(merge tag-default %))))
fix-node-defaults
(fn [node]
(cond-> node
(contains? svg-tag-defaults (:tag node))
(add-defaults)))]
(->> svg-data (map-nodes fix-node-defaults))))
(defn calculate-ratio
;; sqrt((actual-width)**2 + (actual-height)**2)/sqrt(2).
[width height]
(/ (mth/sqrt (+ (mth/pow width 2)
(mth/pow height 2)))
(mth/sqrt 2)))
(defn fix-percents
"Changes percents to a value according to the size of the svg imported"
[svg-data]
;; https://www.w3.org/TR/SVG11/single-page.html#coords-Units
(let [viewbox {:x (:offset-x svg-data)
:y (:offset-y svg-data)
:width (:width svg-data)
:height (:height svg-data)
:ratio (calculate-ratio (:width svg-data) (:height svg-data))}]
(letfn [(fix-length [prop-length val]
(* (get viewbox prop-length) (/ val 100.)))
(fix-coord [prop-coord prop-length val]
(+ (get viewbox prop-coord)
(fix-length prop-length val)))
(fix-percent-attr [attr-key attr-val]
(let [is-percent? (str/ends-with? attr-val "%")
is-x? #{:x :x1 :x2 :cx}
is-y? #{:y :y1 :y2 :cy}
is-width? #{:width}
is-height? #{:height}
is-other? #{:r :stroke-width}]
(if is-percent?
;; JS parseFloat removes the % symbol
(let [attr-num (d/parse-double attr-val)]
(str (cond
(is-x? attr-key) (fix-coord :x :width attr-num)
(is-y? attr-key) (fix-coord :y :height attr-num)
(is-width? attr-key) (fix-length :width attr-num)
(is-height? attr-key) (fix-length :height attr-num)
(is-other? attr-key) (fix-length :ratio attr-num)
:else (do (.warn js/console "Percent property not converted!" (str attr-key) (str attr-val))
attr-val))))
attr-val)))
(fix-percent-attrs [attrs]
(d/mapm fix-percent-attr attrs))
(fix-percent-values [node]
(update node :attrs fix-percent-attrs))]
(->> svg-data (map-nodes fix-percent-values)))))

View file

@ -15,6 +15,7 @@ const plugins = [
{ "convertShapeToPath" : false },
{ "convertEllipseToCircle" : false },
{ "moveElemsAttrsToGroup" : false },
{ "moveGroupAttrsToElems" : false },
{ "collapseGroups" : false },
{
"convertPathData" : {
@ -28,9 +29,10 @@ const plugins = [
{ "mergePaths" : false },
{ "sortDefsChildren" : false },
{ "removeDimensions" : true },
{ "removeStyleElement" : true },
{ "removeStyleElement" : false },
{ "removeScriptElement" : true },
{ "removeOffCanvasPaths" : false },
{ "cleanupNumericValues": true}
];

View file

@ -8,7 +8,7 @@ exports.description = 'rounds numeric values to the fixed precision, removes def
exports.params = {
floatPrecision: 3,
leadingZero: true,
leadingZero: false,
defaultPx: true,
convertToPx: true
};
@ -20,7 +20,8 @@ var regNumericValues = /^([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)(px|pt|pc|mm|cm|m|in|f
mm: 96/25.4,
in: 96,
pt: 4/3,
pc: 16
pc: 16,
em: 16
};
/**
@ -61,12 +62,8 @@ exports.fn = function(item, params) {
// convert absolute values to pixels
if (params.convertToPx && units && (units in absoluteLengths)) {
var pxNum = +(absoluteLengths[units] * match[1]).toFixed(floatPrecision);
if (String(pxNum).length < match[0].length) {
num = pxNum;
units = 'px';
}
num = +(absoluteLengths[units] * match[1]).toFixed(floatPrecision);
units = 'px';
}
// and remove leading zero