mirror of
https://github.com/penpot/penpot.git
synced 2025-01-04 13:50:12 -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:
parent
44845d5d94
commit
3ceb4cf895
62 changed files with 2037 additions and 1011 deletions
|
@ -26,6 +26,7 @@
|
|||
com.cognitect/transit-cljs {:mvn/version "0.8.280"}
|
||||
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
||||
|
||||
funcool/tubax {:mvn/version "2021.05.20-0"}
|
||||
funcool/cuerdas {:mvn/version "2022.06.16-403"}
|
||||
funcool/promesa {:mvn/version "11.0.678"}
|
||||
funcool/datoteka {:mvn/version "3.0.66"
|
||||
|
@ -43,7 +44,7 @@
|
|||
fipp/fipp {:mvn/version "0.6.26"}
|
||||
io.aviso/pretty {:mvn/version "1.4.4"}
|
||||
environ/environ {:mvn/version "1.2.0"}}
|
||||
:paths ["src" "target/classes"]
|
||||
:paths ["src" "vendor" "target/classes"]
|
||||
:aliases
|
||||
{:dev
|
||||
{:extra-deps
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
"main": "index.js",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"luxon": "^3.4.2"
|
||||
"luxon": "^3.4.2",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"test:watch": "clojure -M:dev:shadow-cljs watch test",
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.colors
|
||||
(:refer-clojure :exclude [test]))
|
||||
(:refer-clojure :exclude [test])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.math :as mth]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(def black "#000000")
|
||||
(def canvas "#E8E9EA")
|
||||
|
@ -27,3 +32,428 @@
|
|||
(def new-danger "#ff4986")
|
||||
(def new-warning "#ff9b49")
|
||||
(def canvas-background "#1d1f20")
|
||||
|
||||
(def names
|
||||
{"aliceblue" "#f0f8ff"
|
||||
"antiquewhite" "#faebd7"
|
||||
"aqua" "#00ffff"
|
||||
"aquamarine" "#7fffd4"
|
||||
"azure" "#f0ffff"
|
||||
"beige" "#f5f5dc"
|
||||
"bisque" "#ffe4c4"
|
||||
"black" "#000000"
|
||||
"blanchedalmond" "#ffebcd"
|
||||
"blue" "#0000ff"
|
||||
"blueviolet" "#8a2be2"
|
||||
"brown" "#a52a2a"
|
||||
"burlywood" "#deb887"
|
||||
"cadetblue" "#5f9ea0"
|
||||
"chartreuse" "#7fff00"
|
||||
"chocolate" "#d2691e"
|
||||
"coral" "#ff7f50"
|
||||
"cornflowerblue" "#6495ed"
|
||||
"cornsilk" "#fff8dc"
|
||||
"crimson" "#dc143c"
|
||||
"cyan" "#00ffff"
|
||||
"darkblue" "#00008b"
|
||||
"darkcyan" "#008b8b"
|
||||
"darkgoldenrod" "#b8860b"
|
||||
"darkgray" "#a9a9a9"
|
||||
"darkgreen" "#006400"
|
||||
"darkgrey" "#a9a9a9"
|
||||
"darkkhaki" "#bdb76b"
|
||||
"darkmagenta" "#8b008b"
|
||||
"darkolivegreen" "#556b2f"
|
||||
"darkorange" "#ff8c00"
|
||||
"darkorchid" "#9932cc"
|
||||
"darkred" "#8b0000"
|
||||
"darksalmon" "#e9967a"
|
||||
"darkseagreen" "#8fbc8f"
|
||||
"darkslateblue" "#483d8b"
|
||||
"darkslategray" "#2f4f4f"
|
||||
"darkslategrey" "#2f4f4f"
|
||||
"darkturquoise" "#00ced1"
|
||||
"darkviolet" "#9400d3"
|
||||
"deeppink" "#ff1493"
|
||||
"deepskyblue" "#00bfff"
|
||||
"dimgray" "#696969"
|
||||
"dimgrey" "#696969"
|
||||
"dodgerblue" "#1e90ff"
|
||||
"firebrick" "#b22222"
|
||||
"floralwhite" "#fffaf0"
|
||||
"forestgreen" "#228b22"
|
||||
"fuchsia" "#ff00ff"
|
||||
"gainsboro" "#dcdcdc"
|
||||
"ghostwhite" "#f8f8ff"
|
||||
"gold" "#ffd700"
|
||||
"goldenrod" "#daa520"
|
||||
"gray" "#808080"
|
||||
"green" "#008000"
|
||||
"greenyellow" "#adff2f"
|
||||
"grey" "#808080"
|
||||
"honeydew" "#f0fff0"
|
||||
"hotpink" "#ff69b4"
|
||||
"indianred" "#cd5c5c"
|
||||
"indigo" "#4b0082"
|
||||
"ivory" "#fffff0"
|
||||
"khaki" "#f0e68c"
|
||||
"lavender" "#e6e6fa"
|
||||
"lavenderblush" "#fff0f5"
|
||||
"lawngreen" "#7cfc00"
|
||||
"lemonchiffon" "#fffacd"
|
||||
"lightblue" "#add8e6"
|
||||
"lightcoral" "#f08080"
|
||||
"lightcyan" "#e0ffff"
|
||||
"lightgoldenrodyellow" "#fafad2"
|
||||
"lightgray" "#d3d3d3"
|
||||
"lightgreen" "#90ee90"
|
||||
"lightgrey" "#d3d3d3"
|
||||
"lightpink" "#ffb6c1"
|
||||
"lightsalmon" "#ffa07a"
|
||||
"lightseagreen" "#20b2aa"
|
||||
"lightskyblue" "#87cefa"
|
||||
"lightslategray" "#778899"
|
||||
"lightslategrey" "#778899"
|
||||
"lightsteelblue" "#b0c4de"
|
||||
"lightyellow" "#ffffe0"
|
||||
"lime" "#00ff00"
|
||||
"limegreen" "#32cd32"
|
||||
"linen" "#faf0e6"
|
||||
"magenta" "#ff00ff"
|
||||
"maroon" "#800000"
|
||||
"mediumaquamarine" "#66cdaa"
|
||||
"mediumblue" "#0000cd"
|
||||
"mediumorchid" "#ba55d3"
|
||||
"mediumpurple" "#9370db"
|
||||
"mediumseagreen" "#3cb371"
|
||||
"mediumslateblue" "#7b68ee"
|
||||
"mediumspringgreen" "#00fa9a"
|
||||
"mediumturquoise" "#48d1cc"
|
||||
"mediumvioletred" "#c71585"
|
||||
"midnightblue" "#191970"
|
||||
"mintcream" "#f5fffa"
|
||||
"mistyrose" "#ffe4e1"
|
||||
"moccasin" "#ffe4b5"
|
||||
"navajowhite" "#ffdead"
|
||||
"navy" "#000080"
|
||||
"oldlace" "#fdf5e6"
|
||||
"olive" "#808000"
|
||||
"olivedrab" "#6b8e23"
|
||||
"orange" "#ffa500"
|
||||
"orangered" "#ff4500"
|
||||
"orchid" "#da70d6"
|
||||
"palegoldenrod" "#eee8aa"
|
||||
"palegreen" "#98fb98"
|
||||
"paleturquoise" "#afeeee"
|
||||
"palevioletred" "#db7093"
|
||||
"papayawhip" "#ffefd5"
|
||||
"peachpuff" "#ffdab9"
|
||||
"peru" "#cd853f"
|
||||
"pink" "#ffc0cb"
|
||||
"plum" "#dda0dd"
|
||||
"powderblue" "#b0e0e6"
|
||||
"purple" "#800080"
|
||||
"red" "#ff0000"
|
||||
"rosybrown" "#bc8f8f"
|
||||
"royalblue" "#4169e1"
|
||||
"saddlebrown" "#8b4513"
|
||||
"salmon" "#fa8072"
|
||||
"sandybrown" "#f4a460"
|
||||
"seagreen" "#2e8b57"
|
||||
"seashell" "#fff5ee"
|
||||
"sienna" "#a0522d"
|
||||
"silver" "#c0c0c0"
|
||||
"skyblue" "#87ceeb"
|
||||
"slateblue" "#6a5acd"
|
||||
"slategray" "#708090"
|
||||
"slategrey" "#708090"
|
||||
"snow" "#fffafa"
|
||||
"springgreen" "#00ff7f"
|
||||
"steelblue" "#4682b4"
|
||||
"tan" "#d2b48c"
|
||||
"teal" "#008080"
|
||||
"thistle" "#d8bfd8"
|
||||
"tomato" "#ff6347"
|
||||
"turquoise" "#40e0d0"
|
||||
"violet" "#ee82ee"
|
||||
"wheat" "#f5deb3"
|
||||
"white" "#ffffff"
|
||||
"whitesmoke" "#f5f5f5"
|
||||
"yellow" "#ffff00"
|
||||
"yellowgreen" "#9acd32"})
|
||||
|
||||
(def ^:private hex-color-re
|
||||
#"\#[0-9a-fA-F]{3,6}")
|
||||
|
||||
(def ^:private rgb-color-re
|
||||
#"(?:|rgb)\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\)")
|
||||
|
||||
(defn valid-hex-color?
|
||||
[color]
|
||||
(and (string? color)
|
||||
(some? (re-matches hex-color-re color))))
|
||||
|
||||
(defn parse-rgb
|
||||
[color]
|
||||
(let [result (re-matches rgb-color-re color)]
|
||||
(when (some? result)
|
||||
(let [r (parse-long (nth result 1))
|
||||
g (parse-long (nth result 2))
|
||||
b (parse-long (nth result 3))]
|
||||
(when (and (<= 0 r 255) (<= 0 g 255) (<= 0 b 255))
|
||||
[r g b])))))
|
||||
|
||||
(defn valid-rgb-color?
|
||||
[color]
|
||||
(if (string? color)
|
||||
(let [result (parse-rgb color)]
|
||||
(some? result))
|
||||
false))
|
||||
|
||||
(defn- normalize-hex
|
||||
[color]
|
||||
(if (= (count color) 4) ; of the form #RGB
|
||||
(-> color
|
||||
(str/replace #"\#(.)(.)(.)" "#$1$1$2$2$3$3")
|
||||
(str/lower))
|
||||
(str/lower color)))
|
||||
|
||||
(defn rgb->str
|
||||
[[r g b a]]
|
||||
(if (some? a)
|
||||
(str/ffmt "rgba(%,%,%,%)" r g b a)
|
||||
(str/ffmt "rgb(%,%,%)" r g b)))
|
||||
|
||||
(defn rgb->hsv
|
||||
[[red green blue]]
|
||||
(let [max (d/max red green blue)
|
||||
min (d/min red green blue)
|
||||
val max]
|
||||
(if (= min max)
|
||||
[0 0 val]
|
||||
(let [delta (- max min)
|
||||
sat (/ delta max)
|
||||
hue (if (= red max)
|
||||
(/ (- green blue) delta)
|
||||
(if (= green max)
|
||||
(+ 2 (/ (- blue red) delta))
|
||||
(+ 4 (/ (- red green) delta))))
|
||||
hue (* 60 hue)
|
||||
hue (if (< hue 0)
|
||||
(+ hue 360)
|
||||
hue)
|
||||
hue (if (> hue 360)
|
||||
(- hue 360)
|
||||
hue)]
|
||||
[hue sat val]))))
|
||||
|
||||
(defn hsv->rgb
|
||||
[[h s brightness]]
|
||||
(if (= s 0)
|
||||
[brightness brightness brightness]
|
||||
(let [sextant (int (mth/floor (/ h 60)))
|
||||
remainder (- (/ h 60) sextant)
|
||||
val1 (int (* brightness (- 1 s)))
|
||||
val2 (int (* brightness (- 1 (* s remainder))))
|
||||
val3 (int (* brightness (- 1 (* s (- 1 remainder)))))]
|
||||
(case sextant
|
||||
1 [val2 brightness val1]
|
||||
2 [val1 brightness val3]
|
||||
3 [val1 val2 brightness]
|
||||
4 [val3 val1 brightness]
|
||||
5 [brightness val1 val2]
|
||||
6 [brightness val3 val1]
|
||||
0 [brightness val3 val1]))))
|
||||
|
||||
(defn hex->rgb
|
||||
[color]
|
||||
(try
|
||||
(let [rgb #?(:clj (Integer/parseInt (subs color 1) 16)
|
||||
:cljs (js/parseInt (subs color 1) 16))
|
||||
r (bit-shift-right rgb 16)
|
||||
g (bit-and (bit-shift-right rgb 8) 255)
|
||||
b (bit-and rgb 255)]
|
||||
[r g b])
|
||||
(catch #?(:clj Throwable :cljs :default) _cause
|
||||
[0 0 0])))
|
||||
|
||||
(defn- int->hex
|
||||
"Convert integer to hex string"
|
||||
[v]
|
||||
#?(:clj (Integer/toHexString v)
|
||||
:cljs (.toString v 16)))
|
||||
|
||||
(defn rgb->hex
|
||||
[[r g b]]
|
||||
(let [r (int r)
|
||||
g (int g)
|
||||
b (int b)]
|
||||
(if (or (not= r (bit-and r 255))
|
||||
(not= g (bit-and g 255))
|
||||
(not= b (bit-and b 255)))
|
||||
(throw (ex-info "not valid rgb" {:r r :g g :b b}))
|
||||
(let [rgb (bit-or (bit-shift-left r 16)
|
||||
(bit-shift-left g 8) b)]
|
||||
(if (< r 16)
|
||||
(dm/str "#" (subs (int->hex (bit-or 0x1000000 rgb)) 1))
|
||||
(dm/str "#" (int->hex rgb)))))))
|
||||
|
||||
(defn rgb->hsl
|
||||
[[r g b]]
|
||||
(let [norm-r (/ r 255.0)
|
||||
norm-g (/ g 255.0)
|
||||
norm-b (/ b 255.0)
|
||||
max (d/max norm-r norm-g norm-b)
|
||||
min (d/min norm-r norm-g norm-b)
|
||||
l (/ (+ max min) 2.0)
|
||||
h (if (= max min) 0
|
||||
(if (= max norm-r)
|
||||
(* 60 (/ (- norm-g norm-b) (- max min)))
|
||||
(if (= max norm-g)
|
||||
(+ 120 (* 60 (/ (- norm-b norm-r) (- max min))))
|
||||
(+ 240 (* 60 (/ (- norm-r norm-g) (- max min)))))))
|
||||
s (if (and (> l 0) (<= l 0.5))
|
||||
(/ (- max min) (* 2 l))
|
||||
(/ (- max min) (- 2 (* 2 l))))]
|
||||
[(mod (+ h 360) 360) s l]))
|
||||
|
||||
(defn hex->hsv
|
||||
[v]
|
||||
(-> v hex->rgb rgb->hsv))
|
||||
|
||||
(defn hex->rgba
|
||||
[data opacity]
|
||||
(-> (hex->rgb data)
|
||||
(conj opacity)))
|
||||
|
||||
(defn hex->hsl [hex]
|
||||
(try
|
||||
(-> hex hex->rgb rgb->hsl)
|
||||
(catch #?(:clj Throwable :cljs :default) _e
|
||||
[0 0 0])))
|
||||
|
||||
(defn hex->hsla
|
||||
[data opacity]
|
||||
(-> (hex->hsl data)
|
||||
(conj opacity)))
|
||||
|
||||
#?(:cljs
|
||||
(defn format-hsla
|
||||
[[h s l a]]
|
||||
(let [precision 2
|
||||
rounded-s (* 100 (parse-double (d/format-precision s precision)))
|
||||
rounded-l (* 100 (parse-double (d/format-precision l precision)))]
|
||||
(str/concat "" h ", " rounded-s "%, " rounded-l "%, " a))))
|
||||
|
||||
(defn- hue->rgb
|
||||
"Helper for hsl->rgb"
|
||||
[v1 v2 vh]
|
||||
(let [vh (if (< vh 0)
|
||||
(+ vh 1)
|
||||
(if (> vh 1)
|
||||
(- vh 1)
|
||||
vh))]
|
||||
(cond
|
||||
(< (* 6 vh) 1) (+ v1 (* (- v2 v1) 6 vh))
|
||||
(< (* 2 vh) 1) v2
|
||||
(< (* 3 vh) 2) (+ v1 (* (- v2 v1) (- (/ 2 3) vh) 6))
|
||||
:else v1)))
|
||||
|
||||
(defn hsl->rgb
|
||||
[[h s l]]
|
||||
(if (= s 0)
|
||||
(let [o (* l 255)]
|
||||
[o o o])
|
||||
(let [norm-h (/ h 360.0)
|
||||
temp2 (if (< l 0.5)
|
||||
(* l (+ 1 s))
|
||||
(- (+ l s)
|
||||
(* s l)))
|
||||
temp1 (- (* l 2) temp2)]
|
||||
|
||||
[(mth/round (* 255 (hue->rgb temp1 temp2 (+ norm-h (/ 1 3)))))
|
||||
(mth/round (* 255 (hue->rgb temp1 temp2 norm-h)))
|
||||
(mth/round (* 255 (hue->rgb temp1 temp2 (- norm-h (/ 1 3)))))])))
|
||||
|
||||
(defn hsl->hex
|
||||
[v]
|
||||
(-> v hsl->rgb rgb->hex))
|
||||
|
||||
(defn hsl->hsv
|
||||
[hsl]
|
||||
(-> hsl hsl->rgb rgb->hsv))
|
||||
|
||||
(defn hsv->hex
|
||||
[hsv]
|
||||
(-> hsv hsv->rgb rgb->hex))
|
||||
|
||||
(defn hsv->hsl
|
||||
[hsv]
|
||||
(-> hsv hsv->hex hex->hsl))
|
||||
|
||||
(defn expand-hex
|
||||
[v]
|
||||
(cond
|
||||
(re-matches #"^[0-9A-Fa-f]$" v)
|
||||
(dm/str v v v v v v)
|
||||
|
||||
(re-matches #"^[0-9A-Fa-f]{2}$" v)
|
||||
(dm/str v v v)
|
||||
|
||||
(re-matches #"^[0-9A-Fa-f]{3}$" v)
|
||||
(let [a (nth v 0)
|
||||
b (nth v 1)
|
||||
c (nth v 2)]
|
||||
(dm/str a a b b c c))
|
||||
|
||||
:else
|
||||
v))
|
||||
|
||||
(defn prepend-hash
|
||||
[color]
|
||||
(if (= "#" (subs color 0 1))
|
||||
color
|
||||
(dm/str "#" color)))
|
||||
|
||||
(defn remove-hash
|
||||
[color]
|
||||
(if (str/starts-with? color "#")
|
||||
(subs color 1)
|
||||
color))
|
||||
|
||||
(defn color-string?
|
||||
[color]
|
||||
(and (string? color)
|
||||
(or (valid-hex-color? color)
|
||||
(valid-rgb-color? color)
|
||||
(contains? names color))))
|
||||
|
||||
(defn parse
|
||||
[color]
|
||||
(when (string? color)
|
||||
(if (valid-hex-color? color)
|
||||
(normalize-hex color)
|
||||
(or (some-> (parse-rgb color) (rgb->hex))
|
||||
(get names (str/lower color))))))
|
||||
|
||||
(def color-names
|
||||
(into [] (keys names)))
|
||||
|
||||
(def empty-color
|
||||
(into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity]))
|
||||
|
||||
(defn next-rgb
|
||||
"Given a color in rgb returns the next color"
|
||||
[[r g b]]
|
||||
(cond
|
||||
(and (= 255 r) (= 255 g) (= 255 b))
|
||||
(throw (ex-info "cannot get next color" {:r r :g g :b b}))
|
||||
|
||||
(and (= 255 g) (= 255 b))
|
||||
[(inc r) 0 0]
|
||||
|
||||
(= 255 b)
|
||||
[r (inc g) 0]
|
||||
|
||||
:else
|
||||
[r g (inc b)]))
|
||||
|
|
91
common/src/app/common/files/libraries_helpers.cljc
Normal file
91
common/src/app/common/files/libraries_helpers.cljc
Normal 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]))
|
110
common/src/app/common/files/shapes_helpers.cljc
Normal file
110
common/src/app/common/files/shapes_helpers.cljc
Normal 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]))))
|
||||
|
|
@ -7,8 +7,8 @@
|
|||
(ns app.common.geom.shapes.bool
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.path.bool :as pb]
|
||||
[app.common.path.shapes-to-path :as stp]))
|
||||
[app.common.svg.path.bool :as pb]
|
||||
[app.common.svg.path.shapes-to-path :as stp]))
|
||||
|
||||
(defn calc-bool-content
|
||||
[shape objects]
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes.common :as gco]
|
||||
[app.common.math :as mth]
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.path.subpaths :as sp]))
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.subpath :as sp]))
|
||||
|
||||
(def ^:const curve-curve-precision 0.1)
|
||||
(def ^:const curve-range-precision 2)
|
||||
|
|
|
@ -133,9 +133,10 @@
|
|||
(defn ceil
|
||||
"Returns the smallest integer greater than
|
||||
or equal to a given number."
|
||||
^double
|
||||
[v]
|
||||
#?(:cljs (js/Math.ceil v)
|
||||
:clj (Math/ceil v)))
|
||||
:clj (Math/ceil ^double v)))
|
||||
|
||||
(defn precision
|
||||
[v n]
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
(ns app.common.svg
|
||||
(:require
|
||||
#?(:cljs ["./svg_optimizer.js" :as svgo])
|
||||
#?(:clj [clojure.xml :as xml]
|
||||
:cljs [tubax.core :as tubax])
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
|
@ -14,7 +16,15 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
[cuerdas.core :as str])
|
||||
#?(:clj
|
||||
(:import
|
||||
javax.xml.XMLConstants
|
||||
java.io.InputStream
|
||||
javax.xml.parsers.SAXParserFactory
|
||||
clojure.lang.XMLHandler
|
||||
org.apache.commons.io.IOUtils)))
|
||||
|
||||
|
||||
;; Regex for XML ids per Spec
|
||||
;; https://www.w3.org/TR/2008/REC-xml-20081126/#sec-common-syn
|
||||
|
@ -1043,3 +1053,26 @@
|
|||
([input] (optimize input nil))
|
||||
([input options]
|
||||
(svgo/optimize input (clj->js options)))))
|
||||
|
||||
#?(:clj
|
||||
(defn- secure-parser-factory
|
||||
[^InputStream input ^XMLHandler handler]
|
||||
(.. (doto (SAXParserFactory/newInstance)
|
||||
(.setFeature XMLConstants/FEATURE_SECURE_PROCESSING true)
|
||||
(.setFeature "http://apache.org/xml/features/disallow-doctype-decl" true))
|
||||
(newSAXParser)
|
||||
(parse input handler))))
|
||||
|
||||
(defn strip-doctype
|
||||
[data]
|
||||
(cond-> data
|
||||
(str/includes? data "<!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)))))
|
||||
|
|
|
@ -4,55 +4,80 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.util.path.parser
|
||||
(ns app.common.svg.path
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.path :as upg]
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.math :as mth]
|
||||
[app.common.svg :as csvg]
|
||||
[app.util.path.arc-to-curve :refer [a2c]]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[cuerdas.core :as str]))
|
||||
;;
|
||||
|
||||
(def commands-regex #"(?i)[mzlhvcsqta][^mzlhvcsqta]*")
|
||||
(def regex #"[+-]?(\d+(\.\d+)?|\.\d+)(e[+-]?\d+)?")
|
||||
|
||||
;; Matches numbers for path values allows values like... -.01, 10, +12.22
|
||||
;; 0 and 1 are special because can refer to flags
|
||||
(def num-regex #"[+-]?(\d+(\.\d+)?|\.\d+)(e[+-]?\d+)?")
|
||||
|
||||
(def flag-regex #"[01]")
|
||||
|
||||
(defn extract-params [cmd-str extract-commands]
|
||||
(loop [result []
|
||||
extract-idx 0
|
||||
(defn extract-params
|
||||
[data pattern]
|
||||
(loop [result []
|
||||
ptt-idx 0
|
||||
current {}
|
||||
remain (-> cmd-str (subs 1) (str/trim))]
|
||||
entries (re-seq regex data)
|
||||
match (ffirst entries)]
|
||||
|
||||
(let [[param type] (nth extract-commands extract-idx)
|
||||
regex (case type
|
||||
:flag flag-regex
|
||||
#_:number num-regex)
|
||||
match (re-find regex remain)]
|
||||
(if match
|
||||
(let [[attr-name attr-type] (nth pattern ptt-idx)
|
||||
ptt-idx (inc ptt-idx)
|
||||
ptt-cnt (count pattern)
|
||||
|
||||
(if match
|
||||
(let [value (-> match first csvg/fix-dot-number d/read-string)
|
||||
remain (str/replace-first remain regex "")
|
||||
current (assoc current param value)
|
||||
extract-idx (inc extract-idx)
|
||||
[result current extract-idx]
|
||||
(if (>= extract-idx (count extract-commands))
|
||||
[(conj result current) {} 0]
|
||||
[result current extract-idx])]
|
||||
(recur result
|
||||
extract-idx
|
||||
current
|
||||
remain))
|
||||
(cond-> result
|
||||
(seq current) (conj current))))))
|
||||
value (if (= attr-type :flag)
|
||||
(if (= 1 (count match))
|
||||
(d/parse-integer match)
|
||||
(d/parse-integer (subs match 0 1)))
|
||||
(-> match csvg/fix-dot-number d/parse-double))
|
||||
|
||||
current (assoc current attr-name value)
|
||||
|
||||
result (if (>= ptt-idx ptt-cnt)
|
||||
(conj result current)
|
||||
result)
|
||||
|
||||
current (if (>= ptt-idx ptt-cnt)
|
||||
{}
|
||||
current)
|
||||
|
||||
match (if (and (= attr-type :flag)
|
||||
(> (count match) 1))
|
||||
(subs match 1)
|
||||
nil)
|
||||
|
||||
entries (if match
|
||||
entries
|
||||
(rest entries))
|
||||
|
||||
match (if match
|
||||
match
|
||||
(ffirst entries))
|
||||
|
||||
ptt-idx (if (>= ptt-idx ptt-cnt)
|
||||
0
|
||||
ptt-idx)]
|
||||
|
||||
(recur result
|
||||
ptt-idx
|
||||
current
|
||||
entries
|
||||
match))
|
||||
|
||||
(if (seq current)
|
||||
(conj result current)
|
||||
result))))
|
||||
|
||||
;; Path specification
|
||||
;; https://www.w3.org/TR/SVG11/paths.html
|
||||
(defmulti parse-command (comp str/upper first))
|
||||
(defmulti parse-command
|
||||
(fn [cmd]
|
||||
(str/upper (subs cmd 0 1))))
|
||||
|
||||
(defmethod parse-command "M" [cmd]
|
||||
(let [relative (str/starts-with? cmd "m")
|
||||
|
@ -125,8 +150,8 @@
|
|||
(let [relative (str/starts-with? cmd "q")
|
||||
param-list (extract-params cmd [[:cx :number]
|
||||
[:cy :number]
|
||||
[:x :number]
|
||||
[:y :number]])]
|
||||
[:x :number]
|
||||
[:y :number]])]
|
||||
(for [params param-list]
|
||||
{:command :quadratic-bezier-curve-to
|
||||
:relative relative
|
||||
|
@ -142,7 +167,7 @@
|
|||
:params params})))
|
||||
|
||||
(defmethod parse-command "A" [cmd]
|
||||
(let [relative (str/starts-with? cmd "a")
|
||||
(let [relative (str/starts-with? cmd "a")
|
||||
param-list (extract-params cmd [[:rx :number]
|
||||
[:ry :number]
|
||||
[:x-axis-rotation :number]
|
||||
|
@ -178,7 +203,120 @@
|
|||
:c2x (:x cp2)
|
||||
:c2y (:y cp2)}))
|
||||
|
||||
(defn arc->beziers [from-p command]
|
||||
(defn- unit-vector-angle
|
||||
[ux uy vx vy]
|
||||
(let [sign (if (> 0 (- (* ux vy) (* uy vx))) -1.0 1.0)
|
||||
dot (+ (* ux vx) (* uy vy))
|
||||
dot (cond
|
||||
(> dot 1.0) 1.0
|
||||
(< dot -1.0) -1.0
|
||||
:else dot)]
|
||||
(* sign (mth/acos dot))))
|
||||
|
||||
(defn- get-arc-center [x1 y1 x2 y2 fa fs rx ry sin-phi cos-phi]
|
||||
(let [x1p (+ (* cos-phi (/ (- x1 x2) 2)) (* sin-phi (/ (- y1 y2) 2)))
|
||||
y1p (+ (* (- sin-phi) (/ (- x1 x2) 2)) (* cos-phi (/ (- y1 y2) 2)))
|
||||
rx-sq (* rx rx)
|
||||
ry-sq (* ry ry)
|
||||
x1p-sq (* x1p x1p)
|
||||
y1p-sq (* y1p y1p)
|
||||
radicant (- (* rx-sq ry-sq)
|
||||
(* rx-sq y1p-sq)
|
||||
(* ry-sq x1p-sq))
|
||||
radicant (if (< radicant 0) 0 radicant)
|
||||
radicant (/ radicant (+ (* rx-sq y1p-sq) (* ry-sq x1p-sq)))
|
||||
radicant (* (mth/sqrt radicant) (if (= fa fs) -1 1))
|
||||
|
||||
cxp (* radicant (/ rx ry) y1p)
|
||||
cyp (* radicant (/ (- ry) rx) x1p)
|
||||
cx (+ (- (* cos-phi cxp)
|
||||
(* sin-phi cyp))
|
||||
(/ (+ x1 x2) 2))
|
||||
cy (+ (* sin-phi cxp)
|
||||
(* cos-phi cyp)
|
||||
(/ (+ y1 y2) 2))
|
||||
|
||||
v1x (/ (- x1p cxp) rx)
|
||||
v1y (/ (- y1p cyp) ry)
|
||||
v2x (/ (- (- x1p) cxp) rx)
|
||||
v2y (/ (- (- y1p) cyp) ry)
|
||||
theta1 (unit-vector-angle 1 0 v1x v1y)
|
||||
|
||||
dtheta (unit-vector-angle v1x v1y v2x v2y)
|
||||
dtheta (if (and (= fs 0) (> dtheta 0)) (- dtheta (* mth/PI 2)) dtheta)
|
||||
dtheta (if (and (= fs 1) (< dtheta 0)) (+ dtheta (* mth/PI 2)) dtheta)]
|
||||
|
||||
[cx cy theta1 dtheta]))
|
||||
|
||||
(defn approximate-unit-arc
|
||||
[theta1 dtheta]
|
||||
(let [alpha (* (/ 4 3) (mth/tan (/ dtheta 4)))
|
||||
x1 (mth/cos theta1)
|
||||
y1 (mth/sin theta1)
|
||||
x2 (mth/cos (+ theta1 dtheta))
|
||||
y2 (mth/sin (+ theta1 dtheta))]
|
||||
[x1 y1 (- x1 (* y1 alpha)) (+ y1 (* x1 alpha)) (+ x2 (* y2 alpha)) (- y2 (* x2 alpha)) x2 y2]))
|
||||
|
||||
(defn- process-curve
|
||||
[curve cc rx ry sin-phi cos-phi]
|
||||
(reduce (fn [curve i]
|
||||
(let [x (nth curve i)
|
||||
y (nth curve (inc i))
|
||||
x (* x rx)
|
||||
y (* y ry)
|
||||
xp (- (* cos-phi x) (* sin-phi y))
|
||||
yp (+ (* sin-phi x) (* cos-phi y))]
|
||||
(-> curve
|
||||
(assoc i (+ xp (nth cc 0)))
|
||||
(assoc (inc i) (+ yp (nth cc 1))))))
|
||||
curve
|
||||
(range 0 (count curve) 2)))
|
||||
|
||||
(defn arc->beziers*
|
||||
[x1 y1 x2 y2 fa fs rx ry phi]
|
||||
(let [tau (* mth/PI 2)
|
||||
phi-tau (/ (* phi tau) 360)
|
||||
|
||||
sin-phi (mth/sin phi-tau)
|
||||
cos-phi (mth/cos phi-tau)
|
||||
|
||||
x1p (+ (/ (* cos-phi (- x1 x2)) 2)
|
||||
(/ (* sin-phi (- y1 y2)) 2))
|
||||
y1p (+ (/ (* (- sin-phi) (- x1 x2)) 2)
|
||||
(/ (* cos-phi (- y1 y2)) 2))]
|
||||
|
||||
(if (or (zero? x1p)
|
||||
(zero? y1p)
|
||||
(zero? rx)
|
||||
(zero? ry))
|
||||
[]
|
||||
(let [
|
||||
rx (mth/abs rx)
|
||||
ry (mth/abs ry)
|
||||
lambda (+ (/ (* x1p x1p) (* rx rx))
|
||||
(/ (* y1p y1p) (* ry ry)))
|
||||
rx (if (> lambda 1) (* rx (mth/sqrt lambda)) rx)
|
||||
ry (if (> lambda 1) (* ry (mth/sqrt lambda)) ry)
|
||||
|
||||
cc (get-arc-center x1 y1 x2 y2 fa fs rx ry sin-phi cos-phi)
|
||||
theta1 (nth cc 2)
|
||||
dtheta (nth cc 3)
|
||||
segments (mth/max (mth/ceil (/ (mth/abs dtheta) (/ tau 4))) 1)
|
||||
dtheta (/ dtheta segments)]
|
||||
|
||||
(loop [i 0.0
|
||||
t (double theta1)
|
||||
r []]
|
||||
(if (< i segments)
|
||||
(let [curve (approximate-unit-arc t dtheta)
|
||||
curve (process-curve curve cc rx ry sin-phi cos-phi)]
|
||||
(recur (inc i)
|
||||
(+ t dtheta)
|
||||
(conj r curve)))
|
||||
r))))))
|
||||
|
||||
(defn arc->beziers
|
||||
[from-p {:keys [params] :as command}]
|
||||
(let [to-command
|
||||
(fn [[_ _ c1x c1y c2x c2y x y]]
|
||||
{:command :curve-to
|
||||
|
@ -188,8 +326,16 @@
|
|||
:x x :y y}})
|
||||
|
||||
{from-x :x from-y :y} from-p
|
||||
{:keys [rx ry x-axis-rotation large-arc-flag sweep-flag x y]} (:params command)
|
||||
result (a2c from-x from-y x y large-arc-flag sweep-flag rx ry x-axis-rotation)]
|
||||
|
||||
x (get params :x 0.0)
|
||||
y (get params :y 0.0)
|
||||
rx (get params :rx 0.0)
|
||||
ry (get params :ry 0.0)
|
||||
x-axis-rotation (get params :x-axis-rotation 0)
|
||||
large-arc-flag (get params :large-arc-flag 0)
|
||||
sweep-flag (get params :sweep-flag 0)
|
||||
result (arc->beziers* from-x from-y x y large-arc-flag sweep-flag rx ry x-axis-rotation)]
|
||||
|
||||
(mapv to-command result)))
|
||||
|
||||
(defn simplify-commands
|
||||
|
@ -202,7 +348,6 @@
|
|||
;; prev-qc : previous command control point for quadratic curves
|
||||
(fn [[result prev-pos prev-start prev-cc prev-qc] [command _prev]]
|
||||
(let [command (assoc command :prev-pos prev-pos)
|
||||
|
||||
command
|
||||
(cond-> command
|
||||
(:relative command)
|
||||
|
@ -288,6 +433,7 @@
|
|||
|
||||
next-start (if (= :move-to (:command command)) next-pos prev-start)]
|
||||
|
||||
|
||||
[result next-pos next-start next-cc next-qc]))
|
||||
|
||||
start (first commands)
|
||||
|
@ -301,17 +447,10 @@
|
|||
(reduce simplify-command [[start] start-pos start-pos start-pos start-pos])
|
||||
(first))))
|
||||
|
||||
(defn parse-path [path-str]
|
||||
(defn parse
|
||||
[path-str]
|
||||
(if (empty? path-str)
|
||||
path-str
|
||||
(let [clean-path-str
|
||||
(-> path-str
|
||||
(str/trim)
|
||||
;; Change "commas" for spaces
|
||||
(str/replace #"," " ")
|
||||
;; Remove all consecutive spaces
|
||||
(str/replace #"\s+" " "))
|
||||
commands (re-seq commands-regex clean-path-str)]
|
||||
(let [commands (re-seq commands-regex path-str)]
|
||||
(-> (mapcat parse-command commands)
|
||||
(simplify-commands)))))
|
||||
|
|
@ -4,14 +4,14 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.path.bool
|
||||
(ns app.common.svg.path.bool
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.path.subpaths :as ups]))
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.subpath :as ups]))
|
||||
|
||||
(defn add-previous
|
||||
([content]
|
|
@ -4,7 +4,7 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.path.commands
|
||||
(ns app.common.svg.path.command
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]))
|
|
@ -4,7 +4,7 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.path.shapes-to-path
|
||||
(ns app.common.svg.path.shapes-to-path
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
|
@ -13,8 +13,8 @@
|
|||
[app.common.geom.shapes.common :as gco]
|
||||
[app.common.geom.shapes.corners :as gso]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.path.bool :as pb]
|
||||
[app.common.path.commands :as pc]
|
||||
[app.common.svg.path.bool :as pb]
|
||||
[app.common.svg.path.command :as pc]
|
||||
[app.common.types.shape.radius :as ctsr]))
|
||||
|
||||
(def ^:const bezier-circle-c 0.551915024494)
|
|
@ -4,11 +4,11 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.path.subpaths
|
||||
(ns app.common.svg.path.subpath
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.path.commands :as upc]))
|
||||
[app.common.svg.path.command :as upc]))
|
||||
|
||||
(defn pt=
|
||||
"Check if two points are close"
|
534
common/src/app/common/svg/shapes_builder.cljc
Normal file
534
common/src/app/common/svg/shapes_builder.cljc
Normal 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 %)))]))))))
|
208
common/test/common_tests/arc_to_bezier.js
Normal file
208
common/test/common_tests/arc_to_bezier.js
Normal 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;
|
||||
});
|
99
common/test/common_tests/colors_test.cljc
Normal file
99
common/test/common_tests/colors_test.cljc
Normal 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")))
|
||||
|
||||
|
||||
)
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
(ns common-tests.helpers.files
|
||||
(:require
|
||||
[app.common.files.features :as ffeat]
|
||||
[app.common.features :as ffeat]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.types.colors-list :as ctcl]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
(ns common-tests.pages-test
|
||||
(:require
|
||||
[app.common.files.features :as ffeat]
|
||||
[app.common.features :as ffeat]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.shape :as cts]
|
||||
|
|
111
common/test/common_tests/svg_path_test.cljc
Normal file
111
common/test/common_tests/svg_path_test.cljc
Normal 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")
|
||||
|
38
common/test/common_tests/svg_test.cljc
Normal file
38
common/test/common_tests/svg_test.cljc
Normal 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
4
common/vendor/beicon/impl/rxjs.cljs
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
(ns beicon.impl.rxjs
|
||||
(:require ["rxjs" :as rx]))
|
||||
|
||||
(goog/exportSymbol "rxjsMain" rx)
|
4
common/vendor/beicon/impl/rxjs_operators.cljs
vendored
Normal file
4
common/vendor/beicon/impl/rxjs_operators.cljs
vendored
Normal 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
4
common/vendor/tubax/saxjs.cljs
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
(ns tubax.saxjs
|
||||
(:require ["sax" :as sax]))
|
||||
|
||||
(goog/exportSymbol "sax" sax)
|
|
@ -515,6 +515,11 @@ safer-buffer@^2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
sax@^1.2.4:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
|
||||
integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
|
||||
|
||||
setimmediate@^1.0.4:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
funcool/beicon {:mvn/version "2021.07.05-1"}
|
||||
funcool/okulary {:mvn/version "2022.04.11-16"}
|
||||
funcool/potok {:mvn/version "2022.12.16-71"}
|
||||
funcool/tubax {:mvn/version "2021.05.20-0"}
|
||||
|
||||
funcool/rumext
|
||||
{:git/tag "v2.7"
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.files.features :as ffeat]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.files.libraries-helpers :as cflh]
|
||||
[app.common.files.shapes-helpers :as cfsh]
|
||||
[app.common.geom.align :as gal]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.proportions :as gpp]
|
||||
|
@ -55,7 +57,6 @@
|
|||
[app.main.data.workspace.layers :as dwly]
|
||||
[app.main.data.workspace.layout :as layout]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.libraries-helpers :as dwlh]
|
||||
[app.main.data.workspace.media :as dwm]
|
||||
[app.main.data.workspace.notifications :as dwn]
|
||||
[app.main.data.workspace.path :as dwdp]
|
||||
|
@ -2077,14 +2078,14 @@
|
|||
page
|
||||
(cons shape children))
|
||||
|
||||
[_ _ changes2] (dwlh/generate-add-component it
|
||||
[_ _ changes2] (cflh/generate-add-component it
|
||||
[shape]
|
||||
(:objects page')
|
||||
(:id page)
|
||||
(:id file-data)
|
||||
true
|
||||
nil
|
||||
dwsh/prepare-create-artboard-from-selection)
|
||||
cfsh/prepare-create-artboard-from-selection)
|
||||
|
||||
changes (pcb/concat-changes changes1 changes2)]
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.path.shapes-to-path :as stp]
|
||||
[app.common.svg.path.shapes-to-path :as stp]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
(ns app.main.data.workspace.colors
|
||||
(:require
|
||||
[app.common.colors :as colors]
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.pages.helpers :as cph]
|
||||
|
@ -355,7 +355,7 @@
|
|||
(assoc-in [:workspace-global :picking-color?] true)
|
||||
(assoc ::md/modal {:id (random-uuid)
|
||||
:type :colorpicker
|
||||
:props {:data {:color colors/black
|
||||
:props {:data {:color cc/black
|
||||
:opacity 1}
|
||||
:disable-opacity false
|
||||
:disable-gradient false
|
||||
|
@ -438,9 +438,9 @@
|
|||
|
||||
(defn split-color-components
|
||||
[{:keys [color opacity] :as data}]
|
||||
(let [value (if (uc/hex? color) color colors/black)
|
||||
[r g b] (uc/hex->rgb value)
|
||||
[h s v] (uc/hex->hsv value)]
|
||||
(let [value (if (cc/valid-hex-color? color) color cc/black)
|
||||
[r g b] (cc/hex->rgb value)
|
||||
[h s v] (cc/hex->hsv value)]
|
||||
(merge data
|
||||
{:hex (or value "000000")
|
||||
:alpha (or opacity 1)
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.features :as ffeat]
|
||||
[app.common.files.libraries-helpers :as cflh]
|
||||
[app.common.files.shapes-helpers :as cfsh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logging :as log]
|
||||
[app.common.pages :as cp]
|
||||
|
@ -305,9 +307,9 @@
|
|||
parents (into #{} (map :parent-id) shapes)]
|
||||
(when-not (empty? shapes)
|
||||
(let [[root _ changes]
|
||||
(dwlh/generate-add-component it shapes objects page-id file-id components-v2
|
||||
(cflh/generate-add-component it shapes objects page-id file-id components-v2
|
||||
dwg/prepare-create-group
|
||||
dwsh/prepare-create-artboard-from-selection)]
|
||||
cfsh/prepare-create-artboard-from-selection)]
|
||||
(when-not (empty? (:redo-changes changes))
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(dws/select-shapes (d/ordered-set (:id root)))
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.typography :as cty]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[cljs.spec.alpha :as s]
|
||||
[clojure.set :as set]))
|
||||
|
@ -63,75 +62,6 @@
|
|||
|
||||
;; ---- Components and instances creation ----
|
||||
|
||||
(defn generate-add-component-changes
|
||||
[changes root objects file-id page-id components-v2]
|
||||
(let [name (:name root)
|
||||
[path name] (cph/parse-path-name name)
|
||||
|
||||
[root-shape new-shapes updated-shapes]
|
||||
(if-not components-v2
|
||||
(ctn/make-component-shape root objects file-id components-v2)
|
||||
(ctn/convert-shape-in-component root objects file-id))
|
||||
|
||||
changes (-> changes
|
||||
(pcb/add-component (:id root-shape)
|
||||
path
|
||||
name
|
||||
new-shapes
|
||||
updated-shapes
|
||||
(:id root)
|
||||
page-id))]
|
||||
[root-shape changes]))
|
||||
|
||||
(defn generate-add-component
|
||||
"If there is exactly one id, and it's a frame (or a group in v1), and not already a component,
|
||||
use it as root. Otherwise, create a frame (v2) or group (v1) that contains all ids. Then, make a
|
||||
component with it, and link all shapes to their corresponding one in the component."
|
||||
[it shapes objects page-id file-id components-v2 prepare-create-group prepare-create-board]
|
||||
(let [changes (pcb/empty-changes it page-id)
|
||||
|
||||
[root changes old-root-ids]
|
||||
(if (and (= (count shapes) 1)
|
||||
(or (and (= (:type (first shapes)) :group) (not components-v2))
|
||||
(= (:type (first shapes)) :frame))
|
||||
(not (ctk/instance-head? (first shapes))))
|
||||
|
||||
[(first shapes)
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects))
|
||||
(:shapes (first shapes))]
|
||||
|
||||
(let [root-name (if (= 1 (count shapes))
|
||||
(:name (first shapes))
|
||||
"Component 1")
|
||||
|
||||
[root changes] (if-not components-v2
|
||||
(prepare-create-group it ; These functions needs to be passed as argument
|
||||
objects ; to avoid a circular dependence
|
||||
page-id
|
||||
shapes
|
||||
root-name
|
||||
(not (ctk/instance-head? (first shapes))))
|
||||
(prepare-create-board changes
|
||||
(uuid/next)
|
||||
(:parent-id (first shapes))
|
||||
objects
|
||||
(map :id shapes)
|
||||
nil
|
||||
root-name
|
||||
true))]
|
||||
|
||||
[root changes (map :id shapes)]))
|
||||
|
||||
[root-shape changes] (generate-add-component-changes changes root objects file-id page-id components-v2)
|
||||
|
||||
changes (pcb/update-shapes changes
|
||||
old-root-ids
|
||||
#(dissoc % :component-root)
|
||||
[:component-root])]
|
||||
|
||||
[root (:id root-shape) changes]))
|
||||
|
||||
(defn duplicate-component
|
||||
"Clone the root shape of the component and all children. Generate new
|
||||
ids from all of them."
|
||||
|
@ -141,7 +71,10 @@
|
|||
|
||||
(let [main-instance-page (ctf/get-component-page library-data component)
|
||||
main-instance-shape (ctf/get-component-root library-data component)
|
||||
position (gpt/point (+ (:x main-instance-shape) (:width main-instance-shape) 50) (:y main-instance-shape))
|
||||
position (gpt/point (+ (:x main-instance-shape)
|
||||
(:width main-instance-shape)
|
||||
50)
|
||||
(:y main-instance-shape))
|
||||
options (if components-v2 {:main-instance? true} {})
|
||||
|
||||
[new-instance-shape new-instance-shapes]
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.svg :refer [optimize]]
|
||||
[app.common.svg.shapes-builder :as csvg.shapes-builder]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
|
@ -274,7 +275,7 @@
|
|||
process-svg
|
||||
(fn [svg-data]
|
||||
(let [[shape children]
|
||||
(svg/create-svg-shapes svg-data pos objects uuid/zero nil #{} false)]
|
||||
(csvg.shapes-builder/create-svg-shapes svg-data pos objects uuid/zero nil #{} false)]
|
||||
[shape children]))]
|
||||
|
||||
(->> (upload-images svg-data)
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.flex-layout :as gsl]
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.path.shapes-to-path :as upsp]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.shapes-to-path :as upsp]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.path :as upg]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.path.shapes-to-path :as upsp]
|
||||
[app.common.path.subpaths :as ups]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.shapes-to-path :as upsp]
|
||||
[app.common.svg.path.subpath :as ups]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.path.subpaths :as ups]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.subpath :as ups]
|
||||
[app.main.data.workspace.path.common :as common]
|
||||
[app.main.streams :as ms]
|
||||
[potok.core :as ptk]))
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.path.shapes-to-path :as upsp]
|
||||
[app.common.svg.path.shapes-to-path :as upsp]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
(ns app.main.data.workspace.path.state
|
||||
(:require
|
||||
[app.common.path.shapes-to-path :as upsp]))
|
||||
[app.common.svg.path.shapes-to-path :as upsp]))
|
||||
|
||||
(defn get-path-id
|
||||
"Retrieves the currently editing path id"
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
(ns app.main.data.workspace.path.tools
|
||||
(:require
|
||||
[app.common.path.shapes-to-path :as upsp]
|
||||
[app.common.path.subpaths :as ups]
|
||||
[app.common.svg.path.shapes-to-path :as upsp]
|
||||
[app.common.svg.path.subpath :as ups]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.path.changes :as changes]
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.files.libraries-helpers :as cflh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
|
@ -408,6 +409,7 @@
|
|||
(instantiate-component))]
|
||||
changes))
|
||||
|
||||
;; TODO: move to common.files.shape-helpers
|
||||
(defn- prepare-duplicate-shape-change
|
||||
([changes objects page unames update-unames! ids-map obj delta libraries library-data it file-id]
|
||||
(prepare-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta libraries library-data it file-id (:frame-id obj) (:parent-id obj) false false))
|
||||
|
@ -437,7 +439,7 @@
|
|||
regenerate-component
|
||||
(fn [changes shape]
|
||||
(let [components-v2 (dm/get-in library-data [:options :components-v2])
|
||||
[_ changes] (dwlh/generate-add-component-changes changes shape objects file-id (:id page) components-v2)]
|
||||
[_ changes] (cflh/generate-add-component-changes changes shape objects file-id (:id page) components-v2)]
|
||||
changes))
|
||||
|
||||
new-obj
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.files.shapes-helpers :as cfsh]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.schema :as sm]
|
||||
|
@ -17,8 +17,6 @@
|
|||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.comments :as dc]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
|
@ -32,28 +30,6 @@
|
|||
(def valid-shape-map?
|
||||
(sm/pred-fn ::cts/shape))
|
||||
|
||||
(defn prepare-add-shape
|
||||
[changes shape objects _selected]
|
||||
(let [index (:index (meta shape))
|
||||
id (:id shape)
|
||||
|
||||
mod? (:mod? (meta shape))
|
||||
[row column :as cell] (when-not mod? (:cell (meta shape)))
|
||||
|
||||
changes (-> changes
|
||||
(pcb/with-objects objects)
|
||||
(cond-> (some? index)
|
||||
(pcb/add-object shape {:index index}))
|
||||
(cond-> (nil? index)
|
||||
(pcb/add-object shape))
|
||||
(cond-> (some? (:parent-id shape))
|
||||
(pcb/change-parent (:parent-id shape) [shape] index))
|
||||
(cond-> (some? cell)
|
||||
(pcb/update-shapes [(:parent-id shape)] #(ctl/push-into-cell % [id] row column)))
|
||||
(cond-> (ctl/grid-layout? objects (:parent-id shape))
|
||||
(pcb/update-shapes [(:parent-id shape)] ctl/assign-cells)))]
|
||||
[shape changes]))
|
||||
|
||||
(defn add-shape
|
||||
([shape]
|
||||
(add-shape shape {}))
|
||||
|
@ -73,7 +49,7 @@
|
|||
[shape changes]
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
(prepare-add-shape shape objects selected))
|
||||
(cfsh/prepare-add-shape shape objects selected))
|
||||
|
||||
changes (cond-> changes
|
||||
(cph/text-shape? shape)
|
||||
|
@ -93,23 +69,6 @@
|
|||
(->> (rx/of (dwe/start-edition-mode (:id shape)))
|
||||
(rx/observe-on :async)))))))))
|
||||
|
||||
(defn prepare-move-shapes-into-frame
|
||||
[changes frame-id shapes objects]
|
||||
(let [ordered-indexes (cph/order-by-indexed-shapes objects shapes)
|
||||
parent-id (get-in objects [frame-id :parent-id])
|
||||
ordered-indexes (->> ordered-indexes (remove #(= % parent-id)))
|
||||
to-move-shapes (map (d/getf objects) ordered-indexes)]
|
||||
(if (d/not-empty? to-move-shapes)
|
||||
(-> changes
|
||||
(cond-> (not (ctl/any-layout? objects frame-id))
|
||||
(pcb/update-shapes ordered-indexes ctl/remove-layout-item-data))
|
||||
(pcb/update-shapes ordered-indexes #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true)))
|
||||
(pcb/change-parent frame-id to-move-shapes 0)
|
||||
(cond-> (ctl/grid-layout? objects frame-id)
|
||||
(pcb/update-shapes [frame-id] ctl/assign-cells))
|
||||
(pcb/reorder-grid-children [frame-id]))
|
||||
changes)))
|
||||
|
||||
(defn move-shapes-into-frame
|
||||
[frame-id shapes]
|
||||
(ptk/reify ::move-shapes-into-frame
|
||||
|
@ -120,10 +79,10 @@
|
|||
shapes (->> shapes (remove #(dm/get-in objects [% :blocked])))
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects))
|
||||
changes (prepare-move-shapes-into-frame changes
|
||||
frame-id
|
||||
shapes
|
||||
objects)]
|
||||
changes (cfsh/prepare-move-shapes-into-frame changes
|
||||
frame-id
|
||||
shapes
|
||||
objects)]
|
||||
(if (some? changes)
|
||||
(rx/of (dch/commit-changes changes))
|
||||
(rx/empty))))))
|
||||
|
@ -366,58 +325,6 @@
|
|||
;; Artboard
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
;; FIXME: looks
|
||||
(defn prepare-create-artboard-from-selection
|
||||
[changes id parent-id objects selected index frame-name without-fill?]
|
||||
(let [selected-objs (map #(get objects %) selected)
|
||||
new-index (or index
|
||||
(cph/get-index-replacement selected objects))]
|
||||
(when (d/not-empty? selected)
|
||||
(let [srect (gsh/shapes->rect selected-objs)
|
||||
selected-id (first selected)
|
||||
|
||||
frame-id (dm/get-in objects [selected-id :frame-id])
|
||||
parent-id (or parent-id (dm/get-in objects [selected-id :parent-id]))
|
||||
|
||||
attrs {:type :frame
|
||||
:x (:x srect)
|
||||
:y (:y srect)
|
||||
:width (:width srect)
|
||||
:height (:height srect)}
|
||||
|
||||
shape (cts/setup-shape
|
||||
(cond-> attrs
|
||||
(some? id)
|
||||
(assoc :id id)
|
||||
|
||||
(some? frame-name)
|
||||
(assoc :name frame-name)
|
||||
|
||||
:always
|
||||
(assoc :frame-id frame-id
|
||||
:parent-id parent-id)
|
||||
|
||||
:always
|
||||
(with-meta {:index new-index})
|
||||
|
||||
(or (not= frame-id uuid/zero) without-fill?)
|
||||
(assoc :fills [] :hide-in-viewer true)))
|
||||
|
||||
[shape changes]
|
||||
(prepare-add-shape changes shape objects selected)
|
||||
|
||||
changes
|
||||
(prepare-move-shapes-into-frame changes (:id shape) selected objects)
|
||||
|
||||
changes
|
||||
(cond-> changes
|
||||
(ctl/grid-layout? objects (:parent-id shape))
|
||||
(-> (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells)
|
||||
(pcb/reorder-grid-children [(:parent-id shape)])))]
|
||||
|
||||
[shape changes]))))
|
||||
|
||||
(defn create-artboard-from-selection
|
||||
([]
|
||||
(create-artboard-from-selection nil))
|
||||
|
@ -438,14 +345,14 @@
|
|||
(pcb/with-objects objects))
|
||||
|
||||
[frame-shape changes]
|
||||
(prepare-create-artboard-from-selection changes
|
||||
id
|
||||
parent-id
|
||||
objects
|
||||
selected
|
||||
index
|
||||
nil
|
||||
false)
|
||||
(cfsh/prepare-create-artboard-from-selection changes
|
||||
id
|
||||
parent-id
|
||||
objects
|
||||
selected
|
||||
index
|
||||
nil
|
||||
false)
|
||||
|
||||
undo-id (js/Symbol)]
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(defn lookup-page
|
||||
|
|
|
@ -6,467 +6,23 @@
|
|||
|
||||
(ns app.main.data.workspace.svg-upload
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.common :as gsc]
|
||||
[app.common.geom.shapes.transforms :as gst]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.schema :as sm :refer [max-safe-int min-safe-int]]
|
||||
[app.common.svg :as csvg]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.svg.shapes-builder :as csvg.shapes-builder]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.repo :as rp]
|
||||
[app.util.color :as uc]
|
||||
[app.util.path.parser :as upp]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
(def default-rect
|
||||
{:x 0 :y 0 :width 1 :height 1})
|
||||
|
||||
(defn- assert-valid-num [attr num]
|
||||
(dm/verify!
|
||||
["%1 attribute has invalid value: %2" (d/name attr) num]
|
||||
(and (d/num? num)
|
||||
(<= num max-safe-int)
|
||||
(>= num min-safe-int)))
|
||||
|
||||
(cond
|
||||
(and (> num 0) (< num 1)) 1
|
||||
(and (< num 0) (> num -1)) -1
|
||||
:else num))
|
||||
|
||||
(defn- assert-valid-pos-num
|
||||
[attr num]
|
||||
|
||||
(dm/verify!
|
||||
["%1 attribute should be positive" (d/name attr)]
|
||||
(pos? num))
|
||||
|
||||
num)
|
||||
|
||||
(defn- assert-valid-blend-mode
|
||||
[mode]
|
||||
(let [clean-value (-> mode str/trim str/lower keyword)]
|
||||
(dm/verify!
|
||||
["%1 is not a valid blend mode" clean-value]
|
||||
(contains? cts/blend-modes clean-value))
|
||||
clean-value))
|
||||
|
||||
(defn- svg-dimensions
|
||||
[data]
|
||||
(let [width (dm/get-in data [:attrs :width] 100)
|
||||
height (dm/get-in data [:attrs :height] 100)
|
||||
viewbox (dm/get-in data [:attrs :viewBox] (str "0 0 " width " " height))
|
||||
[x y width height] (->> (str/split viewbox #"\s+")
|
||||
(map d/parse-double))
|
||||
width (if (= width 0) 1 width)
|
||||
height (if (= height 0) 1 height)]
|
||||
[(assert-valid-num :x x)
|
||||
(assert-valid-num :y y)
|
||||
(assert-valid-pos-num :width width)
|
||||
(assert-valid-pos-num :height height)]))
|
||||
|
||||
(defn tag->name
|
||||
"Given a tag returns its layer name"
|
||||
[tag]
|
||||
(let [suffix (cond
|
||||
(string? tag) tag
|
||||
(keyword? tag) (d/name tag)
|
||||
(nil? tag) "node"
|
||||
:else (dm/str tag))]
|
||||
(dm/str "svg-" suffix)))
|
||||
|
||||
(defn setup-fill
|
||||
[shape]
|
||||
(let [color-attr (str/trim (dm/get-in shape [:svg-attrs :fill]))
|
||||
color-attr (if (= color-attr "currentColor") clr/black color-attr)
|
||||
color-style (str/trim (dm/get-in shape [:svg-attrs :style :fill]))
|
||||
color-style (if (= color-style "currentColor") clr/black color-style)]
|
||||
(cond-> shape
|
||||
;; Color present as attribute
|
||||
(uc/color? color-attr)
|
||||
(-> (update :svg-attrs dissoc :fill)
|
||||
(update-in [:svg-attrs :style] dissoc :fill)
|
||||
(assoc-in [:fills 0 :fill-color] (uc/parse-color color-attr)))
|
||||
|
||||
;; Color present as style
|
||||
(uc/color? color-style)
|
||||
(-> (update-in [:svg-attrs :style] dissoc :fill)
|
||||
(update :svg-attrs dissoc :fill)
|
||||
(assoc-in [:fills 0 :fill-color] (uc/parse-color color-style)))
|
||||
|
||||
(dm/get-in shape [:svg-attrs :fillOpacity])
|
||||
(-> (update :svg-attrs dissoc :fillOpacity)
|
||||
(update-in [:svg-attrs :style] dissoc :fillOpacity)
|
||||
(assoc-in [:fills 0 :fill-opacity] (-> (dm/get-in shape [:svg-attrs :fillOpacity])
|
||||
(d/parse-double 1))))
|
||||
|
||||
(dm/get-in shape [:svg-attrs :style :fillOpacity])
|
||||
(-> (update-in [:svg-attrs :style] dissoc :fillOpacity)
|
||||
(update :svg-attrs dissoc :fillOpacity)
|
||||
(assoc-in [:fills 0 :fill-opacity] (-> (dm/get-in shape [:svg-attrs :style :fillOpacity])
|
||||
(d/parse-double 1)))))))
|
||||
|
||||
(defn- setup-stroke
|
||||
[shape]
|
||||
(let [attrs (get shape :svg-attrs)
|
||||
style (get attrs :style)
|
||||
|
||||
stroke (or (str/trim (:stroke attrs))
|
||||
(str/trim (:stroke style)))
|
||||
|
||||
color (cond
|
||||
(= stroke "currentColor") clr/black
|
||||
(= stroke "none") nil
|
||||
:else (uc/parse-color stroke))
|
||||
|
||||
opacity (when (some? color)
|
||||
(d/parse-double
|
||||
(or (:strokeOpacity attrs)
|
||||
(:strokeOpacity style))
|
||||
1))
|
||||
|
||||
width (when (some? color)
|
||||
(d/parse-double
|
||||
(or (:strokeWidth attrs)
|
||||
(:strokeWidth style))
|
||||
1))
|
||||
|
||||
linecap (or (get attrs :strokeLinecap)
|
||||
(get style :strokeLinecap))
|
||||
linecap (some-> linecap str/trim keyword)
|
||||
|
||||
attrs (-> attrs
|
||||
(dissoc :stroke)
|
||||
(dissoc :strokeWidth)
|
||||
(dissoc :strokeOpacity)
|
||||
(update :style (fn [style]
|
||||
(-> style
|
||||
(dissoc :stroke)
|
||||
(dissoc :strokeLinecap)
|
||||
(dissoc :strokeWidth)
|
||||
(dissoc :strokeOpacity)))))]
|
||||
|
||||
(cond-> (assoc shape :svg-attrs attrs)
|
||||
(some? color)
|
||||
(assoc-in [:strokes 0 :stroke-color] color)
|
||||
|
||||
(and (some? color) (some? opacity))
|
||||
(assoc-in [:strokes 0 :stroke-opacity] opacity)
|
||||
|
||||
(and (some? color) (some? width))
|
||||
(assoc-in [:strokes 0 :stroke-width] width)
|
||||
|
||||
(and (some? linecap) (= (:type shape) :path)
|
||||
(or (= linecap :round) (= linecap :square)))
|
||||
(assoc :stroke-cap-start linecap
|
||||
:stroke-cap-end linecap)
|
||||
|
||||
(d/any-key? (dm/get-in shape [:strokes 0])
|
||||
:strokeColor :strokeOpacity :strokeWidth
|
||||
:strokeCapStart :strokeCapEnd)
|
||||
(assoc-in [:strokes 0 :stroke-style] :svg))))
|
||||
|
||||
(defn setup-opacity [shape]
|
||||
(cond-> shape
|
||||
(dm/get-in shape [:svg-attrs :opacity])
|
||||
(-> (update :svg-attrs dissoc :opacity)
|
||||
(assoc :opacity (-> (dm/get-in shape [:svg-attrs :opacity])
|
||||
(d/parse-double 1))))
|
||||
|
||||
(dm/get-in shape [:svg-attrs :style :opacity])
|
||||
(-> (update-in [:svg-attrs :style] dissoc :opacity)
|
||||
(assoc :opacity (-> (dm/get-in shape [:svg-attrs :style :opacity])
|
||||
(d/parse-double 1))))
|
||||
|
||||
(dm/get-in shape [:svg-attrs :mixBlendMode])
|
||||
(-> (update :svg-attrs dissoc :mixBlendMode)
|
||||
(assoc :blend-mode (-> (dm/get-in shape [:svg-attrs :mixBlendMode]) assert-valid-blend-mode)))
|
||||
|
||||
(dm/get-in shape [:svg-attrs :style :mixBlendMode])
|
||||
(-> (update-in [:svg-attrs :style] dissoc :mixBlendMode)
|
||||
(assoc :blend-mode (-> (dm/get-in shape [:svg-attrs :style :mixBlendMode]) assert-valid-blend-mode)))))
|
||||
|
||||
(defn create-raw-svg
|
||||
[name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}]
|
||||
(let [props (csvg/attrs->props attrs)
|
||||
vbox (grc/make-rect offset-x offset-y width height)]
|
||||
(cts/setup-shape
|
||||
{:type :svg-raw
|
||||
:name name
|
||||
:frame-id frame-id
|
||||
:width width
|
||||
:height height
|
||||
:x x
|
||||
:y y
|
||||
:content data
|
||||
:svg-attrs props
|
||||
:svg-viewbox vbox})))
|
||||
|
||||
(defn create-svg-root
|
||||
[frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}]
|
||||
(let [props (-> (dissoc attrs :viewBox :view-box :xmlns)
|
||||
(d/without-keys csvg/inheritable-props)
|
||||
(csvg/attrs->props))]
|
||||
(cts/setup-shape
|
||||
{:type :group
|
||||
:name name
|
||||
:frame-id frame-id
|
||||
:parent-id parent-id
|
||||
:width width
|
||||
:height height
|
||||
:x (+ x offset-x)
|
||||
:y (+ y offset-y)
|
||||
:svg-attrs props})))
|
||||
|
||||
(defn create-group
|
||||
[name frame-id {:keys [x y width height offset-x offset-y] :as svg-data} {:keys [attrs]}]
|
||||
(let [transform (csvg/parse-transform (:transform attrs))
|
||||
attrs (-> (d/without-keys attrs csvg/inheritable-props)
|
||||
(csvg/attrs->props))
|
||||
vbox (grc/make-rect offset-x offset-y width height)]
|
||||
(cts/setup-shape
|
||||
{:type :group
|
||||
:name name
|
||||
:frame-id frame-id
|
||||
:x (+ x offset-x)
|
||||
:y (+ y offset-y)
|
||||
:width width
|
||||
:height height
|
||||
:svg-transform transform
|
||||
:svg-attrs attrs
|
||||
:svg-viewbox vbox})))
|
||||
|
||||
(defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}]
|
||||
(when (and (contains? attrs :d) (seq (:d attrs)))
|
||||
(let [transform (csvg/parse-transform (:transform attrs))
|
||||
content (cond-> (upp/parse-path (:d attrs))
|
||||
(some? transform)
|
||||
(gsh/transform-content transform))
|
||||
|
||||
selrect (gsh/content->selrect content)
|
||||
points (grc/rect->points selrect)
|
||||
origin (gpt/negate (gpt/point svg-data))
|
||||
attrs (-> (dissoc attrs :d :transform)
|
||||
(csvg/attrs->props))]
|
||||
(-> (cts/setup-shape
|
||||
{:type :path
|
||||
:name name
|
||||
:frame-id frame-id
|
||||
:content content
|
||||
:selrect selrect
|
||||
:points points
|
||||
:svg-viewbox selrect
|
||||
:svg-attrs attrs
|
||||
:svg-transform transform
|
||||
:fills []})
|
||||
(gsh/translate-to-frame origin)))))
|
||||
|
||||
(defn calculate-rect-metadata
|
||||
[rect transform]
|
||||
(let [points (-> rect
|
||||
(grc/rect->points)
|
||||
(gsh/transform-points transform))
|
||||
center (gsc/points->center points)
|
||||
selrect (gst/calculate-selrect points center)
|
||||
transform (gst/calculate-transform points center selrect)]
|
||||
|
||||
{:x (:x selrect)
|
||||
:y (:y selrect)
|
||||
:width (:width selrect)
|
||||
:height (:height selrect)
|
||||
:selrect selrect
|
||||
:points points
|
||||
:transform transform
|
||||
:transform-inverse (when (some? transform)
|
||||
(gmt/inverse transform))}))
|
||||
|
||||
(defn- parse-rect-attrs
|
||||
[{:keys [x y width height]}]
|
||||
(grc/make-rect
|
||||
(d/parse-double x 0)
|
||||
(d/parse-double y 0)
|
||||
(d/parse-double width 1)
|
||||
(d/parse-double height 1)))
|
||||
|
||||
(defn create-rect-shape [name frame-id svg-data {:keys [attrs] :as data}]
|
||||
(let [transform (->> (csvg/parse-transform (:transform attrs))
|
||||
(gmt/transform-in (gpt/point svg-data)))
|
||||
|
||||
origin (gpt/negate (gpt/point svg-data))
|
||||
rect (-> (parse-rect-attrs attrs)
|
||||
(update :x - (:x origin))
|
||||
(update :y - (:y origin)))
|
||||
|
||||
props (-> (dissoc attrs :x :y :width :height :rx :ry :transform)
|
||||
(csvg/attrs->props))]
|
||||
|
||||
(cts/setup-shape
|
||||
(-> (calculate-rect-metadata rect transform)
|
||||
(assoc :type :rect)
|
||||
(assoc :name name)
|
||||
(assoc :frame-id frame-id)
|
||||
(assoc :svg-viewbox rect)
|
||||
(assoc :svg-attrs props)
|
||||
;; We need to ensure fills are empty on import process
|
||||
;; because setup-shape assings one by default.
|
||||
(assoc :fills [])
|
||||
(cond-> (contains? attrs :rx)
|
||||
(assoc :rx (d/parse-double (:rx attrs) 0)))
|
||||
(cond-> (contains? attrs :ry)
|
||||
(assoc :ry (d/parse-double (:ry attrs) 0)))))))
|
||||
|
||||
(defn- parse-circle-attrs
|
||||
[attrs]
|
||||
(into [] (comp (map (d/getf attrs))
|
||||
(map d/parse-double))
|
||||
[:cx :cy :r :rx :ry]))
|
||||
|
||||
(defn create-circle-shape
|
||||
[name frame-id svg-data {:keys [attrs] :as data}]
|
||||
(let [[cx cy r rx ry]
|
||||
(parse-circle-attrs attrs)
|
||||
|
||||
transform (->> (csvg/parse-transform (:transform attrs))
|
||||
(gmt/transform-in (gpt/point svg-data)))
|
||||
|
||||
rx (d/nilv r rx)
|
||||
ry (d/nilv r ry)
|
||||
origin (gpt/negate (gpt/point svg-data))
|
||||
|
||||
rect (grc/make-rect
|
||||
(- cx rx (:x origin))
|
||||
(- cy ry (:y origin))
|
||||
(* 2 rx)
|
||||
(* 2 ry))
|
||||
props (-> (dissoc attrs :cx :cy :r :rx :ry :transform)
|
||||
(csvg/attrs->props))]
|
||||
|
||||
(cts/setup-shape
|
||||
(-> (calculate-rect-metadata rect transform)
|
||||
(assoc :type :circle)
|
||||
(assoc :name name)
|
||||
(assoc :frame-id frame-id)
|
||||
(assoc :svg-viewbox rect)
|
||||
(assoc :svg-attrs props)
|
||||
(assoc :fills [])))))
|
||||
|
||||
(defn create-image-shape
|
||||
[name frame-id svg-data {:keys [attrs] :as data}]
|
||||
(let [transform (->> (csvg/parse-transform (:transform attrs))
|
||||
(gmt/transform-in (gpt/point svg-data)))
|
||||
|
||||
image-url (or (:href attrs) (:xlink:href attrs))
|
||||
image-data (dm/get-in svg-data [:image-data image-url])
|
||||
|
||||
metadata {:width (:width image-data)
|
||||
:height (:height image-data)
|
||||
:mtype (:mtype image-data)
|
||||
:id (:id image-data)}
|
||||
|
||||
origin (gpt/negate (gpt/point svg-data))
|
||||
rect (-> (parse-rect-attrs attrs)
|
||||
(update :x - (:x origin))
|
||||
(update :y - (:y origin)))
|
||||
props (-> (dissoc attrs :x :y :width :height :href :xlink:href)
|
||||
(csvg/attrs->props))]
|
||||
|
||||
(when (some? image-data)
|
||||
(cts/setup-shape
|
||||
(-> (calculate-rect-metadata rect transform)
|
||||
(assoc :type :image)
|
||||
(assoc :name name)
|
||||
(assoc :frame-id frame-id)
|
||||
(assoc :metadata metadata)
|
||||
(assoc :svg-viewbox rect)
|
||||
(assoc :svg-attrs props))))))
|
||||
|
||||
(defn parse-svg-element
|
||||
[frame-id svg-data {:keys [tag attrs hidden] :as element} unames]
|
||||
(let [name (or (:id attrs) (tag->name tag))
|
||||
att-refs (csvg/find-attr-references attrs)
|
||||
defs (get svg-data :defs)
|
||||
references (csvg/find-def-references defs att-refs)
|
||||
href-id (-> (or (:href attrs) (:xlink:href attrs) "") (subs 1))
|
||||
use-tag? (and (= :use tag) (contains? defs href-id))]
|
||||
|
||||
(if use-tag?
|
||||
(let [;; Merge the data of the use definition with the properties passed as attributes
|
||||
use-data (-> (get defs href-id)
|
||||
(update :attrs #(d/deep-merge % (dissoc attrs :xlink:href :href))))
|
||||
displacement (gpt/point (d/parse-double (:x attrs "0")) (d/parse-double (:y attrs "0")))
|
||||
disp-matrix (dm/str (gmt/translate-matrix displacement))
|
||||
element (-> element
|
||||
(assoc :tag :g)
|
||||
(update :attrs dissoc :x :y :width :height :href :xlink:href :transform)
|
||||
(update :attrs csvg/add-transform disp-matrix)
|
||||
(assoc :content [use-data]))]
|
||||
(parse-svg-element frame-id svg-data element unames))
|
||||
|
||||
(let [;; SVG graphic elements
|
||||
;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use
|
||||
shape (case tag
|
||||
(:g :a :svg) (create-group name frame-id svg-data element)
|
||||
:rect (create-rect-shape name frame-id svg-data element)
|
||||
(:circle
|
||||
:ellipse) (create-circle-shape name frame-id svg-data element)
|
||||
:path (create-path-shape name frame-id svg-data element)
|
||||
:polyline (create-path-shape name frame-id svg-data (-> element csvg/polyline->path))
|
||||
:polygon (create-path-shape name frame-id svg-data (-> element csvg/polygon->path))
|
||||
:line (create-path-shape name frame-id svg-data (-> element csvg/line->path))
|
||||
:image (create-image-shape name frame-id svg-data element)
|
||||
#_other (create-raw-svg name frame-id svg-data element))]
|
||||
|
||||
|
||||
(when (some? shape)
|
||||
(let [shape (-> shape
|
||||
(assoc :svg-defs (select-keys defs references))
|
||||
(setup-fill)
|
||||
(setup-stroke)
|
||||
(setup-opacity)
|
||||
(update :svg-attrs (fn [attrs]
|
||||
(if (empty? (:style attrs))
|
||||
(dissoc attrs :style)
|
||||
attrs))))]
|
||||
[(cond-> shape
|
||||
hidden (assoc :hidden true))
|
||||
|
||||
(cond->> (:content element)
|
||||
(contains? csvg/parent-tags tag)
|
||||
(mapv #(csvg/inherit-attributes attrs %)))]))))))
|
||||
|
||||
(defn create-svg-children
|
||||
[objects selected frame-id parent-id svg-data [unames children] [_index svg-element]]
|
||||
(let [[shape new-children] (parse-svg-element frame-id svg-data svg-element unames)]
|
||||
(if (some? shape)
|
||||
(let [shape-id (:id shape)
|
||||
shape (-> shape
|
||||
(assoc :frame-id frame-id)
|
||||
(assoc :parent-id parent-id))
|
||||
children (conj children shape)
|
||||
unames (conj unames (:name shape))]
|
||||
|
||||
(reduce (partial create-svg-children objects selected frame-id shape-id svg-data)
|
||||
[unames children]
|
||||
(d/enumerate new-children)))
|
||||
|
||||
[unames children])))
|
||||
|
||||
(defn extract-name [url]
|
||||
(let [query-idx (str/last-index-of url "?")
|
||||
url (if (> query-idx 0) (subs url 0 query-idx) url)
|
||||
|
@ -499,70 +55,6 @@
|
|||
(rx/map #(vector (:url uri-data) %)))))
|
||||
(rx/reduce (fn [acc [url image]] (assoc acc url image)) {})))
|
||||
|
||||
|
||||
(defn create-svg-shapes
|
||||
[svg-data {:keys [x y]} objects frame-id parent-id selected center?]
|
||||
(let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data)
|
||||
|
||||
|
||||
unames (cfh/get-used-names objects)
|
||||
svg-name (str/replace (:name svg-data) ".svg" "")
|
||||
|
||||
svg-data (-> svg-data
|
||||
(assoc :x (mth/round
|
||||
(if center?
|
||||
(- x vb-x (/ vb-width 2))
|
||||
x)))
|
||||
(assoc :y (mth/round
|
||||
(if center?
|
||||
(- y vb-y (/ vb-height 2))
|
||||
y)))
|
||||
(assoc :offset-x vb-x)
|
||||
(assoc :offset-y vb-y)
|
||||
(assoc :width vb-width)
|
||||
(assoc :height vb-height)
|
||||
(assoc :name svg-name))
|
||||
|
||||
[def-nodes svg-data]
|
||||
(-> svg-data
|
||||
(csvg/fix-default-values)
|
||||
(csvg/fix-percents)
|
||||
(csvg/extract-defs))
|
||||
|
||||
;; In penpot groups have the size of their children. To
|
||||
;; respect the imported svg size and empty space let's create
|
||||
;; a transparent shape as background to respect the imported
|
||||
;; size
|
||||
background
|
||||
{:tag :rect
|
||||
:attrs {:x (dm/str vb-x)
|
||||
:y (dm/str vb-y)
|
||||
:width (dm/str vb-width)
|
||||
:height (dm/str vb-height)
|
||||
:fill "none"
|
||||
:id "base-background"}
|
||||
:hidden true
|
||||
:content []}
|
||||
|
||||
svg-data (-> svg-data
|
||||
(assoc :defs def-nodes)
|
||||
(assoc :content (into [background] (:content svg-data))))
|
||||
|
||||
root-shape (create-svg-root frame-id parent-id svg-data)
|
||||
root-id (:id root-shape)
|
||||
|
||||
;; Create the root shape
|
||||
root-attrs (-> (:attrs svg-data)
|
||||
(csvg/format-styles))
|
||||
|
||||
[_ children]
|
||||
(reduce (partial create-svg-children objects selected frame-id root-id svg-data)
|
||||
[unames []]
|
||||
(d/enumerate (->> (:content svg-data)
|
||||
(mapv #(csvg/inherit-attributes root-attrs %)))))]
|
||||
|
||||
[root-shape children]))
|
||||
|
||||
(defn add-svg-shapes
|
||||
[svg-data position]
|
||||
(ptk/reify ::add-svg-shapes
|
||||
|
@ -584,7 +76,7 @@
|
|||
(:parent-id base))
|
||||
|
||||
[new-shape new-children]
|
||||
(create-svg-shapes svg-data position objects frame-id parent-id selected true)
|
||||
(csvg.shapes-builder/create-svg-shapes svg-data position objects frame-id parent-id selected true)
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
(ns app.main.ui.components.color-input
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.util.color :as uc]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.globals :as globals]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
|
@ -19,9 +19,9 @@
|
|||
(defn clean-color
|
||||
[value]
|
||||
(-> value
|
||||
(uc/expand-hex)
|
||||
(uc/parse-color)
|
||||
(uc/prepend-hash)))
|
||||
(cc/expand-hex)
|
||||
(cc/parse)
|
||||
(cc/prepend-hash)))
|
||||
|
||||
(mf/defc color-input*
|
||||
{::mf/wrap-props false
|
||||
|
@ -62,14 +62,14 @@
|
|||
(mf/deps ref)
|
||||
(fn [new-value]
|
||||
(let [input-node (mf/ref-val ref)]
|
||||
(dom/set-value! input-node (uc/remove-hash new-value)))))
|
||||
(dom/set-value! input-node (cc/remove-hash new-value)))))
|
||||
|
||||
apply-value
|
||||
(mf/use-fn
|
||||
(mf/deps on-change update-input)
|
||||
(fn [new-value]
|
||||
(mf/set-ref-val! dirty-ref false)
|
||||
(when (and new-value (not= (uc/remove-hash new-value) value))
|
||||
(when (and new-value (not= (cc/remove-hash new-value) value))
|
||||
(when on-change
|
||||
(on-change new-value))
|
||||
(update-input new-value))))
|
||||
|
@ -170,7 +170,7 @@
|
|||
[:> :input props]
|
||||
;; FIXME: this causes some weird interactions because of using apply-value
|
||||
;; [:datalist {:id list-id}
|
||||
;; (for [color-name uc/color-names]
|
||||
;; (for [color-name cc/color-names]
|
||||
;; [:option color-name])]
|
||||
]))
|
||||
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
(ns app.main.ui.shapes.filters
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.shapes.bounds :as gsb]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.color :as color]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
|||
(mf/defc color-matrix
|
||||
[{:keys [color]}]
|
||||
(let [{:keys [color opacity]} color
|
||||
[r g b a] (color/hex->rgba color opacity)
|
||||
[r g b a] (cc/hex->rgba color opacity)
|
||||
[r g b] [(/ r 255) (/ g 255) (/ b 255)]]
|
||||
[:feColorMatrix
|
||||
{:type "matrix"
|
||||
|
|
|
@ -6,12 +6,11 @@
|
|||
|
||||
(ns app.main.ui.shapes.text.fo-text
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.main.ui.shapes.text.styles :as sts]
|
||||
[app.util.color :as uc]
|
||||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
@ -85,9 +84,9 @@
|
|||
[colors]
|
||||
(assert (set? colors))
|
||||
(loop [current-rgb [0 0 0]]
|
||||
(let [current-hex (uc/rgb->hex current-rgb)]
|
||||
(let [current-hex (cc/rgb->hex current-rgb)]
|
||||
(if (contains? colors current-hex)
|
||||
(recur (uc/next-rgb current-rgb))
|
||||
(recur (cc/next-rgb current-rgb))
|
||||
current-hex))))
|
||||
|
||||
(defn- fill->color
|
||||
|
@ -122,7 +121,7 @@
|
|||
(filter some?))
|
||||
|
||||
colors (->> color-data
|
||||
(into #{clr/black}
|
||||
(into #{cc/black}
|
||||
(comp (filter #(= :solid (:type %)))
|
||||
(map :hex))))
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.main.ui.shapes.text.styles
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.text :as txt]
|
||||
[app.common.transit :as transit]
|
||||
|
@ -76,7 +77,7 @@
|
|||
fill-opacity (or (-> data :fills first :fill-opacity) (:fill-opacity data))
|
||||
fill-gradient (or (-> data :fills first :fill-color-gradient) (:fill-color-gradient data))
|
||||
|
||||
[r g b a] (uc/hex->rgba fill-color fill-opacity)
|
||||
[r g b a] (cc/hex->rgba fill-color fill-opacity)
|
||||
text-color (when (and (some? fill-color) (some? fill-opacity))
|
||||
(str/format "rgba(%s, %s, %s, %s)" r g b a))
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.main.ui.viewer.inspect.attributes.common
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.color-bullet :refer [color-bullet color-name]]
|
||||
|
@ -14,7 +15,6 @@
|
|||
[app.main.ui.components.copy-button :refer [copy-button]]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.util.color :as uc]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -85,10 +85,10 @@
|
|||
(case format
|
||||
:hex [:& cbn/color-name {:color color
|
||||
:size 80}]
|
||||
:rgba (let [[r g b a] (uc/hex->rgba (:color color) (:opacity color))]
|
||||
:rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))]
|
||||
[:* (str/fmt "%s, %s, %s, %s" r g b a)])
|
||||
:hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color))
|
||||
result (uc/format-hsla [h s l a])]
|
||||
:hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
|
||||
result (cc/format-hsla [h s l a])]
|
||||
[:* result])))]
|
||||
|
||||
(when-not (:gradient color)
|
||||
|
@ -111,10 +111,10 @@
|
|||
(case format
|
||||
:hex [:& cbn/color-name {:color color
|
||||
:size 80}]
|
||||
:rgba (let [[r g b a] (uc/hex->rgba (:color color) (:opacity color))]
|
||||
:rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))]
|
||||
[:* (str/fmt "%s, %s, %s, %s" r g b a)])
|
||||
:hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color))
|
||||
result (uc/format-hsla [h s l a])]
|
||||
:hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
|
||||
result (cc/format-hsla [h s l a])]
|
||||
[:* result])))]
|
||||
|
||||
(when-not (:gradient color)
|
||||
|
@ -135,10 +135,10 @@
|
|||
;; (case format
|
||||
;; :hex [:& cbn/color-name {:color color
|
||||
;; :size 80}]
|
||||
;; :rgba (let [[r g b a] (uc/hex->rgba (:color color) (:opacity color))]
|
||||
;; :rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))]
|
||||
;; [:* (str/fmt "%s, %s, %s, %s" r g b a)])
|
||||
;; :hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color))
|
||||
;; result (uc/format-hsla [h s l a])]
|
||||
;; :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
|
||||
;; result (cc/format-hsla [h s l a])]
|
||||
;; [:* result])))]
|
||||
|
||||
;; (when color-library-name
|
||||
|
@ -163,10 +163,10 @@
|
|||
(if (:gradient color)
|
||||
[:& color-name {:color color}]
|
||||
(case format
|
||||
:rgba (let [[r g b a] (uc/hex->rgba (:color color) (:opacity color))]
|
||||
:rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))]
|
||||
[:div (str/fmt "%s, %s, %s, %s" r g b a)])
|
||||
:hsla (let [[h s l a] (uc/hex->hsla (:color color) (:opacity color))
|
||||
result (uc/format-hsla [h s l a])]
|
||||
:hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
|
||||
result (cc/format-hsla [h s l a])]
|
||||
[:div result])
|
||||
[:*
|
||||
[:& color-name {:color color}]
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.main.ui.workspace.colorpicker
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
|
@ -22,7 +23,6 @@
|
|||
[app.main.ui.workspace.colorpicker.hsva :refer [hsva-selector]]
|
||||
[app.main.ui.workspace.colorpicker.libraries :refer [libraries]]
|
||||
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]]
|
||||
[app.util.color :as uc]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -190,9 +190,9 @@
|
|||
(let [node (mf/ref-val node-ref)
|
||||
{:keys [r g b h v]} current-color
|
||||
rgb [r g b]
|
||||
hue-rgb (uc/hsv->rgb [h 1.0 255])
|
||||
hsl-from (uc/hsv->hsl [h 0.0 v])
|
||||
hsl-to (uc/hsv->hsl [h 1.0 v])
|
||||
hue-rgb (cc/hsv->rgb [h 1.0 255])
|
||||
hsl-from (cc/hsv->hsl [h 0.0 v])
|
||||
hsl-to (cc/hsv->hsl [h 1.0 v])
|
||||
|
||||
format-hsl (fn [[h s l]]
|
||||
(str/fmt "hsl(%s, %s, %s)"
|
||||
|
@ -208,8 +208,8 @@
|
|||
(mf/with-effect [picking-color? picked-color picked-color-select]
|
||||
(when (and picking-color? picked-color picked-color-select)
|
||||
(let [[r g b alpha] picked-color
|
||||
hex (uc/rgb->hex [r g b])
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
hex (cc/rgb->hex [r g b])
|
||||
[h s v] (cc/hex->hsv hex)]
|
||||
(handle-change-color {:hex hex
|
||||
:r r :g g :b b
|
||||
:h h :s s :v v
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
(ns app.main.ui.workspace.colorpicker.color-inputs
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.math :as mth]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.util.color :as uc]
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -45,23 +45,27 @@
|
|||
|
||||
setup-hex-color
|
||||
(fn [hex]
|
||||
(let [[r g b] (uc/hex->rgb hex)
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
(let [[r g b] (cc/hex->rgb hex)
|
||||
[h s v] (cc/hex->hsv hex)]
|
||||
(on-change {:hex hex
|
||||
:h h :s s :v v
|
||||
:r r :g g :b b})))
|
||||
on-change-hex
|
||||
(fn [e]
|
||||
(let [val (-> e dom/get-target-val parse-hex)]
|
||||
(when (uc/hex? val)
|
||||
(when (cc/valid-hex-color? val)
|
||||
(setup-hex-color val))))
|
||||
|
||||
on-blur-hex
|
||||
(fn [e]
|
||||
(let [val (-> e dom/get-target-val)
|
||||
;; FIXME: looks redundant, cc/parse already handles
|
||||
;; hex colors; also it performs the parse-hex twice
|
||||
;; that is completly unnecessary
|
||||
val (cond
|
||||
(uc/color? val) (uc/parse-color val)
|
||||
(uc/hex? (parse-hex val)) (parse-hex val))]
|
||||
(cc/color-string? val) (cc/parse val)
|
||||
(cc/valid-hex-color? (parse-hex val)) (parse-hex val))]
|
||||
|
||||
(when (some? val)
|
||||
(setup-hex-color val))))
|
||||
|
||||
|
@ -76,15 +80,15 @@
|
|||
(when (not (nil? val))
|
||||
(if (#{:r :g :b} property)
|
||||
(let [{:keys [r g b]} (merge color (hash-map property val))
|
||||
hex (uc/rgb->hex [r g b])
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
hex (cc/rgb->hex [r g b])
|
||||
[h s v] (cc/hex->hsv hex)]
|
||||
(on-change {:hex hex
|
||||
:h h :s s :v v
|
||||
:r r :g g :b b}))
|
||||
|
||||
(let [{:keys [h s v]} (merge color (hash-map property val))
|
||||
hex (uc/hsv->hex [h s v])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
hex (cc/hsv->hex [h s v])
|
||||
[r g b] (cc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:h h :s s :v v
|
||||
:r r :g g :b b})))))))
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
(ns app.main.ui.workspace.colorpicker.harmony
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.math :as mth]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]]
|
||||
[app.util.color :as uc]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -82,8 +82,8 @@
|
|||
angle (mth/degrees (mth/atan2 px py))
|
||||
new-hue (mod (- angle 90) 360)
|
||||
new-saturation (mth/clamp (mth/distance [px py] [0 0]) 0 1)
|
||||
hex (uc/hsv->hex [new-hue new-saturation value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
hex (cc/hsv->hex [new-hue new-saturation value])
|
||||
[r g b] (cc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:h new-hue
|
||||
|
@ -108,15 +108,15 @@
|
|||
(on-finish-drag))))
|
||||
|
||||
on-change-value (fn [new-value]
|
||||
(let [hex (uc/hsv->hex [hue saturation new-value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(let [hex (cc/hsv->hex [hue saturation new-value])
|
||||
[r g b] (cc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:v new-value})))
|
||||
on-complement-click (fn [_]
|
||||
(let [new-hue (mod (+ hue 180) 360)
|
||||
hex (uc/hsv->hex [new-hue saturation value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
hex (cc/hsv->hex [new-hue saturation value])
|
||||
[r g b] (cc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:h new-hue
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
(ns app.main.ui.workspace.colorpicker.hsva
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]]
|
||||
[app.util.color :as uc]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc hsva-selector [{:keys [color disable-opacity on-change on-start-drag on-finish-drag]}]
|
||||
|
@ -19,8 +19,8 @@
|
|||
(fn [new-value]
|
||||
(let [change (hash-map key new-value)
|
||||
{:keys [h s v]} (merge color change)
|
||||
hex (uc/hsv->hex [h s v])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
hex (cc/hsv->hex [h s v])
|
||||
[r g b] (cc/hex->rgb hex)]
|
||||
(on-change (merge change
|
||||
{:hex hex
|
||||
:r r :g g :b b})))))
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
(ns app.main.ui.workspace.colorpicker.ramp
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.math :as mth]
|
||||
[app.main.ui.components.color-bullet :refer [color-bullet]]
|
||||
[app.main.ui.components.color-bullet-new :as cb]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]]
|
||||
[app.util.color :as uc]
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -72,8 +72,8 @@
|
|||
|
||||
on-change-value-saturation
|
||||
(fn [new-saturation new-value]
|
||||
(let [hex (uc/hsv->hex [hue new-saturation new-value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(let [hex (cc/hsv->hex [hue new-saturation new-value])
|
||||
[r g b] (cc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:s new-saturation
|
||||
|
@ -81,8 +81,8 @@
|
|||
|
||||
on-change-hue
|
||||
(fn [new-hue]
|
||||
(let [hex (uc/hsv->hex [new-hue saturation value])
|
||||
[r g b] (uc/hex->rgb hex)]
|
||||
(let [hex (cc/hsv->hex [new-hue saturation value])
|
||||
[r g b] (cc/hex->rgb hex)]
|
||||
(on-change {:hex hex
|
||||
:r r :g g :b b
|
||||
:h new-hue})))
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
(ns app.main.ui.workspace.shapes.path
|
||||
(:require
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.main.data.workspace.path.helpers :as helpers]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.shapes.path :as path]
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.path.shapes-to-path :as ups]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.shapes-to-path :as ups]
|
||||
[app.main.data.workspace.path :as drp]
|
||||
[app.main.snap :as snap]
|
||||
[app.main.store :as st]
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.main.ui.workspace.sidebar.options.rows.color-row
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.shape.attrs :refer [default-color]]
|
||||
|
@ -222,7 +223,7 @@
|
|||
[:span {:class (stl/css :color-input-wrapper)}
|
||||
[:> color-input* {:value (if multiple-colors?
|
||||
""
|
||||
(-> color :color uc/remove-hash))
|
||||
(-> color :color cc/remove-hash))
|
||||
:placeholder (tr "settings.multiple")
|
||||
:className (stl/css :color-input)
|
||||
:on-focus on-focus
|
||||
|
@ -301,7 +302,7 @@
|
|||
[:div.color-info
|
||||
[:> color-input* {:value (if multiple-colors?
|
||||
""
|
||||
(-> color :color uc/remove-hash))
|
||||
(-> color :color cc/remove-hash))
|
||||
:placeholder (tr "settings.multiple")
|
||||
:on-focus on-focus
|
||||
:on-blur on-blur
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
(ns app.main.ui.workspace.sidebar.options.shapes.svg-raw
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.main.refs :as refs]
|
||||
|
@ -21,7 +21,6 @@
|
|||
[app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]]
|
||||
[app.util.color :as uc]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -45,7 +44,7 @@
|
|||
{:color :multiple
|
||||
:opacity :multiple}
|
||||
|
||||
:else {:color (uc/parse-color color)
|
||||
:else {:color (cc/parse color)
|
||||
:opacity 1})
|
||||
|
||||
(catch :default e
|
||||
|
@ -71,7 +70,7 @@
|
|||
(get-in shape [:content :attrs :style :stroke]))
|
||||
(parse-color))
|
||||
|
||||
stroke-color (:color color clr/black)
|
||||
stroke-color (:color color cc/black)
|
||||
stroke-opacity (:opacity color 1)
|
||||
stroke-style (-> (or (get-in shape [:content :attrs :stroke-style])
|
||||
(get-in shape [:content :attrs :style :stroke-style])
|
||||
|
|
|
@ -5,125 +5,20 @@
|
|||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.util.color
|
||||
"Color conversion utils."
|
||||
"FIXME: this is legacy namespace, all functions of this ns should be
|
||||
relocated under app.common.types on the respective colors related
|
||||
namespace. All generic color conversion and other helpers are moved to
|
||||
app.common.colors namespace."
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.object :as obj]
|
||||
[app.util.strings :as ust]
|
||||
[cuerdas.core :as str]
|
||||
[goog.color :as gcolor]))
|
||||
|
||||
(defn rgb->str
|
||||
[color]
|
||||
{:pre [(vector? color)]}
|
||||
(if (= (count color) 3)
|
||||
(apply str/format "rgb(%s,%s,%s)" color)
|
||||
(apply str/format "rgba(%s,%s,%s,%s)" color)))
|
||||
|
||||
(defn rgb->hsv
|
||||
[[r g b]]
|
||||
(into [] (gcolor/rgbToHsv r g b)))
|
||||
|
||||
(defn hsv->rgb
|
||||
[[h s v]]
|
||||
(into [] (gcolor/hsvToRgb h s v)))
|
||||
|
||||
(defn hex->rgb
|
||||
[v]
|
||||
(try
|
||||
(into [] (gcolor/hexToRgb v))
|
||||
(catch :default _e [0 0 0])))
|
||||
|
||||
(defn rgb->hex
|
||||
[[r g b]]
|
||||
(gcolor/rgbToHex r g b))
|
||||
|
||||
(defn hex->hsv
|
||||
[v]
|
||||
(into [] (gcolor/hexToHsv v)))
|
||||
|
||||
(defn hex->rgba
|
||||
[^string data ^number opacity]
|
||||
(-> (hex->rgb data)
|
||||
(conj opacity)))
|
||||
|
||||
(defn hex->hsl [hex]
|
||||
(try
|
||||
(into [] (gcolor/hexToHsl hex))
|
||||
(catch :default _e [0 0 0])))
|
||||
|
||||
(defn hex->hsla
|
||||
[^string data ^number opacity]
|
||||
(-> (hex->hsl data)
|
||||
(conj opacity)))
|
||||
|
||||
(defn format-hsla
|
||||
[[h s l a]]
|
||||
(let [precision 2
|
||||
rounded-s (* 100 (ust/format-precision s precision))
|
||||
rounded-l (* 100 (ust/format-precision l precision))]
|
||||
|
||||
(str/fmt "%s, %s%, %s%, %s" h rounded-s rounded-l a)))
|
||||
|
||||
(defn hsl->rgb
|
||||
[[h s l]]
|
||||
(gcolor/hslToRgb h s l))
|
||||
|
||||
(defn hsl->hex
|
||||
[[h s l]]
|
||||
(gcolor/hslToHex h s l))
|
||||
|
||||
(defn hex?
|
||||
[v]
|
||||
(and (string? v)
|
||||
(re-seq #"^#[0-9A-Fa-f]{6}$" v)))
|
||||
|
||||
(defn hsl->hsv
|
||||
[[h s l]]
|
||||
(gcolor/hslToHsv h s l))
|
||||
|
||||
(defn hsv->hex
|
||||
[[h s v]]
|
||||
(gcolor/hsvToHex h s v))
|
||||
|
||||
(defn hsv->hsl
|
||||
[hsv]
|
||||
(hex->hsl (hsv->hex hsv)))
|
||||
|
||||
(defn expand-hex
|
||||
[v]
|
||||
(cond
|
||||
(re-matches #"^[0-9A-Fa-f]$" v)
|
||||
(str v v v v v v)
|
||||
|
||||
(re-matches #"^[0-9A-Fa-f]{2}$" v)
|
||||
(str v v v)
|
||||
|
||||
(re-matches #"^[0-9A-Fa-f]{3}$" v)
|
||||
(let [a (nth v 0)
|
||||
b (nth v 1)
|
||||
c (nth v 2)]
|
||||
(str a a b b c c))
|
||||
|
||||
:else
|
||||
v))
|
||||
|
||||
(defn prepend-hash
|
||||
[color]
|
||||
(gcolor/prependHashIfNecessaryHelper color))
|
||||
|
||||
(defn remove-hash
|
||||
[color]
|
||||
(if (str/starts-with? color "#")
|
||||
(subs color 1)
|
||||
color))
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(defn gradient->css [{:keys [type stops]}]
|
||||
(let [parse-stop
|
||||
(fn [{:keys [offset color opacity]}]
|
||||
(let [[r g b] (hex->rgb color)]
|
||||
(let [[r g b] (cc/hex->rgb color)]
|
||||
(str/fmt "rgba(%s, %s, %s, %s) %s" r g b opacity (str (* offset 100) "%"))))
|
||||
|
||||
stops-css (str/join "," (map parse-stop stops))]
|
||||
|
@ -147,7 +42,7 @@
|
|||
(gradient->css gradient)
|
||||
|
||||
(not= color :multiple)
|
||||
(let [[r g b] (hex->rgb (or color value))]
|
||||
(let [[r g b] (cc/hex->rgb (or color value))]
|
||||
(str/fmt "rgba(%s, %s, %s, %s)" r g b opacity))
|
||||
|
||||
:else "transparent")))
|
||||
|
@ -160,56 +55,27 @@
|
|||
|
||||
(not= color :multiple)
|
||||
(case format
|
||||
:rgba (let [[r g b] (hex->rgb color)]
|
||||
:rgba (let [[r g b] (cc/hex->rgb color)]
|
||||
(str/fmt "rgba(%s, %s, %s, %s)" r g b opacity))
|
||||
|
||||
:hsla (let [[h s l] (hex->hsl color)]
|
||||
:hsla (let [[h s l] (cc/hex->hsl color)]
|
||||
(str/fmt "hsla(%s, %s, %s, %s)" h (* 100 s) (* 100 l) opacity))
|
||||
|
||||
:hex (str color (str/upper (d/opacity-to-hex opacity))))
|
||||
|
||||
:else "transparent")))
|
||||
|
||||
(defn multiple? [{:keys [id file-id value color gradient]}]
|
||||
(defn multiple?
|
||||
[{:keys [id file-id value color gradient]}]
|
||||
(or (= value :multiple)
|
||||
(= color :multiple)
|
||||
(= gradient :multiple)
|
||||
(= id :multiple)
|
||||
(= file-id :multiple)))
|
||||
|
||||
(defn color?
|
||||
[color]
|
||||
(and (string? color)
|
||||
(gcolor/isValidColor color)))
|
||||
|
||||
(defn parse-color
|
||||
[color]
|
||||
(when (color? color)
|
||||
(let [result (gcolor/parse color)]
|
||||
(dm/str (.-hex ^js result)))))
|
||||
|
||||
(def color-names
|
||||
(obj/get-keys ^js gcolor/names))
|
||||
|
||||
(def empty-color
|
||||
(into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity]))
|
||||
|
||||
(defn next-rgb
|
||||
"Given a color in rgb returns the next color"
|
||||
[[r g b]]
|
||||
(cond
|
||||
(and (= 255 r) (= 255 g) (= 255 b))
|
||||
(throw (ex-info "cannot get next color" {:r r :g g :b b}))
|
||||
|
||||
(and (= 255 g) (= 255 b))
|
||||
[(inc r) 0 0]
|
||||
|
||||
(= 255 b)
|
||||
[r (inc g) 0]
|
||||
|
||||
:else
|
||||
[r g (inc b)]))
|
||||
|
||||
(defn get-color-name
|
||||
[color]
|
||||
(or (:color-library-name color)
|
||||
|
|
|
@ -6,15 +6,15 @@
|
|||
|
||||
(ns app.util.import.parser
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.svg.path :as svg.path]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.color :as uc]
|
||||
[app.util.json :as json]
|
||||
[app.util.path.parser :as upp]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(def url-regex
|
||||
|
@ -278,7 +278,7 @@
|
|||
|
||||
(defn parse-path
|
||||
[props center svg-data]
|
||||
(let [content (upp/parse-path (:d svg-data))]
|
||||
(let [content (svg.path/parse (:d svg-data))]
|
||||
(-> props
|
||||
(assoc :content content)
|
||||
(assoc :center center))))
|
||||
|
@ -454,7 +454,7 @@
|
|||
:fill-color nil
|
||||
:fill-opacity nil)
|
||||
|
||||
(uc/hex? fill)
|
||||
(cc/valid-hex-color? fill)
|
||||
(assoc :fill-color fill
|
||||
:fill-opacity (-> svg-data (:fill-opacity "1") d/parse-double))
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
(ns app.util.path.format
|
||||
(:require
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.path.subpaths :refer [pt=]]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[app.common.svg.path.subpath :refer [pt=]]
|
||||
[app.util.array :as arr]))
|
||||
|
||||
(def path-precision 3)
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.path :as upg]
|
||||
[app.common.path.commands :as upc]
|
||||
[app.common.svg.path.command :as upc]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defn remove-line-curves
|
||||
|
|
|
@ -7,9 +7,13 @@
|
|||
(ns frontend-tests.helpers.pages
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.libraries-helpers :as cflh]
|
||||
[app.common.files.shapes-helpers :as cfsh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.files.libraries-helpers :as cflh]
|
||||
[app.common.files.shapes-helpers :as cfsh]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
|
@ -113,7 +117,7 @@
|
|||
(if (empty? shapes)
|
||||
state
|
||||
(let [[frame changes]
|
||||
(dwsh/prepare-create-artboard-from-selection changes
|
||||
(cfsh/prepare-create-artboard-from-selection changes
|
||||
nil
|
||||
nil
|
||||
(:objects page)
|
||||
|
@ -133,14 +137,14 @@
|
|||
shapes (dwg/shapes-for-grouping objects shape-ids)
|
||||
|
||||
[group component-id changes]
|
||||
(dwlh/generate-add-component nil
|
||||
(cflh/generate-add-component nil
|
||||
shapes
|
||||
(:objects page)
|
||||
(:id page)
|
||||
current-file-id
|
||||
true
|
||||
dwg/prepare-create-group
|
||||
dwsh/prepare-create-artboard-from-selection)]
|
||||
cfsh/prepare-create-artboard-from-selection)]
|
||||
|
||||
(swap! idmap assoc instance-label (:id group)
|
||||
component-label component-id)
|
||||
|
|
|
@ -201,7 +201,9 @@
|
|||
(t/is (= (:shape-ref c-shape1) nil))
|
||||
(t/is (= (:name c-shape2) "Rect 2"))
|
||||
(t/is (= (:touched c-shape2) nil))
|
||||
(t/is (= (:shape-ref c-shape2) nil)))))]
|
||||
(t/is (= (:shape-ref c-shape2) nil))
|
||||
|
||||
)))]
|
||||
|
||||
(ptk/emit!
|
||||
store
|
||||
|
|
Loading…
Reference in a new issue