mirror of
https://github.com/penpot/penpot.git
synced 2026-11-05 01:20:38 +00:00
🐛 Move displaced path content back onto its box (#12754)
Some files have component copies whose path drawing (:content) sits away from its box (:selrect and :points). The renderer draws the content, while the sidebar, layouts and syncs use the box, so these icons render outside their boards and are clipped or drawn over other shapes. Moving the shape keeps the gap, and sync never repairs it because both groups are touched. Add the migration 0034-fix-path-content-offset. For every path in pages and in component objects, deleted components included, it derives the box from the content as update-geometry does and, when the sizes agree but the position does not, moves the content onto the stored box. The box, the transform and the touched groups stay as they are; paths whose drawing has another size are left untouched. Closes #12752
This commit is contained in:
parent
4ad8d1d36a
commit
410bde8103
@ -2296,6 +2296,45 @@
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "0034-fix-path-content-offset"
|
||||
[data _]
|
||||
;; A path stores its geometry twice: the box (:selrect and :points) and the
|
||||
;; drawing (:content), and the box is always derived from the drawing. Some
|
||||
;; files have component copies whose drawing and box were moved apart, so
|
||||
;; the drawing renders away from where the sidebar, the layouts and the
|
||||
;; syncs place the shape. We move the content back onto the box. A path whose drawing
|
||||
;; has another size than its box is not a displaced drawing, and is left as
|
||||
;; it is.
|
||||
(let [tolerance 0.1]
|
||||
(letfn [(fix-path [shape]
|
||||
(when (and (cfh/path-shape? shape)
|
||||
(some? (:selrect shape))
|
||||
(some? (path.segment/content->selrect (:content shape))))
|
||||
(let [selrect (:selrect shape)
|
||||
derived (:selrect (path/update-geometry shape))
|
||||
offset (gpt/subtract (grc/rect->center selrect)
|
||||
(grc/rect->center derived))]
|
||||
(when (and (mth/close? (:width selrect) (:width derived) tolerance)
|
||||
(mth/close? (:height selrect) (:height derived) tolerance)
|
||||
(or (> (abs (:x offset)) tolerance)
|
||||
(> (abs (:y offset)) tolerance)))
|
||||
(update shape :content path/move-content offset)))))
|
||||
|
||||
(fix-objects [objects]
|
||||
(reduce-kv (fn [objects id shape]
|
||||
(if-let [shape (fix-path shape)]
|
||||
(assoc objects id shape)
|
||||
objects))
|
||||
objects
|
||||
objects))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects fix-objects))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container)))))
|
||||
|
||||
(def available-migrations
|
||||
(into (d/ordered-set)
|
||||
["legacy-2"
|
||||
@ -2386,4 +2425,5 @@
|
||||
"0030-remove-stroke-per-side-attr"
|
||||
"0031-migrate-stroke-width-token-attr"
|
||||
"0032-fix-children-not-in-parent"
|
||||
"0033-normalize-main-instance-names"]))
|
||||
"0033-normalize-main-instance-names"
|
||||
"0034-fix-path-content-offset"]))
|
||||
|
||||
116
common/test/common_tests/files_migrations_0034_test.cljc
Normal file
116
common/test/common_tests/files_migrations_0034_test.cljc
Normal file
@ -0,0 +1,116 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
|
||||
(ns common-tests.files-migrations-0034-test
|
||||
(:require
|
||||
[app.common.files.migrations :as cfm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.segment :as segment]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
;; 0034-fix-path-content-offset
|
||||
;; A path whose drawing (:content) was moved apart from its box
|
||||
;; (:selrect and :points) gets its content moved back onto the box.
|
||||
|
||||
(def ^:private migration "0034-fix-path-content-offset")
|
||||
|
||||
(defn- triangle
|
||||
[x0 y0]
|
||||
[{:command :move-to :params {:x x0 :y y0}}
|
||||
{:command :line-to :params {:x (+ x0 14) :y y0}}
|
||||
{:command :line-to :params {:x (+ x0 7) :y (+ y0 14)}}
|
||||
{:command :close-path}])
|
||||
|
||||
(defn- make-path
|
||||
[shape-id x0 y0]
|
||||
(cts/setup-shape {:id shape-id :type :path :content (triangle x0 y0)}))
|
||||
|
||||
(defn- rotate
|
||||
[shape angle]
|
||||
(gsh/transform-shape shape (ctm/rotation-modifiers shape (gsh/shape->center shape) angle)))
|
||||
|
||||
(defn- displace-content
|
||||
[shape delta]
|
||||
(update shape :content path/move-content delta))
|
||||
|
||||
(defn- migrate-shape
|
||||
[shape]
|
||||
(let [page-id (uuid/next)
|
||||
data {:pages-index {page-id {:objects {(:id shape) shape}}}}
|
||||
data' (cfm/migrate-data data migration)]
|
||||
(get-in data' [:pages-index page-id :objects (:id shape)])))
|
||||
|
||||
(defn- content-matches-box?
|
||||
[shape]
|
||||
(let [derived (path/update-geometry shape)]
|
||||
(and (every? true? (map gpt/close? (:points shape) (:points derived)))
|
||||
(every? #(mth/close? (get (:selrect shape) %) (get (:selrect derived) %))
|
||||
[:x :y :width :height]))))
|
||||
|
||||
(t/deftest migration-0034-moves-displaced-content-onto-its-box
|
||||
(let [shape (make-path (uuid/next) 1 1)
|
||||
broken (displace-content shape (gpt/point 0 -182))
|
||||
shape' (migrate-shape broken)]
|
||||
|
||||
(t/is (not (content-matches-box? broken)) "the fixture is broken")
|
||||
(t/is (content-matches-box? shape') "content back onto the box")
|
||||
(t/is (= (:selrect broken) (:selrect shape')) "box unchanged")
|
||||
(t/is (= (:points broken) (:points shape')) "points unchanged")
|
||||
(t/is (= (segment/content->selrect (:content shape))
|
||||
(segment/content->selrect (:content shape')))
|
||||
"content where it was before being displaced")))
|
||||
|
||||
(t/deftest migration-0034-moves-displaced-content-of-a-rotated-path
|
||||
(let [shape (rotate (make-path (uuid/next) 100 100) 30)
|
||||
broken (displace-content shape (gpt/point 25 -40))
|
||||
shape' (migrate-shape broken)]
|
||||
|
||||
(t/is (not (gmt/close? (gmt/matrix) (:transform shape))) "the fixture is rotated")
|
||||
(t/is (not (content-matches-box? broken)) "the fixture is broken")
|
||||
(t/is (content-matches-box? shape') "content back onto the box")
|
||||
(t/is (= (:selrect broken) (:selrect shape')) "box unchanged")
|
||||
(t/is (= (:transform broken) (:transform shape')) "rotation unchanged")))
|
||||
|
||||
(t/deftest migration-0034-keeps-paths-whose-content-matches-the-box
|
||||
(let [shape (make-path (uuid/next) 10 20)
|
||||
small (displace-content shape (gpt/point 0.05 0))]
|
||||
|
||||
(t/is (identical? shape (migrate-shape shape)) "consistent path untouched")
|
||||
(t/is (identical? small (migrate-shape small)) "float noise untouched")))
|
||||
|
||||
(t/deftest migration-0034-keeps-paths-whose-content-has-another-size
|
||||
(let [shape (make-path (uuid/next) 0 0)
|
||||
bigger (assoc shape :content (path/content [{:command :move-to :params {:x 40 :y 0}}
|
||||
{:command :line-to :params {:x 70 :y 0}}
|
||||
{:command :line-to :params {:x 55 :y 30}}
|
||||
{:command :close-path}]))]
|
||||
|
||||
(t/is (identical? bigger (migrate-shape bigger)) "size mismatch untouched")))
|
||||
|
||||
(t/deftest migration-0034-ignores-shapes-other-than-paths
|
||||
(let [shape-id (uuid/next)
|
||||
rect (cts/setup-shape {:id shape-id :type :rect :x 0 :y 0 :width 10 :height 10})]
|
||||
(t/is (identical? rect (migrate-shape rect)))))
|
||||
|
||||
(t/deftest migration-0034-fixes-paths-inside-components
|
||||
(let [shape (make-path (uuid/next) 1 1)
|
||||
broken (displace-content shape (gpt/point 0 109))
|
||||
component-id (uuid/next)
|
||||
data {:pages-index {}
|
||||
:components {component-id {:id component-id
|
||||
:deleted true
|
||||
:objects {(:id broken) broken}}}}
|
||||
data' (cfm/migrate-data data migration)
|
||||
shape' (get-in data' [:components component-id :objects (:id broken)])]
|
||||
|
||||
(t/is (content-matches-box? shape') "deleted component content fixed")))
|
||||
@ -22,6 +22,7 @@
|
||||
[common-tests.files-changes-test]
|
||||
[common-tests.files-migrations-0025-test]
|
||||
[common-tests.files-migrations-0026-test]
|
||||
[common-tests.files-migrations-0034-test]
|
||||
[common-tests.files-migrations-test]
|
||||
[common-tests.files.comp-processors-test]
|
||||
[common-tests.files.helpers-test]
|
||||
@ -130,6 +131,7 @@
|
||||
'common-tests.files-changes-test
|
||||
'common-tests.files-migrations-0025-test
|
||||
'common-tests.files-migrations-0026-test
|
||||
'common-tests.files-migrations-0034-test
|
||||
'common-tests.files-migrations-test
|
||||
'common-tests.files.comp-processors-test
|
||||
'common-tests.files.helpers-test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user