0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-21 06:02:32 -05:00

♻️ Make svg to shapes conversion code multiplatform

- Move clojure code to common
- Rewrite some native-js code into optimized clojure
This commit is contained in:
Andrey Antukh 2023-10-11 13:39:56 +02:00 committed by Andrés Moya
parent 44845d5d94
commit 3ceb4cf895
62 changed files with 2037 additions and 1011 deletions

View file

@ -26,6 +26,7 @@
com.cognitect/transit-cljs {:mvn/version "0.8.280"} com.cognitect/transit-cljs {:mvn/version "0.8.280"}
java-http-clj/java-http-clj {:mvn/version "0.4.3"} 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/cuerdas {:mvn/version "2022.06.16-403"}
funcool/promesa {:mvn/version "11.0.678"} funcool/promesa {:mvn/version "11.0.678"}
funcool/datoteka {:mvn/version "3.0.66" funcool/datoteka {:mvn/version "3.0.66"
@ -43,7 +44,7 @@
fipp/fipp {:mvn/version "0.6.26"} fipp/fipp {:mvn/version "0.6.26"}
io.aviso/pretty {:mvn/version "1.4.4"} io.aviso/pretty {:mvn/version "1.4.4"}
environ/environ {:mvn/version "1.2.0"}} environ/environ {:mvn/version "1.2.0"}}
:paths ["src" "target/classes"] :paths ["src" "vendor" "target/classes"]
:aliases :aliases
{:dev {:dev
{:extra-deps {:extra-deps

View file

@ -4,7 +4,8 @@
"main": "index.js", "main": "index.js",
"license": "MPL-2.0", "license": "MPL-2.0",
"dependencies": { "dependencies": {
"luxon": "^3.4.2" "luxon": "^3.4.2",
"sax": "^1.2.4"
}, },
"scripts": { "scripts": {
"test:watch": "clojure -M:dev:shadow-cljs watch test", "test:watch": "clojure -M:dev:shadow-cljs watch test",

View file

@ -5,7 +5,12 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.common.colors (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 black "#000000")
(def canvas "#E8E9EA") (def canvas "#E8E9EA")
@ -27,3 +32,428 @@
(def new-danger "#ff4986") (def new-danger "#ff4986")
(def new-warning "#ff9b49") (def new-warning "#ff9b49")
(def canvas-background "#1d1f20") (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)]))

View file

@ -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]))

View file

@ -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]))))

View file

@ -7,8 +7,8 @@
(ns app.common.geom.shapes.bool (ns app.common.geom.shapes.bool
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.path.bool :as pb] [app.common.svg.path.bool :as pb]
[app.common.path.shapes-to-path :as stp])) [app.common.svg.path.shapes-to-path :as stp]))
(defn calc-bool-content (defn calc-bool-content
[shape objects] [shape objects]

View file

@ -12,8 +12,8 @@
[app.common.geom.rect :as grc] [app.common.geom.rect :as grc]
[app.common.geom.shapes.common :as gco] [app.common.geom.shapes.common :as gco]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.path.commands :as upc] [app.common.svg.path.command :as upc]
[app.common.path.subpaths :as sp])) [app.common.svg.path.subpath :as sp]))
(def ^:const curve-curve-precision 0.1) (def ^:const curve-curve-precision 0.1)
(def ^:const curve-range-precision 2) (def ^:const curve-range-precision 2)

View file

