🐛 Compare measured text at its oriented corner

Keep auto-width and auto-height text positions in design comparison by
using the first oriented corner. Measurement can change unrotated x/y
while retaining that corner. Requested geometry also checks this anchor.

The new matrix failed 29 of 88 assertions before the correction. Controls
cover four rotations, reflection, one-pixel drift, fixed width and raw
undo state without changing the geometry tolerance. The older matrix
fixture now anchors both measured versions at the oriented corner;
its assertions remain unchanged.

Combined generator/oracle checks pass 139 tests / 899 assertions with
40 programs per property at seed 712. Original auto-height WASM replay
passes 51 checks with exact undo/redo and full-sync consistency. The
original page and normal focus are restored; no pending work remains.

No production geometry, golden data or benchmark baseline changes.

AI-assisted-by: gpt-6
This commit is contained in:
alonso.torres 2026-10-27 09:46:12 +01:00
parent 7e2f8b8781
commit 00b743da0b
3 changed files with 56 additions and 2 deletions

View File

@ -145,6 +145,12 @@ first can split two positions 7e-7 px apart across a 0.005 boundary (F150).
Scalar values and touched stay exact. comp/undo-check/same-state? supplies
the shared comparison; its undo default remains 1e-10.
Measured auto-width/auto-height text compares its oriented first corner,
not the unrotated selrect x/y, which can move as height/width changes
(F220). Canonical comparison and requested geometry both retain that
anchor at the existing 0.01 tolerance. Layout-owned positions remain
derived; fixed text dimensions and raw undo geometry remain checked.
Swapped-head dimensions use the content main; position, rotation, flips,
constraints and layout-item inputs use the position source (F151).
When sizes differ, expected geometry keeps the position source's

View File

@ -106,6 +106,12 @@
(assoc moved
:transform (matrix-effect shape (:transform moved) derived)
:transform-inverse (matrix-effect shape (:transform-inverse moved) derived))))
;; Measurement preserves the oriented first
;; corner, not the unrotated rectangle's x/y.
(cond-> (and (= :text (:type shape))
(#{:auto-width :auto-height} (:grow-type shape)))
(as-> moved (assoc moved :x (:x (first (:points moved)))
:y (:y (first (:points moved))))))
(as-> shape (apply dissoc shape
(cond-> (derived-attributes objects shape)
(and free-head-placement (:component-id shape) (:shape-ref shape)
@ -273,6 +279,9 @@
(not= (boolean (:flip-y shape)) (boolean (:flip-y expected))) (conj :flip-y)
(and (not width-owned) (not (mth/close? (:width shape) (:width expected) 0.01))) (conj :width)
(and (not height-owned) (not (mth/close? (:height shape) (:height expected) 0.01))) (conj :height)
(and (not layout-child) auto-height
(not (gsh/close-attrs? :points [(first (:points shape))]
[(first (:points expected))] 0.01))) (conj :position)
(and (not layout-child) (not auto-width) (not auto-height)
(not (gsh/close-attrs? :points (:points shape) (:points expected) 0.01))) (conj :points))))

View File

@ -409,6 +409,43 @@
(t/is (= equal? (oracle/equivalent? (oracle/canonical (tm/make-situation file))
(oracle/canonical (tm/make-situation changed))))))))
(t/deftest measured-text-comparison-keeps-the-oriented-first-corner
(doseq [grow-type [:auto-height :auto-width]
angle [0 45 90 135]
reflected? [false true]]
(let [file (-> (thf/sample-file :file)
(ths/add-sample-shape :text :type :text :grow-type grow-type
:width 70 :height 25))
page-id (:id (thf/current-page file))
path [:data :pages-index page-id :objects (thi/id :text)]
orient (fn [shape]
(cond-> (gsh/transform-shape shape (n/geometry-modifiers shape :rotate angle))
reflected? (as-> shape (gsh/transform-shape shape (n/geometry-modifiers shape :flip-x nil)))))
file (update-in file path orient)
original (get-in file path)
measured (update-in file path
(fn [shape]
(let [resized (gsh/transform-shape
shape (ctm/change-size shape
(if (= :auto-width grow-type) 55 70) 17))]
(gsh/move resized (gpt/subtract (first (:points shape))
(first (:points resized)))))))
shifted (update-in measured path gsh/move (gpt/point 1 0))
wrong-width (assoc-in measured (conj path :width) 60)
design #(oracle/canonical (tm/make-situation %) {:free-head-placement false})]
(t/testing (str grow-type ", angle " angle ", reflected " reflected?)
(t/is (oracle/equivalent? (design file) (design measured))))
(t/is (not (oracle/equivalent? (design file) (design shifted))))
(t/is (not= (oracle/snapshot (tm/make-situation file))
(oracle/snapshot (tm/make-situation measured))))
(t/is (nil? (oracle/check-requested-geometry (:objects (thf/current-page measured))
(get-in measured path) original)))
(t/is (thrown? #?(:clj Exception :cljs js/Error)
(oracle/check-requested-geometry (:objects (thf/current-page shifted))
(get-in shifted path) original)))
(when (= :auto-height grow-type)
(t/is (not (oracle/equivalent? (design file) (design wrong-width))))))))
(t/deftest matrix-comparison-does-not-reintroduce-derived-text-dimensions
(doseq [grow-type [:auto-height :auto-width]]
(let [file (-> (thf/sample-file :file)
@ -423,8 +460,10 @@
changed (assoc-in measured (conj path :transform) (gmt/matrix 1 0 0 1 2 0))
stretched (assoc-in measured (conj path :transform) (gmt/matrix 1 0 0 1.1 0 0))
fixed-width (assoc-in measured (conj path :width) 60)
turn #(gsh/absolute-move (gsh/transform-shape % (n/geometry-modifiers % :rotate 45))
(gpt/point 0 0))
;; Both measured versions use the renderer's oriented corner anchor.
turn (fn [shape]
(let [turned (gsh/transform-shape shape (n/geometry-modifiers shape :rotate 45))]
(gsh/move turned (gpt/negate (first (:points turned))))))
rotated (update-in file path turn)
rotated-measured (update-in measured path turn)
design #(oracle/canonical (tm/make-situation %))]