mirror of
https://github.com/penpot/penpot.git
synced 2025-12-05 19:08:09 -06:00
WIP
This commit is contained in:
@@ -24,6 +24,8 @@
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.segment :as path.segm]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.blur :as ctsb]
|
||||
[app.common.types.shape.shadow :as ctss]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
@@ -82,6 +84,182 @@
|
||||
(declare create-svg-children)
|
||||
(declare parse-svg-element)
|
||||
|
||||
(defn- process-gradient-stops
|
||||
"Processes gradient stops to extract stop-color and stop-opacity from style attributes
|
||||
and convert them to direct attributes. This ensures stops with style='stop-color:#...;stop-opacity:1'
|
||||
are properly converted to stop-color and stop-opacity attributes."
|
||||
[stops]
|
||||
(mapv (fn [stop]
|
||||
(let [stop-attrs (:attrs stop)
|
||||
stop-style (get stop-attrs :style)
|
||||
;; Parse style if it's a string - use the same logic as format-styles
|
||||
parsed-style (when (and (string? stop-style) (seq stop-style))
|
||||
(reduce (fn [res item]
|
||||
(let [[k v] (-> (str/trim item) (str/split ":" 2))
|
||||
k (keyword k)]
|
||||
(assoc res k v)))
|
||||
{}
|
||||
(str/split stop-style ";")))
|
||||
;; Extract stop-color and stop-opacity from style
|
||||
style-stop-color (when parsed-style (:stop-color parsed-style))
|
||||
style-stop-opacity (when parsed-style (:stop-opacity parsed-style))
|
||||
;; Merge: use direct attributes first, then style values as fallback
|
||||
final-attrs (cond-> stop-attrs
|
||||
(and style-stop-color (not (contains? stop-attrs :stop-color)))
|
||||
(assoc :stop-color style-stop-color)
|
||||
|
||||
(and style-stop-opacity (not (contains? stop-attrs :stop-opacity)))
|
||||
(assoc :stop-opacity style-stop-opacity)
|
||||
|
||||
;; Remove style attribute if we've extracted its values
|
||||
(or style-stop-color style-stop-opacity)
|
||||
(dissoc :style))]
|
||||
#?(:cljs (when (or style-stop-color style-stop-opacity)
|
||||
(js/console.log "[process-gradient-stops] Extracted from style - stop-color:" style-stop-color "stop-opacity:" style-stop-opacity
|
||||
"final attrs:" (clj->js final-attrs)))
|
||||
:clj nil)
|
||||
(assoc stop :attrs final-attrs)))
|
||||
stops))
|
||||
|
||||
(defn- resolve-gradient-href
|
||||
"Resolves xlink:href references in gradients by merging the referenced gradient's
|
||||
stops and attributes with the referencing gradient. This ensures gradients that
|
||||
reference other gradients (like linearGradient3550 referencing linearGradient3536)
|
||||
inherit the stops from the base gradient.
|
||||
|
||||
According to SVG spec, when a gradient has xlink:href:
|
||||
- It inherits all attributes from the referenced gradient
|
||||
- It inherits all stops from the referenced gradient
|
||||
- The referencing gradient's attributes override the base ones
|
||||
- If the referencing gradient has stops, they replace the base stops
|
||||
|
||||
Returns the defs map with all gradient href references resolved."
|
||||
[defs]
|
||||
#?(:cljs (js/console.log "[resolve-gradient-href] Starting resolution for" (count defs) "defs")
|
||||
:clj nil)
|
||||
(letfn [(resolve-gradient [gradient-id gradient-node defs visited]
|
||||
(if (contains? visited gradient-id)
|
||||
(do
|
||||
#?(:cljs (js/console.warn "[resolve-gradient] Circular reference detected for" gradient-id)
|
||||
:clj nil)
|
||||
gradient-node) ;; Avoid circular references
|
||||
(let [attrs (:attrs gradient-node)
|
||||
href-id (or (:href attrs) (:xlink:href attrs))
|
||||
href-id (when (and (string? href-id) (pos? (count href-id)))
|
||||
(subs href-id 1)) ;; Remove leading #
|
||||
|
||||
base-gradient (when (and href-id (contains? defs href-id))
|
||||
(get defs href-id))
|
||||
|
||||
_ #?(:cljs (when href-id
|
||||
(js/console.log "[resolve-gradient] Looking for base" href-id
|
||||
"in defs:" (clj->js (keys defs))
|
||||
"found?:" (contains? defs href-id)
|
||||
"base-gradient:" (some? base-gradient)))
|
||||
:clj nil)
|
||||
|
||||
resolved-base (when base-gradient
|
||||
(do
|
||||
#?(:cljs (js/console.log "[resolve-gradient] Resolving" gradient-id "->" href-id
|
||||
"base-gradient tag:" (:tag base-gradient)
|
||||
"base-gradient attrs:" (clj->js (keys (:attrs base-gradient)))
|
||||
"base-gradient content:" (count (:content base-gradient)))
|
||||
:clj nil)
|
||||
(resolve-gradient href-id base-gradient defs (conj visited gradient-id))))]
|
||||
|
||||
(if resolved-base
|
||||
;; Merge: base gradient attributes + referencing gradient attributes
|
||||
;; Use referencing gradient's stops if present, otherwise use base stops
|
||||
(let [base-attrs (:attrs resolved-base)
|
||||
ref-attrs (:attrs gradient-node)
|
||||
|
||||
;; Start with base attributes (without id), then merge with ref attributes
|
||||
;; This ensures ref attributes override base ones
|
||||
base-attrs-clean (dissoc base-attrs :id)
|
||||
ref-attrs-clean (dissoc ref-attrs :href :xlink:href :id)
|
||||
|
||||
;; Special handling for gradientTransform: if both have it, combine them
|
||||
base-transform (get base-attrs :gradientTransform)
|
||||
ref-transform (get ref-attrs :gradientTransform)
|
||||
combined-transform (cond
|
||||
(and base-transform ref-transform)
|
||||
(str base-transform " " ref-transform) ;; Apply base first, then ref
|
||||
base-transform base-transform
|
||||
ref-transform ref-transform
|
||||
:else nil)
|
||||
|
||||
;; Merge attributes: base first, then ref (ref overrides)
|
||||
merged-attrs (-> (d/deep-merge base-attrs-clean ref-attrs-clean)
|
||||
(cond-> combined-transform
|
||||
(assoc :gradientTransform combined-transform)))
|
||||
|
||||
;; If referencing gradient has content (stops), use it; otherwise use base content
|
||||
final-content (if (seq (:content gradient-node))
|
||||
(:content gradient-node)
|
||||
(:content resolved-base))
|
||||
|
||||
;; Process stops to extract stop-color and stop-opacity from style attributes
|
||||
processed-content (process-gradient-stops final-content)
|
||||
|
||||
result {:tag (:tag gradient-node)
|
||||
:attrs (assoc merged-attrs :id gradient-id)
|
||||
:content processed-content}]
|
||||
#?(:cljs (do
|
||||
(js/console.log "[resolve-gradient] Merged gradient" gradient-id
|
||||
"attrs:" (clj->js merged-attrs)
|
||||
"x1:" (get merged-attrs :x1)
|
||||
"y1:" (get merged-attrs :y1)
|
||||
"x2:" (get merged-attrs :x2)
|
||||
"y2:" (get merged-attrs :y2)
|
||||
"gradientUnits:" (get merged-attrs :gradientUnits)
|
||||
"gradientTransform:" (get merged-attrs :gradientTransform)
|
||||
"content:" (count final-content) "stops")
|
||||
;; Log each stop details
|
||||
(doseq [[idx stop] (d/enumerate final-content)]
|
||||
(let [stop-attrs (:attrs stop)
|
||||
stop-style (get stop-attrs :style)
|
||||
stop-color-val (get stop-attrs :stop-color)
|
||||
stop-opacity-val (get stop-attrs :stop-opacity)]
|
||||
(js/console.log (str "[resolve-gradient] Stop " idx ":")
|
||||
"offset:" (get stop-attrs :offset)
|
||||
"stop-color attr:" stop-color-val
|
||||
"stop-color type:" (type stop-color-val)
|
||||
"stop-opacity attr:" stop-opacity-val
|
||||
"stop-opacity type:" (type stop-opacity-val)
|
||||
"stop-opacity as number:" (when stop-opacity-val (d/parse-double stop-opacity-val 1))
|
||||
"style:" stop-style
|
||||
"parsed style:" (when (string? stop-style)
|
||||
(let [parsed (reduce (fn [res item]
|
||||
(let [[k v] (-> (str/trim item) (str/split ":" 2))
|
||||
k (keyword k)]
|
||||
(assoc res k v)))
|
||||
{}
|
||||
(str/split stop-style ";"))]
|
||||
parsed))
|
||||
"full attrs:" (clj->js stop-attrs)))))
|
||||
:clj nil)
|
||||
result)
|
||||
(do
|
||||
#?(:cljs (js/console.log "[resolve-gradient] No base gradient found for" gradient-id (when href-id (str "href=" href-id)))
|
||||
:clj nil)
|
||||
;; Process stops even for gradients without references to extract style attributes
|
||||
(let [processed-content (process-gradient-stops (:content gradient-node))]
|
||||
(assoc gradient-node :content processed-content)))))))]
|
||||
(let [gradient-tags #{:linearGradient :radialGradient}
|
||||
result (reduce-kv
|
||||
(fn [acc id node]
|
||||
(if (contains? gradient-tags (:tag node))
|
||||
(do
|
||||
#?(:cljs (js/console.log "[resolve-gradient-href] Processing gradient" id)
|
||||
:clj nil)
|
||||
(assoc acc id (resolve-gradient id node defs #{})))
|
||||
(assoc acc id node)))
|
||||
{}
|
||||
defs)]
|
||||
#?(:cljs (js/console.log "[resolve-gradient-href] Resolution complete. Processed" (count result) "defs")
|
||||
:clj nil)
|
||||
result)))
|
||||
|
||||
(defn create-svg-shapes
|
||||
([svg-data pos objects frame-id parent-id selected center?]
|
||||
(create-svg-shapes (uuid/next) svg-data pos objects frame-id parent-id selected center?))
|
||||
@@ -112,6 +290,9 @@
|
||||
(csvg/fix-percents)
|
||||
(csvg/extract-defs))
|
||||
|
||||
;; Resolve gradient href references in all defs before processing shapes
|
||||
def-nodes (resolve-gradient-href def-nodes)
|
||||
|
||||
;; In penpot groups have the size of their children. To
|
||||
;; respect the imported svg size and empty space let's create
|
||||
;; a transparent shape as background to respect the imported
|
||||
@@ -142,12 +323,43 @@
|
||||
(reduce (partial create-svg-children objects selected frame-id root-id svg-data)
|
||||
[unames []]
|
||||
(d/enumerate (->> (:content svg-data)
|
||||
(mapv #(csvg/inherit-attributes root-attrs %)))))]
|
||||
(mapv #(csvg/inherit-attributes root-attrs %)))))
|
||||
|
||||
[root-shape children])))
|
||||
;; Collect all defs from children and merge into root shape
|
||||
all-defs-from-children (reduce (fn [acc child]
|
||||
(if-let [child-defs (:svg-defs child)]
|
||||
(do
|
||||
#?(:cljs (js/console.log "[create-svg-shapes] Found defs in child" (:name child) "defs:" (keys child-defs))
|
||||
:clj nil)
|
||||
(merge acc child-defs))
|
||||
acc))
|
||||
{}
|
||||
children)
|
||||
|
||||
_ #?(:cljs (js/console.log "[create-svg-shapes] Root shape before defs merge:"
|
||||
"root-shape name:" (:name root-shape)
|
||||
"root-shape type:" (:type root-shape)
|
||||
"def-nodes count:" (count def-nodes)
|
||||
"def-nodes keys:" (keys def-nodes)
|
||||
"all-defs-from-children count:" (count all-defs-from-children)
|
||||
"all-defs-from-children keys:" (keys all-defs-from-children)
|
||||
"children count:" (count children))
|
||||
:clj nil)
|
||||
|
||||
;; Merge defs from svg-data and children into root shape
|
||||
root-shape-with-defs (assoc root-shape :svg-defs (merge def-nodes all-defs-from-children))
|
||||
|
||||
_ #?(:cljs (js/console.log "[create-svg-shapes] Root shape after defs merge:"
|
||||
"root-shape-with-defs name:" (:name root-shape-with-defs)
|
||||
"root-shape-with-defs type:" (:type root-shape-with-defs)
|
||||
"root-shape-with-defs svg-defs count:" (when (:svg-defs root-shape-with-defs) (count (:svg-defs root-shape-with-defs)))
|
||||
"root-shape-with-defs svg-defs keys:" (when (:svg-defs root-shape-with-defs) (keys (:svg-defs root-shape-with-defs))))
|
||||
:clj nil)]
|
||||
|
||||
[root-shape-with-defs children])))
|
||||
|
||||
(defn create-raw-svg
|
||||
[name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}]
|
||||
[name frame-id {:keys [x y width height offset-x offset-y defs] :as svg-data} {:keys [attrs] :as data}]
|
||||
(let [props (csvg/attrs->props attrs)
|
||||
vbox (grc/make-rect offset-x offset-y width height)]
|
||||
(cts/setup-shape
|
||||
@@ -160,10 +372,11 @@
|
||||
:y y
|
||||
:content data
|
||||
:svg-attrs props
|
||||
:svg-viewbox vbox})))
|
||||
:svg-viewbox vbox
|
||||
:svg-defs defs})))
|
||||
|
||||
(defn create-svg-root
|
||||
[id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}]
|
||||
[id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs defs] :as svg-data}]
|
||||
(let [props (-> (dissoc attrs :viewBox :view-box :xmlns)
|
||||
(d/without-keys csvg/inheritable-props)
|
||||
(csvg/attrs->props))]
|
||||
@@ -177,7 +390,8 @@
|
||||
:height height
|
||||
:x (+ x offset-x)
|
||||
:y (+ y offset-y)
|
||||
:svg-attrs props})))
|
||||
:svg-attrs props
|
||||
:svg-defs defs})))
|
||||
|
||||
(defn create-svg-children
|
||||
[objects selected frame-id parent-id svg-data [unames children] [_index svg-element]]
|
||||
@@ -198,7 +412,7 @@
|
||||
|
||||
|
||||
(defn create-group
|
||||
[name frame-id {:keys [x y width height offset-x offset-y] :as svg-data} {:keys [attrs]}]
|
||||
[name frame-id {:keys [x y width height offset-x offset-y defs] :as svg-data} {:keys [attrs]}]
|
||||
(let [transform (csvg/parse-transform (:transform attrs))
|
||||
attrs (-> attrs
|
||||
(d/without-keys csvg/inheritable-props)
|
||||
@@ -214,7 +428,8 @@
|
||||
:height height
|
||||
:svg-transform transform
|
||||
:svg-attrs attrs
|
||||
:svg-viewbox vbox})))
|
||||
:svg-viewbox vbox
|
||||
:svg-defs defs})))
|
||||
|
||||
(defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}]
|
||||
(when (and (contains? attrs :d) (seq (:d attrs)))
|
||||
@@ -392,6 +607,17 @@
|
||||
color-attr (if (= color-attr "currentColor") clr/black color-attr)
|
||||
color-style (str/trim (dm/get-in shape [:svg-attrs :style :fill]))
|
||||
color-style (if (= color-style "currentColor") clr/black color-style)]
|
||||
#?(:cljs (when (or (str/includes? (str color-attr) "url(")
|
||||
(str/includes? (str color-style) "url("))
|
||||
(js/console.log "[setup-fill] Shape" (:name shape) "has gradient fill"
|
||||
"fill-attr:" color-attr
|
||||
"fill-style:" color-style
|
||||
"svg-defs present?:" (some? (:svg-defs shape))
|
||||
"svg-defs type:" (type (:svg-defs shape))
|
||||
"svg-defs count:" (when (:svg-defs shape) (count (:svg-defs shape)))
|
||||
"svg-defs keys:" (when (:svg-defs shape) (keys (:svg-defs shape)))
|
||||
"all shape keys:" (keys shape)))
|
||||
:clj nil)
|
||||
(cond-> shape
|
||||
;; Color present as attribute
|
||||
(clr/color-string? color-attr)
|
||||
@@ -503,6 +729,86 @@
|
||||
(-> (update-in [:svg-attrs :style] dissoc :mixBlendMode)
|
||||
(assoc :blend-mode (-> (dm/get-in shape [:svg-attrs :style :mixBlendMode]) assert-valid-blend-mode)))))
|
||||
|
||||
(defn- parse-svg-filter-to-native
|
||||
"Attempts to convert SVG filters to native Penpot filters (blur, shadow).
|
||||
This is a basic implementation that can be extended for more complex cases.
|
||||
|
||||
Currently supports:
|
||||
- feGaussianBlur -> blur (layer-blur)
|
||||
- Simple drop shadow filters -> shadow (drop-shadow)
|
||||
|
||||
Returns the shape with converted filters applied, or the original shape if
|
||||
conversion is not possible or not supported."
|
||||
[shape]
|
||||
(let [filter-attr (or (dm/get-in shape [:svg-attrs :filter])
|
||||
(dm/get-in shape [:svg-attrs :style :filter]))
|
||||
svg-defs (:svg-defs shape)]
|
||||
|
||||
(if (and filter-attr svg-defs)
|
||||
(let [filter-ids (csvg/extract-ids filter-attr)
|
||||
filter-def (some #(get svg-defs %) filter-ids)]
|
||||
|
||||
(if filter-def
|
||||
(let [filter-content (:content filter-def)
|
||||
;; Try to find feGaussianBlur for blur conversion
|
||||
gaussian-blur (some #(when (= :feGaussianBlur (:tag %)) %) filter-content)
|
||||
|
||||
;; Try to find drop shadow elements
|
||||
drop-shadow-elements (filter (fn [elem]
|
||||
(contains? #{:feOffset :feGaussianBlur :feColorMatrix} (:tag elem)))
|
||||
filter-content)]
|
||||
|
||||
(let [offset-elem (some #(when (= :feOffset (:tag %)) %) filter-content)
|
||||
;; Only create shadow if there's an feOffset (drop shadow requires offset)
|
||||
;; If there's only feGaussianBlur without feOffset, it's just a blur
|
||||
shape-with-blur
|
||||
(if (and gaussian-blur (not (some? (:blur shape))))
|
||||
(assoc shape :blur {:id (uuid/next)
|
||||
:type :layer-blur
|
||||
:value (-> (dm/get-in gaussian-blur [:attrs :stdDeviation])
|
||||
(d/parse-double 0)) ;; For layer-blur, value = stdDeviation directly
|
||||
:hidden false})
|
||||
shape)
|
||||
|
||||
shape-with-shadow
|
||||
(if (and offset-elem
|
||||
(seq drop-shadow-elements)
|
||||
(not (seq (:shadow shape-with-blur))))
|
||||
(let [blur-elem (some #(when (= :feGaussianBlur (:tag %)) %) drop-shadow-elements)
|
||||
|
||||
dx (-> (dm/get-in offset-elem [:attrs :dx])
|
||||
(d/parse-double 0))
|
||||
dy (-> (dm/get-in offset-elem [:attrs :dy])
|
||||
(d/parse-double 0))
|
||||
blur-value (if blur-elem
|
||||
(-> (dm/get-in blur-elem [:attrs :stdDeviation])
|
||||
(d/parse-double 0)
|
||||
(* 2))
|
||||
0)
|
||||
;; Default color - TODO: parse color from feColorMatrix
|
||||
shadow-color "#000000"]
|
||||
(assoc shape-with-blur :shadow [{:id (uuid/next)
|
||||
:style :drop-shadow
|
||||
:offset-x dx
|
||||
:offset-y dy
|
||||
:blur blur-value
|
||||
:spread 0
|
||||
:hidden false
|
||||
:color {:color shadow-color :opacity 1}}]))
|
||||
shape-with-blur)
|
||||
|
||||
;; Remove filter attribute if we successfully converted it
|
||||
converted? (or (some? (:blur shape-with-shadow))
|
||||
(seq (:shadow shape-with-shadow)))
|
||||
final-shape (if converted?
|
||||
(-> shape-with-shadow
|
||||
(update :svg-attrs dissoc :filter)
|
||||
(update-in [:svg-attrs :style] #(when % (dissoc % :filter))))
|
||||
shape-with-shadow)]
|
||||
final-shape))
|
||||
shape))
|
||||
shape)))
|
||||
|
||||
(defn setup-other [shape]
|
||||
(cond-> shape
|
||||
(= (dm/get-in shape [:svg-attrs :display]) "none")
|
||||
@@ -534,7 +840,24 @@
|
||||
(let [name (or (:id attrs) (tag->name tag))
|
||||
att-refs (csvg/find-attr-references attrs)
|
||||
defs (get svg-data :defs)
|
||||
references (csvg/find-def-references defs att-refs)
|
||||
;; Filter out false positive references:
|
||||
;; 1. Colors in style attributes (hex colors like #f9dd67)
|
||||
;; 2. Style fragments that contain CSS keywords (like stop-opacity)
|
||||
;; 3. References that don't exist in defs
|
||||
is-style-fragment? (fn [ref-id]
|
||||
(or (clr/hex-color-string? (str "#" ref-id))
|
||||
(str/includes? ref-id ";") ;; Contains CSS separator
|
||||
(str/includes? ref-id "stop-opacity") ;; CSS keyword
|
||||
(str/includes? ref-id "stop-color"))) ;; CSS keyword
|
||||
valid-refs (->> att-refs
|
||||
(remove is-style-fragment?) ;; Filter style fragments and hex colors
|
||||
(filter #(contains? defs %))) ;; Only existing defs
|
||||
all-refs (csvg/find-def-references defs valid-refs)
|
||||
;; Filter the final result to ensure all references are valid defs
|
||||
;; This prevents false positives from style attributes in gradient stops
|
||||
references (->> all-refs
|
||||
(remove is-style-fragment?) ;; Filter style fragments and hex colors
|
||||
(filter #(contains? defs %))) ;; Only existing defs
|
||||
|
||||
href-id (or (:href attrs) (:xlink:href attrs) " ")
|
||||
href-id (if (and (string? href-id)
|
||||
@@ -574,19 +897,65 @@
|
||||
#_other (create-raw-svg name frame-id svg-data element))]
|
||||
|
||||
(when (some? shape)
|
||||
[(-> shape
|
||||
(assoc :svg-defs (select-keys defs references))
|
||||
(setup-fill)
|
||||
(setup-stroke)
|
||||
(setup-opacity)
|
||||
(setup-other)
|
||||
(update :svg-attrs (fn [attrs]
|
||||
(if (empty? (:style attrs))
|
||||
(dissoc attrs :style)
|
||||
attrs)))
|
||||
(cond-> ^boolean hidden
|
||||
(assoc :hidden true)))
|
||||
(let [selected-defs (select-keys defs references)]
|
||||
#?(:cljs (when (seq selected-defs)
|
||||
(js/console.log "[parse-svg-element] Shape" name "has" (count selected-defs) "defs:" (clj->js (keys selected-defs))
|
||||
"references:" (clj->js references))
|
||||
(doseq [[def-id def-node] selected-defs]
|
||||
(when (contains? #{:linearGradient :radialGradient} (:tag def-node))
|
||||
(let [def-attrs (:attrs def-node)
|
||||
stops (:content def-node)]
|
||||
(js/console.log "[parse-svg-element] Gradient def" def-id
|
||||
"x1:" (get def-attrs :x1)
|
||||
"y1:" (get def-attrs :y1)
|
||||
"x2:" (get def-attrs :x2)
|
||||
"y2:" (get def-attrs :y2)
|
||||
"gradientUnits:" (get def-attrs :gradientUnits)
|
||||
"gradientTransform:" (get def-attrs :gradientTransform)
|
||||
"content:" (count stops) "stops")
|
||||
;; Log each stop in detail
|
||||
(doseq [[idx stop] (d/enumerate stops)]
|
||||
(let [stop-attrs (:attrs stop)
|
||||
stop-style (get stop-attrs :style)
|
||||
parsed-style (when (string? stop-style)
|
||||
(reduce (fn [res item]
|
||||
(let [[k v] (-> (str/trim item) (str/split ":" 2))
|
||||
k (keyword k)]
|
||||
(assoc res k v)))
|
||||
{}
|
||||
(str/split stop-style ";")))]
|
||||
(js/console.log (str "[parse-svg-element] Gradient def " def-id " - Stop " idx ":")
|
||||
"offset:" (get stop-attrs :offset)
|
||||
"stop-color attr:" (get stop-attrs :stop-color)
|
||||
"stop-opacity attr:" (get stop-attrs :stop-opacity)
|
||||
"style string:" stop-style
|
||||
"parsed style:" (clj->js parsed-style)
|
||||
"stop-color from style:" (when parsed-style (:stop-color parsed-style))
|
||||
"stop-opacity from style:" (when parsed-style (:stop-opacity parsed-style))
|
||||
"full stop attrs:" (clj->js stop-attrs))))))))
|
||||
:clj nil)
|
||||
[(let [shape-with-defs (assoc shape :svg-defs (select-keys defs references))]
|
||||
#?(:cljs (js/console.log "[parse-svg-element] After assoc defs, shape has" (count (:svg-defs shape-with-defs)) "defs:" (keys (:svg-defs shape-with-defs)))
|
||||
:clj nil)
|
||||
(-> shape-with-defs
|
||||
(#?(:cljs (fn [shape]
|
||||
(js/console.log "[parse-svg-element] Before setup-fill, shape has svg-defs:" (some? (:svg-defs shape))
|
||||
"count:" (when (:svg-defs shape) (count (:svg-defs shape)))
|
||||
"keys:" (when (:svg-defs shape) (keys (:svg-defs shape))))
|
||||
shape)
|
||||
:clj identity))
|
||||
(setup-fill)
|
||||
(setup-stroke)
|
||||
(setup-opacity)
|
||||
(parse-svg-filter-to-native)
|
||||
(setup-other)
|
||||
(update :svg-attrs (fn [attrs]
|
||||
(if (empty? (:style attrs))
|
||||
(dissoc attrs :style)
|
||||
attrs)))
|
||||
(cond-> ^boolean hidden
|
||||
(assoc :hidden true))))
|
||||
|
||||
(cond->> (:content element)
|
||||
(contains? csvg/parent-tags tag)
|
||||
(mapv (partial csvg/inherit-attributes attrs)))])))))
|
||||
(mapv (partial csvg/inherit-attributes attrs)))]))))))
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
[app.common.geom.shapes.bounds :as gsb]
|
||||
[app.common.json :as json]
|
||||
[app.common.svg :as csvg]
|
||||
[app.util.object :as obj]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn add-matrix [attrs transform-key transform-matrix]
|
||||
@@ -86,7 +87,34 @@
|
||||
[mf/Fragment #js {}])
|
||||
|
||||
props
|
||||
(json/->js attrs :key-fn name)]
|
||||
(json/->js attrs :key-fn name)
|
||||
|
||||
_ (when (or (= tag :stop) (contains? csvg/gradient-tags tag))
|
||||
(js/console.log "[svg-node] Rendering node:"
|
||||
"tag:" tag
|
||||
"content count:" (count content)
|
||||
"has stops?" (some #(= :stop (:tag %)) content)))
|
||||
|
||||
_ (when (= tag :stop)
|
||||
(let [stop-color-attr (get attrs :stop-color)
|
||||
stop-opacity-attr (get attrs :stop-opacity)
|
||||
stop-opacity-type (type stop-opacity-attr)
|
||||
stop-opacity-js (obj/get props "stopOpacity")
|
||||
stop-opacity-js-type (type stop-opacity-js)]
|
||||
(js/console.log "[svg-node] Rendering stop - DETAILED:"
|
||||
"tag:" tag
|
||||
"attrs before csvg/attrs->props:" (clj->js attrs)
|
||||
"stop-color attr:" stop-color-attr
|
||||
"stop-opacity attr:" stop-opacity-attr
|
||||
"stop-opacity attr type:" stop-opacity-type
|
||||
"stop-opacity as number:" (when stop-opacity-attr (d/parse-double stop-opacity-attr 1))
|
||||
"props after json/->js:" props
|
||||
"stopColor prop:" (obj/get props "stopColor")
|
||||
"stopOpacity prop:" stop-opacity-js
|
||||
"stopOpacity prop type:" stop-opacity-js-type
|
||||
"offset prop:" (obj/get props "offset")
|
||||
"all props keys:" (js/Object.keys props)
|
||||
"all props:" props)))]
|
||||
|
||||
[:> (name tag) props
|
||||
[:> wrapper wrapper-props
|
||||
@@ -113,6 +141,34 @@
|
||||
[{:keys [shape render-id]}]
|
||||
(let [defs (:svg-defs shape)
|
||||
|
||||
_ (js/console.log "[svg-defs] Rendering defs for shape - DETAILED:"
|
||||
"shape name:" (:name shape)
|
||||
"shape type:" (:type shape)
|
||||
"shape id:" (:id shape)
|
||||
"shape keys:" (keys shape)
|
||||
"has-svg-defs?:" (contains? shape :svg-defs)
|
||||
"svg-defs value:" (:svg-defs shape)
|
||||
"svg-defs type:" (when (:svg-defs shape) (type (:svg-defs shape)))
|
||||
"svg-defs count:" (when defs (count defs))
|
||||
"svg-defs keys:" (when defs (keys defs))
|
||||
"svg-defs empty?:" (empty? defs)
|
||||
"full shape:" (clj->js shape)
|
||||
"defs details:" (when defs (clj->js (mapv (fn [[k v]]
|
||||
{:key k
|
||||
:tag (:tag v)
|
||||
:attrs-keys (keys (:attrs v))
|
||||
:content-count (count (:content v))
|
||||
:content (clj->js (:content v))
|
||||
:has-stops (some #(= :stop (:tag %)) (:content v))
|
||||
:stops (when-let [stops (seq (filter #(= :stop (:tag %)) (:content v)))]
|
||||
(clj->js (mapv (fn [stop]
|
||||
{:tag (:tag stop)
|
||||
:attrs (:attrs stop)
|
||||
:stop-color (get-in stop [:attrs :stop-color])
|
||||
:stop-opacity (get-in stop [:attrs :stop-opacity])})
|
||||
stops)))})
|
||||
defs))))
|
||||
|
||||
transform (mf/with-memo [shape]
|
||||
(if (= :svg-raw (:type shape))
|
||||
(gmt/matrix)
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.svg :as csvg]
|
||||
[app.common.types.color :as clr]
|
||||
[clojure.string :as str]))
|
||||
@@ -81,9 +83,45 @@
|
||||
width (max 0.01 (dm/get-prop rect :width))
|
||||
height (max 0.01 (dm/get-prop rect :height))
|
||||
origin-x (or (dm/get-prop rect :x) (dm/get-prop rect :x1) 0)
|
||||
origin-y (or (dm/get-prop rect :y) (dm/get-prop rect :y1) 0)]
|
||||
(gpt/point (/ (- (dm/get-prop pt :x) origin-x) width)
|
||||
(/ (- (dm/get-prop pt :y) origin-y) height)))
|
||||
origin-y (or (dm/get-prop rect :y) (dm/get-prop rect :y1) 0)
|
||||
viewbox (:svg-viewbox shape)]
|
||||
(js/console.log "[normalize-point] Normalizing point with userSpaceOnUse:"
|
||||
"pt:" (clj->js pt)
|
||||
"shape selrect:" (clj->js rect)
|
||||
"shape svg-viewbox:" (clj->js viewbox)
|
||||
"shape type:" (dm/get-prop shape :type)
|
||||
"shape transform:" (dm/get-prop shape :transform))
|
||||
(let [;; For userSpaceOnUse, coordinates are in SVG user space
|
||||
;; We need to transform them to shape space before normalizing
|
||||
transformed-pt (if viewbox
|
||||
(let [{svg-x :x svg-y :y svg-width :width svg-height :height} viewbox
|
||||
scale-x (/ width svg-width)
|
||||
scale-y (/ height svg-height)
|
||||
;; Transform from viewBox space to selrect space
|
||||
transformed-x (+ origin-x (* (- (dm/get-prop pt :x) svg-x) scale-x))
|
||||
transformed-y (+ origin-y (* (- (dm/get-prop pt :y) svg-y) scale-y))
|
||||
transformed (gpt/point transformed-x transformed-y)]
|
||||
(js/console.log "[normalize-point] After viewBox transform:"
|
||||
"svg-x:" svg-x "svg-y:" svg-y
|
||||
"svg-width:" svg-width "svg-height:" svg-height
|
||||
"scale-x:" scale-x "scale-y:" scale-y
|
||||
"transformed:" (clj->js transformed))
|
||||
;; Apply shape transform if needed
|
||||
(if-let [transform-matrix (and (or (= :path (dm/get-prop shape :type))
|
||||
(= :group (dm/get-prop shape :type)))
|
||||
(gsh/transform-matrix shape))]
|
||||
(let [final-pt (gpt/transform transformed transform-matrix)]
|
||||
(js/console.log "[normalize-point] After shape transform:"
|
||||
"final:" (clj->js final-pt))
|
||||
final-pt)
|
||||
transformed))
|
||||
pt)
|
||||
normalized-x (/ (- (dm/get-prop transformed-pt :x) origin-x) width)
|
||||
normalized-y (/ (- (dm/get-prop transformed-pt :y) origin-y) height)]
|
||||
(js/console.log "[normalize-point] Final normalized point:"
|
||||
"normalized-x:" normalized-x
|
||||
"normalized-y:" normalized-y)
|
||||
(gpt/point normalized-x normalized-y)))
|
||||
pt))
|
||||
|
||||
(defn- normalize-attrs
|
||||
@@ -143,6 +181,15 @@
|
||||
(defn- resolve-gradient-node
|
||||
[shape gradient-id]
|
||||
(let [defs (dm/get-prop shape :svg-defs)]
|
||||
(js/console.log "[resolve-gradient-node] Resolving gradient:"
|
||||
"gradient-id:" gradient-id
|
||||
"shape name:" (dm/get-prop shape :name)
|
||||
"shape type:" (dm/get-prop shape :type)
|
||||
"shape id:" (dm/get-prop shape :id)
|
||||
"has svg-defs?:" (some? defs)
|
||||
"svg-defs count:" (when defs (count defs))
|
||||
"svg-defs keys:" (when defs (keys defs))
|
||||
"svg-defs:" (when defs defs))
|
||||
(when (and defs gradient-id)
|
||||
(let [chain (loop [gid gradient-id
|
||||
seen #{}
|
||||
@@ -204,6 +251,18 @@
|
||||
(get style :stop-opacity)
|
||||
(get style :stopOpacity))
|
||||
offset (or (get attrs :offset) "0")]
|
||||
(js/console.log "[parse-gradient-stop] Parsing stop:"
|
||||
"stop-node attrs:" (clj->js attrs)
|
||||
"normalized attrs:" (clj->js (normalize-attrs (:attrs stop-node)))
|
||||
"style:" style
|
||||
"stop-color attr:" (get attrs :stop-color)
|
||||
"stop-opacity attr:" (get attrs :stop-opacity)
|
||||
"stop-opacity from style:" (get style :stop-opacity)
|
||||
"stop-opacity from style (stopOpacity):" (get style :stopOpacity)
|
||||
"final opacity:" (some-> opacity parse-opacity)
|
||||
"color-value:" color-value
|
||||
"color:" color
|
||||
"offset:" offset)
|
||||
(when color
|
||||
(d/without-nils {:color color
|
||||
:opacity (some-> opacity parse-opacity)
|
||||
@@ -230,11 +289,24 @@
|
||||
(when (= (keyword (:tag node)) :stop)
|
||||
(parse-gradient-stop node))))
|
||||
vec)]
|
||||
(js/console.log "[build-linear-gradient] Building gradient:"
|
||||
"units:" units
|
||||
"x1:" x1 "y1:" y1 "x2:" x2 "y2:" y2
|
||||
"transform:" transform
|
||||
"shape selrect:" (shape->selrect shape)
|
||||
"stops count:" (count stops))
|
||||
(when (seq stops)
|
||||
(let [points (apply-gradient-transform [(gpt/point x1 y1)
|
||||
(gpt/point x2 y2)]
|
||||
transform)
|
||||
[start end] (map #(normalize-point % units shape) points)]
|
||||
[start end] (map #(normalize-point % units shape) points)
|
||||
selrect (shape->selrect shape)]
|
||||
(js/console.log "[build-linear-gradient] After normalization:"
|
||||
"original points:" (clj->js points)
|
||||
"normalized start:" (clj->js start)
|
||||
"normalized end:" (clj->js end)
|
||||
"selrect:" (clj->js selrect)
|
||||
"units:" units)
|
||||
{:type :linear
|
||||
:start-x (dm/get-prop start :x)
|
||||
:start-y (dm/get-prop start :y)
|
||||
@@ -278,14 +350,30 @@
|
||||
(string? trimmed) trimmed
|
||||
(some? trimmed) (str trimmed)
|
||||
:else nil)]
|
||||
(js/console.log "[svg-gradient->fill] Converting gradient fill:"
|
||||
"value:" value
|
||||
"trimmed:" trimmed
|
||||
"fill-str:" fill-str
|
||||
"shape name:" (dm/get-prop shape :name)
|
||||
"shape type:" (dm/get-prop shape :type))
|
||||
(when-let [gradient-id
|
||||
(when (string? fill-str)
|
||||
(some-> (re-matches url-fill-pattern fill-str)
|
||||
(nth 1 nil)))]
|
||||
(js/console.log "[svg-gradient->fill] Extracted gradient-id:" gradient-id)
|
||||
(when-let [node (resolve-gradient-node shape gradient-id)]
|
||||
(js/console.log "[svg-gradient->fill] Resolved gradient node:"
|
||||
"tag:" (:tag node)
|
||||
"has content:" (seq (:content node))
|
||||
"content count:" (count (:content node))
|
||||
"node:" (clj->js node))
|
||||
(case (:tag node)
|
||||
:linearGradient (build-linear-gradient shape node)
|
||||
:radialGradient (build-radial-gradient shape node)
|
||||
:linearGradient (let [result (build-linear-gradient shape node)]
|
||||
(js/console.log "[svg-gradient->fill] Built linear gradient:" (clj->js result))
|
||||
result)
|
||||
:radialGradient (let [result (build-radial-gradient shape node)]
|
||||
(js/console.log "[svg-gradient->fill] Built radial gradient:" (clj->js result))
|
||||
result)
|
||||
nil)))))
|
||||
|
||||
(defn- parse-svg-fill
|
||||
@@ -333,6 +421,19 @@
|
||||
(let [base-fills (dm/get-prop shape :fills)
|
||||
fallback (svg-fill->fills shape)
|
||||
type (dm/get-prop shape :type)]
|
||||
(js/console.log "[resolve-shape-fills] Resolving fills for shape:"
|
||||
"shape name:" (dm/get-prop shape :name)
|
||||
"shape type:" type
|
||||
"shape id:" (dm/get-prop shape :id)
|
||||
"has base-fills?:" (seq base-fills)
|
||||
"base-fills:" (clj->js base-fills)
|
||||
"has fallback?:" (seq fallback)
|
||||
"fallback:" (clj->js fallback)
|
||||
"has svg-attrs?:" (contains? shape :svg-attrs)
|
||||
"svg-attrs fill:" (dm/get-in shape [:svg-attrs :fill])
|
||||
"svg-attrs style fill:" (dm/get-in shape [:svg-attrs :style :fill])
|
||||
"has svg-defs?:" (contains? shape :svg-defs)
|
||||
"svg-defs:" (when (contains? shape :svg-defs) (clj->js (:svg-defs shape))))
|
||||
(cond
|
||||
(seq base-fills) base-fills
|
||||
(seq fallback) fallback
|
||||
|
||||
Reference in New Issue
Block a user