mirror of
https://github.com/penpot/penpot.git
synced 2025-01-26 08:29:42 -05:00
405aa66357
Also optimizes some functions for faster shape and rect props access (there is still a lot of work ahead optimizing the rest of the functions) Also normalizes shape creation and validation for ensuring correct setup of all the mandatory properties.
229 lines
8.6 KiB
Clojure
229 lines
8.6 KiB
Clojure
;; 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.geom-shapes-test
|
|
(:require
|
|
[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.transforms :as gsht]
|
|
[app.common.math :as mth :refer [close?]]
|
|
[app.common.types.modifiers :as ctm]
|
|
[app.common.types.shape :as cts]
|
|
[clojure.test :as t]))
|
|
|
|
(def default-path
|
|
[{:command :move-to :params {:x 0 :y 0}}
|
|
{:command :line-to :params {:x 20 :y 20}}
|
|
{:command :line-to :params {:x 30 :y 30}}
|
|
{:command :curve-to :params {:x 40 :y 40 :c1x 35 :c1y 35 :c2x 45 :c2y 45}}
|
|
{:command :close-path}])
|
|
|
|
(defn create-test-shape
|
|
([type] (create-test-shape type {}))
|
|
([type params]
|
|
(if (= type :path)
|
|
(cts/setup-shape
|
|
(merge
|
|
{:type :path
|
|
:content (:content params default-path)}
|
|
params))
|
|
(cts/setup-shape
|
|
(merge
|
|
{:type type
|
|
:width 20
|
|
:height 20}
|
|
params)))))
|
|
|
|
(t/deftest transform-shapes
|
|
(t/testing "Shape without modifiers should stay the same"
|
|
(t/are [type]
|
|
(let [shape-before (create-test-shape type)
|
|
shape-after (gsh/transform-shape shape-before)]
|
|
(= shape-before shape-after))
|
|
|
|
:rect :path))
|
|
|
|
(t/testing "Transform shape with translation modifiers"
|
|
(doseq [type [:rect :path]]
|
|
(let [modifiers (ctm/move-modifiers (gpt/point 10 -10))
|
|
shape-before (create-test-shape type {:modifiers modifiers})
|
|
shape-after (gsh/transform-shape shape-before)]
|
|
|
|
(t/is (not= shape-before shape-after))
|
|
|
|
(t/is (close? (get-in shape-before [:selrect :x])
|
|
(- 10 (get-in shape-after [:selrect :x]))))
|
|
|
|
(t/is (close? (get-in shape-before [:selrect :y])
|
|
(+ 10 (get-in shape-after [:selrect :y]))))
|
|
|
|
(t/is (close? (get-in shape-before [:selrect :width])
|
|
(get-in shape-after [:selrect :width])))
|
|
|
|
(t/is (close? (get-in shape-before [:selrect :height])
|
|
(get-in shape-after [:selrect :height])))
|
|
)))
|
|
|
|
(t/testing "Transform with empty translation"
|
|
(t/are [type]
|
|
(let [modifiers {:displacement (gmt/matrix)}
|
|
shape-before (create-test-shape type {:modifiers modifiers})
|
|
shape-after (gsh/transform-shape shape-before)]
|
|
(t/are [prop]
|
|
(t/is (close? (get-in shape-before [:selrect prop])
|
|
(get-in shape-after [:selrect prop])))
|
|
:x :y :width :height :x1 :y1 :x2 :y2))
|
|
:rect :path))
|
|
|
|
(t/testing "Transform shape with resize modifiers"
|
|
(t/are [type]
|
|
(let [modifiers (ctm/resize-modifiers (gpt/point 2 2) (gpt/point 0 0))
|
|
shape-before (create-test-shape type {:modifiers modifiers})
|
|
shape-after (gsh/transform-shape shape-before)]
|
|
(t/is (not= shape-before shape-after))
|
|
|
|
(t/is (close? (get-in shape-before [:selrect :x])
|
|
(get-in shape-after [:selrect :x])))
|
|
|
|
(t/is (close? (get-in shape-before [:selrect :y])
|
|
(get-in shape-after [:selrect :y])))
|
|
|
|
(t/is (close? (* 2 (get-in shape-before [:selrect :width]))
|
|
(get-in shape-after [:selrect :width])))
|
|
|
|
(t/is (close? (* 2 (get-in shape-before [:selrect :height]))
|
|
(get-in shape-after [:selrect :height]))))
|
|
:rect :path))
|
|
|
|
(t/testing "Transform with empty resize"
|
|
(t/are [type]
|
|
(let [modifiers (ctm/resize-modifiers (gpt/point 1 1) (gpt/point 0 0))
|
|
shape-before (create-test-shape type {:modifiers modifiers})
|
|
shape-after (gsh/transform-shape shape-before)]
|
|
(t/are [prop]
|
|
(t/is (close? (get-in shape-before [:selrect prop])
|
|
(get-in shape-after [:selrect prop])))
|
|
:x :y :width :height :x1 :y1 :x2 :y2))
|
|
:rect :path))
|
|
|
|
(t/testing "Transform with resize=0"
|
|
(t/are [type]
|
|
(let [modifiers (ctm/resize-modifiers (gpt/point 0 0) (gpt/point 0 0))
|
|
shape-before (create-test-shape type {:modifiers modifiers})
|
|
shape-after (gsh/transform-shape shape-before)]
|
|
(t/is (close? (get-in shape-before [:selrect :width])
|
|
(get-in shape-after [:selrect :width])))
|
|
(t/is (close? (get-in shape-before [:selrect :height])
|
|
(get-in shape-after [:selrect :height]))))
|
|
:rect :path))
|
|
|
|
(t/testing "Transform shape with rotation modifiers"
|
|
(t/are [type]
|
|
(let [shape-before (create-test-shape type)
|
|
modifiers (ctm/rotation-modifiers shape-before (gsh/shape->center shape-before) 30)
|
|
shape-before (assoc shape-before :modifiers modifiers)
|
|
shape-after (gsh/transform-shape shape-before)]
|
|
|
|
(t/is (close? (get-in shape-before [:selrect :x])
|
|
(get-in shape-after [:selrect :x])))
|
|
|
|
(t/is (close? (get-in shape-before [:selrect :y])
|
|
(get-in shape-after [:selrect :y])))
|
|
|
|
(t/is (= (count (:points shape-before)) (count (:points shape-after))))
|
|
|
|
(for [idx (range 0 (count (:point shape-before)))]
|
|
(do (t/is (not (close? (get-in shape-before [:points idx :x])
|
|
(get-in shape-after [:points idx :x]))))
|
|
(t/is (not (close? (get-in shape-before [:points idx :y])
|
|
(get-in shape-after [:points idx :y])))))))
|
|
:rect :path))
|
|
|
|
(t/testing "Transform shape with rotation = 0 should leave equal selrect"
|
|
(t/are [type]
|
|
(let [shape-before (create-test-shape type)
|
|
modifiers (ctm/rotation-modifiers shape-before (gsh/shape->center shape-before) 0)
|
|
shape-after (gsh/transform-shape (assoc shape-before :modifiers modifiers))]
|
|
(t/are [prop]
|
|
(t/is (close? (get-in shape-before [:selrect prop])
|
|
(get-in shape-after [:selrect prop])))
|
|
:x :y :width :height :x1 :y1 :x2 :y2))
|
|
:rect :path))
|
|
|
|
(t/testing "Transform shape with invalid selrect fails gracefully"
|
|
(t/are [type selrect]
|
|
(let [modifiers (ctm/move-modifiers 0 0)
|
|
shape-before (create-test-shape type {:selrect selrect})
|
|
shape-after (gsh/transform-shape shape-before modifiers)]
|
|
|
|
(t/is (grc/close-rect? (:selrect shape-before)
|
|
(:selrect shape-after))))
|
|
|
|
:rect (grc/make-rect 0 0 ##Inf ##Inf)
|
|
:path (grc/make-rect 0 0 ##Inf ##Inf)
|
|
))
|
|
)
|
|
|
|
(t/deftest points-to-selrect
|
|
(let [points [(gpt/point 0.5 0.5)
|
|
(gpt/point -1 -2)
|
|
(gpt/point 20 65.2)
|
|
(gpt/point 12 -10)]
|
|
result (grc/points->rect points)
|
|
expect {:x -1, :y -10, :width 21, :height 75.2}]
|
|
|
|
(t/is (= (:x expect) (:x result)))
|
|
(t/is (= (:y expect) (:y result)))
|
|
(t/is (= (:width expect) (:width result)))
|
|
(t/is (= (:height expect) (:height result)))))
|
|
|
|
(def g45 (mth/radians 45))
|
|
|
|
(t/deftest points-transform-matrix
|
|
(t/testing "Transform matrix"
|
|
(t/are [selrect points expected]
|
|
(let [result (gsht/transform-points-matrix selrect points)]
|
|
(t/is (gmt/close? expected result)))
|
|
|
|
;; No transformation
|
|
(grc/make-rect 0 0 10 10)
|
|
(-> (grc/make-rect 0 0 10 10)
|
|
(grc/rect->points))
|
|
(gmt/matrix)
|
|
|
|
;; Displacement
|
|
(grc/make-rect 0 0 10 10)
|
|
(-> (grc/make-rect 20 20 10 10)
|
|
(grc/rect->points ))
|
|
(gmt/matrix 1 0 0 1 20 20)
|
|
|
|
;; Resize
|
|
(grc/make-rect 0 0 10 10)
|
|
(-> (grc/make-rect 0 0 20 40)
|
|
(grc/rect->points))
|
|
(gmt/matrix 2 0 0 4 0 0)
|
|
|
|
;; Displacement+Resize
|
|
(grc/make-rect 0 0 10 10)
|
|
(-> (grc/make-rect 10 10 20 40)
|
|
(grc/rect->points))
|
|
(gmt/matrix 2 0 0 4 10 10)
|
|
|
|
;; Rotation
|
|
(grc/make-rect 0 0 10 10)
|
|
(-> (grc/make-rect 0 0 10 10)
|
|
(grc/rect->points)
|
|
(gsh/transform-points (gmt/rotate-matrix 45)))
|
|
(gmt/matrix (mth/cos g45) (mth/sin g45) (- (mth/sin g45)) (mth/cos g45) 0 0)
|
|
|
|
;; Rotation + Resize
|
|
(grc/make-rect 0 0 10 10)
|
|
(-> (grc/make-rect 0 0 20 40)
|
|
(grc/rect->points)
|
|
(gsh/transform-points (gmt/rotate-matrix 45)))
|
|
(gmt/matrix (* (mth/cos g45) 2) (* (mth/sin g45) 2) (* (- (mth/sin g45)) 4) (* (mth/cos g45) 4) 0 0))))
|