xbintsc 0.5.41 → 0.5.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/driver/version.d.ts +1 -1
  3. package/dist/src/driver/version.js +1 -1
  4. package/dist/tests/e2e/gui-data-url.test.d.ts +10 -0
  5. package/dist/tests/e2e/gui-data-url.test.js +127 -0
  6. package/dist/tests/e2e/gui-data-url.test.js.map +1 -0
  7. package/dist/tests/e2e/gui-effects.test.js +144 -4
  8. package/dist/tests/e2e/gui-effects.test.js.map +1 -1
  9. package/dist/tests/e2e/gui-paint.test.js +77 -0
  10. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  11. package/doc/gui/api.md +1 -1
  12. package/doc/gui/canvas.md +6 -2
  13. package/doc/gui/known-issues.md +18 -10
  14. package/doc/gui/media.md +11 -2
  15. package/doc/gui/rendering.md +48 -19
  16. package/doc/gui.md +12 -8
  17. package/doc/zh-CN/gui/api.md +1 -1
  18. package/doc/zh-CN/gui/canvas.md +6 -4
  19. package/doc/zh-CN/gui/known-issues.md +12 -8
  20. package/doc/zh-CN/gui/media.md +9 -2
  21. package/doc/zh-CN/gui/rendering.md +37 -15
  22. package/doc/zh-CN/gui.md +7 -6
  23. package/package.json +7 -7
  24. package/runtime/ext_gui/dom_api_canvas.cpp +42 -24
  25. package/runtime/ext_gui/gradient_values.cpp +44 -48
  26. package/runtime/ext_gui/gui_events.cpp +16 -13
  27. package/runtime/ext_gui/image.cpp +180 -25
  28. package/runtime/ext_gui/image.h +6 -4
  29. package/runtime/ext_gui/paint.cpp +69 -4
  30. package/runtime/ext_gui/paint.h +57 -1
  31. package/runtime/ext_gui/paint_background.cpp +197 -41
  32. package/runtime/ext_gui/paint_effects.cpp +2 -1
  33. package/runtime/ext_gui/paint_internal.h +5 -1
  34. package/runtime/ext_gui/renderer.cpp +81 -13
  35. package/runtime/ext_gui/renderer.h +19 -5
  36. package/runtime/ext_gui/renderer_gradient.cpp +156 -0
  37. package/runtime/ext_gui/renderer_image.cpp +6 -1
  38. package/runtime/ext_gui/renderer_internal.h +48 -0
  39. package/runtime/ext_gui/renderer_layer.cpp +552 -0
  40. package/runtime/ext_gui/style.h +29 -1
  41. package/runtime/ext_gui/style_print.cpp +10 -2
  42. package/runtime/ext_gui/style_values.cpp +10 -0
  43. package/src/driver/version.ts +1 -1
@@ -4,4 +4,4 @@
4
4
  * It lives apart from the command dispatch so the executable entry point stays
5
5
  * under the repository's per-file line budget; the text is data, not logic.
6
6
  */
