xbintsc 0.5.20 → 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.
Files changed (119) hide show
  1. package/AGENTS.md +5 -3
  2. package/dist/src/driver/compiler.js +1 -1
  3. package/dist/src/driver/compiler.js.map +1 -1
  4. package/dist/src/driver/runtime-objects.d.ts +1 -1
  5. package/dist/src/driver/runtime-objects.js +1 -0
  6. package/dist/src/driver/runtime-objects.js.map +1 -1
  7. package/dist/src/driver/version.d.ts +1 -1
  8. package/dist/src/driver/version.js +1 -1
  9. package/dist/src/extensions/node/fs/index.d.ts +4 -3
  10. package/dist/src/extensions/node/fs/index.js +12 -3
  11. package/dist/src/extensions/node/fs/index.js.map +1 -1
  12. package/dist/src/extensions/node/fs-promises/index.d.ts +5 -5
  13. package/dist/src/extensions/node/fs-promises/index.js +15 -6
  14. package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
  15. package/dist/tests/e2e/async-await.test.js +1 -1
  16. package/dist/tests/e2e/async-fs.test.d.ts +9 -0
  17. package/dist/tests/e2e/async-fs.test.js +119 -0
  18. package/dist/tests/e2e/async-fs.test.js.map +1 -0
  19. package/dist/tests/e2e/fetch.test.js +24 -0
  20. package/dist/tests/e2e/fetch.test.js.map +1 -1
  21. package/dist/tests/e2e/gui-canvas.test.js +159 -0
  22. package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
  23. package/dist/tests/e2e/gui-example-canvas.test.d.ts +11 -0
  24. package/dist/tests/e2e/gui-example-canvas.test.js +70 -0
  25. package/dist/tests/e2e/gui-example-canvas.test.js.map +1 -0
  26. package/dist/tests/e2e/gui-inline-svg.test.d.ts +11 -0
  27. package/dist/tests/e2e/gui-inline-svg.test.js +160 -0
  28. package/dist/tests/e2e/gui-inline-svg.test.js.map +1 -0
  29. package/doc/ai/contributing.md +2 -1
  30. package/doc/ai/extensions.md +8 -6
  31. package/doc/ai/language-support.md +2 -1
  32. package/doc/ai/zh-CN/contributing.md +1 -1
  33. package/doc/ai/zh-CN/extensions.md +6 -4
  34. package/doc/ai/zh-CN/language-support.md +2 -1
  35. package/doc/gui/api.md +8 -0
  36. package/doc/gui/canvas.md +47 -21
  37. package/doc/gui/known-issues.md +10 -9
  38. package/doc/gui/media.md +32 -3
  39. package/doc/gui.md +31 -3
  40. package/doc/implemented/async.md +16 -3
  41. package/doc/implemented/fetch.md +38 -6
  42. package/doc/implemented/node/fs-promises.md +19 -8
  43. package/doc/implemented/node.md +1 -1
  44. package/doc/implemented.md +1 -1
  45. package/doc/unimplemented/deviations.md +1 -1
  46. package/doc/unimplemented/node/async.md +6 -3
  47. package/doc/unimplemented/node/fs-promises.md +10 -6
  48. package/doc/unimplemented/node/fs.md +7 -8
  49. package/doc/unimplemented/node.md +2 -2
  50. package/doc/unimplemented/toolchain.md +1 -1
  51. package/doc/zh-CN/gui/api.md +7 -0
  52. package/doc/zh-CN/gui/canvas.md +36 -17
  53. package/doc/zh-CN/gui/known-issues.md +8 -7
  54. package/doc/zh-CN/gui/media.md +26 -2
  55. package/doc/zh-CN/gui.md +24 -3
  56. package/doc/zh-CN/implemented/async.md +13 -2
  57. package/doc/zh-CN/implemented/fetch.md +23 -4
  58. package/doc/zh-CN/implemented/node/fs-promises.md +6 -3
  59. package/doc/zh-CN/implemented/node.md +1 -1
  60. package/doc/zh-CN/implemented.md +1 -1
  61. package/doc/zh-CN/unimplemented/deviations.md +1 -1
  62. package/doc/zh-CN/unimplemented/node/async.md +6 -3
  63. package/doc/zh-CN/unimplemented/node/fs-promises.md +4 -4
  64. package/doc/zh-CN/unimplemented/node/fs.md +2 -2
  65. package/doc/zh-CN/unimplemented/node.md +2 -2
  66. package/doc/zh-CN/unimplemented/toolchain.md +1 -1
  67. package/package.json +1 -1
  68. package/runtime/ext_gui/canvas.cpp +114 -199
  69. package/runtime/ext_gui/canvas.h +101 -2
  70. package/runtime/ext_gui/canvas_gradient.cpp +112 -0
  71. package/runtime/ext_gui/canvas_internal.h +6 -6
  72. package/runtime/ext_gui/canvas_raster.cpp +27 -5
  73. package/runtime/ext_gui/canvas_stroke.cpp +252 -0
  74. package/runtime/ext_gui/canvas_style.cpp +240 -0
  75. package/runtime/ext_gui/dom_api.cpp +1 -0
  76. package/runtime/ext_gui/dom_api_canvas.cpp +38 -4
  77. package/runtime/ext_gui/dom_api_canvas_extra.cpp +232 -0
  78. package/runtime/ext_gui/dom_api_internal.h +31 -0
  79. package/runtime/ext_gui/dom_api_proto.cpp +24 -0
  80. package/runtime/ext_gui/inline_svg.cpp +219 -0
  81. package/runtime/ext_gui/inline_svg.h +41 -0
  82. package/runtime/ext_gui/layout.cpp +18 -0
  83. package/runtime/ext_gui/layout.h +7 -0
  84. package/runtime/ext_gui/layout_flow.cpp +4 -3
  85. package/runtime/ext_gui/paint.cpp +14 -0
  86. package/runtime/ext_gui/paint.h +5 -1
  87. package/runtime/ext_gui/renderer_image.cpp +53 -25
  88. package/runtime/ext_gui/style_ua.cpp +3 -0
  89. package/runtime/ext_gui/svg.cpp +122 -39
  90. package/runtime/ext_gui/svg.h +14 -3
  91. package/runtime/ext_gui/window.cpp +28 -0
  92. package/runtime/ext_node/fs/async_api.c +314 -0
  93. package/runtime/ext_node/fs/async_complete.c +99 -0
  94. package/runtime/ext_node/fs/async_fh.c +206 -0
  95. package/runtime/ext_node/fs/async_job.c +81 -0
  96. package/runtime/ext_node/fs/async_job.h +132 -0
  97. package/runtime/ext_node/fs/async_worker.c +534 -0
  98. package/runtime/ext_node/fs/async_worker_dir.c +439 -0
  99. package/runtime/ext_node/fs/async_worker_dir.h +33 -0
  100. package/runtime/ext_node/fs/async_worker_fd.c +74 -0
  101. package/runtime/ext_node/fs/dir.c +60 -15
  102. package/runtime/ext_node/fs/fs_common.h +9 -0
  103. package/runtime/ext_node/fs/fs_ops.c +18 -29
  104. package/runtime/ext_node/fs/parts/builtins.inc +34 -0
  105. package/runtime/ext_node/fs/parts/platform.inc +66 -0
  106. package/runtime/ext_node/fs/promises.c +44 -234
  107. package/runtime/rt_internal.h +2 -0
  108. package/runtime/xt_async_io.c +333 -0
  109. package/runtime/xt_async_io.h +47 -0
  110. package/runtime/xt_fetch/async.inc +295 -0
  111. package/runtime/xt_fetch/request.inc +6 -183
  112. package/runtime/xt_fetch/socket.inc +5 -4
  113. package/runtime/xt_fetch.c +6 -4
  114. package/runtime/xt_loop.c +44 -3
  115. package/src/driver/compiler.ts +1 -1
  116. package/src/driver/runtime-objects.ts +1 -0
  117. package/src/driver/version.ts +1 -1
  118. package/src/extensions/node/fs/index.ts +12 -3
  119. package/src/extensions/node/fs-promises/index.ts +15 -6
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.
@@ -34,20 +34,21 @@
34
34
  *class change* on a flex container has also been observed to crash the same
