xbintsc 0.5.31 → 0.5.36

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/README.md +25 -16
  2. package/README.zh-CN.md +10 -8
  3. package/dist/scripts/check-version.d.ts +27 -0
  4. package/dist/scripts/check-version.js +71 -0
  5. package/dist/scripts/check-version.js.map +1 -0
  6. package/dist/scripts/package-platform.d.ts +58 -0
  7. package/dist/scripts/package-platform.js +142 -0
  8. package/dist/scripts/package-platform.js.map +1 -0
  9. package/dist/scripts/verify-platform-package.d.ts +34 -0
  10. package/dist/scripts/verify-platform-package.js +161 -0
  11. package/dist/scripts/verify-platform-package.js.map +1 -0
  12. package/dist/src/binder/references.js +14 -1
  13. package/dist/src/binder/references.js.map +1 -1
  14. package/dist/src/cli/help.d.ts +1 -1
  15. package/dist/src/cli/main.js +3 -1
  16. package/dist/src/cli/main.js.map +1 -1
  17. package/dist/src/codegen/generator/module.d.ts +1 -0
  18. package/dist/src/codegen/generator/module.js +54 -0
  19. package/dist/src/codegen/generator/module.js.map +1 -1
  20. package/dist/src/codegen/generator/statements/variables.js +6 -4
  21. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  22. package/dist/src/driver/runtime-lib.d.ts +29 -4
  23. package/dist/src/driver/runtime-lib.js +68 -10
  24. package/dist/src/driver/runtime-lib.js.map +1 -1
  25. package/dist/src/driver/version.d.ts +1 -1
  26. package/dist/src/driver/version.js +1 -1
  27. package/dist/src/extensions/gui/css.d.ts +26 -0
  28. package/dist/src/extensions/gui/css.js +100 -0
  29. package/dist/src/extensions/gui/css.js.map +1 -0
  30. package/dist/src/extensions/gui/html.js +9 -2
  31. package/dist/src/extensions/gui/html.js.map +1 -1
  32. package/dist/src/extensions/gui/index.js +22 -7
  33. package/dist/src/extensions/gui/index.js.map +1 -1
  34. package/dist/src/extensions/gui/three.d.ts +16 -0
  35. package/dist/src/extensions/gui/three.js +22 -0
  36. package/dist/src/extensions/gui/three.js.map +1 -0
  37. package/dist/src/extensions/media/index.d.ts +6 -5
  38. package/dist/src/extensions/media/index.js +15 -16
  39. package/dist/src/extensions/media/index.js.map +1 -1
  40. package/dist/src/parser/context.d.ts +41 -0
  41. package/dist/src/parser/context.js +89 -4
  42. package/dist/src/parser/context.js.map +1 -1
  43. package/dist/src/parser/literals.js +6 -12
  44. package/dist/src/parser/literals.js.map +1 -1
  45. package/dist/tests/driver/platform-package.test.d.ts +1 -0
  46. package/dist/tests/driver/platform-package.test.js +49 -0
  47. package/dist/tests/driver/platform-package.test.js.map +1 -0
  48. package/dist/tests/driver/runtime-lib.test.js +51 -4
  49. package/dist/tests/driver/runtime-lib.test.js.map +1 -1
  50. package/dist/tests/e2e/compile-builtins.test.js +24 -0
  51. package/dist/tests/e2e/compile-builtins.test.js.map +1 -1
  52. package/dist/tests/e2e/compile-objects-classes.test.js +23 -0
  53. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  54. package/dist/tests/e2e/gui-audio.test.d.ts +14 -0
  55. package/dist/tests/e2e/gui-audio.test.js +287 -0
  56. package/dist/tests/e2e/gui-audio.test.js.map +1 -0
  57. package/dist/tests/e2e/gui-browser.test.js +1 -1
  58. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  59. package/dist/tests/e2e/gui-effects.test.d.ts +6 -0
  60. package/dist/tests/e2e/gui-effects.test.js +262 -0
  61. package/dist/tests/e2e/gui-effects.test.js.map +1 -0
  62. package/dist/tests/e2e/gui-example-webgl.test.d.ts +10 -0
  63. package/dist/tests/e2e/gui-example-webgl.test.js +75 -0
  64. package/dist/tests/e2e/gui-example-webgl.test.js.map +1 -0
  65. package/dist/tests/e2e/gui-example-webgl2.test.d.ts +8 -0
  66. package/dist/tests/e2e/gui-example-webgl2.test.js +72 -0
  67. package/dist/tests/e2e/gui-example-webgl2.test.js.map +1 -0
  68. package/dist/tests/e2e/gui-helpers.d.ts +7 -0
  69. package/dist/tests/e2e/gui-helpers.js +1 -0
  70. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  71. package/dist/tests/e2e/gui-layout.test.js +48 -0
  72. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  73. package/dist/tests/e2e/gui-media.test.js +83 -4
  74. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  75. package/dist/tests/e2e/gui-style.test.js +55 -0
  76. package/dist/tests/e2e/gui-style.test.js.map +1 -1
  77. package/dist/tests/e2e/gui-webgl.test.d.ts +11 -0
  78. package/dist/tests/e2e/gui-webgl.test.js +437 -0
  79. package/dist/tests/e2e/gui-webgl.test.js.map +1 -0
  80. package/dist/tests/e2e/gui-webgl2-3d.test.d.ts +4 -0
  81. package/dist/tests/e2e/gui-webgl2-3d.test.js +264 -0
  82. package/dist/tests/e2e/gui-webgl2-3d.test.js.map +1 -0
  83. package/dist/tests/e2e/gui-webgl2-blit.test.d.ts +10 -0
  84. package/dist/tests/e2e/gui-webgl2-blit.test.js +262 -0
  85. package/dist/tests/e2e/gui-webgl2-blit.test.js.map +1 -0
  86. package/dist/tests/e2e/gui-webgl2-formats.test.d.ts +6 -0
  87. package/dist/tests/e2e/gui-webgl2-formats.test.js +395 -0
  88. package/dist/tests/e2e/gui-webgl2-formats.test.js.map +1 -0
  89. package/dist/tests/e2e/gui-webgl2-mrt.test.d.ts +4 -0
  90. package/dist/tests/e2e/gui-webgl2-mrt.test.js +105 -0
  91. package/dist/tests/e2e/gui-webgl2-mrt.test.js.map +1 -0
  92. package/dist/tests/e2e/gui-webgl2-ubo.test.d.ts +8 -0
  93. package/dist/tests/e2e/gui-webgl2-ubo.test.js +112 -0
  94. package/dist/tests/e2e/gui-webgl2-ubo.test.js.map +1 -0
  95. package/dist/tests/e2e/gui-webgl2.test.d.ts +10 -0
  96. package/dist/tests/e2e/gui-webgl2.test.js +282 -0
  97. package/dist/tests/e2e/gui-webgl2.test.js.map +1 -0
  98. package/dist/tests/extensions/gui-index.test.js +1 -0
  99. package/dist/tests/extensions/gui-index.test.js.map +1 -1
  100. package/dist/tests/extensions/gui.test.js +41 -0
  101. package/dist/tests/extensions/gui.test.js.map +1 -1
  102. package/dist/tests/extensions/media.test.js +8 -3
  103. package/dist/tests/extensions/media.test.js.map +1 -1
  104. package/dist/tests/parser/expressions.test.js +30 -0
  105. package/dist/tests/parser/expressions.test.js.map +1 -1
  106. package/dist/tests/platform-tarball.test.d.ts +1 -0
  107. package/dist/tests/platform-tarball.test.js +111 -0
  108. package/dist/tests/platform-tarball.test.js.map +1 -0
  109. package/dist/tests/version.test.js +6 -0
  110. package/dist/tests/version.test.js.map +1 -1
  111. package/doc/ai/contributing.md +11 -0
  112. package/doc/ai/extensions.md +35 -12
  113. package/doc/ai/troubleshooting.md +17 -11
  114. package/doc/ai/zh-CN/contributing.md +9 -0
  115. package/doc/ai/zh-CN/extensions.md +30 -10
  116. package/doc/ai/zh-CN/troubleshooting.md +14 -8
  117. package/doc/gui/api.md +63 -14
  118. package/doc/gui/build.md +79 -16
  119. package/doc/gui/canvas.md +11 -3
  120. package/doc/gui/dom.md +35 -17
  121. package/doc/gui/known-issues.md +57 -24
  122. package/doc/gui/layout.md +18 -0
  123. package/doc/gui/media.md +21 -13
  124. package/doc/gui/rendering.md +50 -3
  125. package/doc/gui/webgl.md +315 -0
  126. package/doc/gui.md +113 -18
  127. package/doc/npm-package.md +59 -29
  128. package/doc/requirements.md +24 -11
  129. package/doc/zh-CN/gui/api.md +53 -11
  130. package/doc/zh-CN/gui/build.md +70 -16
  131. package/doc/zh-CN/gui/canvas.md +9 -2
  132. package/doc/zh-CN/gui/dom.md +22 -7
  133. package/doc/zh-CN/gui/known-issues.md +45 -19
  134. package/doc/zh-CN/gui/layout.md +14 -0
  135. package/doc/zh-CN/gui/media.md +18 -11
  136. package/doc/zh-CN/gui/rendering.md +41 -4
  137. package/doc/zh-CN/gui/webgl.md +121 -0
  138. package/doc/zh-CN/gui.md +91 -15
  139. package/doc/zh-CN/npm-package.md +48 -24
  140. package/doc/zh-CN/requirements.md +16 -7
  141. package/llms.txt +2 -0
  142. package/package.json +17 -1
  143. package/runtime/ext_gui/audio.cpp +365 -153
  144. package/runtime/ext_gui/audio_buffer.cpp +166 -0
  145. package/runtime/ext_gui/audio_buffer.h +37 -0
  146. package/runtime/ext_gui/audio_decode.cpp +219 -0
  147. package/runtime/ext_gui/audio_decode.h +39 -0
  148. package/runtime/ext_gui/audio_device.cpp +87 -0
  149. package/runtime/ext_gui/audio_effects.cpp +163 -0
  150. package/runtime/ext_gui/audio_effects.h +25 -0
  151. package/runtime/ext_gui/audio_internal.h +30 -0
  152. package/runtime/ext_gui/audio_mixer.cpp +494 -0
  153. package/runtime/ext_gui/audio_mixer.h +238 -0
  154. package/runtime/ext_gui/audio_param.cpp +104 -0
  155. package/runtime/ext_gui/audio_param_object.cpp +155 -0
  156. package/runtime/ext_gui/browser.cpp +50 -14
  157. package/runtime/ext_gui/canvas.cpp +3 -18
  158. package/runtime/ext_gui/canvas_internal.h +10 -0
  159. package/runtime/ext_gui/canvas_raster.cpp +60 -0
  160. package/runtime/ext_gui/clip_values.cpp +246 -0
  161. package/runtime/ext_gui/css.cpp +94 -0
  162. package/runtime/ext_gui/css.h +22 -1
  163. package/runtime/ext_gui/css_match.cpp +3 -0
  164. package/runtime/ext_gui/css_selector.cpp +30 -3
  165. package/runtime/ext_gui/css_sheet.cpp +89 -45
  166. package/runtime/ext_gui/document.cpp +4 -1
  167. package/runtime/ext_gui/document.h +2 -0
  168. package/runtime/ext_gui/dom.h +16 -0
  169. package/runtime/ext_gui/dom_api.cpp +1 -0
  170. package/runtime/ext_gui/dom_api_canvas.cpp +17 -80
  171. package/runtime/ext_gui/dom_api_canvas_extra.cpp +130 -1
  172. package/runtime/ext_gui/dom_api_canvas_pending.cpp +41 -0
  173. package/runtime/ext_gui/dom_api_internal.h +275 -0
  174. package/runtime/ext_gui/dom_api_webgl.cpp +383 -0
  175. package/runtime/ext_gui/dom_api_webgl2.cpp +549 -0
  176. package/runtime/ext_gui/dom_api_webgl2_draw.cpp +269 -0
  177. package/runtime/ext_gui/dom_api_webgl_buffer.cpp +216 -0
  178. package/runtime/ext_gui/dom_api_webgl_compressed.cpp +167 -0
  179. package/runtime/ext_gui/dom_api_webgl_framebuffer.cpp +160 -0
  180. package/runtime/ext_gui/dom_api_webgl_rt.h +30 -0
  181. package/runtime/ext_gui/dom_api_webgl_shader.cpp +443 -0
  182. package/runtime/ext_gui/dom_api_webgl_source.cpp +161 -0
  183. package/runtime/ext_gui/dom_api_webgl_state.cpp +590 -0
  184. package/runtime/ext_gui/dom_api_webgl_texture.cpp +485 -0
  185. package/runtime/ext_gui/dom_api_webgl_ubo.cpp +81 -0
  186. package/runtime/ext_gui/effect_values.cpp +312 -0
  187. package/runtime/ext_gui/gradient_values.cpp +476 -0
  188. package/runtime/ext_gui/grid_values.cpp +230 -0
  189. package/runtime/ext_gui/gui.cpp +44 -9
  190. package/runtime/ext_gui/gui_canvas.cpp +50 -2
  191. package/runtime/ext_gui/gui_engine.h +36 -4
  192. package/runtime/ext_gui/gui_events.cpp +4 -1
  193. package/runtime/ext_gui/gui_media.cpp +1 -1
  194. package/runtime/ext_gui/gui_webgl.cpp +83 -0
  195. package/runtime/ext_gui/image_bitmap.cpp +28 -0
  196. package/runtime/ext_gui/image_bitmap.h +26 -0
  197. package/runtime/ext_gui/image_bitmap_browser.cpp +136 -0
  198. package/runtime/ext_gui/layout.cpp +21 -0
  199. package/runtime/ext_gui/layout_flow.cpp +81 -4
  200. package/runtime/ext_gui/layout_flow_internal.h +31 -0
  201. package/runtime/ext_gui/layout_grid.cpp +374 -0
  202. package/runtime/ext_gui/media.cpp +32 -0
  203. package/runtime/ext_gui/media.h +14 -1
  204. package/runtime/ext_gui/media_api.h +25 -2
  205. package/runtime/ext_gui/media_query.cpp +211 -0
  206. package/runtime/ext_gui/paint.cpp +111 -77
  207. package/runtime/ext_gui/paint.h +33 -3
  208. package/runtime/ext_gui/paint_background.cpp +229 -0
  209. package/runtime/ext_gui/paint_clip.cpp +268 -0
  210. package/runtime/ext_gui/paint_effects.cpp +324 -0
  211. package/runtime/ext_gui/paint_internal.h +85 -0
  212. package/runtime/ext_gui/renderer.cpp +8 -31
  213. package/runtime/ext_gui/renderer_clip.cpp +196 -0
  214. package/runtime/ext_gui/renderer_image.cpp +13 -8
  215. package/runtime/ext_gui/renderer_internal.h +15 -8
  216. package/runtime/ext_gui/renderer_text.cpp +6 -31
  217. package/runtime/ext_gui/style.cpp +343 -20
  218. package/runtime/ext_gui/style.h +144 -3
  219. package/runtime/ext_gui/style_print.cpp +134 -1
  220. package/runtime/ext_gui/style_values.cpp +152 -126
  221. package/runtime/ext_gui/style_values_internal.h +35 -0
  222. package/runtime/ext_gui/webgl.h +600 -0
  223. package/runtime/ext_gui/webgl2_constants.h +274 -0
  224. package/runtime/ext_gui/webgl_blit.cpp +95 -0
  225. package/runtime/ext_gui/webgl_buffer.cpp +480 -0
  226. package/runtime/ext_gui/webgl_buffer_objects.cpp +270 -0
  227. package/runtime/ext_gui/webgl_clear.cpp +142 -0
  228. package/runtime/ext_gui/webgl_compressed.cpp +180 -0
  229. package/runtime/ext_gui/webgl_constants.h +317 -0
  230. package/runtime/ext_gui/webgl_context.cpp +551 -0
  231. package/runtime/ext_gui/webgl_draw.cpp +560 -0
  232. package/runtime/ext_gui/webgl_draw_buffers.cpp +75 -0
  233. package/runtime/ext_gui/webgl_glsl.h +165 -0
  234. package/runtime/ext_gui/webgl_glsl_compile.cpp +437 -0
  235. package/runtime/ext_gui/webgl_glsl_rewrite.cpp +367 -0
  236. package/runtime/ext_gui/webgl_glsl_scan.cpp +590 -0
  237. package/runtime/ext_gui/webgl_glsl_scan_block.cpp +126 -0
  238. package/runtime/ext_gui/webgl_glsl_tokens.h +28 -0
  239. package/runtime/ext_gui/webgl_msl.cpp +62 -0
  240. package/runtime/ext_gui/webgl_msl.h +33 -0
  241. package/runtime/ext_gui/webgl_pipeline.cpp +326 -0
  242. package/runtime/ext_gui/webgl_program_blocks.cpp +58 -0
  243. package/runtime/ext_gui/webgl_program_internal.h +24 -0
  244. package/runtime/ext_gui/webgl_shader.cpp +594 -0
  245. package/runtime/ext_gui/webgl_state.cpp +228 -0
  246. package/runtime/ext_gui/webgl_texture.cpp +420 -0
  247. package/runtime/ext_gui/webgl_texture_3d.cpp +215 -0
  248. package/runtime/ext_gui/webgl_uniform.cpp +167 -0
  249. package/runtime/ext_gui/webgl_uniform_block.h +31 -0
  250. package/runtime/ext_gui/webgl_webgl2.cpp +277 -0
  251. package/runtime/ext_media/decode_audio.cpp +308 -0
  252. package/runtime/ext_media/media.cpp +1 -1
  253. package/runtime/ext_media/media_bridge.c +11 -3
  254. package/runtime/rt.h +17 -0
  255. package/runtime/rt_internal.h +1 -0
  256. package/runtime/xt_array_buffer.c +30 -0
  257. package/runtime/xt_containers/access.inc +8 -0
  258. package/runtime/xt_regex/api.inc +2 -0
  259. package/runtime/xt_regex/matcher.inc +16 -3
  260. package/runtime/xt_regex/parser.inc +8 -2
  261. package/runtime/xt_regex/types.inc +3 -0
  262. package/runtime/xt_stdlib2/regexp-match.inc +60 -21
  263. package/runtime/xt_stdlib2/regexp.inc +1 -0
  264. package/runtime/xt_typed_array/elements.inc +13 -0
  265. package/runtime/xt_typed_array.c +1 -1
  266. package/scripts/build-gui.ts +90 -3
  267. package/scripts/build-media.ts +335 -62
  268. package/scripts/build-runtime.ts +21 -16
  269. package/scripts/check-version.ts +39 -9
  270. package/scripts/package-platform.ts +183 -0
  271. package/scripts/set-platform-version.ts +62 -0
  272. package/scripts/setup-vs-toolchain.ps1 +152 -0
  273. package/scripts/verify-platform-package.ts +182 -0
  274. package/src/binder/references.ts +13 -1
  275. package/src/cli/main.ts +5 -1
  276. package/src/codegen/generator/module.ts +50 -0
  277. package/src/codegen/generator/statements/variables.ts +6 -3
  278. package/src/driver/runtime-lib.ts +73 -9
  279. package/src/driver/version.ts +1 -1
  280. package/src/extensions/gui/css.ts +113 -0
  281. package/src/extensions/gui/html.ts +9 -2
  282. package/src/extensions/gui/index.ts +22 -7
  283. package/src/extensions/gui/three.ts +25 -0
  284. package/src/extensions/media/index.ts +16 -18
  285. package/src/parser/context.ts +99 -4
  286. package/src/parser/literals.ts +6 -12
