🐛 Fix problem with snap pixel transforms

This commit is contained in:
alonso.torres
2026-03-13 12:18:14 +01:00
committed by Alonso Torres
parent 39dcad8f54
commit 1ab1d4f6ca
6 changed files with 200 additions and 15 deletions

View File

@@ -0,0 +1,20 @@
{
"~:file-id": "~u3b9773cc-d4f1-81e1-8007-b3f8dcaba770",
"~:id": "~u3ac58b88-38b3-80c9-8007-b4f791c7c36b",
"~:created-at": "~m1773398778658",
"~:modified-at": "~m1773398778658",
"~:type": "fragment",
"~:backend": "db",
"~:data": {
"~:objects": {
"~#penpot/objects-map/v2": {
"~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^H\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~ub8c8efc9-e8a3-8018-8007-b4f6cd99ad3a\"]]]",
"~ub8c8efc9-e8a3-8018-8007-b4f6c15a473a": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",76,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0,\"~:y\",0]],[\"^<\",[\"^ \",\"~:x\",76,\"~:y\",0]],[\"^<\",[\"^ \",\"~:x\",76,\"~:y\",59]],[\"^<\",[\"^ \",\"~:x\",0,\"~:y\",59]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~ub8c8efc9-e8a3-8018-8007-b4f6c15a473a\",\"~:parent-id\",\"~ub8c8efc9-e8a3-8018-8007-b4f6cd99ad3a\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^8\",76,\"~:height\",59,\"~:x1\",0,\"~:y1\",0,\"~:x2\",76,\"~:y2\",59]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",59,\"~:flip-y\",null]]",
"~ub8c8efc9-e8a3-8018-8007-b4f6c7677f74": "[\"~#shape\",[\"^ \",\"~:y\",74,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",95,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",57,\"~:y\",74]],[\"^<\",[\"^ \",\"~:x\",152,\"~:y\",74]],[\"^<\",[\"^ \",\"~:x\",152,\"~:y\",123]],[\"^<\",[\"^ \",\"~:x\",57,\"~:y\",123]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~ub8c8efc9-e8a3-8018-8007-b4f6c7677f74\",\"~:parent-id\",\"~ub8c8efc9-e8a3-8018-8007-b4f6cd99ad3a\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",57,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",57,\"~:y\",74,\"^8\",95,\"~:height\",49,\"~:x1\",57,\"~:y1\",74,\"~:x2\",152,\"~:y2\",123]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",49,\"~:flip-y\",null]]",
"~ub8c8efc9-e8a3-8018-8007-b4f6cd99ad3a": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:index\",2,\"~:name\",\"Group\",\"~:width\",152,\"~:type\",\"~:group\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0,\"~:y\",0]],[\"^:\",[\"^ \",\"~:x\",152,\"~:y\",0]],[\"^:\",[\"^ \",\"~:x\",152,\"~:y\",123]],[\"^:\",[\"^ \",\"~:x\",0,\"~:y\",123]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~ub8c8efc9-e8a3-8018-8007-b4f6cd99ad3a\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",152,\"~:height\",123,\"~:x1\",0,\"~:y1\",0,\"~:x2\",152,\"~:y2\",123]],\"~:fills\",[],\"~:flip-x\",null,\"^D\",123,\"~:flip-y\",null,\"~:shapes\",[\"~ub8c8efc9-e8a3-8018-8007-b4f6c15a473a\",\"~ub8c8efc9-e8a3-8018-8007-b4f6c7677f74\"]]]"
}
},
"~:id": "~u3b9773cc-d4f1-81e1-8007-b3f8dcaba771",
"~:name": "Page 1"
}
}

View File