7
- export declare const HELP = "xbintsc 0.5.41 - TypeScript binary compiler\n\nUsage:\n xbintsc build <file.ts> [options] Compile to a native binary\n xbintsc run <file.ts> [-- args] Compile and execute\n xbintsc check <file.ts> Parse, bind and check without linking\n xbintsc watch <file.ts> [options] Rebuild whenever the source tree changes\n xbintsc emit <file.ts> Print LLVM IR\n xbintsc doctor Report the resolved toolchain\n xbintsc version Print the version\n xbintsc help Show this message\n\nOptions:\n -o, --output <path> Output path\n --out <dir> Output directory (default: build/)\n --emit <kind> exe | obj | ir (default: exe)\n -O0..-O3 Optimization level (default: -O2)\n --ext <names> Comma separated extensions (e.g. node,gui,media)\n --ext-native <m> Register a C++/Rust extension from a JSON manifest\n (comma separated for several)\n --config <path> Use a project config (default: auto-detect\n xbintsc.config.json)\n --no-config Do not read any project config\n --icon <path> Embed an application icon (PNG/ICO/ICNS)\n --bundle macOS: also produce a .app bundle\n --app-name <name> Bundle / display name\n --app-id <id> macOS bundle identifier (e.g. com.example.demo)\n --force Ignore the incremental cache\n --verbose Print progress information\n --time Print per-phase build timings\n --debug Emit debug info (DWARF/CodeView) and keep frame pointers\n --check Run the semantic checker (default)\n --no-check Skip the semantic checker; only erase type syntax\n --lto Compile program and runtime to LTO bitcode; link with lld\n --pgo-generate [dir] Build an instrumented binary writing profiles to dir\n --pgo-use [dir] Build with the profiles collected from --pgo-generate\n --json Print diagnostics as a JSON array on stdout\n --no-hints Do not append the \"hint: \u2026 see <doc>\" line to failures\n\nxbintsc compiles a subset of TypeScript to a native binary. Types are erased;\nonly inference-free facts (names, redeclarations, call arity) are checked.\nNode built-ins need --ext node, packages in node_modules are bundled at build\ntime, and clang 16+ is required to link.\n\nInstalled as a project dependency (npm):\n npx xbintsc doctor\n npx xbintsc build <entry.ts> --out build\n npx xbintsc run <entry.ts>\nThe first build compiles the C runtime; later builds reuse the cache.\n\nDocs: doc/ai/README.md (task guide) - doc/ai/language-support.md (subset) -\ndoc/ai/troubleshooting.md (failures) - llms.txt (index of every document).\nWhen installed from npm, these live under node_modules/xbintsc/doc/ and\nnode_modules/xbintsc/doc/npm-package.md is the project guide.\n";
7
+ export declare const HELP = "xbintsc 0.5.43 - TypeScript binary compiler\n\nUsage:\n xbintsc build <file.ts> [options] Compile to a native binary\n xbintsc run <file.ts> [-- args] Compile and execute\n xbintsc check <file.ts> Parse, bind and check without linking\n xbintsc watch <file.ts> [options] Rebuild whenever the source tree changes\n xbintsc emit <file.ts> Print LLVM IR\n xbintsc doctor Report the resolved toolchain\n xbintsc version Print the version\n xbintsc help Show this message\n\nOptions:\n -o, --output <path> Output path\n --out <dir> Output directory (default: build/)\n --emit <kind> exe | obj | ir (default: exe)\n -O0..-O3 Optimization level (default: -O2)\n --ext <names> Comma separated extensions (e.g. node,gui,media)\n --ext-native <m> Register a C++/Rust extension from a JSON manifest\n (comma separated for several)\n --config <path> Use a project config (default: auto-detect\n xbintsc.config.json)\n --no-config Do not read any project config\n --icon <path> Embed an application icon (PNG/ICO/ICNS)\n --bundle macOS: also produce a .app bundle\n --app-name <name> Bundle / display name\n --app-id <id> macOS bundle identifier (e.g. com.example.demo)\n --force Ignore the incremental cache\n --verbose Print progress information\n --time Print per-phase build timings\n --debug Emit debug info (DWARF/CodeView) and keep frame pointers\n --check Run the semantic checker (default)\n --no-check Skip the semantic checker; only erase type syntax\n --lto Compile program and runtime to LTO bitcode; link with lld\n --pgo-generate [dir] Build an instrumented binary writing profiles to dir\n --pgo-use [dir] Build with the profiles collected from --pgo-generate\n --json Print diagnostics as a JSON array on stdout\n --no-hints Do not append the \"hint: \u2026 see <doc>\" line to failures\n\nxbintsc compiles a subset of TypeScript to a native binary. Types are erased;\nonly inference-free facts (names, redeclarations, call arity) are checked.\nNode built-ins need --ext node, packages in node_modules are bundled at build\ntime, and clang 16+ is required to link.\n\nInstalled as a project dependency (npm):\n npx xbintsc doctor\n npx xbintsc build <entry.ts> --out build\n npx xbintsc run <entry.ts>\nThe first build compiles the C runtime; later builds reuse the cache.\n\nDocs: doc/ai/README.md (task guide) - doc/ai/language-support.md (subset) -\ndoc/ai/troubleshooting.md (failures) - llms.txt (index of every document).\nWhen installed from npm, these live under node_modules/xbintsc/doc/ and\nnode_modules/xbintsc/doc/npm-package.md is the project guide.\n";
@@ -9,4 +9,4 @@
9
9
  * `main`/`master` only (see `.githooks/pre-commit`), so feature branches never
10
10
  * change this line and merging two of them cannot conflict here.
11
11
  */
12
- export declare const COMPILER_VERSION = "0.5.41";
12
+ export declare const COMPILER_VERSION = "0.5.43";
@@ -9,5 +9,5 @@
9
9
  * `main`/`master` only (see `.githooks/pre-commit`), so feature branches never
10
10
  * change this line and merging two of them cannot conflict here.
11
11
  */
