mirror of
https://github.com/penpot/penpot.git
synced 2025-07-21 22:57:14 +02:00
✨ Support for fill,stroke,gradient,text
This commit is contained in:
parent
8d703a3fb4
commit
83879fb931
5 changed files with 179 additions and 45 deletions
|
@ -7,12 +7,12 @@
|
||||||
(ns app.common.file-builder
|
(ns app.common.file-builder
|
||||||
"A version parsing helper."
|
"A version parsing helper."
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.pages.changes :as ch]
|
[app.common.pages.changes :as ch]
|
||||||
[app.common.pages.init :as init]
|
[app.common.pages.init :as init]
|
||||||
[app.common.pages.spec :as spec]
|
[app.common.pages.spec :as spec]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.spec :as us]
|
|
||||||
[app.common.uuid :as uuid]))
|
[app.common.uuid :as uuid]))
|
||||||
|
|
||||||
(def root-frame uuid/zero)
|
(def root-frame uuid/zero)
|
||||||
|
@ -130,6 +130,7 @@
|
||||||
(lookup-shape file frame-id))
|
(lookup-shape file frame-id))
|
||||||
obj (-> (init/make-minimal-shape type)
|
obj (-> (init/make-minimal-shape type)
|
||||||
(merge data)
|
(merge data)
|
||||||
|
(d/without-nils)
|
||||||
(cond-> frame
|
(cond-> frame
|
||||||
(gsh/translate-from-frame frame)))]
|
(gsh/translate-from-frame frame)))]
|
||||||
(commit-shape file obj)))
|
(commit-shape file obj)))
|
||||||
|
|
|
@ -30,6 +30,15 @@
|
||||||
:stop-color color
|
:stop-color color
|
||||||
:stop-opacity opacity}])]))
|
:stop-opacity opacity}])]))
|
||||||
|
|
||||||
|
(defn add-metadata [props gradient]
|
||||||
|
(-> props
|
||||||
|
(obj/set! "penpot:start-x" (:start-x gradient))
|
||||||
|
(obj/set! "penpot:start-x" (:start-x gradient))
|
||||||
|
(obj/set! "penpot:start-y" (:start-y gradient))
|
||||||
|
(obj/set! "penpot:end-x" (:end-x gradient))
|
||||||
|
(obj/set! "penpot:end-y" (:end-y gradient))
|
||||||
|
(obj/set! "penpot:width" (:width gradient))))
|
||||||
|
|
||||||
(mf/defc radial-gradient [{:keys [id gradient shape]}]
|
(mf/defc radial-gradient [{:keys [id gradient shape]}]
|
||||||
(let [{:keys [x y width height]} (:selrect shape)
|
(let [{:keys [x y width height]} (:selrect shape)
|
||||||
center (gsh/center-shape shape)
|
center (gsh/center-shape shape)
|
||||||
|
@ -59,13 +68,17 @@
|
||||||
transform (gmt/multiply transform
|
transform (gmt/multiply transform
|
||||||
(gmt/translate-matrix translate-vec)
|
(gmt/translate-matrix translate-vec)
|
||||||
(gmt/rotate-matrix angle)
|
(gmt/rotate-matrix angle)
|
||||||
(gmt/scale-matrix scale-vec))]
|
(gmt/scale-matrix scale-vec))
|
||||||
[:radialGradient {:id id
|
|
||||||
:cx 0
|
base-props #js {:id id
|
||||||
:cy 0
|
:cx 0
|
||||||
:r 1
|
:cy 0
|
||||||
:gradientUnits "userSpaceOnUse"
|
:r 1
|
||||||
:gradientTransform transform}
|
:gradientUnits "userSpaceOnUse"
|
||||||
|
:gradientTransform transform}
|
||||||
|
|
||||||
|
props (-> base-props (add-metadata gradient))]
|
||||||
|
[:> :radialGradient props
|
||||||
(for [{:keys [offset color opacity]} (:stops gradient)]
|
(for [{:keys [offset color opacity]} (:stops gradient)]
|
||||||
[:stop {:key (str id "-stop-" offset)
|
[:stop {:key (str id "-stop-" offset)
|
||||||
:offset (or offset 0)
|
:offset (or offset 0)
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
[app.main.ui.shapes.gradients :as grad]
|
[app.main.ui.shapes.gradients :as grad]
|
||||||
[app.main.ui.shapes.svg-defs :as defs]
|
[app.main.ui.shapes.svg-defs :as defs]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]
|
||||||
|
[app.util.json :as json]))
|
||||||
|
|
||||||
(defn add-metadata
|
(defn add-metadata
|
||||||
"Adds as metadata properties that we cannot deduce from the exported SVG"
|
"Adds as metadata properties that we cannot deduce from the exported SVG"
|
||||||
|
@ -26,7 +27,8 @@
|
||||||
(let [ns-attr (str "penpot:" (-> attr d/name))]
|
(let [ns-attr (str "penpot:" (-> attr d/name))]
|
||||||
(-> props
|
(-> props
|
||||||
(obj/set! ns-attr val))))
|
(obj/set! ns-attr val))))
|
||||||
frame? (= :frame (:type shape))]
|
frame? (= :frame (:type shape))
|
||||||
|
text? (= :text (:type shape))]
|
||||||
(-> props
|
(-> props
|
||||||
(add! :name (-> shape :name))
|
(add! :name (-> shape :name))
|
||||||
(add! :blocked (-> shape (:blocked false) str))
|
(add! :blocked (-> shape (:blocked false) str))
|
||||||
|
@ -45,6 +47,10 @@
|
||||||
(add! :r3 (-> shape (:r3 0) str))
|
(add! :r3 (-> shape (:r3 0) str))
|
||||||
(add! :r4 (-> shape (:r4 0) str))))
|
(add! :r4 (-> shape (:r4 0) str))))
|
||||||
|
|
||||||
|
(cond-> text?
|
||||||
|
(-> (add! :grow-type (-> shape :grow-type))
|
||||||
|
(add! :content (-> shape :content json/encode))))
|
||||||
|
|
||||||
(cond-> frame?
|
(cond-> frame?
|
||||||
(obj/set! "xmlns:penpot" "https://penpot.app/xmlns")))))
|
(obj/set! "xmlns:penpot" "https://penpot.app/xmlns")))))
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,10 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[cuerdas.core :as str]
|
[app.util.color :as uc]
|
||||||
[app.util.path.parser :as upp]))
|
[app.util.json :as json]
|
||||||
|
[app.util.path.parser :as upp]
|
||||||
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
(defn valid?
|
(defn valid?
|
||||||
[root]
|
[root]
|
||||||
|
@ -38,9 +40,9 @@
|
||||||
(or (close? node)
|
(or (close? node)
|
||||||
(contains? (:attrs node) :penpot:type)))
|
(contains? (:attrs node) :penpot:type)))
|
||||||
|
|
||||||
(defn get-attr
|
(defn get-meta
|
||||||
([m att]
|
([m att]
|
||||||
(get-attr m att identity))
|
(get-meta m att identity))
|
||||||
([m att val-fn]
|
([m att val-fn]
|
||||||
(let [ns-att (->> att d/name (str "penpot:") keyword)
|
(let [ns-att (->> att d/name (str "penpot:") keyword)
|
||||||
val (get-in m [:attrs ns-att])]
|
val (get-in m [:attrs ns-att])]
|
||||||
|
@ -78,22 +80,25 @@
|
||||||
(reduce-kv
|
(reduce-kv
|
||||||
(fn [m k v]
|
(fn [m k v]
|
||||||
(if (#{:style :data-style} k)
|
(if (#{:style :data-style} k)
|
||||||
(assoc m :style (parse-style v))
|
(merge m (parse-style v))
|
||||||
(assoc m k v)))
|
(assoc m k v)))
|
||||||
m
|
m
|
||||||
attrs))
|
attrs))
|
||||||
|
|
||||||
(defn get-data-node
|
|
||||||
[node]
|
|
||||||
|
|
||||||
(let [data-tags #{:ellipse :rect :path}]
|
|
||||||
(->> node
|
|
||||||
(node-seq)
|
|
||||||
(filter #(contains? data-tags (:tag %)))
|
|
||||||
(map #(:attrs %))
|
|
||||||
(reduce add-attrs {}))))
|
|
||||||
|
|
||||||
(def search-data-node? #{:rect :image :path :text :circle})
|
(def search-data-node? #{:rect :image :path :text :circle})
|
||||||
|
|
||||||
|
(defn get-shape-data
|
||||||
|
[type node]
|
||||||
|
|
||||||
|
(if (search-data-node? type)
|
||||||
|
(let [data-tags #{:ellipse :rect :path :text :foreignObject}]
|
||||||
|
(->> node
|
||||||
|
(node-seq)
|
||||||
|
(filter #(contains? data-tags (:tag %)))
|
||||||
|
(map #(:attrs %))
|
||||||
|
(reduce add-attrs {})))
|
||||||
|
(:attrs node)))
|
||||||
|
|
||||||
(def has-position? #{:frame :rect :image :text})
|
(def has-position? #{:frame :rect :image :text})
|
||||||
|
|
||||||
(defn parse-position
|
(defn parse-position
|
||||||
|
@ -123,22 +128,103 @@
|
||||||
(assoc :selrect selrect)
|
(assoc :selrect selrect)
|
||||||
(assoc :points points))))
|
(assoc :points points))))
|
||||||
|
|
||||||
(defn extract-data
|
(def url-regex #"url\(#([^\)]*)\)")
|
||||||
[type node]
|
|
||||||
(let [data (if (search-data-node? type)
|
|
||||||
(get-data-node node)
|
|
||||||
(:attrs node))]
|
|
||||||
(cond-> {}
|
|
||||||
(has-position? type)
|
|
||||||
(-> (parse-position data)
|
|
||||||
(gsh/setup-selrect))
|
|
||||||
|
|
||||||
(= type :circle)
|
(defn seek-node [id coll]
|
||||||
(-> (parse-circle data)
|
(->> coll (d/seek #(= id (-> % :attrs :id)))))
|
||||||
(gsh/setup-selrect))
|
|
||||||
|
|
||||||
(= type :path)
|
(defn parse-stops [gradient-node]
|
||||||
(parse-path data))))
|
(->> gradient-node
|
||||||
|
(node-seq)
|
||||||
|
(filter #(= :stop (:tag %)))
|
||||||
|
(mapv (fn [{{:keys [offset stop-color stop-opacity]} :attrs}]
|
||||||
|
{:color stop-color
|
||||||
|
:opacity (d/parse-double stop-opacity)
|
||||||
|
:offset (d/parse-double offset)}))))
|
||||||
|
|
||||||
|
(defn parse-gradient
|
||||||
|
[node ref-url]
|
||||||
|
(let [[_ url] (re-matches url-regex ref-url)
|
||||||
|
gradient-node (->> node (node-seq) (seek-node url))
|
||||||
|
stops (parse-stops gradient-node)]
|
||||||
|
|
||||||
|
(cond-> {:stops stops}
|
||||||
|
(= :linearGradient (:tag gradient-node))
|
||||||
|
(assoc :type :linear
|
||||||
|
:start-x (-> gradient-node :attrs :x1 d/parse-double)
|
||||||
|
:start-y (-> gradient-node :attrs :y1 d/parse-double)
|
||||||
|
:end-x (-> gradient-node :attrs :x2 d/parse-double)
|
||||||
|
:end-y (-> gradient-node :attrs :y2 d/parse-double)
|
||||||
|
:width 1)
|
||||||
|
|
||||||
|
(= :radialGradient (:tag gradient-node))
|
||||||
|
(assoc :type :radial
|
||||||
|
:start-x (get-meta gradient-node :start-x d/parse-double)
|
||||||
|
:start-y (get-meta gradient-node :start-y d/parse-double)
|
||||||
|
:end-x (get-meta gradient-node :end-x d/parse-double)
|
||||||
|
:end-y (get-meta gradient-node :end-y d/parse-double)
|
||||||
|
:width (get-meta gradient-node :width d/parse-double)))))
|
||||||
|
|
||||||
|
(defn add-position
|
||||||
|
[props type node data]
|
||||||
|
(cond-> props
|
||||||
|
(has-position? type)
|
||||||
|
(-> (parse-position data)
|
||||||
|
(gsh/setup-selrect))
|
||||||
|
|
||||||
|
(= type :circle)
|
||||||
|
(-> (parse-circle data)
|
||||||
|
(gsh/setup-selrect))
|
||||||
|
|
||||||
|
(= type :path)
|
||||||
|
(parse-path data)))
|
||||||
|
|
||||||
|
(defn add-fill
|
||||||
|
[props type node data]
|
||||||
|
|
||||||
|
(let [fill (:fill data)]
|
||||||
|
(cond-> props
|
||||||
|
(= fill "none")
|
||||||
|
(assoc :fill-color nil
|
||||||
|
:fill-opacity nil)
|
||||||
|
|
||||||
|
(str/starts-with? fill "url")
|
||||||
|
(assoc :fill-color-gradient (parse-gradient node fill)
|
||||||
|
:fill-color nil
|
||||||
|
:fill-opacity nil)
|
||||||
|
|
||||||
|
(uc/hex? fill)
|
||||||
|
(assoc :fill-color fill
|
||||||
|
:fill-opacity (-> data (:fill-opacity "1") d/parse-double)))))
|
||||||
|
|
||||||
|
(defn add-stroke
|
||||||
|
[props type node data]
|
||||||
|
|
||||||
|
(let [stroke-style (get-meta node :stroke-style keyword)
|
||||||
|
stroke-alignment (get-meta node :stroke-alignment keyword)
|
||||||
|
stroke (:stroke data)]
|
||||||
|
|
||||||
|
(cond-> props
|
||||||
|
:always
|
||||||
|
(assoc :stroke-alignment stroke-alignment
|
||||||
|
:stroke-style stroke-style
|
||||||
|
:stroke-color (-> data (:stroke "#000000"))
|
||||||
|
:stroke-opacity (-> data (:stroke-opacity "1") d/parse-double)
|
||||||
|
:stroke-width (-> data (:stroke-width "0") d/parse-double))
|
||||||
|
|
||||||
|
(str/starts-with? stroke "url")
|
||||||
|
(assoc :stroke-color-gradient (parse-gradient node stroke)
|
||||||
|
:stroke-color nil
|
||||||
|
:stroke-opacity nil)
|
||||||
|
|
||||||
|
(= stroke-alignment :inner)
|
||||||
|
(update :stroke-width / 2))))
|
||||||
|
|
||||||
|
(defn add-text-data
|
||||||
|
[props node]
|
||||||
|
(-> props
|
||||||
|
(assoc :grow-type (get-meta node :grow-type keyword))
|
||||||
|
(assoc :content (get-meta node :content json/decode))))
|
||||||
|
|
||||||
(defn str->bool
|
(defn str->bool
|
||||||
[val]
|
[val]
|
||||||
|
@ -148,17 +234,26 @@
|
||||||
[type node]
|
[type node]
|
||||||
|
|
||||||
(when-not (close? node)
|
(when-not (close? node)
|
||||||
(let [name (get-attr node :name)
|
(let [name (get-meta node :name)
|
||||||
blocked (get-attr node :blocked str->bool)
|
blocked (get-meta node :blocked str->bool)
|
||||||
hidden (get-attr node :hidden str->bool)
|
hidden (get-meta node :hidden str->bool)
|
||||||
transform (get-attr node :transform gmt/str->matrix)
|
transform (get-meta node :transform gmt/str->matrix)
|
||||||
transform-inverse (get-attr node :transform-inverse gmt/str->matrix)]
|
transform-inverse (get-meta node :transform-inverse gmt/str->matrix)
|
||||||
|
data (get-shape-data type node)]
|
||||||
|
|
||||||
(-> (extract-data type node)
|
(-> {}
|
||||||
|
(add-position type node data)
|
||||||
|
(add-fill type node data)
|
||||||
|
(add-stroke type node data)
|
||||||
(assoc :name name)
|
(assoc :name name)
|
||||||
(assoc :blocked blocked)
|
(assoc :blocked blocked)
|
||||||
(assoc :hidden hidden)
|
(assoc :hidden hidden)
|
||||||
|
|
||||||
|
(cond-> (= :text type)
|
||||||
|
(add-text-data node))
|
||||||
|
|
||||||
(cond-> (some? transform)
|
(cond-> (some? transform)
|
||||||
(assoc :transform transform))
|
(assoc :transform transform))
|
||||||
|
|
||||||
(cond-> (some? transform-inverse)
|
(cond-> (some? transform-inverse)
|
||||||
(assoc :transform-inverse transform-inverse))))))
|
(assoc :transform-inverse transform-inverse))))))
|
||||||
|
|
19
frontend/src/app/util/json.cljs
Normal file
19
frontend/src/app/util/json.cljs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.util.json)
|
||||||
|
|
||||||
|
(defn decode
|
||||||
|
[data]
|
||||||
|
(-> data
|
||||||
|
(js/JSON.parse)
|
||||||
|
(js->clj :keywordize-keys true)))
|
||||||
|
|
||||||
|
(defn encode
|
||||||
|
[data]
|
||||||
|
(-> data
|
||||||
|
(clj->js)
|
||||||
|
(js/JSON.stringify)))
|
Loading…
Add table
Add a link
Reference in a new issue