@@ -0,0 +1,135 @@
{
"~:features": {
"~#set": [
"fdata/path-data",
"plugins/runtime",
"design-tokens/v1",
"variants/v1",
"layout/grid",
"styles/v2",
"fdata/pointer-map",
"fdata/objects-map",
"render-wasm/v1",
"components/v2",
"fdata/shape-data-type"
]
},
"~:team-id": "~ud715d0a5-a44e-8056-8005-a79999e18b64",
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true,
"~:can-read": true,
"~:is-logged": true
},
"~:has-media-trimmed": false,
"~:comment-thread-seqn": 0,
"~:name": "New File 13",
"~:revn": 79,
"~:modified-at": "~m1773398778654",
"~:vern": 0,
"~:id": "~u3b9773cc-d4f1-81e1-8007-b3f8dcaba770",
"~:is-shared": false,
"~:migrations": {
"~#ordered-set": [
"legacy-2",
"legacy-3",
"legacy-5",
"legacy-6",
"legacy-7",
"legacy-8",
"legacy-9",
"legacy-10",
"legacy-11",
"legacy-12",
"legacy-13",
"legacy-14",
"legacy-16",
"legacy-17",
"legacy-18",
"legacy-19",
"legacy-25",
"legacy-26",
"legacy-27",
"legacy-28",
"legacy-29",
"legacy-31",
"legacy-32",
"legacy-33",
"legacy-34",
"legacy-36",
"legacy-37",
"legacy-38",
"legacy-39",
"legacy-40",
"legacy-41",
"legacy-42",
"legacy-43",
"legacy-44",
"legacy-45",
"legacy-46",
"legacy-47",
"legacy-48",
"legacy-49",
"legacy-50",
"legacy-51",
"legacy-52",
"legacy-53",
"legacy-54",
"legacy-55",
"legacy-56",
"legacy-57",
"legacy-59",
"legacy-62",
"legacy-65",
"legacy-66",
"legacy-67",
"0001-remove-tokens-from-groups",
"0002-normalize-bool-content-v2",
"0002-clean-shape-interactions",
"0003-fix-root-shape",
"0003-convert-path-content-v2",
"0005-deprecate-image-type",
"0006-fix-old-texts-fills",
"0008-fix-library-colors-v4",
"0009-clean-library-colors",
"0009-add-partial-text-touched-flags",
"0010-fix-swap-slots-pointing-non-existent-shapes",
"0011-fix-invalid-text-touched-flags",
"0012-fix-position-data",
"0013-fix-component-path",
"0013-clear-invalid-strokes-and-fills",
"0014-fix-tokens-lib-duplicate-ids",
"0014-clear-components-nil-objects",
"0015-fix-text-attrs-blank-strings",
"0015-clean-shadow-color",
"0016-copy-fills-from-position-data-to-text-node",
"0017-fix-layout-flex-dir"
]
},
"~:version": 67,
"~:project-id": "~u76eab896-accf-81a5-8007-2b264ebe7817",
"~:created-at": "~m1773332008622",
"~:backend": "legacy-db",
"~:data": {
"~:pages": [
"~u3b9773cc-d4f1-81e1-8007-b3f8dcaba771"
],
"~:pages-index": {
"~u3b9773cc-d4f1-81e1-8007-b3f8dcaba771": {
"~#penpot/pointer": [
"~u3ac58b88-38b3-80c9-8007-b4f791c7c36b",
{
"~:created-at": "~m1773398778656"
}
]
}
},
"~:id": "~u3b9773cc-d4f1-81e1-8007-b3f8dcaba770",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
}
}
}

View File

@@ -72,7 +72,7 @@ test("BUG 13468 - Fix problem with flex propagation", async ({ page }) => {
fileId: "3a4d7ec7-c391-8146-8007-9a05c41da6b9",
pageId: "95b23c15-79f9-81ba-8007-99d81b5290dd",
});
0
await workspacePage.clickToggableLayer("Parent");
await workspacePage.clickToggableLayer("Container");
@@ -82,4 +82,33 @@ test("BUG 13468 - Fix problem with flex propagation", async ({ page }) => {
await expect(workspacePage.rightSidebar.getByTitle("Height").getByRole("textbox")).toHaveValue("76");
});
test("BUG 13272 - Fix problem with snap to pixel", async ({ page }) => {
const workspacePage = new WasmWorkspacePage(page);
await workspacePage.setupEmptyFile();
await workspacePage.mockGetFile("workspace/get-file-13272.json");
await workspacePage.mockRPC(
"get-file-fragment?file-id=*&fragment-id=*",
"workspace/get-file-13272-fragment.json",
);
await workspacePage.mockRPC("update-file?id=*", "workspace/update-file-empty.json");
await workspacePage.goToWorkspace({
fileId: "3b9773cc-d4f1-81e1-8007-b3f8dcaba770",
pageId: "3b9773cc-d4f1-81e1-8007-b3f8dcaba771",
});
await workspacePage.clickToggableLayer("Group");
await workspacePage.clickLeafLayer("Group");
await workspacePage.page.locator('g:nth-child(11) > .cursor-resize-nesw-0').hover();
await workspacePage.page.mouse.down();
await workspacePage.page.mouse.move(1200, 800);
await workspacePage.page.mouse.up();
await workspacePage.clickLeafLayer("Rectangle");
await expect(workspacePage.rightSidebar.getByTitle("Width").getByRole("textbox")).toHaveValue("197.5");
await expect(workspacePage.rightSidebar.getByTitle("Height").getByRole("textbox")).toHaveValue("128.28");
});