35
35
  way; `examples/gui/pelican-bike` reveals its game-over overlay with `opacity`
36
36
  instead.
37
+ - **SVG is NanoSVG's subset.** SVG *files* (`<img src="logo.svg">`) and inline
38
+ `<svg>` trees share the vendored NanoSVG parser/rasteriser
39
+ (`runtime/ext_gui/svg.cpp`, `inline_svg.cpp`), which renders
40
+ `path`/`rect`/`circle`/`ellipse`/`line`/`polyline`/`polygon`, groups, `defs`
41
+ gradients and class-based `<style>` rules. `<text>`, `<use>`, filters, masks
42
+ and clip paths are not rendered, and CSS applied by the document cascade does
43
+ not reach inside the artwork (fill/stroke are SVG presentation attributes,
44
+ not CSS properties here). Vector art that needs those has to be rasterised
45
+ beforehand, drawn from boxes, or restricted to the supported syntax. See
46
+ [Media](./media.md#images-m6a).
37
47
 
38
48
  ## Open questions
39
49
 
40
50
  - Whether Linux ships X11, Wayland, or both in the first cut. (Decision:
41
51
  X11 first, Wayland later.)
42
- - **SVG:** SVG *files* are supported through the image loader
43
- (`<img src="logo.svg">`): `runtime/ext_gui/svg.cpp` wraps the vendored
44
- **NanoSVG** parser/rasteriser and `image.{h,cpp}` treats an SVG as a vector
45
- image, rasterised at the drawn pixel size. Inline `<svg>` **trees** in a
46
- document are still not supported: the HTML parser knows elements and
47
- attributes, the layout engine knows boxes, and paint knows
48
- rectangles/text/images, so `<svg>` subtrees would need a whole second
49
- geometry+paint path. Vector art therefore has to arrive as an SVG/PNG file or
50
- be drawn from boxes, as `examples/gui/pelican-bike` does.
51
52
  - **Windows: the C++ runtime must not be named on the link line.** `gui.lib` is
52
53
  built with the *static* CRT (`CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded`) and
53
54
  every engine object records `libcpmt` as a dependent library, which is also
package/doc/gui/media.md CHANGED
@@ -12,7 +12,9 @@ size cache (`stbi_info`). Paths accept a `file://` prefix and percent-encoding.
12
12
  library (parse + anti-aliased scanline rasteriser), detects an SVG by extension
13
13
  or by its contents, and produces the same RGBA8 `Image` the pipeline already
14
14
  consumes. `stb_image` cannot decode SVG, so this is what makes
15
- `<img src="logo.svg">` work.
15
+ `<img src="logo.svg">` work. The same parser is used for an inline `<svg>`
16
+ subtree, whose DOM nodes are serialized into a document first
17
+ (`inline_svg.{h,cpp}`).
16
18
 
17
19
  - `<img>` is a replaced element (`display: inline-block`): layout gives it the
18
20
  CSS size when set, otherwise the intrinsic pixel size, and preserves the
@@ -27,12 +29,39 @@ consumes. `stb_image` cannot decode SVG, so this is what makes
27
29
  pixels (logical size × the window's pixel density), preserving the viewBox
28
30
  aspect ratio, and cached by that size, so scaling or a HiDPI display produces
29
31
  a crisp render instead of a magnified bitmap.
32
+ - An **inline** `<svg>` element is a replaced element like `<img>`, but its
33
+ source is the DOM subtree itself. `runtime/ext_gui/inline_svg.{h,cpp}`
34
+ serializes that subtree back into a standalone SVG document, restoring the
35
+ case-sensitive spellings NanoSVG matches (`viewBox`, `preserveAspectRatio`,
36
+ `linearGradient`, `gradientUnits`, …) that the HTML parser lowercased. Layout
37
+ sizes the box from the `width`/`height` attributes, else the `viewBox`, else
38
+ 300x150; paint carries the markup on its image op and the renderer parses and
39
+ rasterises it at the drawn device pixel size through the same NanoSVG cache as
40
+ an SVG file. A DOM mutation re-serializes the subtree on the next layout (so
41
+ `setAttribute` and `innerHTML` both repaint the artwork), and the markup cache
42
+ is bounded (least-recently-used) so an animated document cannot grow it
43
+ without limit.
30
44
  - The parse is cached per path; the parse and rasteriser state are released in
31
45
  `xt_svg_shutdown` (`xt_image_shutdown`).
32
46
 
47
+ Document CSS styles the `<svg>` **box** — size, margins, background, borders,
48
+ `transform`, opacity, `display` — exactly as it styles any other element. The
49
+ artwork inside it keeps SVG semantics: presentation attributes and the
50
+ `class`-based rules of an SVG `<style>` block are read by NanoSVG, not by the
51
+ HTML cascade, so `fill`/`stroke` are not CSS properties here. For layout, paint,
52
+ hit testing and events the whole `<svg>` is one replaced box: its children get no
53
+ DOM boxes, so a click's `e.target` is the `<svg>` element, not a `<rect>`. The
54
+ renderer caches one texture per markup-and-size pair, so a program that rewrites
55
+ its artwork every frame keeps a texture per variant; prefer animating the box
56
+ (`transform`, `opacity`) or swapping between a small set of documents.
57
+
33
58
  Still to do for images: CSS `background-image: url(...)`, `data:` URIs,
34
- `object-fit`, 9-slice borders and inline `<svg>` elements (only SVG *files* are
35
- supported; see [Open questions](./known-issues.md#open-questions)).
59
+ `object-fit` and 9-slice borders.
60
+
61
+ Both kinds of SVG share NanoSVG's subset: `path`, `rect`, `circle`, `ellipse`,
62
+ `line`, `polyline`, `polygon`, groups (`g`), `defs` with linear/radial gradients
63
+ and class-based `<style>` rules rasterise; `<text>`, `<use>`, filters, masks and
64
+ clip paths are not rendered (see [Known issues](./known-issues.md)).
36
65
 
37
66
  ## Video & audio (M13)
38
67
 
package/doc/gui.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xbintsc GUI extension (self-hosted HTML/CSS renderer)
2
2
 
3
- Status: **M15** — features (M1–M14) are complete: HTML parsing, CSS selector
3
+ Status: **M16** — features (M1–M15) are complete: HTML parsing, CSS selector
4
4
  matching, the cascade, computed styles and layout (block, inline, Flexbox and
5
5
  CSS positioning) are
6
6
  in place, and the engine *paints*: it builds a display list of rectangles, images
@@ -9,7 +9,9 @@ tested and delivered to native TS handlers, `:hover`/`:focus` are matched
9
9
  dynamically, `<img>` is sized from its intrinsic dimensions and drawn from a
10
10
  texture — PNG/JPEG/… bitmaps and SVG files alike, the latter rasterised at the
11
11
  drawn size so vector art stays crisp — and CSS transitions animate paint
12
- properties. **M8** adds an interactive
12
+ properties. **M16** gives inline `<svg>` trees the same treatment: the subtree is
13
+ serialized back into an SVG document and rasterised at the drawn device size, so
14
+ an icon needs no separate file. **M8** adds an interactive
13
15
  DOM: element handles with stable identity, mutation (`appendChild`, `textContent`,
14
16
  `classList`, `style`, …) and element-level events with capture/bubbling.
15
17
  **M9** compiles `<script>` bodies ahead of time (inline and `<script src>`) — see
@@ -257,6 +259,18 @@ engine were Rust.
257
259
  [Layout](./gui/layout.md#transforms-m15) and
258
260
  [Animation](./gui/animation.md#css-transforms-m15); e2e in
259
261
  `tests/e2e/gui-transform.test.ts` and `tests/e2e/gui-example-svg.test.ts`.
262
+ 16. **M16 — inline `<svg>`** ✅
263
+ - `<svg>` is a replaced element (`layout.*`): its intrinsic size comes from
264
+ the `width`/`height` attributes, else the `viewBox`, else 300x150, and the
265
+ normal box walk does not descend into its children.
266
+ - `inline_svg.{h,cpp}` serializes the subtree into a standalone SVG document,
267
+ restoring the case-sensitive names the HTML parser lowercased (`viewBox`,
268
+ `preserveAspectRatio`, `linearGradient`, `gradientUnits`, …).
269
+ - Paint carries the markup on its image op (`PaintImage::svg`) and the
270
+ renderer parses/rasterises it at the drawn device size through the same
271
+ NanoSVG cache as an SVG file (`svg.{h,cpp}`); the markup cache is bounded
272
+ and a DOM mutation re-serializes on the next layout. e2e in
273
+ `tests/e2e/gui-inline-svg.test.ts`.
260
274
 
261
275
  ## Progress log
262
276
 
@@ -340,7 +354,13 @@ engine were Rust.
340
354
  `getContext("2d")` handle and its API are bound in `dom_api_canvas.cpp`, and
341
355
  `gui_canvas.cpp` owns the per-window lifetime. The bitmap uploads through the
342
356
  image texture path (`renderer_image.cpp`) and the display list carries a
343
- `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
344
364
  `tests/e2e/gui-canvas.test.ts`.
345
365
  - **M15** ✅ CSS `transform` / `transform-origin`. A new translation unit
346
366
  (`transform.{h,cpp}`) parses `translate`/`translateX`/`translateY`/`scale`/
@@ -355,4 +375,12 @@ engine were Rust.
355
375
  through the inverse, and `getBoundingClientRect` returns the transformed AABB.
356
376
  `examples/gui/pelican-svg` animates its artwork with it; e2e in
357
377
  `tests/e2e/gui-transform.test.ts` and `tests/e2e/gui-example-svg.test.ts`.
378
+ - **M16** ✅ inline `<svg>`. `buildBoxTree` treats an `<svg>` element as a
379
+ replaced box (intrinsic size from `width`/`height`, then `viewBox`, then
380
+ 300x150) and stops descending, `inline_svg.{h,cpp}` serializes the subtree with
381
+ canonical SVG casing, paint carries the markup on `PaintImage::svg`, and
382
+ `renderer_image.cpp` rasterises it at the drawn device pixel size through the
383
+ NanoSVG cache (`svg.{h,cpp}`, bounded LRU for markup). The `win.inlineSvg`
384
+ diagnostic exposes the serialized document, and `tests/e2e/gui-inline-svg.test.ts`
385
+ pins sizing, hit testing, gradients, CSS box styling and mutation.
358
386
 
@@ -3,7 +3,8 @@
3
3
  > Part of [Implemented syntax and features](../implemented.md) · [简体中文](../zh-CN/implemented/async.md)
4
4
 
5
5
  Location: `runtime/xt_async.c`, `runtime/xt_coroutine.c`, `runtime/xt_promise.c`,
6
- `runtime/xt_generator.c`, `runtime/xt_loop.c`, `runtime/xt_performance.c`.
6
+ `runtime/xt_generator.c`, `runtime/xt_loop.c`, `runtime/xt_async_io.c`,
7
+ `runtime/xt_performance.c`.
7
8
 
8
9
  ## async / await
9
10
 
@@ -11,7 +12,7 @@ Location: `runtime/xt_async.c`, `runtime/xt_coroutine.c`, `runtime/xt_promise.c`
11
12
  - The body runs synchronously until the first `await` of a pending promise, then suspends; settlement queues a microtask that resumes the coroutine.
12
13
  - An exception that escapes the body rejects that promise instead of throwing synchronously.
13
14
  - `await` on a non-promise or settled promise keeps the synchronous fast path and raises on rejection.
14
- - Each `async` call owns a private stack and swaps its `try` frame stack and GC stack region, so `await` survives `try`/`finally`, loops and closures. A promise settled from a timer, a socket callback or another coroutine resumes the suspended body on the microtask queue.
15
+ - Each `async` call owns a private stack and swaps its `try` frame stack and GC stack region, so `await` survives `try`/`finally`, loops and closures. A promise settled from a timer, a socket callback, an offloaded file operation or another coroutine resumes the suspended body on the microtask queue.
15
16
 
16
17
  ## async generators
17
18
 
@@ -35,4 +36,16 @@ Location: `runtime/xt_async.c`, `runtime/xt_coroutine.c`, `runtime/xt_promise.c`
35
36
  - `setTimeout` / `clearTimeout` / `setInterval` / `clearInterval` / `setImmediate` / `clearImmediate` / `queueMicrotask` and `performance.now` (monotonic fractional milliseconds).
36
37
  - Timers use numeric ids; `Timeout` / `Immediate` objects are not implemented.
37
38
  - A promise settled from a timer can be awaited: the async body suspends on the pending promise and resumes when the timer fires. Sockets and servers (`net`, `http`, `dgram`) deliver their callbacks on the same loop.
38
- - `fetch` is blocking and `fs/promises` performs its I/O synchronously before settling — see [Runtime deviations](../unimplemented/deviations.md). Async generator cleanup limits are listed on the same page.
39
+ - `fetch` and `fs/promises` are reactor-driven rather than blocking — see the next section.
40
+
41
+ ## Asynchronous I/O offload
42
+
43
+ - `runtime/xt_async_io.c` owns a small pool of worker threads (four by default, created lazily). Jobs are split in two halves: a `run` function on a worker that may only touch plain C memory, and a `complete` function on the main thread that builds values and settles the promise. The split is what keeps the collector and the value model single-threaded.
44
+ - A completed job wakes `select(2)` through a self-pipe (POSIX) or a loopback UDP socket (Windows); the reactor also polls the pool, so a failed wakeup degrades to a short wait instead of a hang.
45
+ - While a job is queued or running, up to four values per job (its promise, and for `fetch` the headers object) are kept in the job's root slots and scanned by the collector.
46
+ - `fetch` runs each request/redirect hop as a job; `fs/promises` runs every file and descriptor operation as a job. Neither blocks timers, sockets or other `await`s.
47
+ - The event loop stays alive while jobs are outstanding (`xt_loop_has_work`), and exits once the pool is idle again.
48
+
49
+ Known limits of the asynchronous surface (no TLS/streaming `fetch`, inert
50
+ `fs.watch`, four-worker pool) are listed in
51
+ [Runtime deviations](../unimplemented/deviations.md) and the Node pages.
@@ -2,11 +2,43 @@
2
2
 
3
3
  > Part of [Implemented syntax and features](../implemented.md) · [简体中文](../zh-CN/implemented/fetch.md)
4
4
 
5
- Location: `runtime/xt_fetch.c`.
5
+ Location: `runtime/xt_fetch.c`, `runtime/xt_fetch/async.inc`, `runtime/xt_async_io.c`.
6
6
 
7
- - Global `fetch(input, init)` is a blocking HTTP/1.1 client whose promise is already settled (the exchange happens synchronously; it is not scheduled on the reactor).
8
- - It accepts `http://` URLs only — `https://` rejects with a `TypeError` because no TLS backend is linked.
9
- - Options: `method`, `headers` (plain object or `Headers`), `body` and `redirect` (`follow` / `manual` / `error`); up to 20 redirects are followed, and a 301/302/303 switches POST to GET.
10
- - The resolved `Response`-shaped object exposes `ok` / `status` / `statusText` / `url` / `headers` plus `text()` / `json()` / `arrayBuffer()` / `bytes()` / `clone()`.
11
- - `Headers` is case-insensitive with `get` / `has` / `set` / `append` / `delete` / `keys` / `values` / `entries` / `forEach` / `getSetCookie`.
7
+ - Global `fetch(input, init)` is an HTTP/1.1 client. The blocking socket exchange
8
+ (`getaddrinfo`, connect, send, receive) runs on the asynchronous I/O worker
9
+ pool, so a request in flight does **not** stall the event loop: timers, sockets
10
+ and other `await`s keep running and the promise settles when the reactor
11
+ dispatches the completed exchange.
12
+ - Redirects are followed on the main thread: each hop is one offloaded
13
+ exchange, up to 20 redirects, and a 301/302/303 switches POST to GET.
14
+ - It accepts `http://` URLs only — `https://` rejects with a `TypeError` because
15
+ no TLS backend is linked.
16
+ - Options: `method`, `headers` (plain object or `Headers`), `body` and `redirect`
17
+ (`follow` / `manual` / `error`).
18
+ - The resolved `Response`-shaped object exposes `ok` / `status` / `statusText` /
19
+ `url` / `headers` plus `text()` / `json()` / `arrayBuffer()` / `bytes()` /
20
+ `clone()`. The body is buffered before the promise settles; streaming bodies
21
+ are not implemented.
22
+ - `Headers` is case-insensitive with `get` / `has` / `set` / `append` /
23
+ `delete` / `keys` / `values` / `entries` / `forEach` / `getSetCookie`.
12
24
  - Invalid URLs and network failures reject with a `TypeError`.
25
+
26
+ ```ts
27
+ async function main(): Promise<void> {
28
+ const response = await fetch("http://127.0.0.1:8080/status");
29
+ console.log(response.status, await response.text());
30
+ }
31
+ main();
32
+ ```
33
+
34
+ A timer scheduled before the request completes fires while the exchange is still
35
+ in flight:
36
+
37
+ ```ts
38
+ const pending = fetch("http://127.0.0.1:8080/slow");
39
+ setTimeout(() => console.log("timer"), 10); // runs before the 150ms response
40
+ await pending;
41
+ ```
42
+
43
+ Known limits (only `http://`, no TLS, buffered body) are listed in
44
+ [Runtime deviations](../unimplemented/deviations.md).
@@ -2,14 +2,20 @@
2
2
 
3
3
  > Part of [Node extension: implemented](../node.md) · [简体中文](../../zh-CN/implemented/node/fs-promises.md)
4
4
 
5
- Location: `src/extensions/node/fs-promises/index.ts`, `runtime/ext_node/fs/promises.c`
5
+ Location: `src/extensions/node/fs-promises/index.ts`, `runtime/ext_node/fs/async_*.c`,
6
+ `runtime/ext_node/fs/promises.c`.
6
7
 
7
- There is no asynchronous I/O scheduler, so each function wraps the corresponding
8
- synchronous `fs` implementation in an **already-settled Promise**. Failures
9
- **reject** with the same Node-shaped `Error` (with `code` / `errno` / `syscall` /
10
- `path`) that the synchronous form throws. `fs.promises` is also reachable from
11
- the `fs` module (`import { promises as fsp } from "fs"`), and both namespaces
12
- expose a `constants` object.
8
+ Every function submits a job to the runtime's asynchronous I/O pool
9
+ (`runtime/xt_async_io.c`): the blocking syscall runs on a worker thread and the
10
+ promise settles on the event loop. `await` therefore yields while a large read,
11
+ a slow network filesystem or a descriptor operation is in progress, and `then` /
12
+ `catch` keep the usual Promise semantics. Failures **reject** with the same
13
+ Node-shaped `Error` (with `code` / `errno` / `syscall` / `path`) that the
14
+ synchronous form throws.
15
+
16
+ `fs.promises` is also reachable from the `fs` module
17
+ (`import { promises as fsp } from "fs"`), and both namespaces expose a
18
+ `constants` object.
13
19
 
14
20
  Promise exports: `readFile`, `writeFile`, `appendFile`, `mkdir`, `readdir`,
15
21
  `rm`, `unlink`, `rmdir`, `rename`, `copyFile`, `cp`, `realpath`, `stat`,
@@ -19,7 +25,9 @@ Promise exports: `readFile`, `writeFile`, `appendFile`, `mkdir`, `readdir`,
19
25
 
20
26
  `open(...)` resolves to a **`FileHandle`** with:
21
27
  `read`, `write`, `readFile`, `writeFile`, `appendFile`, `close`, `stat`,
22
- `truncate`, `chmod`, `chown`, `utimes`, `sync`, `datasync`.
28
+ `truncate`, `chmod`, `chown`, `utimes`, `sync`, `datasync`. Those methods are
29
+ offloaded too; `read(buffer, …)` fills the caller's `Buffer` when the worker
30
+ completes.
23
31
 
24
32
  ```ts
25
33
  import { readFile, writeFile, open } from "fs/promises";
@@ -34,5 +42,8 @@ async function main(): Promise<void> {
34
42
  main();
35
43
  ```
36
44
 
45
+ Work runs on a small fixed pool (four workers) and completions are dispatched in
46
+ submission order per worker; independent operations can overlap. A job never
47
+ blocks another `await`, a timer or a socket callback.
37
48
 
38
49
  Known deviations are listed in [Unimplemented: `fs/promises`](../../unimplemented/node/fs-promises.md).
@@ -19,7 +19,7 @@ work on another. Missing or different behaviour is recorded under
19
19
  | Module | Page | Notes |
20
20
  | --- | --- | --- |
21
21
  | `fs` | [fs](./node/fs.md) | synchronous API only |
22
- | `fs/promises` | [fs/promises](./node/fs-promises.md) | wraps the synchronous API in settled promises |
22
+ | `fs/promises` | [fs/promises](./node/fs-promises.md) | reactor-driven file and `FileHandle` operations on the async I/O pool |
23
23
  | `path` | [path](./node/path.md) | `path.<name>(...)` namespace calls |
24
24
  | `os` | [os](./node/os.md) | `os.<name>(...)` namespace calls |
25
25
  | `process` | [process](./node/process.md) | object: methods and properties |
@@ -37,7 +37,7 @@ small file instead of a single monolithic document.
37
37
  - [Collections](./implemented/collections.md) — Map, Set, WeakMap, WeakSet, WeakRef, iteration
38
38
  - [Typed arrays and binary data](./implemented/typed-arrays.md) — typed arrays, ArrayBuffer, DataView
39
39
  - [Async, promises and generators](./implemented/async.md) — async/await, async generators, promises, timers
40
- - [fetch](./implemented/fetch.md) — blocking HTTP/1.1 client and Headers
40
+ - [fetch](./implemented/fetch.md) — reactor-driven HTTP/1.1 client and Headers
41
41
 
42
42
  ### Modules and tooling
43
43
 
@@ -14,7 +14,7 @@ These features **compile and run**, but the result does not fully match ECMAScri
14
14
  | Array out-of-bounds / sparse | Out-of-bounds access returns `undefined`; assigning `arr.length` truncates / extends, but sparse holes are not tracked distinctly |
15
15
  | Memory management | Non-moving mark-sweep collector behind `xt_alloc`: explicit roots, subsystem root providers and a conservative C-stack scan. Single-threaded and stop-the-world; no moving/compaction or generational mode |
16
16
  | Function `arity` / argument count | No argument count validation; `fn.length` reports the declared arity but calls are never checked against it |
17
- | `async` / `await` | Real suspension on a stackful coroutine; `await` always queues a microtask (non-promises and already-settled promises included), so interleaving with pending `.then` callbacks matches the specification. Top-level `await` blocks on the event loop rather than yielding the module body. `fs/promises` still performs the I/O synchronously before settling; `fetch` is blocking. Async generators suspend at both `await` and `yield`; `for await...of` awaits one step per iteration |
17
+ | `async` / `await` | Real suspension on a stackful coroutine; `await` always queues a microtask (non-promises and already-settled promises included), so interleaving with pending `.then` callbacks matches the specification. Top-level `await` blocks on the event loop rather than yielding the module body. `fs/promises` and `fetch` offload their blocking work to a four-worker pool and settle on the reactor. Async generators suspend at both `await` and `yield`; `for await...of` awaits one step per iteration |
18
18
  | Async generator cleanup | `for await...of` does not call the iterator's `return()` when the loop exits early. `yield*` inside an async generator delegates to synchronous iterables only. `.return()` at a suspended `yield` completes without running `finally` (the same limitation as synchronous generators) |
19
19
  | `super` | `super.x` / `super(...)` takes the prototype of `this`'s prototype; single-level inheritance is correct, but depth > 1 may be inaccurate |
20
20
  | `Error.stack` | Function-level frames only: each frame is `at name (file:line)` with the function's declaration line (updated to the construction line for the innermost frame); no column numbers and no frames for generator / `async` bodies (they switch stacks) |
@@ -5,13 +5,16 @@
5
5
  Implemented:
6
6
 
7
7
  - Core event loop `runtime/xt_loop.c` (a `select(2)` reactor); `net` / `dgram` / `http` are all built on it.
8
- - `Promise`, `async` / `await` are available (see the core documents); `fs/promises` returns Promises.
8
+ - Async I/O offload `runtime/xt_async_io.c`: blocking file and HTTP-client work runs on four worker threads and completes on the reactor, so `fs/promises` and `fetch` do not stall timers or sockets.
9
+ - `Promise`, `async` / `await` are available (see the core documents); `fs/promises` returns Promises that settle from the reactor.
9
10
  - Timers are available as globals: `setTimeout` / `clearTimeout` / `setInterval` / `clearInterval` run on the core reactor and keep the process alive until no timer remains.
10
11
  - A standalone `EventEmitter` is provided by the `events` module (also a global constructor); streams and sockets additionally carry internal emitter methods (`on` / `addListener` / `once` / `off` / `removeListener` / `emit`).
11
12
 
12
13
  Still unimplemented:
13
14
 
14
15
  - Node's `Timeout` object (`setTimeout` returns a numeric id; `setImmediate` and `queueMicrotask` exist as globals).
15
- - A real asynchronous I/O scheduler: `fs/promises` is essentially an immediate-settle wrapper around synchronous operations and does not yield while waiting for I/O.
16
+ - File watching: `fs.watch` / `fs/promises.watch` return inert objects; there is no inotify/kqueue/ReadDirectoryChangesW backend.
17
+ - `Dir.read` / `Dir.close` invoke their callbacks immediately, and callback-style `fs.readFile` / `fs.writeFile` / … are still unsupported (use `fs/promises` or the `*Sync` forms).
16
18
  - On streams and sockets the built-in `once` is equivalent to `on` (no "remove after firing once" semantics); the `events` module's `EventEmitter` does implement real `once`.
17
- - `net`'s `connect` is blocking; HTTP responses require `Connection: close`, with no keep-alive / chunked transfer / pipelining.
19
+ - `net`'s `connect` is still a blocking `connect(2)` performed before the socket joins the reactor; HTTP responses require `Connection: close`, with no keep-alive / chunked transfer / pipelining. `fetch` has no TLS and buffers the whole body before resolving.
20
+ - The offload pool is fixed at four workers; there is no thread-pool sizing option and no `AbortSignal` cancellation for queued jobs.
@@ -2,15 +2,19 @@
2
2
 
3
3
  > Part of [Node extension: unimplemented](../node.md) · [简体中文](../../zh-CN/unimplemented/node/fs-promises.md)
4
4
 
5
- `fs/promises` wraps each synchronous `fs` call in an already-settled promise
6
- (the implemented surface is in
7
- [implemented: `fs/promises`](../../implemented/node/fs-promises.md)).
5
+ `fs/promises` now offloads its blocking I/O to the async I/O pool (the
6
+ implemented surface is in
7
+ [implemented: `fs/promises`](../../implemented/node/fs-promises.md)); the
8
+ remaining differences are behavioural:
8
9
 
9
- - I/O is synchronous: the promise is settled before it is awaited, so awaiting
10
- it does not yield to the event loop.
11
10
  - `FileHandle.appendFile` behaves like `writeFile` (it writes at the current
12
11
  file position instead of appending).
13
12
  - `FileHandle.readFile()` reads from the current file-descriptor offset.
14
- - `Dir.read` / `Dir.close` (sync and promise forms) complete immediately.
13
+ - `Dir.read` / `Dir.close` (sync and promise forms) complete their callbacks
14
+ immediately; `Dir.readSync` / `closeSync` remain synchronous.
15
15
  - `watch` resolves to the same never-emitting watcher as `fs.watch`; there is no
16
16
  real file-watching backend.
17
+ - Reads and writes are whole-file/one-shot operations; there are no streaming
18
+ `FileHandle` variants and no `AbortSignal` cancellation.
19
+ - The pool has four workers; very many simultaneous operations queue behind
20
+ them (they still do not block the event loop).
@@ -2,14 +2,13 @@
2
2
 
3
3
  > Part of [Node extension: unimplemented](../node.md) · [简体中文](../../zh-CN/unimplemented/node/fs.md)
4
4
 
5
- `fs` now implements the **synchronous** API surface (files, directories,
6
- descriptors, metadata, links, `cp`, `glob`, `Dir`, `constants`) and
7
- `fs/promises` wraps each synchronous function in an already-settled Promise
5
+ `fs` implements the **synchronous** API surface (files, directories,
6
+ descriptors, metadata, links, `cp`, `glob`, `Dir`, `constants`), and
7
+ `fs/promises` runs every file/descriptor operation on the asynchronous I/O pool
8
8
  (see [implemented: `fs`](../../implemented/node/fs.md) and
9
- [implemented: `fs/promises`](../../implemented/node/fs-promises.md)). The rest is still
10
- unimplemented because xbintsc has **no asynchronous I/O scheduler** for file
11
- operations (the `fs` module has no reactor backend; the core event loop does
12
- provide timers and sockets):
9
+ [implemented: `fs/promises`](../../implemented/node/fs-promises.md)). What is
10
+ still missing is mostly about APIs that need a different backend or callback
11
+ plumbing:
13
12
 
14
13
  | Unimplemented API | Category |
15
14
  | --- | --- |
@@ -24,7 +23,7 @@ provide timers and sockets):
24
23
  - `readFileSync` supports encoding options (default/`utf8`/`ascii`/`latin1`/`binary`/ `hex`/`base64`/`base64url`), but **does not return a `Buffer`** by default: even though `Buffer` exists (simulated as a plain object), `readFileSync` still returns a string. Pass a `Buffer`-producing path or read raw bytes explicitly.
25
24
  - `utf16le` / `ucs2` decoding is not implemented (the bytes are returned as text).
26
25
  - `watch` / `watchFile` / `unwatchFile` return API-shaped emitter objects whose `.close()` / `.on()` methods exist but which **never fire** events.
27
- - `Dir.read(cb)` / `Dir.close(cb)` and the async form of `FileHandle` methods complete **synchronously** (the callback/Promise is invoked immediately).
26
+ - `Dir.read(cb)` / `Dir.close(cb)` complete **synchronously** (the callback is invoked immediately); `FileHandle` methods do return real promises now.
28
27
  - `mkdtempSync` always appends 6 random characters to the prefix (it does not require a trailing `XXXXXX`).
29
28
  - `globSync` supports `*`, `?`, `[...]` and `**` but not the `exclude` callback or `follow`; `**` does not descend through symlinks (matching Node's default).
30
29
  - `cpSync` symlink handling follows Node for the common cases but `verbatimSymlinks` is not supported.
@@ -10,10 +10,10 @@ is missing or differs from Node.
10
10
  ## Contents
11
11
 
12
12
  - [`fs`](./node/fs.md) — callback-style async operations, real file watching, option callbacks
13
- - [`fs/promises`](./node/fs-promises.md) — synchronous I/O behind a settled promise, `FileHandle` deviations
13
+ - [`fs/promises`](./node/fs-promises.md) — `FileHandle` append/read offsets, immediate `Dir` callbacks, inert `watch`, no cancellation
14
14
  - [Fully unimplemented modules](./node/modules.md) — `https`, `readline`, `tls`, `cluster`, `vm`, ...
15
15
  - [Global objects and namespaces](./node/globals.md) — `global`, `__dirname`, `require` outside `node_modules`
16
- - [Async and the event-loop model](./node/async.md) — no asynchronous I/O scheduler, blocking `connect`, no keep-alive
16
+ - [Async and the event-loop model](./node/async.md) — blocked file watching, blocking `net.connect`, no keep-alive, fixed-size offload pool
17
17
  - [The Bun extension](./node/bun.md) — design example only
18
18
 
19
19
  A module's implemented page links to its limitation page; when a limitation is
@@ -9,6 +9,6 @@
9
9
  | Type checker | ✗ only diagnostic codes are defined; no checker |
10
10
  | Full standard library | partial: Math / JSON / Date / Map / Set / RegExp / `Error` / `BigInt` / `Symbol` / `structuredClone` implemented; String.normalize missing |
11
11
  | Multi-file module bundling | partial: relative-path `.ts` bundling, ESM `node_modules` packages, namespace imports and bare-specifier extension module imports implemented; CommonJS is handled only inside `node_modules`; circular dependencies / live bindings are not implemented |
12
- | Real async runtime | ✓ `async`/`await` suspends on pending promises on the `select(2)` reactor: timers, TCP sockets and promise callbacks resume the coroutine. Still missing: truly asynchronous I/O scheduling (`fs/promises` blocks before settling), a non-blocking `fetch` and worker threads (workers are child processes) |
12
+ | Real async runtime | ✓ `async`/`await` suspends on pending promises on the `select(2)` reactor: timers, TCP sockets, offloaded file/HTTP work and promise callbacks resume the coroutine. Still missing: real OS-level file watching, non-blocking `net.connect`, TLS for `fetch`, and user-visible worker threads (`worker_threads` uses child processes) |
13
13
  | Windows binary artifact verification | adapted at the build layer (`.exe` suffix, link flag branch), verified in CI |
14
14
  | Precise ECMAScript number / string / comparison semantics | partial, see [Runtime deviations](./deviations.md) |
@@ -64,6 +64,7 @@ win.documentTree() // 序列化的 DOM(调试用)
64
64
  win.layoutTree() // 序列化的布局盒(调试用)
65
65
  win.paintCount() // 显示列表中图形的数量
66
66
  win.paintList() // 序列化的显示列表(调试用)
67
+ win.inlineSvg(selector) // 序列化的内联 <svg>:{ markup, width, height, shapes }
67
68
  win.measureText(text, fontSize?, family?) // 整形后的推进宽度(像素)
68
69
  win.fontMetrics(fontSize?, family?) // { ascent, descent, lineHeight, ready }
69
70
  win.hitTest(x, y) // 最深的元素描述符,或 ""
@@ -72,6 +73,12 @@ win.advance(ms) // 推进 CSS transition 时钟(测试
72
73
  win.stats() // 引擎/运行时计数器(内存排查用)
73
74
  ```
74
75
 
76
+ `win.inlineSvg(selector)` 返回引擎为第一个匹配 `selector` 的 `<svg>` 元素构建的
77
+ 独立 SVG 文档(`markup`),以及 NanoSVG 从它解析出的固有尺寸与图形数量;没有
78
+ 匹配或元素不是 `<svg>` 时返回 `undefined`。它让序列化器的规范大小写
79
+ (`viewBox`、`linearGradient`、`gradientUnits` 等)无需 GPU 即可测试——HTML
80
+ 解析器会把它们全部小写化。
81
+
75
82
  `win.stats()` 返回 `{ heapBytes, heapAllocations, treeNodes, detachedRoots,`
76
83
  `detachedNodes, handles, handleSlots, listeners }`:运行时存活堆、DOM 树规模、
77
84
  分离节点池(detached pool)以及元素句柄表。稳定状态下它们都应保持常量——若