@ -133,9 +133,10 @@
(defn ceil (defn ceil
"Returns the smallest integer greater than "Returns the smallest integer greater than
or equal to a given number." or equal to a given number."
^double
[v] [v]
#?(:cljs (js/Math.ceil v) #?(:cljs (js/Math.ceil v)
:clj (Math/ceil v))) :clj (Math/ceil ^double v)))
(defn precision (defn precision
[v n] [v n]

View file

@ -7,6 +7,8 @@
(ns app.common.svg (ns app.common.svg
(:require (:require
#?(:cljs ["./svg_optimizer.js" :as svgo]) #?(:cljs ["./svg_optimizer.js" :as svgo])
#?(:clj [clojure.xml :as xml]
:cljs [tubax.core :as tubax])
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
@ -14,7 +16,15 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.uuid :as uuid] [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 ;; Regex for XML ids per Spec
;; https://www.w3.org/TR/2008/REC-xml-20081126/#sec-common-syn ;; https://www.w3.org/TR/2008/REC-xml-20081126/#sec-common-syn
@ -1043,3 +1053,26 @@
([input] (optimize input nil)) ([input] (optimize input nil))
([input options] ([input options]
(svgo/optimize input (clj->js 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 "<!DOCTYPE")
(str/replace #"<\!DOCTYPE[^>]*>" "")))
(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)))))

View file

@ -4,55 +4,80 @@
;; ;;
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.util.path.parser (ns app.common.svg.path
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as upg] [app.common.geom.shapes.path :as upg]
[app.common.path.commands :as upc] [app.common.math :as mth]
[app.common.svg :as csvg] [app.common.svg :as csvg]
[app.util.path.arc-to-curve :refer [a2c]] [app.common.svg.path.command :as upc]
[cuerdas.core :as str])) [cuerdas.core :as str]))
;;
(def commands-regex #"(?i)[mzlhvcsqta][^mzlhvcsqta]*") (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 (defn extract-params
;; 0 and 1 are special because can refer to flags [data pattern]
(def num-regex #"[+-]?(\d+(\.\d+)?|\.\d+)(e[+-]?\d+)?") (loop [result []
ptt-idx 0
(def flag-regex #"[01]")
(defn extract-params [cmd-str extract-commands]
(loop [result []
extract-idx 0
current {} current {}
remain (-> cmd-str (subs 1) (str/trim))] entries (re-seq regex data)
match (ffirst entries)]
(let [[param type] (nth extract-commands extract-idx) (if match
regex (case type (let [[attr-name attr-type] (nth pattern ptt-idx)
:flag flag-regex ptt-idx (inc ptt-idx)
#_:number num-regex) ptt-cnt (count pattern)
match (re-find regex remain)]
(if match value (if (= attr-type :flag)
(let [value (-> match first csvg/fix-dot-number d/read-string) (if (= 1 (count match))
remain (str/replace-first remain regex "") (d/parse-integer match)
current (assoc current param value) (d/parse-integer (subs match 0 1)))
extract-idx (inc extract-idx) (-> match csvg/fix-dot-number d/parse-double))
[result current extract-idx]
(if (>= extract-idx (count extract-commands)) current (assoc current attr-name value)
[(conj result current) {} 0]
[result current extract-idx])] result (if (>= ptt-idx ptt-cnt)
(recur result (conj result current)
extract-idx result)
current
remain)) current (if (>= ptt-idx ptt-cnt)
(cond-> result {}
(seq current) (conj current)))))) 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 ;; Path specification
;; https://www.w3.org/TR/SVG11/paths.html ;; 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] (defmethod parse-command "M" [cmd]
(let [relative (str/starts-with? cmd "m") (let [relative (str/starts-with? cmd "m")
@ -125,8 +150,8 @@
(let [relative (str/starts-with? cmd "q") (let [relative (str/starts-with? cmd "q")
param-list (extract-params cmd [[:cx :number] param-list (extract-params cmd [[:cx :number]
[:cy :number] [:cy :number]
[:x :number] [:x :number]
[:y :number]])] [:y :number]])]
(for [params param-list] (for [params param-list]
{:command :quadratic-bezier-curve-to {:command :quadratic-bezier-curve-to
:relative relative :relative relative
@ -142,7 +167,7 @@
:params params}))) :params params})))
(defmethod parse-command "A" [cmd] (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] param-list (extract-params cmd [[:rx :number]
[:ry :number] [:ry :number]
[:x-axis-rotation :number] [:x-axis-rotation :number]
@ -178,7 +203,120 @@
:c2x (:x cp2) :c2x (:x cp2)
:c2y (:y 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 (let [to-command
(fn [[_ _ c1x c1y c2x c2y x y]] (fn [[_ _ c1x c1y c2x c2y x y]]
{:command :curve-to {:command :curve-to
@ -188,8 +326,16 @@
:x x :y y}}) :x x :y y}})
{from-x :x from-y :y} from-p {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))) (mapv to-command result)))
(defn simplify-commands (defn simplify-commands
@ -202,7 +348,6 @@
;; prev-qc : previous command control point for quadratic curves ;; prev-qc : previous command control point for quadratic curves
(fn [[result prev-pos prev-start prev-cc prev-qc] [command _prev]] (fn [[result prev-pos prev-start prev-cc prev-qc] [command _prev]]
(let [command (assoc command :prev-pos prev-pos) (let [command (assoc command :prev-pos prev-pos)
command command
(cond-> command (cond-> command
(:relative command) (:relative command)
@ -288,6 +433,7 @@
next-start (if (= :move-to (:command command)) next-pos prev-start)] next-start (if (= :move-to (:command command)) next-pos prev-start)]
[result next-pos next-start next-cc next-qc])) [result next-pos next-start next-cc next-qc]))
start (first commands) start (first commands)
@ -301,17 +447,10 @@
(reduce simplify-command [[start] start-pos start-pos start-pos start-pos]) (reduce simplify-command [[start] start-pos start-pos start-pos start-pos])
(first)))) (first))))
(defn parse-path [path-str] (defn parse
[path-str]
(if (empty? path-str) (if (empty? path-str)
path-str path-str
(let [clean-path-str (let [commands (re-seq commands-regex 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)]
(-> (mapcat parse-command commands) (-> (mapcat parse-command commands)
(simplify-commands))))) (simplify-commands)))))

View file

@ -4,14 +4,14 @@
;; ;;
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.common.path.bool (ns app.common.svg.path.bool
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.rect :as grc] [app.common.geom.rect :as grc]
[app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.path :as gsp]
[app.common.path.commands :as upc] [app.common.svg.path.command :as upc]
[app.common.path.subpaths :as ups])) [app.common.svg.path.subpath :as ups]))
(defn add-previous (defn add-previous
([content] ([content]

View file

@ -4,7 +4,7 @@
;; ;;
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.common.path.commands (ns app.common.svg.path.command
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.point :as gpt])) [app.common.geom.point :as gpt]))

View file

@ -4,7 +4,7 @@
;; ;;
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.common.path.shapes-to-path (ns app.common.svg.path.shapes-to-path
(:require (:require
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.data :as d] [app.common.data :as d]
@ -13,8 +13,8 @@
[app.common.geom.shapes.common :as gco] [app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.corners :as gso] [app.common.geom.shapes.corners :as gso]
[app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.path :as gsp]
[app.common.path.bool :as pb] [app.common.svg.path.bool :as pb]
[app.common.path.commands :as pc] [app.common.svg.path.command :as pc]
[app.common.types.shape.radius :as ctsr])) [app.common.types.shape.radius :as ctsr]))
(def ^:const bezier-circle-c 0.551915024494) (def ^:const bezier-circle-c 0.551915024494)

View file

@ -4,11 +4,11 @@
;; ;;
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.common.path.subpaths (ns app.common.svg.path.subpath
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.path.commands :as upc])) [app.common.svg.path.command :as upc]))
(defn pt= (defn pt=
"Check if two points are close" "Check if two points are close"

View file

@ -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 %)))]))))))

View file

@ -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 <https://opensource.org/licenses/MIT>
*/
"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;
});

View file

@ -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")))
)

View file