View File

@@ -628,12 +628,13 @@
(let [prev-wasm-props (:prev-wasm-props state)
wasm-props (:wasm-props state)
objects (dsh/lookup-page-objects state)
pixel-precision false]
snap-pixel?
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))]
(set-wasm-props! objects prev-wasm-props wasm-props)
(let [structure-entries (parse-structure-modifiers modif-tree)]
(wasm.api/set-structure-modifiers structure-entries)
(let [geometry-entries (parse-geometry-modifiers modif-tree)
modifiers (wasm.api/propagate-modifiers geometry-entries pixel-precision)]
modifiers (wasm.api/propagate-modifiers geometry-entries snap-pixel?)]
(wasm.api/set-modifiers modifiers)
(let [ids (into [] xf:map-key geometry-entries)
selrect (wasm.api/get-selection-rect ids)]

View File

@@ -133,21 +133,21 @@ fn set_pixel_precision(transform: &mut Matrix, bounds: &mut Bounds) {
let width = bounds.width();
let height = bounds.height();
let target_width = bounds.width().round();
let target_height = bounds.height().round();
let scale_width = if width > 0.1 {
f32::max(0.01, bounds.width().round() / bounds.width())
f32::max(0.01, target_width / width)
} else {
1.0
};
let scale_height = if height > 0.1 {
f32::max(0.01, bounds.height().round() / bounds.height())
f32::max(0.01, target_height / height)
} else {
1.0
};
if f32::is_finite(scale_width)
&& f32::is_finite(scale_height)
&& (!math::is_close_to(scale_width, 1.0) || !math::is_close_to(scale_height, 1.0))
{
if f32::is_finite(scale_width) && f32::is_finite(scale_height) {
let mut round_transform = Matrix::scale((scale_width, scale_height));
round_transform.post_concat(&tr);
round_transform.pre_concat(&tr_inv);
@@ -373,7 +373,7 @@ pub fn propagate_modifiers(
if math::identitish(&entry.transform) {
Modifier::Reflow(entry.id, false)
} else {
Modifier::Transform(*entry)
Modifier::Transform(*entry, pixel_precision)
}
})
.collect();
@@ -392,9 +392,9 @@ pub fn propagate_modifiers(
while !entries.is_empty() {
while let Some(modifier) = entries.pop_front() {
match modifier {
Modifier::Transform(entry) => propagate_transform(
Modifier::Transform(entry, pixel) => propagate_transform(
entry,
pixel_precision,
pixel,
state,
&mut entries,
&mut bounds,

View File

@@ -7,16 +7,16 @@ use skia::Matrix;
#[derive(PartialEq, Debug, Clone)]
pub enum Modifier {
Transform(TransformEntry),
Transform(TransformEntry, bool),
Reflow(Uuid, bool),
}
impl Modifier {
pub fn transform_propagate(id: Uuid, transform: Matrix) -> Self {
Modifier::Transform(TransformEntry::from_propagate(id, transform))
Modifier::Transform(TransformEntry::from_propagate(id, transform), false)
}
pub fn parent(id: Uuid, transform: Matrix) -> Self {
Modifier::Transform(TransformEntry::parent(id, transform))
Modifier::Transform(TransformEntry::parent(id, transform), false)
}
pub fn reflow(id: Uuid, force_reflow: bool) -> Self {
Modifier::Reflow(id, force_reflow)