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/build.md CHANGED
@@ -8,13 +8,33 @@
8
8
  `runtime/ext_media/` and is built separately.
9
9
  - `npm run gui` (`scripts/build-gui.ts`) fetches a pinned SDL3 (`SDL3_TAG`,
10
10
  default `release-3.2.10`), builds a static SDL3, fetches and builds static
11
- FreeType (`FREETYPE_VERSION`) and HarfBuzz (`HARFBUZZ_VERSION`), and fetches
12
- and builds static **glslang** (`GLSLANG_VERSION`, default `16.6.0`) against
13
- the pinned SPIR-V headers (`SPIRV_HEADERS_VERSION`), and **SPIRV-Cross**
14
- (`SPIRV_CROSS_VERSION`, default `vulkan-sdk-1.4.313.0`). glslang is the
15
- runtime GLSL ES compiler behind WebGL and SPIRV-Cross turns its SPIR-V into
16
- MSL for the Metal backend (see [WebGL](./webgl.md)); both are merged into the
17
- archive like the other libraries. The script then compiles the engine and
11
+ **FreeType** (`FREETYPE_VERSION`, default `2.13.3`) and **HarfBuzz**
12
+ (`HARFBUZZ_VERSION`, default `10.1.0`) for the optional `shaping` feature,
13
+ and fetches and builds static **glslang** (`GLSLANG_VERSION`, default
14
+ `16.6.0`) against the pinned SPIR-V headers (`SPIRV_HEADERS_VERSION`), and —
15
+ **on macOS only** — **SPIRV-Cross** (`SPIRV_CROSS_VERSION`, default
16
+ `vulkan-sdk-1.4.313.0`). glslang is the runtime GLSL ES compiler behind WebGL
17
+ and SPIRV-Cross turns its SPIR-V into MSL for the Metal backend (see
18
+ [WebGL](./webgl.md)); both are merged into the archive like the other
19
+ libraries. Metal is the only SDL_GPU backend that consumes MSL, so on
20
+ Windows/Linux the script neither fetches nor merges SPIRV-Cross:
21
+ `webgl_msl.cpp` compiles to nothing there and the ~2.5 MiB it contributes
22
+ would be unreachable. The default text path uses the bundled
23
+ **stb_truetype** (`runtime/ext_gui/stb_truetype.h`); HarfBuzz + FreeType are
24
+ only extracted when the `shaping` feature is active.
25
+ Every library (SDL3, FreeType, HarfBuzz, glslang, SPIRV-Cross and the engine)
26
+ is compiled with `-ffunction-sections -fdata-sections`, and SDL is configured
27
+ without the subsystems the engine never calls (`SDL_JOYSTICK`, `SDL_HAPTIC`,
28
+ `SDL_SENSOR`, `SDL_CAMERA`, `SDL_RENDER`, `SDL_OPENGL`/`SDL_OPENGLES`,
29
+ `SDL_OFFSCREEN`, `SDL_HIDAPI`). HarfBuzz omits the APIs the shaping backend
30
+ never uses (`HB_NO_DRAW`/`PAINT`/`COLOR`/`NAME`/`STYLE`/`METRICS`/…, while
31
+ keeping `CFF`/`OPEN`/`VAR` for OTF and variable fonts) and is built with
32
+ `HB_NO_PRAGMA_GCC_DIAGNOSTIC_ERROR`, because `src/hb.hh` raises its warning
33
+ set to errors and two lean defines leave macOS/CoreText helpers unreferenced
34
+ (`HB_NO_MMAP` and `HB_NO_OT_FONT_GLYPH_NAMES`). The linker then drops
35
+ the unreachable functions (`--gc-sections`, `-dead_strip`, or link.exe's
36
+ default `/OPT:REF`). The
37
+ script then compiles the engine and
18
38
  merges everything into `runtime/lib/<os>-<arch>/gui.a` (or
19
39
  `gui.lib` with the MSVC ABI on Windows), same convention as `core.a`,
20
40
  statically bundling all three. The merge uses `libtool` on macOS and
@@ -25,13 +45,17 @@
25
45
  minimal static build with only the demuxers/parsers/decoders the media
26
46
  elements need, compiles `runtime/ext_media/media.cpp` against it and merges
