mirror of
https://github.com/penpot/penpot.git
synced 2026-03-14 06:17:25 +00:00
🐛 Fix negative insets
This commit is contained in:
@@ -0,0 +1,513 @@
|
||||
{
|
||||
"~: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": "~u0b5bcbca-32ab-81eb-8005-a15fc4484678",
|
||||
"~: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 6",
|
||||
"~:revn": 1,
|
||||
"~:modified-at": "~m1773140377840",
|
||||
"~:vern": 0,
|
||||
"~:id": "~ueffcbebc-b8c8-802f-8007-b11dd34fe190",
|
||||
"~: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"
|
||||
]
|
||||
},
|
||||
"~:version": 67,
|
||||
"~:project-id": "~u0b5bcbca-32ab-81eb-8005-a15fc448f334",
|
||||
"~:created-at": "~m1773140371775",
|
||||
"~:backend": "legacy-db",
|
||||
"~:data": {
|
||||
"~:pages": [
|
||||
"~ueffcbebc-b8c8-802f-8007-b11dd34fe191"
|
||||
],
|
||||
"~:pages-index": {
|
||||
"~ueffcbebc-b8c8-802f-8007-b11dd34fe191": {
|
||||
"~:objects": {
|
||||
"~u00000000-0000-0000-0000-000000000000": {
|
||||
"~#shape": {
|
||||
"~:y": 0,
|
||||
"~:hide-fill-on-export": false,
|
||||
"~:transform": {
|
||||
"~#matrix": {
|
||||
"~:a": 1,
|
||||
"~:b": 0,
|
||||
"~:c": 0,
|
||||
"~:d": 1,
|
||||
"~:e": 0,
|
||||
"~:f": 0
|
||||
}
|
||||
},
|
||||
"~:rotation": 0,
|
||||
"~:name": "Root Frame",
|
||||
"~:width": 0.01,
|
||||
"~:type": "~:frame",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0,
|
||||
"~:y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.01,
|
||||
"~:y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0.01,
|
||||
"~:y": 0.01
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 0,
|
||||
"~:y": 0.01
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:r2": 0,
|
||||
"~:proportion-lock": false,
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1,
|
||||
"~:b": 0,
|
||||
"~:c": 0,
|
||||
"~:d": 1,
|
||||
"~:e": 0,
|
||||
"~:f": 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,
|
||||
"~:r4": 0,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 0,
|
||||
"~:y": 0,
|
||||
"~:width": 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,
|
||||
"~:height": 0.01,
|
||||
"~:flip-y": null,
|
||||
"~:shapes": [
|
||||
"~ub952fb5e-cae5-8054-8007-b11dd63f79f9",
|
||||
"~ub952fb5e-cae5-8054-8007-b11dd63f79fa",
|
||||
"~ub952fb5e-cae5-8054-8007-b11dd63f79fb"
|
||||
]
|
||||
}
|
||||
},
|
||||
"~ub952fb5e-cae5-8054-8007-b11dd63f79f9": {
|
||||
"~#shape": {
|
||||
"~:y": 660.000001521671,
|
||||
"~:transform": {
|
||||
"~#matrix": {
|
||||
"~:a": 1,
|
||||
"~:b": 0,
|
||||
"~:c": 0,
|
||||
"~:d": 1,
|
||||
"~:e": 0,
|
||||
"~:f": 0
|
||||
}
|
||||
},
|
||||
"~:rotation": 0,
|
||||
"~:grow-type": "~:fixed",
|
||||
"~:hide-in-viewer": false,
|
||||
"~:name": "Rectangle",
|
||||
"~:width": 99.9999986310249,
|
||||
"~:type": "~:rect",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 989,
|
||||
"~:y": 660.000001521671
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1088.99999863103,
|
||||
"~:y": 660.000001521671
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1088.99999863103,
|
||||
"~:y": 760.000000795896
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 989,
|
||||
"~:y": 760.000000795896
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:r2": 0,
|
||||
"~:proportion-lock": false,
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1,
|
||||
"~:b": 0,
|
||||
"~:c": 0,
|
||||
"~:d": 1,
|
||||
"~:e": 0,
|
||||
"~:f": 0
|
||||
}
|
||||
},
|
||||
"~:r3": 0,
|
||||
"~:r1": 0,
|
||||
"~:id": "~ub952fb5e-cae5-8054-8007-b11dd63f79f9",
|
||||
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes": [
|
||||
{
|
||||
"~:stroke-alignment": "~:inner",
|
||||
"~:stroke-style": "~:solid",
|
||||
"~:stroke-color": "#000000",
|
||||
"~:stroke-opacity": 1,
|
||||
"~:stroke-width": 100
|
||||
}
|
||||
],
|
||||
"~:x": 989,
|
||||
"~:proportion": 1,
|
||||
"~:r4": 0,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 989,
|
||||
"~:y": 660.000001521671,
|
||||
"~:width": 99.9999986310249,
|
||||
"~:height": 99.9999992742251,
|
||||
"~:x1": 989,
|
||||
"~:y1": 660.000001521671,
|
||||
"~:x2": 1088.99999863103,
|
||||
"~:y2": 760.000000795896
|
||||
}
|
||||
},
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color": "#B1B2B5",
|
||||
"~:fill-opacity": 1
|
||||
}
|
||||
],
|
||||
"~:flip-x": null,
|
||||
"~:height": 99.9999992742251,
|
||||
"~:flip-y": null
|
||||
}
|
||||
},
|
||||
"~ub952fb5e-cae5-8054-8007-b11dd63f79fa": {
|
||||
"~#shape": {
|
||||
"~:y": 457.999994456768,
|
||||
"~:transform": {
|
||||
"~#matrix": {
|
||||
"~:a": 1,
|
||||
"~:b": 0,
|
||||
"~:c": 0,
|
||||
"~:d": 1,
|
||||
"~:e": 0,
|
||||
"~:f": 0
|
||||
}
|
||||
},
|
||||
"~:rotation": 0,
|
||||
"~:grow-type": "~:fixed",
|
||||
"~:hide-in-viewer": false,
|
||||
"~:name": "Ellipse",
|
||||
"~:width": 299.99999499321,
|
||||
"~:type": "~:circle",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1171.99998355202,
|
||||
"~:y": 457.999994456768
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1471.99997854523,
|
||||
"~:y": 457.999994456768
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1471.99997854523,
|
||||
"~:y": 757.999989449978
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1171.99998355202,
|
||||
"~:y": 757.999989449978
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:proportion-lock": false,
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1,
|
||||
"~:b": 0,
|
||||
"~:c": 0,
|
||||
"~:d": 1,
|
||||
"~:e": 0,
|
||||
"~:f": 0
|
||||
}
|
||||
},
|
||||
"~:id": "~ub952fb5e-cae5-8054-8007-b11dd63f79fa",
|
||||
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes": [
|
||||
{
|
||||
"~:stroke-alignment": "~:inner",
|
||||
"~:stroke-style": "~:solid",
|
||||
"~:stroke-color": "#000000",
|
||||
"~:stroke-opacity": 1,
|
||||
"~:stroke-width": 400
|
||||
}
|
||||
],
|
||||
"~:x": 1171.99998355202,
|
||||
"~:proportion": 1,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 1171.99998355202,
|
||||
"~:y": 457.999994456768,
|
||||
"~:width": 299.99999499321,
|
||||
"~:height": 299.99999499321,
|
||||
"~:x1": 1171.99998355202,
|
||||
"~:y1": 457.999994456768,
|
||||
"~:x2": 1471.99997854523,
|
||||
"~:y2": 757.999989449978
|
||||
}
|
||||
},
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color": "#B1B2B5",
|
||||
"~:fill-opacity": 1
|
||||
}
|
||||
],
|
||||
"~:flip-x": null,
|
||||
"~:height": 299.99999499321,
|
||||
"~:flip-y": null
|
||||
}
|
||||
},
|
||||
"~ub952fb5e-cae5-8054-8007-b11dd63f79fb": {
|
||||
"~#shape": {
|
||||
"~:y": 444,
|
||||
"~:hide-fill-on-export": false,
|
||||
"~:transform": {
|
||||
"~#matrix": {
|
||||
"~:a": 1,
|
||||
"~:b": 0,
|
||||
"~:c": 0,
|
||||
"~:d": 1,
|
||||
"~:e": 0,
|
||||
"~:f": 0
|
||||
}
|
||||
},
|
||||
"~:rotation": 0,
|
||||
"~:hide-in-viewer": false,
|
||||
"~:name": "Board",
|
||||
"~:width": 100,
|
||||
"~:type": "~:frame",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 989,
|
||||
"~:y": 444
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1089,
|
||||
"~:y": 444
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1089,
|
||||
"~:y": 544
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 989,
|
||||
"~:y": 544
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:r2": 0,
|
||||
"~:proportion-lock": false,
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1,
|
||||
"~:b": 0,
|
||||
"~:c": 0,
|
||||
"~:d": 1,
|
||||
"~:e": 0,
|
||||
"~:f": 0
|
||||
}
|
||||
},
|
||||
"~:r3": 0,
|
||||
"~:r1": 0,
|
||||
"~:id": "~ub952fb5e-cae5-8054-8007-b11dd63f79fb",
|
||||
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes": [
|
||||
{
|
||||
"~:stroke-alignment": "~:inner",
|
||||
"~:stroke-style": "~:solid",
|
||||
"~:stroke-color": "#000000",
|
||||
"~:stroke-opacity": 1,
|
||||
"~:stroke-width": 200
|
||||
}
|
||||
],
|
||||
"~:x": 989,
|
||||
"~:proportion": 1,
|
||||
"~:r4": 0,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 989,
|
||||
"~:y": 444,
|
||||
"~:width": 100,
|
||||
"~:height": 100,
|
||||
"~:x1": 989,
|
||||
"~:y1": 444,
|
||||
"~:x2": 1089,
|
||||
"~:y2": 544
|
||||
}
|
||||
},
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color": "#FFFFFF",
|
||||
"~:fill-opacity": 1
|
||||
}
|
||||
],
|
||||
"~:flip-x": null,
|
||||
"~:height": 100,
|
||||
"~:flip-y": null,
|
||||
"~:shapes": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"~:id": "~ueffcbebc-b8c8-802f-8007-b11dd34fe191",
|
||||
"~:name": "Page 1"
|
||||
}
|
||||
},
|
||||
"~:id": "~ueffcbebc-b8c8-802f-8007-b11dd34fe190",
|
||||
"~:options": {
|
||||
"~:components-v2": true,
|
||||
"~:base-font-size": "16px"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,4 +475,19 @@ test("BUG 13551 - Blurs affecting other elements", async ({
|
||||
maxDiffPixelRatio: 0,
|
||||
threshold: 0.1,
|
||||
});
|
||||
});
|
||||
|
||||
test("BUG 13610 - Huge inner strokes", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = new WasmWorkspacePage(page);
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockGetFile("render-wasm/get-file-huge-inner-strokes.json");
|
||||
|
||||
await workspace.goToWorkspace({
|
||||
id: "effcbebc-b8c8-802f-8007-b11dd34fe190",
|
||||
pageId: "effcbebc-b8c8-802f-8007-b11dd34fe191",
|
||||
});
|
||||
await workspace.waitForFirstRenderWithoutUI();
|
||||
await expect(workspace.canvas).toHaveScreenshot();
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -41,8 +41,8 @@ fn draw_stroke_on_rect(
|
||||
}
|
||||
};
|
||||
|
||||
// By default just draw the rect. Only dotted inner/outer strokes need
|
||||
// clipping to prevent the dotted pattern from appearing in wrong areas.
|
||||
// Dotted inner/outer strokes need clipping to prevent the dotted
|
||||
// pattern from appearing in wrong areas.
|
||||
if let Some(clip_op) = stroke.clip_op() {
|
||||
// Use a neutral layer (no extra paint) so opacity and filters
|
||||
// come solely from the stroke paint. This avoids applying
|
||||
@@ -60,6 +60,35 @@ fn draw_stroke_on_rect(
|
||||
}
|
||||
draw_stroke();
|
||||
canvas.restore();
|
||||
} else if stroke.kind == StrokeKind::Inner
|
||||
&& (stroke.width >= rect.width() || stroke.width >= rect.height())
|
||||
{
|
||||
// When the inner stroke width exceeds a shape dimension, the inset
|
||||
// rect goes negative and the stroke overflows outside the shape.
|
||||
// Fall back to the same approach as the SVG renderer: draw with
|
||||
// doubled width centered on the original shape and clip to it.
|
||||
canvas.save();
|
||||
match corners {
|
||||
Some(radii) => {
|
||||
let rrect = RRect::new_rect_radii(*rect, radii);
|
||||
canvas.clip_rrect(rrect, skia::ClipOp::Intersect, antialias);
|
||||
}
|
||||
None => {
|
||||
canvas.clip_rect(*rect, skia::ClipOp::Intersect, antialias);
|
||||
}
|
||||
}
|
||||
let mut inner_paint = paint.clone();
|
||||
inner_paint.set_stroke_width(stroke.width * 2.0);
|
||||
match corners {
|
||||
Some(radii) => {
|
||||
let rrect = RRect::new_rect_radii(*rect, radii);
|
||||
canvas.draw_rrect(rrect, &inner_paint);
|
||||
}
|
||||
None => {
|
||||
canvas.draw_rect(*rect, &inner_paint);
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
} else {
|
||||
draw_stroke();
|
||||
}
|
||||
@@ -83,8 +112,8 @@ fn draw_stroke_on_circle(
|
||||
let filter = compose_filters(blur, shadow);
|
||||
paint.set_image_filter(filter);
|
||||
|
||||
// By default just draw the circle. Only dotted inner/outer strokes need
|
||||
// clipping to prevent the dotted pattern from appearing in wrong areas.
|
||||
// Dotted inner/outer strokes need clipping to prevent the dotted
|
||||
// pattern from appearing in wrong areas.
|
||||
if let Some(clip_op) = stroke.clip_op() {
|
||||
// Use a neutral layer (no extra paint) so opacity and filters
|
||||
// come solely from the stroke paint. This avoids applying
|
||||
@@ -99,6 +128,24 @@ fn draw_stroke_on_circle(
|
||||
canvas.clip_path(&clip_path, clip_op, antialias);
|
||||
canvas.draw_oval(stroke_rect, &paint);
|
||||
canvas.restore();
|
||||
} else if stroke.kind == StrokeKind::Inner
|
||||
&& (stroke.width >= rect.width() || stroke.width >= rect.height())
|
||||
{
|
||||
// When the inner stroke width exceeds a shape dimension, the inset
|
||||
// rect goes negative and the stroke overflows outside the shape.
|
||||
// Fall back to the same approach as the SVG renderer: draw with
|
||||
// doubled width centered on the original shape and clip to it.
|
||||
canvas.save();
|
||||
let clip_path = {
|
||||
let mut pb = skia::PathBuilder::new();
|
||||
pb.add_oval(rect, None, None);
|
||||
pb.detach()
|
||||
};
|
||||
canvas.clip_path(&clip_path, skia::ClipOp::Intersect, antialias);
|
||||
let mut inner_paint = paint.clone();
|
||||
inner_paint.set_stroke_width(stroke.width * 2.0);
|
||||
canvas.draw_oval(*rect, &inner_paint);
|
||||
canvas.restore();
|
||||
} else {
|
||||
canvas.draw_oval(stroke_rect, &paint);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user