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.
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/tests/e2e/gui-canvas.test.js +159 -0
- package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
- package/dist/tests/e2e/gui-example-canvas.test.d.ts +11 -0
- package/dist/tests/e2e/gui-example-canvas.test.js +70 -0
- package/dist/tests/e2e/gui-example-canvas.test.js.map +1 -0
- package/doc/ai/extensions.md +4 -3
- package/doc/ai/zh-CN/extensions.md +2 -1
- package/doc/gui/canvas.md +47 -21
- package/doc/gui.md +7 -1
- package/doc/zh-CN/gui/canvas.md +36 -17
- package/doc/zh-CN/gui.md +6 -2
- package/package.json +1 -1
- package/runtime/ext_gui/canvas.cpp +114 -199
- package/runtime/ext_gui/canvas.h +101 -2
- package/runtime/ext_gui/canvas_gradient.cpp +112 -0
- package/runtime/ext_gui/canvas_internal.h +6 -6
- package/runtime/ext_gui/canvas_raster.cpp +27 -5
- package/runtime/ext_gui/canvas_stroke.cpp +252 -0
- package/runtime/ext_gui/canvas_style.cpp +240 -0
- package/runtime/ext_gui/dom_api.cpp +1 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +38 -4
- package/runtime/ext_gui/dom_api_canvas_extra.cpp +232 -0
- package/runtime/ext_gui/dom_api_internal.h +31 -0
- package/runtime/ext_gui/dom_api_proto.cpp +24 -0
- package/src/driver/version.ts +1 -1
|
@@ -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.
|
|
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":"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"}
|
|
@@ -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":"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"}
|
package/doc/ai/extensions.md
CHANGED
|
@@ -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
|
|
80
|
-
self-written CPU rasteriser
|
|
81
|
-
`
|
|
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
|
-
|
|
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
|
|
17
|
-
`
|
|
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`,
|
|
55
|
-
|
|
56
|
-
`
|
|
57
|
-
|
|
58
|
-
`
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
`
|
|
93
|
-
|
|
94
|
-
`
|
|
95
|
-
|
|
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.*`).
|
|
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`/
|
package/doc/zh-CN/gui/canvas.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
45
|
-
`
|
|
46
|
-
|
|
47
|
-
`
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
74
|
-
`
|
|
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
|
|
300
|
-
`
|
|
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` 参数被丢弃)
|