xbintsc 0.5.21 → 0.5.22

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.
@@ -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.21";
12
+ export declare const COMPILER_VERSION = "0.5.22";
@@ -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.21";
12
+ export const COMPILER_VERSION = "0.5.22";
13
13
  //# sourceMappingURL=version.js.map
@@ -159,6 +159,165 @@ describe.skipIf(!guiAvailable)("gui extension — canvas", () => {
159
159
  expect(value("fill")).toBe("#ff0000");
160
160
  expect(value("text")).toBe("true");
161
161
  });
162
+ it("supports gradients, ellipses, rounded rects, dashes and line styles", () => {
163
+ const { value } = harness.compileAndRun("canvas-modern", `
164
+ import { createWindow, run } from "gui";
165
+
166
+ const HTML = \`
167
+ <html><head><style>body { margin: 0; }</style></head><body>
168
+ <canvas id="c" width="240" height="260"></canvas>
169
+ </body></html>
170
+ \`;
171
+
172
+ const win = createWindow({ title: "canvas-modern", width: 400, height: 300 });
173
+ win.on("ready", () => {
174
+ const canvas: any = win.document.getElementById("c");
175
+ const ctx: any = canvas.getContext("2d");
176
+ const px = (x: number, y: number): string => {
177
+ const d = ctx.getImageData(x, y, 1, 1).data;
178
+ return d[0] + "," + d[1] + "," + d[2] + "," + d[3];
179
+ };
180
+
181
+ ctx.fillStyle = "#ffffff";
182
+ ctx.fillRect(0, 0, 240, 260);
183
+
184
+ // A horizontal red -> blue gradient, sampled well inside both ends.
185
+ const linear = ctx.createLinearGradient(0, 0, 100, 0);
186
+ linear.addColorStop(0, "#ff0000");
187
+ linear.addColorStop(1, "#0000ff");
188
+ ctx.fillStyle = linear;
189
+ ctx.fillRect(0, 0, 100, 20);
190
+ console.log("grad-left=" + px(5, 10));
191
+ console.log("grad-right=" + px(95, 10));
192
+ // The getter returns the same handle, not a copy.
193
+ console.log("same-gradient=" + (ctx.fillStyle === linear));
194
+
195
+ // A stop added after assignment is live: the right side turns blue.
196
+ const live = ctx.createLinearGradient(0, 0, 100, 0);
197
+ live.addColorStop(0, "#ff0000");
198
+ ctx.fillStyle = live;
199
+ live.addColorStop(1, "#0000ff");
200
+ ctx.fillRect(0, 30, 100, 20);
201
+ console.log("live-right=" + px(95, 40));
202
+
203
+ const radial = ctx.createRadialGradient(50, 70, 0, 50, 70, 35);
204
+ radial.addColorStop(0, "#ffffff");
205
+ radial.addColorStop(1, "#000000");
206
+ ctx.fillStyle = radial;
207
+ ctx.fillRect(0, 60, 100, 40);
208
+ console.log("radial-centre=" + px(50, 70));
209
+ console.log("radial-edge=" + px(88, 70));
210
+
211
+ ctx.fillStyle = "#00ff00";
212
+ ctx.beginPath();
213
+ ctx.ellipse(170, 70, 40, 15, 0, 0, Math.PI * 2);
214
+ ctx.fill();
215
+ console.log("ellipse-centre=" + px(170, 70));
216
+ console.log("ellipse-outside=" + px(170, 40));
217
+
218
+ ctx.fillStyle = "#ff00ff";
219
+ ctx.beginPath();
220
+ ctx.roundRect(10, 100, 70, 50, 14);
221
+ ctx.fill();
222
+ console.log("roundrect-centre=" + px(45, 125));
223
+ console.log("roundrect-corner=" + px(11, 101));
224
+
225
+ ctx.strokeStyle = "#000000";
226
+ ctx.lineWidth = 4;
227
+ ctx.lineCap = "butt";
228
+ ctx.setLineDash([10, 10]);
229
+ ctx.lineDashOffset = 0;
230
+ console.log("cap=" + ctx.lineCap);
231
+ console.log("join=" + ctx.lineJoin);
232
+ console.log("miter=" + ctx.miterLimit);
233
+ console.log("dash=" + ctx.getLineDash().join(","));
234
+ ctx.beginPath();
235
+ ctx.moveTo(120, 150);
236
+ ctx.lineTo(230, 150);
237
+ ctx.stroke();
238
+ console.log("dash-on=" + px(124, 150));
239
+ console.log("dash-off=" + px(136, 150));
240
+
241
+ ctx.setLineDash([]);
242
+ ctx.strokeStyle = "#ff0000";
243
+ ctx.lineWidth = 10;
244
+ ctx.lineCap = "square";
245
+ ctx.beginPath();
246
+ ctx.moveTo(120, 190);
247
+ ctx.lineTo(180, 190);
248
+ ctx.stroke();
249
+ console.log("square-cap=" + px(115, 190));
250
+ console.log("square-cap-far=" + px(105, 190));
251
+
252
+ // A miter fills the outer corner right to the tip; a bevel cuts it off.
253
+ ctx.strokeStyle = "#000000";
254
+ ctx.lineWidth = 20;
255
+ ctx.lineCap = "butt";
256
+ ctx.lineJoin = "miter";
257
+ ctx.beginPath();
258
+ ctx.moveTo(120, 215);
259
+ ctx.lineTo(120, 245);
260
+ ctx.lineTo(160, 245);
261
+ ctx.stroke();
262
+ console.log("miter-join=" + px(111, 254));
263
+ ctx.lineJoin = "bevel";
264
+ ctx.beginPath();
265
+ ctx.moveTo(180, 215);
266
+ ctx.lineTo(180, 245);
267
+ ctx.lineTo(220, 245);
268
+ ctx.stroke();
269
+ console.log("bevel-join=" + px(171, 254));
270
+
271
+ ctx.save();
272
+ ctx.translate(100, 100);
273
+ ctx.rotate(Math.PI / 2);
274
+ const t = ctx.getTransform();
275
+ console.log("nested-transform=" + Math.round(t.e) + "," + Math.round(t.f));
276
+ // Post-multiplied: the square sits at (90,100)..(100,110), not at the
277
+ // rotated origin. A pre-multiplied transform would put it elsewhere.
278
+ ctx.fillStyle = "#ff8800";
279
+ ctx.fillRect(0, 0, 10, 10);
280
+ console.log("rotated-rect=" + px(95, 105));
281
+ ctx.restore();
282
+ const r = ctx.getTransform();
283
+ console.log("restored-transform=" + r.a + "," + r.e + "," + r.f);
284
+
285
+ win.close();
286
+ });
287
+ win.loadHTML(HTML);
288
+ run();
289
+ `);
290
+ const channels = (label) => value(label).split(",").map(Number);
291
+ const left = channels("grad-left");
292
+ expect(left[0]).toBeGreaterThan(200);
293
+ expect(left[2]).toBeLessThan(60);
294
+ const right = channels("grad-right");
295
+ expect(right[0]).toBeLessThan(60);
296
+ expect(right[2]).toBeGreaterThan(200);
297
+ expect(value("same-gradient")).toBe("true");
298
+ expect(channels("live-right")[2]).toBeGreaterThan(200);
299
+ // The radial gradient is white at its centre and black at its edge.
300
+ expect(channels("radial-centre")[0]).toBeGreaterThan(230);
301
+ expect(channels("radial-edge")[0]).toBeLessThan(40);
302
+ expect(channels("ellipse-centre")).toEqual([0, 255, 0, 255]);
303
+ expect(channels("ellipse-outside")).toEqual([255, 255, 255, 255]);
304
+ expect(channels("roundrect-centre")).toEqual([255, 0, 255, 255]);
305
+ expect(channels("roundrect-corner")).toEqual([255, 255, 255, 255]);
306
+ expect(value("cap")).toBe("butt");
307
+ expect(value("join")).toBe("miter");
308
+ expect(value("miter")).toBe("10");
309
+ expect(value("dash")).toBe("10,10");
310
+ expect(channels("dash-on")).toEqual([0, 0, 0, 255]);
311
+ expect(channels("dash-off")).toEqual([255, 255, 255, 255]);
312
+ // A square cap extends half a line width past the endpoint.
313
+ expect(channels("square-cap")).toEqual([255, 0, 0, 255]);
314
+ expect(channels("square-cap-far")).toEqual([255, 255, 255, 255]);
315
+ expect(channels("miter-join")).toEqual([0, 0, 0, 255]);
316
+ expect(channels("bevel-join")).toEqual([255, 255, 255, 255]);
317
+ expect(value("nested-transform")).toBe("100,100");
318
+ expect(channels("rotated-rect")).toEqual([255, 136, 0, 255]);
319
+ expect(value("restored-transform")).toBe("1,0,0");
320
+ });
162
321
  it("draws an image onto a canvas with drawImage", () => {
163
322
  const src = harness.imageSrc(harness.logoPath);
164
323
  const { value } = harness.compileAndRun("canvas-drawimage", `
@@ -1 +1 @@
1
- {"version":3,"file":"gui-canvas.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-canvas.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gui-canvas.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-canvas.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;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,wBAAwB,EAAE,GAAG,EAAE;IAC5D,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,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,aAAa,EACb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAEhG,2EAA2E;QAC3E,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACzC,mEAAmE;QACnE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC3C,0EAA0E;QAC1E,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,iDAAiD,CAAC,CAAC;QACxE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CACrC,aAAa,EACb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCC,CACF,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjC,0CAA0C;QAC1C,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CACrC,cAAc,EACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6CC,CACF,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC3C,yEAAyE;QACzE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC5C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CACrC,eAAe,EACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8HC,CACF,CAAC;QACF,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAY,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAElF,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACjC,MAAM,KAAK,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAEvD,oEAAoE;QACpE,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAC1D,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAEpD,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC7D,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QAClE,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QACjE,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QAEnE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACpD,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QAC3D,4DAA4D;QAC5D,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACzD,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QACjE,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QAE7D,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAClD,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC7D,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CACrC,kBAAkB,EAClB;;;;;8BAKwB,GAAG;;;;;;;;;;;;;;;;;;;;;OAqB1B,CACF,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CACrC,kBAAkB,EAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCC,CACF,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * End-to-end smoke test for `examples/gui/pelican-canvas` — the animated
3
+ * `<canvas>` drawing.
4
+ *
5
+ * The drawing lives in `scene.js`, loaded by `index.html` through a plain
6
+ * `<script src>`: a browser runs it as-is, while xbintsc AOT-compiles the same
7
+ * body via the gui `.html` loader. `main.ts` only hosts the native window and
8
+ * reports deterministic pixel probes, so this test can assert that the
9
+ * gradients, paths and text really rasterised.
10
+ */
11
+ export {};
@@ -0,0 +1,70 @@
1
+ /**
2
+ * End-to-end smoke test for `examples/gui/pelican-canvas` — the animated
3
+ * `<canvas>` drawing.
4
+ *
5
+ * The drawing lives in `scene.js`, loaded by `index.html` through a plain
6
+ * `<script src>`: a browser runs it as-is, while xbintsc AOT-compiles the same
7
+ * body via the gui `.html` loader. `main.ts` only hosts the native window and
8
+ * reports deterministic pixel probes, so this test can assert that the
9
+ * gradients, paths and text really rasterised.
10
+ */
11
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { dirname, join, resolve } from "node:path";
14
+ import { spawnSync } from "node:child_process";
15
+ import { Script } from "node:vm";
16
+ import { fileURLToPath } from "node:url";
17
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
18
+ import { build } from "../../src/driver/compiler.js";
19
+ import { createDefaultRegistry } from "../../src/extensions/registry.js";
20
+ import { guiExtension } from "../../src/extensions/gui/index.js";
21
+ import { guiAvailable } from "./gui-helpers.js";
22
+ /** The example lives at <repo>/examples/gui/pelican-canvas. */
23
+ const exampleDir = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "examples", "gui", "pelican-canvas");
24
+ let workdir = "";
25
+ describe("pelican canvas example — browser entry", () => {
26
+ it("loads the shared scene as a classic script", () => {
27
+ const html = readFileSync(join(exampleDir, "index.html"), "utf8");
28
+ expect(html).toContain('<script src="./scene.js"></script>');
29
+ const script = readFileSync(join(exampleDir, "scene.js"), "utf8");
30
+ // A browser compiles this as a classic script: no ESM, no TypeScript.
31
+ expect(() => new Script(script)).not.toThrow();
32
+ });
33
+ });
34
+ describe.skipIf(!guiAvailable)("gui example — pelican canvas", () => {
35
+ beforeAll(() => {
36
+ workdir = mkdtempSync(join(tmpdir(), "xbintsc-pelican-canvas-"));
37
+ for (const file of ["index.html", "main.ts", "scene.js"]) {
38
+ writeFileSync(join(workdir, file), readFileSync(join(exampleDir, file), "utf8"));
39
+ }
40
+ });
41
+ afterAll(() => {
42
+ rmSync(workdir, { recursive: true, force: true });
43
+ });
44
+ it("compiles the canvas drawing and reports pixels", () => {
45
+ const extensions = createDefaultRegistry().register(guiExtension);
46
+ const result = build(join(workdir, "main.ts"), {
47
+ emit: "exe",
48
+ outDir: join(workdir, "out"),
49
+ cacheDir: join(workdir, ".cache"),
50
+ extensions,
51
+ });
52
+ expect(result.diagnostics.filter((diagnostic) => diagnostic.category === "error")).toEqual([]);
53
+ const executed = spawnSync(result.outputPath, [], {
54
+ encoding: "utf8",
55
+ env: { ...process.env, XT_GUI_AUTOCLOSE_MS: "2000" },
56
+ });
57
+ expect(executed.status, executed.stderr).toBe(0);
58
+ const stdout = executed.stdout ?? "";
59
+ expect(stdout).toContain("window=1000x640 (gui extension, SDL_GPU)");
60
+ expect(stdout).toContain("canvas-box=x=0 y=0 1000x640");
61
+ expect(stdout).toContain("canvas-size=1000x640");
62
+ // The title is white text on the gradient sky.
63
+ expect(stdout).toContain("pixel-title=255,255,255,255");
64
+ // The road gradient, and the pelican body (not the sky colour).
65
+ expect(stdout).toMatch(/pixel-road=\d+,\d+,\d+,255/);
66
+ expect(stdout).toMatch(/pixel-pelican=\d+,\d+,\d+,255/);
67
+ expect(stdout).toContain("gui exited");
68
+ });
69
+ });
70
+ //# sourceMappingURL=gui-example-canvas.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gui-example-canvas.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-example-canvas.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,+DAA+D;AAC/D,MAAM,UAAU,GAAG,OAAO,CACxB,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EACvC,IAAI,EACJ,IAAI,EACJ,UAAU,EACV,KAAK,EACL,gBAAgB,CACjB,CAAC;AAEF,IAAI,OAAO,GAAG,EAAE,CAAC;AAEjB,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACtD,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC;QAClE,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,oCAAoC,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC;QAClE,sEAAsE;QACtE,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAClE,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,yBAAyB,CAAC,CAAC,CAAC;QACjE,KAAK,MAAM,IAAI,IAAI,CAAC,YAAY,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,CAAC;YACzD,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACnF,CAAC;IACH,CAAC,CAAC,CAAC;IACH,QAAQ,CAAC,GAAG,EAAE;QACZ,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,UAAU,GAAG,qBAAqB,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAClE,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE;YAC7C,IAAI,EAAE,KAAK;YACX,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC;YAC5B,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC;YACjC,UAAU;SACX,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAE/F,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE;YAChD,QAAQ,EAAE,MAAM;YAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE,MAAM,EAAE;SACrD,CAAC,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;QAErC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,0CAA0C,CAAC,CAAC;QACrE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;QACjD,+CAA+C;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;QACxD,gEAAgE;QAChE,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC;QACrD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -76,9 +76,10 @@ media API (`play`/`pause`/`currentTime`/`volume`/…) plus the
76
76
  program drive playback. There are no built-in controls — draw your own chrome
77
77
  with elements and CSS (see `examples/gui/media.ts`). Video frames and audio PCM
78
78
  are decoded on the main thread. `<canvas>` is supported too: `getContext("2d")`
79
- returns a context whose fills, paths, text and `drawImage` are rasterised by a
80
- self-written CPU rasteriser, and element handles expose `canvas` drawing plus
81
- `width`/`height`. CSS `transform`/`transform-origin` work as paint-time
79
+ returns a context whose fills, paths, gradients, text and `drawImage` are
80
+ rasterised by a self-written CPU rasteriser (plus line caps/joins, dashed
81
+ strokes, `ellipse` and `roundRect`), and element handles expose `canvas` drawing
82
+ plus `width`/`height`. CSS `transform`/`transform-origin` work as paint-time
82
83
  effects, so a box (and the text, images and children in it) can be translated,
83
84
  scaled and rotated without reflowing anything — animate it per frame from
84
85
  `requestAnimationFrame` or with `transition: transform` (see
@@ -71,7 +71,8 @@ import { createWindow, run } from "gui";
71
71
  `loadedmetadata`/`play`/`pause`/`ended`/`timeupdate`/`error` 事件可让程序驱动播放。
72
72
  没有内置控件——用元素和 CSS 自绘控件(见 `examples/gui/media.ts`);视频帧与音频 PCM
73
73
  都在主线程解码。也支持 `<canvas>`:`getContext("2d")` 返回的上下文,其填充、路径、
74
- 文本与 `drawImage` 由自研 CPU 光栅器处理,元素句柄还暴露 `width`/`height`。CSS
74
+ 渐变、文本与 `drawImage` 由自研 CPU 光栅器处理(另有线段端点/连接、虚线描边、
75
+ `ellipse` 与 `roundRect`),元素句柄还暴露 `width`/`height`。CSS
75
76
  `transform`/`transform-origin` 作为绘制期效果可用,因此一个盒子(以及其中的文本、
76
77
  图像与子元素)可以被平移、缩放与旋转而不触发任何重排——可以用
77
78
  `requestAnimationFrame` 逐帧驱动,或写成 `transition: transform`(见
package/doc/gui/canvas.md CHANGED
@@ -13,8 +13,11 @@ video frames (`renderer_image.cpp`), so the GPU scales it into the CSS box.
13
13
 
14
14
  The engine-side object is `xtgui::Canvas2D`
15
15
  (`runtime/ext_gui/canvas.{h,cpp}`) — one RGBA8 bitmap plus the 2D drawing state.
16
- The rasteriser lives in `canvas_raster.cpp`; the DOM bindings live in
17
- `dom_api_canvas.cpp`, and the per-window lifetime in `gui_canvas.cpp`.
16
+ The rasteriser lives in `canvas_raster.cpp`, stroke geometry (caps, joins,
17
+ dashes) in `canvas_stroke.cpp`, gradients in `canvas_gradient.cpp`, and drawing
18
+ state parsing in `canvas_style.cpp`; the DOM bindings live in
19
+ `dom_api_canvas.cpp` / `dom_api_canvas_extra.cpp`, and the per-window lifetime in
20
+ `gui_canvas.cpp`.
18
21
 
19
22
  ## Getting a context
20
23
 
@@ -37,6 +40,14 @@ horizontal coverage** pass, and fills paths with the **non-zero winding rule**
37
40
  (`b == 0 && c == 0` in the transform) takes a direct coverage fast path.
38
41
  Blending is non-premultiplied source-over, scaled by `globalAlpha`.
39
42
 
43
+ A fill or stroke paints with a solid colour **or a `CanvasGradient`**. Gradient
44
+ coordinates are user-space coordinates, so the rasteriser maps each device pixel
45
+ back through the inverse of the current transform before sampling; this keeps a
46
+ gradient attached to the shape when the shape is translated, scaled or rotated.
47
+ Strokes are flattened into segment quads plus the join/cap shapes selected by
48
+ `lineJoin`/`lineCap`; a dash pattern first splits the polyline into its "on"
49
+ pieces, then those pieces are stroked with the same caps.
50
+
40
51
  Canvas pixels are **not** touched by the garbage collector: a canvas is owned by
41
52
  the window, keyed by its DOM node (the same shape as the `<video>`/`<audio>`
42
53
  decode table). It is released when the node is dropped (`loadHTML`, window
@@ -48,24 +59,39 @@ matching GPU texture.
48
59
  State and transforms: `save` / `restore` (which restore the transform, line
49
60
  width, alpha, font/text settings **and** the fill/stroke styles),
50
61
  `translate`, `rotate`, `scale`, `transform(a,b,c,d,e,f)`, `setTransform`,
51
- `resetTransform`.
62
+ `resetTransform`, `getTransform` (returns an object with `a`…`f` and `is2D`).
63
+ Transforms compose in canvas order: `translate(x, y); rotate(a)` rotates around
64
+ the translated origin.
52
65
 
53
66
  Styles and settings: `fillStyle` / `strokeStyle` (CSS colors understood by the
54
- cascade's color parser), `lineWidth`, `globalAlpha`, `font` (a CSS font
55
- shorthand resolving to family, size and bold/italic — default
56
- `10px sans-serif`), `textAlign` (`start`/`left`, `center`, `right`/`end`) and
57
- `textBaseline` (`alphabetic`, `top`, `middle`, `bottom`, `hanging`,
58
- `ideographic`). Reading a property returns the value last assigned; assigning an
59
- unparseable color keeps the previous one.
60
-
61
- Paths: `beginPath`, `closePath`, `moveTo`, `lineTo`, `rect`, `arc`,
62
- `quadraticCurveTo`, `bezierCurveTo`, then `fill` (multi-loop, non-zero winding)
63
- or `stroke`. Bezier and quadratic segments are flattened into line segments;
64
- arcs are flattened too and each subpath is stroked as segment quads plus a disc
65
- at each joint.
67
+ cascade's color parser, or a `CanvasGradient`), `lineWidth`, `globalAlpha`,
68
+ `lineCap` (`butt`/`round`/`square`), `lineJoin` (`miter`/`round`/`bevel`),
69
+ `miterLimit`, `lineDashOffset`, `font` (a CSS font shorthand resolving to
70
+ family, size and bold/italic — default `10px sans-serif`), `textAlign`
71
+ (`start`/`left`, `center`, `right`/`end`) and `textBaseline` (`alphabetic`,
72
+ `top`, `middle`, `bottom`, `hanging`, `ideographic`). Reading a property returns
73
+ the value last assigned; assigning an unparseable color keeps the previous one.
74
+
75
+ Gradients: `createLinearGradient(x0, y0, x1, y1)` and
76
+ `createRadialGradient(x0, y0, r0, x1, y1, r1)` return a `CanvasGradient` with
77
+ `addColorStop(offset, color)`. Stops stay live, so a stop added after
78
+ `fillStyle = gradient` affects later fills, and the `fillStyle`/`strokeStyle`
79
+ getter returns the same gradient handle. Radial gradients interpolate by
80
+ distance from the **outer** circle's centre (`x1, y1`), so `x0`/`y0` are
81
+ currently ignored: use concentric circles (`x0 == x1`, `y0 == y1`).
82
+
83
+ Paths: `beginPath`, `closePath`, `moveTo`, `lineTo`, `rect`, `roundRect`,
84
+ `arc`, `ellipse`, `quadraticCurveTo`, `bezierCurveTo`, then `fill` (multi-loop,
85
+ non-zero winding) or `stroke`. Bezier and quadratic segments are flattened into
86
+ line segments; arcs and ellipses are flattened too. `roundRect` accepts one
87
+ radius number (the array form is not implemented).
66
88
 
67
89
  Rectangles: `clearRect`, `fillRect`, `strokeRect`.
68
90
 
91
+ Dashes: `setLineDash(segments)` / `getLineDash()` / `lineDashOffset` implement
92
+ the standard pattern rules (an odd-length list is duplicated, negative or
93
+ non-finite entries are ignored, an all-zero list means a solid line).
94
+
69
95
  Text: `measureText(text)` (returns `{ width }`), `fillText(text, x, y)` and
70
96
  `strokeText(text, x, y)`. Text is shaped with HarfBuzz and rasterised with
71
97
  FreeType (the same stack as HTML text) at the transform's approximate scale.
@@ -87,12 +113,12 @@ Out-of-bounds pixels are transparent.
87
113
 
88
114
  ## Not implemented
89
115
 
90
- Gradients and patterns, shadows, `clip()`, `globalCompositeOperation`, line
91
- joins/caps/dashes/mitre, `setLineDash`, `createLinearGradient`/`createPattern`,
92
- `putImageData`, `createImageData`, `isPointInPath`, `getTransform`,
93
- `setTransform` with a `DOMMatrix`, `drawImage` with an image smoothing toggle,
94
- `toDataURL`/`toBlob`, `<canvas>` as a CSS `background-image`, and offscreen
95
- canvases (`OffscreenCanvas`, `transferControlToOffscreen`).
116
+ Patterns (`createPattern`), shadows, `clip()`, `globalCompositeOperation`,
117
+ `putImageData`, `createImageData`, `isPointInPath`, `setTransform` with a
118
+ `DOMMatrix`, `drawImage` with an image smoothing toggle, `toDataURL`/`toBlob`,
119
+ `<canvas>` as a CSS `background-image`, and offscreen canvases
120
+ (`OffscreenCanvas`, `transferControlToOffscreen`). `roundRect` takes a single
121
+ radius, and radial gradients ignore the inner circle's offset.
96
122
 
97
123
  See also [Media](./media.md) for the image/video/audio decode stack that
98
124
  `drawImage` and the texture upload share.
package/doc/gui.md CHANGED
@@ -354,7 +354,13 @@ engine were Rust.
354
354
  `getContext("2d")` handle and its API are bound in `dom_api_canvas.cpp`, and
355
355
  `gui_canvas.cpp` owns the per-window lifetime. The bitmap uploads through the
356
356
  image texture path (`renderer_image.cpp`) and the display list carries a
357
- `canvas_id` (`paint.*`). See [Canvas](./gui/canvas.md); e2e in
357
+ `canvas_id` (`paint.*`). The drawing surface was later widened with linear /
358
+ radial gradients (`canvas_gradient.cpp`), `ellipse`/`roundRect`, line
359
+ caps/joins/miter limits and dash patterns (`canvas_style.cpp`,
360
+ `canvas_stroke.cpp`), and `getTransform`; `transform` now post-multiplies, so
361
+ `translate(x, y); rotate(a)` rotates around the translated origin, and
362
+ `examples/gui/pelican-canvas` draws an animated pelican on a bicycle entirely
363
+ with these APIs. See [Canvas](./gui/canvas.md); e2e in
358
364
  `tests/e2e/gui-canvas.test.ts`.
359
365
  - **M15** ✅ CSS `transform` / `transform-origin`. A new translation unit
360
366
  (`transform.{h,cpp}`) parses `translate`/`translateX`/`translateY`/`scale`/
@@ -10,7 +10,9 @@
10
10
  CSS 盒中。
11
11
 
12
12
  引擎侧的对象是 `xtgui::Canvas2D`(`runtime/ext_gui/canvas.{h,cpp}`)——一块 RGBA8 位图
13
- 加上 2D 绘制状态。光栅器位于 `canvas_raster.cpp`;DOM 绑定位于 `dom_api_canvas.cpp`;
13
+ 加上 2D 绘制状态。光栅器位于 `canvas_raster.cpp`,描边几何(端点、连接、虚线)位于
14
+ `canvas_stroke.cpp`,渐变位于 `canvas_gradient.cpp`,绘制状态解析位于
15
+ `canvas_style.cpp`;DOM 绑定位于 `dom_api_canvas.cpp` / `dom_api_canvas_extra.cpp`;
14
16
  按窗口的生命周期位于 `gui_canvas.cpp`。
15
17
 
16
18
  ## 获取上下文
@@ -31,6 +33,11 @@ const ctx = canvas.getContext("2d"); // 其他上下文类型返回 null
31
33
  以相反方向环绕时能形成空洞)。轴对齐矩形(变换中 `b == 0 && c == 0`)走直接的覆盖率
32
34
  快速路径。混合为非预乘 source-over,并按 `globalAlpha` 缩放。
33
35
 
36
+ 填充或描边可以使用纯色,也可以使用 **`CanvasGradient`**。渐变的坐标属于用户空间,
37
+ 因此光栅器会先用当前变换的逆矩阵把每个设备像素映射回用户空间再采样——形状被平移、
38
+ 缩放或旋转时,渐变仍然贴在形状上。描边被展平为线段四边形,再按 `lineJoin`/`lineCap`
39
+ 补上连接/端点形状;虚线模式先把折线切成“实线”片段,然后用同样的端点规则描边。
40
+
34
41
  Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点为键(与
35
42
  `<video>`/`<audio>` 解码表结构相同)。当节点被丢弃(`loadHTML`、窗口关闭/退出,或丢弃
36
43
  子树的变更操作)时随之释放,并释放对应的 GPU 纹理。
@@ -39,21 +46,34 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
39
46
 
40
47
  状态与变换:`save` / `restore`(会恢复变换、线宽、透明度、字体/文本设置**以及**
41
48
  填充/描边样式)、`translate`、`rotate`、`scale`、`transform(a,b,c,d,e,f)`、
42
- `setTransform`、`resetTransform`。
43
-
44
- 样式与设置:`fillStyle` / `strokeStyle`(层叠所用的颜色解析器能识别的 CSS 颜色)、
45
- `lineWidth`、`globalAlpha`、`font`(CSS 字体简写,解析出字体族、字号与粗体/斜体——
46
- 默认 `10px sans-serif`)、`textAlign`(`start`/`left`、`center`、`right`/`end`)与
47
- `textBaseline`(`alphabetic`、`top`、`middle`、`bottom`、`hanging`、`ideographic`)。
48
- 读取属性返回上次赋予的值;赋予无法解析的颜色会保留原值。
49
-
50
- 路径:`beginPath`、`closePath`、`moveTo`、`lineTo`、`rect`、`arc`、
51
- `quadraticCurveTo`、`bezierCurveTo`,然后 `fill`(多环、非零环绕)或 `stroke`。贝塞尔
52
- 与二次曲线段会被展平为线段;弧线同样被展平,每条子路径按线段四边形描边,并在每个连接
53
- 处补一个圆盘。
49
+ `setTransform`、`resetTransform`、`getTransform`(返回带 `a`…`f` 与 `is2D` 的对象)。
50
+ 变换按 canvas 的顺序组合:`translate(x, y); rotate(a)` 围绕平移后的原点旋转。
51
+
52
+ 样式与设置:`fillStyle` / `strokeStyle`(层叠所用的颜色解析器能识别的 CSS 颜色,或
53
+ `CanvasGradient`)、`lineWidth`、`globalAlpha`、`lineCap`(`butt`/`round`/`square`)、
54
+ `lineJoin`(`miter`/`round`/`bevel`)、`miterLimit`、`lineDashOffset`、`font`
55
+ (CSS 字体简写,解析出字体族、字号与粗体/斜体——默认 `10px sans-serif`)、
56
+ `textAlign`(`start`/`left`、`center`、`right`/`end`)与 `textBaseline`
57
+ (`alphabetic`、`top`、`middle`、`bottom`、`hanging`、`ideographic`)。读取属性返回
58
+ 上次赋予的值;赋予无法解析的颜色会保留原值。
59
+
60
+ 渐变:`createLinearGradient(x0, y0, x1, y1)` 与
61
+ `createRadialGradient(x0, y0, r0, x1, y1, r1)` 返回带
62
+ `addColorStop(offset, color)` 的 `CanvasGradient`。色标是**活的**:在
63
+ `fillStyle = gradient` 之后追加的色标会影响后续填充,且 `fillStyle`/`strokeStyle`
64
+ 的读取返回同一个渐变句柄。径向渐变按到**外圆**圆心(`x1, y1`)的距离插值,因此目前
65
+ 会忽略 `x0`/`y0`——请使用同心圆(`x0 == x1`、`y0 == y1`)。
66
+
67
+ 路径:`beginPath`、`closePath`、`moveTo`、`lineTo`、`rect`、`roundRect`、`arc`、
68
+ `ellipse`、`quadraticCurveTo`、`bezierCurveTo`,然后 `fill`(多环、非零环绕)或
69
+ `stroke`。贝塞尔与二次曲线段会被展平为线段;弧线与椭圆同样被展平。`roundRect` 只接受
70
+ 单个圆角半径数字(数组形式尚未实现)。
54
71
 
55
72
  矩形:`clearRect`、`fillRect`、`strokeRect`。
56
73
 
74
+ 虚线:`setLineDash(segments)` / `getLineDash()` / `lineDashOffset` 实现标准规则
75
+ (奇数长度列表会复制一遍,负数/非有限值会被忽略,全零列表表示实线)。
76
+
57
77
  文本:`measureText(text)`(返回 `{ width }`)、`fillText(text, x, y)` 与
58
78
  `strokeText(text, x, y)`。文本经 HarfBuzz 排版、FreeType 光栅化(与 HTML 文本相同的
59
79
  技术栈),按变换的近似缩放绘制。**仅支持平移与均匀缩放**——旋转或斜切变换仍会把每个
@@ -70,11 +90,10 @@ Canvas 像素**不参与垃圾回收**:canvas 由窗口拥有、以 DOM 节点
70
90
 
71
91
  ## 尚未实现
72
92
 
73
- 渐变与图案、阴影、`clip()`、`globalCompositeOperation`、线段连接/端点/虚线/斜接、
74
- `setLineDash`、`createLinearGradient`/`createPattern`、`putImageData`、
75
- `createImageData`、`isPointInPath`、`getTransform`、以 `DOMMatrix` 调用的
93
+ 图案(`createPattern`)、阴影、`clip()`、`globalCompositeOperation`、
94
+ `putImageData`、`createImageData`、`isPointInPath`、以 `DOMMatrix` 调用的
76
95
  `setTransform`、可切换图像平滑的 `drawImage`、`toDataURL`/`toBlob`、把 `<canvas>` 用作
77
96
  CSS `background-image`,以及离屏画布(`OffscreenCanvas`、
78
- `transferControlToOffscreen`)。
97
+ `transferControlToOffscreen`)。`roundRect` 只接受单个半径,径向渐变会忽略内圆偏移。
79
98
 
80
99
  另见[媒体](./media.md):`drawImage` 与纹理上传所共享的图像/视频/音频解码栈。
package/doc/zh-CN/gui.md CHANGED
@@ -296,8 +296,12 @@ FFmpeg `7.1`(可用 `SDL3_TAG` / `FREETYPE_VERSION` / `HARFBUZZ_VERSION` /
296
296
  (4 条子扫描线 + 水平解析覆盖率、非零环绕),`getContext("2d")` 句柄及其 API
297
297
  在 `dom_api_canvas.cpp` 绑定,`gui_canvas.cpp` 持有按窗口的生命周期。位图经
298
298
  图像纹理路径上传(`renderer_image.cpp`),显示列表携带 `canvas_id`
299
- (`paint.*`)。参见 [Canvas](./gui/canvas.md);e2e 见
300
- `tests/e2e/gui-canvas.test.ts`。
299
+ (`paint.*`)。此后绘制面又补齐了线性/径向渐变(`canvas_gradient.cpp`)、
300
+ `ellipse`/`roundRect`、线段端点/连接/斜接上限与虚线模式(`canvas_style.cpp`、
301
+ `canvas_stroke.cpp`)以及 `getTransform`;`transform` 改为后乘,因此
302
+ `translate(x, y); rotate(a)` 围绕平移后的原点旋转,而
303
+ `examples/gui/pelican-canvas` 完全用这些 API 画出会动的鹈鹕骑自行车。参见
304
+ [Canvas](./gui/canvas.md);e2e 见 `tests/e2e/gui-canvas.test.ts`。
301
305
  - **M15** ✅ CSS `transform` / `transform-origin`。新的翻译单元
302
306
  (`transform.{h,cpp}`)把 `translate`/`translateX`/`translateY`/`scale`/
303
307
  `scaleX`/`scaleY`/`rotate`/`matrix`(以及 `translate3d`/`scale3d`,其 `z` 参数被丢弃)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xbintsc",
3
- "version": "0.5.21",
3
+ "version": "0.5.22",
4
4
  "description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
5
5
  "type": "module",
6
6
  "license": "MIT",