12
- export const COMPILER_VERSION = "0.5.41";
12
+ export const COMPILER_VERSION = "0.5.43";
13
13
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * End-to-end tests for `data:` image URLs: raster and SVG, base64 and
3
+ * percent-encoded payloads, in `<img>`, `canvas.drawImage(String, …)` and CSS
4
+ * `background-image`.
5
+ *
6
+ * The SVG below deliberately exercises the decoder, not NanoSVG's feature set:
7
+ * the engine rasterises NanoSVG's subset, so an SVG that uses filters still
8
+ * decodes but renders the shapes without the filter effect.
9
+ */
10
+ export {};
@@ -0,0 +1,127 @@
1
+ /**
2
+ * End-to-end tests for `data:` image URLs: raster and SVG, base64 and
3
+ * percent-encoded payloads, in `<img>`, `canvas.drawImage(String, …)` and CSS
4
+ * `background-image`.
5
+ *
6
+ * The SVG below deliberately exercises the decoder, not NanoSVG's feature set:
7
+ * the engine rasterises NanoSVG's subset, so an SVG that uses filters still
8
+ * decodes but renders the shapes without the filter effect.
9
+ */
10
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
11
+ import { createGuiHarness, guiAvailable } from "./gui-helpers.js";
12
+ const harness = createGuiHarness();
13
+ /* 4x4 PNG: left half red, right half blue. */
14
+ const PNG = "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAFElEQVR42mP4z8DwH4Sh1H8G0gUALFAf4eNWqTEAAAAASUVORK5CYII=";
15
+ const SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="8" height="6"><rect width="8" height="6" fill="#ff0000"/></svg>';
16
+ const SVG_B64 = Buffer.from(SVG).toString("base64");
17
+ const SVG_ENC = encodeURIComponent(SVG);
18
+ /** The `feTurbulence` example from the request: `<filter>` is not in NanoSVG's
19
+ * subset, but the document still decodes (the rect renders, unfiltered). */
20
+ const NOISE = "data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.8'/%3E%3C/svg%3E";
21
+ const PAGE = `
22
+ <html><head><style>body { margin: 0; }</style></head><body>
23
+ <img id="vec" src="data:image/svg+xml,${SVG_ENC}">
24
+ <img id="vec64" src="data:image/svg+xml;base64,${SVG_B64}">
25
+ <img id="noise" src="${NOISE}">
26
+ <div id="bg" style="width:20px;height:20px"></div>
27
+ <canvas id="c" width="240" height="240"></canvas>
28
+ </body></html>`;
29
+ describe.skipIf(!guiAvailable)("gui extension — data URL images", () => {
30
+ beforeAll(() => harness.setup());
31
+ afterAll(() => harness.cleanup());
32
+ it("decodes SVG data URLs in <img>, drawImage and CSS backgrounds", () => {
33
+ const { value, stderr } = harness.compileAndRun("data-url-svg", `
34
+ import { createWindow, run } from "gui";
35
+
36
+ const win = createWindow({ title: "data-url", width: 500, height: 400 });
37
+ win.on("ready", () => {
38
+ const doc: any = document;
39
+ const ctx: any = doc.getElementById("c").getContext("2d");
40
+ const px = (x: number, y: number): string => {
41
+ const d = ctx.getImageData(x, y, 1, 1).data;
42
+ return d[0] + "," + d[1] + "," + d[2] + "," + d[3];
43
+ };
44
+ const clear = (): void => { ctx.clearRect(0, 0, 240, 240); };
45
+ const box = (id: string): string => {
46
+ const r: any = doc.getElementById(id).getBoundingClientRect();
47
+ return r.width + "x" + r.height;
48
+ };
49
+
50
+ const png = "data:image/png;base64,${PNG}";
51
+ const svgEnc = "data:image/svg+xml,${SVG_ENC}";
52
+ const svg64 = "data:image/svg+xml;base64,${SVG_B64}";
53
+ const noise = "${NOISE}";
54
+
55
+ console.log("vec-box=" + box("vec"));
56
+ console.log("vec64-box=" + box("vec64"));
57
+ console.log("noise-box=" + box("noise"));
58
+
59
+ // A raster data URL drawn from its string, without an <img>.
60
+ clear();
61
+ ctx.drawImage(png, 0, 0);
62
+ console.log("png-string=" + px(0, 0) + ":" + px(3, 0));
63
+
64
+ // SVG data URLs in both encodings, as a string and as an <img>.
65
+ clear();
66
+ ctx.drawImage(svgEnc, 0, 0);
67
+ console.log("svg-enc-string=" + px(0, 0) + ":" + px(7, 5));
68
+
69
+ clear();
70
+ ctx.drawImage(svg64, 0, 0);
71
+ console.log("svg64-string=" + px(0, 0));
72
+
73
+ clear();
74
+ ctx.drawImage(svgEnc, 0, 0, 16, 12);
75
+ console.log("svg-scaled=" + px(0, 0) + ":" + px(15, 11));
76
+
77
+ clear();
78
+ ctx.drawImage(doc.getElementById("vec"), 0, 0);
79
+ console.log("svg-img=" + px(0, 0));
80
+
81
+ clear();
82
+ ctx.drawImage(doc.getElementById("vec64"), 0, 0);
83
+ console.log("svg64-img=" + px(0, 0));
84
+
85
+ // The request's feTurbulence document: it decodes (opaque rect), while
86
+ // the filter itself remains outside NanoSVG's subset.
87
+ clear();
88
+ ctx.drawImage(noise, 0, 0, 32, 32);
89
+ console.log("noise-alpha=" + ctx.getImageData(0, 0, 1, 1).data[3]);
90
+
91
+ // SVG data URL as a CSS background.
92
+ const bg: any = doc.getElementById("bg");
93
+ bg.style.backgroundImage = 'url("' + svgEnc + '")';
94
+ console.log("bg-css=" + getComputedStyle(bg).backgroundImage.substring(0, 26));
95
+ let hits = 0;
96
+ for (const line of win.paintList().split("\\n")) {
97
+ if (line.indexOf("src=data:image/svg+xml") >= 0) hits++;
98
+ }
99
+ console.log("paint-svg=" + hits);
100
+
101
+ // Let one rendered frame decode the background texture (stderr reports
102
+ // a failure), then close.
103
+ requestAnimationFrame(() => {
104
+ requestAnimationFrame(() => {
105
+ win.close();
106
+ });
107
+ });
108
+ });
109
+ win.loadHTML(\`${PAGE}\`);
110
+ run();
111
+ `, { autocloseMs: 3000 });
112
+ expect(value("vec-box")).toBe("8x6");
113
+ expect(value("vec64-box")).toBe("8x6");
114
+ expect(value("noise-box")).toBe("180x180");
115
+ expect(value("png-string")).toBe("255,0,0,255:0,0,255,255");
116
+ expect(value("svg-enc-string")).toBe("255,0,0,255:255,0,0,255");
117
+ expect(value("svg64-string")).toBe("255,0,0,255");
118
+ expect(value("svg-scaled")).toBe("255,0,0,255:255,0,0,255");
119
+ expect(value("svg-img")).toBe("255,0,0,255");
120
+ expect(value("svg64-img")).toBe("255,0,0,255");
121
+ expect(Number(value("noise-alpha"))).toBeGreaterThan(0);
122
+ expect(value("bg-css")).toMatch(/^url\(data:image\/svg\+xml/);
123
+ expect(value("paint-svg")).toBe("4");
124
+ expect(stderr).not.toContain("could not decode image");
125
+ });
126
+ });
127
+ //# sourceMappingURL=gui-data-url.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gui-data-url.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-data-url.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAElE,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;AAEnC,8CAA8C;AAC9C,MAAM,GAAG,GACP,0GAA0G,CAAC;AAC7G,MAAM,GAAG,GACP,gHAAgH,CAAC;AACnH,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACpD,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;AAExC;4EAC4E;AAC5E,MAAM,KAAK,GACT,4SAA4S,CAAC;AAE/S,MAAM,IAAI,GAAG;;wCAE2B,OAAO;iDACE,OAAO;uBACjC,KAAK;;;eAGb,CAAC;AAEhB,QAAQ,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,iCAAiC,EAAE,GAAG,EAAE;IACrE,SAAS,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAElC,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,cAAc,EACd;;;;;;;;;;;;;;;;;6CAiBuC,GAAG;6CACH,OAAO;mDACD,OAAO;yBACjC,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAwDP,IAAI;;OAEpB,EACD,EAAE,WAAW,EAAE,IAAI,EAAE,CACtB,CAAC;QAEF,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QAChE,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAClD,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC;QAC9D,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -130,13 +130,153 @@ describe.skipIf(!guiAvailable)("gui extension — effects", () => {
130
130
  const list = stdout.slice(stdout.indexOf("list-start"), stdout.indexOf("list-end"));
131
131
  expect(value("image")).toContain("radial-gradient()");
132
132
  expect(value("image")).toContain("linear-gradient()");
133
- // Both layers are painted: the red radial core and its green outer ring
134
- // (the linear blue layer is underneath them).
133
+ // The radial layer is one rasterised gradient carrying its stops; the linear
134
+ // layer is still painted as solid bands.
135
+ expect(list).toContain("gradient=radial");
135
136
  expect(list).toContain("#ff0000ff");
136
137
  expect(list).toContain("#00ff00ff");
137
138
  expect(list).toMatch(/#0000[0-9a-f]{2}ff/);
138
- // Radial bands plus the linear strips.
139
- expect(Number(value("count"))).toBeGreaterThan(30);
139
+ // Eighteen linear strips plus the background colour: the gradient image is
140
+ // not a stack of rectangles.
141
+ expect(Number(value("count"))).toBe(19);
142
+ });
143
+ it("rasterises a translucent radial vignette as one gradient image", () => {
144
+ const { value, stdout } = harness.compileAndRun("vignette", `
145
+ import { createWindow, run } from "gui";
146
+
147
+ const HTML = \`
148
+ <html><head><style>
149
+ body { margin: 0; }
150
+ #scene { position: relative; width: 200px; height: 100px; background: #3366cc; }
151
+ #dim {
152
+ position: absolute; inset: -5%; z-index: 2; pointer-events: none;
153
+ background: radial-gradient(ellipse at center,
154
+ transparent 45%, rgba(0, 0, 0, 0.46) 76%, rgba(0, 0, 0, 0.88) 100%);
155
+ }
156
+ </style></head><body>
157
+ <div id="scene"><div id="dim"></div></div>
158
+ </body></html>
159
+ \`;
160
+
161
+ const win = createWindow({ title: "vignette", width: 400, height: 300 });
162
+ win.on("ready", () => {
163
+ console.log("image=" + win.computedStyle("#dim", "background-image"));
164
+ console.log("count=" + win.paintCount());
165
+ console.log("list-start");
166
+ console.log(win.paintList());
167
+ console.log("list-end");
168
+ win.close();
169
+ });
170
+ win.loadHTML(HTML);
171
+ run();
172
+ `);
173
+ const list = stdout.slice(stdout.indexOf("list-start"), stdout.indexOf("list-end"));
174
+ expect(value("image")).toContain("radial-gradient()");
175
+ // A single op whose stops keep the alpha ramp. Painting this as nested
176
+ // translucent ellipses composited the alpha once per band and turned the
177
+ // whole scene solid black.
178
+ expect((list.match(/gradient=radial/g) ?? []).length).toBe(1);
179
+ expect(list).toContain("#00000000@0.4500");
180
+ expect(list).toContain("#000000e0@1.0000");
181
+ // Only the scene background is a rectangle; the vignette is not bands.
182
+ expect(value("count")).toBe("1");
183
+ });
184
+ it("tiles repeating gradients across the whole box", () => {
185
+ const { value, stdout } = harness.compileAndRun("repeating-gradient", `
186
+ import { createWindow, run } from "gui";
187
+
188
+ const HTML = \`
189
+ <html><head><style>
190
+ body { margin: 0; }
191
+ #r {
192
+ width: 100px; height: 40px;
193
+ background: repeating-linear-gradient(90deg,
194
+ #ff0000 0%, #ff0000 10%, #0000ff 10%, #0000ff 20%);
195
+ }
196
+ </style></head><body><div id="r"></div></body></html>
197
+ \`;
198
+
199
+ const win = createWindow({ title: "repeating-gradient", width: 400, height: 300 });
200
+ win.on("ready", () => {
201
+ console.log("image=" + win.computedStyle("#r", "background-image"));
202
+ console.log("list-start");
203
+ console.log(win.paintList());
204
+ console.log("list-end");
205
+ win.close();
206
+ });
207
+ win.loadHTML(HTML);
208
+ run();
209
+ `);
210
+ const list = stdout.slice(stdout.indexOf("list-start"), stdout.indexOf("list-end"));
211
+ // The computed value keeps the repeating function name ...
212
+ expect(value("image")).toBe("repeating-linear-gradient()");
213
+ // ... and the whole 100px box is tiled: a 20% period gives five red and
214
+ // five blue bands, not one ramp that holds the final colour.
215
+ expect((list.match(/#ff0000ff/g) ?? []).length).toBeGreaterThan(4);
216
+ expect((list.match(/#0000ffff/g) ?? []).length).toBeGreaterThan(4);
217
+ });
218
+ it("resolves pixel offsets in repeating gradients", () => {
219
+ const { value, stdout } = harness.compileAndRun("repeating-gradient-px", `
220
+ import { createWindow, run } from "gui";
221
+
222
+ const HTML = \`
223
+ <html><head><style>
224
+ body { margin: 0; }
225
+ #r {
226
+ width: 100px; height: 20px;
227
+ background: repeating-linear-gradient(90deg,
228
+ #123456 0px 10px, #abcdef 10px 20px);
229
+ }
230
+ </style></head><body><div id="r"></div></body></html>
231
+ \`;
232
+
233
+ const win = createWindow({ title: "repeating-gradient-px", width: 400, height: 300 });
234
+ win.on("ready", () => {
235
+ console.log("image=" + win.computedStyle("#r", "background-image"));
236
+ console.log("list-start");
237
+ console.log(win.paintList());
238
+ console.log("list-end");
239
+ win.close();
240
+ });
241
+ win.loadHTML(HTML);
242
+ run();
243
+ `);
244
+ const list = stdout.slice(stdout.indexOf("list-start"), stdout.indexOf("list-end"));
245
+ expect(value("image")).toBe("repeating-linear-gradient()");
246
+ expect((list.match(/#123456ff/g) ?? []).length).toBeGreaterThan(4);
247
+ expect((list.match(/#abcdefff/g) ?? []).length).toBeGreaterThan(4);
248
+ });
249
+ it("tiles repeating radial gradients", () => {
250
+ const { value, stdout } = harness.compileAndRun("repeating-radial-gradient", `
251
+ import { createWindow, run } from "gui";
252
+
253
+ const HTML = \`
254
+ <html><head><style>
255
+ body { margin: 0; }
256
+ #r {
257
+ width: 40px; height: 40px;
258
+ background: repeating-radial-gradient(circle at 50% 50%,
259
+ #ff0000 0%, #ff0000 5%, #0000ff 5%, #0000ff 10%);
260
+ }
261
+ </style></head><body><div id="r"></div></body></html>
262
+ \`;
263
+
264
+ const win = createWindow({ title: "repeating-radial-gradient", width: 400, height: 300 });
265
+ win.on("ready", () => {
266
+ console.log("image=" + win.computedStyle("#r", "background-image"));
267
+ console.log("list-start");
268
+ console.log(win.paintList());
269
+ console.log("list-end");
270
+ win.close();
271
+ });
272
+ win.loadHTML(HTML);
273
+ run();
274
+ `);
275
+ const list = stdout.slice(stdout.indexOf("list-start"), stdout.indexOf("list-end"));
276
+ expect(value("image")).toBe("repeating-radial-gradient()");
277
+ expect(list).toContain("gradient=repeating-radial");
278
+ expect(list).toContain("#ff0000ff");
279
+ expect(list).toContain("#0000ffff");
140
280
  });
141
281
  it("paints text-shadow copies behind the text", () => {
142
282
  const { value, stdout } = harness.compileAndRun("text-shadow", `
@@ -1 +1 @@
1
- {"version":3,"file":"gui-effects.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-effects.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gui-effects.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-effects.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAElE,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;AAEnC,QAAQ,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,yBAAyB,EAAE,GAAG,EAAE;IAC7D,SAAS,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAElC,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CACrC,OAAO,EACP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BC,CACF,CAAC;QACF,wEAAwE;QACxE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,QAAQ,EACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;QACpF,qDAAqD;QACrD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpC,qEAAqE;QACrE,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACpC,0DAA0D;QAC1D,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACxC,2EAA2E;QAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QACnC,wEAAwE;QACxE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,aAAa,EACb;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QACtD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QACtD,6EAA6E;QAC7E,yCAAyC;QACzC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAC1C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAC3C,2EAA2E;QAC3E,6BAA6B;QAC7B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,UAAU,EACV;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QACtD,uEAAuE;QACvE,yEAAyE;QACzE,2BAA2B;QAC3B,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9D,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;QAC3C,uEAAuE;QACvE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,oBAAoB,EACpB;;;;;;;;;;;;;;;;;;;;;;;;OAwBC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;QACpF,2DAA2D;QAC3D,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC3D,wEAAwE;QACxE,6DAA6D;QAC7D,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACnE,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,uBAAuB,EACvB;;;;;;;;;;;;;;;;;;;;;;;;OAwBC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC3D,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACnE,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,2BAA2B,EAC3B;;;;;;;;;;;;;;;;;;;;;;;;OAwBC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC3D,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;QACpD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,aAAa,EACb;;;;;;;;;;;;;;;;;;;;OAoBC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,+BAA+B,CAAC,CAAC;QACxD,oDAAoD;QACpD,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,WAAW,EACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC3C,oEAAoE;QACpE,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,QAAQ,EACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QAC7D,2EAA2E;QAC3E,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QAC5D,+DAA+D;QAC/D,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACpC,yCAAyC;QACzC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACpC,gEAAgE;QAChE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAClD,4DAA4D;QAC5D,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -187,5 +187,82 @@ describe.skipIf(!guiAvailable)("gui extension — paint", () => {
187
187
  // Anything above 0 is painted in full (there is no partial compositing yet).
188
188
  expect(list).toContain("#ff0000ff");
189
189
  });
190
+ it("applies fractional `opacity` to every painted colour in the subtree", () => {
191
+ const { value, stdout } = harness.compileAndRun("opacity-half", `
192
+ import { createWindow, run } from "gui";
193
+
194
+ const HTML = \`
195
+ <html><head><style>
196
+ body { margin: 0; background-color: #000000; }
197
+ #half { width: 40px; height: 20px; background: #ff0000; opacity: 0.5; }
198
+ #nested { width: 20px; height: 10px; background: #00ff00; color: #00ff00; opacity: 0.5; }
199
+ #label { color: #ffff00; }
200
+ </style></head><body>
201
+ <div id="half"><div id="nested"><span id="label">faded</span></div></div>
202
+ </body></html>
203
+ \`;
204
+
205
+ const win = createWindow({ title: "opacity-half", width: 400, height: 300 });
206
+ win.on("ready", () => {
207
+ console.log("opacity=" + win.computedStyle("#nested", "opacity"));
208
+ console.log("list-start");
209
+ console.log(win.paintList());
210
+ console.log("list-end");
211
+ win.close();
212
+ });
213
+ win.loadHTML(HTML);
214
+ run();
215
+ `);
216
+ const list = stdout.slice(stdout.indexOf("list-start"), stdout.indexOf("list-end"));
217
+ expect(value("opacity")).toBe("0.5");
218
+ // #half's red background, then #nested's 0.5 on top of it, then the text's
219
+ // inherited 0.25: alpha is multiplied down the paint chain instead of
220
+ // treating any value above zero as opaque.
221
+ expect(list).toContain("color=#ff000080");
222
+ expect(list).toContain("color=#00ff0040");
223
+ expect(list).toContain("color=#ffff0040");
224
+ });
225
+ it("wraps `mix-blend-mode` boxes in a compositing layer", () => {
226
+ const { value, stdout, stderr } = harness.compileAndRun("mix-blend-mode", `
227
+ import { createWindow, run } from "gui";
228
+
229
+ const HTML = \`
230
+ <html><head><style>
231
+ body { margin: 0; }
232
+ #card { width: 100px; height: 100px; background: #ffffff; }
233
+ #multiply { width: 50px; height: 50px; background: #ff0000; mix-blend-mode: multiply; }
234
+ #overlay { width: 20px; height: 20px; background: #00ff00; mix-blend-mode: overlay; }
235
+ </style></head><body>
236
+ <div id="card"><div id="multiply"><div id="overlay"></div></div></div>
237
+ </body></html>
238
+ \`;
239
+
240
+ const win = createWindow({ title: "mix-blend-mode", width: 400, height: 300 });
241
+ win.on("ready", () => {
242
+ console.log("multiply=" + win.computedStyle("#multiply", "mix-blend-mode"));
243
+ console.log("overlay=" + win.computedStyle("#overlay", "mix-blend-mode"));
244
+ console.log("list-start");
245
+ console.log(win.paintList());
246
+ console.log("list-end");
247
+ win.close();
248
+ });
249
+ win.loadHTML(HTML);
250
+ run();
251
+ `);
252
+ const list = stdout.slice(stdout.indexOf("list-start"), stdout.indexOf("list-end"));
253
+ expect(value("multiply")).toBe("multiply");
254
+ expect(value("overlay")).toBe("overlay");
255
+ // The card paints normally; the multiply element and its overlay child are
256
+ // separate nested groups (the renderer composites each as a unit).
257
+ expect(list).toContain("layer blend=multiply");
258
+ expect(list).toContain("layer blend=overlay");
259
+ expect(list).toContain("end-layer");
260
+ expect(list).toContain("color=#ffffffff");
261
+ expect(list).toContain("color=#ff0000ff");
262
+ expect(list).toContain("color=#00ff00ff");
263
+ // The offscreen blend pipeline and its runtime-compiled shader must not
264
+ // fall back on this GPU backend.
265
+ expect(stderr).not.toContain("mix-blend-mode");
266
+ });
190
267
  });
191
268
  //# sourceMappingURL=gui-paint.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gui-paint.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-paint.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gui-paint.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-paint.test.ts"],"names":[],"mappings":"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"}
package/doc/gui/api.md CHANGED
@@ -187,7 +187,7 @@ win.queryCount(selector) // number of matching elements
187
187
  win.getBoundingClientRect(selector) // { x, y, width, height } (border box, transformed)
188
188
  win.documentTree() // serialized DOM (debugging)
189
189
  win.layoutTree() // serialized layout boxes (debugging)
190
- win.paintCount() // number of shapes in the display list
190
+ win.paintCount() // number of filled rectangles in the display list
191
191
  win.paintList() // serialized display list (debugging)
192
192
  win.inlineSvg(selector) // serialized inline <svg>: { markup, width, height, shapes }
193
193
  win.measureText(text, fontSize?, family?) // shaped advance width in pixels
package/doc/gui/canvas.md CHANGED
@@ -206,7 +206,10 @@ engine's 9-argument form is destination-first) forms. A full affine transform
206
206
  (rotation/skew/scale) is honoured: the destination rectangle becomes a
207
207
  parallelogram and every device pixel is inverse-mapped to the source rectangle.
208
208
  `source` may be an `<img>` element (decoded through the image module), another
209
- `<canvas>`, an `OffscreenCanvas`, or an `ImageBitmap`. Sampling is bilinear when
209
+ `<canvas>`, an `OffscreenCanvas`, an `ImageBitmap`, or — an engine extension — a
210
+ string path/`data:` URL that is decoded directly, so an SVG or bitmap data URL
211
+ can be drawn without an `<img>`. A vector source is rasterised at the destination
212
+ size (`dw`/`dh`), keeping a scaled SVG crisp. Sampling is bilinear when
210
213
  `imageSmoothingEnabled` is `true` (the default) and nearest-neighbour when it is
211
214
  `false`.
212
215
 
@@ -230,7 +233,8 @@ from a microtask (the bytes are on `__xt_gui_blob_data`; there is no
230
233
  shape and `transferToImageBitmap()` snapshots the bitmap.
231
234
 
232
235
  CSS backgrounds: `background-image: url(...)` accepts a file path or a `data:`
233
- URL, so a canvas can be used as a CSS background by exporting it first
236
+ URL (a raster payload or an SVG document, base64 or percent-encoded), so a
237
+ canvas can be used as a CSS background by exporting it first
234
238
  (`element.style.backgroundImage = "url(" + canvas.toDataURL() + ")"`). An image
235
239
  layer is stretched to the element's border box; `background-size`,
236
240
  `background-position` and tiling are not modelled.
@@ -59,18 +59,24 @@
59
59
  `pointer-events`, `@media` queries, generated content (`::before`/`::after`),
60
60
  multi-layer linear/radial backgrounds, `text-shadow`, `clip-path`
61
61
  (`inset`/`circle`/`ellipse`/`polygon`) and `filter` colour functions are
62
- implemented. Known limits: `mix-blend-mode`, `background-clip`,
63
- `background-position`/`size`/`repeat`, repeating gradients and `backdrop-filter`
64
- compositing are not implemented (`backdrop-filter` parses and cascades, but
65
- sampling the backdrop needs an offscreen layer, so it paints nothing);
62
+ implemented. Known limits: `background-clip`,
63
+ `background-position`/`size`/`repeat` and `backdrop-filter` compositing are not
64
+ implemented (`backdrop-filter` parses and cascades, but sampling the backdrop
65
+ needs an offscreen layer, so it paints nothing); `mix-blend-mode` and repeating
66
+ gradients are implemented (see [Rendering](./rendering.md)), but group
67
+ `opacity` is a per-shape alpha rather than a true group composite, and the
68
+ blend compositor is unavailable on the D3D12/DXIL backend, where groups fall
69
+ back to normal source-over painting;
66
70
  `filter: blur()` is a nine-tap per-shape spread rather than a true group blur,
67
71
  so overlapping descendants can darken; `drop-shadow` is approximated from box
68
72
  silhouettes; at most two nested `clip-path` shapes are intersected (deeper
69
73
  nesting keeps the outer two); and images/canvases are not colour-filtered
70
74
  except for the brightness/opacity tint the image shader can carry. Canvas
71
- textures are sampled nearest-neighbour. Background gradients remain solid
72
- bands (18 for linear, 32 concentric ellipses for radial), so a soft gradient
73
- may show slight banding.
75
+ textures are sampled nearest-neighbour. A background linear gradient remains
76
+ solid bands (18 strips, 64 while repeating), so a large soft ramp may show
77
+ slight banding; a radial gradient is rasterised per pixel into a texture
78
+ (capped at 1024 px per edge, then stretched, so a hard stop in a very large
79
+ box comes out slightly soft).
74
80
  - **SVG is NanoSVG's subset.** SVG *files* (`<img src="logo.svg">`) and inline
75
81
  `<svg>` trees share the vendored NanoSVG parser/rasteriser
76
82
  (`runtime/ext_gui/svg.cpp`, `inline_svg.cpp`), which renders
@@ -78,9 +84,11 @@
78
84
  gradients and class-based `<style>` rules. `<text>`, `<use>`, filters, masks
79
85
  and clip paths are not rendered, and CSS applied by the document cascade does
80
86
  not reach inside the artwork (fill/stroke are SVG presentation attributes,
81
- not CSS properties here). Vector art that needs those has to be rasterised
82
- beforehand, drawn from boxes, or restricted to the supported syntax. See
83
- [Media](./media.md#images-m6a).
87
+ not CSS properties here). An SVG whose only artwork comes from a filter
88
+ (`feTurbulence` noise, blur, …) therefore shows the unfiltered shapes — often
89
+ a solid rect — rather than the filter result. Vector art that needs those has
90
+ to be rasterised beforehand, drawn from boxes, or restricted to the supported
91
+ syntax. See [Media](./media.md#images-m6a).
84
92
  - **Rendering is still whole-document per change.** A DOM mutation recomputes
85
93
  the cascade and lays out the complete document again (there is no incremental
86
94
  subtree invalidation), and a change that only affects paint (a hover colour,
package/doc/gui/media.md CHANGED
@@ -43,6 +43,15 @@ subtree, whose DOM nodes are serialized into a document first
43
43
  without limit.
44
44
  - The parse is cached per path; the parse and rasteriser state are released in
45
45
  `xt_svg_shutdown` (`xt_image_shutdown`).
46
+ - `data:` URLs are decoded in memory and work wherever a path does:
47
+ `<img src>`, CSS `background-image` and `canvas.drawImage(url, …)`. Both the
48
+ `;base64` form and a percent-encoded (or plain) payload are accepted, and the
49
+ `base64` flag is case-insensitive. The MIME type only classifies the payload:
50
+ `image/svg+xml` (or an SVG sniff when the type is generic or absent) goes
51
+ through NanoSVG — a data-URL SVG is rasterised at the drawn size exactly like
52
+ an SVG file — while PNG/JPEG/BMP/GIF/TGA go through `stb_image`. NanoSVG's
53
+ subset still applies: an SVG whose only artwork comes from `<filter>`
54
+ (`feTurbulence`, …) decodes and lays out, but the filter itself is ignored.
46
55
 
47
56
  Document CSS styles the `<svg>` **box** — size, margins, background, borders,
48
57
  `transform`, opacity, `display` — exactly as it styles any other element. The
@@ -55,8 +64,8 @@ renderer caches one texture per markup-and-size pair, so a program that rewrites
55
64
  its artwork every frame keeps a texture per variant; prefer animating the box
56
65
  (`transform`, `opacity`) or swapping between a small set of documents.
57
66
 
58
- Still to do for images: CSS `background-image: url(...)`, `data:` URIs,
59
- `object-fit` and 9-slice borders.
67
+ Still to do for images: `object-fit` and 9-slice borders. (CSS
68
+ `background-image: url(...)` and `data:` URLs are supported.)
60
69
 
61
70
  Both kinds of SVG share NanoSVG's subset: `path`, `rect`, `circle`, `ellipse`,
62
71
  `line`, `polyline`, `polygon`, groups (`g`), `defs` with linear/radial gradients