From 83879fb931ec3f8e3dfef220d62773caf523973e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 3 Jun 2021 15:01:24 +0200 Subject: [PATCH] :sparkles: Support for fill,stroke,gradient,text --- common/src/app/common/file_builder.cljc | 3 +- .../src/app/main/ui/shapes/gradients.cljs | 27 ++- frontend/src/app/main/ui/shapes/shape.cljs | 10 +- frontend/src/app/util/import/parser.cljc | 165 ++++++++++++++---- frontend/src/app/util/json.cljs | 19 ++ 5 files changed, 179 insertions(+), 45 deletions(-) create mode 100644 frontend/src/app/util/json.cljs diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 807d507c2..1b0dbe2e1 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -7,12 +7,12 @@ (ns app.common.file-builder "A version parsing helper." (:require + [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.pages.changes :as ch] [app.common.pages.init :as init] [app.common.pages.spec :as spec] [app.common.spec :as us] - [app.common.spec :as us] [app.common.uuid :as uuid])) (def root-frame uuid/zero) @@ -130,6 +130,7 @@ (lookup-shape file frame-id)) obj (-> (init/make-minimal-shape type) (merge data) + (d/without-nils) (cond-> frame (gsh/translate-from-frame frame)))] (commit-shape file obj))) diff --git a/frontend/src/app/main/ui/shapes/gradients.cljs b/frontend/src/app/main/ui/shapes/gradients.cljs index 9475ec8d3..a6e091cbc 100644 --- a/frontend/src/app/main/ui/shapes/gradients.cljs +++ b/frontend/src/app/main/ui/shapes/gradients.cljs @@ -30,6 +30,15 @@ :stop-color color :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]}] (let [{:keys [x y width height]} (:selrect shape) center (gsh/center-shape shape) @@ -59,13 +68,17 @@ transform (gmt/multiply transform (gmt/translate-matrix translate-vec) (gmt/rotate-matrix angle) - (gmt/scale-matrix scale-vec))] - [:radialGradient {:id id - :cx 0 - :cy 0 - :r 1 - :gradientUnits "userSpaceOnUse" - :gradientTransform transform} + (gmt/scale-matrix scale-vec)) + + base-props #js {:id id + :cx 0 + :cy 0 + :r 1 + :gradientUnits "userSpaceOnUse" + :gradientTransform transform} + + props (-> base-props (add-metadata gradient))] + [:> :radialGradient props (for [{:keys [offset color opacity]} (:stops gradient)] [:stop {:key (str id "-stop-" offset) :offset (or offset 0) diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index 86420ee12..a91e2c7e7 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -16,7 +16,8 @@ [app.main.ui.shapes.gradients :as grad] [app.main.ui.shapes.svg-defs :as defs] [app.util.object :as obj] - [rumext.alpha :as mf])) + [rumext.alpha :as mf] + [app.util.json :as json])) (defn add-metadata "Adds as metadata properties that we cannot deduce from the exported SVG" @@ -26,7 +27,8 @@ (let [ns-attr (str "penpot:" (-> attr d/name))] (-> props (obj/set! ns-attr val)))) - frame? (= :frame (:type shape))] + frame? (= :frame (:type shape)) + text? (= :text (:type shape))] (-> props (add! :name (-> shape :name)) (add! :blocked (-> shape (:blocked false) str)) @@ -45,6 +47,10 @@ (add! :r3 (-> shape (:r3 0) str)) (add! :r4 (-> shape (:r4 0) str)))) + (cond-> text? + (-> (add! :grow-type (-> shape :grow-type)) + (add! :content (-> shape :content json/encode)))) + (cond-> frame? (obj/set! "xmlns:penpot" "https://penpot.app/xmlns"))))) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 6847b194f..88929ad5b 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -9,8 +9,10 @@ [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] - [cuerdas.core :as str] - [app.util.path.parser :as upp])) + [app.util.color :as uc] + [app.util.json :as json] + [app.util.path.parser :as upp] + [cuerdas.core :as str])) (defn valid? [root] @@ -38,9 +40,9 @@ (or (close? node) (contains? (:attrs node) :penpot:type))) -(defn get-attr +(defn get-meta ([m att] - (get-attr m att identity)) + (get-meta m att identity)) ([m att val-fn] (let [ns-att (->> att d/name (str "penpot:") keyword) val (get-in m [:attrs ns-att])] @@ -78,22 +80,25 @@ (reduce-kv (fn [m k v] (if (#{:style :data-style} k) - (assoc m :style (parse-style v)) + (merge m (parse-style v)) (assoc m k v))) m 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}) + +(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}) (defn parse-position @@ -123,22 +128,103 @@ (assoc :selrect selrect) (assoc :points points)))) -(defn extract-data - [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)) +(def url-regex #"url\(#([^\)]*)\)") - (= type :circle) - (-> (parse-circle data) - (gsh/setup-selrect)) +(defn seek-node [id coll] + (->> coll (d/seek #(= id (-> % :attrs :id))))) - (= type :path) - (parse-path data)))) +(defn parse-stops [gradient-node] + (->> 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 [val] @@ -148,17 +234,26 @@ [type node] (when-not (close? node) - (let [name (get-attr node :name) - blocked (get-attr node :blocked str->bool) - hidden (get-attr node :hidden str->bool) - transform (get-attr node :transform gmt/str->matrix) - transform-inverse (get-attr node :transform-inverse gmt/str->matrix)] + (let [name (get-meta node :name) + blocked (get-meta node :blocked str->bool) + hidden (get-meta node :hidden str->bool) + transform (get-meta node :transform 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 :blocked blocked) (assoc :hidden hidden) + + (cond-> (= :text type) + (add-text-data node)) + (cond-> (some? transform) (assoc :transform transform)) + (cond-> (some? transform-inverse) (assoc :transform-inverse transform-inverse)))))) diff --git a/frontend/src/app/util/json.cljs b/frontend/src/app/util/json.cljs new file mode 100644 index 000000000..02ff7d58d --- /dev/null +++ b/frontend/src/app/util/json.cljs @@ -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)))