diff --git a/common/deps.edn b/common/deps.edn index 01ee915f0..168253588 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -26,6 +26,7 @@ com.cognitect/transit-cljs {:mvn/version "0.8.280"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} + funcool/tubax {:mvn/version "2021.05.20-0"} funcool/cuerdas {:mvn/version "2022.06.16-403"} funcool/promesa {:mvn/version "11.0.678"} funcool/datoteka {:mvn/version "3.0.66" @@ -43,7 +44,7 @@ fipp/fipp {:mvn/version "0.6.26"} io.aviso/pretty {:mvn/version "1.4.4"} environ/environ {:mvn/version "1.2.0"}} - :paths ["src" "target/classes"] + :paths ["src" "vendor" "target/classes"] :aliases {:dev {:extra-deps diff --git a/common/package.json b/common/package.json index 33d56eecf..ce7e549b0 100644 --- a/common/package.json +++ b/common/package.json @@ -4,7 +4,8 @@ "main": "index.js", "license": "MPL-2.0", "dependencies": { - "luxon": "^3.4.2" + "luxon": "^3.4.2", + "sax": "^1.2.4" }, "scripts": { "test:watch": "clojure -M:dev:shadow-cljs watch test", diff --git a/common/src/app/common/colors.cljc b/common/src/app/common/colors.cljc index b668065de..101eecb98 100644 --- a/common/src/app/common/colors.cljc +++ b/common/src/app/common/colors.cljc @@ -5,7 +5,12 @@ ;; Copyright (c) KALEIDOS INC (ns app.common.colors - (:refer-clojure :exclude [test])) + (:refer-clojure :exclude [test]) + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.math :as mth] + [cuerdas.core :as str])) (def black "#000000") (def canvas "#E8E9EA") @@ -27,3 +32,428 @@ (def new-danger "#ff4986") (def new-warning "#ff9b49") (def canvas-background "#1d1f20") + +(def names + {"aliceblue" "#f0f8ff" + "antiquewhite" "#faebd7" + "aqua" "#00ffff" + "aquamarine" "#7fffd4" + "azure" "#f0ffff" + "beige" "#f5f5dc" + "bisque" "#ffe4c4" + "black" "#000000" + "blanchedalmond" "#ffebcd" + "blue" "#0000ff" + "blueviolet" "#8a2be2" + "brown" "#a52a2a" + "burlywood" "#deb887" + "cadetblue" "#5f9ea0" + "chartreuse" "#7fff00" + "chocolate" "#d2691e" + "coral" "#ff7f50" + "cornflowerblue" "#6495ed" + "cornsilk" "#fff8dc" + "crimson" "#dc143c" + "cyan" "#00ffff" + "darkblue" "#00008b" + "darkcyan" "#008b8b" + "darkgoldenrod" "#b8860b" + "darkgray" "#a9a9a9" + "darkgreen" "#006400" + "darkgrey" "#a9a9a9" + "darkkhaki" "#bdb76b" + "darkmagenta" "#8b008b" + "darkolivegreen" "#556b2f" + "darkorange" "#ff8c00" + "darkorchid" "#9932cc" + "darkred" "#8b0000" + "darksalmon" "#e9967a" + "darkseagreen" "#8fbc8f" + "darkslateblue" "#483d8b" + "darkslategray" "#2f4f4f" + "darkslategrey" "#2f4f4f" + "darkturquoise" "#00ced1" + "darkviolet" "#9400d3" + "deeppink" "#ff1493" + "deepskyblue" "#00bfff" + "dimgray" "#696969" + "dimgrey" "#696969" + "dodgerblue" "#1e90ff" + "firebrick" "#b22222" + "floralwhite" "#fffaf0" + "forestgreen" "#228b22" + "fuchsia" "#ff00ff" + "gainsboro" "#dcdcdc" + "ghostwhite" "#f8f8ff" + "gold" "#ffd700" + "goldenrod" "#daa520" + "gray" "#808080" + "green" "#008000" + "greenyellow" "#adff2f" + "grey" "#808080" + "honeydew" "#f0fff0" + "hotpink" "#ff69b4" + "indianred" "#cd5c5c" + "indigo" "#4b0082" + "ivory" "#fffff0" + "khaki" "#f0e68c" + "lavender" "#e6e6fa" + "lavenderblush" "#fff0f5" + "lawngreen" "#7cfc00" + "lemonchiffon" "#fffacd" + "lightblue" "#add8e6" + "lightcoral" "#f08080" + "lightcyan" "#e0ffff" + "lightgoldenrodyellow" "#fafad2" + "lightgray" "#d3d3d3" + "lightgreen" "#90ee90" + "lightgrey" "#d3d3d3" + "lightpink" "#ffb6c1" + "lightsalmon" "#ffa07a" + "lightseagreen" "#20b2aa" + "lightskyblue" "#87cefa" + "lightslategray" "#778899" + "lightslategrey" "#778899" + "lightsteelblue" "#b0c4de" + "lightyellow" "#ffffe0" + "lime" "#00ff00" + "limegreen" "#32cd32" + "linen" "#faf0e6" + "magenta" "#ff00ff" + "maroon" "#800000" + "mediumaquamarine" "#66cdaa" + "mediumblue" "#0000cd" + "mediumorchid" "#ba55d3" + "mediumpurple" "#9370db" + "mediumseagreen" "#3cb371" + "mediumslateblue" "#7b68ee" + "mediumspringgreen" "#00fa9a" + "mediumturquoise" "#48d1cc" + "mediumvioletred" "#c71585" + "midnightblue" "#191970" + "mintcream" "#f5fffa" + "mistyrose" "#ffe4e1" + "moccasin" "#ffe4b5" + "navajowhite" "#ffdead" + "navy" "#000080" + "oldlace" "#fdf5e6" + "olive" "#808000" + "olivedrab" "#6b8e23" + "orange" "#ffa500" + "orangered" "#ff4500" + "orchid" "#da70d6" + "palegoldenrod" "#eee8aa" + "palegreen" "#98fb98" + "paleturquoise" "#afeeee" + "palevioletred" "#db7093" + "papayawhip" "#ffefd5" + "peachpuff" "#ffdab9" + "peru" "#cd853f" + "pink" "#ffc0cb" + "plum" "#dda0dd" + "powderblue" "#b0e0e6" + "purple" "#800080" + "red" "#ff0000" + "rosybrown" "#bc8f8f" + "royalblue" "#4169e1" + "saddlebrown" "#8b4513" + "salmon" "#fa8072" + "sandybrown" "#f4a460" + "seagreen" "#2e8b57" + "seashell" "#fff5ee" + "sienna" "#a0522d" + "silver" "#c0c0c0" + "skyblue" "#87ceeb" + "slateblue" "#6a5acd" + "slategray" "#708090" + "slategrey" "#708090" + "snow" "#fffafa" + "springgreen" "#00ff7f" + "steelblue" "#4682b4" + "tan" "#d2b48c" + "teal" "#008080" + "thistle" "#d8bfd8" + "tomato" "#ff6347" + "turquoise" "#40e0d0" + "violet" "#ee82ee" + "wheat" "#f5deb3" + "white" "#ffffff" + "whitesmoke" "#f5f5f5" + "yellow" "#ffff00" + "yellowgreen" "#9acd32"}) + +(def ^:private hex-color-re + #"\#[0-9a-fA-F]{3,6}") + +(def ^:private rgb-color-re + #"(?:|rgb)\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\)") + +(defn valid-hex-color? + [color] + (and (string? color) + (some? (re-matches hex-color-re color)))) + +(defn parse-rgb + [color] + (let [result (re-matches rgb-color-re color)] + (when (some? result) + (let [r (parse-long (nth result 1)) + g (parse-long (nth result 2)) + b (parse-long (nth result 3))] + (when (and (<= 0 r 255) (<= 0 g 255) (<= 0 b 255)) + [r g b]))))) + +(defn valid-rgb-color? + [color] + (if (string? color) + (let [result (parse-rgb color)] + (some? result)) + false)) + +(defn- normalize-hex + [color] + (if (= (count color) 4) ; of the form #RGB + (-> color + (str/replace #"\#(.)(.)(.)" "#$1$1$2$2$3$3") + (str/lower)) + (str/lower color))) + +(defn rgb->str + [[r g b a]] + (if (some? a) + (str/ffmt "rgba(%,%,%,%)" r g b a) + (str/ffmt "rgb(%,%,%)" r g b))) + +(defn rgb->hsv + [[red green blue]] + (let [max (d/max red green blue) + min (d/min red green blue) + val max] + (if (= min max) + [0 0 val] + (let [delta (- max min) + sat (/ delta max) + hue (if (= red max) + (/ (- green blue) delta) + (if (= green max) + (+ 2 (/ (- blue red) delta)) + (+ 4 (/ (- red green) delta)))) + hue (* 60 hue) + hue (if (< hue 0) + (+ hue 360) + hue) + hue (if (> hue 360) + (- hue 360) + hue)] + [hue sat val])))) + +(defn hsv->rgb + [[h s brightness]] + (if (= s 0) + [brightness brightness brightness] + (let [sextant (int (mth/floor (/ h 60))) + remainder (- (/ h 60) sextant) + val1 (int (* brightness (- 1 s))) + val2 (int (* brightness (- 1 (* s remainder)))) + val3 (int (* brightness (- 1 (* s (- 1 remainder)))))] + (case sextant + 1 [val2 brightness val1] + 2 [val1 brightness val3] + 3 [val1 val2 brightness] + 4 [val3 val1 brightness] + 5 [brightness val1 val2] + 6 [brightness val3 val1] + 0 [brightness val3 val1])))) + +(defn hex->rgb + [color] + (try + (let [rgb #?(:clj (Integer/parseInt (subs color 1) 16) + :cljs (js/parseInt (subs color 1) 16)) + r (bit-shift-right rgb 16) + g (bit-and (bit-shift-right rgb 8) 255) + b (bit-and rgb 255)] + [r g b]) + (catch #?(:clj Throwable :cljs :default) _cause + [0 0 0]))) + +(defn- int->hex + "Convert integer to hex string" + [v] + #?(:clj (Integer/toHexString v) + :cljs (.toString v 16))) + +(defn rgb->hex + [[r g b]] + (let [r (int r) + g (int g) + b (int b)] + (if (or (not= r (bit-and r 255)) + (not= g (bit-and g 255)) + (not= b (bit-and b 255))) + (throw (ex-info "not valid rgb" {:r r :g g :b b})) + (let [rgb (bit-or (bit-shift-left r 16) + (bit-shift-left g 8) b)] + (if (< r 16) + (dm/str "#" (subs (int->hex (bit-or 0x1000000 rgb)) 1)) + (dm/str "#" (int->hex rgb))))))) + +(defn rgb->hsl + [[r g b]] + (let [norm-r (/ r 255.0) + norm-g (/ g 255.0) + norm-b (/ b 255.0) + max (d/max norm-r norm-g norm-b) + min (d/min norm-r norm-g norm-b) + l (/ (+ max min) 2.0) + h (if (= max min) 0 + (if (= max norm-r) + (* 60 (/ (- norm-g norm-b) (- max min))) + (if (= max norm-g) + (+ 120 (* 60 (/ (- norm-b norm-r) (- max min)))) + (+ 240 (* 60 (/ (- norm-r norm-g) (- max min))))))) + s (if (and (> l 0) (<= l 0.5)) + (/ (- max min) (* 2 l)) + (/ (- max min) (- 2 (* 2 l))))] + [(mod (+ h 360) 360) s l])) + +(defn hex->hsv + [v] + (-> v hex->rgb rgb->hsv)) + +(defn hex->rgba + [data opacity] + (-> (hex->rgb data) + (conj opacity))) + +(defn hex->hsl [hex] + (try + (-> hex hex->rgb rgb->hsl) + (catch #?(:clj Throwable :cljs :default) _e + [0 0 0]))) + +(defn hex->hsla + [data opacity] + (-> (hex->hsl data) + (conj opacity))) + +#?(:cljs + (defn format-hsla + [[h s l a]] + (let [precision 2 + rounded-s (* 100 (parse-double (d/format-precision s precision))) + rounded-l (* 100 (parse-double (d/format-precision l precision)))] + (str/concat "" h ", " rounded-s "%, " rounded-l "%, " a)))) + +(defn- hue->rgb + "Helper for hsl->rgb" + [v1 v2 vh] + (let [vh (if (< vh 0) + (+ vh 1) + (if (> vh 1) + (- vh 1) + vh))] + (cond + (< (* 6 vh) 1) (+ v1 (* (- v2 v1) 6 vh)) + (< (* 2 vh) 1) v2 + (< (* 3 vh) 2) (+ v1 (* (- v2 v1) (- (/ 2 3) vh) 6)) + :else v1))) + +(defn hsl->rgb + [[h s l]] + (if (= s 0) + (let [o (* l 255)] + [o o o]) + (let [norm-h (/ h 360.0) + temp2 (if (< l 0.5) + (* l (+ 1 s)) + (- (+ l s) + (* s l))) + temp1 (- (* l 2) temp2)] + + [(mth/round (* 255 (hue->rgb temp1 temp2 (+ norm-h (/ 1 3))))) + (mth/round (* 255 (hue->rgb temp1 temp2 norm-h))) + (mth/round (* 255 (hue->rgb temp1 temp2 (- norm-h (/ 1 3)))))]))) + +(defn hsl->hex + [v] + (-> v hsl->rgb rgb->hex)) + +(defn hsl->hsv + [hsl] + (-> hsl hsl->rgb rgb->hsv)) + +(defn hsv->hex + [hsv] + (-> hsv hsv->rgb rgb->hex)) + +(defn hsv->hsl + [hsv] + (-> hsv hsv->hex hex->hsl)) + +(defn expand-hex + [v] + (cond + (re-matches #"^[0-9A-Fa-f]$" v) + (dm/str v v v v v v) + + (re-matches #"^[0-9A-Fa-f]{2}$" v) + (dm/str v v v) + + (re-matches #"^[0-9A-Fa-f]{3}$" v) + (let [a (nth v 0) + b (nth v 1) + c (nth v 2)] + (dm/str a a b b c c)) + + :else + v)) + +(defn prepend-hash + [color] + (if (= "#" (subs color 0 1)) + color + (dm/str "#" color))) + +(defn remove-hash + [color] + (if (str/starts-with? color "#") + (subs color 1) + color)) + +(defn color-string? + [color] + (and (string? color) + (or (valid-hex-color? color) + (valid-rgb-color? color) + (contains? names color)))) + +(defn parse + [color] + (when (string? color) + (if (valid-hex-color? color) + (normalize-hex color) + (or (some-> (parse-rgb color) (rgb->hex)) + (get names (str/lower color)))))) + +(def color-names + (into [] (keys names))) + +(def empty-color + (into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity])) + +(defn next-rgb + "Given a color in rgb returns the next color" + [[r g b]] + (cond + (and (= 255 r) (= 255 g) (= 255 b)) + (throw (ex-info "cannot get next color" {:r r :g g :b b})) + + (and (= 255 g) (= 255 b)) + [(inc r) 0 0] + + (= 255 b) + [r (inc g) 0] + + :else + [r g (inc b)])) diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc new file mode 100644 index 000000000..bdac7d903 --- /dev/null +++ b/common/src/app/common/files/libraries_helpers.cljc @@ -0,0 +1,91 @@ +;; 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) KALEIDOS INC + +(ns app.common.files.libraries-helpers + (:require + [app.common.pages.changes-builder :as pcb] + [app.common.pages.helpers :as cph] + [app.common.types.component :as ctk] + [app.common.types.container :as ctn] + [app.common.uuid :as uuid])) + +(defn generate-add-component-changes + [changes root objects file-id page-id components-v2] + (let [name (:name root) + [path name] (cph/parse-path-name name) + + [root-shape new-shapes updated-shapes] + (if-not components-v2 + (ctn/make-component-shape root objects file-id components-v2) + (let [new-id (uuid/next)] + [(assoc root :id new-id) + nil + [(assoc root + :component-id new-id + :component-file file-id + :component-root true + :main-instance true)]])) + + changes (-> changes + (pcb/add-component (:id root-shape) + path + name + new-shapes + updated-shapes + (:id root) + page-id))] + [root-shape changes])) + + +(defn generate-add-component + "If there is exactly one id, and it's a frame (or a group in v1), and not already a + component, use it as root. Otherwise, create a frame (v2) or group (v1) that contains + all ids. Then, make a component with it, and link all shapes to their corresponding one + in the component." + [it shapes objects page-id file-id components-v2 prepare-create-group prepare-create-board] + (let [changes (pcb/empty-changes it page-id) + + [root changes old-root-ids] + (if (and (= (count shapes) 1) + (or (and (= (:type (first shapes)) :group) (not components-v2)) + (= (:type (first shapes)) :frame)) + (not (ctk/instance-head? (first shapes)))) + + [(first shapes) + (-> (pcb/empty-changes it page-id) + (pcb/with-objects objects)) + (:shapes (first shapes))] + + (let [root-name (if (= 1 (count shapes)) + (:name (first shapes)) + "Component 1") + + [root changes] (if-not components-v2 + (prepare-create-group it ; These functions needs to be passed as argument + objects ; to avoid a circular dependence + page-id + shapes + root-name + (not (ctk/instance-head? (first shapes)))) + (prepare-create-board changes + (uuid/next) + (:parent-id (first shapes)) + objects + (map :id shapes) + nil + root-name + true))] + + [root changes (map :id shapes)])) + + [root-shape changes] (generate-add-component-changes changes root objects file-id page-id components-v2) + + changes (pcb/update-shapes changes + old-root-ids + #(dissoc % :component-root) + [:component-root])] + + [root (:id root-shape) changes])) diff --git a/common/src/app/common/files/shapes_helpers.cljc b/common/src/app/common/files/shapes_helpers.cljc new file mode 100644 index 000000000..6b4bf2e5e --- /dev/null +++ b/common/src/app/common/files/shapes_helpers.cljc @@ -0,0 +1,110 @@ +;; 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) KALEIDOS INC + +(ns app.common.files.shapes-helpers + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.geom.shapes :as gsh] + [app.common.pages.changes-builder :as pcb] + [app.common.pages.helpers :as cph] + [app.common.schema :as sm] + [app.common.types.shape :as cts] + [app.common.types.shape.layout :as ctl] + [app.common.uuid :as uuid])) + +(def valid-shape-map? + (sm/pred-fn ::cts/shape)) + +(defn prepare-add-shape + [changes shape objects _selected] + (let [index (:index (meta shape)) + id (:id shape) + + mod? (:mod? (meta shape)) + [row column :as cell] (when-not mod? (:cell (meta shape))) + + changes (-> changes + (pcb/with-objects objects) + (cond-> (some? index) + (pcb/add-object shape {:index index})) + (cond-> (nil? index) + (pcb/add-object shape)) + (cond-> (some? (:parent-id shape)) + (pcb/change-parent (:parent-id shape) [shape] index)) + (cond-> (some? cell) + (pcb/update-shapes [(:parent-id shape)] #(ctl/push-into-cell % [id] row column))) + (cond-> (ctl/grid-layout? objects (:parent-id shape)) + (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells)))] + [shape changes])) + +(defn prepare-move-shapes-into-frame + [changes frame-id shapes objects] + (let [ordered-indexes (cph/order-by-indexed-shapes objects shapes) + parent-id (get-in objects [frame-id :parent-id]) + ordered-indexes (->> ordered-indexes (remove #(= % parent-id))) + to-move-shapes (map (d/getf objects) ordered-indexes)] + (if (d/not-empty? to-move-shapes) + (-> changes + (cond-> (not (ctl/any-layout? objects frame-id)) + (pcb/update-shapes ordered-indexes ctl/remove-layout-item-data)) + (pcb/update-shapes ordered-indexes #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true))) + (pcb/change-parent frame-id to-move-shapes 0) + (cond-> (ctl/grid-layout? objects frame-id) + (pcb/update-shapes [frame-id] ctl/assign-cells)) + (pcb/reorder-grid-children [frame-id])) + changes))) + +(defn prepare-create-artboard-from-selection + [changes id parent-id objects selected index frame-name without-fill?] + (let [selected-objs (map #(get objects %) selected) + new-index (or index + (cph/get-index-replacement selected objects))] + (when (d/not-empty? selected) + (let [srect (gsh/shapes->rect selected-objs) + selected-id (first selected) + + frame-id (dm/get-in objects [selected-id :frame-id]) + parent-id (or parent-id (dm/get-in objects [selected-id :parent-id])) + + attrs {:type :frame + :x (:x srect) + :y (:y srect) + :width (:width srect) + :height (:height srect)} + + shape (cts/setup-shape + (cond-> attrs + (some? id) + (assoc :id id) + + (some? frame-name) + (assoc :name frame-name) + + :always + (assoc :frame-id frame-id + :parent-id parent-id) + + :always + (with-meta {:index new-index}) + + (or (not= frame-id uuid/zero) without-fill?) + (assoc :fills [] :hide-in-viewer true))) + + [shape changes] + (prepare-add-shape changes shape objects selected) + + changes + (prepare-move-shapes-into-frame changes (:id shape) selected objects) + + changes + (cond-> changes + (ctl/grid-layout? objects (:parent-id shape)) + (-> (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells) + (pcb/reorder-grid-children [(:parent-id shape)])))] + + [shape changes])))) + diff --git a/common/src/app/common/geom/shapes/bool.cljc b/common/src/app/common/geom/shapes/bool.cljc index a3a464544..a1eeb1581 100644 --- a/common/src/app/common/geom/shapes/bool.cljc +++ b/common/src/app/common/geom/shapes/bool.cljc @@ -7,8 +7,8 @@ (ns app.common.geom.shapes.bool (:require [app.common.data :as d] - [app.common.path.bool :as pb] - [app.common.path.shapes-to-path :as stp])) + [app.common.svg.path.bool :as pb] + [app.common.svg.path.shapes-to-path :as stp])) (defn calc-bool-content [shape objects] diff --git a/common/src/app/common/geom/shapes/path.cljc b/common/src/app/common/geom/shapes/path.cljc index 7497ef87e..604861f3e 100644 --- a/common/src/app/common/geom/shapes/path.cljc +++ b/common/src/app/common/geom/shapes/path.cljc @@ -12,8 +12,8 @@ [app.common.geom.rect :as grc] [app.common.geom.shapes.common :as gco] [app.common.math :as mth] - [app.common.path.commands :as upc] - [app.common.path.subpaths :as sp])) + [app.common.svg.path.command :as upc] + [app.common.svg.path.subpath :as sp])) (def ^:const curve-curve-precision 0.1) (def ^:const curve-range-precision 2) diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc index 688872c16..ac2f67ba8 100644 --- a/common/src/app/common/math.cljc +++ b/common/src/app/common/math.cljc @@ -133,9 +133,10 @@ (defn ceil "Returns the smallest integer greater than or equal to a given number." + ^double [v] #?(:cljs (js/Math.ceil v) - :clj (Math/ceil v))) + :clj (Math/ceil ^double v))) (defn precision [v n] diff --git a/common/src/app/common/svg.cljc b/common/src/app/common/svg.cljc index 6e07af39a..35e8bb79c 100644 --- a/common/src/app/common/svg.cljc +++ b/common/src/app/common/svg.cljc @@ -7,6 +7,8 @@ (ns app.common.svg (:require #?(:cljs ["./svg_optimizer.js" :as svgo]) + #?(:clj [clojure.xml :as xml] + :cljs [tubax.core :as tubax]) [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] @@ -14,7 +16,15 @@ [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.uuid :as uuid] - [cuerdas.core :as str])) + [cuerdas.core :as str]) + #?(:clj + (:import + javax.xml.XMLConstants + java.io.InputStream + javax.xml.parsers.SAXParserFactory + clojure.lang.XMLHandler + org.apache.commons.io.IOUtils))) + ;; Regex for XML ids per Spec ;; https://www.w3.org/TR/2008/REC-xml-20081126/#sec-common-syn @@ -1043,3 +1053,26 @@ ([input] (optimize input nil)) ([input options] (svgo/optimize input (clj->js options))))) + +#?(:clj + (defn- secure-parser-factory + [^InputStream input ^XMLHandler handler] + (.. (doto (SAXParserFactory/newInstance) + (.setFeature XMLConstants/FEATURE_SECURE_PROCESSING true) + (.setFeature "http://apache.org/xml/features/disallow-doctype-decl" true)) + (newSAXParser) + (parse input handler)))) + +(defn strip-doctype + [data] + (cond-> data + (str/includes? data "]*>" ""))) + + +(defn parse + [text] + #?(:cljs (tubax/xml->clj text) + :clj (let [text (strip-doctype text)] + (dm/with-open [istream (IOUtils/toInputStream text "UTF-8")] + (xml/parse istream secure-parser-factory))))) diff --git a/frontend/src/app/util/path/parser.cljs b/common/src/app/common/svg/path.cljc similarity index 61% rename from frontend/src/app/util/path/parser.cljs rename to common/src/app/common/svg/path.cljc index bc2ab6714..73ba490ed 100644 --- a/frontend/src/app/util/path/parser.cljs +++ b/common/src/app/common/svg/path.cljc @@ -4,55 +4,80 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.util.path.parser +(ns app.common.svg.path (:require [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as upg] - [app.common.path.commands :as upc] + [app.common.math :as mth] [app.common.svg :as csvg] - [app.util.path.arc-to-curve :refer [a2c]] + [app.common.svg.path.command :as upc] [cuerdas.core :as str])) -;; + (def commands-regex #"(?i)[mzlhvcsqta][^mzlhvcsqta]*") +(def regex #"[+-]?(\d+(\.\d+)?|\.\d+)(e[+-]?\d+)?") -;; Matches numbers for path values allows values like... -.01, 10, +12.22 -;; 0 and 1 are special because can refer to flags -(def num-regex #"[+-]?(\d+(\.\d+)?|\.\d+)(e[+-]?\d+)?") - -(def flag-regex #"[01]") - -(defn extract-params [cmd-str extract-commands] - (loop [result [] - extract-idx 0 +(defn extract-params + [data pattern] + (loop [result [] + ptt-idx 0 current {} - remain (-> cmd-str (subs 1) (str/trim))] + entries (re-seq regex data) + match (ffirst entries)] - (let [[param type] (nth extract-commands extract-idx) - regex (case type - :flag flag-regex - #_:number num-regex) - match (re-find regex remain)] + (if match + (let [[attr-name attr-type] (nth pattern ptt-idx) + ptt-idx (inc ptt-idx) + ptt-cnt (count pattern) - (if match - (let [value (-> match first csvg/fix-dot-number d/read-string) - remain (str/replace-first remain regex "") - current (assoc current param value) - extract-idx (inc extract-idx) - [result current extract-idx] - (if (>= extract-idx (count extract-commands)) - [(conj result current) {} 0] - [result current extract-idx])] - (recur result - extract-idx - current - remain)) - (cond-> result - (seq current) (conj current)))))) + value (if (= attr-type :flag) + (if (= 1 (count match)) + (d/parse-integer match) + (d/parse-integer (subs match 0 1))) + (-> match csvg/fix-dot-number d/parse-double)) + + current (assoc current attr-name value) + + result (if (>= ptt-idx ptt-cnt) + (conj result current) + result) + + current (if (>= ptt-idx ptt-cnt) + {} + current) + + match (if (and (= attr-type :flag) + (> (count match) 1)) + (subs match 1) + nil) + + entries (if match + entries + (rest entries)) + + match (if match + match + (ffirst entries)) + + ptt-idx (if (>= ptt-idx ptt-cnt) + 0 + ptt-idx)] + + (recur result + ptt-idx + current + entries + match)) + + (if (seq current) + (conj result current) + result)))) ;; Path specification ;; https://www.w3.org/TR/SVG11/paths.html -(defmulti parse-command (comp str/upper first)) +(defmulti parse-command + (fn [cmd] + (str/upper (subs cmd 0 1)))) (defmethod parse-command "M" [cmd] (let [relative (str/starts-with? cmd "m") @@ -125,8 +150,8 @@ (let [relative (str/starts-with? cmd "q") param-list (extract-params cmd [[:cx :number] [:cy :number] - [:x :number] - [:y :number]])] + [:x :number] + [:y :number]])] (for [params param-list] {:command :quadratic-bezier-curve-to :relative relative @@ -142,7 +167,7 @@ :params params}))) (defmethod parse-command "A" [cmd] - (let [relative (str/starts-with? cmd "a") + (let [relative (str/starts-with? cmd "a") param-list (extract-params cmd [[:rx :number] [:ry :number] [:x-axis-rotation :number] @@ -178,7 +203,120 @@ :c2x (:x cp2) :c2y (:y cp2)})) -(defn arc->beziers [from-p command] +(defn- unit-vector-angle + [ux uy vx vy] + (let [sign (if (> 0 (- (* ux vy) (* uy vx))) -1.0 1.0) + dot (+ (* ux vx) (* uy vy)) + dot (cond + (> dot 1.0) 1.0 + (< dot -1.0) -1.0 + :else dot)] + (* sign (mth/acos dot)))) + +(defn- get-arc-center [x1 y1 x2 y2 fa fs rx ry sin-phi cos-phi] + (let [x1p (+ (* cos-phi (/ (- x1 x2) 2)) (* sin-phi (/ (- y1 y2) 2))) + y1p (+ (* (- sin-phi) (/ (- x1 x2) 2)) (* cos-phi (/ (- y1 y2) 2))) + rx-sq (* rx rx) + ry-sq (* ry ry) + x1p-sq (* x1p x1p) + y1p-sq (* y1p y1p) + radicant (- (* rx-sq ry-sq) + (* rx-sq y1p-sq) + (* ry-sq x1p-sq)) + radicant (if (< radicant 0) 0 radicant) + radicant (/ radicant (+ (* rx-sq y1p-sq) (* ry-sq x1p-sq))) + radicant (* (mth/sqrt radicant) (if (= fa fs) -1 1)) + + cxp (* radicant (/ rx ry) y1p) + cyp (* radicant (/ (- ry) rx) x1p) + cx (+ (- (* cos-phi cxp) + (* sin-phi cyp)) + (/ (+ x1 x2) 2)) + cy (+ (* sin-phi cxp) + (* cos-phi cyp) + (/ (+ y1 y2) 2)) + + v1x (/ (- x1p cxp) rx) + v1y (/ (- y1p cyp) ry) + v2x (/ (- (- x1p) cxp) rx) + v2y (/ (- (- y1p) cyp) ry) + theta1 (unit-vector-angle 1 0 v1x v1y) + + dtheta (unit-vector-angle v1x v1y v2x v2y) + dtheta (if (and (= fs 0) (> dtheta 0)) (- dtheta (* mth/PI 2)) dtheta) + dtheta (if (and (= fs 1) (< dtheta 0)) (+ dtheta (* mth/PI 2)) dtheta)] + + [cx cy theta1 dtheta])) + +(defn approximate-unit-arc + [theta1 dtheta] + (let [alpha (* (/ 4 3) (mth/tan (/ dtheta 4))) + x1 (mth/cos theta1) + y1 (mth/sin theta1) + x2 (mth/cos (+ theta1 dtheta)) + y2 (mth/sin (+ theta1 dtheta))] + [x1 y1 (- x1 (* y1 alpha)) (+ y1 (* x1 alpha)) (+ x2 (* y2 alpha)) (- y2 (* x2 alpha)) x2 y2])) + +(defn- process-curve + [curve cc rx ry sin-phi cos-phi] + (reduce (fn [curve i] + (let [x (nth curve i) + y (nth curve (inc i)) + x (* x rx) + y (* y ry) + xp (- (* cos-phi x) (* sin-phi y)) + yp (+ (* sin-phi x) (* cos-phi y))] + (-> curve + (assoc i (+ xp (nth cc 0))) + (assoc (inc i) (+ yp (nth cc 1)))))) + curve + (range 0 (count curve) 2))) + +(defn arc->beziers* + [x1 y1 x2 y2 fa fs rx ry phi] + (let [tau (* mth/PI 2) + phi-tau (/ (* phi tau) 360) + + sin-phi (mth/sin phi-tau) + cos-phi (mth/cos phi-tau) + + x1p (+ (/ (* cos-phi (- x1 x2)) 2) + (/ (* sin-phi (- y1 y2)) 2)) + y1p (+ (/ (* (- sin-phi) (- x1 x2)) 2) + (/ (* cos-phi (- y1 y2)) 2))] + + (if (or (zero? x1p) + (zero? y1p) + (zero? rx) + (zero? ry)) + [] + (let [ + rx (mth/abs rx) + ry (mth/abs ry) + lambda (+ (/ (* x1p x1p) (* rx rx)) + (/ (* y1p y1p) (* ry ry))) + rx (if (> lambda 1) (* rx (mth/sqrt lambda)) rx) + ry (if (> lambda 1) (* ry (mth/sqrt lambda)) ry) + + cc (get-arc-center x1 y1 x2 y2 fa fs rx ry sin-phi cos-phi) + theta1 (nth cc 2) + dtheta (nth cc 3) + segments (mth/max (mth/ceil (/ (mth/abs dtheta) (/ tau 4))) 1) + dtheta (/ dtheta segments)] + + (loop [i 0.0 + t (double theta1) + r []] + (if (< i segments) + (let [curve (approximate-unit-arc t dtheta) + curve (process-curve curve cc rx ry sin-phi cos-phi)] + (recur (inc i) + (+ t dtheta) + (conj r curve))) + r)))))) + +(defn arc->beziers + [from-p {:keys [params] :as command}] (let [to-command (fn [[_ _ c1x c1y c2x c2y x y]] {:command :curve-to @@ -188,8 +326,16 @@ :x x :y y}}) {from-x :x from-y :y} from-p - {:keys [rx ry x-axis-rotation large-arc-flag sweep-flag x y]} (:params command) - result (a2c from-x from-y x y large-arc-flag sweep-flag rx ry x-axis-rotation)] + + x (get params :x 0.0) + y (get params :y 0.0) + rx (get params :rx 0.0) + ry (get params :ry 0.0) + x-axis-rotation (get params :x-axis-rotation 0) + large-arc-flag (get params :large-arc-flag 0) + sweep-flag (get params :sweep-flag 0) + result (arc->beziers* from-x from-y x y large-arc-flag sweep-flag rx ry x-axis-rotation)] + (mapv to-command result))) (defn simplify-commands @@ -202,7 +348,6 @@ ;; prev-qc : previous command control point for quadratic curves (fn [[result prev-pos prev-start prev-cc prev-qc] [command _prev]] (let [command (assoc command :prev-pos prev-pos) - command (cond-> command (:relative command) @@ -288,6 +433,7 @@ next-start (if (= :move-to (:command command)) next-pos prev-start)] + [result next-pos next-start next-cc next-qc])) start (first commands) @@ -301,17 +447,10 @@ (reduce simplify-command [[start] start-pos start-pos start-pos start-pos]) (first)))) -(defn parse-path [path-str] +(defn parse + [path-str] (if (empty? path-str) path-str - (let [clean-path-str - (-> path-str - (str/trim) - ;; Change "commas" for spaces - (str/replace #"," " ") - ;; Remove all consecutive spaces - (str/replace #"\s+" " ")) - commands (re-seq commands-regex clean-path-str)] + (let [commands (re-seq commands-regex path-str)] (-> (mapcat parse-command commands) (simplify-commands))))) - diff --git a/common/src/app/common/path/bool.cljc b/common/src/app/common/svg/path/bool.cljc similarity index 99% rename from common/src/app/common/path/bool.cljc rename to common/src/app/common/svg/path/bool.cljc index 5a72e687f..10f8fdffe 100644 --- a/common/src/app/common/path/bool.cljc +++ b/common/src/app/common/svg/path/bool.cljc @@ -4,14 +4,14 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.common.path.bool +(ns app.common.svg.path.bool (:require [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] [app.common.geom.shapes.path :as gsp] - [app.common.path.commands :as upc] - [app.common.path.subpaths :as ups])) + [app.common.svg.path.command :as upc] + [app.common.svg.path.subpath :as ups])) (defn add-previous ([content] diff --git a/common/src/app/common/path/commands.cljc b/common/src/app/common/svg/path/command.cljc similarity index 99% rename from common/src/app/common/path/commands.cljc rename to common/src/app/common/svg/path/command.cljc index a434cb09e..7dd91e2a5 100644 --- a/common/src/app/common/path/commands.cljc +++ b/common/src/app/common/svg/path/command.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.common.path.commands +(ns app.common.svg.path.command (:require [app.common.data :as d] [app.common.geom.point :as gpt])) diff --git a/common/src/app/common/path/shapes_to_path.cljc b/common/src/app/common/svg/path/shapes_to_path.cljc similarity index 98% rename from common/src/app/common/path/shapes_to_path.cljc rename to common/src/app/common/svg/path/shapes_to_path.cljc index 6f43e0a24..0c3174a3e 100644 --- a/common/src/app/common/path/shapes_to_path.cljc +++ b/common/src/app/common/svg/path/shapes_to_path.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.common.path.shapes-to-path +(ns app.common.svg.path.shapes-to-path (:require [app.common.colors :as clr] [app.common.data :as d] @@ -13,8 +13,8 @@ [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.corners :as gso] [app.common.geom.shapes.path :as gsp] - [app.common.path.bool :as pb] - [app.common.path.commands :as pc] + [app.common.svg.path.bool :as pb] + [app.common.svg.path.command :as pc] [app.common.types.shape.radius :as ctsr])) (def ^:const bezier-circle-c 0.551915024494) diff --git a/common/src/app/common/path/subpaths.cljc b/common/src/app/common/svg/path/subpath.cljc similarity index 98% rename from common/src/app/common/path/subpaths.cljc rename to common/src/app/common/svg/path/subpath.cljc index ba1fb7325..2000debb3 100644 --- a/common/src/app/common/path/subpaths.cljc +++ b/common/src/app/common/svg/path/subpath.cljc @@ -4,11 +4,11 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.common.path.subpaths +(ns app.common.svg.path.subpath (:require [app.common.data :as d] [app.common.geom.point :as gpt] - [app.common.path.commands :as upc])) + [app.common.svg.path.command :as upc])) (defn pt= "Check if two points are close" diff --git a/common/src/app/common/svg/shapes_builder.cljc b/common/src/app/common/svg/shapes_builder.cljc new file mode 100644 index 000000000..d14eafeca --- /dev/null +++ b/common/src/app/common/svg/shapes_builder.cljc @@ -0,0 +1,534 @@ +;; 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) KALEIDOS INC + +(ns app.common.svg.shapes-builder + "A SVG to Shapes builder." + (:require + [app.common.colors :as clr] + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.files.helpers :as cfh] + [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.geom.shapes.common :as gsc] + [app.common.geom.shapes.transforms :as gst] + [app.common.math :as mth] + [app.common.schema :as sm :refer [max-safe-int min-safe-int]] + [app.common.svg :as csvg] + [app.common.svg.path :as path] + [app.common.types.shape :as cts] + [cuerdas.core :as str])) + +(def default-rect + {:x 0 :y 0 :width 1 :height 1}) + +(defn- assert-valid-num [attr num] + (dm/verify! + ["%1 attribute has invalid value: %2" (d/name attr) num] + (and (d/num? num) + (<= num max-safe-int) + (>= num min-safe-int))) + + (cond + (and (> num 0) (< num 1)) 1 + (and (< num 0) (> num -1)) -1 + :else num)) + +(defn- assert-valid-pos-num + [attr num] + + (dm/verify! + ["%1 attribute should be positive" (d/name attr)] + (pos? num)) + + num) + +(defn- assert-valid-blend-mode + [mode] + (let [clean-value (-> mode str/trim str/lower keyword)] + (dm/verify! + ["%1 is not a valid blend mode" clean-value] + (contains? cts/blend-modes clean-value)) + clean-value)) + +(defn- svg-dimensions + [data] + (let [width (dm/get-in data [:attrs :width] 100) + height (dm/get-in data [:attrs :height] 100) + viewbox (or (dm/get-in data [:attrs :viewBox]) + (dm/str "0 0 " width " " height)) + [x y width height] (->> (str/split viewbox #"\s+") + (map d/parse-double)) + width (if (= width 0) 1 width) + height (if (= height 0) 1 height)] + + [(assert-valid-num :x x) + (assert-valid-num :y y) + (assert-valid-pos-num :width width) + (assert-valid-pos-num :height height)])) + +(declare create-svg-root) +(declare create-svg-children) +(declare parse-svg-element) + +(defn create-svg-shapes + [svg-data {:keys [x y]} objects frame-id parent-id selected center?] + (let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) + + + unames (cfh/get-used-names objects) + svg-name (str/replace (:name svg-data) ".svg" "") + + svg-data (-> svg-data + (assoc :x (mth/round + (if center? + (- x vb-x (/ vb-width 2)) + x))) + (assoc :y (mth/round + (if center? + (- y vb-y (/ vb-height 2)) + y))) + (assoc :offset-x vb-x) + (assoc :offset-y vb-y) + (assoc :width vb-width) + (assoc :height vb-height) + (assoc :name svg-name)) + + [def-nodes svg-data] + (-> svg-data + (csvg/fix-default-values) + (csvg/fix-percents) + (csvg/extract-defs)) + + ;; 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 + ;; size + background + {:tag :rect + :attrs {:x (dm/str vb-x) + :y (dm/str vb-y) + :width (dm/str vb-width) + :height (dm/str vb-height) + :fill "none" + :id "base-background"} + :hidden true + :content []} + + svg-data (-> svg-data + (assoc :defs def-nodes) + (assoc :content (into [background] (:content svg-data)))) + + root-shape (create-svg-root frame-id parent-id svg-data) + root-id (:id root-shape) + + ;; Create the root shape + root-attrs (-> (:attrs svg-data) + (csvg/format-styles)) + + [_ children] + (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 %)))))] + + [root-shape children])) + +(defn create-raw-svg + [name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}] + (let [props (csvg/attrs->props attrs) + vbox (grc/make-rect offset-x offset-y width height)] + (cts/setup-shape + {:type :svg-raw + :name name + :frame-id frame-id + :width width + :height height + :x x + :y y + :content data + :svg-attrs props + :svg-viewbox vbox}))) + +(defn create-svg-root + [frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}] + (let [props (-> (dissoc attrs :viewBox :view-box :xmlns) + (d/without-keys csvg/inheritable-props) + (csvg/attrs->props))] + (cts/setup-shape + {:type :group + :name name + :frame-id frame-id + :parent-id parent-id + :width width + :height height + :x (+ x offset-x) + :y (+ y offset-y) + :svg-attrs props}))) + +(defn create-svg-children + [objects selected frame-id parent-id svg-data [unames children] [_index svg-element]] + (let [[shape new-children] (parse-svg-element frame-id svg-data svg-element unames)] + (if (some? shape) + (let [shape-id (:id shape) + shape (-> shape + (assoc :frame-id frame-id) + (assoc :parent-id parent-id)) + children (conj children shape) + unames (conj unames (:name shape))] + + (reduce (partial create-svg-children objects selected frame-id shape-id svg-data) + [unames children] + (d/enumerate new-children))) + + [unames children]))) + + +(defn create-group + [name frame-id {:keys [x y width height offset-x offset-y] :as svg-data} {:keys [attrs]}] + (let [transform (csvg/parse-transform (:transform attrs)) + attrs (-> (d/without-keys attrs csvg/inheritable-props) + (csvg/attrs->props)) + vbox (grc/make-rect offset-x offset-y width height)] + (cts/setup-shape + {:type :group + :name name + :frame-id frame-id + :x (+ x offset-x) + :y (+ y offset-y) + :width width + :height height + :svg-transform transform + :svg-attrs attrs + :svg-viewbox vbox}))) + +(defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}] + (when (and (contains? attrs :d) (seq (:d attrs))) + (let [transform (csvg/parse-transform (:transform attrs)) + content (cond-> (path/parse (:d attrs)) + (some? transform) + (gsh/transform-content transform)) + + selrect (gsh/content->selrect content) + points (grc/rect->points selrect) + origin (gpt/negate (gpt/point svg-data)) + attrs (-> (dissoc attrs :d :transform) + (csvg/attrs->props))] + (-> (cts/setup-shape + {:type :path + :name name + :frame-id frame-id + :content content + :selrect selrect + :points points + :svg-viewbox selrect + :svg-attrs attrs + :svg-transform transform + :fills []}) + (gsh/translate-to-frame origin))))) + +(defn calculate-rect-metadata + [rect transform] + (let [points (-> rect + (grc/rect->points) + (gsh/transform-points transform)) + center (gsc/points->center points) + selrect (gst/calculate-selrect points center) + transform (gst/calculate-transform points center selrect)] + + {:x (:x selrect) + :y (:y selrect) + :width (:width selrect) + :height (:height selrect) + :selrect selrect + :points points + :transform transform + :transform-inverse (when (some? transform) + (gmt/inverse transform))})) + +(defn- parse-rect-attrs + [{:keys [x y width height]}] + (grc/make-rect + (d/parse-double x 0) + (d/parse-double y 0) + (d/parse-double width 1) + (d/parse-double height 1))) + +(defn create-rect-shape [name frame-id svg-data {:keys [attrs] :as data}] + (let [transform (->> (csvg/parse-transform (:transform attrs)) + (gmt/transform-in (gpt/point svg-data))) + + origin (gpt/negate (gpt/point svg-data)) + rect (-> (parse-rect-attrs attrs) + (update :x - (:x origin)) + (update :y - (:y origin))) + + props (-> (dissoc attrs :x :y :width :height :rx :ry :transform) + (csvg/attrs->props))] + + (cts/setup-shape + (-> (calculate-rect-metadata rect transform) + (assoc :type :rect) + (assoc :name name) + (assoc :frame-id frame-id) + (assoc :svg-viewbox rect) + (assoc :svg-attrs props) + ;; We need to ensure fills are empty on import process + ;; because setup-shape assings one by default. + (assoc :fills []) + (cond-> (contains? attrs :rx) + (assoc :rx (d/parse-double (:rx attrs) 0))) + (cond-> (contains? attrs :ry) + (assoc :ry (d/parse-double (:ry attrs) 0))))))) + +(defn- parse-circle-attrs + [attrs] + (into [] (comp (map (d/getf attrs)) + (map d/parse-double)) + [:cx :cy :r :rx :ry])) + +(defn create-circle-shape + [name frame-id svg-data {:keys [attrs] :as data}] + (let [[cx cy r rx ry] + (parse-circle-attrs attrs) + + transform (->> (csvg/parse-transform (:transform attrs)) + (gmt/transform-in (gpt/point svg-data))) + + rx (d/nilv r rx) + ry (d/nilv r ry) + origin (gpt/negate (gpt/point svg-data)) + + rect (grc/make-rect + (- cx rx (:x origin)) + (- cy ry (:y origin)) + (* 2 rx) + (* 2 ry)) + props (-> (dissoc attrs :cx :cy :r :rx :ry :transform) + (csvg/attrs->props))] + + (cts/setup-shape + (-> (calculate-rect-metadata rect transform) + (assoc :type :circle) + (assoc :name name) + (assoc :frame-id frame-id) + (assoc :svg-viewbox rect) + (assoc :svg-attrs props) + (assoc :fills []))))) + +(defn create-image-shape + [name frame-id svg-data {:keys [attrs] :as data}] + (let [transform (->> (csvg/parse-transform (:transform attrs)) + (gmt/transform-in (gpt/point svg-data))) + + image-url (or (:href attrs) (:xlink:href attrs)) + image-data (dm/get-in svg-data [:image-data image-url]) + + metadata {:width (:width image-data) + :height (:height image-data) + :mtype (:mtype image-data) + :id (:id image-data)} + + origin (gpt/negate (gpt/point svg-data)) + rect (-> (parse-rect-attrs attrs) + (update :x - (:x origin)) + (update :y - (:y origin))) + props (-> (dissoc attrs :x :y :width :height :href :xlink:href) + (csvg/attrs->props))] + + (when (some? image-data) + (cts/setup-shape + (-> (calculate-rect-metadata rect transform) + (assoc :type :image) + (assoc :name name) + (assoc :frame-id frame-id) + (assoc :metadata metadata) + (assoc :svg-viewbox rect) + (assoc :svg-attrs props)))))) + +(defn setup-fill + [shape] + (let [color-attr (str/trim (dm/get-in shape [:svg-attrs :fill])) + 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)] + (cond-> shape + ;; Color present as attribute + (clr/color-string? color-attr) + (-> (update :svg-attrs dissoc :fill) + (update-in [:svg-attrs :style] dissoc :fill) + (assoc-in [:fills 0 :fill-color] (clr/parse color-attr))) + + ;; Color present as style + (clr/color-string? color-style) + (-> (update-in [:svg-attrs :style] dissoc :fill) + (update :svg-attrs dissoc :fill) + (assoc-in [:fills 0 :fill-color] (clr/parse color-style))) + + (dm/get-in shape [:svg-attrs :fillOpacity]) + (-> (update :svg-attrs dissoc :fillOpacity) + (update-in [:svg-attrs :style] dissoc :fillOpacity) + (assoc-in [:fills 0 :fill-opacity] (-> (dm/get-in shape [:svg-attrs :fillOpacity]) + (d/parse-double 1)))) + + (dm/get-in shape [:svg-attrs :style :fillOpacity]) + (-> (update-in [:svg-attrs :style] dissoc :fillOpacity) + (update :svg-attrs dissoc :fillOpacity) + (assoc-in [:fills 0 :fill-opacity] (-> (dm/get-in shape [:svg-attrs :style :fillOpacity]) + (d/parse-double 1))))))) +(defn- setup-stroke + [shape] + (let [attrs (get shape :svg-attrs) + style (get attrs :style) + + stroke (or (str/trim (:stroke attrs)) + (str/trim (:stroke style))) + + color (cond + (= stroke "currentColor") clr/black + (= stroke "none") nil + :else (clr/parse stroke)) + + opacity (when (some? color) + (d/parse-double + (or (:strokeOpacity attrs) + (:strokeOpacity style)) + 1)) + + width (when (some? color) + (d/parse-double + (or (:strokeWidth attrs) + (:strokeWidth style)) + 1)) + + linecap (or (get attrs :strokeLinecap) + (get style :strokeLinecap)) + linecap (some-> linecap str/trim keyword) + + attrs (-> attrs + (dissoc :stroke) + (dissoc :strokeWidth) + (dissoc :strokeOpacity) + (update :style (fn [style] + (-> style + (dissoc :stroke) + (dissoc :strokeLinecap) + (dissoc :strokeWidth) + (dissoc :strokeOpacity)))))] + + (cond-> (assoc shape :svg-attrs attrs) + (some? color) + (assoc-in [:strokes 0 :stroke-color] color) + + (and (some? color) (some? opacity)) + (assoc-in [:strokes 0 :stroke-opacity] opacity) + + (and (some? color) (some? width)) + (assoc-in [:strokes 0 :stroke-width] width) + + (and (some? linecap) (= (:type shape) :path) + (or (= linecap :round) (= linecap :square))) + (assoc :stroke-cap-start linecap + :stroke-cap-end linecap) + + (d/any-key? (dm/get-in shape [:strokes 0]) + :strokeColor :strokeOpacity :strokeWidth + :strokeCapStart :strokeCapEnd) + (assoc-in [:strokes 0 :stroke-style] :svg)))) + +(defn setup-opacity [shape] + (cond-> shape + (dm/get-in shape [:svg-attrs :opacity]) + (-> (update :svg-attrs dissoc :opacity) + (assoc :opacity (-> (dm/get-in shape [:svg-attrs :opacity]) + (d/parse-double 1)))) + + (dm/get-in shape [:svg-attrs :style :opacity]) + (-> (update-in [:svg-attrs :style] dissoc :opacity) + (assoc :opacity (-> (dm/get-in shape [:svg-attrs :style :opacity]) + (d/parse-double 1)))) + + (dm/get-in shape [:svg-attrs :mixBlendMode]) + (-> (update :svg-attrs dissoc :mixBlendMode) + (assoc :blend-mode (-> (dm/get-in shape [:svg-attrs :mixBlendMode]) assert-valid-blend-mode))) + + (dm/get-in shape [:svg-attrs :style :mixBlendMode]) + (-> (update-in [:svg-attrs :style] dissoc :mixBlendMode) + (assoc :blend-mode (-> (dm/get-in shape [:svg-attrs :style :mixBlendMode]) assert-valid-blend-mode))))) + + + + +(defn tag->name + "Given a tag returns its layer name" + [tag] + (let [suffix (cond + (string? tag) tag + (keyword? tag) (d/name tag) + (nil? tag) "node" + :else (dm/str tag))] + (dm/str "svg-" suffix))) + +(defn parse-svg-element + [frame-id svg-data {:keys [tag attrs hidden] :as element} unames] + + ;; FIXME: there are cases where element is directly a string, I + ;; think we should handle this case early and avoid some code + ;; execution + + (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) + href-id (-> (or (:href attrs) (:xlink:href attrs) " ") (subs 1)) + use-tag? (and (= :use tag) (contains? defs href-id))] + + (if use-tag? + (let [;; Merge the data of the use definition with the properties passed as attributes + use-data (-> (get defs href-id) + (update :attrs #(d/deep-merge % (dissoc attrs :xlink:href :href)))) + displacement (gpt/point (d/parse-double (:x attrs "0")) (d/parse-double (:y attrs "0"))) + disp-matrix (dm/str (gmt/translate-matrix displacement)) + element (-> element + (assoc :tag :g) + (update :attrs dissoc :x :y :width :height :href :xlink:href :transform) + (update :attrs csvg/add-transform disp-matrix) + (assoc :content [use-data]))] + (parse-svg-element frame-id svg-data element unames)) + + (let [;; SVG graphic elements + ;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use + shape (case tag + (:g :a :svg) (create-group name frame-id svg-data element) + :rect (create-rect-shape name frame-id svg-data element) + (:circle + :ellipse) (create-circle-shape name frame-id svg-data element) + :path (create-path-shape name frame-id svg-data element) + :polyline (create-path-shape name frame-id svg-data (-> element csvg/polyline->path)) + :polygon (create-path-shape name frame-id svg-data (-> element csvg/polygon->path)) + :line (create-path-shape name frame-id svg-data (-> element csvg/line->path)) + :image (create-image-shape name frame-id svg-data element) + #_other (create-raw-svg name frame-id svg-data element))] + + + (when (some? shape) + (let [shape (-> shape + (assoc :svg-defs (select-keys defs references)) + (setup-fill) + (setup-stroke) + (setup-opacity) + (update :svg-attrs (fn [attrs] + (if (empty? (:style attrs)) + (dissoc attrs :style) + attrs))))] + [(cond-> shape + hidden (assoc :hidden true)) + + (cond->> (:content element) + (contains? csvg/parent-tags tag) + (mapv #(csvg/inherit-attributes attrs %)))])))))) diff --git a/common/test/common_tests/arc_to_bezier.js b/common/test/common_tests/arc_to_bezier.js new file mode 100644 index 000000000..bc2c1a843 --- /dev/null +++ b/common/test/common_tests/arc_to_bezier.js @@ -0,0 +1,208 @@ +/** + * Arc to Bezier curves transformer + * + * Is a modified and google closure compatible version of the a2c + * functions by https://github.com/fontello/svgpath + * + * @author KALEIDOS INC + * @license MIT License + */ + +"use strict"; + +goog.provide("common_tests.arc_to_bezier"); + +// https://raw.githubusercontent.com/fontello/svgpath/master/lib/a2c.js +goog.scope(function() { + const self = common_tests.arc_to_bezier; + + var TAU = Math.PI * 2; + + /* eslint-disable space-infix-ops */ + + // Calculate an angle between two unit vectors + // + // Since we measure angle between radii of circular arcs, + // we can use simplified math (without length normalization) + // + function unit_vector_angle(ux, uy, vx, vy) { + var sign = (ux * vy - uy * vx < 0) ? -1 : 1; + var dot = ux * vx + uy * vy; + + // Add this to work with arbitrary vectors: + // dot /= Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy); + + // rounding errors, e.g. -1.0000000000000002 can screw up this + if (dot > 1.0) { dot = 1.0; } + if (dot < -1.0) { dot = -1.0; } + + return sign * Math.acos(dot); + } + + + // Convert from endpoint to center parameterization, + // see http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // + // Return [cx, cy, theta1, delta_theta] + // + function get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi) { + // Step 1. + // + // Moving an ellipse so origin will be the middlepoint between our two + // points. After that, rotate it to line up ellipse axes with coordinate + // axes. + // + var x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2; + var y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2; + + var rx_sq = rx * rx; + var ry_sq = ry * ry; + var x1p_sq = x1p * x1p; + var y1p_sq = y1p * y1p; + + // Step 2. + // + // Compute coordinates of the centre of this ellipse (cx', cy') + // in the new coordinate system. + // + var radicant = (rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq); + + if (radicant < 0) { + // due to rounding errors it might be e.g. -1.3877787807814457e-17 + radicant = 0; + } + + radicant /= (rx_sq * y1p_sq) + (ry_sq * x1p_sq); + radicant = Math.sqrt(radicant) * (fa === fs ? -1 : 1); + + var cxp = radicant * rx/ry * y1p; + var cyp = radicant * -ry/rx * x1p; + + // Step 3. + // + // Transform back to get centre coordinates (cx, cy) in the original + // coordinate system. + // + var cx = cos_phi*cxp - sin_phi*cyp + (x1+x2)/2; + var cy = sin_phi*cxp + cos_phi*cyp + (y1+y2)/2; + + // Step 4. + // + // Compute angles (theta1, delta_theta). + // + var v1x = (x1p - cxp) / rx; + var v1y = (y1p - cyp) / ry; + var v2x = (-x1p - cxp) / rx; + var v2y = (-y1p - cyp) / ry; + + var theta1 = unit_vector_angle(1, 0, v1x, v1y); + var delta_theta = unit_vector_angle(v1x, v1y, v2x, v2y); + + if (fs === 0 && delta_theta > 0) { + delta_theta -= TAU; + } + if (fs === 1 && delta_theta < 0) { + delta_theta += TAU; + } + + return [ cx, cy, theta1, delta_theta ]; + } + + // + // Approximate one unit arc segment with bézier curves, + // see http://math.stackexchange.com/questions/873224 + // + function approximate_unit_arc(theta1, delta_theta) { + var alpha = 4/3 * Math.tan(delta_theta/4); + + var x1 = Math.cos(theta1); + var y1 = Math.sin(theta1); + var x2 = Math.cos(theta1 + delta_theta); + var y2 = Math.sin(theta1 + delta_theta); + + return [ x1, y1, x1 - y1*alpha, y1 + x1*alpha, x2 + y2*alpha, y2 - x2*alpha, x2, y2 ]; + } + + function a2c(x1, y1, x2, y2, fa, fs, rx, ry, phi) { + var sin_phi = Math.sin(phi * TAU / 360); + var cos_phi = Math.cos(phi * TAU / 360); + + // Make sure radii are valid + // + var x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2; + var y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2; + + if (x1p === 0 && y1p === 0) { + // we're asked to draw line to itself + return []; + } + + if (rx === 0 || ry === 0) { + // one of the radii is zero + return []; + } + + + // Compensate out-of-range radii + // + rx = Math.abs(rx); + ry = Math.abs(ry); + + var lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry); + if (lambda > 1) { + rx *= Math.sqrt(lambda); + ry *= Math.sqrt(lambda); + } + + + // Get center parameters (cx, cy, theta1, delta_theta) + // + var cc = get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi); + + + var result = []; + var theta1 = cc[2]; + var delta_theta = cc[3]; + + + + // Split an arc to multiple segments, so each segment + // will be less than τ/4 (= 90°) + // + var segments = Math.max(Math.ceil(Math.abs(delta_theta) / (TAU / 4)), 1); + delta_theta /= segments; + + + for (var i = 0; i < segments; i++) { + var item = approximate_unit_arc(theta1, delta_theta); + result.push(item); + theta1 += delta_theta; + } + + // We have a bezier approximation of a unit circle, + // now need to transform back to the original ellipse + // + return result.map(function (curve) { + for (var i = 0; i < curve.length; i += 2) { + var x = curve[i + 0]; + var y = curve[i + 1]; + + // scale + x *= rx; + y *= ry; + + // rotate + var xp = cos_phi*x - sin_phi*y; + var yp = sin_phi*x + cos_phi*y; + + // translate + curve[i + 0] = xp + cc[0]; + curve[i + 1] = yp + cc[1]; + } + + return curve; + }); + } + + self.a2c = a2c; +}); diff --git a/common/test/common_tests/colors_test.cljc b/common/test/common_tests/colors_test.cljc new file mode 100644 index 000000000..277cf158c --- /dev/null +++ b/common/test/common_tests/colors_test.cljc @@ -0,0 +1,99 @@ +;; 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) KALEIDOS INC + +(ns common-tests.colors-test + (:require + [app.common.data :as d] + [app.common.colors :as colors] + [clojure.test :as t] + #?(:cljs [goog.color :as gcolors]))) + +(t/deftest valid-hex-color + (t/is (false? (colors/valid-hex-color? nil))) + (t/is (false? (colors/valid-hex-color? ""))) + (t/is (false? (colors/valid-hex-color? "#"))) + (t/is (false? (colors/valid-hex-color? "#qqqqqq"))) + (t/is (true? (colors/valid-hex-color? "#aaa"))) + (t/is (true? (colors/valid-hex-color? "#fabada"))) + ) + +(t/deftest valid-rgb-color + (t/is (false? (colors/valid-rgb-color? nil))) + (t/is (false? (colors/valid-rgb-color? ""))) + (t/is (false? (colors/valid-rgb-color? "()"))) + (t/is (true? (colors/valid-rgb-color? "(255, 30, 30)"))) + (t/is (true? (colors/valid-rgb-color? "rgb(255, 30, 30)"))) + ) + +(t/deftest rgb-to-str + (t/is (= "rgb(1,2,3)" (colors/rgb->str [1 2 3]))) + (t/is (= "rgba(1,2,3,4)" (colors/rgb->str [1 2 3 4])))) + +(t/deftest rgb-to-hsv + ;; (prn (colors/rgb->hsv [1 2 3])) + ;; (prn (gcolors/rgbToHsv 1 2 3)) + (t/is (= [210.0 0.6666666666666666 3.0] (colors/rgb->hsv [1.0 2.0 3.0]))) + #?(:cljs (t/is (= (colors/rgb->hsv [1 2 3]) (vec (gcolors/rgbToHsv 1 2 3))))) + ) + +(t/deftest hsv-to-rgb + (t/is (= [1 2 3] + (colors/hsv->rgb [210 0.6666666666666666 3]))) + #?(:cljs + (t/is (= (colors/hsv->rgb [210 0.6666666666666666 3]) + (vec (gcolors/hsvToRgb 210 0.6666666666666666 3))))) + ) + +(t/deftest rgb-to-hex + (t/is (= "#010203" (colors/rgb->hex [1 2 3])))) + +(t/deftest hex-to-rgb + (t/is (= [0 0 0] (colors/hex->rgb "#kkk"))) + (t/is (= [1 2 3] (colors/hex->rgb "#010203")))) + +#?(:cljs + (t/deftest format-hsla + (t/is (= "210, 50%, 1%, 1" (colors/format-hsla [210.0 0.5 0.00784313725490196 1]))))) + +(t/deftest rgb-to-hsl + (t/is (= [210.0 0.5 0.00784313725490196] (colors/rgb->hsl [1 2 3]))) + #?(:cljs (t/is (= (colors/rgb->hsl [1 2 3]) + (vec (gcolors/rgbToHsl 1 2 3)))))) + +(t/deftest hsl-to-rgb + (t/is (= [1 2 3] (colors/hsl->rgb [210.0 0.5 0.00784313725490196]))) + (t/is (= [210.0 0.5 0.00784313725490196] (colors/rgb->hsl [1 2 3]))) + #?(:cljs (t/is (= (colors/hsl->rgb [210 0.5 0.00784313725490196]) + (vec (gcolors/hslToRgb 210 0.5 0.00784313725490196))))) + + ) + +(t/deftest expand-hex + (t/is (= "aaaaaa" (colors/expand-hex "a"))) + (t/is (= "aaaaaa" (colors/expand-hex "aa"))) + (t/is (= "aaaaaa" (colors/expand-hex "aaa"))) + (t/is (= "aaaa" (colors/expand-hex "aaaa")))) + +(t/deftest prepend-hash + (t/is "#aaa" (colors/prepend-hash "aaa")) + (t/is "#aaa" (colors/prepend-hash "#aaa"))) + +(t/deftest remove-hash + (t/is "aaa" (colors/remove-hash "aaa")) + (t/is "aaa" (colors/remove-hash "#aaa"))) + +(t/deftest color-string-pred + (t/is (true? (colors/color-string? "#aaa"))) + (t/is (true? (colors/color-string? "(10,10,10)"))) + (t/is (true? (colors/color-string? "rgb(10,10,10)"))) + (t/is (true? (colors/color-string? "magenta"))) + (t/is (false? (colors/color-string? nil))) + (t/is (false? (colors/color-string? ""))) + (t/is (false? (colors/color-string? "kkkkkk"))) + + + ) + diff --git a/common/test/common_tests/helpers/files.cljc b/common/test/common_tests/helpers/files.cljc index fd033ef35..3da07f3a7 100644 --- a/common/test/common_tests/helpers/files.cljc +++ b/common/test/common_tests/helpers/files.cljc @@ -6,7 +6,7 @@ (ns common-tests.helpers.files (:require - [app.common.files.features :as ffeat] + [app.common.features :as ffeat] [app.common.geom.point :as gpt] [app.common.types.colors-list :as ctcl] [app.common.types.components-list :as ctkl] diff --git a/common/test/common_tests/pages_test.cljc b/common/test/common_tests/pages_test.cljc index db27463f7..be4eaa117 100644 --- a/common/test/common_tests/pages_test.cljc +++ b/common/test/common_tests/pages_test.cljc @@ -6,7 +6,7 @@ (ns common-tests.pages-test (:require - [app.common.files.features :as ffeat] + [app.common.features :as ffeat] [app.common.pages :as cp] [app.common.types.file :as ctf] [app.common.types.shape :as cts] diff --git a/common/test/common_tests/svg_path_test.cljc b/common/test/common_tests/svg_path_test.cljc new file mode 100644 index 000000000..bd69702f5 --- /dev/null +++ b/common/test/common_tests/svg_path_test.cljc @@ -0,0 +1,111 @@ +;; 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) KALEIDOS INC + +(ns common-tests.svg-path-test + (:require + [app.common.data :as d] + [app.common.math :as mth] + [app.common.svg.path :as svg.path] + [clojure.test :as t] + #?(:cljs [common-tests.arc-to-bezier :as impl]))) + +(t/deftest arc-to-bezier-1 + (let [expected1 [-1.6697754290362354e-13 + -5.258016244624741e-13 + 182.99396814652343 + 578.9410968299095 + 338.05561855139365 + 1059.4584670906731 + 346.33988979885567 + 1073.265585836443] + expected2 [346.33988979885567 + 1073.265585836443 + 354.6241610463177 + 1087.0727045822134 + 212.99396814652377 + 628.9410968299106 + 30.00000000000016 + 50.000000000000504]] + + (let [[result1 result2 :as total] (svg.path/arc->beziers* 0 0 30 50 0 0 1 162.55 162.45)] + (t/is (= (count total) 2)) + (dotimes [i (count result1)] + (t/is (mth/close? (nth result1 i) + (nth expected1 i) + 0.000000000001))) + + (dotimes [i (count result2)] + (t/is (mth/close? (nth result2 i) + (nth expected2 i) + 0.000000000001)))) + + )) + +;; "m -994.563 4564.1423 149.3086 -52.8821 30.1828 -1.9265 5.2446 -117.5157 98.6828 -43.7312 219.9492 9.5361 9.0977 121.0797 115.0586 12.7148 -1.1774 75.7109 134.7524 3.1787 -6.1008 85.0544 -137.3211 59.9137 -301.293 -1.0595 -51.375 25.7186 -261.0492 -7.706 " [[:x :number] [:y :number]] + +(t/deftest extract-params-1 + (let [expected [{:x -994.563, :y 4564.1423} + {:x 149.3086, :y -52.8821} + {:x 30.1828, :y -1.9265} + {:x 5.2446, :y -117.5157} + {:x 98.6828, :y -43.7312} + {:x 219.9492, :y 9.5361} + {:x 9.0977, :y 121.0797} + {:x 115.0586, :y 12.7148} + {:x -1.1774, :y 75.7109} + {:x 134.7524, :y 3.1787} + {:x -6.1008, :y 85.0544} + {:x -137.3211, :y 59.9137} + {:x -301.293, :y -1.0595} + {:x -51.375, :y 25.7186} + {:x -261.0492, :y -7.706}] + cmdstr (str "m -994.563 4564.1423 149.3086 -52.8821 30.1828 " + "-1.9265 5.2446 -117.5157 98.6828 -43.7312 219.9492 " + "9.5361 9.0977 121.0797 115.0586 12.7148 -1.1774 " + "75.7109 134.7524 3.1787 -6.1008 85.0544 -137.3211 " + "59.9137 -301.293 -1.0595 -51.375 25.7186 -261.0492 -7.706 ") + pattern [[:x :number] [:y :number]]] + + (t/is (= expected (svg.path/extract-params cmdstr pattern))))) + +(t/deftest extract-params-2 + (let [expected [{:x -994.563, :y 4564.1423 :r 0}] + cmdstr (str "m -994.563 4564.1423 0") + pattern [[:x :number] [:y :number] [:r :flag]]] + + (t/is (= expected (svg.path/extract-params cmdstr pattern))))) + +(t/deftest extract-params-3 + (let [cmdstr (str "a1.42 1.42 0 00-1.415-1.416 1.42 1.42 0 00-1.416 1.416 " + "1.42 1.42 0 001.416 1.415 1.42 1.42 0 001.415-1.415") + + expected [{:rx 1.42, :ry 1.42, :x-axis-rotation 0.0, :large-arc-flag 0, :sweep-flag 0, :x -1.415, :y -1.416} + {:rx 1.42, :ry 1.42, :x-axis-rotation 0.0, :large-arc-flag 0, :sweep-flag 0, :x -1.416, :y 1.416} + {:rx 1.42, :ry 1.42, :x-axis-rotation 0.0, :large-arc-flag 0, :sweep-flag 0, :x 1.416, :y 1.415} + {:rx 1.42, :ry 1.42, :x-axis-rotation 0.0, :large-arc-flag 0, :sweep-flag 0, :x 1.415, :y -1.415}] + + pattern [[:rx :number] + [:ry :number] + [:x-axis-rotation :number] + [:large-arc-flag :flag] + [:sweep-flag :flag] + [:x :number] + [:y :number]] + result (svg.path/extract-params cmdstr pattern)] + + (t/is (= (nth result 0) + (nth expected 0))) + (t/is (= (nth result 1) + (nth expected 1))) + (t/is (= (nth result 2) + (nth expected 2))) + (t/is (= (nth result 3) + (nth expected 3))) + )) + +;; FOR POSSIBLE FUTURE TEST CASES +;; (str "M259.958 89.134c-6.88-.354-10.484-1.241-12.44-3.064-1.871-1.743-6.937-3.098-15.793-4.226-7.171-.913-17.179-2.279-22.24-3.034-5.06-.755-15.252-2.016-22.648-2.8-18.685-1.985-35.63-4.223-38.572-5.096-3.655-1.084-3.016-3.548.708-2.726 1.751.387 13.376 1.701 25.833 2.922 12.456 1.22 29.018 3.114 36.803 4.208 29.94 4.206 29.433 4.204 34.267.136 3.787-3.186 5.669-3.669 14.303-3.669 14.338 0 17.18 1.681 12.182 7.205-2.053 2.268-1.994 2.719.707 5.42 3.828 3.827 3.74 5.846-.238 5.5-1.752-.153-7.544-.502-12.872-.776zm7.563-3.194c0-.778-1.751-1.352-3.892-1.274l-3.893.141 3.539 1.133c1.946.624 3.698 1.197 3.893 1.275.194.077.354-.496.354-1.275zm-15.899-8.493c1.43-2.29 1.414-2.83-.084-2.83-2.05 0-5.25 2.76-5.25 4.529 0 2.226 3.599 1.08 5.334-1.699zm8.114 0c2.486-2.746 2.473-2.83-.438-2.83-1.65 0-3.683 1.273-4.516 2.83-1.175 2.196-1.077 2.831.438 2.831 1.075 0 3.107-1.274 4.516-2.83zm7.814.674c2.858-3.444.476-4.085-3.033-.816-2.451 2.284-2.677 2.973-.975 2.973 1.22 0 3.023-.97 4.008-2.157zm-49.571-4.509c-1.168-.43-3.294-1.802-4.725-3.051-2.112-1.843-9.304-2.595-38.219-3.994-46.474-2.25-63-4.077-60.27-6.665.324-.308 9.507.261 20.406 1.264 10.9 1.003 31.16 2.258 45.024 2.789l25.207.964 4.625-3.527c4.313-3.29 5.41-3.474 16.24-2.732 6.389.438 11.981 1.388 12.428 2.111.447.723-.517 2.73-2.141 4.46l-2.954 3.144c1.607 1.697 3.308 3.289 5.049 4.845 3.248 2.189-5.438 1.289-8.678 1.284-5.428-.061-10.825-.463-11.992-.892zm12.74-3.242c-1.123-.694-2.36-.943-2.75-.554-.389.39.21 1.275 1.334 1.97 1.122.693 2.36.942 2.749.553.389-.39-.21-1.275-1.334-1.97zm-5.663 0a1.42 1.42 0 00-1.415-1.416 1.42 1.42 0 00-1.416 1.416 1.42 1.42 0 001.416 1.415 1.42 1.42 0 001.415-1.415zm-8.464-6.404c.984-1.187 1.35-2.598.813-3.135-1.181-1.18-5.408 1.297-6.184 3.624-.806 2.42 3.265 2.048 5.37-.49zm6.863.258c.867-1.045 1.163-2.313.658-2.819-1.063-1.062-4.719 1.631-4.719 3.476 0 1.864 2.274 1.496 4.061-.657zm8.792-.36c1.637-1.972 1.448-2.197-1.486-1.77-1.848.27-3.622 1.287-3.943 2.26-.838 2.547 3.212 2.181 5.429-.49zm32.443-4.11c-6.156-2.228-67.1-6.138-119.124-7.642-39.208-1.134-72.072-.928-94.618.593-6.617.446-19.681 1.16-29.03 1.587-15.798.72-17.183.573-19.588-2.085-4.498-4.97-2.544-7.857 6.39-9.44 4.394-.778 9.164-2.436 10.6-3.685 5.44-4.729 20.332-14.06 31.14-19.509C65.717 11.88 78.955 7.79 103.837 3.08 121.686-.3 125.552-.642 129.318.82c2.44.948 12.4 1.948 22.132 2.221 15.37.432 20.004 1.18 35.294 5.698 22.36 6.606 39.732 15.1 56.55 27.653 7.307 5.452 14.086 9.913 15.066 9.913.98 0 2.148.956 2.596 2.124.55 1.432 2.798 2.123 6.914 2.123 6.213 0 12.4 3.046 12.38 6.096-.012 1.75-6.502 5.353-9.118 5.063-.818-.09-3.717-.972-6.442-1.958zm-16.986-7.436c0-1.575-33.326-18.118-43.173-21.43-23.008-7.739-54.084-12.922-77.136-12.866-16.863.041-37.877 3.628-52.465 8.956-18.062 6.596-26.563 10.384-29.181 13.002-1.205 1.205-5.306 3.769-9.112 5.698-7.754 3.929-8.841 5.482-3.029 4.325 13.494-2.685 66.794-3.773 110.913-2.264 38.005 1.3 96.812 4.435 102.122 5.443.584.111 1.061-.277 1.061-.864zm-236.39-3.18c0-.78-1.592-1.416-3.539-1.416-1.946 0-3.538.637-3.538 1.415 0 .779 1.592 1.416 3.538 1.416 1.947 0 3.54-.637 3.54-1.416zm7.078-1.416c0-.779-.956-1.416-2.124-1.416-1.167 0-2.123.637-2.123 1.416 0 .778.956 1.415 2.123 1.415 1.168 0 2.124-.637 2.124-1.415zm11.734-4.437c3.278-1.661 6.278-3.483 6.667-4.048 1.366-1.98 20.645-11.231 32.557-15.622 11.862-4.372 36.546-9.865 44.327-9.865 3.485 0 3.867-.404 3.012-3.185-.538-1.752-1.177-3.41-1.42-3.685-.907-1.026-36.72 7.16-45.065 10.302-17.226 6.484-47.566 24.27-47.566 27.886 0 1.786.845 1.585 7.488-1.783zm206.254-5.577c-12.298-10.518-53.842-27.166-70.896-28.41-5.526-.404-6.3-.097-6.695 2.655-.33 2.307.402 3.275 2.831 3.742 32.436 6.237 52.205 12.315 66.975 20.594 11.904 6.673 14.477 7.141 7.785 1.419zM150.1 11.04c-1.949-3.64-7.568-4.078-6.886-.538.256 1.329 2.054 2.817 3.997 3.309 4.498 1.137 4.816.832 2.888-2.771zm6.756.94c-.248-1.752-1.026-3.185-1.727-3.185-.7 0-1.493 1.433-1.76 3.185-.328 2.152.232 3.185 1.727 3.185 1.485 0 2.064-1.047 1.76-3.185zm-30.178-2.458c0-2.303-.908-3.694-2.627-4.025-3.6-.694-5.23 1.301-4.22 5.166 1.216 4.647 6.847 3.709 6.847-1.14zm12.544 2.104c-.448-1.168-1.224-2.132-1.725-2.142-.5-.013-2.343-.404-4.095-.873-2.569-.689-3.185-.274-3.185 2.142 0 2.476.854 2.996 4.91 2.996 3.783 0 4.723-.487 4.095-2.123z") + diff --git a/common/test/common_tests/svg_test.cljc b/common/test/common_tests/svg_test.cljc new file mode 100644 index 000000000..f5731999f --- /dev/null +++ b/common/test/common_tests/svg_test.cljc @@ -0,0 +1,38 @@ +;; 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) KALEIDOS INC + +(ns common-tests.svg-test + (:require + [app.common.data :as d] + [app.common.svg :as svg] + [clojure.test :as t])) + +(t/deftest clean-attrs-1 + (let [attrs {:class "foobar"} + result (svg/clean-attrs attrs)] + (t/is (= result {:className "foobar"})))) + +(t/deftest clean-attrs-2 + (let [attrs {:overline-position "top" + :style {:fill "none" + :stroke-dashoffset 1}} + result (svg/clean-attrs attrs true)] + (t/is (= result {:overlinePosition "top", :style {:fill "none", :strokeDashoffset 1}})))) + +(t/deftest clean-attrs-3 + (let [attrs {:overline-position "top" + :style (str "fill:#00801b;fill-opacity:1;stroke:none;stroke-width:2749.72;" + "stroke-linecap:round;stroke-dasharray:none;stop-color:#000000")} + result (svg/clean-attrs attrs true)] + (t/is (= result {:overlinePosition "top", + :style {:fill "#00801b", + :fillOpacity "1", + :stroke "none", + :strokeWidth "2749.72", + :strokeLinecap "round", + :strokeDasharray "none", + :stopColor "#000000"}})))) + diff --git a/common/vendor/beicon/impl/rxjs.cljs b/common/vendor/beicon/impl/rxjs.cljs new file mode 100644 index 000000000..6fa0bcfa0 --- /dev/null +++ b/common/vendor/beicon/impl/rxjs.cljs @@ -0,0 +1,4 @@ +(ns beicon.impl.rxjs + (:require ["rxjs" :as rx])) + +(goog/exportSymbol "rxjsMain" rx) diff --git a/common/vendor/beicon/impl/rxjs_operators.cljs b/common/vendor/beicon/impl/rxjs_operators.cljs new file mode 100644 index 000000000..22b4e2313 --- /dev/null +++ b/common/vendor/beicon/impl/rxjs_operators.cljs @@ -0,0 +1,4 @@ +(ns beicon.impl.rxjs-operators + (:require ["rxjs/operators" :as rxop])) + +(goog/exportSymbol "rxjsOperators" rxop) diff --git a/common/vendor/tubax/saxjs.cljs b/common/vendor/tubax/saxjs.cljs new file mode 100644 index 000000000..3dc98550b --- /dev/null +++ b/common/vendor/tubax/saxjs.cljs @@ -0,0 +1,4 @@ +(ns tubax.saxjs + (:require ["sax" :as sax])) + +(goog/exportSymbol "sax" sax) diff --git a/common/yarn.lock b/common/yarn.lock index cd89df192..2831c2061 100644 --- a/common/yarn.lock +++ b/common/yarn.lock @@ -515,6 +515,11 @@ safer-buffer@^2.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sax@^1.2.4: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" + integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== + setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" diff --git a/frontend/deps.edn b/frontend/deps.edn index 260186222..fc0e78170 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -10,7 +10,6 @@ funcool/beicon {:mvn/version "2021.07.05-1"} funcool/okulary {:mvn/version "2022.04.11-16"} funcool/potok {:mvn/version "2022.12.16-71"} - funcool/tubax {:mvn/version "2021.05.20-0"} funcool/rumext {:git/tag "v2.7" diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 2bd275501..6e4f6e7c3 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -11,6 +11,8 @@ [app.common.data.macros :as dm] [app.common.files.features :as ffeat] [app.common.files.helpers :as cfh] + [app.common.files.libraries-helpers :as cflh] + [app.common.files.shapes-helpers :as cfsh] [app.common.geom.align :as gal] [app.common.geom.point :as gpt] [app.common.geom.proportions :as gpp] @@ -55,7 +57,6 @@ [app.main.data.workspace.layers :as dwly] [app.main.data.workspace.layout :as layout] [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.libraries-helpers :as dwlh] [app.main.data.workspace.media :as dwm] [app.main.data.workspace.notifications :as dwn] [app.main.data.workspace.path :as dwdp] @@ -2077,14 +2078,14 @@ page (cons shape children)) - [_ _ changes2] (dwlh/generate-add-component it + [_ _ changes2] (cflh/generate-add-component it [shape] (:objects page') (:id page) (:id file-data) true nil - dwsh/prepare-create-artboard-from-selection) + cfsh/prepare-create-artboard-from-selection) changes (pcb/concat-changes changes1 changes2)] diff --git a/frontend/src/app/main/data/workspace/bool.cljs b/frontend/src/app/main/data/workspace/bool.cljs index d0181e9b0..9945ce336 100644 --- a/frontend/src/app/main/data/workspace/bool.cljs +++ b/frontend/src/app/main/data/workspace/bool.cljs @@ -10,7 +10,7 @@ [app.common.geom.shapes :as gsh] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] - [app.common.path.shapes-to-path :as stp] + [app.common.svg.path.shapes-to-path :as stp] [app.common.types.container :as ctn] [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index 3fdb48ac9..77a42e088 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -6,7 +6,7 @@ (ns app.main.data.workspace.colors (:require - [app.common.colors :as colors] + [app.common.colors :as cc] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] @@ -355,7 +355,7 @@ (assoc-in [:workspace-global :picking-color?] true) (assoc ::md/modal {:id (random-uuid) :type :colorpicker - :props {:data {:color colors/black + :props {:data {:color cc/black :opacity 1} :disable-opacity false :disable-gradient false @@ -438,9 +438,9 @@ (defn split-color-components [{:keys [color opacity] :as data}] - (let [value (if (uc/hex? color) color colors/black) - [r g b] (uc/hex->rgb value) - [h s v] (uc/hex->hsv value)] + (let [value (if (cc/valid-hex-color? color) color cc/black) + [r g b] (cc/hex->rgb value) + [h s v] (cc/hex->hsv value)] (merge data {:hex (or value "000000") :alpha (or opacity 1) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index a3424b7e0..0b28dc6c8 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -9,6 +9,8 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.features :as ffeat] + [app.common.files.libraries-helpers :as cflh] + [app.common.files.shapes-helpers :as cfsh] [app.common.geom.point :as gpt] [app.common.logging :as log] [app.common.pages :as cp] @@ -305,9 +307,9 @@ parents (into #{} (map :parent-id) shapes)] (when-not (empty? shapes) (let [[root _ changes] - (dwlh/generate-add-component it shapes objects page-id file-id components-v2 + (cflh/generate-add-component it shapes objects page-id file-id components-v2 dwg/prepare-create-group - dwsh/prepare-create-artboard-from-selection)] + cfsh/prepare-create-artboard-from-selection)] (when-not (empty? (:redo-changes changes)) (rx/of (dch/commit-changes changes) (dws/select-shapes (d/ordered-set (:id root))) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index f6b0a841d..ffa0a1240 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -25,7 +25,6 @@ [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] [app.common.types.typography :as cty] - [app.common.uuid :as uuid] [app.main.data.workspace.state-helpers :as wsh] [cljs.spec.alpha :as s] [clojure.set :as set])) @@ -63,75 +62,6 @@ ;; ---- Components and instances creation ---- -(defn generate-add-component-changes - [changes root objects file-id page-id components-v2] - (let [name (:name root) - [path name] (cph/parse-path-name name) - - [root-shape new-shapes updated-shapes] - (if-not components-v2 - (ctn/make-component-shape root objects file-id components-v2) - (ctn/convert-shape-in-component root objects file-id)) - - changes (-> changes - (pcb/add-component (:id root-shape) - path - name - new-shapes - updated-shapes - (:id root) - page-id))] - [root-shape changes])) - -(defn generate-add-component - "If there is exactly one id, and it's a frame (or a group in v1), and not already a component, - use it as root. Otherwise, create a frame (v2) or group (v1) that contains all ids. Then, make a - component with it, and link all shapes to their corresponding one in the component." - [it shapes objects page-id file-id components-v2 prepare-create-group prepare-create-board] - (let [changes (pcb/empty-changes it page-id) - - [root changes old-root-ids] - (if (and (= (count shapes) 1) - (or (and (= (:type (first shapes)) :group) (not components-v2)) - (= (:type (first shapes)) :frame)) - (not (ctk/instance-head? (first shapes)))) - - [(first shapes) - (-> (pcb/empty-changes it page-id) - (pcb/with-objects objects)) - (:shapes (first shapes))] - - (let [root-name (if (= 1 (count shapes)) - (:name (first shapes)) - "Component 1") - - [root changes] (if-not components-v2 - (prepare-create-group it ; These functions needs to be passed as argument - objects ; to avoid a circular dependence - page-id - shapes - root-name - (not (ctk/instance-head? (first shapes)))) - (prepare-create-board changes - (uuid/next) - (:parent-id (first shapes)) - objects - (map :id shapes) - nil - root-name - true))] - - [root changes (map :id shapes)])) - - [root-shape changes] (generate-add-component-changes changes root objects file-id page-id components-v2) - - changes (pcb/update-shapes changes - old-root-ids - #(dissoc % :component-root) - [:component-root])] - - [root (:id root-shape) changes])) - (defn duplicate-component "Clone the root shape of the component and all children. Generate new ids from all of them." @@ -141,7 +71,10 @@ (let [main-instance-page (ctf/get-component-page library-data component) main-instance-shape (ctf/get-component-root library-data component) - position (gpt/point (+ (:x main-instance-shape) (:width main-instance-shape) 50) (:y main-instance-shape)) + position (gpt/point (+ (:x main-instance-shape) + (:width main-instance-shape) + 50) + (:y main-instance-shape)) options (if components-v2 {:main-instance? true} {}) [new-instance-shape new-instance-shapes] diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 121d32be1..cac7ba07c 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -14,6 +14,7 @@ [app.common.pages.changes-builder :as pcb] [app.common.schema :as sm] [app.common.svg :refer [optimize]] + [app.common.svg.shapes-builder :as csvg.shapes-builder] [app.common.types.container :as ctn] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] @@ -274,7 +275,7 @@ process-svg (fn [svg-data] (let [[shape children] - (svg/create-svg-shapes svg-data pos objects uuid/zero nil #{} false)] + (csvg.shapes-builder/create-svg-shapes svg-data pos objects uuid/zero nil #{} false)] [shape children]))] (->> (upload-images svg-data) diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index 12f3b8653..ac24a2fa3 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -9,8 +9,8 @@ [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes.flex-layout :as gsl] - [app.common.path.commands :as upc] - [app.common.path.shapes-to-path :as upsp] + [app.common.svg.path.command :as upc] + [app.common.svg.path.shapes-to-path :as upsp] [app.common.types.container :as ctn] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 2258843fd..e9f221b6a 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -11,9 +11,9 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as upg] [app.common.pages.helpers :as cph] - [app.common.path.commands :as upc] - [app.common.path.shapes-to-path :as upsp] - [app.common.path.subpaths :as ups] + [app.common.svg.path.command :as upc] + [app.common.svg.path.shapes-to-path :as upsp] + [app.common.svg.path.subpath :as ups] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.edition :as dwe] diff --git a/frontend/src/app/main/data/workspace/path/helpers.cljs b/frontend/src/app/main/data/workspace/path/helpers.cljs index 09e26d4c5..5792a69f8 100644 --- a/frontend/src/app/main/data/workspace/path/helpers.cljs +++ b/frontend/src/app/main/data/workspace/path/helpers.cljs @@ -11,8 +11,8 @@ [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] [app.common.math :as mth] - [app.common.path.commands :as upc] - [app.common.path.subpaths :as ups] + [app.common.svg.path.command :as upc] + [app.common.svg.path.subpath :as ups] [app.main.data.workspace.path.common :as common] [app.main.streams :as ms] [potok.core :as ptk])) diff --git a/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs b/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs index e82698eb9..f61e5ded8 100644 --- a/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs +++ b/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs @@ -8,7 +8,7 @@ (:require [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] - [app.common.path.shapes-to-path :as upsp] + [app.common.svg.path.shapes-to-path :as upsp] [app.common.types.container :as ctn] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.state-helpers :as wsh] diff --git a/frontend/src/app/main/data/workspace/path/state.cljs b/frontend/src/app/main/data/workspace/path/state.cljs index de30cf841..6b54b74d2 100644 --- a/frontend/src/app/main/data/workspace/path/state.cljs +++ b/frontend/src/app/main/data/workspace/path/state.cljs @@ -6,7 +6,7 @@ (ns app.main.data.workspace.path.state (:require - [app.common.path.shapes-to-path :as upsp])) + [app.common.svg.path.shapes-to-path :as upsp])) (defn get-path-id "Retrieves the currently editing path id" diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs index 3a432cb87..9743a3c8a 100644 --- a/frontend/src/app/main/data/workspace/path/tools.cljs +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -6,8 +6,8 @@ (ns app.main.data.workspace.path.tools (:require - [app.common.path.shapes-to-path :as upsp] - [app.common.path.subpaths :as ups] + [app.common.svg.path.shapes-to-path :as upsp] + [app.common.svg.path.subpath :as ups] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.path.changes :as changes] diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 22d00d658..41d07c2c4 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] + [app.common.files.libraries-helpers :as cflh] [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] @@ -408,6 +409,7 @@ (instantiate-component))] changes)) +;; TODO: move to common.files.shape-helpers (defn- prepare-duplicate-shape-change ([changes objects page unames update-unames! ids-map obj delta libraries library-data it file-id] (prepare-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta libraries library-data it file-id (:frame-id obj) (:parent-id obj) false false)) @@ -437,7 +439,7 @@ regenerate-component (fn [changes shape] (let [components-v2 (dm/get-in library-data [:options :components-v2]) - [_ changes] (dwlh/generate-add-component-changes changes shape objects file-id (:id page) components-v2)] + [_ changes] (cflh/generate-add-component-changes changes shape objects file-id (:id page) components-v2)] changes)) new-obj diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 48b9d0d2b..1c04b5228 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -8,7 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.geom.shapes :as gsh] + [app.common.files.shapes-helpers :as cfsh] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.schema :as sm] @@ -17,8 +17,6 @@ [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] [app.common.types.shape.interactions :as ctsi] - [app.common.types.shape.layout :as ctl] - [app.common.uuid :as uuid] [app.main.data.comments :as dc] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.edition :as dwe] @@ -32,28 +30,6 @@ (def valid-shape-map? (sm/pred-fn ::cts/shape)) -(defn prepare-add-shape - [changes shape objects _selected] - (let [index (:index (meta shape)) - id (:id shape) - - mod? (:mod? (meta shape)) - [row column :as cell] (when-not mod? (:cell (meta shape))) - - changes (-> changes - (pcb/with-objects objects) - (cond-> (some? index) - (pcb/add-object shape {:index index})) - (cond-> (nil? index) - (pcb/add-object shape)) - (cond-> (some? (:parent-id shape)) - (pcb/change-parent (:parent-id shape) [shape] index)) - (cond-> (some? cell) - (pcb/update-shapes [(:parent-id shape)] #(ctl/push-into-cell % [id] row column))) - (cond-> (ctl/grid-layout? objects (:parent-id shape)) - (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells)))] - [shape changes])) - (defn add-shape ([shape] (add-shape shape {})) @@ -73,7 +49,7 @@ [shape changes] (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) - (prepare-add-shape shape objects selected)) + (cfsh/prepare-add-shape shape objects selected)) changes (cond-> changes (cph/text-shape? shape) @@ -93,23 +69,6 @@ (->> (rx/of (dwe/start-edition-mode (:id shape))) (rx/observe-on :async))))))))) -(defn prepare-move-shapes-into-frame - [changes frame-id shapes objects] - (let [ordered-indexes (cph/order-by-indexed-shapes objects shapes) - parent-id (get-in objects [frame-id :parent-id]) - ordered-indexes (->> ordered-indexes (remove #(= % parent-id))) - to-move-shapes (map (d/getf objects) ordered-indexes)] - (if (d/not-empty? to-move-shapes) - (-> changes - (cond-> (not (ctl/any-layout? objects frame-id)) - (pcb/update-shapes ordered-indexes ctl/remove-layout-item-data)) - (pcb/update-shapes ordered-indexes #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true))) - (pcb/change-parent frame-id to-move-shapes 0) - (cond-> (ctl/grid-layout? objects frame-id) - (pcb/update-shapes [frame-id] ctl/assign-cells)) - (pcb/reorder-grid-children [frame-id])) - changes))) - (defn move-shapes-into-frame [frame-id shapes] (ptk/reify ::move-shapes-into-frame @@ -120,10 +79,10 @@ shapes (->> shapes (remove #(dm/get-in objects [% :blocked]))) changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects)) - changes (prepare-move-shapes-into-frame changes - frame-id - shapes - objects)] + changes (cfsh/prepare-move-shapes-into-frame changes + frame-id + shapes + objects)] (if (some? changes) (rx/of (dch/commit-changes changes)) (rx/empty)))))) @@ -366,58 +325,6 @@ ;; Artboard ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; FIXME: looks -(defn prepare-create-artboard-from-selection - [changes id parent-id objects selected index frame-name without-fill?] - (let [selected-objs (map #(get objects %) selected) - new-index (or index - (cph/get-index-replacement selected objects))] - (when (d/not-empty? selected) - (let [srect (gsh/shapes->rect selected-objs) - selected-id (first selected) - - frame-id (dm/get-in objects [selected-id :frame-id]) - parent-id (or parent-id (dm/get-in objects [selected-id :parent-id])) - - attrs {:type :frame - :x (:x srect) - :y (:y srect) - :width (:width srect) - :height (:height srect)} - - shape (cts/setup-shape - (cond-> attrs - (some? id) - (assoc :id id) - - (some? frame-name) - (assoc :name frame-name) - - :always - (assoc :frame-id frame-id - :parent-id parent-id) - - :always - (with-meta {:index new-index}) - - (or (not= frame-id uuid/zero) without-fill?) - (assoc :fills [] :hide-in-viewer true))) - - [shape changes] - (prepare-add-shape changes shape objects selected) - - changes - (prepare-move-shapes-into-frame changes (:id shape) selected objects) - - changes - (cond-> changes - (ctl/grid-layout? objects (:parent-id shape)) - (-> (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells) - (pcb/reorder-grid-children [(:parent-id shape)])))] - - [shape changes])))) - (defn create-artboard-from-selection ([] (create-artboard-from-selection nil)) @@ -438,14 +345,14 @@ (pcb/with-objects objects)) [frame-shape changes] - (prepare-create-artboard-from-selection changes - id - parent-id - objects - selected - index - nil - false) + (cfsh/prepare-create-artboard-from-selection changes + id + parent-id + objects + selected + index + nil + false) undo-id (js/Symbol)] diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index 9a569fbd1..969df9f8e 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -11,7 +11,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] - [app.common.path.commands :as upc] + [app.common.svg.path.command :as upc] [app.common.uuid :as uuid])) (defn lookup-page diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index b41dee828..b932de976 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -6,467 +6,23 @@ (ns app.main.data.workspace.svg-upload (:require - [app.common.colors :as clr] [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.files.helpers :as cfh] - [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.geom.shapes.common :as gsc] - [app.common.geom.shapes.transforms :as gst] - [app.common.math :as mth] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] - [app.common.schema :as sm :refer [max-safe-int min-safe-int]] [app.common.svg :as csvg] - [app.common.types.shape :as cts] + [app.common.svg.shapes-builder :as csvg.shapes-builder] [app.common.types.shape-tree :as ctst] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] [app.main.repo :as rp] - [app.util.color :as uc] - [app.util.path.parser :as upp] [app.util.webapi :as wapi] [beicon.core :as rx] [cuerdas.core :as str] [potok.core :as ptk])) -(def default-rect - {:x 0 :y 0 :width 1 :height 1}) - -(defn- assert-valid-num [attr num] - (dm/verify! - ["%1 attribute has invalid value: %2" (d/name attr) num] - (and (d/num? num) - (<= num max-safe-int) - (>= num min-safe-int))) - - (cond - (and (> num 0) (< num 1)) 1 - (and (< num 0) (> num -1)) -1 - :else num)) - -(defn- assert-valid-pos-num - [attr num] - - (dm/verify! - ["%1 attribute should be positive" (d/name attr)] - (pos? num)) - - num) - -(defn- assert-valid-blend-mode - [mode] - (let [clean-value (-> mode str/trim str/lower keyword)] - (dm/verify! - ["%1 is not a valid blend mode" clean-value] - (contains? cts/blend-modes clean-value)) - clean-value)) - -(defn- svg-dimensions - [data] - (let [width (dm/get-in data [:attrs :width] 100) - height (dm/get-in data [:attrs :height] 100) - viewbox (dm/get-in data [:attrs :viewBox] (str "0 0 " width " " height)) - [x y width height] (->> (str/split viewbox #"\s+") - (map d/parse-double)) - width (if (= width 0) 1 width) - height (if (= height 0) 1 height)] - [(assert-valid-num :x x) - (assert-valid-num :y y) - (assert-valid-pos-num :width width) - (assert-valid-pos-num :height height)])) - -(defn tag->name - "Given a tag returns its layer name" - [tag] - (let [suffix (cond - (string? tag) tag - (keyword? tag) (d/name tag) - (nil? tag) "node" - :else (dm/str tag))] - (dm/str "svg-" suffix))) - -(defn setup-fill - [shape] - (let [color-attr (str/trim (dm/get-in shape [:svg-attrs :fill])) - 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)] - (cond-> shape - ;; Color present as attribute - (uc/color? color-attr) - (-> (update :svg-attrs dissoc :fill) - (update-in [:svg-attrs :style] dissoc :fill) - (assoc-in [:fills 0 :fill-color] (uc/parse-color color-attr))) - - ;; Color present as style - (uc/color? color-style) - (-> (update-in [:svg-attrs :style] dissoc :fill) - (update :svg-attrs dissoc :fill) - (assoc-in [:fills 0 :fill-color] (uc/parse-color color-style))) - - (dm/get-in shape [:svg-attrs :fillOpacity]) - (-> (update :svg-attrs dissoc :fillOpacity) - (update-in [:svg-attrs :style] dissoc :fillOpacity) - (assoc-in [:fills 0 :fill-opacity] (-> (dm/get-in shape [:svg-attrs :fillOpacity]) - (d/parse-double 1)))) - - (dm/get-in shape [:svg-attrs :style :fillOpacity]) - (-> (update-in [:svg-attrs :style] dissoc :fillOpacity) - (update :svg-attrs dissoc :fillOpacity) - (assoc-in [:fills 0 :fill-opacity] (-> (dm/get-in shape [:svg-attrs :style :fillOpacity]) - (d/parse-double 1))))))) - -(defn- setup-stroke - [shape] - (let [attrs (get shape :svg-attrs) - style (get attrs :style) - - stroke (or (str/trim (:stroke attrs)) - (str/trim (:stroke style))) - - color (cond - (= stroke "currentColor") clr/black - (= stroke "none") nil - :else (uc/parse-color stroke)) - - opacity (when (some? color) - (d/parse-double - (or (:strokeOpacity attrs) - (:strokeOpacity style)) - 1)) - - width (when (some? color) - (d/parse-double - (or (:strokeWidth attrs) - (:strokeWidth style)) - 1)) - - linecap (or (get attrs :strokeLinecap) - (get style :strokeLinecap)) - linecap (some-> linecap str/trim keyword) - - attrs (-> attrs - (dissoc :stroke) - (dissoc :strokeWidth) - (dissoc :strokeOpacity) - (update :style (fn [style] - (-> style - (dissoc :stroke) - (dissoc :strokeLinecap) - (dissoc :strokeWidth) - (dissoc :strokeOpacity)))))] - - (cond-> (assoc shape :svg-attrs attrs) - (some? color) - (assoc-in [:strokes 0 :stroke-color] color) - - (and (some? color) (some? opacity)) - (assoc-in [:strokes 0 :stroke-opacity] opacity) - - (and (some? color) (some? width)) - (assoc-in [:strokes 0 :stroke-width] width) - - (and (some? linecap) (= (:type shape) :path) - (or (= linecap :round) (= linecap :square))) - (assoc :stroke-cap-start linecap - :stroke-cap-end linecap) - - (d/any-key? (dm/get-in shape [:strokes 0]) - :strokeColor :strokeOpacity :strokeWidth - :strokeCapStart :strokeCapEnd) - (assoc-in [:strokes 0 :stroke-style] :svg)))) - -(defn setup-opacity [shape] - (cond-> shape - (dm/get-in shape [:svg-attrs :opacity]) - (-> (update :svg-attrs dissoc :opacity) - (assoc :opacity (-> (dm/get-in shape [:svg-attrs :opacity]) - (d/parse-double 1)))) - - (dm/get-in shape [:svg-attrs :style :opacity]) - (-> (update-in [:svg-attrs :style] dissoc :opacity) - (assoc :opacity (-> (dm/get-in shape [:svg-attrs :style :opacity]) - (d/parse-double 1)))) - - (dm/get-in shape [:svg-attrs :mixBlendMode]) - (-> (update :svg-attrs dissoc :mixBlendMode) - (assoc :blend-mode (-> (dm/get-in shape [:svg-attrs :mixBlendMode]) assert-valid-blend-mode))) - - (dm/get-in shape [:svg-attrs :style :mixBlendMode]) - (-> (update-in [:svg-attrs :style] dissoc :mixBlendMode) - (assoc :blend-mode (-> (dm/get-in shape [:svg-attrs :style :mixBlendMode]) assert-valid-blend-mode))))) - -(defn create-raw-svg - [name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}] - (let [props (csvg/attrs->props attrs) - vbox (grc/make-rect offset-x offset-y width height)] - (cts/setup-shape - {:type :svg-raw - :name name - :frame-id frame-id - :width width - :height height - :x x - :y y - :content data - :svg-attrs props - :svg-viewbox vbox}))) - -(defn create-svg-root - [frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}] - (let [props (-> (dissoc attrs :viewBox :view-box :xmlns) - (d/without-keys csvg/inheritable-props) - (csvg/attrs->props))] - (cts/setup-shape - {:type :group - :name name - :frame-id frame-id - :parent-id parent-id - :width width - :height height - :x (+ x offset-x) - :y (+ y offset-y) - :svg-attrs props}))) - -(defn create-group - [name frame-id {:keys [x y width height offset-x offset-y] :as svg-data} {:keys [attrs]}] - (let [transform (csvg/parse-transform (:transform attrs)) - attrs (-> (d/without-keys attrs csvg/inheritable-props) - (csvg/attrs->props)) - vbox (grc/make-rect offset-x offset-y width height)] - (cts/setup-shape - {:type :group - :name name - :frame-id frame-id - :x (+ x offset-x) - :y (+ y offset-y) - :width width - :height height - :svg-transform transform - :svg-attrs attrs - :svg-viewbox vbox}))) - -(defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}] - (when (and (contains? attrs :d) (seq (:d attrs))) - (let [transform (csvg/parse-transform (:transform attrs)) - content (cond-> (upp/parse-path (:d attrs)) - (some? transform) - (gsh/transform-content transform)) - - selrect (gsh/content->selrect content) - points (grc/rect->points selrect) - origin (gpt/negate (gpt/point svg-data)) - attrs (-> (dissoc attrs :d :transform) - (csvg/attrs->props))] - (-> (cts/setup-shape - {:type :path - :name name - :frame-id frame-id - :content content - :selrect selrect - :points points - :svg-viewbox selrect - :svg-attrs attrs - :svg-transform transform - :fills []}) - (gsh/translate-to-frame origin))))) - -(defn calculate-rect-metadata - [rect transform] - (let [points (-> rect - (grc/rect->points) - (gsh/transform-points transform)) - center (gsc/points->center points) - selrect (gst/calculate-selrect points center) - transform (gst/calculate-transform points center selrect)] - - {:x (:x selrect) - :y (:y selrect) - :width (:width selrect) - :height (:height selrect) - :selrect selrect - :points points - :transform transform - :transform-inverse (when (some? transform) - (gmt/inverse transform))})) - -(defn- parse-rect-attrs - [{:keys [x y width height]}] - (grc/make-rect - (d/parse-double x 0) - (d/parse-double y 0) - (d/parse-double width 1) - (d/parse-double height 1))) - -(defn create-rect-shape [name frame-id svg-data {:keys [attrs] :as data}] - (let [transform (->> (csvg/parse-transform (:transform attrs)) - (gmt/transform-in (gpt/point svg-data))) - - origin (gpt/negate (gpt/point svg-data)) - rect (-> (parse-rect-attrs attrs) - (update :x - (:x origin)) - (update :y - (:y origin))) - - props (-> (dissoc attrs :x :y :width :height :rx :ry :transform) - (csvg/attrs->props))] - - (cts/setup-shape - (-> (calculate-rect-metadata rect transform) - (assoc :type :rect) - (assoc :name name) - (assoc :frame-id frame-id) - (assoc :svg-viewbox rect) - (assoc :svg-attrs props) - ;; We need to ensure fills are empty on import process - ;; because setup-shape assings one by default. - (assoc :fills []) - (cond-> (contains? attrs :rx) - (assoc :rx (d/parse-double (:rx attrs) 0))) - (cond-> (contains? attrs :ry) - (assoc :ry (d/parse-double (:ry attrs) 0))))))) - -(defn- parse-circle-attrs - [attrs] - (into [] (comp (map (d/getf attrs)) - (map d/parse-double)) - [:cx :cy :r :rx :ry])) - -(defn create-circle-shape - [name frame-id svg-data {:keys [attrs] :as data}] - (let [[cx cy r rx ry] - (parse-circle-attrs attrs) - - transform (->> (csvg/parse-transform (:transform attrs)) - (gmt/transform-in (gpt/point svg-data))) - - rx (d/nilv r rx) - ry (d/nilv r ry) - origin (gpt/negate (gpt/point svg-data)) - - rect (grc/make-rect - (- cx rx (:x origin)) - (- cy ry (:y origin)) - (* 2 rx) - (* 2 ry)) - props (-> (dissoc attrs :cx :cy :r :rx :ry :transform) - (csvg/attrs->props))] - - (cts/setup-shape - (-> (calculate-rect-metadata rect transform) - (assoc :type :circle) - (assoc :name name) - (assoc :frame-id frame-id) - (assoc :svg-viewbox rect) - (assoc :svg-attrs props) - (assoc :fills []))))) - -(defn create-image-shape - [name frame-id svg-data {:keys [attrs] :as data}] - (let [transform (->> (csvg/parse-transform (:transform attrs)) - (gmt/transform-in (gpt/point svg-data))) - - image-url (or (:href attrs) (:xlink:href attrs)) - image-data (dm/get-in svg-data [:image-data image-url]) - - metadata {:width (:width image-data) - :height (:height image-data) - :mtype (:mtype image-data) - :id (:id image-data)} - - origin (gpt/negate (gpt/point svg-data)) - rect (-> (parse-rect-attrs attrs) - (update :x - (:x origin)) - (update :y - (:y origin))) - props (-> (dissoc attrs :x :y :width :height :href :xlink:href) - (csvg/attrs->props))] - - (when (some? image-data) - (cts/setup-shape - (-> (calculate-rect-metadata rect transform) - (assoc :type :image) - (assoc :name name) - (assoc :frame-id frame-id) - (assoc :metadata metadata) - (assoc :svg-viewbox rect) - (assoc :svg-attrs props)))))) - -(defn parse-svg-element - [frame-id svg-data {:keys [tag attrs hidden] :as element} unames] - (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) - href-id (-> (or (:href attrs) (:xlink:href attrs) "") (subs 1)) - use-tag? (and (= :use tag) (contains? defs href-id))] - - (if use-tag? - (let [;; Merge the data of the use definition with the properties passed as attributes - use-data (-> (get defs href-id) - (update :attrs #(d/deep-merge % (dissoc attrs :xlink:href :href)))) - displacement (gpt/point (d/parse-double (:x attrs "0")) (d/parse-double (:y attrs "0"))) - disp-matrix (dm/str (gmt/translate-matrix displacement)) - element (-> element - (assoc :tag :g) - (update :attrs dissoc :x :y :width :height :href :xlink:href :transform) - (update :attrs csvg/add-transform disp-matrix) - (assoc :content [use-data]))] - (parse-svg-element frame-id svg-data element unames)) - - (let [;; SVG graphic elements - ;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use - shape (case tag - (:g :a :svg) (create-group name frame-id svg-data element) - :rect (create-rect-shape name frame-id svg-data element) - (:circle - :ellipse) (create-circle-shape name frame-id svg-data element) - :path (create-path-shape name frame-id svg-data element) - :polyline (create-path-shape name frame-id svg-data (-> element csvg/polyline->path)) - :polygon (create-path-shape name frame-id svg-data (-> element csvg/polygon->path)) - :line (create-path-shape name frame-id svg-data (-> element csvg/line->path)) - :image (create-image-shape name frame-id svg-data element) - #_other (create-raw-svg name frame-id svg-data element))] - - - (when (some? shape) - (let [shape (-> shape - (assoc :svg-defs (select-keys defs references)) - (setup-fill) - (setup-stroke) - (setup-opacity) - (update :svg-attrs (fn [attrs] - (if (empty? (:style attrs)) - (dissoc attrs :style) - attrs))))] - [(cond-> shape - hidden (assoc :hidden true)) - - (cond->> (:content element) - (contains? csvg/parent-tags tag) - (mapv #(csvg/inherit-attributes attrs %)))])))))) - -(defn create-svg-children - [objects selected frame-id parent-id svg-data [unames children] [_index svg-element]] - (let [[shape new-children] (parse-svg-element frame-id svg-data svg-element unames)] - (if (some? shape) - (let [shape-id (:id shape) - shape (-> shape - (assoc :frame-id frame-id) - (assoc :parent-id parent-id)) - children (conj children shape) - unames (conj unames (:name shape))] - - (reduce (partial create-svg-children objects selected frame-id shape-id svg-data) - [unames children] - (d/enumerate new-children))) - - [unames children]))) - (defn extract-name [url] (let [query-idx (str/last-index-of url "?") url (if (> query-idx 0) (subs url 0 query-idx) url) @@ -499,70 +55,6 @@ (rx/map #(vector (:url uri-data) %))))) (rx/reduce (fn [acc [url image]] (assoc acc url image)) {}))) - -(defn create-svg-shapes - [svg-data {:keys [x y]} objects frame-id parent-id selected center?] - (let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) - - - unames (cfh/get-used-names objects) - svg-name (str/replace (:name svg-data) ".svg" "") - - svg-data (-> svg-data - (assoc :x (mth/round - (if center? - (- x vb-x (/ vb-width 2)) - x))) - (assoc :y (mth/round - (if center? - (- y vb-y (/ vb-height 2)) - y))) - (assoc :offset-x vb-x) - (assoc :offset-y vb-y) - (assoc :width vb-width) - (assoc :height vb-height) - (assoc :name svg-name)) - - [def-nodes svg-data] - (-> svg-data - (csvg/fix-default-values) - (csvg/fix-percents) - (csvg/extract-defs)) - - ;; 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 - ;; size - background - {:tag :rect - :attrs {:x (dm/str vb-x) - :y (dm/str vb-y) - :width (dm/str vb-width) - :height (dm/str vb-height) - :fill "none" - :id "base-background"} - :hidden true - :content []} - - svg-data (-> svg-data - (assoc :defs def-nodes) - (assoc :content (into [background] (:content svg-data)))) - - root-shape (create-svg-root frame-id parent-id svg-data) - root-id (:id root-shape) - - ;; Create the root shape - root-attrs (-> (:attrs svg-data) - (csvg/format-styles)) - - [_ children] - (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 %)))))] - - [root-shape children])) - (defn add-svg-shapes [svg-data position] (ptk/reify ::add-svg-shapes @@ -584,7 +76,7 @@ (:parent-id base)) [new-shape new-children] - (create-svg-shapes svg-data position objects frame-id parent-id selected true) + (csvg.shapes-builder/create-svg-shapes svg-data position objects frame-id parent-id selected true) changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) diff --git a/frontend/src/app/main/ui/components/color_input.cljs b/frontend/src/app/main/ui/components/color_input.cljs index 3d546a045..2f5777fa5 100644 --- a/frontend/src/app/main/ui/components/color_input.cljs +++ b/frontend/src/app/main/ui/components/color_input.cljs @@ -6,8 +6,8 @@ (ns app.main.ui.components.color-input (:require + [app.common.colors :as cc] [app.common.data :as d] - [app.util.color :as uc] [app.util.dom :as dom] [app.util.globals :as globals] [app.util.i18n :as i18n :refer [tr]] @@ -19,9 +19,9 @@ (defn clean-color [value] (-> value - (uc/expand-hex) - (uc/parse-color) - (uc/prepend-hash))) + (cc/expand-hex) + (cc/parse) + (cc/prepend-hash))) (mf/defc color-input* {::mf/wrap-props false @@ -62,14 +62,14 @@ (mf/deps ref) (fn [new-value] (let [input-node (mf/ref-val ref)] - (dom/set-value! input-node (uc/remove-hash new-value))))) + (dom/set-value! input-node (cc/remove-hash new-value))))) apply-value (mf/use-fn (mf/deps on-change update-input) (fn [new-value] (mf/set-ref-val! dirty-ref false) - (when (and new-value (not= (uc/remove-hash new-value) value)) + (when (and new-value (not= (cc/remove-hash new-value) value)) (when on-change (on-change new-value)) (update-input new-value)))) @@ -170,7 +170,7 @@ [:> :input props] ;; FIXME: this causes some weird interactions because of using apply-value ;; [:datalist {:id list-id} - ;; (for [color-name uc/color-names] + ;; (for [color-name cc/color-names] ;; [:option color-name])] ])) diff --git a/frontend/src/app/main/ui/shapes/filters.cljs b/frontend/src/app/main/ui/shapes/filters.cljs index 3b542483a..8ac7a15af 100644 --- a/frontend/src/app/main/ui/shapes/filters.cljs +++ b/frontend/src/app/main/ui/shapes/filters.cljs @@ -6,11 +6,11 @@ (ns app.main.ui.shapes.filters (:require + [app.common.colors :as cc] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.shapes.bounds :as gsb] [app.common.uuid :as uuid] - [app.util.color :as color] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -26,7 +26,7 @@ (mf/defc color-matrix [{:keys [color]}] (let [{:keys [color opacity]} color - [r g b a] (color/hex->rgba color opacity) + [r g b a] (cc/hex->rgba color opacity) [r g b] [(/ r 255) (/ g 255) (/ b 255)]] [:feColorMatrix {:type "matrix" diff --git a/frontend/src/app/main/ui/shapes/text/fo_text.cljs b/frontend/src/app/main/ui/shapes/text/fo_text.cljs index 20742fd12..8644be21b 100644 --- a/frontend/src/app/main/ui/shapes/text/fo_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/fo_text.cljs @@ -6,12 +6,11 @@ (ns app.main.ui.shapes.text.fo-text (:require - [app.common.colors :as clr] + [app.common.colors :as cc] [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.text.styles :as sts] - [app.util.color :as uc] [app.util.object :as obj] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -85,9 +84,9 @@ [colors] (assert (set? colors)) (loop [current-rgb [0 0 0]] - (let [current-hex (uc/rgb->hex current-rgb)] + (let [current-hex (cc/rgb->hex current-rgb)] (if (contains? colors current-hex) - (recur (uc/next-rgb current-rgb)) + (recur (cc/next-rgb current-rgb)) current-hex)))) (defn- fill->color @@ -122,7 +121,7 @@ (filter some?)) colors (->> color-data - (into #{clr/black} + (into #{cc/black} (comp (filter #(= :solid (:type %))) (map :hex)))) diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 38625b2c7..64a8f34ef 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.shapes.text.styles (:require + [app.common.colors :as cc] [app.common.data :as d] [app.common.text :as txt] [app.common.transit :as transit] @@ -76,7 +77,7 @@ fill-opacity (or (-> data :fills first :fill-opacity) (:fill-opacity data)) fill-gradient (or (-> data :fills first :fill-color-gradient) (:fill-color-gradient data)) - [r g b a] (uc/hex->rgba fill-color fill-opacity) + [r g b a] (cc/hex->rgba fill-color fill-opacity) text-color (when (and (some? fill-color) (some? fill-opacity)) (str/format "rgba(%s, %s, %s, %s)" r g b a)) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs index 178ae3cc3..7b2cc13b8 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.viewer.inspect.attributes.common (:require-macros [app.main.style :as stl]) (:require + [app.common.colors :as cc] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.color-bullet :refer [color-bullet color-name]] @@ -14,7 +15,6 @@ [app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.components.select :refer [select]] [app.main.ui.context :as ctx] - [app.util.color :as uc] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [cuerdas.core :as str] @@ -85,10 +85,10 @@ (case format :hex [:& cbn/color-name {:color color :size 80}] - :rgba (let [[r g b a] (uc/hex->rgba (:color color) (:opacity color))] + :rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))] [:* (str/fmt "%s, %s, %s, %s" r g b a)]) - :hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color)) - result (uc/format-hsla [h s l a])] + :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color)) + result (cc/format-hsla [h s l a])] [:* result])))] (when-not (:gradient color) @@ -111,10 +111,10 @@ (case format :hex [:& cbn/color-name {:color color :size 80}] - :rgba (let [[r g b a] (uc/hex->rgba (:color color) (:opacity color))] + :rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))] [:* (str/fmt "%s, %s, %s, %s" r g b a)]) - :hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color)) - result (uc/format-hsla [h s l a])] + :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color)) + result (cc/format-hsla [h s l a])] [:* result])))] (when-not (:gradient color) @@ -135,10 +135,10 @@ ;; (case format ;; :hex [:& cbn/color-name {:color color ;; :size 80}] - ;; :rgba (let [[r g b a] (uc/hex->rgba (:color color) (:opacity color))] + ;; :rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))] ;; [:* (str/fmt "%s, %s, %s, %s" r g b a)]) - ;; :hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color)) - ;; result (uc/format-hsla [h s l a])] + ;; :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color)) + ;; result (cc/format-hsla [h s l a])] ;; [:* result])))] ;; (when color-library-name @@ -163,10 +163,10 @@ (if (:gradient color) [:& color-name {:color color}] (case format - :rgba (let [[r g b a] (uc/hex->rgba (:color color) (:opacity color))] + :rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))] [:div (str/fmt "%s, %s, %s, %s" r g b a)]) - :hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color)) - result (uc/format-hsla [h s l a])] + :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color)) + result (cc/format-hsla [h s l a])] [:div result]) [:* [:& color-name {:color color}] diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index 28884c363..c9e1dccab 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.colorpicker (:require-macros [app.main.style :as stl]) (:require + [app.common.colors :as cc] [app.main.data.modal :as modal] [app.main.data.workspace.colors :as dc] [app.main.data.workspace.libraries :as dwl] @@ -22,7 +23,6 @@ [app.main.ui.workspace.colorpicker.hsva :refer [hsva-selector]] [app.main.ui.workspace.colorpicker.libraries :refer [libraries]] [app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]] - [app.util.color :as uc] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [cuerdas.core :as str] @@ -190,9 +190,9 @@ (let [node (mf/ref-val node-ref) {:keys [r g b h v]} current-color rgb [r g b] - hue-rgb (uc/hsv->rgb [h 1.0 255]) - hsl-from (uc/hsv->hsl [h 0.0 v]) - hsl-to (uc/hsv->hsl [h 1.0 v]) + hue-rgb (cc/hsv->rgb [h 1.0 255]) + hsl-from (cc/hsv->hsl [h 0.0 v]) + hsl-to (cc/hsv->hsl [h 1.0 v]) format-hsl (fn [[h s l]] (str/fmt "hsl(%s, %s, %s)" @@ -208,8 +208,8 @@ (mf/with-effect [picking-color? picked-color picked-color-select] (when (and picking-color? picked-color picked-color-select) (let [[r g b alpha] picked-color - hex (uc/rgb->hex [r g b]) - [h s v] (uc/hex->hsv hex)] + hex (cc/rgb->hex [r g b]) + [h s v] (cc/hex->hsv hex)] (handle-change-color {:hex hex :r r :g g :b b :h h :s s :v v diff --git a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs index b7bb1e579..531b67ccb 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs @@ -7,10 +7,10 @@ (ns app.main.ui.workspace.colorpicker.color-inputs (:require-macros [app.main.style :as stl]) (:require + [app.common.colors :as cc] [app.common.data :as d] [app.common.math :as mth] [app.main.ui.context :as ctx] - [app.util.color :as uc] [app.util.dom :as dom] [rumext.v2 :as mf])) @@ -45,23 +45,27 @@ setup-hex-color (fn [hex] - (let [[r g b] (uc/hex->rgb hex) - [h s v] (uc/hex->hsv hex)] + (let [[r g b] (cc/hex->rgb hex) + [h s v] (cc/hex->hsv hex)] (on-change {:hex hex :h h :s s :v v :r r :g g :b b}))) on-change-hex (fn [e] (let [val (-> e dom/get-target-val parse-hex)] - (when (uc/hex? val) + (when (cc/valid-hex-color? val) (setup-hex-color val)))) on-blur-hex (fn [e] (let [val (-> e dom/get-target-val) + ;; FIXME: looks redundant, cc/parse already handles + ;; hex colors; also it performs the parse-hex twice + ;; that is completly unnecessary val (cond - (uc/color? val) (uc/parse-color val) - (uc/hex? (parse-hex val)) (parse-hex val))] + (cc/color-string? val) (cc/parse val) + (cc/valid-hex-color? (parse-hex val)) (parse-hex val))] + (when (some? val) (setup-hex-color val)))) @@ -76,15 +80,15 @@ (when (not (nil? val)) (if (#{:r :g :b} property) (let [{:keys [r g b]} (merge color (hash-map property val)) - hex (uc/rgb->hex [r g b]) - [h s v] (uc/hex->hsv hex)] + hex (cc/rgb->hex [r g b]) + [h s v] (cc/hex->hsv hex)] (on-change {:hex hex :h h :s s :v v :r r :g g :b b})) (let [{:keys [h s v]} (merge color (hash-map property val)) - hex (uc/hsv->hex [h s v]) - [r g b] (uc/hex->rgb hex)] + hex (cc/hsv->hex [h s v]) + [r g b] (cc/hex->rgb hex)] (on-change {:hex hex :h h :s s :v v :r r :g g :b b}))))))) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs index 1c1d9b30a..2bc19b28b 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs @@ -7,11 +7,11 @@ (ns app.main.ui.workspace.colorpicker.harmony (:require-macros [app.main.style :as stl]) (:require + [app.common.colors :as cc] [app.common.geom.point :as gpt] [app.common.math :as mth] [app.main.ui.context :as ctx] [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] - [app.util.color :as uc] [app.util.dom :as dom] [app.util.object :as obj] [cuerdas.core :as str] @@ -82,8 +82,8 @@ angle (mth/degrees (mth/atan2 px py)) new-hue (mod (- angle 90) 360) new-saturation (mth/clamp (mth/distance [px py] [0 0]) 0 1) - hex (uc/hsv->hex [new-hue new-saturation value]) - [r g b] (uc/hex->rgb hex)] + hex (cc/hsv->hex [new-hue new-saturation value]) + [r g b] (cc/hex->rgb hex)] (on-change {:hex hex :r r :g g :b b :h new-hue @@ -108,15 +108,15 @@ (on-finish-drag)))) on-change-value (fn [new-value] - (let [hex (uc/hsv->hex [hue saturation new-value]) - [r g b] (uc/hex->rgb hex)] + (let [hex (cc/hsv->hex [hue saturation new-value]) + [r g b] (cc/hex->rgb hex)] (on-change {:hex hex :r r :g g :b b :v new-value}))) on-complement-click (fn [_] (let [new-hue (mod (+ hue 180) 360) - hex (uc/hsv->hex [new-hue saturation value]) - [r g b] (uc/hex->rgb hex)] + hex (cc/hsv->hex [new-hue saturation value]) + [r g b] (cc/hex->rgb hex)] (on-change {:hex hex :r r :g g :b b :h new-hue diff --git a/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs b/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs index 2b0667607..923637114 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs @@ -7,9 +7,9 @@ (ns app.main.ui.workspace.colorpicker.hsva (:require-macros [app.main.style :as stl]) (:require + [app.common.colors :as cc] [app.main.ui.context :as ctx] [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] - [app.util.color :as uc] [rumext.v2 :as mf])) (mf/defc hsva-selector [{:keys [color disable-opacity on-change on-start-drag on-finish-drag]}] @@ -19,8 +19,8 @@ (fn [new-value] (let [change (hash-map key new-value) {:keys [h s v]} (merge color change) - hex (uc/hsv->hex [h s v]) - [r g b] (uc/hex->rgb hex)] + hex (cc/hsv->hex [h s v]) + [r g b] (cc/hex->rgb hex)] (on-change (merge change {:hex hex :r r :g g :b b}))))) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs index 4adfb4463..4063c4015 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs @@ -7,12 +7,12 @@ (ns app.main.ui.workspace.colorpicker.ramp (:require-macros [app.main.style :as stl]) (:require + [app.common.colors :as cc] [app.common.math :as mth] [app.main.ui.components.color-bullet :refer [color-bullet]] [app.main.ui.components.color-bullet-new :as cb] [app.main.ui.context :as ctx] [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] - [app.util.color :as uc] [app.util.dom :as dom] [rumext.v2 :as mf])) @@ -72,8 +72,8 @@ on-change-value-saturation (fn [new-saturation new-value] - (let [hex (uc/hsv->hex [hue new-saturation new-value]) - [r g b] (uc/hex->rgb hex)] + (let [hex (cc/hsv->hex [hue new-saturation new-value]) + [r g b] (cc/hex->rgb hex)] (on-change {:hex hex :r r :g g :b b :s new-saturation @@ -81,8 +81,8 @@ on-change-hue (fn [new-hue] - (let [hex (uc/hsv->hex [new-hue saturation value]) - [r g b] (uc/hex->rgb hex)] + (let [hex (cc/hsv->hex [new-hue saturation value]) + [r g b] (cc/hex->rgb hex)] (on-change {:hex hex :r r :g g :b b :h new-hue}))) diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs index 9093a8940..b20f65b1d 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs @@ -6,7 +6,7 @@ (ns app.main.ui.workspace.shapes.path (:require - [app.common.path.commands :as upc] + [app.common.svg.path.command :as upc] [app.main.data.workspace.path.helpers :as helpers] [app.main.refs :as refs] [app.main.ui.shapes.path :as path] diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs index 36edbce91..36500386c 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -10,8 +10,8 @@ [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as gsp] - [app.common.path.commands :as upc] - [app.common.path.shapes-to-path :as ups] + [app.common.svg.path.command :as upc] + [app.common.svg.path.shapes-to-path :as ups] [app.main.data.workspace.path :as drp] [app.main.snap :as snap] [app.main.store :as st] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index cbdb4699e..adce16118 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.rows.color-row (:require-macros [app.main.style :as stl]) (:require + [app.common.colors :as cc] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.types.shape.attrs :refer [default-color]] @@ -222,7 +223,7 @@ [:span {:class (stl/css :color-input-wrapper)} [:> color-input* {:value (if multiple-colors? "" - (-> color :color uc/remove-hash)) + (-> color :color cc/remove-hash)) :placeholder (tr "settings.multiple") :className (stl/css :color-input) :on-focus on-focus @@ -301,7 +302,7 @@ [:div.color-info [:> color-input* {:value (if multiple-colors? "" - (-> color :color uc/remove-hash)) + (-> color :color cc/remove-hash)) :placeholder (tr "settings.multiple") :on-focus on-focus :on-blur on-blur diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs index be577a2cc..40cfc8083 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs @@ -6,7 +6,7 @@ (ns app.main.ui.workspace.sidebar.options.shapes.svg-raw (:require - [app.common.colors :as clr] + [app.common.colors :as cc] [app.common.data :as d] [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] @@ -21,7 +21,6 @@ [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]] - [app.util.color :as uc] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -45,7 +44,7 @@ {:color :multiple :opacity :multiple} - :else {:color (uc/parse-color color) + :else {:color (cc/parse color) :opacity 1}) (catch :default e @@ -71,7 +70,7 @@ (get-in shape [:content :attrs :style :stroke])) (parse-color)) - stroke-color (:color color clr/black) + stroke-color (:color color cc/black) stroke-opacity (:opacity color 1) stroke-style (-> (or (get-in shape [:content :attrs :stroke-style]) (get-in shape [:content :attrs :style :stroke-style]) diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index 5b8ca29fd..f581a7742 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -5,125 +5,20 @@ ;; Copyright (c) KALEIDOS INC (ns app.util.color - "Color conversion utils." + "FIXME: this is legacy namespace, all functions of this ns should be + relocated under app.common.types on the respective colors related + namespace. All generic color conversion and other helpers are moved to + app.common.colors namespace." (:require + [app.common.colors :as cc] [app.common.data :as d] - [app.common.data.macros :as dm] [app.util.i18n :as i18n :refer [tr]] - [app.util.object :as obj] - [app.util.strings :as ust] - [cuerdas.core :as str] - [goog.color :as gcolor])) - -(defn rgb->str - [color] - {:pre [(vector? color)]} - (if (= (count color) 3) - (apply str/format "rgb(%s,%s,%s)" color) - (apply str/format "rgba(%s,%s,%s,%s)" color))) - -(defn rgb->hsv - [[r g b]] - (into [] (gcolor/rgbToHsv r g b))) - -(defn hsv->rgb - [[h s v]] - (into [] (gcolor/hsvToRgb h s v))) - -(defn hex->rgb - [v] - (try - (into [] (gcolor/hexToRgb v)) - (catch :default _e [0 0 0]))) - -(defn rgb->hex - [[r g b]] - (gcolor/rgbToHex r g b)) - -(defn hex->hsv - [v] - (into [] (gcolor/hexToHsv v))) - -(defn hex->rgba - [^string data ^number opacity] - (-> (hex->rgb data) - (conj opacity))) - -(defn hex->hsl [hex] - (try - (into [] (gcolor/hexToHsl hex)) - (catch :default _e [0 0 0]))) - -(defn hex->hsla - [^string data ^number opacity] - (-> (hex->hsl data) - (conj opacity))) - -(defn format-hsla - [[h s l a]] - (let [precision 2 - rounded-s (* 100 (ust/format-precision s precision)) - rounded-l (* 100 (ust/format-precision l precision))] - - (str/fmt "%s, %s%, %s%, %s" h rounded-s rounded-l a))) - -(defn hsl->rgb - [[h s l]] - (gcolor/hslToRgb h s l)) - -(defn hsl->hex - [[h s l]] - (gcolor/hslToHex h s l)) - -(defn hex? - [v] - (and (string? v) - (re-seq #"^#[0-9A-Fa-f]{6}$" v))) - -(defn hsl->hsv - [[h s l]] - (gcolor/hslToHsv h s l)) - -(defn hsv->hex - [[h s v]] - (gcolor/hsvToHex h s v)) - -(defn hsv->hsl - [hsv] - (hex->hsl (hsv->hex hsv))) - -(defn expand-hex - [v] - (cond - (re-matches #"^[0-9A-Fa-f]$" v) - (str v v v v v v) - - (re-matches #"^[0-9A-Fa-f]{2}$" v) - (str v v v) - - (re-matches #"^[0-9A-Fa-f]{3}$" v) - (let [a (nth v 0) - b (nth v 1) - c (nth v 2)] - (str a a b b c c)) - - :else - v)) - -(defn prepend-hash - [color] - (gcolor/prependHashIfNecessaryHelper color)) - -(defn remove-hash - [color] - (if (str/starts-with? color "#") - (subs color 1) - color)) + [cuerdas.core :as str])) (defn gradient->css [{:keys [type stops]}] (let [parse-stop (fn [{:keys [offset color opacity]}] - (let [[r g b] (hex->rgb color)] + (let [[r g b] (cc/hex->rgb color)] (str/fmt "rgba(%s, %s, %s, %s) %s" r g b opacity (str (* offset 100) "%")))) stops-css (str/join "," (map parse-stop stops))] @@ -147,7 +42,7 @@ (gradient->css gradient) (not= color :multiple) - (let [[r g b] (hex->rgb (or color value))] + (let [[r g b] (cc/hex->rgb (or color value))] (str/fmt "rgba(%s, %s, %s, %s)" r g b opacity)) :else "transparent"))) @@ -160,56 +55,27 @@ (not= color :multiple) (case format - :rgba (let [[r g b] (hex->rgb color)] + :rgba (let [[r g b] (cc/hex->rgb color)] (str/fmt "rgba(%s, %s, %s, %s)" r g b opacity)) - :hsla (let [[h s l] (hex->hsl color)] + :hsla (let [[h s l] (cc/hex->hsl color)] (str/fmt "hsla(%s, %s, %s, %s)" h (* 100 s) (* 100 l) opacity)) :hex (str color (str/upper (d/opacity-to-hex opacity)))) :else "transparent"))) -(defn multiple? [{:keys [id file-id value color gradient]}] +(defn multiple? + [{:keys [id file-id value color gradient]}] (or (= value :multiple) (= color :multiple) (= gradient :multiple) (= id :multiple) (= file-id :multiple))) -(defn color? - [color] - (and (string? color) - (gcolor/isValidColor color))) - -(defn parse-color - [color] - (when (color? color) - (let [result (gcolor/parse color)] - (dm/str (.-hex ^js result))))) - -(def color-names - (obj/get-keys ^js gcolor/names)) - (def empty-color (into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity])) -(defn next-rgb - "Given a color in rgb returns the next color" - [[r g b]] - (cond - (and (= 255 r) (= 255 g) (= 255 b)) - (throw (ex-info "cannot get next color" {:r r :g g :b b})) - - (and (= 255 g) (= 255 b)) - [(inc r) 0 0] - - (= 255 b) - [r (inc g) 0] - - :else - [r g (inc b)])) - (defn get-color-name [color] (or (:color-library-name color) diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs index 33c242220..0300c1763 100644 --- a/frontend/src/app/util/import/parser.cljs +++ b/frontend/src/app/util/import/parser.cljs @@ -6,15 +6,15 @@ (ns app.util.import.parser (:require + [app.common.colors :as cc] [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.svg.path :as svg.path] [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] - [app.util.color :as uc] [app.util.json :as json] - [app.util.path.parser :as upp] [cuerdas.core :as str])) (def url-regex @@ -278,7 +278,7 @@ (defn parse-path [props center svg-data] - (let [content (upp/parse-path (:d svg-data))] + (let [content (svg.path/parse (:d svg-data))] (-> props (assoc :content content) (assoc :center center)))) @@ -454,7 +454,7 @@ :fill-color nil :fill-opacity nil) - (uc/hex? fill) + (cc/valid-hex-color? fill) (assoc :fill-color fill :fill-opacity (-> svg-data (:fill-opacity "1") d/parse-double)) diff --git a/frontend/src/app/util/path/format.cljs b/frontend/src/app/util/path/format.cljs index c0d38829d..28a43adf3 100644 --- a/frontend/src/app/util/path/format.cljs +++ b/frontend/src/app/util/path/format.cljs @@ -6,8 +6,8 @@ (ns app.util.path.format (:require - [app.common.path.commands :as upc] - [app.common.path.subpaths :refer [pt=]] + [app.common.svg.path.command :as upc] + [app.common.svg.path.subpath :refer [pt=]] [app.util.array :as arr])) (def path-precision 3) diff --git a/frontend/src/app/util/path/tools.cljs b/frontend/src/app/util/path/tools.cljs index f6877b733..516ef047d 100644 --- a/frontend/src/app/util/path/tools.cljs +++ b/frontend/src/app/util/path/tools.cljs @@ -9,7 +9,7 @@ [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as upg] - [app.common.path.commands :as upc] + [app.common.svg.path.command :as upc] [clojure.set :as set])) (defn remove-line-curves diff --git a/frontend/test/frontend_tests/helpers/pages.cljs b/frontend/test/frontend_tests/helpers/pages.cljs index 7ef367f8d..fbf5a7109 100644 --- a/frontend/test/frontend_tests/helpers/pages.cljs +++ b/frontend/test/frontend_tests/helpers/pages.cljs @@ -7,9 +7,13 @@ (ns frontend-tests.helpers.pages (:require [app.common.data :as d] + [app.common.files.libraries-helpers :as cflh] + [app.common.files.shapes-helpers :as cfsh] [app.common.geom.point :as gpt] [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] + [app.common.files.libraries-helpers :as cflh] + [app.common.files.shapes-helpers :as cfsh] [app.common.pages.helpers :as cph] [app.common.types.shape :as cts] [app.common.uuid :as uuid] @@ -113,7 +117,7 @@ (if (empty? shapes) state (let [[frame changes] - (dwsh/prepare-create-artboard-from-selection changes + (cfsh/prepare-create-artboard-from-selection changes nil nil (:objects page) @@ -133,14 +137,14 @@ shapes (dwg/shapes-for-grouping objects shape-ids) [group component-id changes] - (dwlh/generate-add-component nil + (cflh/generate-add-component nil shapes (:objects page) (:id page) current-file-id true dwg/prepare-create-group - dwsh/prepare-create-artboard-from-selection)] + cfsh/prepare-create-artboard-from-selection)] (swap! idmap assoc instance-label (:id group) component-label component-id) diff --git a/frontend/test/frontend_tests/state_components_sync_test.cljs b/frontend/test/frontend_tests/state_components_sync_test.cljs index fd66ca12d..aa2c50fb9 100644 --- a/frontend/test/frontend_tests/state_components_sync_test.cljs +++ b/frontend/test/frontend_tests/state_components_sync_test.cljs @@ -201,7 +201,9 @@ (t/is (= (:shape-ref c-shape1) nil)) (t/is (= (:name c-shape2) "Rect 2")) (t/is (= (:touched c-shape2) nil)) - (t/is (= (:shape-ref c-shape2) nil)))))] + (t/is (= (:shape-ref c-shape2) nil)) + + )))] (ptk/emit! store