0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-09 08:20:45 -05:00

Merge closing subpaths for seamless paths

This commit is contained in:
alonso.torres 2021-04-22 18:27:05 +02:00 committed by Andrés Moya
parent 3583eb6aa9
commit 2184286a78
2 changed files with 156 additions and 9 deletions

View file

@ -7,13 +7,9 @@
(ns app.util.path.format
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.path :as gshp]
[app.util.svg :as usvg]
[app.util.path.commands :as upc]
[cuerdas.core :as str]
[clojure.set :as set]
[app.common.math :as mth]
))
[app.util.path.subpaths :as ups]))
(defn command->param-list [command]
(let [params (:params command)]
@ -69,6 +65,22 @@
(defn format-path [content]
(->> content
(mapv command->string)
(str/join "")))
(let [content (ups/close-subpaths content)]
(loop [result ""
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))))

View 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 []))))