diff --git a/common/src/app/common/svg/path/Parser.java b/common/src/app/common/svg/path/Parser.java index cfceaf9443..dec9ff00fa 100644 --- a/common/src/app/common/svg/path/Parser.java +++ b/common/src/app/common/svg/path/Parser.java @@ -1,3 +1,11 @@ +/** + * Performance focused pure java implementation of the + * SVG path parser. + * + * @author KALEIDOS INC + * @license MPL-2.0 + */ + package app.common.svg.path; import java.util.Arrays; @@ -61,9 +69,11 @@ public class Parser { command = MOVE_TO; params = new Object[] {K_X, this.params[0], K_Y, this.params[1]}; break; + case 'Z': command = CLOSE_PATH; break; + case 'L': command = LINE_TO; params = new Object[] {K_X, this.params[0], K_Y, this.params[1]}; @@ -781,16 +791,6 @@ public class Parser { var segments = arcToBeziers(currentX, currentY, x, y, fa, fs, rx, ry, phi); result.addAll(segments); - - // if (rx == 0 || ry == 0) { - // segment.command = 'C'; - // segment.params = new double[] {currentX, currentY, x, y, x, y}; - // result.add(segment); - // } else if (currentX != x || currentY != y) { - // var segments = arcToBeziers(currentX, currentY, x, y, fa, fs, rx, ry, phi); - // result.addAll(segments); - // } - currentX = x; currentY = y; @@ -871,7 +871,6 @@ public class Parser { } private static void processCurve(double[] curve, double cx, double cy, double rx, double ry, double sinPhi, double cosPhi) { - double x0 = curve[0] * rx; double y0 = curve[1] * ry; double x1 = curve[2] * rx; @@ -911,7 +910,13 @@ public class Parser { double x1p = ((cosPhi * (x1 - x2)) / 2) + ((sinPhi * (y1 - y2)) / 2); double y1p = ((-sinPhi * (x1 - x2)) / 2) + ((cosPhi * (y1 - y2)) / 2); - if (x1p == 0 || y1p == 0 || rx == 0 || ry == 0) { + if (x1p == 0 && y1p == 0) { + // we're asked to draw line to itself + return new ArrayList<>(); + } + + if (rx == 0 || ry == 0) { + // one of the radii is zero return new ArrayList<>(); } diff --git a/common/test/common_tests/arc_to_bezier.js b/common/src/app/common/svg/path/arc_to_bezier.js similarity index 93% rename from common/test/common_tests/arc_to_bezier.js rename to common/src/app/common/svg/path/arc_to_bezier.js index bc2c1a843a..39dc8d447f 100644 --- a/common/test/common_tests/arc_to_bezier.js +++ b/common/src/app/common/svg/path/arc_to_bezier.js @@ -2,7 +2,8 @@ * Arc to Bezier curves transformer * * Is a modified and google closure compatible version of the a2c - * functions by https://github.com/fontello/svgpath + * functions by https://github.com/fontello/svgpath used as reference + * implementation for tests * * @author KALEIDOS INC * @license MIT License @@ -10,11 +11,11 @@ "use strict"; -goog.provide("common_tests.arc_to_bezier"); +goog.provide("app.common.svg.path.arc_to_bezier"); // https://raw.githubusercontent.com/fontello/svgpath/master/lib/a2c.js goog.scope(function() { - const self = common_tests.arc_to_bezier; + const self = app.common.svg.path.arc_to_bezier; var TAU = Math.PI * 2; @@ -123,7 +124,7 @@ goog.scope(function() { 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) { + function calculate_beziers(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); @@ -132,6 +133,8 @@ goog.scope(function() { var x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2; var y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2; + // console.log("L", x1p, y1p) + if (x1p === 0 && y1p === 0) { // we're asked to draw line to itself return []; @@ -204,5 +207,5 @@ goog.scope(function() { }); } - self.a2c = a2c; + self.calculateBeziers = calculate_beziers; }); diff --git a/common/src/app/common/svg/path/legacy_parser1.cljs b/common/src/app/common/svg/path/legacy_parser1.cljs new file mode 100644 index 0000000000..70acc58242 --- /dev/null +++ b/common/src/app/common/svg/path/legacy_parser1.cljs @@ -0,0 +1,325 @@ +;; 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.path.legacy-parser1 + "The first SVG Path parser implementation. + + Written in a mix of CLJS and JS code and used in production until + 1.19, used mainly for tests." + (:require + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.shapes.path :as upg] + [app.common.svg :as csvg] + [app.common.svg.path.arc-to-bezier :as a2b] + [app.common.svg.path.command :as upc] + [cuerdas.core :as str])) + +(def commands-regex #"(?i)[mzlhvcsqta][^mzlhvcsqta]*") + +;; 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 + current {} + remain (-> cmd-str (subs 1) (str/trim))] + + (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 [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)))))) + +;; Path specification +;; https://www.w3.org/TR/SVG11/paths.html +(defmulti parse-command (comp str/upper first)) + +(defmethod parse-command "M" [cmd] + (let [relative (str/starts-with? cmd "m") + param-list (extract-params cmd [[:x :number] + [:y :number]])] + + (into [{:command :move-to + :relative relative + :params (first param-list)}] + + (for [params (rest param-list)] + {:command :line-to + :relative relative + :params params})))) + +(defmethod parse-command "Z" [_] + [{:command :close-path}]) + +(defmethod parse-command "L" [cmd] + (let [relative (str/starts-with? cmd "l") + param-list (extract-params cmd [[:x :number] + [:y :number]])] + (for [params param-list] + {:command :line-to + :relative relative + :params params}))) + +(defmethod parse-command "H" [cmd] + (let [relative (str/starts-with? cmd "h") + param-list (extract-params cmd [[:value :number]])] + (for [params param-list] + {:command :line-to-horizontal + :relative relative + :params params}))) + +(defmethod parse-command "V" [cmd] + (let [relative (str/starts-with? cmd "v") + param-list (extract-params cmd [[:value :number]])] + (for [params param-list] + {:command :line-to-vertical + :relative relative + :params params}))) + +(defmethod parse-command "C" [cmd] + (let [relative (str/starts-with? cmd "c") + param-list (extract-params cmd [[:c1x :number] + [:c1y :number] + [:c2x :number] + [:c2y :number] + [:x :number] + [:y :number]]) + ] + (for [params param-list] + {:command :curve-to + :relative relative + :params params}))) + +(defmethod parse-command "S" [cmd] + (let [relative (str/starts-with? cmd "s") + param-list (extract-params cmd [[:cx :number] + [:cy :number] + [:x :number] + [:y :number]])] + (for [params param-list] + {:command :smooth-curve-to + :relative relative + :params params}))) + +(defmethod parse-command "Q" [cmd] + (let [relative (str/starts-with? cmd "q") + param-list (extract-params cmd [[:cx :number] + [:cy :number] + [:x :number] + [:y :number]])] + (for [params param-list] + {:command :quadratic-bezier-curve-to + :relative relative + :params params}))) + +(defmethod parse-command "T" [cmd] + (let [relative (str/starts-with? cmd "t") + param-list (extract-params cmd [[:x :number] + [:y :number]])] + (for [params param-list] + {:command :smooth-quadratic-bezier-curve-to + :relative relative + :params params}))) + +(defmethod parse-command "A" [cmd] + (let [relative (str/starts-with? cmd "a") + param-list (extract-params cmd [[:rx :number] + [:ry :number] + [:x-axis-rotation :number] + [:large-arc-flag :flag] + [:sweep-flag :flag] + [:x :number] + [:y :number]])] + (for [params param-list] + {:command :elliptical-arc + :relative relative + :params params}))) + +(defn smooth->curve + [{:keys [params]} pos handler] + (let [{c1x :x c1y :y} (upg/calculate-opposite-handler pos handler)] + {:c1x c1x + :c1y c1y + :c2x (:cx params) + :c2y (:cy params)})) + +(defn quadratic->curve + [sp ep cp] + (let [cp1 (-> (gpt/to-vec sp cp) + (gpt/scale (/ 2 3)) + (gpt/add sp)) + + cp2 (-> (gpt/to-vec ep cp) + (gpt/scale (/ 2 3)) + (gpt/add ep))] + + {:c1x (:x cp1) + :c1y (:y cp1) + :c2x (:x cp2) + :c2y (:y cp2)})) + +(defn arc->beziers* + [from-x from-y x y large-arc-flag sweep-flag rx ry x-axis-rotation] + (a2b/calculateBeziers from-x from-y x y large-arc-flag sweep-flag rx ry x-axis-rotation)) + +(defn arc->beziers [from-p command] + (let [to-command + (fn [[_ _ c1x c1y c2x c2y x y]] + {:command :curve-to + :relative (:relative command) + :params {:c1x c1x :c1y c1y + :c2x c2x :c2y c2y + :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 (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 + "Removes some commands and convert relative to absolute coordinates" + [commands] + (let [simplify-command + ;; prev-pos : previous position for the current path. Necessary for relative commands + ;; prev-start : previous move-to necessary for Z commands + ;; prev-cc : previous command control point for cubic beziers + ;; 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) + (-> (assoc :relative false) + (d/update-in-when [:params :c1x] + (:x prev-pos)) + (d/update-in-when [:params :c1y] + (:y prev-pos)) + + (d/update-in-when [:params :c2x] + (:x prev-pos)) + (d/update-in-when [:params :c2y] + (:y prev-pos)) + + (d/update-in-when [:params :cx] + (:x prev-pos)) + (d/update-in-when [:params :cy] + (:y prev-pos)) + + (d/update-in-when [:params :x] + (:x prev-pos)) + (d/update-in-when [:params :y] + (:y prev-pos)) + + (cond-> + (= :line-to-horizontal (:command command)) + (d/update-in-when [:params :value] + (:x prev-pos)) + + (= :line-to-vertical (:command command)) + (d/update-in-when [:params :value] + (:y prev-pos))))) + + params (:params command) + orig-command command + + command + (cond-> command + (= :line-to-horizontal (:command command)) + (-> (assoc :command :line-to) + (update :params dissoc :value) + (assoc-in [:params :x] (:value params)) + (assoc-in [:params :y] (:y prev-pos))) + + (= :line-to-vertical (:command command)) + (-> (assoc :command :line-to) + (update :params dissoc :value) + (assoc-in [:params :y] (:value params)) + (assoc-in [:params :x] (:x prev-pos))) + + (= :smooth-curve-to (:command command)) + (-> (assoc :command :curve-to) + (update :params dissoc :cx :cy) + (update :params merge (smooth->curve command prev-pos prev-cc))) + + (= :quadratic-bezier-curve-to (:command command)) + (-> (assoc :command :curve-to) + (update :params dissoc :cx :cy) + (update :params merge (quadratic->curve prev-pos (gpt/point params) (gpt/point (:cx params) (:cy params))))) + + (= :smooth-quadratic-bezier-curve-to (:command command)) + (-> (assoc :command :curve-to) + (update :params merge (quadratic->curve prev-pos (gpt/point params) (upg/calculate-opposite-handler prev-pos prev-qc))))) + + result (if (= :elliptical-arc (:command command)) + (into result (arc->beziers prev-pos command)) + (conj result command)) + + next-cc (case (:command orig-command) + :smooth-curve-to + (gpt/point (get-in orig-command [:params :cx]) (get-in orig-command [:params :cy])) + + :curve-to + (gpt/point (get-in orig-command [:params :c2x]) (get-in orig-command [:params :c2y])) + + (:line-to-horizontal :line-to-vertical) + (gpt/point (get-in command [:params :x]) (get-in command [:params :y])) + + (gpt/point (get-in orig-command [:params :x]) (get-in orig-command [:params :y]))) + + next-qc (case (:command orig-command) + :quadratic-bezier-curve-to + (gpt/point (get-in orig-command [:params :cx]) (get-in orig-command [:params :cy])) + + :smooth-quadratic-bezier-curve-to + (upg/calculate-opposite-handler prev-pos prev-qc) + + (gpt/point (get-in orig-command [:params :x]) (get-in orig-command [:params :y]))) + + next-pos (if (= :close-path (:command command)) + prev-start + (upc/command->point prev-pos command)) + + next-start (if (= :move-to (:command command)) next-pos prev-start)] + + [result next-pos next-start next-cc next-qc])) + + start (first commands) + start (cond-> start + (:relative start) + (assoc :relative false)) + + start-pos (gpt/point (:params start))] + + (->> (map vector (rest commands) commands) + (reduce simplify-command [[start] start-pos start-pos start-pos start-pos]) + (first)))) + +(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)] + (-> (mapcat parse-command commands) + (simplify-commands))))) + diff --git a/common/src/app/common/svg/path/legacy.cljc b/common/src/app/common/svg/path/legacy_parser2.cljc similarity index 97% rename from common/src/app/common/svg/path/legacy.cljc rename to common/src/app/common/svg/path/legacy_parser2.cljc index 5949059064..fab3f8102e 100644 --- a/common/src/app/common/svg/path/legacy.cljc +++ b/common/src/app/common/svg/path/legacy_parser2.cljc @@ -4,9 +4,11 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.common.svg.path.legacy - "The first svg path parser implementation in pure clojure, used as reference impl - and for tests." +(ns app.common.svg.path.legacy-parser2 + "The second SVG Path parser implementation. + + Written in crossplatform CLJC code. Used meanwhile a hight + performance parser is developed in the 1.20 version." (:require [app.common.data :as d] [app.common.geom.point :as gpt] @@ -16,7 +18,6 @@ [app.common.svg.path.command :as upc] [cuerdas.core :as str])) - (def commands-regex #"(?i)[mzlhvcsqta][^mzlhvcsqta]*") (def regex #"[+-]?(\d+(\.\d+)?|\.\d+)(e[+-]?\d+)?") @@ -296,10 +297,10 @@ y1p (+ (/ (* (- sin-phi) (- x1 x2)) 2) (/ (* cos-phi (- y1 y2)) 2))] - (if (or (zero? x1p) - (zero? y1p) - (zero? rx) - (zero? ry)) + (if (or (and (zero? x1p) + (zero? y1p)) + (and (zero? rx) + (zero? ry))) [] (let [ rx (mth/abs rx) @@ -462,19 +463,10 @@ (reduce simplify-command [[start] start-pos start-pos start-pos start-pos]) (first)))) - (defn parse [path-str] (if (empty? path-str) path-str (let [commands (re-seq commands-regex path-str)] (->> (mapcat parse-command commands) - (simplify-commands) - (map (fn [segment] - ;; (prn "LEGACY:" segment) - segment)))))) - - - - - + (simplify-commands))))) diff --git a/common/src/app/common/svg/path/parser.js b/common/src/app/common/svg/path/parser.js index d156d6ca21..804112f073 100644 --- a/common/src/app/common/svg/path/parser.js +++ b/common/src/app/common/svg/path/parser.js @@ -1,3 +1,11 @@ +/** + * Performance focused pure javascript implementation of the + * SVG path parser. + * + * @author KALEIDOS INC + * @license MPL-2.0 + */ + import cljs from "goog:cljs.core"; const MOVE_TO = cljs.keyword("move-to"); @@ -674,7 +682,13 @@ export function arcToBeziers(x1, y1, x2, y2, fa, fs, rx, ry, phi) { let x1p = (cosPhi * (x1 - x2)) / 2 + (sinPhi * (y1 - y2)) / 2; let y1p = (-sinPhi * (x1 - x2)) / 2 + (cosPhi * (y1 - y2)) / 2; - if (x1p === 0 || y1p === 0 || rx === 0 || ry === 0) { + 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 []; } @@ -877,6 +891,7 @@ function simplifyPathData(pdata) { currentX = x; currentY = y; } else if (currentX !== x || currentY !== y) { + var segments = arcToBeziers(currentX, currentY, x, y, fa, fs, rx, ry, phi); result.push(...segments); diff --git a/common/src/app/common/svg/shapes_builder.cljc b/common/src/app/common/svg/shapes_builder.cljc index 632b1483d8..6cb5429aa6 100644 --- a/common/src/app/common/svg/shapes_builder.cljc +++ b/common/src/app/common/svg/shapes_builder.cljc @@ -229,6 +229,7 @@ :svg-viewbox selrect :svg-attrs attrs :svg-transform transform + :strokes [] :fills []}) (gsh/translate-to-frame origin))))) @@ -355,9 +356,9 @@ (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) + [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 @@ -384,6 +385,7 @@ (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) @@ -422,7 +424,8 @@ (dissoc :stroke) (dissoc :strokeLinecap) (dissoc :strokeWidth) - (dissoc :strokeOpacity)))))] + (dissoc :strokeOpacity)))) + (d/without-nils))] (cond-> (assoc shape :svg-attrs attrs) (some? color) @@ -434,7 +437,7 @@ (and (some? color) (some? width)) (assoc-in [:strokes 0 :stroke-width] width) - (and (some? linecap) (= (:type shape) :path) + (and (some? linecap) (cfh/path-shape? shape) (or (= linecap :round) (= linecap :square))) (assoc :stroke-cap-start linecap :stroke-cap-end linecap) @@ -464,9 +467,6 @@ (-> (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] diff --git a/common/target/classes/app/common/svg/path/Parser$ParserImpl.class b/common/target/classes/app/common/svg/path/Parser$ParserImpl.class index eb42edaca5..e146fff62f 100644 Binary files a/common/target/classes/app/common/svg/path/Parser$ParserImpl.class and b/common/target/classes/app/common/svg/path/Parser$ParserImpl.class differ diff --git a/common/target/classes/app/common/svg/path/Parser$Segment.class b/common/target/classes/app/common/svg/path/Parser$Segment.class index 646d3b8036..29c2be1117 100644 Binary files a/common/target/classes/app/common/svg/path/Parser$Segment.class and b/common/target/classes/app/common/svg/path/Parser$Segment.class differ diff --git a/common/target/classes/app/common/svg/path/Parser.class b/common/target/classes/app/common/svg/path/Parser.class index 13d8402cf5..351b04b772 100644 Binary files a/common/target/classes/app/common/svg/path/Parser.class and b/common/target/classes/app/common/svg/path/Parser.class differ diff --git a/common/test/common_tests/svg_path_test.cljc b/common/test/common_tests/svg_path_test.cljc index 3fb89c432d..19140f91e4 100644 --- a/common/test/common_tests/svg_path_test.cljc +++ b/common/test/common_tests/svg_path_test.cljc @@ -10,9 +10,9 @@ [app.common.pprint :as pp] [app.common.math :as mth] [app.common.svg.path :as svg.path] - [app.common.svg.path.legacy :as svg.path.legacy] + [app.common.svg.path.legacy-parser2 :as svg.path.legacy2] [clojure.test :as t] - #?(:cljs [common-tests.arc-to-bezier :as impl]))) + #?(:cljs [app.common.svg.path.legacy-parser2 :as svg.path.legacy1]))) (t/deftest parse-test-1 (let [data (str "m -994.563 4564.1423 149.3086 -52.8821 30.1828 " @@ -23,14 +23,25 @@ result1 (->> (svg.path/parse data) (mapv (fn [entry] (update entry :params #(into (sorted-map) %))))) - result2 (->> (svg.path.legacy/parse data) + result2 (->> (svg.path.legacy2/parse data) (mapv (fn [entry] - (update entry :params #(into (sorted-map) %)))))] + (update entry :params #(into (sorted-map) %))))) + + result3 #?(:cljs (->> (svg.path.legacy1/parse data) + (mapv (fn [entry] + (update entry :params #(into (sorted-map) %))))) + :clj nil)] (t/is (= 15 (count result1) (count result2))) + + #?(:cljs + (t/is (= 15 + (count result1) + (count result3)))) + (dotimes [i (count result1)] (let [item1 (nth result1 i) item2 (nth result2 i)] @@ -40,6 +51,14 @@ (t/is (= (:params item1) (:params item2))) + #?(:cljs + (let [item3 (nth result3 i)] + (t/is (= (:command item1) + (:command item3))) + (t/is (= (:params item1) + (:params item3))))) + + #_(println "------------------------") #_(pp/pprint (dissoc item1 :relative)) #_(pp/pprint (dissoc item2 :prev-pos :relative)))))) @@ -92,7 +111,7 @@ result1 (->> (svg.path/parse data) (mapv (fn [entry] (update entry :params #(into (sorted-map) %))))) - result2 (->> (svg.path.legacy/parse data) + result2 (->> (svg.path.legacy2/parse data) (mapv (fn [entry] (update entry :params #(into (sorted-map) %)))))] @@ -108,7 +127,6 @@ (t/is (= (:command item1) (:command item2))) - ;; (println "================" (:command item1)) ;; (pp/pprint (:params item1)) ;; (println "---------") @@ -124,7 +142,7 @@ result1 (->> (svg.path/parse data) (mapv (fn [entry] (update entry :params #(into (sorted-map) %))))) - result2 (->> (svg.path.legacy/parse data) + result2 (->> (svg.path.legacy2/parse data) (mapv (fn [entry] (update entry :params #(into (sorted-map) %)))))] @@ -203,7 +221,7 @@ result1 (->> (svg.path/parse data) (mapv (fn [entry] (update entry :params #(into (sorted-map) %))))) - result2 (->> (svg.path.legacy/parse data) + result2 (->> (svg.path.legacy2/parse data) (mapv (fn [entry] (update entry :params #(into (sorted-map) %)))))] @@ -256,7 +274,7 @@ result1 (->> (svg.path/parse data) (mapv (fn [entry] (update entry :params #(into (sorted-map) %))))) - result2 (->> (svg.path.legacy/parse data) + result2 (->> (svg.path.legacy2/parse data) (mapv (fn [entry] (update entry :params #(into (sorted-map) %)))))] @@ -279,6 +297,61 @@ (t/is (mth/close? v (get-in item2 [:params k]) 0.000000001)) ))))) +(t/deftest parse-test-6 + (let [data (str "M3.078 3.548v16.9a.5.5 0 0 0 1 0v-16.9a.5.5 0 0 0-1 0ZM18.422 11.5" + "H7.582a2.5 2.5 0 0 1-2.5-2.5V6.565a2.5 2.5 0 0 1 2.5-2.5" + "h10.84a2.5 2.5 0 0 1 2.5 2.5V9a2.5 2.5 0 0 1-2.5 2.5Z" + "M7.582 5.065a1.5 1.5 0 0 0-1.5 1.5V9a1.5 1.5 0 0 0 1.5 1.5" + "h10.84a1.5 1.5 0 0 0 1.5-1.5V6.565a1.5 1.5 0 0 0-1.5-1.5Z" + "M13.451 19.938H7.582a2.5 2.5 0 0 1-2.5-2.5V15" + "a2.5 2.5 0 0 1 2.5-2.5h5.869a2.5 2.5 0 0 1 2.5 2.5v2.436" + "a2.5 2.5 0 0 1-2.5 2.502ZM7.582 13.5a1.5 1.5 0 0 0-1.5 1.5v2.436" + "a1.5 1.5 0 0 0 1.5 1.5h5.869a1.5 1.5 0 0 0 1.5-1.5V15" + "a1.5 1.5 0 0 0-1.5-1.5Z") + + result1 (->> (svg.path/parse data) + (mapv (fn [entry] + (update entry :params #(into (sorted-map) %))))) + result2 (->> (svg.path.legacy2/parse data) + (mapv (fn [entry] + (update entry :params #(into (sorted-map) %)))))] + + (t/is (= 47 + (count result1) + (count result2))) + + ;; (pp/pprint result1 {:length 100}) + ;; (pp/pprint result2 {:length 50}) + + (dotimes [i (count result1)] + (let [item1 (nth result1 i) + item2 (nth result2 i) + ] + + (t/is (= (:command item1) + (:command item2))) + + (doseq [[k v] (:params item1)] + (t/is (mth/close? v (get-in item2 [:params k]) 0.000000001)) + ))) + + #?(:cljs + (let [result3 (svg.path.legacy1/parse data)] + (t/is (= 47 + (count result1) + (count result3))) + + (dotimes [i (count result1)] + (let [item1 (nth result1 i) + item3 (nth result2 i)] + + (t/is (= (:command item1) + (:command item3))) + + (t/is (= (:params item1) + (:params item3))))))))) + + (t/deftest arc-to-bezier-1 (let [expected1 [-1.6697754290362354e-13 -5.258016244624741e-13 @@ -316,7 +389,7 @@ (nth expected2 (+ i 2)) 0.0000000001)))) - (let [[result1 result2 :as total] (svg.path.legacy/arc->beziers* 0 0 30 50 0 0 1 162.55 162.45)] + (let [[result1 result2 :as total] (svg.path.legacy2/arc->beziers* 0 0 30 50 0 0 1 162.55 162.45)] (t/is (= (count total) 2)) (dotimes [i (count result1)] @@ -327,7 +400,96 @@ (dotimes [i (count result2)] (t/is (mth/close? (nth result2 i) (nth expected2 i) - 0.000000000001)))))) + 0.000000000001)))) + + #?(:cljs + (let [[result1 result2 :as total] (svg.path.legacy1/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))))) + + )) + +(t/deftest arc-to-bezier-2 + (let [expected1 [3.0779999999999994, + 20.448, + 3.0780000082296834, + 20.724142369096132, + 3.3018576309038683, + 20.94799998509884, + 3.5779999999999994, + 20.94799998509884] + + expected2 [3.5779999999999994, + 20.94799998509884, + 3.854142369096131, + 20.94799998509884, + 4.077999991770315, + 20.724142369096132, + 4.077999999999999, + 20.448]] + + (let [[result1 result2 :as total] (->> (svg.path/arc->beziers 3.078 20.448 4.077999999999999 20.448 0 0 0.5 0.5 0) + (mapv (fn [segment] + (vec (.-params segment)))))] + (t/is (= (count total) 2)) + ;; (println "================" 11111111) + ;; (pp/pprint expected1 {:width 50}) + ;; (println "------------") + ;; (pp/pprint result1 {:width 50}) + + (dotimes [i (count result1)] + (t/is (mth/close? (nth result1 i) + (nth expected1 (+ i 2)) + 0.0000000001))) + + (dotimes [i (count result2)] + (t/is (mth/close? (nth result2 i) + (nth expected2 (+ i 2)) + 0.0000000001)))) + + (let [[result1 result2 :as total] (svg.path.legacy2/arc->beziers* 3.078 20.448 4.077999999999999 20.448 0 0 0.5 0.5 0)] + (t/is (= (count total) 2)) + + ;; (println "================" 11111111) + ;; (pp/pprint expected1 {:width 50}) + ;; (println "------------") + ;; (pp/pprint (vec result1) {:width 50}) + + (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)))) + + #?(:cljs + (let [[result1 result2 :as total] (svg.path.legacy1/arc->beziers* 3.078 20.448 4.077999999999999 20.448 0 0 0.5 0.5 0)] + (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))))) + + )) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -357,14 +519,14 @@ "59.9137 -301.293 -1.0595 -51.375 25.7186 -261.0492 -7.706 ") pattern [[:x :number] [:y :number]]] - (t/is (= expected (svg.path.legacy/extract-params cmdstr pattern))))) + (t/is (= expected (svg.path.legacy2/extract-params cmdstr pattern))))) (t/deftest extract-params-legacy-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.legacy/extract-params cmdstr pattern))))) + (t/is (= expected (svg.path.legacy2/extract-params cmdstr pattern))))) (t/deftest extract-params-legacy-3 (let [cmdstr (str "a1.42 1.42 0 00-1.415-1.416 1.42 1.42 0 00-1.416 1.416 " @@ -382,7 +544,7 @@ [:sweep-flag :flag] [:x :number] [:y :number]] - result (svg.path.legacy/extract-params cmdstr pattern)] + result (svg.path.legacy2/extract-params cmdstr pattern)] (t/is (= (nth result 0) (nth expected 0))) diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index 205f83a6e4..8bd9120479 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -21,6 +21,9 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) +;; FIXME: this clearly should be renamed to something different, this +;; namespace has also fill related code + (mf/defc inner-stroke-clip-path {::mf/wrap-props false} [{:keys [shape render-id index]}] @@ -449,10 +452,8 @@ (obj/unset! style "fillOpacity") (obj/set! props "fill" (dm/fmt "url(#fill-%-%)" position render-id))) - (and ^boolean (or (obj/contains? svg-styles "fill") - (obj/contains? svg-styles "fillOpacity")) + (and ^boolean (some? svg-styles) ^boolean (obj/contains? svg-styles "fill")) - (let [fill (obj/get svg-styles "fill") opacity (obj/get svg-styles "fillOpacity")] (when (some? fill) @@ -460,8 +461,7 @@ (when (some? opacity) (obj/set! style "fillOpacity" opacity))) - (and ^boolean (or (obj/contains? svg-attrs "fill") - (obj/contains? svg-attrs "fillOpacity")) + (and ^boolean (some? svg-attrs) ^boolean (empty? shape-fills)) (let [fill (obj/get svg-attrs "fill") opacity (obj/get svg-attrs "fillOpacity")] @@ -562,13 +562,6 @@ (mf/defc shape-custom-strokes {::mf/wrap-props false} [props] - (let [children (unchecked-get props "children") - shape (unchecked-get props "shape") - position (unchecked-get props "position") - render-id (unchecked-get props "render-id") - props #js {:shape shape - :position position - :render-id render-id}] - [:* - [:> shape-fills props children] - [:> shape-strokes props children]])) + [:* + [:> shape-fills props] + [:> shape-strokes props]]) diff --git a/frontend/src/app/main/ui/shapes/fills.cljs b/frontend/src/app/main/ui/shapes/fills.cljs index dca1f78a9c..cea64f7ca3 100644 --- a/frontend/src/app/main/ui/shapes/fills.cljs +++ b/frontend/src/app/main/ui/shapes/fills.cljs @@ -17,7 +17,7 @@ (def no-repeat-padding 1.05) -(mf/defc fills* +(mf/defc internal-fills {::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") @@ -120,7 +120,6 @@ (mf/defc fills {::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") type (dm/get-prop shape :type) image (:fill-image shape) @@ -132,4 +131,4 @@ (> (count fills) 1) (some :fill-color-gradient fills) (some :fill-image fills)) - [:> fills* props]))) + [:> internal-fills props])))