xbintsc 0.5.45 → 0.5.51

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 (206) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +5 -4
  3. package/README.zh-CN.md +4 -3
  4. package/dist/src/cli/extensions.d.ts +24 -0
  5. package/dist/src/cli/extensions.js +62 -0
  6. package/dist/src/cli/extensions.js.map +1 -0
  7. package/dist/src/cli/help.d.ts +1 -1
  8. package/dist/src/cli/help.js +3 -1
  9. package/dist/src/cli/help.js.map +1 -1
  10. package/dist/src/cli/hints.js +8 -3
  11. package/dist/src/cli/hints.js.map +1 -1
  12. package/dist/src/cli/main.js +21 -28
  13. package/dist/src/cli/main.js.map +1 -1
  14. package/dist/src/diagnostics/diagnostic.d.ts +3 -1
  15. package/dist/src/diagnostics/diagnostic.js +2 -0
  16. package/dist/src/diagnostics/diagnostic.js.map +1 -1
  17. package/dist/src/driver/build-key.d.ts +2 -0
  18. package/dist/src/driver/build-key.js +1 -0
  19. package/dist/src/driver/build-key.js.map +1 -1
  20. package/dist/src/driver/compiler.js +61 -8
  21. package/dist/src/driver/compiler.js.map +1 -1
  22. package/dist/src/driver/config.d.ts +4 -0
  23. package/dist/src/driver/config.js +2 -0
  24. package/dist/src/driver/config.js.map +1 -1
  25. package/dist/src/driver/link-features.d.ts +37 -0
  26. package/dist/src/driver/link-features.js +123 -0
  27. package/dist/src/driver/link-features.js.map +1 -0
  28. package/dist/src/driver/options.d.ts +10 -0
  29. package/dist/src/driver/runtime-objects.d.ts +5 -0
  30. package/dist/src/driver/runtime-objects.js +20 -1
  31. package/dist/src/driver/runtime-objects.js.map +1 -1
  32. package/dist/src/driver/usage.d.ts +23 -0
  33. package/dist/src/driver/usage.js +353 -0
  34. package/dist/src/driver/usage.js.map +1 -0
  35. package/dist/src/driver/version.d.ts +1 -1
  36. package/dist/src/driver/version.js +1 -1
  37. package/dist/src/extensions/catalog.js +1 -2
  38. package/dist/src/extensions/catalog.js.map +1 -1
  39. package/dist/src/extensions/gui/features.d.ts +31 -0
  40. package/dist/src/extensions/gui/features.js +160 -0
  41. package/dist/src/extensions/gui/features.js.map +1 -0
  42. package/dist/src/extensions/gui/index.js +10 -4
  43. package/dist/src/extensions/gui/index.js.map +1 -1
  44. package/dist/src/extensions/link-feature.d.ts +70 -0
  45. package/dist/src/extensions/link-feature.js +18 -0
  46. package/dist/src/extensions/link-feature.js.map +1 -0
  47. package/dist/src/extensions/registry.d.ts +24 -4
  48. package/dist/src/extensions/registry.js +26 -3
  49. package/dist/src/extensions/registry.js.map +1 -1
  50. package/dist/src/index.d.ts +3 -2
  51. package/dist/src/index.js +0 -1
  52. package/dist/src/index.js.map +1 -1
  53. package/dist/tests/cli/hints.test.js +5 -7
  54. package/dist/tests/cli/hints.test.js.map +1 -1
  55. package/dist/tests/driver/link-features.test.js +105 -0
  56. package/dist/tests/driver/link-features.test.js.map +1 -0
  57. package/dist/tests/driver/usage.test.d.ts +1 -0
  58. package/dist/tests/driver/usage.test.js +88 -0
  59. package/dist/tests/driver/usage.test.js.map +1 -0
  60. package/dist/tests/e2e/gui-audio.test.js +4 -4
  61. package/dist/tests/e2e/gui-audio.test.js.map +1 -1
  62. package/dist/tests/e2e/gui-feature-linking.test.d.ts +9 -0
  63. package/dist/tests/e2e/gui-feature-linking.test.js +68 -0
  64. package/dist/tests/e2e/gui-feature-linking.test.js.map +1 -0
  65. package/dist/tests/e2e/gui-helpers.d.ts +5 -1
  66. package/dist/tests/e2e/gui-helpers.js +2 -0
  67. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  68. package/dist/tests/e2e/gui-layout.test.js +4 -1
  69. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  70. package/dist/tests/e2e/gui-media-disabled.test.d.ts +5 -4
  71. package/dist/tests/e2e/gui-media-disabled.test.js +7 -6
  72. package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -1
  73. package/dist/tests/e2e/gui-media.test.d.ts +5 -4
  74. package/dist/tests/e2e/gui-media.test.js +9 -9
  75. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  76. package/dist/tests/e2e/gui-paint.test.js +5 -2
  77. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  78. package/dist/tests/e2e/gui-shaping.test.d.ts +5 -0
  79. package/dist/tests/e2e/gui-shaping.test.js +47 -0
  80. package/dist/tests/e2e/gui-shaping.test.js.map +1 -0
  81. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.d.ts +18 -0
  82. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js +120 -0
  83. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js.map +1 -0
  84. package/dist/tests/extensions/gui-features.test.d.ts +1 -0
  85. package/dist/tests/extensions/gui-features.test.js +168 -0
  86. package/dist/tests/extensions/gui-features.test.js.map +1 -0
  87. package/doc/ai/cli.md +5 -1
  88. package/doc/ai/extensions.md +48 -23
  89. package/doc/ai/troubleshooting.md +10 -7
  90. package/doc/ai/zh-CN/cli.md +5 -1
  91. package/doc/ai/zh-CN/extensions.md +41 -20
  92. package/doc/ai/zh-CN/troubleshooting.md +6 -5
  93. package/doc/gui/api.md +2 -1
  94. package/doc/gui/build.md +136 -19
  95. package/doc/gui/canvas.md +1 -1
  96. package/doc/gui/known-issues.md +32 -3
  97. package/doc/gui/layout.md +1 -1
  98. package/doc/gui/media.md +13 -7
  99. package/doc/gui/rendering.md +32 -15
  100. package/doc/gui/runtime.md +5 -0
  101. package/doc/gui/webgl.md +24 -6
  102. package/doc/gui.md +50 -35
  103. package/doc/implemented/cli.md +2 -0
  104. package/doc/implemented/extensions.md +9 -3
  105. package/doc/npm-package.md +4 -4
  106. package/doc/xbintsc.config.schema.json +10 -0
  107. package/doc/zh-CN/gui/api.md +2 -1
  108. package/doc/zh-CN/gui/build.md +110 -16
  109. package/doc/zh-CN/gui/canvas.md +1 -1
  110. package/doc/zh-CN/gui/known-issues.md +24 -3
  111. package/doc/zh-CN/gui/layout.md +1 -1
  112. package/doc/zh-CN/gui/media.md +11 -6
  113. package/doc/zh-CN/gui/rendering.md +26 -9
  114. package/doc/zh-CN/gui/runtime.md +3 -0
  115. package/doc/zh-CN/gui/webgl.md +3 -3
  116. package/doc/zh-CN/gui.md +42 -29
  117. package/doc/zh-CN/implemented/cli.md +2 -0
  118. package/doc/zh-CN/implemented/extensions.md +9 -3
  119. package/doc/zh-CN/npm-package.md +4 -3
  120. package/package.json +7 -7
  121. package/runtime/ext_gui/browser.cpp +22 -15
  122. package/runtime/ext_gui/browser_loop.cpp +13 -1
  123. package/runtime/ext_gui/canvas.h +1 -1
  124. package/runtime/ext_gui/canvas_raster.cpp +1 -1
  125. package/runtime/ext_gui/canvas_text.cpp +1 -1
  126. package/runtime/ext_gui/dom_api_canvas.cpp +15 -8
  127. package/runtime/ext_gui/dom_api_canvas_extra.cpp +44 -20
  128. package/runtime/ext_gui/dom_api_canvas_image.cpp +3 -1
  129. package/runtime/ext_gui/dom_api_node_read.cpp +10 -0
  130. package/runtime/ext_gui/dom_api_offscreen.cpp +3 -1
  131. package/runtime/ext_gui/dom_api_style.cpp +7 -0
  132. package/runtime/ext_gui/dom_api_webgl_internal.h +4 -0
  133. package/runtime/ext_gui/dom_api_webgl_source.cpp +5 -2
  134. package/runtime/ext_gui/gpu_bridge.c +28 -0
  135. package/runtime/ext_gui/gpu_feature.cpp +30 -0
  136. package/runtime/ext_gui/gpu_shader.cpp +22 -0
  137. package/runtime/ext_gui/gpu_shader_api.h +50 -0
  138. package/runtime/ext_gui/gui.cpp +6 -37
  139. package/runtime/ext_gui/gui_canvas.cpp +14 -6
  140. package/runtime/ext_gui/gui_engine.h +1 -18
  141. package/runtime/ext_gui/gui_events.cpp +3 -2
  142. package/runtime/ext_gui/gui_webgl.cpp +25 -10
  143. package/runtime/ext_gui/image_api.cpp +18 -0
  144. package/runtime/ext_gui/image_api.h +41 -0
  145. package/runtime/ext_gui/image_bitmap_browser.cpp +20 -11
  146. package/runtime/ext_gui/image_bridge.c +22 -0
  147. package/runtime/ext_gui/image_feature.cpp +105 -0
  148. package/runtime/ext_gui/layout.cpp +13 -8
  149. package/runtime/ext_gui/layout.h +2 -2
  150. package/runtime/ext_gui/media.cpp +4 -2
  151. package/runtime/ext_gui/media.h +3 -3
  152. package/runtime/ext_gui/media_api.h +7 -6
  153. package/runtime/ext_gui/renderer.h +1 -1
  154. package/runtime/ext_gui/renderer_image.cpp +13 -6
  155. package/runtime/ext_gui/renderer_layer.cpp +10 -5
  156. package/runtime/ext_gui/stb_truetype.h +5077 -0
  157. package/runtime/ext_gui/text.cpp +24 -498
  158. package/runtime/ext_gui/text.h +7 -5
  159. package/runtime/ext_gui/text_api.h +45 -0
  160. package/runtime/ext_gui/text_bridge.c +23 -0
  161. package/runtime/ext_gui/text_cache.cpp +1 -1
  162. package/runtime/ext_gui/text_glyph_cache.cpp +2 -2
  163. package/runtime/ext_gui/text_hb.cpp +554 -0
  164. package/runtime/ext_gui/text_stb.cpp +513 -0
  165. package/runtime/ext_gui/webgl_api.cpp +19 -0
  166. package/runtime/ext_gui/webgl_api.h +53 -0
  167. package/runtime/ext_gui/webgl_bridge.c +25 -0
  168. package/runtime/ext_gui/webgl_draw_setup.cpp +42 -1
  169. package/runtime/ext_gui/webgl_feature.cpp +82 -0
  170. package/runtime/ext_gui/webgl_feature.h +28 -0
  171. package/runtime/ext_gui/webgl_msl.cpp +41 -0
  172. package/runtime/ext_gui/webgl_msl.h +6 -0
  173. package/runtime/ext_gui/webgl_shader.cpp +40 -32
  174. package/runtime/ext_gui/window.cpp +7 -7
  175. package/runtime/ext_media/decode_audio.cpp +1 -1
  176. package/runtime/ext_media/media.cpp +1 -1
  177. package/runtime/ext_media/media_bridge.c +7 -7
  178. package/scripts/build-gui.ts +97 -98
  179. package/scripts/check-file-length.ts +1 -0
  180. package/scripts/gui-build-options.ts +122 -0
  181. package/src/cli/extensions.ts +72 -0
  182. package/src/cli/help.ts +3 -1
  183. package/src/cli/hints.ts +9 -3
  184. package/src/cli/main.ts +21 -27
  185. package/src/diagnostics/diagnostic.ts +2 -0
  186. package/src/driver/build-key.ts +3 -0
  187. package/src/driver/compiler.ts +63 -8
  188. package/src/driver/config.ts +6 -0
  189. package/src/driver/link-features.ts +163 -0
  190. package/src/driver/options.ts +10 -0
  191. package/src/driver/runtime-objects.ts +26 -1
  192. package/src/driver/usage.ts +376 -0
  193. package/src/driver/version.ts +1 -1
  194. package/src/extensions/catalog.ts +1 -2
  195. package/src/extensions/gui/features.ts +162 -0
  196. package/src/extensions/gui/index.ts +10 -4
  197. package/src/extensions/link-feature.ts +72 -0
  198. package/src/extensions/registry.ts +45 -4
  199. package/src/index.ts +3 -2
  200. package/dist/src/extensions/media/index.d.ts +0 -28
  201. package/dist/src/extensions/media/index.js +0 -59
  202. package/dist/src/extensions/media/index.js.map +0 -1
  203. package/dist/tests/extensions/media.test.js +0 -63
  204. package/dist/tests/extensions/media.test.js.map +0 -1
  205. package/src/extensions/media/index.ts +0 -65
  206. /package/dist/tests/{extensions/media.test.d.ts → driver/link-features.test.d.ts} +0 -0