@ -6,7 +6,7 @@
(ns common-tests.helpers.files (ns common-tests.helpers.files
(:require (:require
[app.common.files.features :as ffeat] [app.common.features :as ffeat]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.types.colors-list :as ctcl] [app.common.types.colors-list :as ctcl]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]

View file

@ -6,7 +6,7 @@
(ns common-tests.pages-test (ns common-tests.pages-test
(:require (:require
[app.common.files.features :as ffeat] [app.common.features :as ffeat]
[app.common.pages :as cp] [app.common.pages :as cp]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]

View file

@ -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")

View file

@ -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"}}))))

4
common/vendor/beicon/impl/rxjs.cljs vendored Normal file
View file

@ -0,0 +1,4 @@
(ns beicon.impl.rxjs
(:require ["rxjs" :as rx]))
(goog/exportSymbol "rxjsMain" rx)

View file

@ -0,0 +1,4 @@
(ns beicon.impl.rxjs-operators
(:require ["rxjs/operators" :as rxop]))
(goog/exportSymbol "rxjsOperators" rxop)

4
common/vendor/tubax/saxjs.cljs vendored Normal file
View file

@ -0,0 +1,4 @@
(ns tubax.saxjs
(:require ["sax" :as sax]))
(goog/exportSymbol "sax" sax)

View file

@ -515,6 +515,11 @@ safer-buffer@^2.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 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: setimmediate@^1.0.4:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"

View file

@ -10,7 +10,6 @@
funcool/beicon {:mvn/version "2021.07.05-1"} funcool/beicon {:mvn/version "2021.07.05-1"}
funcool/okulary {:mvn/version "2022.04.11-16"} funcool/okulary {:mvn/version "2022.04.11-16"}
funcool/potok {:mvn/version "2022.12.16-71"} funcool/potok {:mvn/version "2022.12.16-71"}
funcool/tubax {:mvn/version "2021.05.20-0"}
funcool/rumext funcool/rumext
{:git/tag "v2.7" {:git/tag "v2.7"

View file

@ -11,6 +11,8 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.features :as ffeat] [app.common.files.features :as ffeat]
[app.common.files.helpers :as cfh] [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.align :as gal]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.proportions :as gpp] [app.common.geom.proportions :as gpp]
@ -55,7 +57,6 @@
[app.main.data.workspace.layers :as dwly] [app.main.data.workspace.layers :as dwly]
[app.main.data.workspace.layout :as layout] [app.main.data.workspace.layout :as layout]
[app.main.data.workspace.libraries :as dwl] [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.media :as dwm]
[app.main.data.workspace.notifications :as dwn] [app.main.data.workspace.notifications :as dwn]
[app.main.data.workspace.path :as dwdp] [app.main.data.workspace.path :as dwdp]
@ -2077,14 +2078,14 @@
page page
(cons shape children)) (cons shape children))
[_ _ changes2] (dwlh/generate-add-component it [_ _ changes2] (cflh/generate-add-component it
[shape] [shape]
(:objects page') (:objects page')
(:id page) (:id page)
(:id file-data) (:id file-data)
true true
nil nil
dwsh/prepare-create-artboard-from-selection) cfsh/prepare-create-artboard-from-selection)
changes (pcb/concat-changes changes1 changes2)] changes (pcb/concat-changes changes1 changes2)]

View file

@ -10,7 +10,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.pages.changes-builder :as pcb] [app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph] [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.container :as ctn]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]

View file