27
47
  everything into `runtime/lib/<os>-<arch>/media.a` (`media.lib` with the MSVC
28
- ABI on Windows). `gui.a` does **not** link FFmpeg: a program enables playback
29
- explicitly with `--ext media`.
48
+ ABI on Windows). `gui.a` does **not** link FFmpeg: the compiler selects the
49
+ `media` link feature for a program that may play media and only then links
50
+ `media.a` (see [Link features](#link-features)).
30
51
  - `src/extensions/gui/index.ts` exposes the engine archive through
31
52
  `nativeObjects()` (via `findRuntimeLibrary`, so `.a`/`.lib` both work) and the
32
- OS frameworks through `linkerFlags()`. `src/extensions/media/index.ts` does
33
- the same for `media.a`/`media.lib` and compiles the registration bridge
34
- (`runtime/ext_media/media_bridge.c`) as a normal runtime source.
53
+ OS frameworks through `linkerFlags()`. Its `linkFeatures()` (in
54
+ `src/extensions/gui/features.ts`) declares `media`, `gpu`, `images`, `shaping` and `webgl`; the driver
55
+ compiles their bridges only when a feature is active.
56
+ - glslang (and SPIRV-Cross on macOS) is merged into `gui.a` like SDL3, but the
57
+ engine reaches it only through the `gpu` feature's dispatch table. A program
58
+ that never compiles GLSL does not extract those archive members.
35
59
  - `scripts/build-gui.ts` compiles with the *same* clang the driver resolves
36
60
  (`src/driver/toolchain-provider.ts`); on Windows that is the MSVC-targeting
37
61
  LLVM bundled with Visual Studio, and its sibling `clang++`/`llvm-ar` are used
@@ -59,11 +83,12 @@
59
83
  - CI builds `gui.a`/`gui.lib` and `media.a`/`media.lib` before assembling the
60
84
  release archive so they ship inside `runtime/lib/<slug>/` (the release tarball
61
85
  copies the whole `runtime/` tree). The same folders are packed into the
62
- optional `@xbintsc/<slug>` npm package, so `--ext gui`/`--ext media` work from
86
+ optional `@xbintsc/<slug>` npm package, so `--ext gui` (with automatic
87
+ `media`/`gpu`/`images`/`shaping`/`webgl` selection) works from
63
88
  an npm install on the hosts the release builds for. The archives are built and
64
89
  the examples are run on Linux (under Xvfb, with the lavapipe software Vulkan
65
90
  driver) and macOS;
66
- the Windows x64 job builds both archives and links a `gui,media` program, so
91
+ the Windows x64 job builds both archives and links a `gui` program, so
67
92
  the MSVC build is validated even though *running* the examples there stays
68
93
  provisional (the hosted runner has no GPU/Vulkan driver). The macOS run is
69
94
  provisional too: the Intel runner's virtualised GPU lacks
@@ -82,8 +107,9 @@
82
107
  - The existing cache fingerprint already hashes `nativeObjects()` contents and
83
108
  `runtimeSources()` content, so rebuilding either archive invalidates cached
84
109
  binaries automatically.
85
- - CMake build trees live *next to* their sources (`vendor/sdl-build`,
86
- `vendor/freetype-build`, `vendor/harfbuzz-build`), never inside them: a
110
+ - CMake build trees live *next to* their sources (`vendor/sdl-build-lean`,
111
+ `vendor/freetype-build-lean`, `vendor/harfbuzz-build-lean`,
112
+ `vendor/glslang-build-lean`, `vendor/spirv-cross-build-lean`), never inside them: a
87
113
  `CMakeCache.txt` records the machine it was configured on, and SDL's video
88
114
  backends are `option()`s, so the first configure to run wins. A cached
89
115
  `vendor/SDL` tree that also carried `build/` therefore restored one machine's
@@ -95,6 +121,96 @@
95
121
  `xbintsc_SKIP_HEADER_CHECK=1` when they live under a prefix that check does
96
122
  not know).
97
123
 
124
+ ## Link features
125
+
126
+ `gui` is the only user-facing extension, but two heavy halves are **optional link
127
+ features** selected per program:
128
+
129
+ | Feature | What it links | Selected when the program may use it |
130
+ | --- | --- | --- |
131
+ | `media` | `media_bridge.c` + `media.a` (FFmpeg) | an HTML `<video>`/`<audio>` tag, `createElement("video"/"audio")`, `new Audio()`, `decodeAudioData`, or runtime-built markup |
132
+ | `gpu` | `gpu_bridge.c` (the implementation is already in `gui.a`) | a `getContext("webgl"/"webgl2"/"experimental-webgl")` (or a kind decided at run time), any `webgl` string, `mix-blend-mode`/`mixBlendMode`, or runtime-built markup |
133
+ | `images` | `image_bridge.c` (the implementation is already in `gui.a`) | `<img>`, inline `<svg>`, `background-image`/`url(...)`, a `.svg` path, `drawImage`/`createImageBitmap`/`Image`, or runtime-built markup |
134
+ | `shaping` | `text_bridge.c` (the implementation is already in `gui.a`) | non-ASCII text (CJK/Cyrillic/Greek/…), an escaped/entity codepoint, or runtime-built markup |
135
+ | `webgl` | `webgl_bridge.c` (the implementation is already in `gui.a`; requires `gpu`) | a WebGL `getContext(...)`, a `webgl` string, or runtime-built markup |
136
+
137
+ The driver scans the bundled AST (`src/driver/usage.ts`): every string literal
138
+ (including imported `.html` and the CSS inlined from it), identifiers and
139
+ property names, each `getContext(...)` kind, and whether HTML/CSS can be built
140
+ from data the compiler cannot see. Simple `const` aliases are resolved;
141
+ anything unresolved makes the program “dynamic markup” and enables the optional
142
+ features conservatively.
143
+
144
+ When a feature is active, the driver compiles its C **bridge only for that
145
+ build** and links it before `gui.a`:
146
+
147
+ - `media` → `runtime/ext_media/media_bridge.c`; its constructor installs the
148
+ decoder table and references `xt_media_backend_*`, which is what extracts the
149
+ FFmpeg members from `media.a`.
150
+ - `gpu` → `runtime/ext_gui/gpu_bridge.c`; its constructor calls
151
+ `xt_gpu_feature_register` in `gpu_feature.cpp`, which installs the
152
+ `XtGpuShaderApi` table and references `compile_glsl` / `cross_compile_msl` /
153
+ the reflection helpers, extracting glslang (and SPIRV-Cross on macOS).
154
+ - `images` → `runtime/ext_gui/image_bridge.c`; its constructor installs
155
+ `XtImageApi` via `image_feature.cpp`, which references stb_image and NanoSVG.
156
+ Layout, paint, the canvas `drawImage` path and the app-icon path call through
157
+ that table, so a text-only page links neither decoder.
158
+ - `shaping` → `runtime/ext_gui/text_bridge.c`; its constructor installs
159
+ `TextApi` via `text_hb.cpp`, which references HarfBuzz and FreeType. Without
160
+ it the engine uses the built-in stb_truetype backend, so a Latin-only page
161
+ links neither library.
162
+ - `webgl` → `runtime/ext_gui/webgl_bridge.c`; its constructor installs
163
+ `XtWebglApi` via `webgl_feature.cpp`, which references the WebGL context and
164
+ DOM bindings. The core reaches `getContext`, the canvas width/height setters,
165
+ `createImageBitmap`, the host types and the renderer through that table, so a
166
+ page with no WebGL links none of `webgl_*`/`dom_api_webgl*`.
167
+
168
+ A bridge is the “strong reference” that makes the linker search the archive; with
169
+ no bridge, the engine's table stays null and the implementation members are never
170
+ extracted. That is why the large libraries can stay in the existing archives
171
+ without a new package layout.
172
+
173
+ Overrides: `--feature media,gpu,images,shaping,webgl` (or `all`) forces a
174
+ feature on; `--no-feature media` keeps it out even when detection finds a use.
175
+ `webgl` requires `gpu`, and the resolver activates both together. An app icon
176
+ forces `images` because the icon is decoded through the same pipeline. `--ext media`
177
+ is a deprecated alias for `--ext gui --feature media`. If an auto-detected
178
+ feature's archive is missing, the build warns and continues (the element/context
179
+ degrades as documented); a forced feature makes it an error.
180
+
181
+ > Detection is static by design. A page that builds HTML/CSS from `fetch`,
182
+ > `readFileSync` or other runtime data should pass `--feature all` (or the
183
+ > specific feature). `--verbose` prints the selected set, e.g.
184
+ > `xbintsc: link features: none`.
185
+
186
+ ### Binary size
187
+
188
+ The lean build is what makes a DOM-only program small. On Windows x64 a
189
+ `hello`-sized program drops from roughly 6.4 MiB to about 3.1 MiB once SDL is
190
+ configured as above, function/data sections let `/OPT:REF` discard what the page
191
+ never reaches, the WebGL/images link features stay off, and text uses the
192
+ bundled stb_truetype instead of HarfBuzz + FreeType. The remaining weight is
193
+ mostly the static CRT, the lean SDL (~1 MiB), stb_truetype (~0.1 MiB), and the
194
+ DOM/CSS/layout/paint engine itself (~1.2 MiB). Linux and macOS use their own
195
+ linker sections (`--gc-sections`/`-dead_strip`) and differ somewhat, but the
196
+ relative ranking is the same.
197
+
198
+ WebGL and images/SVG are link features (roughly 0.4 MiB of WebGL context/DOM
199
+ code and 0.23 MiB of stb_image/NanoSVG that a DOM-only page does not link).
200
+ Canvas2D, Web Audio and the `mix-blend-mode` compositor are not gated yet; a
201
+ program that uses none of them still pays for their API surface (roughly
202
+ 0.1–0.2 MiB each).
203
+
204
+ The text trade-off is deliberate. The default backend is stb_truetype: only
205
+ TrueType outlines and a per-codepoint mapping plus the font's `kern` table, so
206
+ CFF/OTF and variable fonts, OpenType ligatures and complex-script shaping
207
+ (Arabic/Indic/Thai) are not available. The fallback chain still selects another
208
+ face for codepoints the primary lacks. Selecting the optional **`shaping`**
209
+ feature (automatically for non-ASCII text, or with `--feature shaping`) links
210
+ HarfBuzz + FreeType back in (~1.2 MiB) and restores full OpenType shaping and
211
+ CFF/OTF/variable-font support. A Latin-only DOM page therefore stays near
212
+ 3.1 MiB; a Chinese/complex-script page is closer to 4.3 MiB.
213
+
98
214
  ## Running the example
