xbintsc 0.5.45 → 0.5.52

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 (286) 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 +34 -0
  40. package/dist/src/extensions/gui/features.js +165 -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-css-coverage.test.d.ts +9 -0
  63. package/dist/tests/e2e/gui-css-coverage.test.js +333 -0
  64. package/dist/tests/e2e/gui-css-coverage.test.js.map +1 -0
  65. package/dist/tests/e2e/gui-css-features.test.d.ts +6 -0
  66. package/dist/tests/e2e/gui-css-features.test.js +270 -0
  67. package/dist/tests/e2e/gui-css-features.test.js.map +1 -0
  68. package/dist/tests/e2e/gui-effects.test.js +92 -11
  69. package/dist/tests/e2e/gui-effects.test.js.map +1 -1
  70. package/dist/tests/e2e/gui-feature-linking.test.d.ts +9 -0
  71. package/dist/tests/e2e/gui-feature-linking.test.js +68 -0
  72. package/dist/tests/e2e/gui-feature-linking.test.js.map +1 -0
  73. package/dist/tests/e2e/gui-font-face.test.d.ts +10 -0
  74. package/dist/tests/e2e/gui-font-face.test.js +142 -0
  75. package/dist/tests/e2e/gui-font-face.test.js.map +1 -0
  76. package/dist/tests/e2e/gui-fonts.test.d.ts +10 -0
  77. package/dist/tests/e2e/gui-fonts.test.js +150 -0
  78. package/dist/tests/e2e/gui-fonts.test.js.map +1 -0
  79. package/dist/tests/e2e/gui-helpers.d.ts +10 -1
  80. package/dist/tests/e2e/gui-helpers.js +6 -0
  81. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  82. package/dist/tests/e2e/gui-layout.test.js +7 -3
  83. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  84. package/dist/tests/e2e/gui-media-disabled.test.d.ts +5 -4
  85. package/dist/tests/e2e/gui-media-disabled.test.js +7 -6
  86. package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -1
  87. package/dist/tests/e2e/gui-media.test.d.ts +5 -4
  88. package/dist/tests/e2e/gui-media.test.js +9 -9
  89. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  90. package/dist/tests/e2e/gui-paint.test.js +5 -2
  91. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  92. package/dist/tests/e2e/gui-shaping.test.d.ts +5 -0
  93. package/dist/tests/e2e/gui-shaping.test.js +47 -0
  94. package/dist/tests/e2e/gui-shaping.test.js.map +1 -0
  95. package/dist/tests/e2e/gui-track-sizing.test.d.ts +6 -0
  96. package/dist/tests/e2e/gui-track-sizing.test.js +173 -0
  97. package/dist/tests/e2e/gui-track-sizing.test.js.map +1 -0
  98. package/dist/tests/e2e/gui-transform.test.js +3 -2
  99. package/dist/tests/e2e/gui-transform.test.js.map +1 -1
  100. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.d.ts +18 -0
  101. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js +120 -0
  102. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js.map +1 -0
  103. package/dist/tests/extensions/gui-features.test.d.ts +1 -0
  104. package/dist/tests/extensions/gui-features.test.js +175 -0
  105. package/dist/tests/extensions/gui-features.test.js.map +1 -0
  106. package/doc/ai/cli.md +5 -1
  107. package/doc/ai/extensions.md +56 -26
  108. package/doc/ai/troubleshooting.md +10 -7
  109. package/doc/ai/zh-CN/cli.md +5 -1
  110. package/doc/ai/zh-CN/extensions.md +48 -22
  111. package/doc/ai/zh-CN/troubleshooting.md +6 -5
  112. package/doc/gui/animation.md +39 -9
  113. package/doc/gui/api.md +2 -1
  114. package/doc/gui/build.md +137 -19
  115. package/doc/gui/canvas.md +1 -1
  116. package/doc/gui/dom.md +20 -4
  117. package/doc/gui/known-issues.md +63 -26
  118. package/doc/gui/layout.md +31 -8
  119. package/doc/gui/media.md +13 -7
  120. package/doc/gui/rendering.md +114 -39
  121. package/doc/gui/runtime.md +5 -0
  122. package/doc/gui/webgl.md +24 -6
  123. package/doc/gui.md +110 -47
  124. package/doc/implemented/cli.md +2 -0
  125. package/doc/implemented/extensions.md +9 -3
  126. package/doc/npm-package.md +4 -4
  127. package/doc/xbintsc.config.schema.json +10 -0
  128. package/doc/zh-CN/gui/animation.md +33 -10
  129. package/doc/zh-CN/gui/api.md +2 -1
  130. package/doc/zh-CN/gui/build.md +110 -16
  131. package/doc/zh-CN/gui/canvas.md +1 -1
  132. package/doc/zh-CN/gui/dom.md +16 -3
  133. package/doc/zh-CN/gui/known-issues.md +45 -15
  134. package/doc/zh-CN/gui/layout.md +23 -6
  135. package/doc/zh-CN/gui/media.md +11 -6
  136. package/doc/zh-CN/gui/rendering.md +84 -26
  137. package/doc/zh-CN/gui/runtime.md +3 -0
  138. package/doc/zh-CN/gui/webgl.md +3 -3
  139. package/doc/zh-CN/gui.md +92 -41
  140. package/doc/zh-CN/implemented/cli.md +2 -0
  141. package/doc/zh-CN/implemented/extensions.md +9 -3
  142. package/doc/zh-CN/npm-package.md +4 -3
  143. package/package.json +7 -7
  144. package/runtime/ext_gui/animation_values.cpp +425 -0
  145. package/runtime/ext_gui/animation_values.h +64 -0
  146. package/runtime/ext_gui/browser.cpp +22 -15
  147. package/runtime/ext_gui/browser_loop.cpp +13 -1
  148. package/runtime/ext_gui/canvas.h +1 -1
  149. package/runtime/ext_gui/canvas_raster.cpp +1 -1
  150. package/runtime/ext_gui/canvas_text.cpp +1 -1
  151. package/runtime/ext_gui/css.cpp +172 -21
  152. package/runtime/ext_gui/css.h +75 -4
  153. package/runtime/ext_gui/css_match.cpp +4 -0
  154. package/runtime/ext_gui/css_sheet.cpp +218 -2
  155. package/runtime/ext_gui/document.cpp +42 -192
  156. package/runtime/ext_gui/document.h +25 -12
  157. package/runtime/ext_gui/document_animation.cpp +428 -0
  158. package/runtime/ext_gui/dom_api_canvas.cpp +15 -8
  159. package/runtime/ext_gui/dom_api_canvas_extra.cpp +44 -20
  160. package/runtime/ext_gui/dom_api_canvas_image.cpp +3 -1
  161. package/runtime/ext_gui/dom_api_node_read.cpp +10 -0
  162. package/runtime/ext_gui/dom_api_offscreen.cpp +3 -1
  163. package/runtime/ext_gui/dom_api_style.cpp +7 -0
  164. package/runtime/ext_gui/dom_api_webgl_internal.h +4 -0
  165. package/runtime/ext_gui/dom_api_webgl_source.cpp +5 -2
  166. package/runtime/ext_gui/effect_values.cpp +245 -21
  167. package/runtime/ext_gui/gpu_bridge.c +28 -0
  168. package/runtime/ext_gui/gpu_feature.cpp +30 -0
  169. package/runtime/ext_gui/gpu_shader.cpp +22 -0
  170. package/runtime/ext_gui/gpu_shader_api.h +50 -0
  171. package/runtime/ext_gui/gradient_values.cpp +24 -0
  172. package/runtime/ext_gui/grid_values.cpp +23 -0
  173. package/runtime/ext_gui/gui.cpp +6 -37
  174. package/runtime/ext_gui/gui_canvas.cpp +14 -6
  175. package/runtime/ext_gui/gui_cursor.cpp +50 -0
  176. package/runtime/ext_gui/gui_engine.h +1 -18
  177. package/runtime/ext_gui/gui_events.cpp +19 -6
  178. package/runtime/ext_gui/gui_internal.h +3 -0
  179. package/runtime/ext_gui/gui_webgl.cpp +25 -10
  180. package/runtime/ext_gui/image_api.cpp +18 -0
  181. package/runtime/ext_gui/image_api.h +43 -0
  182. package/runtime/ext_gui/image_bitmap_browser.cpp +20 -11
  183. package/runtime/ext_gui/image_bridge.c +22 -0
  184. package/runtime/ext_gui/image_feature.cpp +107 -0
  185. package/runtime/ext_gui/inline_svg.cpp +95 -8
  186. package/runtime/ext_gui/inline_svg.h +5 -1
  187. package/runtime/ext_gui/layout.cpp +15 -9
  188. package/runtime/ext_gui/layout.h +8 -2
  189. package/runtime/ext_gui/layout_flex.cpp +134 -29
  190. package/runtime/ext_gui/layout_flow.cpp +58 -7
  191. package/runtime/ext_gui/layout_flow_internal.h +39 -4
  192. package/runtime/ext_gui/layout_grid.cpp +31 -0
  193. package/runtime/ext_gui/layout_inline.cpp +26 -2
  194. package/runtime/ext_gui/layout_vertical.cpp +196 -0
  195. package/runtime/ext_gui/media.cpp +4 -2
  196. package/runtime/ext_gui/media.h +3 -3
  197. package/runtime/ext_gui/media_api.h +7 -6
  198. package/runtime/ext_gui/paint.cpp +67 -9
  199. package/runtime/ext_gui/paint.h +61 -8
  200. package/runtime/ext_gui/paint_backdrop.cpp +211 -0
  201. package/runtime/ext_gui/paint_background.cpp +175 -97
  202. package/runtime/ext_gui/paint_effects.cpp +12 -6
  203. package/runtime/ext_gui/paint_internal.h +27 -0
  204. package/runtime/ext_gui/paint_outline.cpp +44 -0
  205. package/runtime/ext_gui/paint_shadow.cpp +368 -0
  206. package/runtime/ext_gui/renderer.cpp +15 -7
  207. package/runtime/ext_gui/renderer.h +11 -2
  208. package/runtime/ext_gui/renderer_backdrop.cpp +374 -0
  209. package/runtime/ext_gui/renderer_bitmap.cpp +82 -0
  210. package/runtime/ext_gui/renderer_gradient.cpp +51 -17
  211. package/runtime/ext_gui/renderer_image.cpp +19 -7
  212. package/runtime/ext_gui/renderer_internal.h +21 -0
  213. package/runtime/ext_gui/renderer_layer.cpp +37 -6
  214. package/runtime/ext_gui/renderer_text.cpp +2 -1
  215. package/runtime/ext_gui/stb_truetype.h +5077 -0
  216. package/runtime/ext_gui/style.cpp +62 -8
  217. package/runtime/ext_gui/style.h +111 -4
  218. package/runtime/ext_gui/style_cascade.cpp +83 -0
  219. package/runtime/ext_gui/style_internal.h +13 -1
  220. package/runtime/ext_gui/style_length.cpp +92 -0
  221. package/runtime/ext_gui/style_print.cpp +83 -2
  222. package/runtime/ext_gui/style_svg.cpp +40 -0
  223. package/runtime/ext_gui/style_text.cpp +302 -0
  224. package/runtime/ext_gui/style_ui.cpp +150 -0
  225. package/runtime/ext_gui/style_values.cpp +76 -62
  226. package/runtime/ext_gui/style_values_internal.h +23 -0
  227. package/runtime/ext_gui/text.cpp +27 -499
  228. package/runtime/ext_gui/text.h +18 -9
  229. package/runtime/ext_gui/text_api.h +45 -0
  230. package/runtime/ext_gui/text_bridge.c +23 -0
  231. package/runtime/ext_gui/text_cache.cpp +12 -3
  232. package/runtime/ext_gui/text_font_db.cpp +427 -0
  233. package/runtime/ext_gui/text_font_paths.cpp +250 -0
  234. package/runtime/ext_gui/text_font_paths.h +39 -0
  235. package/runtime/ext_gui/text_font_sfnt.cpp +396 -0
  236. package/runtime/ext_gui/text_font_sfnt.h +44 -0
  237. package/runtime/ext_gui/text_fonts.cpp +348 -0
  238. package/runtime/ext_gui/text_fonts.h +78 -0
  239. package/runtime/ext_gui/text_fonts_internal.h +77 -0
  240. package/runtime/ext_gui/text_glyph_cache.cpp +2 -2
  241. package/runtime/ext_gui/text_hb.cpp +438 -0
  242. package/runtime/ext_gui/text_stb.cpp +369 -0
  243. package/runtime/ext_gui/transform.cpp +107 -9
  244. package/runtime/ext_gui/transform.h +5 -0
  245. package/runtime/ext_gui/webgl_api.cpp +19 -0
  246. package/runtime/ext_gui/webgl_api.h +53 -0
  247. package/runtime/ext_gui/webgl_bridge.c +25 -0
  248. package/runtime/ext_gui/webgl_draw_setup.cpp +42 -1
  249. package/runtime/ext_gui/webgl_feature.cpp +82 -0
  250. package/runtime/ext_gui/webgl_feature.h +28 -0
  251. package/runtime/ext_gui/webgl_msl.cpp +41 -0
  252. package/runtime/ext_gui/webgl_msl.h +6 -0
  253. package/runtime/ext_gui/webgl_shader.cpp +40 -32
  254. package/runtime/ext_gui/window.cpp +7 -7
  255. package/runtime/ext_media/decode_audio.cpp +1 -1
  256. package/runtime/ext_media/media.cpp +1 -1
  257. package/runtime/ext_media/media_bridge.c +7 -7
  258. package/scripts/build-gui.ts +97 -98
  259. package/scripts/check-file-length.ts +1 -0
  260. package/scripts/gui-build-options.ts +122 -0
  261. package/src/cli/extensions.ts +72 -0
  262. package/src/cli/help.ts +3 -1
  263. package/src/cli/hints.ts +9 -3
  264. package/src/cli/main.ts +21 -27
  265. package/src/diagnostics/diagnostic.ts +2 -0
  266. package/src/driver/build-key.ts +3 -0
  267. package/src/driver/compiler.ts +63 -8
  268. package/src/driver/config.ts +6 -0
  269. package/src/driver/link-features.ts +163 -0
  270. package/src/driver/options.ts +10 -0
  271. package/src/driver/runtime-objects.ts +26 -1
  272. package/src/driver/usage.ts +376 -0
  273. package/src/driver/version.ts +1 -1
  274. package/src/extensions/catalog.ts +1 -2
  275. package/src/extensions/gui/features.ts +166 -0
  276. package/src/extensions/gui/index.ts +10 -4
  277. package/src/extensions/link-feature.ts +72 -0
  278. package/src/extensions/registry.ts +45 -4
  279. package/src/index.ts +3 -2
  280. package/dist/src/extensions/media/index.d.ts +0 -28
  281. package/dist/src/extensions/media/index.js +0 -59
  282. package/dist/src/extensions/media/index.js.map +0 -1
  283. package/dist/tests/extensions/media.test.js +0 -63
  284. package/dist/tests/extensions/media.test.js.map +0 -1
  285. package/src/extensions/media/index.ts +0 -65
  286. /package/dist/tests/{extensions/media.test.d.ts → driver/link-features.test.d.ts} +0 -0