@ -6,7 +6,7 @@
(ns app.main.data.workspace.colors (ns app.main.data.workspace.colors
(:require (:require
[app.common.colors :as colors] [app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
@ -355,7 +355,7 @@
(assoc-in [:workspace-global :picking-color?] true) (assoc-in [:workspace-global :picking-color?] true)
(assoc ::md/modal {:id (random-uuid) (assoc ::md/modal {:id (random-uuid)
:type :colorpicker :type :colorpicker
:props {:data {:color colors/black :props {:data {:color cc/black
:opacity 1} :opacity 1}
:disable-opacity false :disable-opacity false
:disable-gradient false :disable-gradient false
@ -438,9 +438,9 @@
(defn split-color-components (defn split-color-components
[{:keys [color opacity] :as data}] [{:keys [color opacity] :as data}]
(let [value (if (uc/hex? color) color colors/black) (let [value (if (cc/valid-hex-color? color) color cc/black)
[r g b] (uc/hex->rgb value) [r g b] (cc/hex->rgb value)
[h s v] (uc/hex->hsv value)] [h s v] (cc/hex->hsv value)]
(merge data (merge data
{:hex (or value "000000") {:hex (or value "000000")
:alpha (or opacity 1) :alpha (or opacity 1)

View file

@ -9,6 +9,8 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.features :as ffeat] [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.geom.point :as gpt]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.pages :as cp] [app.common.pages :as cp]
@ -305,9 +307,9 @@
parents (into #{} (map :parent-id) shapes)] parents (into #{} (map :parent-id) shapes)]
(when-not (empty? shapes) (when-not (empty? shapes)
(let [[root _ changes] (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 dwg/prepare-create-group
dwsh/prepare-create-artboard-from-selection)] cfsh/prepare-create-artboard-from-selection)]
(when-not (empty? (:redo-changes changes)) (when-not (empty? (:redo-changes changes))
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dws/select-shapes (d/ordered-set (:id root))) (dws/select-shapes (d/ordered-set (:id root)))

View file

@ -25,7 +25,6 @@
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.types.typography :as cty] [app.common.types.typography :as cty]
[app.common.uuid :as uuid]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[clojure.set :as set])) [clojure.set :as set]))
@ -63,75 +62,6 @@
;; ---- Components and instances creation ---- ;; ---- 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 (defn duplicate-component
"Clone the root shape of the component and all children. Generate new "Clone the root shape of the component and all children. Generate new
ids from all of them." ids from all of them."
@ -141,7 +71,10 @@
(let [main-instance-page (ctf/get-component-page library-data component) (let [main-instance-page (ctf/get-component-page library-data component)
main-instance-shape (ctf/get-component-root 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} {}) options (if components-v2 {:main-instance? true} {})
[new-instance-shape new-instance-shapes] [new-instance-shape new-instance-shapes]

View file

@ -14,6 +14,7 @@
[app.common.pages.changes-builder :as pcb] [app.common.pages.changes-builder :as pcb]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.svg :refer [optimize]] [app.common.svg :refer [optimize]]
[app.common.svg.shapes-builder :as csvg.shapes-builder]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
@ -274,7 +275,7 @@
process-svg process-svg
(fn [svg-data] (fn [svg-data]
(let [[shape children] (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]))] [shape children]))]
(->> (upload-images svg-data) (->> (upload-images svg-data)

View file

@ -9,8 +9,8 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.flex-layout :as gsl] [app.common.geom.shapes.flex-layout :as gsl]
[app.common.path.commands :as upc] [app.common.svg.path.command :as upc]
[app.common.path.shapes-to-path :as upsp] [app.common.svg.path.shapes-to-path :as upsp]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]

View file

@ -11,9 +11,9 @@
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as upg] [app.common.geom.shapes.path :as upg]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.path.commands :as upc] [app.common.svg.path.command :as upc]
[app.common.path.shapes-to-path :as upsp] [app.common.svg.path.shapes-to-path :as upsp]
[app.common.path.subpaths :as ups] [app.common.svg.path.subpath :as ups]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc] [app.main.data.workspace.common :as dwc]
[app.main.data.workspace.edition :as dwe] [app.main.data.workspace.edition :as dwe]

View file

@ -11,8 +11,8 @@
[app.common.geom.rect :as grc] [app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.path.commands :as upc] [app.common.svg.path.command :as upc]
[app.common.path.subpaths :as ups] [app.common.svg.path.subpath :as ups]
[app.main.data.workspace.path.common :as common] [app.main.data.workspace.path.common :as common]
[app.main.streams :as ms] [app.main.streams :as ms]
[potok.core :as ptk])) [potok.core :as ptk]))

View file

@ -8,7 +8,7 @@
(:require (:require
[app.common.pages.changes-builder :as pcb] [app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph] [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.common.types.container :as ctn]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]

View file

@ -6,7 +6,7 @@
(ns app.main.data.workspace.path.state (ns app.main.data.workspace.path.state
(:require (:require
[app.common.path.shapes-to-path :as upsp])) [app.common.svg.path.shapes-to-path :as upsp]))
(defn get-path-id (defn get-path-id
"Retrieves the currently editing path id" "Retrieves the currently editing path id"

View file

@ -6,8 +6,8 @@
(ns app.main.data.workspace.path.tools (ns app.main.data.workspace.path.tools
(:require (:require
[app.common.path.shapes-to-path :as upsp] [app.common.svg.path.shapes-to-path :as upsp]
[app.common.path.subpaths :as ups] [app.common.svg.path.subpath :as ups]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.edition :as dwe] [app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.path.changes :as changes] [app.main.data.workspace.path.changes :as changes]

View file

@ -9,6 +9,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.libraries-helpers :as cflh]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.rect :as grc] [app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
@ -408,6 +409,7 @@
(instantiate-component))] (instantiate-component))]
changes)) changes))
;; TODO: move to common.files.shape-helpers
(defn- prepare-duplicate-shape-change (defn- prepare-duplicate-shape-change
([changes objects page unames update-unames! ids-map obj delta libraries library-data it file-id] ([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)) (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 regenerate-component
(fn [changes shape] (fn [changes shape]
(let [components-v2 (dm/get-in library-data [:options :components-v2]) (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)) changes))
new-obj new-obj

View file

@ -8,7 +8,7 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [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.changes-builder :as pcb]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.schema :as sm] [app.common.schema :as sm]
@ -17,8 +17,6 @@
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi] [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.comments :as dc]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.edition :as dwe] [app.main.data.workspace.edition :as dwe]
@ -32,28 +30,6 @@
(def valid-shape-map? (def valid-shape-map?
(sm/pred-fn ::cts/shape)) (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 (defn add-shape
([shape] ([shape]
(add-shape shape {})) (add-shape shape {}))
@ -73,7 +49,7 @@
[shape changes] [shape changes]
(-> (pcb/empty-changes it page-id) (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects) (pcb/with-objects objects)
(prepare-add-shape shape objects selected)) (cfsh/prepare-add-shape shape objects selected))
changes (cond-> changes changes (cond-> changes
(cph/text-shape? shape) (cph/text-shape? shape)
@ -93,23 +69,6 @@
(->> (rx/of (dwe/start-edition-mode (:id shape))) (->> (rx/of (dwe/start-edition-mode (:id shape)))
(rx/observe-on :async))))))))) (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 (defn move-shapes-into-frame
[frame-id shapes] [frame-id shapes]
(ptk/reify ::move-shapes-into-frame (ptk/reify ::move-shapes-into-frame
@ -120,10 +79,10 @@
shapes (->> shapes (remove #(dm/get-in objects [% :blocked]))) shapes (->> shapes (remove #(dm/get-in objects [% :blocked])))
changes (-> (pcb/empty-changes it page-id) changes (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)) (pcb/with-objects objects))
changes (prepare-move-shapes-into-frame changes changes (cfsh/prepare-move-shapes-into-frame changes
frame-id frame-id
shapes shapes
objects)] objects)]
(if (some? changes) (if (some? changes)
(rx/of (dch/commit-changes changes)) (rx/of (dch/commit-changes changes))
(rx/empty)))))) (rx/empty))))))
@ -366,58 +325,6 @@
;; Artboard ;; 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 (defn create-artboard-from-selection
([] ([]
(create-artboard-from-selection nil)) (create-artboard-from-selection nil))
@ -438,14 +345,14 @@
(pcb/with-objects objects)) (pcb/with-objects objects))
[frame-shape changes] [frame-shape changes]
(prepare-create-artboard-from-selection changes (cfsh/prepare-create-artboard-from-selection changes
id id
parent-id parent-id
objects objects
selected selected
index index
nil nil
false) false)
undo-id (js/Symbol)] undo-id (js/Symbol)]

View file

