xbintsc 0.3.69 → 0.3.81

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 (115) hide show
  1. package/README.md +57 -4
  2. package/README.zh-CN.md +51 -2
  3. package/dist/src/driver/compiler.js +6 -1
  4. package/dist/src/driver/compiler.js.map +1 -1
  5. package/dist/src/extensions/gui/index.js +10 -2
  6. package/dist/src/extensions/gui/index.js.map +1 -1
  7. package/dist/tests/e2e/gui-advanced.test.d.ts +6 -0
  8. package/dist/tests/e2e/gui-advanced.test.js +261 -0
  9. package/dist/tests/e2e/gui-advanced.test.js.map +1 -0
  10. package/dist/tests/e2e/gui-canvas.test.d.ts +9 -0
  11. package/dist/tests/e2e/gui-canvas.test.js +233 -0
  12. package/dist/tests/e2e/gui-canvas.test.js.map +1 -0
  13. package/dist/tests/e2e/gui-example-svg.test.d.ts +21 -0
  14. package/dist/tests/e2e/gui-example-svg.test.js +105 -0
  15. package/dist/tests/e2e/gui-example-svg.test.js.map +1 -0
  16. package/dist/tests/e2e/gui-example.test.d.ts +1 -1
  17. package/dist/tests/e2e/gui-example.test.js +2 -2
  18. package/dist/tests/e2e/gui-helpers.d.ts +3 -0
  19. package/dist/tests/e2e/gui-helpers.js +5 -0
  20. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  21. package/dist/tests/e2e/gui-layout.test.js +73 -0
  22. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  23. package/dist/tests/e2e/gui-media.test.d.ts +8 -0
  24. package/dist/tests/e2e/gui-media.test.js +119 -0
  25. package/dist/tests/e2e/gui-media.test.js.map +1 -0
  26. package/dist/tests/e2e/gui-paint.test.js +40 -0
  27. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  28. package/dist/tests/e2e/gui-transform.test.d.ts +7 -0
  29. package/dist/tests/e2e/gui-transform.test.js +198 -0
  30. package/dist/tests/e2e/gui-transform.test.js.map +1 -0
  31. package/doc/ai/cli.md +5 -0
  32. package/doc/ai/extensions.md +20 -3
  33. package/doc/ai/zh-CN/extensions.md +14 -3
  34. package/doc/gui/animation.md +97 -0
  35. package/doc/gui/api.md +92 -0
  36. package/doc/gui/build.md +91 -0
  37. package/doc/gui/canvas.md +98 -0
  38. package/doc/gui/dom.md +128 -0
  39. package/doc/gui/input.md +25 -0
  40. package/doc/gui/known-issues.md +78 -0
  41. package/doc/gui/layout.md +108 -0
  42. package/doc/gui/media.md +87 -0
  43. package/doc/gui/rendering.md +113 -0
  44. package/doc/gui/runtime.md +88 -0
  45. package/doc/gui-scripts.md +7 -3
  46. package/doc/gui.md +109 -612
  47. package/doc/implemented.md +1 -0
  48. package/doc/zh-CN/gui/animation.md +93 -0
  49. package/doc/zh-CN/gui/api.md +88 -0
  50. package/doc/zh-CN/gui/build.md +84 -0
  51. package/doc/zh-CN/gui/canvas.md +80 -0
  52. package/doc/zh-CN/gui/dom.md +119 -0
  53. package/doc/zh-CN/gui/input.md +23 -0
  54. package/doc/zh-CN/gui/known-issues.md +60 -0
  55. package/doc/zh-CN/gui/layout.md +91 -0
  56. package/doc/zh-CN/gui/media.md +71 -0
  57. package/doc/zh-CN/gui/rendering.md +92 -0
  58. package/doc/zh-CN/gui/runtime.md +76 -0
  59. package/doc/zh-CN/gui-scripts.md +6 -2
  60. package/doc/zh-CN/gui.md +89 -532
  61. package/doc/zh-CN/implemented.md +1 -0
  62. package/llms.txt +15 -2
  63. package/package.json +1 -1
  64. package/runtime/ext_gui/canvas.cpp +580 -0
  65. package/runtime/ext_gui/canvas.h +170 -0
  66. package/runtime/ext_gui/canvas_internal.h +59 -0
  67. package/runtime/ext_gui/canvas_raster.cpp +254 -0
  68. package/runtime/ext_gui/document.cpp +45 -4
  69. package/runtime/ext_gui/document.h +15 -1
  70. package/runtime/ext_gui/dom_api.cpp +3 -0
  71. package/runtime/ext_gui/dom_api_canvas.cpp +548 -0
  72. package/runtime/ext_gui/dom_api_internal.h +73 -0
  73. package/runtime/ext_gui/dom_api_media.cpp +274 -0
  74. package/runtime/ext_gui/dom_api_node.cpp +6 -4
  75. package/runtime/ext_gui/dom_api_proto.cpp +80 -0
  76. package/runtime/ext_gui/gui.cpp +6 -0
  77. package/runtime/ext_gui/gui_canvas.cpp +95 -0
  78. package/runtime/ext_gui/gui_engine.h +49 -0
  79. package/runtime/ext_gui/gui_events.cpp +21 -5
  80. package/runtime/ext_gui/gui_media.cpp +193 -0
  81. package/runtime/ext_gui/image.cpp +31 -12
  82. package/runtime/ext_gui/image.h +20 -7
  83. package/runtime/ext_gui/layout.cpp +130 -6
  84. package/runtime/ext_gui/layout.h +54 -1
  85. package/runtime/ext_gui/layout_flex.cpp +263 -99
  86. package/runtime/ext_gui/layout_flow.cpp +85 -9
  87. package/runtime/ext_gui/layout_flow_internal.h +84 -5
  88. package/runtime/ext_gui/layout_inline.cpp +54 -37
  89. package/runtime/ext_gui/media.cpp +514 -0
  90. package/runtime/ext_gui/media.h +104 -0
  91. package/runtime/ext_gui/nanosvg.h +3278 -0
  92. package/runtime/ext_gui/nanosvgrast.h +1472 -0
  93. package/runtime/ext_gui/paint.cpp +156 -38
  94. package/runtime/ext_gui/paint.h +42 -6
  95. package/runtime/ext_gui/renderer.cpp +117 -12
  96. package/runtime/ext_gui/renderer.h +49 -9
  97. package/runtime/ext_gui/renderer_image.cpp +205 -78
  98. package/runtime/ext_gui/renderer_internal.h +12 -9
  99. package/runtime/ext_gui/renderer_text.cpp +46 -39
  100. package/runtime/ext_gui/style.h +21 -0
  101. package/runtime/ext_gui/style_print.cpp +17 -0
  102. package/runtime/ext_gui/style_ua.cpp +7 -0
  103. package/runtime/ext_gui/style_values.cpp +83 -1
  104. package/runtime/ext_gui/svg.cpp +182 -0
  105. package/runtime/ext_gui/svg.h +46 -0
  106. package/runtime/ext_gui/text.cpp +156 -25
  107. package/runtime/ext_gui/text.h +11 -2
  108. package/runtime/ext_gui/transform.cpp +412 -0
  109. package/runtime/ext_gui/transform.h +112 -0
  110. package/runtime/ext_gui/window.cpp +10 -5
  111. package/scripts/build-gui.ts +148 -6
  112. package/scripts/check-file-length.ts +5 -1
  113. package/scripts/install-gui-deps.sh +28 -0
  114. package/src/driver/compiler.ts +6 -1
  115. package/src/extensions/gui/index.ts +10 -2