@@ -15,6 +15,10 @@ void Layouter::flattenInline(LayoutBox *box, float contentWidth,
15
15
  std::vector<InlineItem> &out) const {
16
16
  const XtStyle &style = *box->style;
17
17
  if (box->is_text) {
18
+ if (style.writing_mode != WritingMode::HorizontalTB) {
19
+ flattenVerticalText(box, out);
20
+ return;
21
+ }
18
22
  float height = lineHeightOf(style);
19
23
  float ascent = ascentOf(style);
20
24
  const std::string &text = box->text;
@@ -45,7 +49,8 @@ void Layouter::flattenInline(LayoutBox *box, float contentWidth,
45
49
  }
46
50
  return;
47
51
  }
48
- if (box->display == Display::InlineBlock) {
52
+ if (box->display == Display::InlineBlock || style.inline_level ||
53
+ style.writing_mode != WritingMode::HorizontalTB) {
49
54
  float available = contentWidth;
50
55
  float intrinsic = preferredContentWidth(box, available);
51
56
  setContainingBlock(box, containingBlockAncestor(box));
@@ -56,6 +61,13 @@ void Layouter::flattenInline(LayoutBox *box, float contentWidth,
56
61
  item.atomic = true;
57
62
  item.width = box->margin_left + box->width + box->margin_right;
58
63
  item.height = box->margin_top + box->height + box->margin_bottom;
64
+ float shift = 0.0f;
65
+ if (!isAuto(style.vertical_align)) {
66
+ shift = px(style.vertical_align, available, style.font_size, 0.0f);
67
+ }
68
+ /* A positive `vertical-align` raises the atomic box above the baseline;
69
+ * the line box keeps its own ascent so the shift is purely visual. */
70
+ item.shift = shift;
59
71
  item.ascent = item.height;
60
72
  out.push_back(std::move(item));
61
73
  return;
@@ -68,6 +80,13 @@ void Layouter::flattenInline(LayoutBox *box, float contentWidth,
68
80
  }
69
81
 
70
82
  void Layouter::computeInlineGeometry(LayoutBox *box) const {
83
+ /* An atomic inline box already has geometry from its own formatting context;
84
+ * replacing it with the union of its descendants would shrink an inline-grid
85
+ * to its text (and an inline-block to its content). */
86
+ if (!box->is_text && box->style != nullptr &&
87
+ (box->display == Display::InlineBlock || box->style->inline_level)) {
88
+ return;
89
+ }
71
90
  if (box->is_text) {
72
91
  if (box->fragments.empty()) return;
73
92
  float minx = kHuge, miny = kHuge, maxx = -kHuge, maxy = -kHuge;
@@ -119,6 +138,11 @@ float Layouter::layoutInlineRun(LayoutBox *container, const std::vector<LayoutBo
119
138
  if (items.empty()) return 0;
120
139
 
121
140
  const XtStyle &containerStyle = *container->style;
141
+ if (containerStyle.writing_mode != WritingMode::HorizontalTB) {
142
+ float height = layoutVerticalRun(container, items, contentWidth, y);
143
+ for (LayoutBox *box : run) computeInlineGeometry(box);
144
+ return height;
145
+ }
122
146
  float cursorY = y;
123
147
  std::vector<InlineItem *> line;
124
148
  float lineWidth = 0;
@@ -159,7 +183,7 @@ float Layouter::layoutInlineRun(LayoutBox *container, const std::vector<LayoutBo
159
183
  * `layoutBlock`, so it must not be moved back onto the line. */
160
184
  if (!isAbsoluteChild(item->box)) {
161
185
  float dx = x + item->box->margin_left - item->box->x;
162
- float dy = cursorY + item->box->margin_top - item->box->y;
186
+ float dy = cursorY + item->box->margin_top - item->box->y - item->shift;
163
187
  translate(item->box, dx, dy);
164
188
  /* `position: relative` on an inline-block shifts it after line
165
189
  * placement, so the line box keeps the space it reserved. */
@@ -0,0 +1,196 @@
1
+ /*
2
+ * xbintsc GUI engine — vertical writing mode (`writing-mode: vertical-rl|-lr`).
3
+ *
4
+ * This is a focused text layout: characters are placed one per column step,
5
+ * CJK glyphs upright and other scripts rotated 90° at paint time. It covers the
6
+ * decorative vertical labels a page uses; mixed block/inline content in a
7
+ * vertical container still falls back to horizontal layout (documented).
8
+ */
9
+
10
+ #include "layout_flow_internal.h"
11
+
12
+ namespace xtgui {
13
+ namespace layout_flow_detail {
14
+
15
+ namespace {
16
+
17
+ /** Decode one UTF-8 codepoint at `index`, advancing it. */
18
+ uint32_t nextCodePoint(const std::string &text, size_t *index) {
19
+ unsigned char first = (unsigned char)text[*index];
20
+ uint32_t value = first;
21
+ size_t length = 1;
22
+ if ((first & 0x80u) == 0) {
23
+ value = first;
24
+ } else if ((first & 0xe0u) == 0xc0u) {
25
+ value = first & 0x1fu;
26
+ length = 2;
27
+ } else if ((first & 0xf0u) == 0xe0u) {
28
+ value = first & 0x0fu;
29
+ length = 3;
30
+ } else {
31
+ value = first & 0x07u;
32
+ length = 4;
33
+ }
34
+ for (size_t i = 1; i < length && *index + i < text.size(); i++) {
35
+ value = (value << 6) | ((unsigned char)text[*index + i] & 0x3fu);
36
+ }
37
+ *index += length;
38
+ return value;
39
+ }
40
+
41
+ /** CJK, kana, Hangul and fullwidth forms are kept upright in vertical text. */
42
+ bool isUprightCodePoint(uint32_t value) {
43
+ if (value >= 0x1100 && value <= 0x11ff) return true;
44
+ if (value >= 0x2e80 && value <= 0xa4cf) return true;
45
+ if (value >= 0xac00 && value <= 0xd7af) return true;
46
+ if (value >= 0xf900 && value <= 0xfaff) return true;
47
+ if (value >= 0xfe30 && value <= 0xfe4f) return true;
48
+ if (value >= 0xff00 && value <= 0xffef) return true;
49
+ if (value >= 0x20000) return true;
50
+ return false;
51
+ }
52
+
53
+ /** The column's top-left x for the first column (right edge for `vertical-rl`). */
54
+ float firstColumnX(const LayoutBox *container, const XtStyle &style, float contentWidth,
55
+ float columnWidth) {
56
+ if (style.writing_mode == WritingMode::VerticalLR) return container->content_x;
57
+ float x = container->content_x + contentWidth - columnWidth;
58
+ if (x < container->content_x) x = container->content_x;
59
+ return x;
60
+ }
61
+
62
+ } // namespace
63
+
64
+ void Layouter::flattenVerticalText(LayoutBox *box, std::vector<InlineItem> &out) const {
65
+ const XtStyle &style = *box->style;
66
+ const float columnWidth = lineHeightOf(style);
67
+ const std::string &text = box->text;
68
+ size_t index = 0;
69
+ while (index < text.size()) {
70
+ size_t start = index;
71
+ uint32_t code = nextCodePoint(text, &index);
72
+ if (code == '\n') {
73
+ InlineItem br;
74
+ br.box = box;
75
+ br.style = &style;
76
+ br.force_break = true;
77
+ br.vertical = true;
78
+ out.push_back(std::move(br));
79
+ continue;
80
+ }
81
+ InlineItem item;
82
+ item.box = box;
83
+ item.style = &style;
84
+ item.text = text.substr(start, index - start);
85
+ item.is_space = code == ' ' || code == '\t';
86
+ item.vertical = true;
87
+ item.upright = isUprightCodePoint(code);
88
+ item.width = columnWidth;
89
+ item.height = textWidth(item.text, style);
90
+ item.ascent = item.height;
91
+ out.push_back(std::move(item));
92
+ }
93
+ }
94
+
95
+ float Layouter::layoutVerticalRun(LayoutBox *container, const std::vector<InlineItem> &items,
96
+ float contentWidth, float y) const {
97
+ const XtStyle &style = *container->style;
98
+ const float columnWidth = lineHeightOf(style);
99
+ const bool rightToLeft = style.writing_mode != WritingMode::VerticalLR;
100
+ float limit = -1.0f;
101
+ if (!isAuto(style.height)) limit = px(style.height, container->content_height, style.font_size, -1.0f);
102
+
103
+ float columnX = firstColumnX(container, style, contentWidth, columnWidth);
104
+ float cursorY = y;
105
+ float startY = y;
106
+ float minX = columnX;
107
+ float maxX = columnX + columnWidth;
108
+ float maxY = y;
109
+ bool atColumnStart = true;
110
+
111
+ auto newColumn = [&]() {
112
+ columnX += rightToLeft ? -columnWidth : columnWidth;
113
+ cursorY = startY;
114
+ atColumnStart = true;
115
+ if (columnX < minX) minX = columnX;
116
+ if (columnX + columnWidth > maxX) maxX = columnX + columnWidth;
117
+ };
118
+
119
+ for (const InlineItem &item : items) {
120
+ if (item.force_break) {
121
+ newColumn();
122
+ continue;
123
+ }
124
+ if (item.is_space && atColumnStart) continue;
125
+ if (limit >= 0.0f && !atColumnStart && cursorY + item.height > startY + limit) newColumn();
126
+ item.box->fragments.push_back(
127
+ {columnX, cursorY, columnWidth, item.height, item.text, true, item.upright});
128
+ cursorY += item.height;
129
+ atColumnStart = false;
130
+ if (cursorY > maxY) maxY = cursorY;
131
+ if (item.width > 0.0f) {
132
+ float right = columnX + columnWidth;
133
+ if (right > maxX) maxX = right;
134
+ }
135
+ }
136
+ (void)minX;
137
+ (void)maxX;
138
+ return maxY - startY;
139
+ }
140
+
141
+ float Layouter::layoutVerticalTextBox(LayoutBox *box, float x, float y, float width) const {
142
+ const XtStyle &style = *box->style;
143
+ const float columnWidth = lineHeightOf(style);
144
+ const bool rightToLeft = style.writing_mode != WritingMode::VerticalLR;
145
+ float limit = -1.0f;
146
+ if (!isAuto(style.height)) limit = px(style.height, -1.0f, style.font_size, -1.0f);
147
+
148
+ float columnX = rightToLeft ? x + width - columnWidth : x;
149
+ if (rightToLeft && columnX < x) columnX = x;
150
+ float cursorY = y;
151
+ float startY = y;
152
+ float minX = columnX;
153
+ float maxX = columnX + columnWidth;
154
+ float maxY = y;
155
+ bool atColumnStart = true;
156
+
157
+ size_t index = 0;
158
+ const std::string &text = box->text;
159
+ while (index < text.size()) {
160
+ size_t start = index;
161
+ uint32_t code = nextCodePoint(text, &index);
162
+ if (code == '\n') {
163
+ columnX += rightToLeft ? -columnWidth : columnWidth;
164
+ cursorY = startY;
165
+ atColumnStart = true;
166
+ continue;
167
+ }
168
+ std::string glyph = text.substr(start, index - start);
169
+ float advance = textWidth(glyph, style);
170
+ if (limit >= 0.0f && !atColumnStart && cursorY + advance > startY + limit) {
171
+ columnX += rightToLeft ? -columnWidth : columnWidth;
172
+ cursorY = startY;
173
+ atColumnStart = true;
174
+ }
175
+ if (code == ' ' && atColumnStart) continue;
176
+ box->fragments.push_back(
177
+ {columnX, cursorY, columnWidth, advance, glyph, true, isUprightCodePoint(code)});
178
+ cursorY += advance;
179
+ atColumnStart = false;
180
+ if (cursorY > maxY) maxY = cursorY;
181
+ }
182
+
183
+ box->x = minX;
184
+ box->y = startY;
185
+ box->width = maxX - minX;
186
+ box->height = maxY - startY;
187
+ box->content_x = box->x;
188
+ box->content_y = box->y;
189
+ box->content_width = box->width;
190
+ box->content_height = box->height;
191
+ box->baseline = box->height;
192
+ return box->height;
193
+ }
194
+
195
+ } // namespace layout_flow_detail
196
+ } // namespace xtgui
@@ -33,7 +33,9 @@ namespace xtgui {
33
33
  std::unique_ptr<MediaPlayer> MediaPlayer::open(const std::string& path, std::string* error,
34
34
  std::unique_ptr<AudioSink> audio) {
35
35
  if (g_api == nullptr || g_api->open == nullptr) {
36
- if (error) *error = "media support requires the 'media' extension (pass --ext media)";
36
+ if (error) {
37
+ *error = "media support was not linked; rebuild with '--feature media' (or make the media use visible to the compiler)";
38
+ }
37
39
  return nullptr;
38
40
  }
39
41
  char message[kErrorSize] = {0};
@@ -59,7 +61,7 @@ bool xt_media_decode_audio(const uint8_t* data, size_t size, DecodedAudio* out,
59
61
  std::string* error) {
60
62
  if (g_api == nullptr || g_api->decode_audio == nullptr) {
61
63
  if (error) {
62
- *error = "cannot decode this audio format; enable the 'media' extension (pass --ext media)";
64
+ *error = "cannot decode this audio format; rebuild with '--feature media' (the FFmpeg backend was not linked)";
63
65
  }
64
66
  return false;
65
67
  }
@@ -7,8 +7,8 @@
7
7
  * The engine owns the DOM integration (one `MediaPlayer` per element, owned by
8
8
  * the window) and the renderer uploads decoded frames as GPU textures. The
9
9
  * decoder itself is optional: `MediaPlayer::open` dispatches through the table
10
- * the `media` extension installs at startup (see `media_api.h`). A program built
11
- * with `--ext gui` alone therefore links without FFmpeg and reports an `error`
10
+ * the `media` link feature installs at startup (see `media_api.h`). A program
11
+ * that does not use media therefore links without FFmpeg and reports an `error`
12
12
  * event when a media element is opened.
13
13
  *
14
14
  * A `MediaPlayer` owns one demux/decode pipeline for a single source file. It
@@ -25,7 +25,7 @@
25
25
  * engine already runs a frame loop, so there is no separate media thread and no
26
26
  * interaction with the garbage collector.
27
27
  *
28
- * The FFmpeg headers stay inside the media extension's `media.cpp`; this header
28
+ * The FFmpeg headers stay inside the media feature's `media.cpp`; this header
29
29
  * only exposes value types so the rest of the engine (and `gui_engine.h`) can
30
30
  * hold a player without pulling in libav*.
31
31
  */
@@ -6,16 +6,17 @@
6
6
  *
7
7
  * The engine knows how to model `<video>`/`<audio>` elements and how to draw
8
8
  * their frames, but it deliberately contains no decoder: FFmpeg lives in the
9
- * separate `media` compile extension (`runtime/ext_media/`, linked as
9
+ * optional `media` link feature (`runtime/ext_media/`, linked as
10
10
  * `media.a`, or `media.lib` with the MSVC ABI on Windows). Keeping the boundary C-only means the backend can be a prebuilt
11
11
  * C++ archive that is compiled independently of the engine.
12
12
  *
13
13
  * Registration is push-based: the backend's bridge translation unit is linked
14
- * directly (it is a `runtimeSources()` entry of the media extension), and a
14
+ * only when the compiler selects the feature (it is the feature's
15
+ * `runtimeSources()` entry), and a
15
16
  * constructor calls `xt_media_install` with its function table. The GUI's
16
- * dispatch layer (`media.cpp`) starts with a null table, so a program built
17
- * with `--ext gui` alone still links and simply reports that media support was
18
- * not enabled.
17
+ * dispatch layer (`media.cpp`) starts with a null table, so a program that does
18
+ * not use media still links and simply reports that media support was
19
+ * not linked.
19
20
  *
20
21
  * `audio_sink` is an opaque `xtgui::AudioSink*` owned by the caller until the
21
22
  * open call; the backend takes ownership whether the open succeeds or not.
@@ -72,7 +73,7 @@ void xt_media_install(const XtMediaApi *api);
72
73
 
73
74
  /** Decoder backend entry points, implemented by `runtime/ext_media/media.cpp`
74
75
  * (inside `media.a`/`media.lib`). The bridge references them, which is what makes the
75
- * linker extract the archive member when the media extension is enabled. */
76
+ * linker extract the archive member when the `media` feature is selected. */
76
77
  XtMediaPlayerHandle xt_media_backend_open(const char *path, char *error,
77
78
  size_t error_size, void *audio_sink);
78
79
  XtMediaPlayerHandle xt_media_backend_by_id(uint64_t id);
@@ -9,6 +9,7 @@
9
9
  #include "paint.h"
10
10
 
11
11
  #include <algorithm>
12
+ #include <cmath>
12
13
  #include <cstddef>
13
14
  #include <cstdio>
14
15
  #include <utility>
@@ -46,6 +47,7 @@ FontSpec specOf(const XtStyle &style) {
46
47
  spec.pixel_size = style.font_size;
47
48
  spec.weight = style.font_weight;
48
49
  spec.italic = style.font_style == FontStyle::Italic;
50
+ spec.letter_spacing = style.letter_spacing.unit == Unit::Px ? style.letter_spacing.value : 0.0f;
49
51
  return spec;
50
52
  }
51
53
 
@@ -145,11 +147,34 @@ void addTextRuns(const LayoutBox *box, const XtStyle &style, DisplayList &out,
145
147
  xt_text_metrics(spec, &ascent, nullptr, nullptr);
146
148
  if (ascent <= 0.0f) ascent = style.font_size * 0.8f;
147
149
  Color base = paint_detail::filterColor(chain, style.color);
150
+ constexpr float kQuarterTurn = 1.57079632679f;
148
151
  for (const TextFragment &fragment : box->fragments) {
149
152
  if (fragment.text.empty()) continue;
150
- paint_detail::addTextRun(out, fragment.x, fragment.y + ascent, fragment.width,
151
- fragment.height, fragment.text, spec, base, box->world, clip, style,
152
- chain, viewportWidth, viewportHeight);
153
+ if (!fragment.vertical) {
154
+ paint_detail::addTextRun(out, fragment.x, fragment.y + ascent, fragment.width,
155
+ fragment.height, fragment.text, spec, base, box->world, clip, style,
156
+ chain, viewportWidth, viewportHeight);
157
+ continue;
158
+ }
159
+ if (fragment.upright) {
160
+ /* CJK-style upright glyph centred in its column cell. */
161
+ float glyphWidth = fragment.height;
162
+ float x = fragment.x + (fragment.width - glyphWidth) / 2.0f;
163
+ float baseline = fragment.y + (fragment.height - style.font_size) / 2.0f + ascent;
164
+ paint_detail::addTextRun(out, x, baseline, glyphWidth, fragment.width, fragment.text, spec,
165
+ base, box->world, clip, style, chain, viewportWidth, viewportHeight);
166
+ continue;
167
+ }
168
+ /* Rotate the horizontal run 90° clockwise about its column's top edge. */
169
+ float pivotX = fragment.x + ascent;
170
+ float pivotY = fragment.y;
171
+ Matrix2D turn = Matrix2D::translate(pivotX, pivotY)
172
+ .multiply(Matrix2D::rotate(kQuarterTurn))
173
+ .multiply(Matrix2D::translate(-pivotX, -pivotY));
174
+ Matrix2D transform = box->world.multiply(turn);
175
+ paint_detail::addTextRun(out, pivotX, pivotY + ascent, fragment.height, fragment.width,
176
+ fragment.text, spec, base, transform, clip, style, chain,
177
+ viewportWidth, viewportHeight);
153
178
  }
154
179
  }
155
180
 
@@ -191,6 +216,13 @@ void paintBoxInner(const LayoutBox *box, DisplayList &out, const PaintClip &clip
191
216
  /* Drop shadows sit behind the box's own background but above earlier
192
217
  * siblings. */
193
218
  paint_detail::addBoxDropShadows(out, *box, *style, chain, own);
219
+ /* A backdrop filter samples everything painted before this box, so it is
220
+ * emitted before any of the box's own shapes. */
221
+ paint_detail::addBackdropFilter(out, *box, *style, own, viewportWidth, viewportHeight);
222
+ /* CSS `box-shadow` outsets paint behind the background too; inset ones
223
+ * paint above it. */
224
+ paint_detail::addBoxShadows(out, *box, *style, chain, own, false, viewportWidth,
225
+ viewportHeight);
194
226
  paint_detail::paintBackgrounds(out, *box, *style, chain, own, viewportWidth,
195
227
  viewportHeight);
196
228
  const Matrix2D &matrix = box->world;
@@ -210,6 +242,9 @@ void paintBoxInner(const LayoutBox *box, DisplayList &out, const PaintClip &clip
210
242
  paint_detail::addRectFiltered(out, box->x + box->width - right, box->y + top, right,
211
243
  inner_height, 0.0f, bc, own, matrix, chain);
212
244
  }
245
+ paint_detail::addBoxShadows(out, *box, *style, chain, own, true, viewportWidth,
246
+ viewportHeight);
247
+ paint_detail::addOutline(out, *box, *style, chain, own, viewportWidth, viewportHeight);
213
248
  if (box->is_canvas && box->canvas_id != 0 && box->content_width > 0.0f &&
214
249
  box->content_height > 0.0f) {
215
250
  PaintImage image;
@@ -361,6 +396,15 @@ std::string DisplayList::dump() const {
361
396
  text += prefix + "end-layer\n";
362
397
  continue;
363
398
  }
399
+ if (op.kind == PaintKind::Backdrop) {
400
+ const PaintBackdrop &backdrop = backdrops[op.index];
401
+ std::snprintf(buffer, sizeof(buffer),
402
+ "backdrop x=%.1f y=%.1f w=%.1f h=%.1f r=%.1f blur=%.1f", backdrop.x,
403
+ backdrop.y, backdrop.width, backdrop.height, backdrop.radius,
404
+ backdrop.blur);
405
+ text += prefix + buffer + "\n";
406
+ continue;
407
+ }
364
408
  std::string suffix;
365
409
  if (op.kind == PaintKind::Rect) {
366
410
  suffix = transformSuffix(rects[op.index].transform);
@@ -382,12 +426,22 @@ std::string DisplayList::dump() const {
382
426
  suffix = transformSuffix(image.transform);
383
427
  if (image.gradient != nullptr) {
384
428
  const PaintGradient &gradient = *image.gradient;
385
- std::snprintf(buffer, sizeof(buffer),
386
- "image x=%.1f y=%.1f w=%.1f h=%.1f gradient=%s center=%.1f,%.1f "
387
- "radius=%.1f,%.1f stops=",
388
- image.x, image.y, image.width, image.height,
389
- gradient.repeating ? "repeating-radial" : "radial", gradient.center_x,
390
- gradient.center_y, gradient.radius_x, gradient.radius_y);
429
+ const bool linear = gradient.kind == PaintGradient::Kind::Linear;
430
+ const char *kind = linear ? (gradient.repeating ? "repeating-linear" : "linear")
431
+ : (gradient.repeating ? "repeating-radial" : "radial");
432
+ if (linear) {
433
+ std::snprintf(buffer, sizeof(buffer),
434
+ "image x=%.1f y=%.1f w=%.1f h=%.1f gradient=%s start=%.1f,%.1f "
435
+ "dir=%.4f,%.4f length=%.1f stops=",
436
+ image.x, image.y, image.width, image.height, kind, gradient.start_x,
437
+ gradient.start_y, gradient.dir_x, gradient.dir_y, gradient.length);
438
+ } else {
439
+ std::snprintf(buffer, sizeof(buffer),
440
+ "image x=%.1f y=%.1f w=%.1f h=%.1f gradient=%s center=%.1f,%.1f "
441
+ "radius=%.1f,%.1f stops=",
442
+ image.x, image.y, image.width, image.height, kind, gradient.center_x,
443
+ gradient.center_y, gradient.radius_x, gradient.radius_y);
444
+ }
391
445
  text += prefix + buffer;
392
446
  for (size_t stop = 0; stop < gradient.stops.size(); stop++) {
393
447
  std::snprintf(buffer, sizeof(buffer), "%s%s@%.4f", stop == 0 ? "" : ",",
@@ -395,6 +449,10 @@ std::string DisplayList::dump() const {
395
449
  gradient.stops[stop].offset);
396
450
  text += buffer;
397
451
  }
452
+ } else if (image.bitmap != nullptr) {
453
+ std::snprintf(buffer, sizeof(buffer),
454
+ "image x=%.1f y=%.1f w=%.1f h=%.1f bitmap=%s", image.x, image.y,
455
+ image.width, image.height, image.bitmap->key.c_str());
398
456
  } else if (image.media_id != 0) {
399
457
  std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f media=%llu",
400
458
  image.x, image.y, image.width, image.height,
@@ -32,22 +32,35 @@ struct GradientStop {
32
32
  Color color;
33
33
  };
34
34
 
35
- /** A radial `background-image` layer resolved by the paint pass: the renderer
35
+ /** A `background-image` gradient layer resolved by the paint pass: the renderer
36
36
  * rasterises it into one RGBA texture and draws that as a single image quad.
37
37
  *
38
38
  * Nesting translucent bands instead composites every band's alpha once per band
39
39
  * — each band covers the whole interior — so a vignette whose stops fade to
40
40
  * `transparent` turns into an opaque disc that hides the scene behind it.
41
- * Resolving the ramp per pixel composites it exactly once. */
41
+ * Resolving the ramp per pixel composites it exactly once, and it is the only
42
+ * way to draw a stop narrower than a band: a walk of solid rectangles either
43
+ * swallows a 1px divider line (it falls between two samples) or widens it into
44
+ * a block the width of a band. */
42
45
  struct PaintGradient {
46
+ enum class Kind { Radial, Linear };
47
+ Kind kind = Kind::Radial;
43
48
  std::vector<GradientStop> stops;
49
+ /* radial: the ending shape, box-local logical pixels. */
44
50
  /** Centre in box-local logical pixels; the drawn quad spans the whole box. */
45
51
  float center_x = 0.0f;
46
52
  float center_y = 0.0f;
47
53
  float radius_x = 0.0f;
48
54
  float radius_y = 0.0f;
49
- /** `repeating-radial-gradient()`: the stop pattern restarts every `period`
50
- * (the last stop's offset) instead of clamping at the outer ellipse. */
55
+ /* linear: `t` grows along the gradient line from `start_*` in the direction
56
+ * `(dir_x, dir_y)` over `length`; both box-local logical pixels. */
57
+ float start_x = 0.0f;
58
+ float start_y = 0.0f;
59
+ float dir_x = 1.0f;
60
+ float dir_y = 0.0f;
61
+ float length = 0.0f;
62
+ /** `repeating-*`: the stop pattern restarts every `period` (the last stop's
63
+ * offset) instead of clamping at the last stop. */
51
64
  bool repeating = false;
52
65
  float period = 1.0f;
53
66
  /** Identity of the ramp (stops and geometry) for the renderer's texture cache. */
@@ -81,6 +94,22 @@ struct PaintText {
81
94
  bool shadow = false;
82
95
  };
83
96
 
97
+ /** A CPU-rasterised RGBA bitmap carried by a display-list image op. Shadows are
98
+ * painted as one pre-blurred texture rather than a stack of offset copies: a
99
+ * separable Gaussian on the mask is the CSS blur, and the shader only has to
100
+ * composite one quad. `scale` is bitmap pixels per logical pixel. */
101
+ struct PaintBitmap {
102
+ int width = 0;
103
+ int height = 0;
104
+ float scale = 1.0f;
105
+ /** Logical offset of the bitmap's top-left from the shape's origin (a text
106
+ * run's start/baseline, or a box's top-left). */
107
+ float origin_x = 0.0f;
108
+ float origin_y = 0.0f;
109
+ std::string key;
110
+ std::vector<unsigned char> pixels;
111
+ };
112
+
84
113
  /** An image to draw, scaled into the given rectangle (viewport-relative).
85
114
  * A non-zero `media_id` names a live `<video>` frame owned by the media
86
115
  * subsystem; a non-zero `canvas_id` names a live `<canvas>` bitmap owned by the
@@ -101,9 +130,13 @@ struct PaintImage {
101
130
  /** Modulating colour for the image shader (white normally; a filter's
102
131
  * multiplicative effect may tint it). */
103
132
  Color tint{1.0f, 1.0f, 1.0f, 1.0f};
104
- /** A `background-image` radial gradient: the renderer rasterises it into the
105
- * quad instead of loading `src`/`svg`. Its stops already carry the colour
106
- * functions of the filter chain; `tint` still carries brightness/opacity. */
133
+ /** A CPU-rasterised shadow mask (white RGB, coverage in alpha) drawn with
134
+ * `tint` as its colour. */
135
+ std::shared_ptr<const PaintBitmap> bitmap;
136
+ /** A `background-image` gradient (linear or radial): the renderer rasterises
137
+ * it into the quad instead of loading `src`/`svg`. Its stops already carry the
138
+ * colour functions of the filter chain; `tint` still carries
139
+ * brightness/opacity. */
107
140
  std::shared_ptr<const PaintGradient> gradient;
108
141
  };
109
142
 
@@ -136,7 +169,7 @@ struct PaintClip {
136
169
  int paths[2] = {-1, -1};
137
170
  };
138
171
 
139
- enum class PaintKind { Rect, Text, Image, LayerBegin, LayerEnd };
172
+ enum class PaintKind { Rect, Text, Image, LayerBegin, LayerEnd, Backdrop };
140
173
 
141
174
  /** One `mix-blend-mode` compositing group. The ops between its `LayerBegin`
142
175
  * and `LayerEnd` markers are rendered to an offscreen texture and composited
@@ -145,6 +178,23 @@ struct PaintLayer {
145
178
  MixBlendMode blend = MixBlendMode::Normal;
146
179
  };
147
180
 
181
+ /** One `backdrop-filter` application: the renderer snapshots the scene painted
182
+ * so far, applies the Gaussian blur / colour matrix to it, and composites the
183
+ * result back only inside the element's rounded border box. */
184
+ struct PaintBackdrop {
185
+ float x = 0;
186
+ float y = 0;
187
+ float width = 0;
188
+ float height = 0;
189
+ float radius = 0;
190
+ /** Standard deviation of the Gaussian in logical pixels (0 = no blur). */
191
+ float blur = 0;
192
+ /** Combined colour filter, row-major 3x3 plus a per-channel offset. */
193
+ float color[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
194
+ float offset[3] = {0, 0, 0};
195
+ float alpha = 1;
196
+ };
197
+
148
198
  /** One entry in painter order: which kind of shape, its index in the matching
149
199
  * payload vector, and the clip in effect. */
150
200
  struct PaintOp {
@@ -164,6 +214,8 @@ struct DisplayList {
164
214
  std::vector<ClipPathGeometry> clip_paths;
165
215
  /** `mix-blend-mode` groups referenced by `LayerBegin` ops. */
166
216
  std::vector<PaintLayer> layers;
217
+ /** `backdrop-filter` regions referenced by `Backdrop` ops. */
218
+ std::vector<PaintBackdrop> backdrops;
167
219
 
168
220
  void clear() {
169
221
  rects.clear();
@@ -172,6 +224,7 @@ struct DisplayList {
172
224
  ops.clear();
173
225
  clip_paths.clear();
174
226
  layers.clear();
227
+ backdrops.clear();
175
228
  }
176
229
  bool empty() const { return rects.empty() && texts.empty() && images.empty(); }
177
230
  /** Human-readable dump, one shape per line, in painter order (diagnostics/tests). */