99
215
 
100
216
  ```sh
@@ -133,10 +249,11 @@ its README for controls and the geometry report it prints.
133
249
 
134
250
  `media.ts` (with `media.html`) demonstrates `<video>`/`<audio>`: it overlays a
135
251
  CSS progress bar on the video and drives playback with `play()` / `pause()` /
136
- `currentTime` / `volume`. It needs the separate `media` extension, so build and
137
- enable both. Media `src` paths resolve relative to the process working
252
+ `currentTime` / `volume`. The compiler sees the elements in `media.html` and
253
+ links the `media` feature automatically; build `media.a` once with
254
+ `npm run media`. Media `src` paths resolve relative to the process working
138
255
  directory, so run it from the repository root:
139
- `xbintsc run examples/gui/media.ts --ext gui,media`.
256
+ `xbintsc run examples/gui/media.ts --ext gui`.
140
257
 
141
258
  Set `XT_GUI_AUTOCLOSE_MS=<n>` to close all windows after `n` milliseconds,
142
259
  which the e2e test (`tests/e2e/gui-*.test.ts`) uses to run headlessly.
package/doc/gui/canvas.md CHANGED
@@ -191,7 +191,7 @@ non-finite entries are ignored, an all-zero list means a solid line).
191
191
 
192
192
  Text: `measureText(text)` (returns `{ width }`), `fillText(text, x, y,
193
193
  maxWidth?)` and `strokeText(text, x, y, maxWidth?)`. Text is shaped with
194
- HarfBuzz and rasterised with FreeType (the same stack as HTML text) at the
194
+ stb_truetype (shaped and rasterised) (the same stack as HTML text) at the
195
195
  transform's approximate scale. A positive `maxWidth` condenses advances and
196
196
  glyph bitmaps horizontally so the run is at most that wide (as the canvas API
197
197
  specifies) instead of being ignored.
@@ -22,6 +22,16 @@
22
22
  shader unroller; `WebGLRenderer` runs on the real backend and there is no
23
23
  Canvas2D fallback. Shadow maps (comparative `sampler2DShadow` targeting depth
24
24
  textures), cube textures/environment maps and HDR render targets are supported.
25
+ - **Comparative samplers can miscompile on Intel Macs.** On an Intel GPU (seen
26
+ on Intel Iris Plus Graphics with macOS 26), the Metal shader compiler drops a
27
+ fragment function that calls `sample_compare`: the draw produces no pixels at
28
+ all, so `texture(sampler2DShadow, …)` reads black and shadow maps disappear.
29
+ The MSL is correct (`depth2d<float>` + `sample_compare`), the sampler carries
30
+ the comparison op, and the same program renders correctly once Metal GPU
31
+ shader validation is enabled — running with `MTL_SHADER_VALIDATION=1` makes
32
+ `tests/e2e/gui-webgl2-shadow-sampler.test.ts` pass on such a machine. That
33
+ points at the driver's shader compiler rather than the engine, so the
34
+ workaround is the environment variable; other Apple GPUs may not show it.
25
35
 
26
36
 
27
37
 
@@ -111,6 +121,22 @@
111
121
  bottleneck. Inline `style="…"` attributes are re-parsed on each restyle of
112
122
  the element that carries them.
113
123
 
124
+ - **Text shaping is a simple shaper.** stb_truetype handles Latin/CJK/Cyrillic/
125
+ Greek with basic `kern` and per-codepoint fallback, but it does not run
126
+ OpenType ligatures or complex-script shaping (Arabic/Indic/Thai), and it only
127
+ accepts TrueType outlines (no CFF/OTF or variable fonts). Colour emoji are not
128
+ rendered as colour. This is the price of the ~3 MiB DOM-only binary; the
129
+ optional `shaping` feature restores HarfBuzz + FreeType (and full
130
+ complex-script shaping).
131
+ - **Link-feature detection is static.** `media`/`gpu`/`images`/`shaping`/`webgl` are selected from the
132
+ bundled HTML/CSS/TS (including imported `.html` and the CSS inlined from it).
133
+ A page that builds markup or CSS from runtime data (`fetch`, `readFileSync`,
134
+ a value returned by a function) cannot be seen; the scanner falls back to
135
+ enabling both when it can prove the sink is dynamic, but an opaque value that
136
+ it cannot classify needs an explicit `--feature media`/`gpu`/`images`/`shaping`/`webgl` (or `all`).
137
+ `--verbose` prints the selected set. See
138
+ [Link features](./build.md#link-features).
139
+
114
140
  ## Open questions
115
141
 
116
142
  - Whether Linux ships X11, Wayland, or both in the first cut. (Decision:
@@ -130,7 +156,8 @@
130
156
  - Windows: an MSVC-compatible `gui.lib` (COFF objects + `ar -M`/`llvm-ar`) is
131
157
  produced by `scripts/build-gui.ts`. The Windows x64 CI job builds it with the
132
158
  Visual Studio toolchain (`.github/actions/vs-toolchain` imports `vcvarsall`
133
- and uses VS's bundled LLVM), links a `gui,media` program, and ships it in the
159
+ and uses VS's bundled LLVM), links a `gui` program that exercises the `media`
160
+ link feature, and ships it in the
134
161
  x64 release. *Running* the examples on the hosted runner stays provisional
135
162
  because it has no GPU/Vulkan driver (SDL_GPU selects the **Vulkan** backend
136
163
  there, so no DXIL/`dxc` toolchain is needed). Windows arm64 still skips both
@@ -139,12 +166,14 @@
139
166
  - **Windows: x64 builds in CI, arm64 and runtime validation stay provisional.**
140
167
  `media.lib` is produced by `scripts/build-media.ts` with FFmpeg's MSVC
141
168
  toolchain (`--toolchain=msvc --cc=clang-cl`) so its COFF objects and static CRT
142
- match `gui.lib`, and `--ext media` links and plays there. Building it needs a
169
+ match `gui.lib`, and the auto-selected `media` feature links and plays there.
170
+ Building it needs a
143
171
  POSIX shell to run FFmpeg's `configure` and GNU make; the shell comes from Git
144
172
  for Windows (already needed to clone SDL3), and make is a native GNU make
145
173
  (Chocolatey's `make`, or `xbintsc_MAKE`). CI installs make explicitly,
146
174
  builds `gui.lib` + `media.lib` on Windows x64 (using the same Visual Studio
147
- `vcvarsall` + bundled LLVM as a local build), links a `gui,media` program, and
175
+ `vcvarsall` + bundled LLVM as a local build), links a `gui` program that
176
+ exercises the feature, and
148
177
  ships both archives in the Windows x64 release; the example *run* remains
149
178
  provisional because hosted runners have no GPU/Vulkan driver. Windows arm64
150
179
  still skips both archives, and `<video controls>` is likewise deferred — the
package/doc/gui/layout.md CHANGED
@@ -22,7 +22,7 @@ with absolute (viewport-relative) geometry:
22
22
  Each line shrinks to the band left free by any floats (`float: left|right`)
23
23
  that overlap it, so later lines wrap around them.
24
24
  Each text fragment remembers the run it covers, so paint can shape it. Text is
25
- measured with the HarfBuzz/FreeType stack (see [Text stack](./rendering.md#text-stack-m4b)).
25
+ measured with the stb_truetype stack (see [Text stack](./rendering.md#text-stack-m4b)).
26
26
  - **Flexbox** — `row`/`column` (and the `-reverse` variants) with `gap`,
27
27
  `flex-basis`/`flex-grow`/`flex-shrink`, `justify-content` and `align-items`
28
28
  (including `stretch` when the cross size is definite). `flex-wrap: wrap`
package/doc/gui/media.md CHANGED
@@ -74,8 +74,9 @@ clip paths are not rendered (see [Known issues](./known-issues.md)).
74
74
 
75
75
  ## Video & audio (M13)
76
76
 
77
- Media playback is split across two extensions so a GUI program does not pay for
78
- a decoder it never uses:
77
+ Media playback is split inside the `gui` extension so a GUI program does not pay
78
+ for a decoder it never uses. Decoding is an optional **`media` link feature**,
79
+ not a separate extension:
79
80
 
80
81
  - **`gui`** (`gui.a`/`gui.lib`) owns the `<video>`/`<audio>` elements, the DOM
81
82
  API, the renderer integration and the SDL audio sink, but contains **no
@@ -85,13 +86,18 @@ a decoder it never uses:
85
86
  - **`media`** (`media.a`, or `media.lib` with the MSVC ABI on Windows;
86
87
  `runtime/ext_media/`) is the optional decoder backend:
87
88
  `runtime/ext_media/media.cpp` wraps a statically vendored **FFmpeg** build,
88
- and `runtime/ext_media/media_bridge.c` registers it with the engine. Enable it
89
- with `--ext media` (or `--ext gui,media`). `media` requires `gui`; the
90
- registry rejects the combination in the wrong order.
89
+ and `runtime/ext_media/media_bridge.c` registers it with the engine. The
90
+ compiler selects the feature when the program may use it (a `<video>`/`<audio>`
91
+ element in imported HTML, `createElement("video"/"audio")`, `new Audio()`,
92
+ `decodeAudioData`, or runtime-built markup) and only then compiles the bridge
93
+ and links `media.a`. `--feature media` forces it and `--no-feature media`
94
+ keeps it out; see [Link features](./build.md#link-features).
91
95
 
92
- Without the `media` extension the element still exists and its DOM API is
96
+ Without the feature the element still exists and its DOM API is
93
97
  callable, but opening a source fails and fires `error`. The same happens on any
94
- host where the archive has not been built (run `npm run media`); Windows is no
98
+ host where the archive has not been built (run `npm run media`) — an
99
+ auto-detected feature with no archive produces a build warning and degrades this
100
+ way, while `--feature media` fails the build. Windows is no
95
101
  longer special: `media.lib` is FFmpeg compiled with clang-cl for the MSVC ABI,
96
102
  so it links against `gui.lib` and plays normally. There is no WebView and no
97
103
  platform media framework in the path either way.
@@ -153,33 +153,33 @@ compiler here — keep painting the group with ordinary source-over blending.
153
153
 
154
154
  ## Text stack (M4b)
155
155
 
156
- `runtime/ext_gui/text.{h,cpp}` wraps **HarfBuzz** (shaping) and **FreeType**
157
- (metrics + eventual rasterisation). Both are built statically and linked into
158
- `gui.a` by `scripts/build-gui.ts`.
156
+ `runtime/ext_gui/text.{h,cpp}` is the front-end; it dispatches through a small
157
+ backend table. The default backend is the bundled **stb_truetype**
158
+ (`text_stb.cpp`, `runtime/ext_gui/stb_truetype.h`). The optional **`shaping`**
159
+ link feature installs `text_hb.cpp`, a HarfBuzz + FreeType backend, and is
160
+ selected automatically for non-ASCII text.
159
161
 
160
162
  - Fonts are resolved from well-known system paths (Helvetica/Arial on macOS,
161
163
  DejaVu/Liberation on Linux, Segoe UI/Arial on Windows), overridable with
162
164
  `XT_GUI_FONT` (and `XT_GUI_FONT_MONO`), and cached per `(family class, size)`.
163
165
  Only regular upright faces are used for now; weight/italic selection is a
164
166
  later refinement.
165
- - The default candidates prefer a **CJK-capable** face (Microsoft YaHei on
166
- Windows, PingFang/STHeiti on macOS, Noto Sans CJK/WenQuanYi on Linux) before
167
- the Latin-only ones. Setting `XT_GUI_FONT` to a Latin-only face gives its
168
- Latin design instead (at the cost of CJK coverage).
167
+ - The default candidates prefer a **CJK-capable TrueType/TTC** face (Microsoft
168
+ YaHei on Windows, PingFang/STHeiti on macOS, Noto Sans CJK/WenQuanYi on Linux)
169
+ before the Latin-only ones. A candidate that stb_truetype cannot parse (for
170
+ example a CFF/OTF collection) is skipped in favour of the next one.
169
171
  - **Codepoints the primary face lacks fall back to another face.** The engine
170
172
  keeps a small fallback chain — the platform's symbol face (Segoe UI Symbol,
171
173
  Apple Symbols, Noto Sans Symbols, DejaVu Sans) first, then the other family
172
174
  class — and resolves each codepoint to the first face in it that has the
173
175
  glyph. The run is shaped in segments, one per face, so a mixed run (Latin
174
176
  plus an arrow, box-drawing or geometric shape, or Latin inside CJK) draws
175
- every glyph instead of dropping the missing ones as `.notdef`. This is what
176
- makes a character such as `▮`/`▯` (U+25AE/U+25AF, used by the pelican-bike
177
- speed bar) visible even when the CJK primary has no geometric shapes. Each
178
- shaped glyph carries its face (`ShapedGlyph::font`) so the renderer rasterises
179
- it from the right atlas entry; `xt_text_scale` maps a face to the display's
180
- scale factor.
181
- - `xt_text_measure_width` shapes the run with HarfBuzz (so kerning and
182
- ligatures are honoured), `xt_text_metrics` returns FreeType's ascent /
177
+ every glyph instead of dropping the missing ones as `.notdef`. Each shaped
178
+ glyph carries its face (`ShapedGlyph::font`) so the renderer rasterises it
179
+ from the right atlas entry; `xt_text_scale` maps a face to the display's scale
180
+ factor.
181
+ - `xt_text_measure_width` maps each codepoint to a glyph and adds its advance
182
+ plus the font's `kern` table; `xt_text_metrics` returns stb_truetype's ascent /
183
183
  descent / normal line height. When no font file can be found the module falls
184
184
  back to a deterministic per-byte approximation, so layout still works.
185
185
  - `xt_text_shape_run` returns positioned glyphs and `xt_text_rasterize` renders
@@ -189,6 +189,15 @@ compiler here — keep painting the group with ordinary source-over blending.
189
189
  - Layout uses these real metrics for text widths, line breaking and
190
190
  `line-height: normal`; `measureText`/`fontMetrics` expose them to tests.
191
191
 
192
+ Shape trade-off: the default is a simple shaper, not a full OpenType engine. It
193
+ handles Latin/CJK/Cyrillic/Greek, basic kerning and per-codepoint fallback, but
194
+ it does not run ligatures or complex-script shaping (Arabic/Indic/Thai), and
195
+ stb_truetype only accepts TrueType outlines, not CFF/OTF or variable fonts. The
196
+ choice keeps a DOM-only binary near 3 MiB. The optional **`shaping`** link
197
+ feature installs the HarfBuzz + FreeType backend instead (about 1.2 MiB) and
198
+ restores full OpenType shaping and CFF/OTF/variable-font support; it is selected
199
+ automatically for non-ASCII text and can be forced with `--feature shaping`.
200
+
192
201
  ## Performance (M20)
193
202
 
194
203
  The engine is a retained-mode pipeline: a DOM mutation restyles the document,
@@ -211,6 +220,14 @@ is what `tests/e2e/gui-perf.test.ts` asserts on.
211
220
  and becomes current, so the hot path has no LRU bookkeeping. A word is shaped
212
221
  once and reused by every later layout pass and repaint that draws it. Primary
213
222
  face lookups are cached by (family class, size) as well.
223
+ - **No-op writes.** `textContent` and `style.<property>` do nothing when the
224
+ node already shows that value: the document is not dirtied, so no restyle,
225
+ relayout, repaint or upload follows. A HUD that assigns the same clock string,
226
+ the same bar width or the same transform every frame therefore costs nothing —
227
+ before this each assignment rebuilt a text node (an allocation, and one the
228
+ collector then had to sweep) or re-serialized the inline style, and dirtied
229
+ the document for a full restyle. `classList` and `removeAttribute` already
230
+ behaved this way; `setAttribute` still dirties unconditionally.
214
231
  - **Frame loop.** The media/canvas tables are reconciled with the DOM only when
215
232
  the document changed (or on the first frame) instead of walking the tree every
216
233
  frame. A frame presents only when something happened — a mutation, a scroll,
@@ -55,6 +55,11 @@ that case with a single 16 ms `xt_set_timeout` tick:
55
55
  queued animation-frame callbacks before layout, so a page can stop itself),
56
56
  - it re-arms **only while callbacks remain**, so an idle page lets the event
57
57
  loop drain and the process exit — the browser-tab lifecycle;
58
+ - it schedules the next tick for *frame start + 16 ms*, not 16 ms after the
59
+ frame finished: the frame's own duration is not added to the interval, so a
60
+ 10 ms frame runs at a 16 ms period instead of 26 ms, and a page slower than
61
+ 16 ms runs as fast as it can instead of falling a frame's worth of time
62
+ further behind on every frame;
58
63
  - it stands aside while `xt_gui_run_active()` is set (the program called
59
64
  `run()`), so the same window is not rendered twice per tick;
60
65
  - `XT_GUI_AUTOCLOSE_MS=n` makes the pump quit after `n` milliseconds, which is
package/doc/gui/webgl.md CHANGED
@@ -48,8 +48,19 @@ reports the type as well.
48
48
  `readPixels` flips rows back, and the compositor sees an upright texture.
49
49
  - A fixed-function state change (`blendFunc`, `depthFunc`, `cullFace`,
50
50
  `colorMask`, …) changes which `SDL_GPUGraphicsPipeline` is needed, so
51
- pipelines are created lazily per (program, vertex layout, state) and cached on
52
- the program.
51
+ pipelines are created lazily and cached on the program. The key is the
52
+ *content* of the draw: the program, the primitive mode, a hash of the vertex
53
+ layout (attribute locations, formats, offsets, buffer pitches and input rates),
54
+ the reflected fixed-function state and the attachment formats. It deliberately
55
+ excludes the context's monotonic version counters (`attribs_version_`,
56
+ `framebuffer_version_`) and the GPU buffer handles: those advance on every VAO
57
+ bind, framebuffer switch or buffer re-creation without changing the pipeline,
58
+ and keying on them re-created every pipeline once per frame in a scene that
59
+ binds several layouts per frame — three.js does, and measured 248
60
+ `SDL_CreateGPUGraphicsPipeline` calls per frame while the per-program cache
61
+ grew without bound. `tests/e2e/gui-webgl-pipeline-cache.test.ts` pins the
62
+ steady state: two draws with the same layout and different buffers create no
63
+ pipeline after the first frame.
53
64
 
54
65
  ## Shaders: GLSL ES to SPIR-V (and on to MSL)
55
66
 
@@ -283,9 +294,15 @@ format — Direct3D 12, which would need DXIL — `getContext("webgl")` and
283
294
  `getContext("webgl2")` both answer `null`; the 2D canvas and the rest of the page
284
295
  still work.
285
296
 
286
- glslang and SPIRV-Cross are fetched and built by `npm run gui` and merged into
287
- `gui.a` / `gui.lib` (see [Packaging and build](./build.md)); a program that only
288
- uses the 2D canvas or the DOM pays no run-time cost beyond the archive size.
297
+ glslang is fetched and built by `npm run gui` and merged into `gui.a` /
298
+ `gui.lib`; SPIRV-Cross is merged **on macOS only**, because no other SDL_GPU
299
+ backend consumes MSL (see [Packaging and build](./build.md)). Both are reached
300
+ through the optional **`gpu` link feature**: the compiler selects it when the
301
+ program may call `getContext("webgl…")` or use `mix-blend-mode`, and only then
302
+ does the feature bridge pull the translator members out of `gui.a`. The WebGL
303
+ context and DOM bindings are the separate **`webgl` feature**, which requires
304
+ `gpu`; a program that only uses the 2D canvas or the DOM links neither glslang,
305
+ SPIRV-Cross, nor any `webgl_*` member (see [Link features](./build.md#link-features)).
289
306
 
290
307
  ## Testing
291
308
 
@@ -339,6 +356,7 @@ Canvas2D fallback is bundled any more**. Verified with three.js r160 through SDL
339
356
  Features outside this engine's subset still cannot be emulated: transform
340
357
  feedback and MSAA. Depth textures, float render targets and compressed textures
341
358
  are available, and comparative `sampler2DShadow` sampling is translated, so
342
- three.js shadow maps can use the hardware depth compare on both backends. A
359
+ three.js shadow maps can use the hardware depth compare on both backends (see
360
+ [known issues](./known-issues.md) for an Intel-Mac driver caveat). A
343
361
  scene that needs transform feedback or MSAA will not render correctly;
344
362
  everything the previous Canvas2D fallback could show now goes through the GPU.