@@ -0,0 +1,91 @@
1
+ # 布局
2
+
3
+ > 语言 / Language:[English](../../gui/layout.md) | **简体中文**
4
+
5
+ > 属于 [GUI 扩展](../gui.md) 的一部分;引擎总览、锁定决策与路线图在那里。
6
+
7
+ ## 布局引擎(M3)
8
+
9
+ `runtime/ext_gui/layout.{h,cpp}` 把带样式的 DOM 转换为具有绝对(相对视口)
10
+ 几何信息的 `LayoutBox` 树:
11
+
12
+ - **块级流** —— 块级子元素垂直堆叠;`display: none`
13
+ 不生成盒;`width: auto` 填满包含块,`height: auto` 包裹内容。盒模型
14
+ (margin/padding/border)会被解析,包括相对包含块宽度的百分比。相邻兄弟的
15
+ 垂直外边距会折叠为 `max(正值) + min(负值)`;父元素的下外边距不会与其最后一个
16
+ 子元素折叠(见下方限制)。
17
+ - **行内流** —— 连续的行内级子元素构成一个匿名行内格式化上下文,采用贪心、
18
+ 基于单词的断行、`text-align` 与 `line-height`。行内元素获得其后代几何信息
19
+ 的并集;`display: inline-block` 以 shrink-to-fit 宽度原子化布局。每一行会收缩到
20
+ 与之重叠的浮动(`float: left|right`)所留出的空白带,因此后续行会绕开它们。
21
+ 每个文本片段会记住它覆盖的 run,以便绘制时对它整形。文本使用 HarfBuzz/FreeType
22
+ 技术栈测量(见[文本技术栈](./rendering.md))。
23
+ - **Flexbox** —— `row`/`column`(以及 `-reverse` 变体),支持 `gap`、
24
+ `flex-basis`/`flex-grow`/`flex-shrink`、`justify-content` 与 `align-items`
25
+ (当交叉轴尺寸确定时包括 `stretch`)。`flex-wrap: wrap` 会把项断成多行,
26
+ `align-content`(`stretch`/`flex-start`/`flex-end`/`center`/`space-between`/
27
+ `space-around`)在交叉轴上分配这些行,`wrap-reverse` 则翻转交叉轴。
28
+ flex 项的外边距参与其外框尺寸,因此
29
+ 负的 `margin-top` 会把该项上移,而不是被忽略。项之间的纯空白文本
30
+ (源码缩进)不会成为 flex 项。
31
+ - **定位** —— `position: relative` 会按 `top`/`right`/`bottom`/`left` 偏移该盒子
32
+ 及其子树,但不改变它在流中占用的空间;`position: absolute`(以及针对视口解析
33
+ 的 `fixed`)使其脱离文档流,并相对最近的已定位祖先的 padding box 定位(没有
34
+ 这样的祖先时则相对视口)。`auto` 的 inset 在该轴上保留静态位置;宽度 `auto`
35
+ 时使用 shrink-to-fit,除非 `left` 与 `right` 同时给出(此时填满两者之间的空间);
36
+ 百分比相对包含块解析。层叠通过绘制前按 `z-index` 对每个父元素的子元素排序
37
+ (负值 → `auto` → 正值)来近似实现;这只是一个层级,并非完整的层叠上下文,
38
+ 因此子元素无法脱离父元素的层级。
39
+ 注意该 shrink-to-fit 规则的后果:一个 `width: auto` 且没有显式尺寸的绝对定位
40
+ 盒子,其内部行内内容得到的**可用宽度为 0**(它的首选宽度只来自文本与
41
+ 非绝对定位的子元素),因此其中的行内 run 会各自换到独立的一行。请为此类盒子
42
+ 给出显式 `width`,或用 `left`/`width` 摆放其子元素(`examples/gui/pelican-bike`
43
+ 的精灵 run 就是这样做的),而不要依赖其内部的行流。
44
+
45
+ - **浮动与清除** —— `float: left|right` 使盒子脱离文档流,将其宽度设为
46
+ shrink-to-fit,放在当前行旁,并让后续行内行绕开它;`clear: left|right|both`
47
+ 会把后续块推到相关浮动的下方,而只包含浮动的块会增高以包裹它们。
48
+ - **`overflow` 裁剪** —— `overflow: hidden`(或 `scroll`/`auto`)的盒子会将其
49
+ 子树裁剪到自身的 padding box,并与任一祖先裁剪区域取交集;渲染器用 GPU
50
+ scissor 矩形实现该结果。目前还没有滚动,因此 `scroll`/`auto` 与 `hidden`
51
+ 一样只做裁剪。
52
+
53
+ 尚未实现:父/子与空块的外边距折叠、由非空白文本产生的匿名 flex 项、完整的
54
+ `z-index` 层叠上下文,以及 `float` 对块级子元素的影响(只有行内内容会绕排,
55
+ 块必须 `clear`)。
56
+
57
+ ## 变换(M15)
58
+
59
+ `transform` 与 `transform-origin` 是**绘制期**效果:上面的盒模型完全不变
60
+ (不重排、不重算几何),`xt_layout_run` 之后的一次额外遍历会给每个
61
+ `LayoutBox` 一个 `Matrix2D world`(整条祖先链的变换)及其 `inverse`:
62
+
63
+ - **只有元素盒参与。** 文本盒与其父元素共享同一个 `style` 指针,若两者都参与
64
+ 就会把元素的列表应用两次;该遍历只合成元素自身的列表与祖先的。
65
+ - **`transform-origin` 在这里解析**,而不是在层叠阶段:计算样式保留两个
66
+ (可能是 `%` 的)`Length`(默认 `50% 50%`),布局按*边框盒*解析它们,应用
67
+ `T(origin) · M · T(-origin)`。这就是
68
+ `computedStyle(el, "transform-origin")` 仍报告 `50% 92%` 的原因。关键字与
69
+ CSS 一样带方向:单独写 `top` 等于 `center top`,两个关键字可以任意顺序
70
+ (`bottom right` 等于 `right bottom`)。
71
+ - **长度按盒子解析**:`translate()` 里的 `%` 是边框盒宽/高的比例,`em`/`rem`
72
+ 按根字号(16px),`vw`/`vh` 按视口。
73
+ - **合成顺序**遵循 CSS:列表从右往左应用,所以
74
+ `rotate(90deg) translate(10px)` 把盒子往屏幕**下方**移动,而不是往右。
75
+
76
+ 下游消费者都跟着矩阵走:
77
+
78
+ | 消费者 | 行为 |
79
+ | --- | --- |
80
+ | 绘制 | 每个 op 携带矩阵(见[绘制](./rendering.md#绘制与显示列表m4am4b));被裁剪的子树按它的内边距盒经矩阵变换后的包围盒裁剪 |
81
+ | 命中测试 | `hitTestBox` 先把点经每个盒的逆矩阵映射,再做包含判断,所以旋转或缩放的元素在它**被画出来**的位置可点击 |
82
+ | `getBoundingClientRect` | **变换后**边框盒的轴对齐包围盒(`xt_layout_transformed_bounds`) |
83
+ | 布局几何 | 不变——`layoutTree()` 与 `offsetWidth`/`offsetHeight` 仍报告未变换的盒子 |
84
+
85
+ 不支持的变换函数(`skew`、`perspective` 等)会让整条声明失效,因此一个盒子
86
+ 要么被正确变换、要么完全不变换,而不是只应用了一部分。
87
+
88
+ **Document**(`runtime/ext_gui/document.{h,cpp}`):持有 DOM 树,把 `<style>`
89
+ 文本汇总为一份样式表,为某个视口计算样式与布局,并提供
90
+ `querySelector`/`querySelectorAll`/`styleOf`/`boxOf`。
91
+
@@ -0,0 +1,71 @@
1
+ # 媒体:图像、视频与音频
2
+
3
+ > 语言 / Language:[English](../../gui/media.md) | **简体中文**
4
+
5
+ > 属于 [GUI 扩展](../gui.md) 的一部分;引擎总览、锁定决策与路线图在那里。
6
+
7
+ ## 图像(M6a)
8
+
9
+ `runtime/ext_gui/image.{h,cpp}` 封装 vendored **stb_image** 头文件,把
10
+ PNG/JPEG/BMP/GIF/TGA 解码为 RGBA8,并带有解码缓存与仅头文件的尺寸缓存
11
+ (`stbi_info`)。路径接受 `file://` 前缀与百分号编码。
12
+ `runtime/ext_gui/svg.{h,cpp}` 在其上增加 SVG:它封装 vendored **NanoSVG**
13
+ 库(解析 + 抗锯齿扫描线光栅化),按扩展名或内容识别 SVG,并产出与既有管线
14
+ 相同的 RGBA8 `Image`。`stb_image` 无法解码 SVG,这正是
15
+ `<img src="logo.svg">` 能工作的原因。
16
+
17
+ - `<img>` 是一个替换元素(`display: inline-block`):设置了 CSS 尺寸时布局
18
+ 采用该尺寸,否则采用固有像素尺寸;当只约束一个轴时保持宽高比。构建盒树时
19
+ 从文件头(无需完整解码)读取固有尺寸;对 SVG 则来自解析后的 `width`/`height`
20
+ 或 `viewBox`(两者都缺失时回退到 300x150)。
21
+ - 绘制时为每个 `<img>` 生成一个 `PaintImage`;渲染器把每个唯一 `src` 上传到
22
+ RGBA 纹理并绘制带纹理的四边形,把共享同一纹理的连续四边形批处理。
23
+ **位图**按固有分辨率解码并按路径缓存;**矢量图**(SVG)按设备像素下的绘制
24
+ 尺寸(逻辑尺寸 × 窗口像素密度)光栅化并保持 viewBox 宽高比,再按该尺寸缓存,
25
+ 因此缩放或 HiDPI 得到的是清晰渲染而非放大的位图。
26
+ - 解析结果按路径缓存;解析与光栅化器状态在 `xt_svg_shutdown`
27
+ (`xt_image_shutdown`)中释放。
28
+
29
+ 图像仍需完成:CSS `background-image: url(...)`、`data:` URI、`object-fit`、
30
+ 九宫格边框与内联 `<svg>` 元素(仅支持 SVG *文件*,参见[待决问题](./known-issues.md))。
31
+
32
+ ## 视频与音频(M13)
33
+
34
+ `runtime/ext_gui/media.{h,cpp}` 封装一个静态 vendored 的 **FFmpeg** 构建,
35
+ `runtime/ext_gui/gui_media.cpp` 把 DOM 树接到它上面。`<video>` 与 `<audio>`
36
+ 真正解码并播放;路径中没有 WebView,也没有平台媒体框架。
37
+
38
+ - **解码。** `avformat_open_input` 打开 `src`(是路径——接受 `file://`,但与
39
+ 图像不同,媒体路径*不做*百分号解码),为每种媒体选取最佳流,软件解码器
40
+ 产出 `yuv/rgb` 帧,`sws_scale` 按编码尺寸转换为 RGBA8。支持的容器/编码即
41
+ `scripts/build-gui.ts` 的最小 FFmpeg 配置:MP4/MOV/MKV/AVI/FLV/MPEG-TS/M4V/
42
+ ASF,MP3/OGG/WAV/FLAC/AAC;视频 H.264、HEVC、VP8、VP9、AV1、MPEG-4、MJPEG;
43
+ 音频 AAC、MP3、Opus、Vorbis、FLAC、ALAC、AC3/E-AC3、WMAv2 及原始 PCM 格式。
44
+ - **视频。** `<video>` 与 `<img>` 一样是替换元素(UA 默认 `300x150`,解码
45
+ 尺寸通过 `setMediaSize` 覆盖)。绘制产出与图像相同的 `PaintImage`;渲染器把
46
+ 每个新解码帧上传到 RGBA 纹理(`ensureMediaTexture`)并绘制带纹理四边形,
47
+ 因此由 GPU 缩放到 CSS 盒,而非 CPU 缩放。播放计时基于墙钟
48
+ (`SDL_GetTicks`),不是帧间隔。
49
+ - **音频。** 没有音频回调:解码器随主线程 tick 被推送,PCM 以 push 模式
50
+ (`NULL` 回调)通过 `SDL_OpenAudioDeviceStream` 排队,在前方缓冲约 0.25 秒,
51
+ 格式为 48 kHz 立体声 `SDL_AUDIO_F32`。`volume` 映射到
52
+ `SDL_SetAudioStreamGain`;音频是尽力而为,无设备时(无头 CI)跳过。
53
+ - **线程。** 引擎是单线程且非线程感知,因此所有解码都在主线程的
54
+ `xt_gui_media_tick` 内完成——没有解码线程,也没有跨线程 GC 暴露。
55
+ - **DOM API。** 元素句柄直接暴露媒体接口(见
56
+ `runtime/ext_gui/dom_api_media.cpp` 与 `dom_api_proto.cpp` 中的原型):
57
+ `play()` / `pause()` / `load()`,以及 `paused` / `ended` / `duration` /
58
+ `currentTime` / `volume` / `muted` / `loop` / `autoplay` / `videoWidth` /
59
+ `videoHeight` / `readyState` / `src` 属性。媒体元素还会触发
60
+ `loadedmetadata`、`play`、`pause`、`ended`、`timeupdate`(每 250 毫秒)与
61
+ `error`,因此常见的 `addEventListener` 写法可用。
62
+ - **没有内置控件。** `<video controls>` 刻意*不*实现:元素始终渲染原始帧,
63
+ 程序用普通元素和 CSS 绘制自己的控件(进度条、按钮)。模式见
64
+ `examples/gui/media.html`(`examples/gui/media.ts`)。
65
+ - **生命周期。** 播放器按窗口持有(`XtGuiMediaEntry`,以节点为键)。
66
+ `xt_gui_media_sync` 清除节点已不存在的条目、在 `src` 变化时重开、并在首次
67
+ 使用时打开;`xt_gui_media_shutdown` 在窗口关闭/退出/`loadHTML` 时运行。
68
+
69
+ 媒体仍需完成:`<video controls>`、音视频时钟漂移校正、硬件解码、流式协议
70
+ (仅启用 `file`)、字幕、`playbackRate` 与逐帧 `requestVideoFrameCallback`。
71
+
@@ -0,0 +1,92 @@
1
+ # 绘制、GPU 与文本
2
+
3
+ > 语言 / Language:[English](../../gui/rendering.md) | **简体中文**
4
+
5
+ > 属于 [GUI 扩展](../gui.md) 的一部分;引擎总览、锁定决策与路线图在那里。
6
+
7
+ ## 绘制与显示列表(M4a/M4b)
8
+
9
+ `runtime/ext_gui/paint.{h,cpp}` 按画家顺序遍历布局树,生成一个与后端无关的
10
+ `DisplayList`:一个按画家顺序排列的 `ops` 列表,包含**矩形**(背景与四条实心
11
+ 边框,带 `border-radius`)、**文本 run**(每个携带其文本、颜色与已解析的
12
+ `FontSpec`)与**图像**,背后由并列的负载数组支撑。每个 op 还携带当前生效的裁剪
13
+ 矩形(最近的 `overflow: hidden` 祖先的 padding box),因此 `overflow` 裁剪与
14
+ `z-index` 顺序都能保留到渲染器。`DisplayList::dump()` 为
15
+ `paintList()`/`paintCount()` 提供数据。
16
+
17
+ `opacity` 只在两端生效:计算值为 `0` 的盒子(及其整棵子树)不绘制任何内容,
18
+ 大于 `0` 的一律按完全不透明绘制。图形只携带其颜色自身的 alpha,因此没有部分
19
+ 透明盒子的合成——CSS 解析器接受的 `opacity` 过渡因此也看不到效果。把隐藏态写成
20
+ `opacity: 0` 来隐藏/显示一层,正是 `examples/gui/pelican-bike` 游戏结束遮罩的做法。
21
+
22
+ `runtime/ext_gui/renderer.{h,cpp}` 把该列表转换为每个窗口的批处理顶点缓冲
23
+ (图形、字形四边形与图像),并把管线、裁剪与纹理都相同的连续 op 合并为
24
+ **绘制 run**。每个 run 用各自的 GPU scissor 矩形(按设备像素缩放)绘制,因此
25
+ 画家顺序与 `overflow` 裁剪按 op 生效,并通过 SDL_GPU 图形管线完成:
26
+
27
+ - 共享管线会依据设备支持的着色器格式延迟创建。SDL_GPU 的三个后端各自只接受一种
28
+ 二进制格式,且都不会在运行时编译 GLSL/HLSL,因此引擎三种都随包提供,由
29
+ `selectShader`(`renderer_shaders.h`)挑选设备可用的那一种:Metal 用 **MSL**
30
+ (SDL 从内嵌源码编译)、Vulkan 用 **SPIR-V**
31
+ (`runtime/ext_gui/spirv/*.{vert,frag}`,由 `glslc` 编译)、Direct3D 12 用
32
+ **DXIL**(`shaders.hlsl`,由 `dxc` 编译)。
33
+ `scripts/build-gui-shaders.mjs` 重新生成内嵌二进制块,产物已提交,因此构建引擎
34
+ 无需额外工具。各格式的描述符绑定不同——SPIR-V 遵循 `SDL_gpu_vulkan.c` 的布局
35
+ (uniform 在 `set=1, binding=0`,采样器在 `set=2, binding=0`,纹理在
36
+ `set=2, binding=1`)——且 glslc 会把所有入口点命名为 `main`,而 MSL/DXIL 保留
37
+ 各自的描述性名称。
38
+ 需注意:SDL 3.2.10 的 Direct3D 12 后端无法创建着色器中声明了 uniform buffer 的
39
+ 图形管线(返回 `E_INVALIDARG`);引擎正是用这种方式传视口,因此 DXIL 这条路已
40
+ 构建但在上游修复前不可用。
41
+ - 图形片元着色器中的**圆角矩形距离场**提供抗锯齿填充;顶点携带
42
+ `position`、`local`、`half extents`、`radius` 与颜色,一个视口尺寸的
43
+ push constant 负责投影。启用 alpha 混合。
44
+ - 文本管线采样一张**单一共享的灰度字形图集**(`R8_UNORM`,2048²,
45
+ shelf 打包,LINEAR 过滤),把每个字形绘制为带纹理的四边形
46
+ (`position`、`uv`、颜色),按覆盖率调制 alpha。
47
+ - 只有在文档或视口变化(`geometry.dirty`)时才上传几何数据,因此稳态帧
48
+ 只需绑定并绘制。
49
+
50
+ 每个 op 还携带它来自的那个盒子的 `Matrix2D`(`runtime/ext_gui/transform.h`、
51
+ `PaintRect::transform` 等)。它在构建顶点缓冲时应用,而不是在着色器里:
52
+ `pushRect` 把四个角经矩阵映射(距离场的 `local`/半尺寸/半径字段仍留在**盒子**
53
+ 坐标系,因此圆角旋转后依然是圆的),`pushQuad`/`pushGlyph` 同样映射字形或图像
54
+ 四边形。这样 MSL/SPIR-V/DXIL 着色器与管线都无需改动——一次变换的代价只是每个
55
+ 顶点几次乘法,与像素数无关。被变换盒子的裁剪矩形取其内边距盒经变换后的包围盒,
56
+ 因为 GPU 的 scissor 是轴对齐的。文本是唯一需要多于“乘一下四个角”的情况:
57
+ 缩放型的变换会放大图集中的位图,因此向图集请求字形时用的是设备缩放**乘以矩阵的
58
+ 放大倍率**(限制在 0.5–4 倍之间,图集容量有限),再把四边形除回逻辑单位——
59
+ 这正是 HiDPI 缩放原本就在用的做法。
60
+
61
+ 窗口背景(`setBackground`)是渲染通道的清除色。
62
+
63
+ M4 仍需完成:渐变。
64
+
65
+ ## 文本技术栈(M4b)
66
+
67
+ `runtime/ext_gui/text.{h,cpp}` 封装 **HarfBuzz**(整形)与 **FreeType**(度量
68
+ 与最终光栅化)。两者都静态构建,并由 `scripts/build-gui.ts` 链接进 `gui.a`。
69
+
70
+ - 字体从常见系统路径解析(macOS 上的 Helvetica/Arial、Linux 上的
71
+ DejaVu/Liberation、Windows 上的 Segoe UI/Arial),可用 `XT_GUI_FONT`
72
+ (以及 `XT_GUI_FONT_MONO`)覆盖,并按 `(字体类别, 字号)` 缓存。目前只使用
73
+ 常规直立字面;字重/斜体选择是后续细化。
74
+ - **主字面缺少的码点会回退到其他字面。** 引擎维护一条小的回退链——先平台的
75
+ 符号字体(Segoe UI Symbol、Apple Symbols、Noto Sans Symbols、DejaVu Sans),
76
+ 再是另一字体类别——并为每个码点解析出回退链中第一个拥有该字形的字面。
77
+ run 以每个字面一段的方式整形,因此混合 run(拉丁文加箭头、制表符或几何图形,
78
+ 或 CJK 中的拉丁文)会绘制每个字形,而不是把缺失的字形当作 `.notdef` 丢弃。
79
+ 这正是让诸如 `▮`/`▯`(U+25AE/U+25AF,鹈鹕自行车速度条所用)这样的字符在
80
+ CJK 主字面没有几何图形时仍能显示的原因。每个整形后的字形携带自己的字面
81
+ (`ShapedGlyph::font`),渲染器据此从正确的图集条目光栅化;`xt_text_scale`
82
+ 把字面映射到显示的缩放系数。
83
+ - `xt_text_measure_width` 用 HarfBuzz 对 run 整形(因此字距调整与连字会生效),
84
+ `xt_text_metrics` 返回 FreeType 的 ascent / descent / 正常行高。当找不到
85
+ 字体文件时,模块回退到确定性的逐字节近似,因此布局仍可工作。
86
+ - `xt_text_shape_run` 返回定位后的字形,`xt_text_rasterize` 渲染 8 位位图;
87
+ 渲染器把它们打包进图集。在 HiDPI 显示器上,字形以
88
+ `font_size * SDL_GetWindowPixelDensity` 光栅化,而四边形以逻辑像素定位,
89
+ 因此文本保持清晰。
90
+ - 布局使用这些真实度量进行文本宽度、断行与 `line-height: normal` 计算;
91
+ `measureText`/`fontMetrics` 把它们暴露给测试。
92
+
@@ -0,0 +1,76 @@
1
+ # 运行时集成与内存
2
+
3
+ > 语言 / Language:[English](../../gui/runtime.md) | **简体中文**
4
+
5
+ > 属于 [GUI 扩展](../gui.md) 的一部分;引擎总览、锁定决策与路线图在那里。
6
+
7
+ ## 事件循环集成
8
+
9
+ 运行时改动(已落地):
10
+
11
+ - `int xt_loop_poll(int timeout_ms)` —— 一次 reactor 迭代;`0` 非阻塞轮询,
12
+ `< 0` 阻塞。
13
+ - `void xt_loop_set_main(xt_main_loop_fn fn)` —— 宿主可以接管主循环。
14
+ `xt_run_event_loop()` 会委托给它;生成的 `main` 不变。
15
+ - `xt_loop_set_main(NULL)` 恢复默认的 `select(2)` 循环。
16
+
17
+ GUI 引擎既可以通过 `xt_loop_set_main` 注册自己的循环,也可以暴露显式的
18
+ `run()`;**M2 使用显式的 `run()`**,因此窗口是 TypeScript 程序可以控制的
19
+ 普通原生调用。每次 tick 它都会:
20
+
21
+ 1. 为每个窗口泵送 SDL 窗口/输入事件,
22
+ 2. 当显示列表变化时构建/上传它,并渲染每个打开的窗口(清除通道 + 图形几何
23
+ + 文本几何),
24
+ 3. 调用 `xt_loop_poll(0)` 与 `xt_drain_microtasks()`,使 socket/定时器与
25
+ `await` 续体持续取得进展,
26
+ 4. 重复直到所有窗口关闭或调用 `quit()`。
27
+
28
+ `xt_loop_set_main` 钩子仍保留给希望自行掌控循环的宿主。
29
+
30
+ 这使网络 I/O、定时器与 `await` 在 GUI 程序内部继续工作。
31
+
32
+ ## 原生 ↔ TS 桥接
33
+
34
+ - **TS → 引擎**:直接的 `xt_gui_*` 调用 / 窗口方法。
35
+ - **引擎 → TS**:`xt_call_with_this(fn, thisValue, argc, argv)`,其中函数值
36
+ 从 TS 捕获(例如用 `win.on(...)` 注册的事件处理器)。
37
+ - **未来的页面→原生 RPC**:在没有页面 JS 时不需要;原生 TS 就是控制器。
38
+ 如果日后加入声明式层,它将使用相同的 `xt_call_*` 入口。
39
+
40
+ ## 内存与 GC
41
+
42
+ GUI 程序的大部分内存不在运行时堆里:DOM 树、分离节点池、元素句柄表、布局盒树
43
+ 与显示列表都是 C++,标记-清扫收集器看不到它们。`stats()`(见[诊断](./api.md))报告其中
44
+ 可能增长的部分:
45
+
46
+ | 计数器 | 稳定状态下的期望值 |
47
+ | --- | --- |
48
+ | `treeNodes` | 常量——即编写的文档 |
49
+ | `detachedRoots`、`detachedNodes` | 0;若脚本自己移除了节点并仍持有,则为常量 |
50
+ | `handles`、`handleSlots` | 常量——每个脚本仍引用的元素一个 |
51
+ | `listeners` | 常量——每个注册了监听器的元素一个 |
52
+ | `heapBytes`、`heapAllocations` | 在收集器阈值附近呈锯齿波动 |
53
+
54
+ 其余都是每帧用完即释放的临时数据(盒、显示列表、顶点暂存)。若某个计数器随帧数
55
+ 持续上升,那就是最该先查的泄漏特征;`tests/e2e/gui-retention.test.ts` 断言常见
56
+ 变更模式不会改变这些计数。
57
+
58
+ 读这些数字时有两处引擎细节需要注意:
59
+
60
+ - **谁会释放节点。** `innerHTML`/`inner`/`textContent`/`replaceChild` 会释放它们
61
+ 丢弃的子树;`removeChild`/`remove` 会保留(见[节点生命周期](./dom.md))。因此用
62
+ `innerHTML` 每帧重建精灵的游戏内存恒定。
63
+ - **什么仍会累积。** 用 `createElement` 创建却从未插入的节点会留在池中:收集器
64
+ 没有弱引用,引擎无法知道脚本已丢弃其句柄。脚本移除后又不再引用的节点同理。
65
+ 两者都以*存活句柄*数量为界,但每帧创建元素却不插入的程序会让池随帧数增长。
66
+ - **矢量图像按像素尺寸缓存。** 位图 `<img>` 只解码一次并按路径缓存,而 SVG 会按
67
+ 绘制设备尺寸光栅化,因此解码像素缓存与 GPU 纹理缓存会为每个不同尺寸保留一项。
68
+ 对静态布局(常见情况)是常量,对少量尺寸也很小;但每帧把同一 SVG 重排到新整数
69
+ 尺寸的程序会不断累积条目——此时宜改为动画位图,或让矢量图保持固定尺寸。
70
+
71
+ `heapBytes` 背后的收集器是 `runtime/xt_alloc.c` 中的非移动标记-清扫。它的触发
72
+ 阈值跟随存活集:每次回收后阈值变为存活堆的 2 倍(下限 1 MiB),因此存活集小
73
+ 就意味着回收频繁而轻量、常驻内存有界。用 `XT_GC_THRESHOLD=<bytes>` 可以固定
74
+ 阈值;`XT_GC_TRACE=1` 会为每次回收向 stderr 打印一行(回收前后的堆与存活分配数、
75
+ 下一次触发阈值,以及按对象类型统计的存活集)——DOM 泄漏正是这样被定位到节点池的。
76
+
@@ -226,6 +226,9 @@ b.addEventListener("click", () => { n = double(n) + 1; b.textContent = String(n)
226
226
  - 变更:`appendChild` / `insertBefore` / `removeChild` / `replaceChild` /
227
227
  `remove` / `cloneNode(deep?)`。
228
228
  - 几何/焦点:`getBoundingClientRect`、`focus`、`blur`、`click`。
229
+ - 媒体(`<video>` / `<audio>`):`play()` / `pause()` / `load()` 与
230
+ `src`、`paused`、`ended`、`duration`、`currentTime`、`volume`、`muted`、
231
+ `loop`、`autoplay`、`videoWidth`、`videoHeight`、`readyState` 属性。
229
232
  - 事件:`addEventListener(type, fn, optionsOrCapture?)`、
230
233
  `removeEventListener`、`dispatchEvent`。
231
234
 
@@ -323,7 +326,8 @@ Event 的 `target` 是句柄,其描述符(`toString`)与同一 `div#id.cla
323
326
 
324
327
  ## 需要更新的文档
325
328
 
326
- - `gui.md` —— 非目标(“执行页面 `<script>`” → “执行*运行时*页面脚本;
327
- 编译期脚本为 AOT 编译”)、锁定决策 #1、面向 TS 的 API、里程碑、进度日志。
329
+ - `gui.md`(总览)与 `gui/dom.md` —— 非目标(“执行页面 `<script>`” → “执行
330
+ *运行时*页面脚本;编译期脚本为 AOT 编译”)、锁定决策 #1、AOT 脚本章节、
331
+ 里程碑与进度日志;面向 TS 的 API 见 `gui/api.md`。
328
332
  - `implemented.md` / `unimplemented.md` —— 把新的 DOM/脚本能力相应迁移。
329
333
  - `README.md` —— 提及 DOM API + AOT 脚本。