@ -11,7 +11,7 @@
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.path.commands :as upc] [app.common.svg.path.command :as upc]
[app.common.uuid :as uuid])) [app.common.uuid :as uuid]))
(defn lookup-page (defn lookup-page

View file

@ -6,467 +6,23 @@
(ns app.main.data.workspace.svg-upload (ns app.main.data.workspace.svg-upload
(:require (:require
[app.common.colors :as clr]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [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.changes-builder :as pcb]
[app.common.pages.helpers :as cph] [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.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.common.types.shape-tree :as ctst]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.selection :as dws] [app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.util.color :as uc]
[app.util.path.parser :as upp]
[app.util.webapi :as wapi] [app.util.webapi :as wapi]
[beicon.core :as rx] [beicon.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.core :as ptk])) [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] (defn extract-name [url]
(let [query-idx (str/last-index-of url "?") (let [query-idx (str/last-index-of url "?")
url (if (> query-idx 0) (subs url 0 query-idx) url) url (if (> query-idx 0) (subs url 0 query-idx) url)
@ -499,70 +55,6 @@
(rx/map #(vector (:url uri-data) %))))) (rx/map #(vector (:url uri-data) %)))))
(rx/reduce (fn [acc [url image]] (assoc acc url image)) {}))) (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 (defn add-svg-shapes
[svg-data position] [svg-data position]
(ptk/reify ::add-svg-shapes (ptk/reify ::add-svg-shapes
@ -584,7 +76,7 @@
(:parent-id base)) (:parent-id base))
[new-shape new-children] [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) changes (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects) (pcb/with-objects objects)

View file

@ -6,8 +6,8 @@
(ns app.main.ui.components.color-input (ns app.main.ui.components.color-input
(:require (:require
[app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]
[app.util.color :as uc]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.globals :as globals] [app.util.globals :as globals]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
@ -19,9 +19,9 @@
(defn clean-color (defn clean-color
[value] [value]
(-> value (-> value
(uc/expand-hex) (cc/expand-hex)
(uc/parse-color) (cc/parse)
(uc/prepend-hash))) (cc/prepend-hash)))
(mf/defc color-input* (mf/defc color-input*
{::mf/wrap-props false {::mf/wrap-props false
@ -62,14 +62,14 @@
(mf/deps ref) (mf/deps ref)
(fn [new-value] (fn [new-value]
(let [input-node (mf/ref-val ref)] (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 apply-value
(mf/use-fn (mf/use-fn
(mf/deps on-change update-input) (mf/deps on-change update-input)
(fn [new-value] (fn [new-value]
(mf/set-ref-val! dirty-ref false) (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 (when on-change
(on-change new-value)) (on-change new-value))
(update-input new-value)))) (update-input new-value))))
@ -170,7 +170,7 @@
[:> :input props] [:> :input props]
;; FIXME: this causes some weird interactions because of using apply-value ;; FIXME: this causes some weird interactions because of using apply-value
;; [:datalist {:id list-id} ;; [:datalist {:id list-id}
;; (for [color-name uc/color-names] ;; (for [color-name cc/color-names]
;; [:option color-name])] ;; [:option color-name])]
])) ]))

View file

@ -6,11 +6,11 @@
(ns app.main.ui.shapes.filters (ns app.main.ui.shapes.filters
(:require (:require
[app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.shapes.bounds :as gsb] [app.common.geom.shapes.bounds :as gsb]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.util.color :as color]
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -26,7 +26,7 @@
(mf/defc color-matrix (mf/defc color-matrix
[{:keys [color]}] [{:keys [color]}]
(let [{:keys [color opacity]} 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)]] [r g b] [(/ r 255) (/ g 255) (/ b 255)]]
[:feColorMatrix [:feColorMatrix
{:type "matrix" {:type "matrix"

View file

@ -6,12 +6,11 @@
(ns app.main.ui.shapes.text.fo-text (ns app.main.ui.shapes.text.fo-text
(:require (:require
[app.common.colors :as clr] [app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.text.styles :as sts] [app.main.ui.shapes.text.styles :as sts]
[app.util.color :as uc]
[app.util.object :as obj] [app.util.object :as obj]
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -85,9 +84,9 @@
[colors] [colors]
(assert (set? colors)) (assert (set? colors))
(loop [current-rgb [0 0 0]] (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) (if (contains? colors current-hex)
(recur (uc/next-rgb current-rgb)) (recur (cc/next-rgb current-rgb))
current-hex)))) current-hex))))
(defn- fill->color (defn- fill->color
@ -122,7 +121,7 @@
(filter some?)) (filter some?))
colors (->> color-data colors (->> color-data
(into #{clr/black} (into #{cc/black}
(comp (filter #(= :solid (:type %))) (comp (filter #(= :solid (:type %)))
(map :hex)))) (map :hex))))

View file

@ -6,6 +6,7 @@
(ns app.main.ui.shapes.text.styles (ns app.main.ui.shapes.text.styles
(:require (:require
[app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.transit :as transit] [app.common.transit :as transit]
@ -76,7 +77,7 @@
fill-opacity (or (-> data :fills first :fill-opacity) (:fill-opacity data)) fill-opacity (or (-> data :fills first :fill-opacity) (:fill-opacity data))
fill-gradient (or (-> data :fills first :fill-color-gradient) (:fill-color-gradient 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)) text-color (when (and (some? fill-color) (some? fill-opacity))
(str/format "rgba(%s, %s, %s, %s)" r g b a)) (str/format "rgba(%s, %s, %s, %s)" r g b a))

View file

@ -7,6 +7,7 @@
(ns app.main.ui.viewer.inspect.attributes.common (ns app.main.ui.viewer.inspect.attributes.common
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.colors :as cc]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.color-bullet :refer [color-bullet color-name]] [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.copy-button :refer [copy-button]]
[app.main.ui.components.select :refer [select]] [app.main.ui.components.select :refer [select]]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.util.color :as uc]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -85,10 +85,10 @@
(case format (case format
:hex [:& cbn/color-name {:color color :hex [:& cbn/color-name {:color color
:size 80}] :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)]) [:* (str/fmt "%s, %s, %s, %s" r g b a)])
:hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color)) :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
result (uc/format-hsla [h s l a])] result (cc/format-hsla [h s l a])]
[:* result])))] [:* result])))]
(when-not (:gradient color) (when-not (:gradient color)
@ -111,10 +111,10 @@
(case format (case format
:hex [:& cbn/color-name {:color color :hex [:& cbn/color-name {:color color
:size 80}] :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)]) [:* (str/fmt "%s, %s, %s, %s" r g b a)])
:hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color)) :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
result (uc/format-hsla [h s l a])] result (cc/format-hsla [h s l a])]
[:* result])))] [:* result])))]
(when-not (:gradient color) (when-not (:gradient color)
@ -135,10 +135,10 @@
;; (case format ;; (case format
;; :hex [:& cbn/color-name {:color color ;; :hex [:& cbn/color-name {:color color
;; :size 80}] ;; :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)]) ;; [:* (str/fmt "%s, %s, %s, %s" r g b a)])
;; :hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color)) ;; :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
;; result (uc/format-hsla [h s l a])] ;; result (cc/format-hsla [h s l a])]
;; [:* result])))] ;; [:* result])))]
;; (when color-library-name ;; (when color-library-name
@ -163,10 +163,10 @@
(if (:gradient color) (if (:gradient color)
[:& color-name {:color color}] [:& color-name {:color color}]
(case format (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)]) [:div (str/fmt "%s, %s, %s, %s" r g b a)])
:hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color)) :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
result (uc/format-hsla [h s l a])] result (cc/format-hsla [h s l a])]
[:div result]) [:div result])
[:* [:*
[:& color-name {:color color}] [:& color-name {:color color}]

View file

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.colorpicker (ns app.main.ui.workspace.colorpicker
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.colors :as cc]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace.colors :as dc] [app.main.data.workspace.colors :as dc]
[app.main.data.workspace.libraries :as dwl] [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.hsva :refer [hsva-selector]]
[app.main.ui.workspace.colorpicker.libraries :refer [libraries]] [app.main.ui.workspace.colorpicker.libraries :refer [libraries]]
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]] [app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]]
[app.util.color :as uc]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -190,9 +190,9 @@
(let [node (mf/ref-val node-ref) (let [node (mf/ref-val node-ref)
{:keys [r g b h v]} current-color {:keys [r g b h v]} current-color
rgb [r g b] rgb [r g b]
hue-rgb (uc/hsv->rgb [h 1.0 255]) hue-rgb (cc/hsv->rgb [h 1.0 255])
hsl-from (uc/hsv->hsl [h 0.0 v]) hsl-from (cc/hsv->hsl [h 0.0 v])
hsl-to (uc/hsv->hsl [h 1.0 v]) hsl-to (cc/hsv->hsl [h 1.0 v])
format-hsl (fn [[h s l]] format-hsl (fn [[h s l]]
(str/fmt "hsl(%s, %s, %s)" (str/fmt "hsl(%s, %s, %s)"
@ -208,8 +208,8 @@
(mf/with-effect [picking-color? picked-color picked-color-select] (mf/with-effect [picking-color? picked-color picked-color-select]
(when (and picking-color? picked-color picked-color-select) (when (and picking-color? picked-color picked-color-select)
(let [[r g b alpha] picked-color (let [[r g b alpha] picked-color
hex (uc/rgb->hex [r g b]) hex (cc/rgb->hex [r g b])
[h s v] (uc/hex->hsv hex)] [h s v] (cc/hex->hsv hex)]
(handle-change-color {:hex hex (handle-change-color {:hex hex
:r r :g g :b b :r r :g g :b b
:h h :s s :v v :h h :s s :v v

View file

@ -7,10 +7,10 @@
(ns app.main.ui.workspace.colorpicker.color-inputs (ns app.main.ui.workspace.colorpicker.color-inputs
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as mth] [app.common.math :as mth]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.util.color :as uc]
[app.util.dom :as dom] [app.util.dom :as dom]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -45,23 +45,27 @@
setup-hex-color setup-hex-color
(fn [hex] (fn [hex]
(let [[r g b] (uc/hex->rgb hex) (let [[r g b] (cc/hex->rgb hex)
[h s v] (uc/hex->hsv hex)] [h s v] (cc/hex->hsv hex)]
(on-change {:hex hex (on-change {:hex hex
:h h :s s :v v :h h :s s :v v
:r r :g g :b b}))) :r r :g g :b b})))
on-change-hex on-change-hex
(fn [e] (fn [e]
(let [val (-> e dom/get-target-val parse-hex)] (let [val (-> e dom/get-target-val parse-hex)]
(when (uc/hex? val) (when (cc/valid-hex-color? val)
(setup-hex-color val)))) (setup-hex-color val))))
on-blur-hex on-blur-hex
(fn [e] (fn [e]
(let [val (-> e dom/get-target-val) (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 val (cond
(uc/color? val) (uc/parse-color val) (cc/color-string? val) (cc/parse val)
(uc/hex? (parse-hex val)) (parse-hex val))] (cc/valid-hex-color? (parse-hex val)) (parse-hex val))]
(when (some? val) (when (some? val)
(setup-hex-color val)))) (setup-hex-color val))))
@ -76,15 +80,15 @@
(when (not (nil? val)) (when (not (nil? val))
(if (#{:r :g :b} property) (if (#{:r :g :b} property)
(let [{:keys [r g b]} (merge color (hash-map property val)) (let [{:keys [r g b]} (merge color (hash-map property val))
hex (uc/rgb->hex [r g b]) hex (cc/rgb->hex [r g b])
[h s v] (uc/hex->hsv hex)] [h s v] (cc/hex->hsv hex)]
(on-change {:hex hex (on-change {:hex hex
:h h :s s :v v :h h :s s :v v
:r r :g g :b b})) :r r :g g :b b}))
(let [{:keys [h s v]} (merge color (hash-map property val)) (let [{:keys [h s v]} (merge color (hash-map property val))
hex (uc/hsv->hex [h s v]) hex (cc/hsv->hex [h s v])
[r g b] (uc/hex->rgb hex)] [r g b] (cc/hex->rgb hex)]
(on-change {:hex hex (on-change {:hex hex
:h h :s s :v v :h h :s s :v v
:r r :g g :b b}))))))) :r r :g g :b b})))))))

View file

@ -7,11 +7,11 @@
(ns app.main.ui.workspace.colorpicker.harmony (ns app.main.ui.workspace.colorpicker.harmony
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.colors :as cc]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.math :as mth] [app.common.math :as mth]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]]
[app.util.color :as uc]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.object :as obj] [app.util.object :as obj]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -82,8 +82,8 @@
angle (mth/degrees (mth/atan2 px py)) angle (mth/degrees (mth/atan2 px py))
new-hue (mod (- angle 90) 360) new-hue (mod (- angle 90) 360)
new-saturation (mth/clamp (mth/distance [px py] [0 0]) 0 1) new-saturation (mth/clamp (mth/distance [px py] [0 0]) 0 1)
hex (uc/hsv->hex [new-hue new-saturation value]) hex (cc/hsv->hex [new-hue new-saturation value])
[r g b] (uc/hex->rgb hex)] [r g b] (cc/hex->rgb hex)]
(on-change {:hex hex (on-change {:hex hex
:r r :g g :b b :r r :g g :b b
:h new-hue :h new-hue
@ -108,15 +108,15 @@
(on-finish-drag)))) (on-finish-drag))))
on-change-value (fn [new-value] on-change-value (fn [new-value]
(let [hex (uc/hsv->hex [hue saturation new-value]) (let [hex (cc/hsv->hex [hue saturation new-value])
[r g b] (uc/hex->rgb hex)] [r g b] (cc/hex->rgb hex)]
(on-change {:hex hex (on-change {:hex hex
:r r :g g :b b :r r :g g :b b
:v new-value}))) :v new-value})))
on-complement-click (fn [_] on-complement-click (fn [_]
(let [new-hue (mod (+ hue 180) 360) (let [new-hue (mod (+ hue 180) 360)
hex (uc/hsv->hex [new-hue saturation value]) hex (cc/hsv->hex [new-hue saturation value])
[r g b] (uc/hex->rgb hex)] [r g b] (cc/hex->rgb hex)]
(on-change {:hex hex (on-change {:hex hex
:r r :g g :b b :r r :g g :b b
:h new-hue :h new-hue

View file

@ -7,9 +7,9 @@
(ns app.main.ui.workspace.colorpicker.hsva (ns app.main.ui.workspace.colorpicker.hsva
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.colors :as cc]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]]
[app.util.color :as uc]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc hsva-selector [{:keys [color disable-opacity on-change on-start-drag on-finish-drag]}] (mf/defc hsva-selector [{:keys [color disable-opacity on-change on-start-drag on-finish-drag]}]
@ -19,8 +19,8 @@
(fn [new-value] (fn [new-value]
(let [change (hash-map key new-value) (let [change (hash-map key new-value)
{:keys [h s v]} (merge color change) {:keys [h s v]} (merge color change)
hex (uc/hsv->hex [h s v]) hex (cc/hsv->hex [h s v])
[r g b] (uc/hex->rgb hex)] [r g b] (cc/hex->rgb hex)]
(on-change (merge change (on-change (merge change
{:hex hex {:hex hex
:r r :g g :b b}))))) :r r :g g :b b})))))

View file

@ -7,12 +7,12 @@
(ns app.main.ui.workspace.colorpicker.ramp (ns app.main.ui.workspace.colorpicker.ramp
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.colors :as cc]
[app.common.math :as mth] [app.common.math :as mth]
[app.main.ui.components.color-bullet :refer [color-bullet]] [app.main.ui.components.color-bullet :refer [color-bullet]]
[app.main.ui.components.color-bullet-new :as cb] [app.main.ui.components.color-bullet-new :as cb]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]]
[app.util.color :as uc]
[app.util.dom :as dom] [app.util.dom :as dom]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -72,8 +72,8 @@
on-change-value-saturation on-change-value-saturation
(fn [new-saturation new-value] (fn [new-saturation new-value]
(let [hex (uc/hsv->hex [hue new-saturation new-value]) (let [hex (cc/hsv->hex [hue new-saturation new-value])
[r g b] (uc/hex->rgb hex)] [r g b] (cc/hex->rgb hex)]
(on-change {:hex hex (on-change {:hex hex
:r r :g g :b b :r r :g g :b b
:s new-saturation :s new-saturation
@ -81,8 +81,8 @@
on-change-hue on-change-hue
(fn [new-hue] (fn [new-hue]
(let [hex (uc/hsv->hex [new-hue saturation value]) (let [hex (cc/hsv->hex [new-hue saturation value])
[r g b] (uc/hex->rgb hex)] [r g b] (cc/hex->rgb hex)]
(on-change {:hex hex (on-change {:hex hex
:r r :g g :b b :r r :g g :b b
:h new-hue}))) :h new-hue})))

View file

@ -6,7 +6,7 @@
(ns app.main.ui.workspace.shapes.path (ns app.main.ui.workspace.shapes.path
(:require (:require
[app.common.path.commands :as upc] [app.common.svg.path.command :as upc]
[app.main.data.workspace.path.helpers :as helpers] [app.main.data.workspace.path.helpers :as helpers]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.shapes.path :as path] [app.main.ui.shapes.path :as path]

View file

@ -10,8 +10,8 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.path :as gsp]
[app.common.path.commands :as upc] [app.common.svg.path.command :as upc]
[app.common.path.shapes-to-path :as ups] [app.common.svg.path.shapes-to-path :as ups]
[app.main.data.workspace.path :as drp] [app.main.data.workspace.path :as drp]
[app.main.snap :as snap] [app.main.snap :as snap]
[app.main.store :as st] [app.main.store :as st]

View file

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.sidebar.options.rows.color-row (ns app.main.ui.workspace.sidebar.options.rows.color-row
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.types.shape.attrs :refer [default-color]] [app.common.types.shape.attrs :refer [default-color]]
@ -222,7 +223,7 @@
[:span {:class (stl/css :color-input-wrapper)} [:span {:class (stl/css :color-input-wrapper)}
[:> color-input* {:value (if multiple-colors? [:> color-input* {:value (if multiple-colors?
"" ""
(-> color :color uc/remove-hash)) (-> color :color cc/remove-hash))
:placeholder (tr "settings.multiple") :placeholder (tr "settings.multiple")
:className (stl/css :color-input) :className (stl/css :color-input)
:on-focus on-focus :on-focus on-focus
@ -301,7 +302,7 @@
[:div.color-info [:div.color-info
[:> color-input* {:value (if multiple-colors? [:> color-input* {:value (if multiple-colors?
"" ""
(-> color :color uc/remove-hash)) (-> color :color cc/remove-hash))
:placeholder (tr "settings.multiple") :placeholder (tr "settings.multiple")
:on-focus on-focus :on-focus on-focus
:on-blur on-blur :on-blur on-blur

View file

@ -6,7 +6,7 @@
(ns app.main.ui.workspace.sidebar.options.shapes.svg-raw (ns app.main.ui.workspace.sidebar.options.shapes.svg-raw
(:require (:require
[app.common.colors :as clr] [app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.main.refs :as refs] [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.shadow :refer [shadow-menu]]
[app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-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.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]]
[app.util.color :as uc]
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -45,7 +44,7 @@
{:color :multiple {:color :multiple
:opacity :multiple} :opacity :multiple}
:else {:color (uc/parse-color color) :else {:color (cc/parse color)
:opacity 1}) :opacity 1})
(catch :default e (catch :default e
@ -71,7 +70,7 @@
(get-in shape [:content :attrs :style :stroke])) (get-in shape [:content :attrs :style :stroke]))
(parse-color)) (parse-color))
stroke-color (:color color clr/black) stroke-color (:color color cc/black)
stroke-opacity (:opacity color 1) stroke-opacity (:opacity color 1)
stroke-style (-> (or (get-in shape [:content :attrs :stroke-style]) stroke-style (-> (or (get-in shape [:content :attrs :stroke-style])
(get-in shape [:content :attrs :style :stroke-style]) (get-in shape [:content :attrs :style :stroke-style])

View file

@ -5,125 +5,20 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.util.color (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 (:require
[app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.object :as obj] [cuerdas.core :as str]))
[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))
(defn gradient->css [{:keys [type stops]}] (defn gradient->css [{:keys [type stops]}]
(let [parse-stop (let [parse-stop
(fn [{:keys [offset color opacity]}] (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) "%")))) (str/fmt "rgba(%s, %s, %s, %s) %s" r g b opacity (str (* offset 100) "%"))))
stops-css (str/join "," (map parse-stop stops))] stops-css (str/join "," (map parse-stop stops))]
@ -147,7 +42,7 @@
(gradient->css gradient) (gradient->css gradient)
(not= color :multiple) (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)) (str/fmt "rgba(%s, %s, %s, %s)" r g b opacity))
:else "transparent"))) :else "transparent")))
@ -160,56 +55,27 @@
(not= color :multiple) (not= color :multiple)
(case format (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)) (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)) (str/fmt "hsla(%s, %s, %s, %s)" h (* 100 s) (* 100 l) opacity))
:hex (str color (str/upper (d/opacity-to-hex opacity)))) :hex (str color (str/upper (d/opacity-to-hex opacity))))
:else "transparent"))) :else "transparent")))
(defn multiple? [{:keys [id file-id value color gradient]}] (defn multiple?
[{:keys [id file-id value color gradient]}]
(or (= value :multiple) (or (= value :multiple)
(= color :multiple) (= color :multiple)
(= gradient :multiple) (= gradient :multiple)
(= id :multiple) (= id :multiple)
(= file-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 (def empty-color
(into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity])) (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 (defn get-color-name
[color] [color]
(or (:color-library-name color) (or (:color-library-name color)

View file

@ -6,15 +6,15 @@
(ns app.util.import.parser (ns app.util.import.parser
(:require (:require
[app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.svg.path :as svg.path]
[app.common.types.shape.interactions :as ctsi] [app.common.types.shape.interactions :as ctsi]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.util.color :as uc]
[app.util.json :as json] [app.util.json :as json]
[app.util.path.parser :as upp]
[cuerdas.core :as str])) [cuerdas.core :as str]))
(def url-regex (def url-regex
@ -278,7 +278,7 @@
(defn parse-path (defn parse-path
[props center svg-data] [props center svg-data]
(let [content (upp/parse-path (:d svg-data))] (let [content (svg.path/parse (:d svg-data))]
(-> props (-> props
(assoc :content content) (assoc :content content)
(assoc :center center)))) (assoc :center center))))
@ -454,7 +454,7 @@
:fill-color nil :fill-color nil
:fill-opacity nil) :fill-opacity nil)
(uc/hex? fill) (cc/valid-hex-color? fill)
(assoc :fill-color fill (assoc :fill-color fill
:fill-opacity (-> svg-data (:fill-opacity "1") d/parse-double)) :fill-opacity (-> svg-data (:fill-opacity "1") d/parse-double))

View file

@ -6,8 +6,8 @@
(ns app.util.path.format (ns app.util.path.format
(:require (:require
[app.common.path.commands :as upc] [app.common.svg.path.command :as upc]
[app.common.path.subpaths :refer [pt=]] [app.common.svg.path.subpath :refer [pt=]]
[app.util.array :as arr])) [app.util.array :as arr]))
(def path-precision 3) (def path-precision 3)

View file

@ -9,7 +9,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as upg] [app.common.geom.shapes.path :as upg]
[app.common.path.commands :as upc] [app.common.svg.path.command :as upc]
[clojure.set :as set])) [clojure.set :as set]))
(defn remove-line-curves (defn remove-line-curves

View file

@ -7,9 +7,13 @@
(ns frontend-tests.helpers.pages (ns frontend-tests.helpers.pages
(:require (:require
[app.common.data :as d] [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.geom.point :as gpt]
[app.common.pages :as cp] [app.common.pages :as cp]
[app.common.pages.changes-builder :as pcb] [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.pages.helpers :as cph]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
@ -113,7 +117,7 @@
(if (empty? shapes) (if (empty? shapes)
state state
(let [[frame changes] (let [[frame changes]
(dwsh/prepare-create-artboard-from-selection changes (cfsh/prepare-create-artboard-from-selection changes
nil nil
nil nil
(:objects page) (:objects page)
@ -133,14 +137,14 @@
shapes (dwg/shapes-for-grouping objects shape-ids) shapes (dwg/shapes-for-grouping objects shape-ids)
[group component-id changes] [group component-id changes]
(dwlh/generate-add-component nil (cflh/generate-add-component nil
shapes shapes
(:objects page) (:objects page)
(:id page) (:id page)
current-file-id current-file-id
true true
dwg/prepare-create-group dwg/prepare-create-group
dwsh/prepare-create-artboard-from-selection)] cfsh/prepare-create-artboard-from-selection)]
(swap! idmap assoc instance-label (:id group) (swap! idmap assoc instance-label (:id group)
component-label component-id) component-label component-id)

View file

@ -201,7 +201,9 @@
(t/is (= (:shape-ref c-shape1) nil)) (t/is (= (:shape-ref c-shape1) nil))
(t/is (= (:name c-shape2) "Rect 2")) (t/is (= (:name c-shape2) "Rect 2"))
(t/is (= (:touched c-shape2) nil)) (t/is (= (:touched c-shape2) nil))
(t/is (= (:shape-ref c-shape2) nil)))))] (t/is (= (:shape-ref c-shape2) nil))
)))]
(ptk/emit! (ptk/emit!
store store