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
@@ -77,7 +77,8 @@ xtgui::Canvas2D *xt_gui_canvas_get(XtGuiWindow *win, const xtgui::Node *node) {
77
77
 
78
78
  void xt_gui_canvas_forget(XtGuiWindow *win, const xtgui::Node *node) {
79
79
  if (win == nullptr || node == nullptr) return;
80
- xt_gui_webgl_forget(win, node);
80
+ const XtWebglApi *webgl = xt_webgl_api();
81
+ if (webgl != nullptr) webgl->forget(win, node);
81
82
  auto found = win->canvases.find(node);
82
83
  if (found == win->canvases.end()) return;
83
84
  releaseCanvasTexture(found->second.get());
@@ -88,11 +89,17 @@ void xt_gui_canvas_sync(XtGuiWindow *win) {
88
89
  if (win == nullptr || win->document == nullptr) return;
89
90
  std::vector<const xtgui::Node *> nodes;
90
91
  collectCanvases(win->document->root(), &nodes);
92
+ const XtWebglApi *webgl = xt_webgl_api();
91
93
  for (const xtgui::Node *node : nodes) {
92
- if (xtgui::WebGLContext *webgl = xt_gui_webgl_find(win, node)) {
93
- win->document->setCanvasInfo(node, (float)webgl->width(), (float)webgl->height(),
94
- webgl->id());
95
- continue;
94
+ if (webgl != nullptr) {
95
+ if (void *context = webgl->find(win, node)) {
96
+ float width = 0.0f;
97
+ float height = 0.0f;
98
+ uint64_t id = 0;
99
+ webgl->info(context, &width, &height, &id);
100
+ win->document->setCanvasInfo(node, width, height, id);
101
+ continue;
102
+ }
96
103
  }
97
104
  xtgui::Canvas2D *canvas = xt_gui_canvas_find(win, node);
98
105
  if (canvas != nullptr) {
@@ -136,7 +143,8 @@ void xt_gui_canvas_bind_pending(XtGuiWindow *win, const xtgui::Node *node, xt_va
136
143
 
137
144
  void xt_gui_canvas_shutdown(XtGuiWindow *win) {
138
145
  if (win == nullptr) return;
139
- xt_gui_webgl_shutdown(win);
146
+ const XtWebglApi *webgl = xt_webgl_api();
147
+ if (webgl != nullptr) webgl->shutdown(win);
140
148
  for (auto &entry : win->canvases) releaseCanvasTexture(entry.second.get());
141
149
  win->canvases.clear();
142
150
  for (auto &entry : win->offscreen_canvases) releaseCanvasTexture(entry.second.get());
@@ -0,0 +1,50 @@
1
+ /*
2
+ * xbintsc GUI engine — CSS `cursor` → SDL system cursor.
3
+ *
4
+ * Kept apart from `gui_events.cpp` (which owns the input dispatch) so both
5
+ * translation units stay under the repository's per-file line budget.
6
+ */
7
+
8
+ #include "gui_internal.h"
9
+
10
+ namespace guidev {
11
+
12
+ namespace {
13
+
14
+ /** Lazily created SDL system cursors for the CSS cursor values we map. */
15
+ SDL_Cursor *g_system_cursors[9] = {};
16
+
17
+ SDL_SystemCursor sdlCursorFor(xtgui::CursorShape shape) {
18
+ switch (shape) {
19
+ case xtgui::CursorShape::Pointer: return SDL_SYSTEM_CURSOR_POINTER;
20
+ case xtgui::CursorShape::Text: return SDL_SYSTEM_CURSOR_TEXT;
21
+ case xtgui::CursorShape::Move: return SDL_SYSTEM_CURSOR_MOVE;
22
+ case xtgui::CursorShape::Wait: return SDL_SYSTEM_CURSOR_WAIT;
23
+ case xtgui::CursorShape::Help: return SDL_SYSTEM_CURSOR_DEFAULT;
24
+ case xtgui::CursorShape::NotAllowed: return SDL_SYSTEM_CURSOR_NOT_ALLOWED;
25
+ case xtgui::CursorShape::Crosshair: return SDL_SYSTEM_CURSOR_CROSSHAIR;
26
+ case xtgui::CursorShape::Auto:
27
+ case xtgui::CursorShape::Default: return SDL_SYSTEM_CURSOR_DEFAULT;
28
+ }
29
+ return SDL_SYSTEM_CURSOR_DEFAULT;
30
+ }
31
+
32
+ } // namespace
33
+
34
+ /** Apply the hovered element's computed `cursor` to the window. */
35
+ void applyCursor(XtGuiWindow *win, const xtgui::Node *node) {
36
+ if (win == nullptr || win->window == nullptr) return;
37
+ xtgui::CursorShape shape = xtgui::CursorShape::Default;
38
+ if (node != nullptr && win->document != nullptr) {
39
+ const xtgui::XtStyle *style = win->document->styleOf(node);
40
+ if (style != nullptr) shape = style->cursor;
41
+ }
42
+ int index = (int)shape;
43
+ if (index < 0 || index >= 9) index = 0;
44
+ if (g_system_cursors[index] == nullptr) {
45
+ g_system_cursors[index] = SDL_CreateSystemCursor(sdlCursorFor(shape));
46
+ }
47
+ if (g_system_cursors[index] != nullptr) SDL_SetCursor(g_system_cursors[index]);
48
+ }
49
+
50
+ } // namespace guidev
@@ -94,7 +94,7 @@ xt_value xt_gui_audio_create(int32_t argc, xt_value *argv);
94
94
  #include "media.h"
95
95
  #include "paint.h"
96
96
  #include "renderer.h"
97
- #include "webgl.h"
97
+ #include "webgl_api.h"
98
98
 
99
99
  /** One element-level event listener registered through `addEventListener`. */
100
100
  struct XtGuiNodeListener {
@@ -225,9 +225,6 @@ struct XtGuiWindow {
225
225
  * lazily by `getContext("2d")` (or by `xt_gui_canvas_sync` for a live canvas),
226
226
  * released when the node is forgotten or the document reloads. */
227
227
  std::unordered_map<const xtgui::Node *, std::unique_ptr<xtgui::Canvas2D>> canvases;
228
- /** One WebGL context per `<canvas>` that asked for `getContext("webgl")`.
229
- * A canvas uses either this map or `canvases`, never both. */
230
- std::unordered_map<const xtgui::Node *, std::unique_ptr<xtgui::WebGLContext>> webgl_canvases;
231
228
  /** Canvases whose element handle was created before `loadHTML` (a module
232
229
  * that calls `getElementById("scene").getContext("2d")` while it
233
230
  * initialises). Keyed by canvas id; moved into `canvases` when the pending
@@ -355,20 +352,6 @@ void xt_gui_canvas_sync(XtGuiWindow *win);
355
352
  /** Release every canvas of `win` (window close / `loadHTML`). */
356
353
  void xt_gui_canvas_shutdown(XtGuiWindow *win);
357
354
 
358
- /** The WebGL context of a `<canvas>`, creating it on first use. `webgl2`
359
- * selects the WebGL 2 API/prototype; a canvas keeps the first context kind it
360
- * asked for, so a later request for the other one answers null. */
361
- xtgui::WebGLContext *xt_gui_webgl_get(XtGuiWindow *win, const xtgui::Node *node, bool webgl2);
362
- /** The existing WebGL context of `node`, or null (no side effects). */
363
- xtgui::WebGLContext *xt_gui_webgl_find(XtGuiWindow *win, const xtgui::Node *node);
364
- /** Drop a node's WebGL context (called when its handle is forgotten). */
365
- void xt_gui_webgl_forget(XtGuiWindow *win, const xtgui::Node *node);
366
- /** Release every WebGL context of `win`. */
367
- void xt_gui_webgl_shutdown(XtGuiWindow *win);
368
- /** Submit every live WebGL context's queued commands. Called once per frame
369
- * before the page's own render pass samples a canvas texture. */
370
- void xt_gui_webgl_submit_all();
371
-
372
355
  #endif /* __cplusplus */
373
356
 
374
357
  #endif /* XT_GUI_ENGINE_H */
@@ -157,6 +157,7 @@ static void xt_gui_update_hover(XtGuiWindow *win, const xtgui::Node *node) {
157
157
  win->geometry.dirty = 1;
158
158
  win->present_dirty = 1;
159
159
  }
160
+ guidev::applyCursor(win, node);
160
161
  }
161
162
 
162
163
  /** Pointer payload. `node` is the hit-test result the caller already resolved,
@@ -187,10 +188,21 @@ void xt_gui_dispatch_pointer(XtGuiWindow *win, const char *type, float x, float
187
188
  if (win == NULL || !win->open) return;
188
189
  const xtgui::Node *node = xt_gui_hit_node(win, x, y);
189
190
  xt_gui_update_hover(win, node);
190
- if (strcmp(type, "mousedown") == 0 && win->document != nullptr) {
191
- if (win->document->setFocus(node)) {
192
- win->geometry.dirty = 1;
193
- win->present_dirty = 1;
191
+ if (win->document != nullptr) {
192
+ if (strcmp(type, "mousedown") == 0) {
193
+ if (win->document->setFocus(node)) {
194
+ win->geometry.dirty = 1;
195
+ win->present_dirty = 1;
196
+ }
197
+ if (win->document->setActive(node)) {
198
+ win->geometry.dirty = 1;
199
+ win->present_dirty = 1;
200
+ }
201
+ } else if (strcmp(type, "mouseup") == 0) {
202
+ if (win->document->setActive(nullptr)) {
203
+ win->geometry.dirty = 1;
204
+ win->present_dirty = 1;
205
+ }
194
206
  }
195
207
  }
196
208
  xt_value payload = xt_gui_pointer_payload(type, win, x, y, button, clicks, node);
@@ -475,8 +487,9 @@ void xt_gui_render_window(XtGuiWindow *win) {
475
487
  xt_gui_upload_end(&uploads);
476
488
  /* WebGL contexts record into their own command buffers; submit them first so
477
489
  * the page's render pass samples this frame's drawing-buffer contents. */
478
- xt_gui_webgl_submit_all();
479
- if (uploaded || !win->webgl_canvases.empty()) win->present_dirty = 1;
490
+ const XtWebglApi *webgl = xt_webgl_api();
491
+ const bool webgl_live = webgl != nullptr && webgl->submit_all();
492
+ if (uploaded || webgl_live) win->present_dirty = 1;
480
493
 
481
494
  /* Present only when something happened. A static document otherwise burned a
482
495
  * swapchain acquire and a full-scene draw every frame. `XT_GUI_ALWAYS_PRESENT`
@@ -24,6 +24,9 @@ extern int g_window_count;
24
24
  /** Translate one SDL event into engine callbacks (defined in gui_events.cpp). */
25
25
  void handle_event(const SDL_Event *event);
26
26
 
27
+ /** Apply the hovered element's CSS `cursor` to the window. */
28
+ void applyCursor(XtGuiWindow *win, const xtgui::Node *node);
29
+
27
30
  } // namespace guidev
28
31
 
29
32
  #endif /* XT_GUI_INTERNAL_H */
@@ -7,12 +7,21 @@
7
7
  */
8
8
 
9
9
  #include <memory>
10
+ #include <unordered_map>
10
11
 
11
12
  #include "gui_engine.h"
12
13
  #include "gui_internal.h"
14
+ #include "webgl_feature.h"
13
15
 
14
16
  namespace {
15
17
 
18
+ /** WebGL contexts are owned here rather than on `XtGuiWindow` so the core
19
+ * window type does not need `webgl.h`. Entries are dropped by
20
+ * `xt_gui_webgl_shutdown` when a window closes or reloads its document. */
21
+ std::unordered_map<XtGuiWindow *,
22
+ std::unordered_map<const xtgui::Node *, std::unique_ptr<xtgui::WebGLContext>>>
23
+ g_webgl;
24
+
16
25
  bool isCanvasElement(const xtgui::Node *node) {
17
26
  return node != nullptr && node->isElement() && node->isTag("canvas");
18
27
  }
@@ -29,8 +38,10 @@ uint64_t next_webgl_id() {
29
38
 
30
39
  xtgui::WebGLContext *xt_gui_webgl_find(XtGuiWindow *win, const xtgui::Node *node) {
31
40
  if (win == nullptr || node == nullptr) return nullptr;
32
- auto found = win->webgl_canvases.find(node);
33
- return found == win->webgl_canvases.end() ? nullptr : found->second.get();
41
+ auto found = g_webgl.find(win);
42
+ if (found == g_webgl.end()) return nullptr;
43
+ auto entry = found->second.find(node);
44
+ return entry == found->second.end() ? nullptr : entry->second.get();
34
45
  }
35
46
 
36
47
  xtgui::WebGLContext *xt_gui_webgl_get(XtGuiWindow *win, const xtgui::Node *node, bool webgl2) {
@@ -57,7 +68,7 @@ xtgui::WebGLContext *xt_gui_webgl_get(XtGuiWindow *win, const xtgui::Node *node,
57
68
  auto context = std::make_unique<xtgui::WebGLContext>(
58
69
  next_webgl_id(), (int)(width + 0.5f), (int)(height + 0.5f), webgl2);
59
70
  xtgui::WebGLContext *raw = context.get();
60
- win->webgl_canvases[node] = std::move(context);
71
+ g_webgl[win][node] = std::move(context);
61
72
  if (win->document != nullptr) {
62
73
  win->document->setCanvasInfo(node, (float)raw->width(), (float)raw->height(), raw->id());
63
74
  }
@@ -66,18 +77,22 @@ xtgui::WebGLContext *xt_gui_webgl_get(XtGuiWindow *win, const xtgui::Node *node,
66
77
 
67
78
  void xt_gui_webgl_forget(XtGuiWindow *win, const xtgui::Node *node) {
68
79
  if (win == nullptr || node == nullptr) return;
69
- win->webgl_canvases.erase(node);
80
+ auto found = g_webgl.find(win);
81
+ if (found != g_webgl.end()) found->second.erase(node);
70
82
  }
71
83
 
72
84
  void xt_gui_webgl_shutdown(XtGuiWindow *win) {
73
85
  if (win == nullptr) return;
74
- win->webgl_canvases.clear();
86
+ g_webgl.erase(win);
75
87
  }
76
88
 
77
- void xt_gui_webgl_submit_all() {
78
- for (int i = 0; i < XT_GUI_MAX_WINDOWS; i++) {
79
- XtGuiWindow &win = guidev::g_windows[i];
80
- if (!win.open) continue;
81
- for (auto &entry : win.webgl_canvases) entry.second->submit();
89
+ bool xt_gui_webgl_submit_all() {
90
+ bool any = false;
91
+ for (auto &window : g_webgl) {
92
+ for (auto &entry : window.second) {
93
+ entry.second->submit();
94
+ any = true;
95
+ }
82
96
  }
97
+ return any;
83
98
  }
@@ -0,0 +1,18 @@
1
+ /*
2
+ * xbintsc GUI engine — core-side registry for the optional image/SVG table.
3
+ *
4
+ * Kept in `gui.a`, free of stb_image/NanoSVG: the table is installed by the
5
+ * `images` feature bridge when that feature is linked, and stays null otherwise.
6
+ */
7
+
8
+ #include "image_api.h"
9
+
10
+ namespace {
11
+
12
+ const XtImageApi *g_api = nullptr;
13
+
14
+ } // namespace
15
+
16
+ void xt_image_api_install(const XtImageApi *api) { g_api = api; }
17
+
18
+ const XtImageApi *xt_image_api() { return g_api; }
@@ -0,0 +1,43 @@
1
+ /*
2
+ * xbintsc GUI engine — optional image/SVG decoding table.
3
+ *
4
+ * `<img>`, CSS `background-image`, `drawImage` and inline `<svg>` all need
5
+ * stb_image/NanoSVG, which together are a noticeable part of a DOM-only binary.
6
+ * The `images` link feature installs this table from a bridge; without it, the
7
+ * core's image/SVG entry points are no-ops and the page simply draws nothing for
8
+ * those sources (the element and its layout still exist).
9
+ */
10
+ #ifndef XT_GUI_IMAGE_API_H
11
+ #define XT_GUI_IMAGE_API_H
12
+
13
+ #include <cstddef>
14
+ #include <string>
15
+
16
+ #include "image.h"
17
+
18
+ struct XtImageApi {
19
+ const xtgui::Image *(*load)(const std::string &path, int target_width, int target_height);
20
+ bool (*size)(const std::string &path, int *width, int *height);
21
+ bool (*decode)(const unsigned char *data, size_t size, xtgui::Image *out);
22
+ bool (*is_vector)(const std::string &path);
23
+ void (*shutdown)();
24
+ bool (*svg_size)(const std::string &path, int *width, int *height);
25
+ bool (*svg_render)(const std::string &path, int width, int height, xtgui::Image *out);
26
+ bool (*svg_markup_size)(const std::string &markup, int *width, int *height);
27
+ bool (*svg_markup_render)(const std::string &markup, int width, int height, xtgui::Image *out);
28
+ int (*svg_markup_shapes)(const std::string &markup);
29
+ bool (*inline_is_root)(const void *node);
30
+ /** Serialize an inline `<svg>`; `styles` is the document's computed-style map
31
+ * (`const std::unordered_map<const Node*, XtStyle>*`, or null). */
32
+ std::string (*inline_serialize)(const void *node, const void *styles);
33
+ /** Set the window icon from the embedded PNG (a no-op without an icon). */
34
+ void (*apply_icon)(void *window);
35
+ };
36
+
37
+ /** Install the table. Called by the `images` feature bridge before `main`. */
38
+ void xt_image_api_install(const XtImageApi *api);
39
+
40
+ /** The installed table, or null when the `images` feature was not linked. */
41
+ const XtImageApi *xt_image_api();
42
+
43
+ #endif /* XT_GUI_IMAGE_API_H */
@@ -16,7 +16,8 @@
16
16
 
17
17
  #include "image_bitmap.h"
18
18
  #include "canvas.h"
19
- #include "webgl.h"
19
+ #include "image_api.h"
20
+ #include "webgl_api.h"
20
21
 
21
22
  namespace {
22
23
 
@@ -63,7 +64,9 @@ bool source_pixels(xt_value source, xtgui::Image *out) {
63
64
  if (node == nullptr || !node->isElement()) return false;
64
65
  if (node->isTag("img")) {
65
66
  const std::string *src = node->attr("src");
66
- const xtgui::Image *image = src != nullptr ? xtgui::xt_image_load(*src) : nullptr;
67
+ const XtImageApi *images = xt_image_api();
68
+ const xtgui::Image *image =
69
+ src != nullptr && images != nullptr ? images->load(*src, 0, 0) : nullptr;
67
70
  if (image == nullptr) return false;
68
71
  *out = *image;
69
72
  return true;
@@ -73,16 +76,22 @@ bool source_pixels(xt_value source, xtgui::Image *out) {
73
76
  *out = canvas->image();
74
77
  return true;
75
78
  }
76
- if (xtgui::WebGLContext *context = xt_gui_webgl_find(win, node)) {
77
- out->width = context->width();
78
- out->height = context->height();
79
- out->pixels.resize((size_t)out->width * out->height * 4);
80
- if (!context->read_pixels(0, 0, out->width, out->height, out->pixels.data())) {
81
- return false;
79
+ if (const XtWebglApi *webgl = xt_webgl_api()) {
80
+ if (void *context = webgl->find(win, node)) {
81
+ float width = 0.0f;
82
+ float height = 0.0f;
83
+ uint64_t id = 0;
84
+ webgl->info(context, &width, &height, &id);
85
+ out->width = (int)width;
86
+ out->height = (int)height;
87
+ out->pixels.resize((size_t)out->width * out->height * 4);
88
+ if (!webgl->read_pixels(context, 0, 0, out->width, out->height, out->pixels.data())) {
89
+ return false;
90
+ }
91
+ /* readPixels is GL-ordered (bottom-up); bitmaps are stored top-down. */
92
+ flip_rows(&out->pixels, out->width, out->height);
93
+ return true;
82
94
  }
83
- /* readPixels is GL-ordered (bottom-up); bitmaps are stored top-down. */
84
- flip_rows(&out->pixels, out->width, out->height);
85
- return true;
86
95
  }
87
96
  return false;
88
97
  }
@@ -0,0 +1,22 @@
1
+ /*
2
+ * xbintsc GUI engine — `images` link feature registration bridge.
3
+ *
4
+ * Compiled and linked only when the compiler detects that the program may draw
5
+ * an image or SVG. Its constructor installs `XtImageApi` and references the
6
+ * stb_image/NanoSVG members, pulling them out of `gui.a`; without it they are
7
+ * never linked.
8
+ */
9
+
10
+ extern void xt_image_feature_register(void);
11
+
12
+ #if defined(__GNUC__) || defined(__clang__)
13
+ __attribute__((constructor)) static void xt_image_feature_boot(void) {
14
+ xt_image_feature_register();
15
+ }
16
+ #elif defined(_MSC_VER)
17
+ void xt_image_feature_boot(void);
18
+ #pragma section(".CRT$XCU", read)
19
+ __declspec(allocate(".CRT$XCU")) static void (*const xt_image_feature_ctor)(void) =
20
+ xt_image_feature_boot;
21
+ void xt_image_feature_boot(void) { xt_image_feature_register(); }
22
+ #endif
@@ -0,0 +1,107 @@
1
+ /*
2
+ * xbintsc GUI engine — `images` link feature implementation.
3
+ *
4
+ * The only core-side place that references stb_image/NanoSVG and the inline-SVG
5
+ * serializer. `image_bridge.c` (linked only when the feature is active) calls
6
+ * `xt_image_feature_register`, which installs `XtImageApi`; without it the core
7
+ * draws no images or SVG and those members stay out of the binary.
8
+ */
9
+
10
+ #include "image_api.h"
11
+
12
+ #include <SDL3/SDL.h>
13
+
14
+ #include <cstring>
15
+
16
+ #include "inline_svg.h"
17
+ #include "svg.h"
18
+
19
+ /* Embedded by the driver's icon object; a zero size means "no icon". */
20
+ extern "C" {
21
+ extern const unsigned char xt_app_icon_data[];
22
+ extern const unsigned long long xt_app_icon_size;
23
+ extern const char xt_app_icon_format[];
24
+ }
25
+
26
+ namespace {
27
+
28
+ const xtgui::Image *load(const std::string &path, int target_width, int target_height) {
29
+ return xtgui::xt_image_load(path, target_width, target_height);
30
+ }
31
+
32
+ bool size(const std::string &path, int *width, int *height) {
33
+ return xtgui::xt_image_size(path, width, height);
34
+ }
35
+
36
+ bool decode(const unsigned char *data, size_t size, xtgui::Image *out) {
37
+ return xtgui::xt_image_decode(data, size, out);
38
+ }
39
+
40
+ bool is_vector(const std::string &path) { return xtgui::xt_image_is_vector(path); }
41
+
42
+ void shutdown() { xtgui::xt_image_shutdown(); }
43
+
44
+ bool svg_size(const std::string &path, int *width, int *height) {
45
+ return xtgui::xt_svg_size(path, width, height);
46
+ }
47
+
48
+ bool svg_render(const std::string &path, int width, int height, xtgui::Image *out) {
49
+ return xtgui::xt_svg_render(path, width, height, out);
50
+ }
51
+
52
+ bool svg_markup_size(const std::string &markup, int *width, int *height) {
53
+ return xtgui::xt_svg_markup_size(markup, width, height);
54
+ }
55
+
56
+ bool svg_markup_render(const std::string &markup, int width, int height, xtgui::Image *out) {
57
+ return xtgui::xt_svg_markup_render(markup, width, height, out);
58
+ }
59
+
60
+ int svg_markup_shapes(const std::string &markup) {
61
+ return xtgui::xt_svg_markup_shapes(markup);
62
+ }
63
+
64
+ bool inline_is_root(const void *node) {
65
+ return xtgui::xt_inline_svg_is_root(static_cast<const xtgui::Node *>(node));
66
+ }
67
+
68
+ std::string inline_serialize(const void *node, const void *styles) {
69
+ using StyleMap = std::unordered_map<const xtgui::Node *, xtgui::XtStyle>;
70
+ return xtgui::xt_inline_svg_serialize(static_cast<const xtgui::Node *>(node),
71
+ static_cast<const StyleMap *>(styles));
72
+ }
73
+
74
+ void apply_icon(void *window) {
75
+ SDL_Window *sdl_window = static_cast<SDL_Window *>(window);
76
+ const size_t size = (size_t)xt_app_icon_size;
77
+ if (sdl_window == nullptr || size == 0) return;
78
+ if (strcmp(xt_app_icon_format, "png") != 0) return;
79
+ xtgui::Image image;
80
+ if (!xtgui::xt_image_decode(xt_app_icon_data, size, &image)) return;
81
+ if (image.width <= 0 || image.height <= 0) return;
82
+ SDL_Surface *surface = SDL_CreateSurfaceFrom(
83
+ image.width, image.height, SDL_PIXELFORMAT_RGBA32, image.pixels.data(), image.width * 4);
84
+ if (surface == nullptr) return;
85
+ SDL_SetWindowIcon(sdl_window, surface);
86
+ SDL_DestroySurface(surface);
87
+ }
88
+
89
+ const XtImageApi kImageApi = {
90
+ load,
91
+ size,
92
+ decode,
93
+ is_vector,
94
+ shutdown,
95
+ svg_size,
96
+ svg_render,
97
+ svg_markup_size,
98
+ svg_markup_render,
99
+ svg_markup_shapes,
100
+ inline_is_root,
101
+ inline_serialize,
102
+ apply_icon,
103
+ };
104
+
105
+ } // namespace
106
+
107
+ extern "C" void xt_image_feature_register(void) { xt_image_api_install(&kImageApi); }
@@ -5,8 +5,11 @@
5
5
  #include "inline_svg.h"
6
6
 
7
7
  #include <cctype>
8
+ #include <cstdio>
8
9
  #include <string>
9
10
  #include <unordered_map>
11
+ #include <utility>
12
+ #include <vector>
10
13
 
11
14
  namespace xtgui {
12
15
  namespace {
@@ -161,26 +164,99 @@ bool isHtmlAttr(const std::string &name) {
161
164
  return name == "tabindex" || name == "role" || name == "contenteditable";
162
165
  }
163
166
 
164
- void serializeNode(const Node *node, std::string *out);
167
+ const std::vector<const char *> &svgStyleOrder() {
168
+ static const std::vector<const char *> order = {
169
+ "fill", "fill-opacity", "fill-rule", "stroke",
170
+ "stroke-opacity", "stroke-width", "stroke-dasharray", "stroke-dashoffset",
171
+ "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stop-color",
172
+ "stop-opacity", "paint-order"};
173
+ return order;
174
+ }
175
+
176
+ bool isColorStyleProperty(const std::string &name) {
177
+ return name == "fill" || name == "stroke" || name == "stop-color";
178
+ }
179
+
180
+ const char *opacityFor(const std::string &name) {
181
+ if (name == "fill") return "fill-opacity";
182
+ if (name == "stroke") return "stroke-opacity";
183
+ if (name == "stop-color") return "stop-opacity";
184
+ return nullptr;
185
+ }
165
186
 
166
- void serializeStyleText(const Node *node, std::string *out) {
187
+ /** Build the inline `style="…"` attribute for one SVG element from the
188
+ * document cascade. Colours are rewritten as `#rrggbb` plus a `*-opacity`
189
+ * because NanoSVG's reader has no `rgba()` syntax. */
190
+ std::string computedSvgStyle(const XtStyle &style) {
191
+ std::vector<std::pair<std::string, std::string>> declarations;
192
+ for (const char *name : svgStyleOrder()) {
193
+ auto found = style.svg_props.find(name);
194
+ if (found == style.svg_props.end()) continue;
195
+ const std::string &value = found->second;
196
+ if (isColorStyleProperty(name)) {
197
+ Color color = xt_css_parse_color(value);
198
+ if (color.valid) {
199
+ char hex[16];
200
+ std::snprintf(hex, sizeof(hex), "#%02x%02x%02x", (int)(color.r * 255.0f + 0.5f),
201
+ (int)(color.g * 255.0f + 0.5f), (int)(color.b * 255.0f + 0.5f));
202
+ declarations.emplace_back(name, hex);
203
+ const char *opacity_name = opacityFor(name);
204
+ bool opacity_set = style.svg_props.find(opacity_name) != style.svg_props.end();
205
+ if (!opacity_set && color.a < 0.999f) {
206
+ char alpha[32];
207
+ std::snprintf(alpha, sizeof(alpha), "%.3f", (double)color.a);
208
+ declarations.emplace_back(opacity_name, alpha);
209
+ }
210
+ continue;
211
+ }
212
+ }
213
+ declarations.emplace_back(name, value);
214
+ }
215
+ std::string text;
216
+ for (const auto &declaration : declarations) {
217
+ if (!text.empty()) text.push_back(';');
218
+ text += declaration.first;
219
+ text.push_back(':');
220
+ text += declaration.second;
221
+ }
222
+ return text;
223
+ }
224
+
225
+ void serializeNode(const Node *node, const std::unordered_map<const Node *, XtStyle> *styles,
226
+ std::string *out);
227
+
228
+ void serializeStyleText(const Node *node, const std::unordered_map<const Node *, XtStyle> *styles,
229
+ std::string *out) {
167
230
  for (const std::unique_ptr<Node> &child : node->children) {
168
231
  if (child->isText()) {
169
232
  out->append(child->text);
170
233
  } else if (child->isElement()) {
171
- serializeNode(child.get(), out);
234
+ serializeNode(child.get(), styles, out);
172
235
  }
173
236
  }
174
237
  }
175
238
 
176
- void serializeNode(const Node *node, std::string *out) {
239
+ void serializeNode(const Node *node, const std::unordered_map<const Node *, XtStyle> *styles,
240
+ std::string *out) {
177
241
  if (node == nullptr || !node->isElement()) return;
242
+ const XtStyle *computed = nullptr;
243
+ if (styles != nullptr) {
244
+ auto found = styles->find(node);
245
+ if (found != styles->end()) computed = &found->second;
246
+ }
247
+ std::string injected;
248
+ if (computed != nullptr && !computed->svg_props.empty()) injected = computedSvgStyle(*computed);
249
+ const std::string *raw_style = node->attr("style");
250
+
178
251
  std::string tag = canonicalTag(node->tag);
179
252
  out->push_back('<');
180
253
  out->append(tag);
181
254
  if (node->isTag("svg")) out->append(" xmlns=\"http://www.w3.org/2000/svg\"");
182
255
  for (const Attr &attr : node->attrs) {
183
256
  if (isNamespaceAttr(attr.name) || isHtmlAttr(attr.name)) continue;
257
+ /* The computed style already includes the element's own `style="…"`
258
+ * declarations; emitting both would be a duplicate XML attribute. */
259
+ if (!injected.empty() && attr.name == "style") continue;
184
260
  /* The root's xmlns is emitted above, so a second copy would be an XML
185
261
  * duplicate-attribute error. */
186
262
  const char *name = canonicalAttr(attr.name);
@@ -190,12 +266,22 @@ void serializeNode(const Node *node, std::string *out) {
190
266
  escapeAttribute(attr.value, out);
191
267
  out->push_back('"');
192
268
  }
269
+ if (!injected.empty()) {
270
+ /* The computed declarations include the element's own inline ones; merge
271
+ * them so unrelated inline properties (display, opacity, …) survive. */
272
+ std::string merged = (raw_style != nullptr) ? *raw_style : std::string();
273
+ if (!merged.empty() && merged.back() != ';') merged.push_back(';');
274
+ merged += injected;
275
+ out->append(" style=\"");
276
+ escapeAttribute(merged, out);
277
+ out->push_back('"');
278
+ }
193
279
  out->push_back('>');
194
280
  if (node->isTag("style")) {
195
- serializeStyleText(node, out);
281
+ serializeStyleText(node, styles, out);
196
282
  } else {
197
283
  for (const std::unique_ptr<Node> &child : node->children) {
198
- if (child->isElement()) serializeNode(child.get(), out);
284
+ if (child->isElement()) serializeNode(child.get(), styles, out);
199
285
  }
200
286
  }
201
287
  out->append("</");
@@ -209,10 +295,11 @@ bool xt_inline_svg_is_root(const Node *node) {
209
295
  return node != nullptr && node->isElement() && node->isTag("svg");
210
296
  }
211
297
 
212
- std::string xt_inline_svg_serialize(const Node *node) {
298
+ std::string xt_inline_svg_serialize(
299
+ const Node *node, const std::unordered_map<const Node *, XtStyle> *styles) {
213
300
  if (!xt_inline_svg_is_root(node)) return std::string();
214
301
  std::string out;
215
- serializeNode(node, &out);
302
+ serializeNode(node, styles, &out);
216
303
  return out;
217
304
  }
218
305