import assert from "node:assert/strict"; import { fileURLToPath } from "node:url"; import { test } from "node:test"; import { build } from "esbuild"; const compiled = await build({ entryPoints: [fileURLToPath(new URL("../src/player/rendertransform.ts", import.meta.url))], bundle: true, write: false, format: "esm", platform: "node", logLevel: "silent" }); const { Matrix4, layoutTransform } = await import(`data:text/javascript;base64,${Buffer.from(compiled.outputFiles[0].text).toString("base64")}`); const close = (a, b, eps = 1e-6) => assert.ok(Math.abs(a - b) < eps, `${a} != ${b}`); test("zoom grows the box and scales the child about its centre; the anchor is a fraction of the grown box", () => { const out = layoutTransform({ zoom: 2, xpos: 0.5, ypos: 1, xanchor: 0.5, yanchor: 1 }, 100, 50, 1920, 1080); close(out.width, 200); close(out.height, 100); close(out.left, 960 - 100); close(out.top, 1080 - 100); assert.equal(out.transform, "matrix3d(2,0,0,0,0,2,0,0,0,0,1,0,0,0,0,1)"); }); test("rotate pads the box to its diagonal (rotate_pad) and rotates about the centre", () => { const out = layoutTransform({ rotate: 90, xpos: 0, ypos: 0 }, 60, 80, 1000, 1000); close(out.width, 100); close(out.height, 100); // the child's centre (30, 40) must land on the box centre (50, 50); rotating 90 degrees clockwise const css = out.transform.match(/matrix3d\((.*)\)/)[1].split(",").map(Number); const x = css[0] * 30 + css[4] * 40 + css[12], y = css[1] * 30 + css[5] * 40 + css[13]; close(x, 50, 1e-4); close(y, 50, 1e-4); // the child's top-left corner (0,0) goes to (50+40, 50-30) close(css[12], 90, 1e-4); close(css[13], 20, 1e-4); }); test("without rotate_pad the box is the bounding box of the rotated child", () => { const out = layoutTransform({ rotate: 90, rotate_pad: false }, 60, 80, 1000, 1000); close(out.width, 80); close(out.height, 60); }); test("transform_anchor measures the anchor in the child and carries it through the transform", () => { const out = layoutTransform({ zoom: 0.5, transform_anchor: true, xanchor: 985, yanchor: 1535, xpos: 0, ypos: 0 }, 2000, 3000, 1920, 1080); close(out.left, -985 * 0.5); close(out.top, -1535 * 0.5); }); test("a negative xzoom flips the child inside the same box", () => { const out = layoutTransform({ xzoom: -1 }, 100, 50, 1000, 1000); close(out.width, 100); assert.equal(out.transform, "matrix3d(-1,0,0,0,0,1,0,0,0,0,1,0,100,0,0,1)"); }); test("crop shows the rectangle and the box takes its size; size and fit scale first", () => { const out = layoutTransform({ crop: [10, 20, 30, 40] }, 100, 100, 1000, 1000); close(out.width, 30); close(out.height, 40); assert.equal(out.clip, "inset(20px 60px 40px 10px)"); const fit = layoutTransform({ xysize: [200, 200], fit: "contain" }, 100, 50, 1000, 1000); close(fit.width, 200); close(fit.height, 100); }); test("matrix products follow Ren'Py's order: a.mul(b) applies b first, and 3D rotation about the centre", () => { const m = Matrix4.offset(10, 0).mul(Matrix4.scale(2, 2, 1)); assert.deepEqual(m.transform(1, 1).slice(0, 2), [12, 2]); const out = layoutTransform({ yrotate: 180 }, 100, 50, 1000, 1000); const css = out.transform.match(/matrix3d\((.*)\)/)[1].split(",").map(Number); close(css[0], -1, 1e-5); // the picture is seen from behind: mirrored });