Skip to content
This repository was archived by the owner on May 19, 2026. It is now read-only.

Commit a38d282

Browse files
authored
fix: normalize projective quad matrices (#60)
Co-authored-by: agustin-littlehat <minotopo@gmail.com>
1 parent a31743a commit a38d282

3 files changed

Lines changed: 19 additions & 10 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Public API is **mirrored** across React and Vue. Adding a hook on one side witho
2626

2727
| Tag | Strategy | When chosen | Paint mechanism | Atlas memory |
2828
|---|---|---|---|---|
29-
| `<b>` | **Quads** | Axis-aligned rectangle, or untextured convex quad when the homography passes stability guards | `background: currentColor`; canonical quads use a 1px rectangle mapped by `matrix3d` / projective `matrix3d` with tiny solid bleed to overlap antialias seams | None |
29+
| `<b>` | **Quads** | Axis-aligned rectangle, or untextured convex quad when the homography passes stability guards | `background: currentColor`; affine rects use a 1px rectangle mapped by `matrix3d`; projective quads use a bbox-sized rectangle with a normalized high-precision projective `matrix3d` and tiny solid bleed to overlap antialias seams | None |
3030
| `<i>` | **Border-shape clipped solid** | Untextured non-rect on browsers with CSS `border-shape` (Chromium + `pointer:fine` + `hover:hover`) | `border-color: currentColor` on a fixed 16px border-shape primitive, clipped by `border-shape: polygon(...)`; polygon bbox scale and tiny solid bleed are folded into `matrix3d` | None |
3131
| `<s>` | **Atlas slice** | Textured polygons, or untextured non-rect on browsers without `border-shape` | `background-image` slice of packed bitmap on a canonical 1px primitive; atlas position/size are normalized to the slice, scale lives in `matrix3d`, and shared textured edges get low-alpha atlas pixels repaired during atlas generation | Bounding-rect area |
3232
| `<u>` | **Stable solid triangle** | Opt-in for triangles via `renderPolygonsWithStableTriangles` | CSS border-color triangle trick with a fixed canonical 1px border triangle; tiny solid bleed is folded into `matrix3d` | None |

packages/polycss/src/render/polyDOM.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,8 +1300,8 @@ describe("renderPolygonsWithTextureAtlas — strategies.disable", () => {
13001300
expect(element.tagName.toLowerCase()).toBe("b");
13011301
expect(result.rendered[0].kind).toBe("solid");
13021302
expect(style).toContain("transform:matrix3d(");
1303-
expect(style).not.toContain("width");
1304-
expect(style).not.toContain("height");
1303+
expect(style).toContain("width:");
1304+
expect(style).toContain("height:");
13051305
expect(style).not.toContain("border-shape");
13061306
expect(canvases).toHaveLength(0);
13071307
result.dispose();
@@ -1323,16 +1323,19 @@ describe("renderPolygonsWithTextureAtlas — strategies.disable", () => {
13231323
{ doc },
13241324
);
13251325
const matrix = extractMatrix(result.rendered[0].element);
1326+
const plan = result.rendered[0].plan!;
1327+
const width = plan.canvasW;
1328+
const height = plan.canvasH;
13261329
const expected = NON_RECT_QUAD.vertices.map((vertex): [number, number, number] => [
13271330
vertex[1] * 50,
13281331
vertex[0] * 50,
13291332
vertex[2] * 50,
13301333
]);
13311334

13321335
expectPointClose(transformMatrixPoint(matrix, 0, 0), expected[0]);
1333-
expectPointClose(transformMatrixPoint(matrix, 1, 0), expected[1]);
1334-
expectPointClose(transformMatrixPoint(matrix, 1, 1), expected[2]);
1335-
expectPointClose(transformMatrixPoint(matrix, 0, 1), expected[3]);
1336+
expectPointClose(transformMatrixPoint(matrix, width, 0), expected[1]);
1337+
expectPointClose(transformMatrixPoint(matrix, width, height), expected[2]);
1338+
expectPointClose(transformMatrixPoint(matrix, 0, height), expected[3]);
13361339
result.dispose();
13371340
});
13381341

packages/polycss/src/render/textureAtlas.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,8 @@ function computeProjectiveQuadMatrix(
496496
tx: number,
497497
ty: number,
498498
tz: number,
499+
sourceWidth: number,
500+
sourceHeight: number,
499501
guards: ProjectiveQuadGuardSettings,
500502
): string | null {
501503
if (screenPts.length !== 8) return null;
@@ -518,6 +520,8 @@ function computeProjectiveQuadMatrix(
518520
if (!coeffs) return null;
519521
const { g, h, w1, w3 } = coeffs;
520522
const [q0, q1, , q3] = q;
523+
const sx = Math.max(1, sourceWidth);
524+
const sy = Math.max(1, sourceHeight);
521525

522526
const p0: Vec3 = [
523527
tx + q0[0] * xAxis[0] + q0[1] * yAxis[0],
@@ -531,11 +535,11 @@ function computeProjectiveQuadMatrix(
531535
];
532536

533537
return formatMatrix3dValues([
534-
...projectiveColumn(q1, w1), g,
535-
...projectiveColumn(q3, w3), h,
538+
...projectiveColumn(q1, w1).map((value) => value / sx), g / sx,
539+
...projectiveColumn(q3, w3).map((value) => value / sy), h / sy,
536540
normal[0], normal[1], normal[2], 0,
537541
p0[0], p0[1], p0[2], 1,
538-
]);
542+
], 6);
539543
}
540544

541545
function formatPercent(value: number, decimals = DEFAULT_BORDER_SHAPE_DECIMALS): string {
@@ -1524,6 +1528,8 @@ function computeTextureAtlasPlan(
15241528
tx,
15251529
ty,
15261530
tz,
1531+
canvasW,
1532+
canvasH,
15271533
projectiveQuadGuards,
15281534
)
15291535
: null;
@@ -2750,7 +2756,7 @@ function createProjectiveSolidElement(
27502756
solidPaintDefaults?: SolidPaintDefaults,
27512757
): HTMLElement {
27522758
const el = doc.createElement("b");
2753-
el.setAttribute("style", `transform:matrix3d(${entry.projectiveMatrix})`);
2759+
el.setAttribute("style", `width:${formatCssLength(entry.canvasW)};height:${formatCssLength(entry.canvasH)};transform:matrix3d(${entry.projectiveMatrix})`);
27542760
applyPolygonDataAttrs(el, entry.polygon);
27552761
applySolidPaint(el, entry, textureLighting, solidPaintDefaults);
27562762

0 commit comments

Comments
 (0)