@@ -7,7 +7,7 @@
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
9
  * separate `media` compile extension (`runtime/ext_media/`, linked as
10
- * `media.a`). Keeping the boundary C-only means the backend can be a prebuilt
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
@@ -40,10 +40,30 @@ typedef XtMediaPlayerHandle (*XtMediaByIdFn)(uint64_t id);
40
40
  /** Tear down every decoder and process-wide media state. */
41
41
  typedef void (*XtMediaShutdownFn)(void);
42
42
 
43
+ /** Interleaved float PCM returned by the memory decoder.
44
+ *
45
+ * `samples` is allocated by the backend and must be released with the table's
46
+ * `free_audio` entry point (both archives use the same static CRT). */
47
+ typedef struct XtMediaAudioBuffer {
48
+ int channels;
49
+ int sample_rate;
50
+ int frames;
51
+ float *samples;
52
+ } XtMediaAudioBuffer;
53
+
54
+ /** Decode an in-memory audio file (`decodeAudioData`). Returns 1 on success and
55
+ * fills `out`; on failure returns 0 and copies a message into `error`. */
56
+ typedef int (*XtMediaDecodeAudioFn)(const unsigned char *data, size_t size,
57
+ XtMediaAudioBuffer *out, char *error, size_t error_size);
58
+ /** Release the `samples` a successful `decode_audio` allocated. */
59
+ typedef void (*XtMediaFreeAudioFn)(void *samples);
60
+
43
61
  typedef struct XtMediaApi {
44
62
  XtMediaOpenFn open;
45
63
  XtMediaByIdFn by_id;
46
64
  XtMediaShutdownFn shutdown;
65
+ XtMediaDecodeAudioFn decode_audio;
66
+ XtMediaFreeAudioFn free_audio;
47
67
  } XtMediaApi;