package/doc/gui.md CHANGED
@@ -21,13 +21,15 @@ what `examples/gui/pelican-bike` — a playable 2D game — is built on. **M12**
21
21
  DOM node lifetime explicit (what a mutation releases, what it keeps) and gives the
22
22
  engine bounded memory: see [Node lifetime](./gui/dom.md#node-lifetime) and
23
23
  [Memory & GC](./gui/runtime.md#memory--gc). **M13** plays real
24
- `<video>`/`<audio>` — the optional `media` extension decodes them on the main
25
- thread with a vendored minimal FFmpeg, video frames go through the image texture
26
- path and audio to an SDL stream, with the DOM media API exposing
27
- `play`/`pause`/`currentTime`/… and media events. Enabling `gui` alone does not
24
+ `<video>`/`<audio>` — the optional **`media` link feature** decodes them on the
25
+ main thread with a vendored minimal FFmpeg, video frames go through the image
26
+ texture path and audio to an SDL stream, with the DOM media API exposing
27
+ `play`/`pause`/`currentTime`/… and media events. The compiler selects the
28
+ feature from the program (a `<video>`/`<audio>` element, `new Audio()`,
29
+ `decodeAudioData`, or markup built at run time), so a DOM/CSS program does not
28
30
  link FFmpeg. The browser globals also get a real `AudioContext`: a 48 kHz mixer
29
31
  in `runtime/ext_gui/audio_mixer.cpp` plus `decodeAudioData` (WAV in-engine,
30
- compressed formats through `media`) — see
32
+ compressed formats through the `media` feature) — see
31
33
  [GUI API](./gui/api.md#web-audio).
32
34
  **M14** adds `<canvas>` with a self-written 2D software rasteriser (anti-aliased
33
35
  fills, paths, text and `drawImage`) uploaded through the image texture path —
@@ -38,7 +40,7 @@ them) can be translated, scaled and rotated without reflowing anything — and
38
40
  `transition: transform`, so those poses animate. See
39
41
  [Layout](./gui/layout.md#transforms-m15) and
40
42
  [Animation](./gui/animation.md#css-transforms-m15).
41
- **M17** adds real **WebGL 1.0**, and **M18** adds **WebGL 2.0**: `<canvas>.getContext("webgl2")` accepts `#version 300 es` shaders, vertex-array objects, instanced draws and sampler objects, all compiled to SPIR-V by the bundled glslang (and to MSL by the bundled SPIRV-Cross on Metal) and rendered through SDL_GPU. Geometry, lighting, texture sampling and blending run on the GPU and the page composites the colour texture without a readback — see [WebGL](./gui/webgl.md).
43
+ **M17** adds real **WebGL 1.0**, and **M18** adds **WebGL 2.0**: `<canvas>.getContext("webgl2")` accepts `#version 300 es` shaders, vertex-array objects, instanced draws and sampler objects, all compiled to SPIR-V by the bundled glslang (and to MSL by the bundled SPIRV-Cross on Metal) and rendered through SDL_GPU. Geometry, lighting, texture sampling and blending run on the GPU and the page composites the colour texture without a readback — see [WebGL](./gui/webgl.md). glslang is an optional **`gpu` link feature** shared with `mix-blend-mode`; the WebGL context/DOM bindings are the separate **`webgl` feature**, which requires `gpu`. A DOM/2D-canvas page links neither.
42
44
  **M19** fills in the decorative CSS layer: `@media` queries, generated content
43
45
  (`::before`/`::after`), multi-layer linear/radial/repeating backgrounds,
44
46
  `text-shadow`, `clip-path`, `mix-blend-mode` (through an offscreen compositor)
@@ -100,7 +102,8 @@ pages above.
100
102
  | 4 | The engine is **not self-hosted** (it is C/C++, not TS), but must not affect the compiler's platform-agnostic design. Delivered as a per-platform prebuilt archive linked via `nativeObjects`. |
101
103
  | 5 | The **event loop is generic**: the runtime exposes a generic main-loop hook and a poll primitive; nothing GUI-specific enters `runtime/`. |
102
104
  | 6 | **Multiple windows** are supported by the core object model. |
103
- | 7 | Third-party low-level libs are **statically linked into the extension archives** so releases stay self-contained; only OS frameworks are added at link time. `gui.a` carries SDL3/FreeType/HarfBuzz, and the optional `media.a` carries FFmpeg and is linked only with `--ext media`. |
105
+ | 7 | Third-party low-level libs are **statically linked into the extension archives** so releases stay self-contained; only OS frameworks are added at link time. `gui.a` carries SDL3, FreeType/HarfBuzz for the optional `shaping` feature, glslang and (on macOS only) SPIRV-Cross; the default text path uses the bundled stb_truetype. glslang and SPIRV-Cross are only *extracted* when the compiler selects the **`gpu` link feature**; Metal is the only SDL_GPU backend that consumes MSL, so merging SPIRV-Cross elsewhere would add ~2.5 MiB no driver can ask for. The WebGL context/DOM code is the **`webgl` feature** (requires `gpu`), and stb_image/NanoSVG are the **`images` feature**. The optional `media.a` carries FFmpeg and is linked only when the **`media` link feature** is selected. All features are chosen from the program's static usage; `--feature`/`--no-feature` override the scan. |
106
+ | 8 | **Heavy optional halves are link features, not extensions.** FFmpeg and glslang are selected by what the bundled HTML/CSS/TS may use, and a small C bridge (linked only when active) installs the feature's dispatch table and forces the linker to extract the implementation. This keeps one user-facing `gui` extension while a DOM-only program pays for neither stack. |
104
107
 
105
108
  ## Architecture
106
109
 
@@ -120,7 +123,7 @@ pages above.
120
123
  │
121
124
  ▼
122
125
  windowing + input (SDL3) ── multiple windows
123
- text (HarfBuzz + FreeType) ── glyph atlas / SDF
126
+ text (stb_truetype) ── glyph atlas
124
127
  images (stb_image; SVG via NanoSVG)
125
128
  media DOM + audio sink ── optional media.a (FFmpeg) installs the decoder
126
129
  ```
@@ -134,14 +137,19 @@ with `nativeObjects()`, `linkerFlags()` and `modules()`.
134
137
  | --- | --- | --- |
135
138
  | Window + input + multi-window | **SDL3** | cross-platform windows, HiDPI, IME, clipboard, DnD, close/hide events |
136
139
  | GPU | **SDL_GPU** (SDL3) | one render path over Metal / Vulkan / D3D12; avoids three backends |
137
- | Text shaping | **HarfBuzz** | correct complex-script shaping |
138
- | Glyph raster | **FreeType** | glyph outlines → GPU atlas / SDF |
140
+ | Text shaping | **stb_truetype** simple shaper; optional **`shaping`** feature links HarfBuzz + FreeType | default keeps the DOM-only binary near 3 MiB; `shaping` restores ligatures and complex-script shaping |
141
+ | Glyph raster | **stb_truetype**; FreeType only under `shaping` | TrueType outlines → grayscale atlas by default |
139
142
  | Images | **stb_image** (vendored header) for bitmaps, **NanoSVG** (vendored headers) for SVG | single headers to start; SVG files rasterise on demand |
140
- | Video + audio | **FFmpeg** (statically vendored, minimal build, optional `media` extension) | decodes the common container/codec set; no WebView or platform media framework |
143
+ | Video + audio | **FFmpeg** (statically vendored, minimal build, optional `media` link feature) | decodes the common container/codec set; no WebView or platform media framework |
141
144
 
142
- Pinned versions: SDL3 `release-3.2.10`, FreeType `2.13.3`, HarfBuzz `10.1.0`,
143
- FFmpeg `7.1` for the optional `media` archive (overridable with `SDL3_TAG` /
144
- `FREETYPE_VERSION` / `HARFBUZZ_VERSION` / `FFMPEG_VERSION`).
145
+ Pinned versions: SDL3 `release-3.2.10`, FreeType `2.13.3`, HarfBuzz `10.1.0`
146
+ (for the optional `shaping` feature), glslang `16.6.0`, SPIRV-Cross
147
+ `vulkan-sdk-1.4.313.0`, FFmpeg `7.1` for the optional `media` archive
148
+ (overridable with `SDL3_TAG` / `FREETYPE_VERSION` / `HARFBUZZ_VERSION` /
149
+ `GLSLANG_VERSION` / `SPIRV_CROSS_VERSION` / `FFMPEG_VERSION`). The default text
150
+ backend is the bundled `stb_truetype.h`; it accepts TrueType outlines (not
151
+ CFF/OTF or variable fonts). The `shaping` feature swaps in HarfBuzz + FreeType
152
+ for full OpenType shaping.
145
153
 
146
154
  Alternative if SDL3 is rejected: **GLFW + OpenGL 3.3** (simpler, but OpenGL is
147
155
  deprecated on macOS and gives no modern GPU abstraction). **wgpu-native** if the
@@ -169,10 +177,11 @@ engine were Rust.
169
177
  4. **M4 — paint + text + display list**
170
178
  - **M4a — display list + GPU shapes** ✅ background/border display list,
171
179
  rounded-rect SDL_GPU pipeline (`paint.*`, `renderer.*`).
172
- - **M4b-1 — text stack + metrics** ✅ HarfBuzz + FreeType linked into
173
- `gui.a`, font resolution/caching, shaping-based text metrics used by
174
- layout (`text.*`, `measureText`/`fontMetrics`).
175
- - **M4b-2 — glyph rendering** ✅ FreeType rasterisation, a shared shelf-packed
180
+ - **M4b-1 — text stack + metrics** ✅ the text module (`text.*`) with font
181
+ resolution/caching and shaping-based metrics used by layout
182
+ (`measureText`/`fontMetrics`); originally HarfBuzz + FreeType, now the
183
+ bundled stb_truetype.
184
+ - **M4b-2 — glyph rendering** ✅ stb_truetype rasterisation, a shared shelf-packed
176
185
  glyph atlas, textured text quads in the display list and HiDPI-aware raster
177
186
  scaling. Gradients remain.
178
187
  5. **M5 — input + events** ✅
@@ -199,16 +208,17 @@ engine were Rust.
199
208
  (`package-release` copies all of `runtime/`) and inside the optional
200
209
  `@xbintsc/<os>-<arch>` npm package. Windows x64 is included; Windows arm64
201
210
  still skips them.
202
- - `vendor/` (SDL3/FreeType/HarfBuzz for the GUI archive, FFmpeg for `media`)
211
+ - `vendor/` (SDL3/FreeType/HarfBuzz/glslang for the GUI archive, FFmpeg for `media`)
203
212
  is cached per OS/arch, keyed by `scripts/build-gui.ts` /
204
213
  `scripts/build-media.ts`. Windows x64 gets its own source cache plus the
205
214
  installed `vendor/ffmpeg-build-msvc/dist` prefix.
206
- - Windows x64 builds both archives in CI and links a `gui,media` program,
207
- using the same Visual Studio `vcvarsall` + bundled LLVM toolchain as a
208
- local build (`.github/actions/vs-toolchain`); the host has no GPU/Vulkan
209
- driver, so *running* the GUI/media examples there remains provisional (a
210
- failure is logged without annotating the run). The Windows arm64 job still
211
- skips both archives (see
215
+ - Windows x64 builds both archives in CI and links a `gui` program that
216
+ exercises the `media` link feature (plus an explicit `--feature media`
217
+ build), using the same Visual Studio `vcvarsall` + bundled LLVM toolchain
218
+ as a local build (`.github/actions/vs-toolchain`); the host has no
219
+ GPU/Vulkan driver, so *running* the GUI/media examples there remains
220
+ provisional (a failure is logged without annotating the run). The Windows
221
+ arm64 job still skips both archives (see
212
222
  [Open questions](./gui/known-issues.md#open-questions)).
213
223
  8. **M8 — interactive DOM** ✅
214
224
  - Element/document handles with stable identity, read/write properties via
@@ -260,15 +270,17 @@ engine were Rust.
260
270
  `tests/e2e/gui-retention.test.ts` (see [Memory & GC](./gui/runtime.md#memory--gc)).
261
271
  13. **M13 — video & audio** ✅
262
272
  - `<video>` / `<audio>` decode through a statically vendored minimal FFmpeg
263
- in the optional `media` extension (`runtime/ext_media/media.cpp`), frames
273
+ in the optional `media` link feature (`runtime/ext_media/media.cpp`); the
274
+ compiler selects it from the program and only then compiles the bridge and
275
+ links `media.a`. Frames
264
276
  upload to GPU textures through the existing image pipeline, audio is
265
277
  pushed to an SDL stream on the main thread by the GUI's audio sink, and
266
278
  the DOM media API plus `loadedmetadata`/`play`/`pause`/`ended`/
267
279
  `timeupdate`/`error` events are exposed on element handles
268
280
  (`dom_api_media.cpp`, `dom_api_proto.cpp`). Controlled from TS + CSS;
269
281
  there are no built-in controls. Example in `examples/gui/media.*`, e2e in
270
- `tests/e2e/gui-media.test.ts` (with the `media` extension) and
271
- `tests/e2e/gui-media-disabled.test.ts` (without it).
282
+ `tests/e2e/gui-media.test.ts` (auto-selected) and
283
+ `tests/e2e/gui-media-disabled.test.ts` (`--no-feature media`).
272
284
  14. **M14 — `<canvas>` 2D** ✅
273
285
  - `<canvas>` is a replaced element (`xtgui::Canvas2D`, `layout.*`), the
274
286
  `getContext("2d")` handle and drawing API live in `dom_api_canvas.cpp`,
@@ -373,9 +385,10 @@ engine were Rust.
373
385
  coverage.
374
386
  - **M4a** ✅ display list (`paint.*`) and the SDL_GPU 2D renderer with an MSL
375
387
  rounded-rect pipeline (`renderer.*`), `paintList`/`paintCount`, e2e coverage.
376
- - **M4b-1** ✅ FreeType + HarfBuzz fetched/built/merged into `gui.a`, the text
377
- module (`text.*`) with font resolution, HarfBuzz shaping and FreeType metrics,
378
- real text metrics in layout, `measureText`/`fontMetrics`, e2e coverage.
388
+ - **M4b-1** ✅ the text module (`text.*`) with font resolution, shaping and
389
+ metrics, real text metrics in layout, `measureText`/`fontMetrics`, e2e
390
+ coverage. Originally HarfBuzz + FreeType; now stb_truetype (see the stack
391
+ table and the size trade-off in [build](./gui/build.md#binary-size)).
379
392
  - **M4b-2** ✅ glyph atlas + textured text pipeline in `renderer.*`, shaped text
380
393
  runs in `paint.*`, `xt_text_shape_run`/`xt_text_rasterize` in `text.*`, HiDPI
381
394
  raster scaling, e2e coverage.
@@ -430,17 +443,19 @@ engine were Rust.
430
443
  ratcheting upwards. `win.stats()` exposes the engine counters and
431
444
  `tests/e2e/gui-retention.test.ts` pins them; a game loop rewriting its sprite
432
445
  every frame now runs in constant memory (see [Memory & GC](./gui/runtime.md#memory--gc)).
433
- - **M13** ✅ `<video>` / `<audio>` support. The optional `media` extension
446
+ - **M13** ✅ `<video>` / `<audio>` support. The optional `media` **link feature**
434
447
  vendors and builds a minimal static FFmpeg (`scripts/build-media.ts`) and
435
448
  merges it into `media.a` (or `media.lib` from clang-cl/MSVC objects on
436
- Windows); `runtime/ext_media/media.cpp` decodes video to RGBA
449
+ Windows). The compiler selects the feature from the program and only then
450
+ compiles `media_bridge.c` and links `media.a`; a DOM/CSS program never links
451
+ FFmpeg. `runtime/ext_media/media.cpp` decodes video to RGBA
437
452
  frames and audio to 48 kHz stereo PCM on the main thread,
438
453
  `runtime/ext_gui/gui_media.cpp` owns the players, implements the SDL audio
439
454
  sink and pumps the media clock and events each frame,
440
455
  `renderer_image.cpp` uploads each new video frame through the image texture
441
456
  path, and `dom_api_media.cpp` exposes `play`/`pause`/`load` plus the media
442
- properties on element handles. `gui.a` stays FFmpeg-free; enabling `media`
443
- without `gui` is rejected by the registry. `examples/gui/media.*` demonstrates
457
+ properties on element handles. `gui.a` stays FFmpeg-free.
458
+ `examples/gui/media.*` demonstrates
444
459
  the TS-plus-CSS-controls pattern and `tests/e2e/gui-media.test.ts` pins it.
445
460
  - **M14** ✅ `<canvas>` support. A new replaced element (`layout.*`) carries an
446
461
  intrinsic size from its `width`/`height` attributes (default `300x150`),
@@ -29,6 +29,8 @@ default; `--no-check` skips it (and is part of the cache key). See
29
29
  --emit <kind> exe | obj | ir (default: exe)
30
30
  -O0..-O3 Optimization level (default: -O2)
31
31
  --ext <names> Comma separated extensions (e.g. node)
32
+ --feature <names> Force optional link features on (media,gpu,images,shaping,webgl,all)
33
+ --no-feature <n> Keep optional link features out even when detected
32
34
  --ext-native <m> Register a C++/Rust extension from a JSON manifest
33
35
  --config <path> Use a project config (default: xbintsc.config.json)
34
36
  --no-config Do not read any project config
@@ -2,10 +2,16 @@
2
2
 
3
3
  > Part of [Implemented syntax and features](../implemented.md) · [简体中文](../zh-CN/implemented/extensions.md)
4
4
 
5
- Location: `src/extensions/registry.ts`, `src/extensions/node/`
5
+ Location: `src/extensions/registry.ts`, `src/extensions/node/`, `src/extensions/link-feature.ts`, `src/driver/usage.ts`, `src/driver/link-features.ts`
6
6
 
7
- - An extension is a plain object: `runtimeSources()` (extra C sources), `linkerFlags()` (extra link flags), `builtins()` (global identifier → runtime symbol, uniform `(argc, argv)` ABI) and `modules()` (import specifier → named exports / namespace).
8
- - `ExtensionRegistry`: register / unregister / lookup / aggregate builtins, modules, runtime sources and link flags.
7
+ - An extension is a plain object: `runtimeSources()` (extra C sources), `linkerFlags()` (extra link flags), `builtins()` (global identifier → runtime symbol, uniform `(argc, argv)` ABI), `modules()` (import specifier → named exports / namespace) and `linkFeatures()` (optional heavy capabilities).
8
+ - `ExtensionRegistry`: register / unregister / lookup / aggregate builtins, modules, runtime sources, link flags and link features.
9
+ - **Optional link features** (`media`, `gpu`, `images`, `shaping`, `webgl`):
10
+ - A feature declares `runtimeSources()` (a C bridge compiled only when active), `nativeObjects()` (archives linked only when active), `linkerFlags()`, optional `requires` (another feature it depends on) and `used(usage)`.
11
+ - The driver builds `ProgramUsage` from the bundled AST (`collectUsage` in `src/driver/usage.ts`): string literals (including imported HTML and inlined CSS), identifiers/property names, `getContext(...)` kinds, and whether markup can be built at run time. Simple `const` aliases are resolved; unresolved markup makes the program conservative.
12
+ - `resolveLinkFeatures` applies `--feature` / `--no-feature` (and `all`), expands `requires`, reports an unavailable auto-detected feature as a warning and a forced one as an error, and returns the bridges/archives/flags to link.
13
+ - The bridge is the strong reference: its constructor installs the feature's dispatch table and its references force the linker to extract the implementation. With no bridge, the table stays null and the implementation members are never linked.
14
+ - Current clients: `media` (FFmpeg, `runtime/ext_media/media_bridge.c` + `media.a`), `gpu` (glslang/SPIRV-Cross, `runtime/ext_gui/gpu_bridge.c` + `gpu_feature.cpp` inside `gui.a`), `images` (stb_image/NanoSVG, `runtime/ext_gui/image_bridge.c` + `image_feature.cpp`), `shaping` (HarfBuzz/FreeType, `runtime/ext_gui/text_bridge.c` + `text_hb.cpp`), and `webgl` (context/DOM bindings, `runtime/ext_gui/webgl_bridge.c` + `webgl_feature.cpp`, requiring `gpu`).
9
15
  - The built-in core extension `core`: exposes `print` (mapped to `xt_println`), always registered.
10
16
  - The Node extension `node`:
11
17
  - Modular organisation: `src/extensions/node/fs/` + `runtime/ext_node/fs/read_file.c`
@@ -69,8 +69,8 @@ The package does **not** contain:
69
69
  If npm skips the optional package (`--omit=optional`, a package manager that
70
70
  does not install optional dependencies, or a host the release does not build
71
71
  for), the driver falls back to compiling the C runtime from the shipped sources
72
- and caches the objects; that build works but is slower, and `--ext gui` /
73
- `--ext media` need the archive to be present.
72
+ and caches the objects; that build works but is slower, and `--ext gui` (plus
73
+ the auto-selected `media`/`gpu`/`images`/`shaping`/`webgl` link features) needs the archive to be present.
74
74
 
75
75
  ## Requirements
76
76
 
@@ -398,7 +398,7 @@ jobs:
398
398
  | --- | --- | --- |
399
399
  | Needs Node.js | Yes, 22+ | No |
400
400
  | First build | Links the prebuilt runtime from the optional platform package | Links the prebuilt runtime |
401
- | GUI / media extensions | Included on Linux/macOS and Windows x64 (the hosts the release builds them for) | Included in Linux/macOS and Windows x64 archives |
401
+ | GUI engine + media archive | Included on Linux/macOS and Windows x64 (the hosts the release builds them for) | Included in Linux/macOS and Windows x64 archives |
402
402
  | Installation | `npm install` + lockfile / provenance | Download, verify `.sha256`, unpack |
403
403
  | Programmatic API | Yes | No (CLI only) |
404
404
  | Cross-compiles | No — host only | No — host only |
@@ -439,7 +439,7 @@ the same file under `node_modules/xbintsc/doc/ai/`.
439
439
  | The first build takes a long time | The optional `@xbintsc/<os>-<arch>` package was skipped (or this host has no prebuilt package), so the runtime C sources are compiled and cached. Check `npm ls @xbintsc/<os>-<arch>` and reinstall without `--omit=optional`; `--time` confirms. |
440
440
  | Permission errors writing `.xbintsc/` | Set `xbintsc_CACHE_DIR` to a writable directory, or run from a writable working directory. |
441
441
  | `module 'fs' is provided by the 'node' extension` | Add `--ext node`, or `"extensions": ["node"]` in the config. |
442
- | `GUI native library not found` / `media native library not found` | The optional platform package that ships `gui.a`/`gui.lib` or `media.a`/`media.lib` was skipped, or this host has no prebuilt GUI/media archive (Windows arm64). Reinstall without `--omit=optional`, use a Linux/macOS/Windows x64 release archive, or build the archive from a source checkout. |
442
+ | `GUI native library not found` / `media native library not found` | The optional platform package that ships `gui.a`/`gui.lib` or `media.a`/`media.lib` was skipped, or this host has no prebuilt GUI/media archive (Windows arm64). Reinstall without `--omit=optional`, use a Linux/macOS/Windows x64 release archive, or build the archive from a source checkout. An auto-detected `media` feature with a missing archive only warns; `--feature media` makes it an error. |
443
443
  | `require("xbintsc")` fails | The package is ESM-only; use `import` (or dynamic `import()`). |
444
444
  | Windows link errors about missing MSVC libraries | Run from a Native Tools prompt, install the VS C++ build tools, and check `npx xbintsc doctor`. |
445
445
  | `npm ci --omit=dev` then the build cannot find `xbintsc` | Install devDependencies for the build stage, or move `xbintsc` to `dependencies`. |
@@ -48,6 +48,16 @@
48
48
  "items": { "type": "string" },
49
49
  "description": "Paths to native C++/Rust extension manifests."
50
50
  },
51
+ "features": {
52
+ "type": "array",
53
+ "items": { "type": "string" },
54
+ "description": "Optional link features to force on, e.g. [\"media\", \"gpu\", \"images\", \"shaping\", \"webgl\"] or [\"all\"]."
55
+ },
56
+ "noFeatures": {
57
+ "type": "array",
58
+ "items": { "type": "string" },
59
+ "description": "Optional link features to keep out even when auto-detection finds a use."
60
+ },
51
61
  "force": {
52
62
  "type": "boolean",
53
63
  "description": "Ignore the incremental cache."
@@ -128,7 +128,8 @@ requestAnimationFrame(frame);
128
128
  `getChannelData(i)` 是真正的 `Float32Array`,可以用代码逐样本填充。
129
129
  `decodeAudioData(bytes)` 返回解码后的 AudioBuffer Promise:WAV(PCM 8/16/24/32 位、
130
130
  IEEE float 32/64、A-law 与 µ-law)由引擎自身解码;MP3、AAC/M4A、Vorbis、Opus 与 FLAC
131
- 需要可选的 `media` 扩展(`--ext gui,media`),以其中的 FFmpeg 解码器作为回退。
131
+ 需要可选的 `media` 链接特性(程序调用 `decodeAudioData` 时自动选中),以其中的
132
+ FFmpeg 解码器作为回退。
132
133
  `bytes` 可以是 ArrayBuffer、类型化数组、Node `Buffer`,或 `fs.readFileSync`
133
134
  返回的原始字节字符串(本运行时没有专门的二进制类型,而该字符串的存储正是文件字节)。
134
135
  解码结果会重采样到上下文的 48 kHz,因此 `buffer.sampleRate` 与
@@ -7,13 +7,29 @@
7
7
  - 源码位于 `runtime/ext_gui/`(C/C++),外加 `vendor/` 下的 vendored 库
8
8
  (被 gitignore;按需获取)。媒体解码位于 `runtime/ext_media/`,单独构建。
9
9
  - `npm run gui`(`scripts/build-gui.ts`)获取固定版本的 SDL3(`SDL3_TAG`,
10
- 默认 `release-3.2.10`),构建静态 SDL3,获取并构建静态 FreeType
11
- (`FREETYPE_VERSION`)与 HarfBuzz(`HARFBUZZ_VERSION`),并获取、构建静态
10
+ 默认 `release-3.2.10`),构建静态 SDL3,获取并构建静态 **FreeType**
11
+ (`FREETYPE_VERSION`,默认 `2.13.3`)与 **HarfBuzz**(`HARFBUZZ_VERSION`,
12
+ 默认 `10.1.0`)供可选的 `shaping` 特性使用,并获取、构建静态
12
13
  **glslang**(`GLSLANG_VERSION`,默认 `16.6.0`)、固定版本的 SPIR-V 头文件
13
- (`SPIRV_HEADERS_VERSION`)以及 **SPIRV-Cross**(`SPIRV_CROSS_VERSION`,默认
14
- `vulkan-sdk-1.4.313.0`)。glslang 是 WebGL 背后的运行时 GLSL ES 编译器,
15
- SPIRV-Cross 把它的 SPIR-V 转成 Metal 后端使用的 MSL(见
16
- [WebGL](./webgl.md)),两者都与其他库一样合并进归档。随后编译引擎,并把
14
+ (`SPIRV_HEADERS_VERSION`)以及——**仅 macOS**——**SPIRV-Cross**
15
+ (`SPIRV_CROSS_VERSION`,默认 `vulkan-sdk-1.4.313.0`)。glslang 是 WebGL
16
+ 背后的运行时 GLSL ES 编译器,SPIRV-Cross 把它的 SPIR-V 转成 Metal 后端使用的
17
+ MSL(见 [WebGL](./webgl.md)),两者都与其他库一样合并进归档。Metal 是唯一消费
18
+ MSL 的 SDL_GPU 后端,因此 Windows/Linux 上脚本既不获取也不合并 SPIRV-Cross:
19
+ 那里的 `webgl_msl.cpp` 编译为空,合并进来只会多出约 2.5 MiB 不可达代码。
20
+ 默认文本路径使用内置的 **stb_truetype**(`runtime/ext_gui/stb_truetype.h`);
21
+ HarfBuzz/FreeType 只在 `shaping` 特性启用时被取出。
22
+ 每个库(SDL3、FreeType、HarfBuzz、glslang、SPIRV-Cross 与引擎)都用
23
+ `-ffunction-sections -fdata-sections` 编译;SDL 关闭引擎从不调用的子系统
24
+ (`SDL_JOYSTICK`、`SDL_HAPTIC`、`SDL_SENSOR`、`SDL_CAMERA`、`SDL_RENDER`、
25
+ `SDL_OPENGL`/`SDL_OPENGLES`、`SDL_OFFSCREEN`、`SDL_HIDAPI`);HarfBuzz 关闭
26
+ 整形后端从不使用的 API(`HB_NO_DRAW`/`PAINT`/`COLOR`/`NAME`/`STYLE`/`METRICS`/…,
27
+ 但保留 `CFF`/`OPEN`/`VAR` 以支持 OTF 与可变字体),并以
28
+ `HB_NO_PRAGMA_GCC_DIAGNOSTIC_ERROR` 编译:`src/hb.hh` 会把自身警告集提升为
29
+ 错误,而两个精简宏(`HB_NO_MMAP` 与 `HB_NO_OT_FONT_GLYPH_NAMES`)会让
30
+ macOS/CoreText 的辅助函数变成未引用。链接器随后丢弃
31
+ 不可达函数(`--gc-sections`、`-dead_strip` 或 link.exe 默认的 `/OPT:REF`)。
32
+ 随后编译引擎,并把
17
33
  所有内容合并到 `runtime/lib/<os>-<arch>/gui.a`(Windows 上 MSVC ABI 则为
18
34
  `gui.lib`),与 `core.a` 采用相同约定,静态打包这些库。合并使用 macOS 上的
19
35
  `libtool` 与其他平台上的 `ar -M`(GNU/LLVM);CMake 归档会在构建根目录或
@@ -23,12 +39,15 @@
23
39
  demuxer/parser/decoder 的最小静态构建,用它编译
24
40
  `runtime/ext_media/media.cpp`,并把所有内容合并到
25
41
  `runtime/lib/<os>-<arch>/media.a`(Windows 上 MSVC ABI 则为 `media.lib`)。
26
- `gui.a` **不会**链接 FFmpeg:程序通过 `--ext media` 显式启用播放能力。
42
+ `gui.a` **不会**链接 FFmpeg:编译器为可能播放媒体的程序选中 `media` 链接特性后
43
+ 才链接 `media.a`(见[链接特性](#链接特性))。
27
44
  - `src/extensions/gui/index.ts` 通过 `nativeObjects()` 暴露引擎归档(经由
28
45
  `findRuntimeLibrary`,因此 `.a`/`.lib` 都可用),并通过 `linkerFlags()`
29
- 暴露操作系统框架。`src/extensions/media/index.ts` 对 `media.a`/`media.lib`
30
- 做同样的暴露,并把注册桥(`runtime/ext_media/media_bridge.c`)作为普通
31
- runtime 源编译。
46
+ 暴露操作系统框架。它的 `linkFeatures()`(位于
47
+ `src/extensions/gui/features.ts`)声明 `media`、`gpu`、`images`、`shaping` 与 `webgl`;driver 只在该特性
48
+ 启用时编译对应的 bridge。
49
+ - glslang(以及 macOS 上的 SPIRV-Cross)与 SDL3 一样合并进 `gui.a`,但引擎只
50
+ 通过 `gpu` 特性的分发表访问它;从不编译 GLSL 的程序不会取出这些归档成员。
32
51
  - `scripts/build-gui.ts` 使用与驱动**相同**的 clang
33
52
  (`src/driver/toolchain-provider.ts`);Windows 上就是 Visual Studio 自带的、
34
53
  面向 MSVC 的 LLVM,并配套使用其同目录下的 `clang++`/`llvm-ar` 来配置 CMake、
@@ -53,9 +72,10 @@
53
72
  - CI 在组装发布归档之前构建 `gui.a`/`gui.lib` 与 `media.a`/`media.lib`,因此它们
54
73
  会随 `runtime/lib/<slug>/` 一起发布(发布 tarball 会复制整个 `runtime/` 树)。
55
74
  同一批目录还会打进可选的 `@xbintsc/<slug>` npm 包,因此发布覆盖的宿主平台从
56
- npm 安装后也能使用 `--ext gui`/`--ext media`。归档在 Linux(Xvfb 下,使用
75
+ npm 安装后也能使用 `--ext gui`(`media`/`gpu`/`images`/`shaping`/`webgl` 自动选择)。归档在 Linux(Xvfb 下,使用
57
76
  lavapipe 软件 Vulkan 驱动)与 macOS 上构建并运行
58
- 示例;Windows x64 也会构建两个归档并链接一个 `gui,media` 程序,因此 MSVC
77
+ 示例;Windows x64 也会构建两个归档并链接一个会触发 `media` 特性的 `gui` 程序
78
+ (另有一条显式 `--feature media` 的构建),因此 MSVC
59
79
  构建在 CI 中得到验证,只是*运行*示例仍为试验性(托管 runner 没有 GPU/Vulkan
60
80
  驱动)。macOS 的运行同样是试验性的:Intel runner 的虚拟 GPU 缺少
61
81
  `MTLGPUFamilyMac2`,引擎会以 **77**(“此机器无法运行 GUI”)退出,任务记录为跳过,
@@ -69,8 +89,9 @@
69
89
  configure/make 构建树本身不缓存(其中记录了另一台机器的绝对路径与编译选项)。
70
90
  - 现有的缓存指纹已经会对 `nativeObjects()` 的内容与 `runtimeSources()` 的内容
71
91
  做哈希,因此重建任一归档都会自动使缓存二进制失效。
72
- - CMake 构建树位于其源码**旁边**(`vendor/sdl-build`、`vendor/freetype-build`、
73
- `vendor/harfbuzz-build`),绝不放在源码内部:`CMakeCache.txt` 记录的是配置它
92
+ - CMake 构建树位于其源码**旁边**(`vendor/sdl-build-lean`、
93
+ `vendor/freetype-build-lean`、`vendor/harfbuzz-build-lean`、
94
+ `vendor/glslang-build-lean`、`vendor/spirv-cross-build-lean`),绝不放在源码内部:`CMakeCache.txt` 记录的是配置它
74
95
  的那台机器,而 SDL 的视频后端是 `option()`,因此先运行的那次配置会一直生效。
75
96
  所以一个同时携带 `build/` 的 `vendor/SDL` 缓存树,会把某台机器的
76
97
  `SDL_X11`/`SDL_WAYLAND` 选择带进另一台机器的配置——即使头文件已安装,SDL 也会
@@ -79,6 +100,78 @@
79
100
  检查 X11/Wayland 头文件,并按名称报告缺失的开发包(当头文件位于该检查未知的
80
101
  前缀下时,可设置 `xbintsc_SKIP_HEADER_CHECK=1`)。
81
102
 
103
+ ## 链接特性
104
+
105
+ 用户只启用一个 `gui` 扩展,但其中的重型部分是可选的**链接特性**,按程序选择:
106
+
107
+ | 特性 | 链接什么 | 程序可能用到时 |
108
+ | --- | --- | --- |
109
+ | `media` | `media_bridge.c` + `media.a`(FFmpeg) | HTML 里有 `<video>`/`<audio>`、`createElement("video"/"audio")`、`new Audio()`、`decodeAudioData`,或运行时拼出的 HTML |
110
+ | `gpu` | `gpu_bridge.c`(实现本身已在 `gui.a`) | `getContext("webgl"/"webgl2"/"experimental-webgl")`(或运行时决定的 kind)、任意 `webgl` 字符串、`mix-blend-mode`/`mixBlendMode`,或运行时拼出的 HTML |
111
+ | `images` | `image_bridge.c`(实现本身已在 `gui.a`) | `<img>`、内联 `<svg>`、`background-image`/`url(...)`、`.svg` 路径、`drawImage`/`createImageBitmap`/`Image`,或运行时拼出的 HTML |
112
+ | `shaping` | `text_bridge.c`(实现本身已在 `gui.a`) | 非 ASCII 文本(CJK/西里尔/希腊文等)、转义或实体码点,或运行时拼出的 HTML |
113
+ | `webgl` | `webgl_bridge.c`(实现本身已在 `gui.a`,依赖 `gpu`) | WebGL 的 `getContext(...)`、`webgl` 字符串,或运行时拼出的 HTML |
114
+
115
+ driver 会扫描打包后的 AST(`src/driver/usage.ts`):所有字符串字面量(包括导入的
116
+ `.html` 与内联进来的 CSS)、标识符与属性名、每个 `getContext(...)` 的 kind,以及
117
+ HTML/CSS 是否可能来自编译器看不见的数据。简单的 `const` 别名会被解析;无法解析的
118
+ markup 会让程序按“动态 markup”保守处理,启用可选特性。
119
+
120
+ 特性启用时,driver 只为本构建编译它的 C **bridge**,并链接在 `gui.a` 之前:
121
+
122
+ - `media` → `runtime/ext_media/media_bridge.c`;构造函数安装解码表并引用
123
+ `xt_media_backend_*`,从而把 FFmpeg 成员从 `media.a` 里取出。
124
+ - `gpu` → `runtime/ext_gui/gpu_bridge.c`;构造函数调用 `gpu_feature.cpp` 里的
125
+ `xt_gpu_feature_register`,安装 `XtGpuShaderApi` 表并引用 `compile_glsl` /
126
+ `cross_compile_msl` / 反射函数,从而取出 glslang(macOS 上还有 SPIRV-Cross)。
127
+ - `images` → `runtime/ext_gui/image_bridge.c`;构造函数通过 `image_feature.cpp`
128
+ 安装 `XtImageApi`,其引用取出 stb_image 与 NanoSVG。layout、paint、canvas
129
+ `drawImage` 与应用图标都走这张表,因此纯文本页面不链接这两个解码器。
130
+ - `shaping` → `runtime/ext_gui/text_bridge.c`;构造函数通过 `text_hb.cpp` 安装
131
+ `TextApi`,其引用取出 HarfBuzz 与 FreeType。没有它时引擎使用内置 stb_truetype,
132
+ 纯拉丁页面两个库都不链接。
133
+ - `webgl` → `runtime/ext_gui/webgl_bridge.c`;构造函数通过 `webgl_feature.cpp` 安装
134
+ `XtWebglApi`,其引用取出 WebGL context 与 DOM 绑定。core 的 `getContext`、
135
+ canvas 宽高 setter、`createImageBitmap`、host type 与 renderer 都走这张表,因此
136
+ 不用 WebGL 的页面不链接任何 `webgl_*`/`dom_api_webgl*`。
137
+
138
+ bridge 就是让链接器搜索归档的“强引用”;没有 bridge 时引擎的表保持 null,
139
+ 实现成员永不被取出。因此大库可以留在现有归档里,不需要新的包布局。
140
+
141
+ 覆盖方式:`--feature media,gpu,images,shaping,webgl`(或 `all`)强制开启;
142
+ `--no-feature media` 即使检测到也不链接。`webgl` 依赖 `gpu`,resolver 会一起
143
+ 激活;配置应用图标会强制 `images`,因为图标走同一条解码管线。`--ext media` 是
144
+ `--ext gui --feature media` 的弃用别名。
145
+ 自动检测到的特性归档缺失时构建打印 warning 并继续(元素/上下文按文档降级);
146
+ 显式强制的特性则报错。
147
+
148
+ > 检测是静态的。用 `fetch`、`readFileSync` 等运行时数据拼 HTML/CSS 的页面应传
149
+ > `--feature all`(或具体特性)。`--verbose` 会打印选中的集合,例如
150
+ > `xbintsc: link features: none`。
151
+
152
+ ### 二进制体积
153
+
154
+ DOM-only 程序的小体积来自上述 lean 构建。Windows x64 上,一个 `hello` 规模的程序
155
+ 从约 6.4 MiB 降到约 3.1 MiB:SDL 按上面的选项配置,function/data sections 让
156
+ `/OPT:REF` 丢弃页面触达不到的函数,WebGL/图片链接特性保持关闭,文本改用内置
157
+ stb_truetype 而不是 HarfBuzz + FreeType。剩余重量主要是静态 CRT、lean SDL
158
+ (约 1 MiB)、stb_truetype(约 0.1 MiB)以及 DOM/CSS/layout/paint 引擎本身
159
+ (约 1.2 MiB)。Linux 与 macOS 使用各自的链接器 section(`--gc-sections`/
160
+ `-dead_strip`),数字会有差异,但相对排序相同。
161
+
162
+ WebGL 与图片/SVG 是链接特性(约 0.4 MiB 的 WebGL context/DOM 代码与 0.23 MiB 的
163
+ stb_image/NanoSVG,DOM-only 页面不链接)。Canvas2D、Web Audio 与
164
+ `mix-blend-mode` 合成器尚未按需化;不使用它们的程序仍要付出其 API 面积(每项大约
165
+ 0.1–0.2 MiB)。
166
+
167
+ 文本替换是有意的取舍。默认后端是 stb_truetype:只接受 TrueType 轮廓,整形是逐码点
168
+ 映射加字体的 `kern` 表,因此 CFF/OTF 与可变字体、OpenType 连字以及复杂文种
169
+ (阿拉伯/印度/泰文)整形不可用;fallback 链仍会在主字体缺字时切换到其他字体。
170
+ 选中可选的 **`shaping`** 特性(非 ASCII 文本会自动选中,也可用
171
+ `--feature shaping`)会重新链接 HarfBuzz + FreeType(约 1.2 MiB),恢复完整的
172
+ OpenType 整形与 CFF/OTF/可变字体支持。纯拉丁 DOM 页面因此保持约 3.1 MiB;
173
+ 中文/复杂文种页面更接近 4.3 MiB。
174
+
82
175
  ## 运行示例
83
176
 
84
177
  ```sh
@@ -117,9 +210,10 @@ Chocolatey/独立 LLVM,CI、本地构建与预编译归档共用同一条编
117
210
 
118
211
  `examples/gui/media.ts`(配合 `media.html`)演示 `<video>`/`<audio>`:它在
119
212
  `<video>` 上叠一个 CSS 进度条,并用 `play()`/`pause()`/`currentTime`/
120
- `volume` 驱动播放。它需要单独的可选 `media` 扩展,因此要构建并同时启用两者。
213
+ `volume` 驱动播放。编译器从 `media.html` 里看到这些元素后会自动选中 `media`
214
+ 特性;先用 `npm run media` 构建一次 `media.a`。
121
215
  媒体 `src` 相对于进程工作目录解析,因此从仓库根目录运行:
122
- `xbintsc run examples/gui/media.ts --ext gui,media`。几何报告见该目录的 README。
216
+ `xbintsc run examples/gui/media.ts --ext gui`。几何报告见该目录的 README。
123
217
 
124
218
  这些测试套件在 Windows 上默认跳过(归档可以在本机通过 Vulkan 运行,但 CI
125
219
  runner 没有可用的 GPU 驱动)。如果本机有 `gui.lib` 与可用的 GPU,可用
@@ -156,7 +156,7 @@ canvas 由脚本重绘而不是引擎重绘,因此开销按绘制调用计。
156
156
  (奇数长度列表会复制一遍,负数/非有限值会被忽略,全零列表表示实线)。
157
157
 
158
158
  文本:`measureText(text)`(返回 `{ width }`)、`fillText(text, x, y, maxWidth?)` 与
159
- `strokeText(text, x, y, maxWidth?)`。文本经 HarfBuzz 排版、FreeType 光栅化(与 HTML
159
+ `strokeText(text, x, y, maxWidth?)`。文本经 stb_truetype 整形与光栅化(与 HTML
160
160
  文本相同的技术栈),按变换的近似缩放绘制。正的 `maxWidth` 会按比例压缩步进与字形位图,
161
161
  使文本宽度不超过该值(与 canvas API 一致),而不是忽略该参数。**仅支持平移与均匀
162
162
  缩放**——旋转或斜切变换仍会把每个字形放到其变换后的基线点,但不会旋转字形位图。
@@ -20,6 +20,15 @@
20
20
  `WebGLRenderer` 直接运行在真实后端上,不再有 Canvas2D 回退。阴影贴图
21
21
  (比较型 `sampler2DShadow` 采样深度纹理)、立方体贴图/环境贴图与 HDR 渲染目标
22
22
  均已支持。
23
+ - **部分 Intel Mac 上比较型采样器会被错误编译。** 在 Intel GPU(在 Intel Iris
24
+ Plus Graphics + macOS 26 上观察到)上,Metal 着色器编译器会丢弃调用
25
+ `sample_compare` 的片元函数:绘制不产生任何像素,于是
26
+ `texture(sampler2DShadow, …)` 读到黑色,阴影贴图消失。MSL 本身是正确的
27
+ (`depth2d<float>` + `sample_compare`),采样器也带比较操作,而同一个程序在
28
+ 打开 Metal GPU 着色器校验后渲染正常——在这类机器上以
29
+ `MTL_SHADER_VALIDATION=1` 运行即可让
30
+ `tests/e2e/gui-webgl2-shadow-sampler.test.ts` 通过。这指向驱动的着色器编译器
31
+ 而非引擎,因此以环境变量作为绕过办法;其他 Apple GPU 未必存在该问题。
23
32
 
24
33
 
25
34
  - **自动关闭时的偶发崩溃。** 设置 `XT_GUI_AUTOCLOSE_MS` 结束运行时,约五次中有两次会以原生
@@ -79,6 +88,17 @@
79
88
  `win.stats()` 的 `textCacheBytes`/`textShapeHits` 能看出它是否成为瓶颈。
80
89
  元素自带的 `style="…"` 内联属性在该元素每次重算样式时都会重新解析。
81
90
 
91
+ - **文本整形是简单整形器。** stb_truetype 覆盖拉丁/CJK/西里尔/希腊文、基础
92
+ `kern` 与逐码点回退,但不执行 OpenType 连字或复杂文种(阿拉伯/印度/泰文)整形,
93
+ 且只接受 TrueType 轮廓(不支持 CFF/OTF 或可变字体);彩色 emoji 也不会以彩色
94
+ 渲染。这是 DOM-only 二进制约 3 MiB 的代价;可选的 `shaping` 特性会重新链接
95
+ HarfBuzz + FreeType,恢复完整整形。
96
+ - **链接特性检测是静态的。** `media`/`gpu`/`images`/`shaping`/`webgl` 从打包后的 HTML/CSS/TS(包括导入的
97
+ `.html` 与其内联 CSS)里选择。用运行时数据(`fetch`、`readFileSync`、函数返回值)
98
+ 拼 HTML/CSS 的页面无法被看到;扫描器在能证明 sink 是动态时会保守地启用可选特性,
99
+ 但对于它无法分类的不透明值,需要显式 `--feature media`/`gpu`/`images`/`shaping`/`webgl`(或 `all`)。
100
+ `--verbose` 会打印选中的集合。见[链接特性](./build.md#链接特性)。
101
+
82
102
  ## 待决问题
83
103
 
84
104
  - Linux 首发是只发布 X11、Wayland,还是两者都发布。(决定:先 X11,
@@ -97,7 +117,8 @@
97
117
  - Windows:`scripts/build-gui.ts` 会生成 MSVC 兼容的 `gui.lib`
98
118
  (COFF 对象 + `ar -M`/`llvm-ar`)。Windows x64 的 CI job 使用 Visual Studio
99
119
  工具链(`.github/actions/vs-toolchain` 导入 `vcvarsall` 并采用 VS 自带的
100
- LLVM)构建它、链接一个 `gui,media` 程序,并随 x64 发布归档一起发布。示例在
120
+ LLVM)构建它、链接一个会触发 `media` 链接特性的 `gui` 程序,并随 x64 发布归档
121
+ 一起发布。示例在
101
122
  托管 runner 上的*运行*仍为试验性,因为那里没有 GPU/Vulkan 驱动(SDL_GPU 在
102
123
  Windows 上选择 **Vulkan** 后端,因此不需要 DXIL/`dxc` 工具链)。Windows
103
124
  arm64 仍跳过两个归档。在开发者机器上构建并运行
@@ -105,12 +126,12 @@
105
126
  - **Windows:x64 在 CI 中构建,arm64 与运行时验证仍为试验性。**
106
127
  `scripts/build-media.ts` 用 FFmpeg 的 MSVC 工具链
107
128
  (`--toolchain=msvc --cc=clang-cl`)生成 `media.lib`,其 COFF 对象与静态 CRT
108
- 和 `gui.lib` 一致,`--ext media` 在 Windows 上可以正常链接并播放。构建它需要
129
+ 和 `gui.lib` 一致,自动选中的 `media` 特性在 Windows 上可以正常链接并播放。构建它需要
109
130
  POSIX shell 运行 FFmpeg 的 `configure` 以及 GNU make;shell 来自 Git for
110
131
  Windows(克隆 SDL3 本来就需要 Git),make 是原生 GNU make(Chocolatey 的
111
132
  `make`,或 `xbintsc_MAKE`)。CI 会显式安装 make,在
112
133
  Windows x64 上使用与本地构建相同的 Visual Studio `vcvarsall` + 自带 LLVM
113
- 构建 `gui.lib` 与 `media.lib`,链接 `gui,media` 程序,并把两个归档放进
134
+ 构建 `gui.lib` 与 `media.lib`,链接一个会触发该特性的 `gui` 程序,并把两个归档放进
114
135
  Windows x64 发布包;示例的*运行*仍因托管 runner 没有 GPU/Vulkan 驱动而保持
115
136
  试验性。Windows arm64 仍跳过两个归档;`<video controls>` 同样推迟——预期模式
116
137
  是 TS + CSS 叠层,如 `examples/gui/media.ts`。
@@ -18,7 +18,7 @@
18
18
  基于单词的断行、`text-align` 与 `line-height`。行内元素获得其后代几何信息
19
19
  的并集;`display: inline-block` 以 shrink-to-fit 宽度原子化布局。每一行会收缩到
20
20
  与之重叠的浮动(`float: left|right`)所留出的空白带,因此后续行会绕开它们。
21
- 每个文本片段会记住它覆盖的 run,以便绘制时对它整形。文本使用 HarfBuzz/FreeType
21
+ 每个文本片段会记住它覆盖的 run,以便绘制时对它整形。文本使用 stb_truetype
22
22
  技术栈测量(见[文本技术栈](./rendering.md))。
23
23
  - **Flexbox** —— `row`/`column`(以及 `-reverse` 变体),支持 `gap`、
24
24
  `flex-basis`/`flex-grow`/`flex-shrink`、`justify-content` 与 `align-items`
@@ -62,7 +62,8 @@ opacity、`display`——与任何其他元素一样。盒子内部的艺术内
62
62
 
63
63
  ## 视频与音频(M13)
64
64
 
65
- 媒体播放横跨两个扩展,因此只做界面的 GUI 程序不必为用不到的解码器付出体积:
65
+ 媒体播放拆在 `gui` 扩展内部,因此只做界面的 GUI 程序不必为用不到的解码器付出
66
+ 体积。解码是可选的 **`media` 链接特性**,不是独立扩展:
66
67
 
67
68
  - **`gui`**(`gui.a`/`gui.lib`)负责 `<video>`/`<audio>` 元素、DOM API、渲染器
68
69
  集成与 SDL 音频 sink,但**不含 FFmpeg**。`runtime/ext_gui/media.{h,cpp}`
@@ -71,12 +72,16 @@ opacity、`display`——与任何其他元素一样。盒子内部的艺术内
71
72
  - **`media`**(`media.a`,Windows 上 MSVC ABI 则为 `media.lib`;
72
73
  `runtime/ext_media/`)是可选的解码后端:`runtime/ext_media/media.cpp`
73
74
  封装静态 vendored 的 **FFmpeg** 构建,`runtime/ext_media/media_bridge.c`
74
- 把它注册进引擎。用 `--ext media`(或 `--ext gui,media`)启用。`media`
75
- 依赖 `gui`;注册表会拒绝错误的组合。
75
+ 把它注册进引擎。编译器在程序可能用到媒体时(导入 HTML 里的
76
+ `<video>`/`<audio>`、`createElement("video"/"audio")`、`new Audio()`、
77
+ `decodeAudioData`,或运行时拼出的 HTML)自动选中该特性,只在那时才编译
78
+ bridge 并链接 `media.a`。`--feature media` 强制开启,`--no-feature media`
79
+ 保持关闭;见[链接特性](./build.md#链接特性)。
76
80
 
77
- 没有 `media` 扩展时,元素仍然存在、DOM API 仍可调用,但打开源会失败并触发
78
- `error`。任何没有构建该归档的宿主(先运行 `npm run media`)都是如此;
79
- Windows 已不再特殊:`media.lib` 是用 clang-cl 为 MSVC ABI 编译的 FFmpeg,
81
+ 没有 `media` 特性时,元素仍然存在、DOM API 仍可调用,但打开源会失败并触发
82
+ `error`。任何没有构建该归档的宿主(先运行 `npm run media`)都是如此——自动
83
+ 检测到但归档缺失时构建给出 warning 并按此降级,显式 `--feature media` 则直接
84
+ 报错。Windows 已不再特殊:`media.lib` 是用 clang-cl 为 MSVC ABI 编译的 FFmpeg,
80
85
  因此可以和 `gui.lib` 一起链接并正常播放。两条路径都没有 WebView,也没有平台
81
86
  媒体框架。
82
87