mirror of
https://github.com/penpot/penpot.git
synced 2025-01-09 16:30:37 -05:00
✨ Merge closing subpaths for seamless paths
This commit is contained in:
parent
3583eb6aa9
commit
2184286a78
2 changed files with 156 additions and 9 deletions
|
@ -7,13 +7,9 @@
|
||||||
(ns app.util.path.format
|
(ns app.util.path.format
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.geom.point :as gpt]
|
[app.util.path.commands :as upc]
|
||||||
[app.common.geom.shapes.path :as gshp]
|
|
||||||
[app.util.svg :as usvg]
|
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[clojure.set :as set]
|
[app.util.path.subpaths :as ups]))
|
||||||
[app.common.math :as mth]
|
|
||||||
))
|
|
||||||
|
|
||||||
(defn command->param-list [command]
|
(defn command->param-list [command]
|
||||||
(let [params (:params command)]
|
(let [params (:params command)]
|
||||||
|
@ -69,6 +65,22 @@
|
||||||
|
|
||||||
|
|
||||||
(defn format-path [content]
|
(defn format-path [content]
|
||||||
(->> content
|
(let [content (ups/close-subpaths content)]
|
||||||
(mapv command->string)
|
(loop [result ""
|
||||||
(str/join "")))
|
last-move nil
|
||||||
|
current (first content)
|
||||||
|
content (rest content)]
|
||||||
|
|
||||||
|
(if (some? current)
|
||||||
|
(let [point (upc/command->point current)
|
||||||
|
current-move? (= :move-to (:command current))
|
||||||
|
result (str result (command->string current))
|
||||||
|
result (if (and (not current-move?) (= last-move point))
|
||||||
|
(str result "Z")
|
||||||
|
result)
|
||||||
|
last-move (if current-move? point last-move)]
|
||||||
|
(recur result
|
||||||
|
last-move
|
||||||
|
(first content)
|
||||||
|
(rest content)))
|
||||||
|
result))))
|
||||||
|
|
135
frontend/src/app/util/path/subpaths.cljs
Normal file
135
frontend/src/app/util/path/subpaths.cljs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.util.path.subpaths
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.util.path.commands :as upc]))
|
||||||
|
|
||||||
|
(defn make-subpath
|
||||||
|
"Creates a subpath either from a single command or with all the data"
|
||||||
|
([command]
|
||||||
|
(let [p (upc/command->point command)]
|
||||||
|
(make-subpath p p [command])))
|
||||||
|
([from to data]
|
||||||
|
{:from from
|
||||||
|
:to to
|
||||||
|
:data data}))
|
||||||
|
|
||||||
|
(defn add-subpath-command
|
||||||
|
"Adds a command to the subpath"
|
||||||
|
[subpath command]
|
||||||
|
(let [p (upc/command->point command)]
|
||||||
|
(-> subpath
|
||||||
|
(assoc :to p)
|
||||||
|
(update :data conj command))))
|
||||||
|
|
||||||
|
(defn reverse-command
|
||||||
|
"Reverses a single command"
|
||||||
|
[command prev]
|
||||||
|
|
||||||
|
(let [{:keys [x y]} (:params prev)
|
||||||
|
{:keys [c1x c1y c2x c2y]} (:params command)]
|
||||||
|
|
||||||
|
(-> command
|
||||||
|
(update :params assoc :x x :y y)
|
||||||
|
|
||||||
|
(cond-> (= :curve-to (:command command))
|
||||||
|
(update :params assoc
|
||||||
|
:c1x c2x :c1y c2y
|
||||||
|
:c2x c1x :c2y c1y)))))
|
||||||
|
|
||||||
|
(defn reverse-subpath
|
||||||
|
"Reverses a subpath starting with move-to"
|
||||||
|
[subpath]
|
||||||
|
|
||||||
|
(let [reverse-commands
|
||||||
|
(fn [result [command prev]]
|
||||||
|
(if (some? prev)
|
||||||
|
(conj result (reverse-command command prev))
|
||||||
|
result))
|
||||||
|
|
||||||
|
new-data (->> subpath :data d/with-prev reverse
|
||||||
|
(reduce reverse-commands [(upc/make-move-to (:to subpath))]))]
|
||||||
|
|
||||||
|
(make-subpath (:to subpath) (:from subpath) new-data)))
|
||||||
|
|
||||||
|
(defn get-subpaths
|
||||||
|
"Retrieves every subpath inside the current content"
|
||||||
|
[content]
|
||||||
|
(let [reduce-subpath
|
||||||
|
(fn [subpaths current]
|
||||||
|
(let [is-move? (= :move-to (:command current))
|
||||||
|
last-idx (dec (count subpaths))]
|
||||||
|
(if is-move?
|
||||||
|
(conj subpaths (make-subpath current))
|
||||||
|
(update subpaths last-idx add-subpath-command current))))]
|
||||||
|
(->> content
|
||||||
|
(reduce reduce-subpath []))))
|
||||||
|
|
||||||
|
(defn subpaths-join
|
||||||
|
"Join two subpaths together when the first finish where the second starts"
|
||||||
|
[subpath other]
|
||||||
|
(assert (= (:to subpath) (:from other)))
|
||||||
|
(-> subpath
|
||||||
|
(update :data d/concat (rest (:data other)))
|
||||||
|
(assoc :to (:to other))))
|
||||||
|
|
||||||
|
(defn- merge-paths
|
||||||
|
"Tries to merge into candidate the subpaths. Will return the candidate with the subpaths merged
|
||||||
|
and removed from subpaths the subpaths merged"
|
||||||
|
[candidate subpaths]
|
||||||
|
(let [merge-with-candidate
|
||||||
|
(fn [[candidate result] current]
|
||||||
|
(cond
|
||||||
|
(= (:to current) (:from current))
|
||||||
|
[candidate (conj result current)]
|
||||||
|
|
||||||
|
(= (:to candidate) (:from current))
|
||||||
|
[(subpaths-join candidate current) result]
|
||||||
|
|
||||||
|
(= (:to candidate) (:to current))
|
||||||
|
[(subpaths-join candidate (reverse-subpath current)) result]
|
||||||
|
|
||||||
|
:else
|
||||||
|
[candidate (conj result current)]))]
|
||||||
|
|
||||||
|
(->> subpaths
|
||||||
|
(reduce merge-with-candidate [candidate []]))))
|
||||||
|
|
||||||
|
(defn close-subpaths
|
||||||
|
"Searches a path for posible supaths that can create closed loops and merge them"
|
||||||
|
[content]
|
||||||
|
|
||||||
|
(let [subpaths (get-subpaths content)
|
||||||
|
|
||||||
|
closed-subpaths
|
||||||
|
(loop [result []
|
||||||
|
current (first subpaths)
|
||||||
|
subpaths (rest subpaths)]
|
||||||
|
|
||||||
|
(if (some? current)
|
||||||
|
(let [[new-current new-subpaths]
|
||||||
|
(if (= (:from current) (:to current))
|
||||||
|
[current subpaths]
|
||||||
|
(merge-paths current subpaths))]
|
||||||
|
|
||||||
|
(if (= current new-current)
|
||||||
|
;; If equal we haven't found any matching subpaths we advance
|
||||||
|
(recur (conj result new-current)
|
||||||
|
(first new-subpaths)
|
||||||
|
(rest new-subpaths))
|
||||||
|
|
||||||
|
;; If different we need to pass again the merge to check for additional
|
||||||
|
;; subpaths to join
|
||||||
|
(recur result
|
||||||
|
new-current
|
||||||
|
new-subpaths)))
|
||||||
|
result))]
|
||||||
|
|
||||||
|
(->> closed-subpaths
|
||||||
|
(mapcat :data)
|
||||||
|
(into []))))
|
Loading…
Reference in a new issue