48
68
 
49
69
  /** Install a decoder table. Implemented by the GUI engine; called once by the
@@ -51,12 +71,15 @@ typedef struct XtMediaApi {
51
71
  void xt_media_install(const XtMediaApi *api);
52
72
 
53
73
  /** Decoder backend entry points, implemented by `runtime/ext_media/media.cpp`
54
- * (inside `media.a`). The bridge references them, which is what makes the
74
+ * (inside `media.a`/`media.lib`). The bridge references them, which is what makes the
55
75
  * linker extract the archive member when the media extension is enabled. */
56
76
  XtMediaPlayerHandle xt_media_backend_open(const char *path, char *error,
57
77
  size_t error_size, void *audio_sink);
58
78
  XtMediaPlayerHandle xt_media_backend_by_id(uint64_t id);
59
79
  void xt_media_backend_shutdown(void);
80
+ int xt_media_backend_decode_audio(const unsigned char *data, size_t size,
81
+ XtMediaAudioBuffer *out, char *error, size_t error_size);
82
+ void xt_media_backend_free_audio(void *samples);
60
83
 
61
84
  #ifdef __cplusplus
62
85
  }
@@ -0,0 +1,211 @@
1
+ /*
2
+ * xbintsc GUI engine — `@media` query evaluation.
3
+ *
4
+ * The CSS parser keeps each rule's raw media prelude and this unit decides
5
+ * whether it applies to the current viewport. The subset mirrors what a desktop
6
+ * window can answer: width/height ranges, orientation, hover/pointer class and
7
+ * the two accessibility preferences. Unknown features evaluate to false, which
8
+ * is what a browser does (and keeps e.g. `@media print` blocks inert).
9
+ */
10
+ #include "css_internal.h"
11
+
12
+ #include <cmath>
13
+ #include <string>
14
+ #include <vector>
15
+
16
+ namespace xtgui {
17
+
18
+ namespace {
19
+
20
+ std::string lowerTrim(const std::string &text) { return xt_css_lower(xt_css_trim(text)); }
21
+
22
+ /** Split `text` on the word `and` at nesting depth zero. */
23
+ std::vector<std::string> splitAnd(const std::string &text) {
24
+ std::vector<std::string> parts;
25
+ std::string current;
26
+ int depth = 0;
27
+ for (size_t i = 0; i < text.size();) {
28
+ char c = text[i];
29
+ if (c == '(') depth++;
30
+ else if (c == ')') depth--;
31
+ bool atWord = (c == 'a' || c == 'A') && i + 3 <= text.size() &&
32
+ xt_css_lower(text.substr(i, 3)) == "and" &&
33
+ (i == 0 || xt_is_css_space(text[i - 1])) &&
34
+ (i + 3 == text.size() || xt_is_css_space(text[i + 3]));
35
+ if (atWord && depth == 0) {
36
+ std::string part = xt_css_trim(current);
37
+ if (!part.empty()) parts.push_back(part);
38
+ current.clear();
39
+ i += 3;
40
+ continue;
41
+ }
42
+ current.push_back(c);
43
+ i++;
44
+ }
45
+ std::string part = xt_css_trim(current);
46
+ if (!part.empty()) parts.push_back(part);
47
+ return parts;
48
+ }
49
+
50
+ /** Resolve a media-feature length. `rem`/`em` assume the 16px initial font. */
51
+ bool mediaLength(const std::string &raw, float vw, float vh, float *out) {
52
+ Length length = xt_css_parse_length(raw);
53
+ switch (length.unit) {
54
+ case Unit::Px:
55
+ case Unit::Number:
56
+ *out = length.value;
57
+ return true;
58
+ case Unit::Em:
59
+ case Unit::Rem:
60
+ *out = length.value * 16.0f;
61
+ return true;
62
+ case Unit::Vw:
63
+ *out = vw * length.value / 100.0f;
64
+ return true;
65
+ case Unit::Vh:
66
+ *out = vh * length.value / 100.0f;
67
+ return true;
68
+ default:
69
+ return false;
70
+ }
71
+ }
72
+
73
+ /** Compare `actual` with a feature value, honouring a min-/max- prefix. */
74
+ bool compareLength(const std::string &name, const std::string &rawValue, float actual, float vw,
75
+ float vh) {
76
+ float value = 0.0f;
77
+ if (!mediaLength(rawValue, vw, vh, &value)) return false;
78
+ const bool min = name.rfind("min-", 0) == 0;
79
+ const bool max = name.rfind("max-", 0) == 0;
80
+ if (min) return actual >= value;
81
+ if (max) return actual <= value;
82
+ return std::fabs(actual - value) < 0.01f;
83
+ }
84
+
85
+ bool splitFeature(const std::string &inner, std::string *name, std::string *value) {
86
+ int depth = 0;
87
+ for (size_t i = 0; i < inner.size(); i++) {
88
+ char c = inner[i];
89
+ if (c == '(') depth++;
90
+ else if (c == ')') depth--;
91
+ else if (c == ':' && depth == 0) {
92
+ *name = lowerTrim(inner.substr(0, i));
93
+ *value = lowerTrim(inner.substr(i + 1));
94
+ return true;
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+
100
+ /** `(width >= 600px)` / `(600px <= width)` range syntax. */
101
+ bool evaluateRange(const std::string &inner, float vw, float vh, bool *matched) {
102
+ static const char *const ops[] = {">=", "<=", ">", "<", "="};
103
+ for (const char *op : ops) {
104
+ size_t pos = inner.find(op);
105
+ if (pos == std::string::npos) continue;
106
+ std::string left = lowerTrim(inner.substr(0, pos));
107
+ std::string right = lowerTrim(inner.substr(pos + std::string(op).size()));
108
+ std::string opText(op);
109
+ /* `600px < width` means width > 600px: flip the comparison. */
110
+ bool flipped = left != "width" && left != "height";
111
+ std::string feature = flipped ? right : left;
112
+ std::string valueText = flipped ? left : right;
113
+ float actual = 0.0f;
114
+ if (feature == "width") actual = vw;
115
+ else if (feature == "height") actual = vh;
116
+ else return false;
117
+ float value = 0.0f;
118
+ if (!mediaLength(valueText, vw, vh, &value)) return false;
119
+ if (flipped) {
120
+ if (opText == ">") opText = "<";
121
+ else if (opText == "<") opText = ">";
122
+ else if (opText == ">=") opText = "<=";
123
+ else if (opText == "<=") opText = ">=";
124
+ }
125
+ if (opText == ">=") *matched = actual >= value;
126
+ else if (opText == "<=") *matched = actual <= value;
127
+ else if (opText == ">") *matched = actual > value;
128
+ else if (opText == "<") *matched = actual < value;
129
+ else *matched = std::fabs(actual - value) < 0.01f;
130
+ return true;
131
+ }
132
+ return false;
133
+ }
134
+
135
+ bool evaluateFeature(const std::string &inner, float vw, float vh) {
136
+ bool rangeMatched = false;
137
+ if (evaluateRange(inner, vw, vh, &rangeMatched)) return rangeMatched;
138
+
139
+ std::string name;
140
+ std::string value;
141
+ if (!splitFeature(inner, &name, &value)) return false;
142
+ if (name.empty() || value.empty()) return false;
143
+
144
+ if (name == "width" || name == "min-width" || name == "max-width") {
145
+ return compareLength(name, value, vw, vw, vh);
146
+ }
147
+ if (name == "height" || name == "min-height" || name == "max-height") {
148
+ return compareLength(name, value, vh, vw, vh);
149
+ }
150
+ if (name == "orientation") {
151
+ if (value == "landscape") return vw >= vh;
152
+ if (value == "portrait") return vh > vw;
153
+ return false;
154
+ }
155
+ if (name == "hover" || name == "any-hover") return value == "hover";
156
+ if (name == "pointer" || name == "any-pointer") return value == "fine";
157
+ if (name == "prefers-color-scheme") return value == "light";
158
+ if (name == "prefers-reduced-motion") return value == "no-preference";
159
+ if (name == "prefers-contrast") return value == "no-preference";
160
+ if (name == "scripting") return value == "enabled";
161
+ if (name == "display-mode") return value == "browser";
162
+ return false;
163
+ }
164
+
165
+ } // namespace
166
+
167
+ bool xt_css_media_matches(const std::string &query, float viewport_width, float viewport_height) {
168
+ std::string text = lowerTrim(query);
169
+ if (text.empty()) return true;
170
+
171
+ bool any = false;
172
+ for (const std::string &rawQuery : xt_css_split_commas(text)) {
173
+ std::string part = lowerTrim(rawQuery);
174
+ bool negate = false;
175
+ if (part.rfind("not ", 0) == 0) {
176
+ negate = true;
177
+ part = xt_css_trim(part.substr(4));
178
+ } else if (part.rfind("only ", 0) == 0) {
179
+ part = xt_css_trim(part.substr(5));
180
+ }
181
+ bool matched = true;
182
+ bool first = true;
183
+ for (const std::string &segment : splitAnd(part)) {
184
+ std::string token = xt_css_trim(segment);
185
+ if (token.empty()) continue;
186
+ if (first && token[0] != '(') {
187
+ /* Media type: a desktop window is `screen`, never `print`. */
188
+ if (token == "all" || token == "screen") {
189
+ matched = true;
190
+ } else {
191
+ matched = false;
192
+ }
193
+ } else {
194
+ std::string inner = token;
195
+ if (inner.front() == '(' && inner.back() == ')') {
196
+ inner = xt_css_trim(inner.substr(1, inner.size() - 2));
197
+ }
198
+ if (!evaluateFeature(inner, viewport_width, viewport_height)) matched = false;
199
+ }
200
+ first = false;
201
+ }
202
+ if (negate) matched = !matched;
203
+ if (matched) {
204
+ any = true;
205
+ break;
206
+ }
207
+ }
208
+ return any;
209
+ }
210
+
211
+ } // namespace xtgui
@@ -1,5 +1,9 @@
1
1
  /*
2
2
  * xbintsc GUI engine — display list construction.
3
+ *
4
+ * The tree walk lives here. Background layers are in `paint_background.cpp`,
5
+ * filter/shadow approximations in `paint_effects.cpp` and `clip-path`
6
+ * triangulation in `paint_clip.cpp`.
3
7
  */
4
8
 
5
9
  #include "paint.h"
@@ -8,6 +12,7 @@
8
12
  #include <cstdio>
9
13
  #include <utility>
10
14
 
15
+ #include "paint_internal.h"
11
16
  #include "style.h"
12
17
 
13
18
  namespace xtgui {
@@ -43,50 +48,14 @@ FontSpec specOf(const XtStyle &style) {
43
48
  return spec;
44
49
  }
45
50
 
46
- void addRect(DisplayList &out, float x, float y, float width, float height, float radius,
47
- const Color &color, const PaintClip &clip, const Matrix2D &transform) {
48
- if (width <= 0.0f || height <= 0.0f || color.a <= 0.0f) return;
49
- PaintRect rect;
50
- rect.x = x;
51
- rect.y = y;
52
- rect.width = width;
53
- rect.height = height;
54
- rect.radius = radius;
55
- rect.color = color;
56
- rect.transform = transform;
57
- out.rects.push_back(rect);
58
- out.ops.push_back(PaintOp{PaintKind::Rect, (uint32_t)(out.rects.size() - 1), clip});
59
- }
60
-
61
- void addTextRuns(const LayoutBox *box, const XtStyle &style, DisplayList &out,
62
- const PaintClip &clip) {
63
- if (box->fragments.empty() || style.color.a <= 0.0f) return;
64
- FontSpec spec = specOf(style);
65
- float ascent = 0;
66
- xt_text_metrics(spec, &ascent, nullptr, nullptr);
67
- if (ascent <= 0.0f) ascent = style.font_size * 0.8f;
68
- for (const TextFragment &fragment : box->fragments) {
69
- if (fragment.text.empty()) continue;
70
- PaintText run;
71
- run.x = fragment.x;
72
- run.baseline = fragment.y + ascent;
73
- run.width = fragment.width;
74
- run.height = fragment.height;
75
- run.color = style.color;
76
- run.text = fragment.text;
77
- run.font = spec;
78
- run.transform = box->world;
79
- out.texts.push_back(std::move(run));
80
- out.ops.push_back(PaintOp{PaintKind::Text, (uint32_t)(out.texts.size() - 1), clip});
81
- }
82
- }
83
-
84
- void addImage(const PaintImage &image, DisplayList &out, const PaintClip &clip) {
85
- out.images.push_back(image);
86
- out.ops.push_back(PaintOp{PaintKind::Image, (uint32_t)(out.images.size() - 1), clip});
51
+ int zIndexOf(const LayoutBox *box) {
52
+ if (box->style == nullptr || !box->style->has_z_index) return 0;
53
+ return box->style->z_index;
87
54
  }
88
55
 
89
- void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip);
56
+ void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip,
57
+ const paint_detail::FilterChain &parentChain, float viewportWidth,
58
+ float viewportHeight);
90
59
 
91
60
  /** The clip a box imposes on its content: its padding box, intersected with the
92
61
  * clip already in effect. Only `overflow: hidden/scroll/auto` clips. A
@@ -106,23 +75,32 @@ PaintClip clipForChildren(const LayoutBox *box, const PaintClip &parent) {
106
75
  width = bounds.width;
107
76
  height = bounds.height;
108
77
  }
109
- if (!parent.active) return PaintClip{true, x, y, width, height};
78
+ PaintClip result = parent;
79
+ result.active = true;
80
+ if (!parent.active) {
81
+ result.x = x;
82
+ result.y = y;
83
+ result.width = width;
84
+ result.height = height;
85
+ return result;
86
+ }
110
87
  float x0 = std::max(x, parent.x);
111
88
  float y0 = std::max(y, parent.y);
112
89
  float x1 = std::min(x + width, parent.x + parent.width);
113
90
  float y1 = std::min(y + height, parent.y + parent.height);
114
- return PaintClip{true, x0, y0, std::max(0.0f, x1 - x0), std::max(0.0f, y1 - y0)};
115
- }
116
-
117
- int zIndexOf(const LayoutBox *box) {
118
- if (box->style == nullptr || !box->style->has_z_index) return 0;
119
- return box->style->z_index;
91
+ result.x = x0;
92
+ result.y = y0;
93
+ result.width = std::max(0.0f, x1 - x0);
94
+ result.height = std::max(0.0f, y1 - y0);
95
+ return result;
120
96
  }
121
97
 
122
98
  /** Paint `box`'s children following z-index: negative z first (ascending), then
123
99
  * auto/zero in document order, then positive z (ascending), so higher stacks
124
100
  * paint last. */
125
- void paintChildren(const LayoutBox *box, DisplayList &out, const PaintClip &clip) {
101
+ void paintChildren(const LayoutBox *box, DisplayList &out, const PaintClip &clip,
102
+ const paint_detail::FilterChain &chain, float viewportWidth,
103
+ float viewportHeight) {
126
104
  std::vector<const LayoutBox *> children;
127
105
  for (const std::unique_ptr<LayoutBox> &child : box->children) {
128
106
  if (child->display == Display::None || child->style == nullptr) continue;
@@ -131,41 +109,78 @@ void paintChildren(const LayoutBox *box, DisplayList &out, const PaintClip &clip
131
109
  std::stable_sort(children.begin(), children.end(), [](const LayoutBox *a, const LayoutBox *b) {
132
110
  return zIndexOf(a) < zIndexOf(b);
133
111
  });
134
- for (const LayoutBox *child : children) paintBox(child, out, clip);
112
+ for (const LayoutBox *child : children)
113
+ paintBox(child, out, clip, chain, viewportWidth, viewportHeight);
135
114
  }
136
115
 
137
- void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip) {
116
+ void addTextRuns(const LayoutBox *box, const XtStyle &style, DisplayList &out,
117
+ const PaintClip &clip, const paint_detail::FilterChain &chain,
118
+ float viewportWidth, float viewportHeight) {
119
+ if (box->fragments.empty() || style.color.a <= 0.0f) return;
120
+ FontSpec spec = specOf(style);
121
+ float ascent = 0;
122
+ xt_text_metrics(spec, &ascent, nullptr, nullptr);
123
+ if (ascent <= 0.0f) ascent = style.font_size * 0.8f;
124
+ Color base = paint_detail::filterColor(chain, style.color);
125
+ for (const TextFragment &fragment : box->fragments) {
126
+ if (fragment.text.empty()) continue;
127
+ paint_detail::addTextRun(out, fragment.x, fragment.y + ascent, fragment.width,
128
+ fragment.height, fragment.text, spec, base, box->world, clip, style,
129
+ chain, viewportWidth, viewportHeight);
130
+ }
131
+ }
132
+
133
+ void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip,
134
+ const paint_detail::FilterChain &parentChain, float viewportWidth,
135
+ float viewportHeight) {
138
136
  if (box == nullptr) return;
139
137
  const XtStyle *style = box->style;
140
138
 
141
- /* A fully transparent box paints nothing — not itself, and not its subtree. */
142
- if (style != nullptr && style->opacity <= 0.0f) return;
139
+ /* A fully transparent or hidden box paints nothing — not itself, and not its
140
+ * subtree. */
141
+ if (style != nullptr && (style->opacity <= 0.0f || !style->visible)) return;
142
+
143
+ /* A text box points at its parent element's style; its filters were already
144
+ * folded into `parentChain`, so applying them again would double them. */
145
+ paint_detail::FilterChain chain = parentChain;
146
+ if (style != nullptr && !box->is_text && !style->filters.empty()) {
147
+ chain = paint_detail::extendFilter(parentChain, style->filters, *style, viewportWidth,
148
+ viewportHeight);
149
+ }
150
+
151
+ PaintClip own = clip;
152
+ if (style != nullptr && box->display != Display::None && !box->is_text) {
153
+ if (style->clip_path.type != ClipPathSpec::Type::None) {
154
+ int index = paint_detail::addClipPathForBox(out, *box, *style, viewportWidth, viewportHeight);
155
+ own = paint_detail::addClipPath(own, index);
156
+ }
157
+ }
143
158
 
144
159
  if (style != nullptr && box->display != Display::None) {
145
160
  if (box->is_text) {
146
- addTextRuns(box, *style, out, clip);
161
+ addTextRuns(box, *style, out, own, chain, viewportWidth, viewportHeight);
147
162
  } else {
148
- /* Background first, then the border ring, then replaced content. Children
149
- * are clipped by this box's padding box when it establishes one. Every
150
- * shape carries the box's `transform` matrix for the renderer. */
163
+ /* Drop shadows sit behind the box's own background but above earlier
164
+ * siblings. */
165
+ paint_detail::addBoxDropShadows(out, *box, *style, chain, own);
166
+ paint_detail::paintBackgrounds(out, *box, *style, chain, own, viewportWidth,
167
+ viewportHeight);
151
168
  const Matrix2D &matrix = box->world;
152
- if (style->has_background) {
153
- addRect(out, box->x, box->y, box->width, box->height, style->border_radius,
154
- style->background, clip, matrix);
155
- }
156
169
  if (style->border_style != BorderStyle::None && style->has_border_color) {
157
- const Color bc = style->border_color;
170
+ const Color bc = paint_detail::filterColor(chain, style->border_color);
158
171
  float top = box->border_top;
159
172
  float right = box->border_right;
160
173
  float bottom = box->border_bottom;
161
174
  float left = box->border_left;
162
175
  float inner_height = box->height - top - bottom;
163
- addRect(out, box->x, box->y, box->width, top, 0.0f, bc, clip, matrix);
164
- addRect(out, box->x, box->y + box->height - bottom, box->width, bottom, 0.0f, bc, clip,
165
- matrix);
166
- addRect(out, box->x, box->y + top, left, inner_height, 0.0f, bc, clip, matrix);
167
- addRect(out, box->x + box->width - right, box->y + top, right, inner_height, 0.0f, bc,
168
- clip, matrix);
176
+ paint_detail::addRectFiltered(out, box->x, box->y, box->width, top, 0.0f, bc, own,
177
+ matrix, chain);
178
+ paint_detail::addRectFiltered(out, box->x, box->y + box->height - bottom, box->width,
179
+ bottom, 0.0f, bc, own, matrix, chain);
180
+ paint_detail::addRectFiltered(out, box->x, box->y + top, left, inner_height, 0.0f, bc,
181
+ own, matrix, chain);
182
+ paint_detail::addRectFiltered(out, box->x + box->width - right, box->y + top, right,
183
+ inner_height, 0.0f, bc, own, matrix, chain);
169
184
  }
170
185
  if (box->is_canvas && box->canvas_id != 0 && box->content_width > 0.0f &&
171
186
  box->content_height > 0.0f) {
@@ -176,7 +191,7 @@ void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip) {
176
191
  image.height = box->content_height;
177
192
  image.canvas_id = box->canvas_id;
178
193
  image.transform = matrix;
179
- addImage(image, out, clip);
194
+ paint_detail::addImage(out, image, own, chain);
180
195
  } else if (box->is_media && box->media_id != 0 && box->content_width > 0.0f &&
181
196
  box->content_height > 0.0f) {
182
197
  PaintImage image;
@@ -186,7 +201,7 @@ void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip) {
186
201
  image.height = box->content_height;
187
202
  image.media_id = box->media_id;
188
203
  image.transform = matrix;
189
- addImage(image, out, clip);
204
+ paint_detail::addImage(out, image, own, chain);
190
205
  } else if (box->is_inline_svg && !box->svg_markup.empty() && box->content_width > 0.0f &&
191
206
  box->content_height > 0.0f) {
192
207
  PaintImage image;
@@ -196,7 +211,7 @@ void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip) {
196
211
  image.height = box->content_height;
197
212
  image.svg = box->svg_markup;
198
213
  image.transform = matrix;
199
- addImage(image, out, clip);
214
+ paint_detail::addImage(out, image, own, chain);
200
215
  } else if (box->is_image && !box->image_src.empty() && box->content_width > 0.0f &&
201
216
  box->content_height > 0.0f) {
202
217
  PaintImage image;
@@ -206,24 +221,34 @@ void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip) {
206
221
  image.height = box->content_height;
207
222
  image.src = box->image_src;
208
223
  image.transform = matrix;
209
- addImage(image, out, clip);
224
+ paint_detail::addImage(out, image, own, chain);
210
225
  }
211
226
  }
212
227
  }
213
228
 
214
- paintChildren(box, out, clipForChildren(box, clip));
229
+ /* Drop shadows belong to the box that declared them: its silhouette has
230
+ * already been emitted, so descendants must not repeat it. */
231
+ paint_detail::FilterChain childChain = chain;
232
+ childChain.shadows.clear();
233
+ paintChildren(box, out, clipForChildren(box, own), childChain, viewportWidth, viewportHeight);
215
234
  }
216
235
 
217
236
  } // namespace
218
237
 
219
- void xt_paint_build(const LayoutBox *root, DisplayList &out) {
238
+ void xt_paint_build(const LayoutBox *root, DisplayList &out, float viewportWidth,
239
+ float viewportHeight) {
220
240
  PaintClip none;
221
- paintBox(root, out, none);
241
+ paint_detail::FilterChain chain;
242
+ if (root != nullptr && viewportWidth <= 0.0f) {
243
+ viewportWidth = root->cb_width;
244
+ viewportHeight = root->cb_height;
245
+ }
246
+ paintBox(root, out, none, chain, viewportWidth, viewportHeight);
222
247
  }
223
248
 
224
249
  std::string DisplayList::dump() const {
225
250
  std::string text;
226
- char buffer[288];
251
+ char buffer[320];
227
252
  for (const PaintOp &op : ops) {
228
253
  std::string prefix;
229
254
  if (op.clip.active) {
@@ -231,6 +256,14 @@ std::string DisplayList::dump() const {
231
256
  op.clip.width, op.clip.height);
232
257
  prefix = buffer;
233
258
  }
259
+ if (op.clip.path_count > 0) {
260
+ prefix += "clip-path=";
261
+ for (int i = 0; i < op.clip.path_count; i++) {
262
+ if (i > 0) prefix += ",";
263
+ prefix += std::to_string(op.clip.paths[i]);
264
+ }
265
+ prefix += " ";
266
+ }
234
267
  std::string suffix;
235
268
  if (op.kind == PaintKind::Rect) {
236
269
  suffix = transformSuffix(rects[op.index].transform);
@@ -246,6 +279,7 @@ std::string DisplayList::dump() const {
246
279
  "text x=%.1f baseline=%.1f size=%.1f color=%s \"%s\"", run.x, run.baseline,
247
280
  run.font.pixel_size, formatColor(run.color).c_str(), run.text.c_str());
248
281
  text += prefix + buffer;
282
+ if (run.shadow) text += " shadow=1";
249
283
  } else {
250
284
  const PaintImage &image = images[op.index];
251
285
  suffix = transformSuffix(image.transform);
@@ -46,6 +46,9 @@ struct PaintText {
46
46
  std::string text;
47
47
  FontSpec font;
48
48
  Matrix2D transform;
49
+ /** Set on a `text-shadow` copy (diagnostics only; the renderer draws it like
50
+ * any other glyph run). */
51
+ bool shadow = false;
49
52
  };
50
53
 
51
54
  /** An image to draw, scaled into the given rectangle (viewport-relative).
@@ -65,17 +68,38 @@ struct PaintImage {
65
68
  uint64_t media_id = 0;
66
69
  uint64_t canvas_id = 0;
67
70
  Matrix2D transform;
71
+ /** Modulating colour for the image shader (white normally; a filter's
72
+ * multiplicative effect may tint it). */
73
+ Color tint{1.0f, 1.0f, 1.0f, 1.0f};
74
+ };
75
+
76
+ /** One triangle of a clip path in device-space logical pixels. The paint pass
77
+ * triangulates (possibly concave) polygons once so the renderer can clip
78
+ * convex shape/glyph quads by a simple half-plane test per triangle. */
79
+ struct ClipTriangle {
80
+ float x[3] = {0, 0, 0};
81
+ float y[3] = {0, 0, 0};
82
+ };
83
+
84
+ /** A resolved `clip-path` polygon plus its triangulation. */
85
+ struct ClipPathGeometry {
86
+ std::vector<float> points; // x, y pairs (diagnostics)
87
+ std::vector<ClipTriangle> triangles;
68
88
  };
69
89
 
70
90
  /** The clip rectangle in effect for a painted shape (device-space logical
71
91
  * pixels — a transformed subtree contributes the bounding box of its clip).
72
- * `width <= 0` means "no clip". */
92
+ * `width <= 0` means "no clip". `paths` index `DisplayList::clip_paths`; at most
93
+ * two nested `clip-path` shapes are kept (CSS would intersect every ancestor;
94
+ * deeper nesting is rare and the outermost two are the ones a UI uses). */
73
95
  struct PaintClip {
74
96
  bool active = false;
75
97
  float x = 0;
76
98
  float y = 0;
77
99
  float width = 0;
78
100
  float height = 0;
101
+ int path_count = 0;
102
+ int paths[2] = {-1, -1};
79
103
  };
80
104
 
81
105
  enum class PaintKind { Rect, Text, Image };
@@ -95,20 +119,26 @@ struct DisplayList {
95
119
  /** Shapes in painter order (backgrounds/borders first, then children, then
96
120
  * stacked siblings), each with the clip rectangle that bounds it. */
97
121
  std::vector<PaintOp> ops;
122
+ /** Device-space clip-path polygons shared by an element's ops. */
123
+ std::vector<ClipPathGeometry> clip_paths;
98
124
 
99
125
  void clear() {
100
126
  rects.clear();
101
127
  texts.clear();
102
128
  images.clear();
103
129
  ops.clear();
130
+ clip_paths.clear();
104
131
  }
105
132
  bool empty() const { return rects.empty() && texts.empty() && images.empty(); }
106
133
  /** Human-readable dump, one shape per line, in painter order (diagnostics/tests). */
107
134
  std::string dump() const;
108
135
  };
109
136
 
110
- /** Build the display list for a laid-out tree and append it to `out`. */
111
- void xt_paint_build(const LayoutBox *root, DisplayList &out);
137
+ /** Build the display list for a laid-out tree and append it to `out`. The
138
+ * viewport size resolves `vw`/`vh` units inside `clip-path` and shadows; pass 0
139
+ * to take it from the root box's containing block. */
140
+ void xt_paint_build(const LayoutBox *root, DisplayList &out, float viewportWidth = 0.0f,
141
+ float viewportHeight = 0.0f);
112
142
 
113
143
  } // namespace xtgui
114
144