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
@@ -19,8 +19,10 @@
19
19
  #define XT_GUI_INLINE_SVG_H
20
20
 
21
21
  #include <string>
22
+ #include <unordered_map>
22
23
 
23
24
  #include "dom.h"
25
+ #include "style.h"
24
26
  #include "svg.h"
25
27
 
26
28
  namespace xtgui {
@@ -34,7 +36,9 @@ bool xt_inline_svg_is_root(const Node *node);
34
36
  * expects. Text outside `<style>` is dropped (NanoSVG renders shapes, not
35
37
  * `<text>`), and HTML-only attributes are skipped. Returns an empty string when
36
38
  * `node` is not an `<svg>` element. */
37
- std::string xt_inline_svg_serialize(const Node *node);
39
+ std::string xt_inline_svg_serialize(
40
+ const Node *node,
41
+ const std::unordered_map<const Node *, XtStyle> *styles = nullptr);
38
42
 
39
43
  } // namespace xtgui
40
44
 
@@ -6,7 +6,7 @@
6
6
  * - Block flow: consecutive inline-level children form an anonymous inline
7
7
  * formatting context; block-level children are stacked vertically.
8
8
  * - Inline flow: greedy, word-based line breaking with `text-align`; text is
9
- * measured with the HarfBuzz/FreeType stack in `text.h`.
9
+ * measured with the stb_truetype stack in `text.h`.
10
10
  * - Flexbox: single-line row/column with grow/shrink, gap, justify-content
11
11
  * and align-items.
12
12
  * Margins do not collapse yet, and only static positioning is honoured.
@@ -14,6 +14,8 @@
14
14
 
15
15
  #include "layout_internal.h"
16
16
 
17
+ #include "image_api.h"
18
+
17
19
  #include <algorithm>
18
20
  #include <cstdio>
19
21
  #include <cstring>
@@ -38,7 +40,8 @@ std::unique_ptr<LayoutBox> buildBoxTree(const Node *node,
38
40
  auto box = std::make_unique<LayoutBox>();
39
41
  box->node = node;
40
42
  box->is_text = true;
41
- box->text = node->text;
43
+ box->text = inherited != nullptr ? xt_apply_text_transform(node->text, inherited->text_transform)
44
+ : node->text;
42
45
  box->style = inherited;
43
46
  box->display = Display::Inline;
44
47
  return box;
@@ -51,6 +54,7 @@ std::unique_ptr<LayoutBox> buildBoxTree(const Node *node,
51
54
  box->node = node;
52
55
  box->style = style;
53
56
  box->display = style != nullptr ? style->display : Display::Block;
57
+ const XtImageApi *images = xt_image_api();
54
58
  if (node->isElement() && node->isTag("img")) {
55
59
  box->is_image = true;
56
60
  const std::string *src = node->attr("src");
@@ -58,7 +62,7 @@ std::unique_ptr<LayoutBox> buildBoxTree(const Node *node,
58
62
  box->image_src = *src;
59
63
  int width = 0;
60
64
  int height = 0;
61
- if (xt_image_size(*src, &width, &height)) {
65
+ if (images != nullptr && images->size(*src, &width, &height)) {
62
66
  box->intrinsic_width = (float)width;
63
67
  box->intrinsic_height = (float)height;
64
68
  }
@@ -113,12 +117,14 @@ std::unique_ptr<LayoutBox> buildBoxTree(const Node *node,
113
117
  * let paint/rasterisation handle the children — the normal box walk must
114
118
  * not descend into them. */
115
119
  box->is_inline_svg = true;
116
- box->svg_markup = xt_inline_svg_serialize(node);
117
- int width = 0;
118
- int height = 0;
119
- if (xt_svg_markup_size(box->svg_markup, &width, &height)) {
120
- box->intrinsic_width = (float)width;
121
- box->intrinsic_height = (float)height;
120
+ if (images != nullptr) {
121
+ box->svg_markup = images->inline_serialize(node, &styles);
122
+ int width = 0;
123
+ int height = 0;
124
+ if (images->svg_markup_size(box->svg_markup, &width, &height)) {
125
+ box->intrinsic_width = (float)width;
126
+ box->intrinsic_height = (float)height;
127
+ }
122
128
  }
123
129
  if (box->intrinsic_width <= 0.0f) box->intrinsic_width = 300.0f;
124
130
  if (box->intrinsic_height <= 0.0f) box->intrinsic_height = 150.0f;
@@ -4,7 +4,7 @@
4
4
  * `LayoutTree::compute` turns the styled DOM into a tree of `LayoutBox`es with
5
5
  * absolute (viewport-relative) geometry. It implements the slice a UI needs:
6
6
  * block flow, a simple inline formatting context with greedy line breaking
7
- * (text metrics come from the HarfBuzz/FreeType stack) and single-line
7
+ * (text metrics come from the stb_truetype stack) and single-line
8
8
  * Flexbox. `<img>` is a replaced element sized from its intrinsic dimensions.
9
9
  *
10
10
  * Coordinates are absolute: `x`/`y` is the border-box origin measured from the
@@ -51,6 +51,10 @@ struct TextFragment {
51
51
  float width = 0;
52
52
  float height = 0;
53
53
  std::string text;
54
+ /** A vertical-writing-mode run: `x`/`y`/`width`/`height` are its column box.
55
+ * `upright` is a CJK-style glyph; otherwise the run is rotated 90° clockwise. */
56
+ bool vertical = false;
57
+ bool upright = false;
54
58
  };
55
59
 
56
60
  /** A viewport-relative rectangle (`getBoundingClientRect` result). */
@@ -65,6 +69,8 @@ struct LayoutBox {
65
69
  bool is_text = false;
66
70
  std::string text; // only for text boxes
67
71
  std::vector<TextFragment> fragments;
72
+ /** `border-radius` resolved against this border box (percent supported). */
73
+ float border_radius = 0.0f;
68
74
 
69
75
  /* Replaced element (currently `<img>`): source + intrinsic pixel size. */
70
76
  bool is_image = false;
@@ -140,7 +146,7 @@ class LayoutTree {
140
146
  XtStyle root_style_;
141
147
  };
142
148
 
143
- /** Text width under `style` (shaped with the HarfBuzz/FreeType stack). */
149
+ /** Text width under `style` (shaped with the stb_truetype stack). */
144
150
  float xt_layout_text_width(const std::string &text, const XtStyle &style);
145
151
 
146
152
  /** The box's border box in device space: its layout rectangle pushed through
@@ -12,6 +12,37 @@
12
12
  namespace xtgui {
13
13
  namespace layout_flow_detail {
14
14
 
15
+ /** Give the line's free main-axis space to `auto` margins (CSS flexbox). Returns
16
+ * true when any item had one; the caller then skips `justify-content`. */
17
+ static bool applyAutoMainMargins(const std::vector<LayoutBox *> &items,
18
+ const std::vector<size_t> &line, bool row, float freeSpace) {
19
+ int count = 0;
20
+ for (size_t i : line) {
21
+ const XtStyle &style = *items[i]->style;
22
+ if (row) {
23
+ if (style.margin_left_auto) count++;
24
+ if (style.margin_right_auto) count++;
25
+ } else {
26
+ if (style.margin_top_auto) count++;
27
+ if (style.margin_bottom_auto) count++;
28
+ }
29
+ }
30
+ if (count == 0) return false;
31
+ if (freeSpace < 0.0f) freeSpace = 0.0f;
32
+ float share = freeSpace / (float)count;
33
+ for (size_t i : line) {
34
+ const XtStyle &style = *items[i]->style;
35
+ if (row) {
36
+ if (style.margin_left_auto) items[i]->margin_left = share;
37
+ if (style.margin_right_auto) items[i]->margin_right = share;
38
+ } else {
39
+ if (style.margin_top_auto) items[i]->margin_top = share;
40
+ if (style.margin_bottom_auto) items[i]->margin_bottom = share;
41
+ }
42
+ }
43
+ return true;
44
+ }
45
+
15
46
  /** Partition `basis` (outer main sizes) into lines, breaking before an item
16
47
  * that no longer fits on the current line. A single line is produced when
17
48
  * `wrap` is false. */
@@ -37,8 +68,19 @@ static std::vector<std::vector<size_t>> breakIntoLines(const std::vector<float>
37
68
 
38
69
  float Layouter::layoutFlex(LayoutBox *container, float contentWidth, float definiteHeight) const {
39
70
  const XtStyle &containerStyle = *container->style;
40
- bool isRow = containerStyle.flex_direction == FlexDirection::Row ||
41
- containerStyle.flex_direction == FlexDirection::RowReverse;
71
+ /* In a vertical writing mode the inline axis is vertical, so a `row` flex
72
+ * container stacks its items top-to-bottom (and `column` lays them out
73
+ * horizontally). */
74
+ FlexDirection direction = containerStyle.flex_direction;
75
+ if (containerStyle.writing_mode != WritingMode::HorizontalTB) {
76
+ switch (direction) {
77
+ case FlexDirection::Row: direction = FlexDirection::Column; break;
78
+ case FlexDirection::RowReverse: direction = FlexDirection::ColumnReverse; break;
79
+ case FlexDirection::Column: direction = FlexDirection::Row; break;
80
+ case FlexDirection::ColumnReverse: direction = FlexDirection::RowReverse; break;
81
+ }
82
+ }
83
+ bool isRow = direction == FlexDirection::Row || direction == FlexDirection::RowReverse;
42
84
 
43
85
  /* An absolutely positioned child is out of flow: it takes no part in the flex
44
86
  * algorithm and is placed against the container's padding box instead. */
@@ -58,15 +100,17 @@ float Layouter::layoutFlex(LayoutBox *container, float contentWidth, float defin
58
100
  px(isRow ? containerStyle.row_gap : containerStyle.column_gap, contentWidth,
59
101
  containerStyle.font_size, 0.0f);
60
102
  if (isRow)
61
- return layoutFlexRow(container, items, contentWidth, definiteHeight, mainGap, crossGap);
62
- return layoutFlexColumn(container, items, contentWidth, definiteHeight, mainGap, crossGap);
103
+ return layoutFlexRow(container, items, contentWidth, definiteHeight, mainGap, crossGap,
104
+ direction);
105
+ return layoutFlexColumn(container, items, contentWidth, definiteHeight, mainGap, crossGap,
106
+ direction);
63
107
  }
64
108
 
65
109
  float Layouter::layoutFlexRow(LayoutBox *container, const std::vector<LayoutBox *> &items,
66
110
  float contentWidth, float definiteHeight, float mainGap,
67
- float crossGap) const {
111
+ float crossGap, FlexDirection direction) const {
68
112
  const XtStyle &containerStyle = *container->style;
69
- bool reverse = containerStyle.flex_direction == FlexDirection::RowReverse;
113
+ bool reverse = direction == FlexDirection::RowReverse;
70
114
  bool wrap = containerStyle.flex_wrap != FlexWrap::NoWrap;
71
115
  bool wrapReverse = containerStyle.flex_wrap == FlexWrap::WrapReverse;
72
116
  size_t count = items.size();
@@ -169,12 +213,14 @@ float Layouter::layoutFlexRow(LayoutBox *container, const std::vector<LayoutBox
169
213
  float freeSpace = mainSize - (sumMain + mainGap * (float)(line.size() - 1));
170
214
  float offset = 0;
171
215
  float between = mainGap;
216
+ if (applyAutoMainMargins(items, line, true, freeSpace)) freeSpace = 0.0f;
172
217
  applyJustify(containerStyle.justify_content, freeSpace, line.size(), &offset, &between);
173
218
  float cursor = container->content_x + offset;
174
219
  for (size_t k = 0; k < line.size(); k++) {
175
220
  size_t i = reverse ? line[line.size() - 1 - k] : line[k];
176
- bool stretch = containerStyle.align_items == AlignItems::Stretch &&
177
- isAuto(items[i]->style->height) &&
221
+ AlignItems align = items[i]->style->has_align_self ? items[i]->style->align_self
222
+ : containerStyle.align_items;
223
+ bool stretch = align == AlignItems::Stretch && isAuto(items[i]->style->height) &&
178
224
  (definiteHeight >= 0 || lines.size() > 1);
179
225
  if (stretch) {
180
226
  float contentH = lineCross[li] - verticalEdges(items[i]);
@@ -184,12 +230,15 @@ float Layouter::layoutFlexRow(LayoutBox *container, const std::vector<LayoutBox
184
230
  definiteHeight, contentW, contentH);
185
231
  cross[i] = lineCross[li];
186
232
  }
187
- float crossOffset = crossOffsetFor(containerStyle.align_items, lineCross[li], cross[i]);
233
+ float crossOffset = crossOffsetFor(align, lineCross[li], cross[i]);
188
234
  float dx = cursor + items[i]->margin_left - items[i]->x;
189
235
  float dy =
190
236
  container->content_y + lineStart + crossOffset + items[i]->margin_top - items[i]->y;
191
237
  translate(items[i], dx, dy);
192
- cursor += mainSizes[i] + between;
238
+ float auto_extra = 0.0f;
239
+ if (items[i]->style->margin_left_auto) auto_extra += items[i]->margin_left;
240
+ if (items[i]->style->margin_right_auto) auto_extra += items[i]->margin_right;
241
+ cursor += mainSizes[i] + between + auto_extra;
193
242
  }
194
243
  lineOffset += lineCross[li] + lineStep;
195
244
  }
@@ -198,9 +247,9 @@ float Layouter::layoutFlexRow(LayoutBox *container, const std::vector<LayoutBox
198
247
 
199
248
  float Layouter::layoutFlexColumn(LayoutBox *container, const std::vector<LayoutBox *> &items,
200
249
  float contentWidth, float definiteHeight, float mainGap,
201
- float crossGap) const {
250
+ float crossGap, FlexDirection direction) const {
202
251
  const XtStyle &containerStyle = *container->style;
203
- bool reverse = containerStyle.flex_direction == FlexDirection::ColumnReverse;
252
+ bool reverse = direction == FlexDirection::ColumnReverse;
204
253
  bool wrap = containerStyle.flex_wrap != FlexWrap::NoWrap && definiteHeight >= 0;
205
254
  bool wrapReverse = containerStyle.flex_wrap == FlexWrap::WrapReverse;
206
255
  size_t count = items.size();
@@ -213,57 +262,104 @@ float Layouter::layoutFlexColumn(LayoutBox *container, const std::vector<LayoutB
213
262
  outer[i] = verticalEdges(items[i]) + items[i]->height;
214
263
  }
215
264
 
265
+ /* Main-axis basis: `flex-basis` when set, otherwise the measured height. A
266
+ * percentage basis needs a definite container height; with `height: auto` it
267
+ * behaves like `auto`, as on the row axis. */
268
+ std::vector<float> basis(count, 0.0f);
269
+ for (size_t i = 0; i < count; i++) {
270
+ const XtStyle &style = *items[i]->style;
271
+ float base = outer[i];
272
+ if (!isAuto(style.flex_basis)) {
273
+ float resolved = -1.0f;
274
+ if (style.flex_basis.unit != Unit::Percent || definiteHeight >= 0)
275
+ resolved = px(style.flex_basis, definiteHeight >= 0 ? definiteHeight : contentWidth,
276
+ style.font_size, -1.0f);
277
+ if (resolved >= 0) base = resolved + verticalEdges(items[i]);
278
+ }
279
+ basis[i] = base;
280
+ }
281
+
216
282
  if (!wrap) {
217
283
  float mainSize = definiteHeight >= 0 ? definiteHeight : -1;
218
- std::vector<float> mainSizes = outer;
284
+ std::vector<float> mainSizes = basis;
219
285
  if (mainSize >= 0) {
220
- float free = mainSize - sumOf(outer) - mainGap * (float)(count - 1);
286
+ float free = mainSize - sumOf(basis) - mainGap * (float)(count - 1);
221
287
  if (free > 0) {
222
288
  float totalGrow = 0;
223
289
  for (LayoutBox *item : items) totalGrow += item->style->flex_grow;
224
- if (totalGrow > 0) {
225
- for (size_t i = 0; i < count; i++) {
290
+ if (totalGrow > 0)
291
+ for (size_t i = 0; i < count; i++)
226
292
  mainSizes[i] += free * items[i]->style->flex_grow / totalGrow;
227
- float contentH = mainSizes[i] - verticalEdges(items[i]);
228
- if (contentH < 0) contentH = 0;
229
- layoutBlock(items[i], container->content_x, container->content_y, contentWidth,
230
- definiteHeight, -1, contentH);
293
+ } else if (free < 0) {
294
+ float totalShrink = 0;
295
+ for (size_t i = 0; i < count; i++)
296
+ totalShrink += items[i]->style->flex_shrink * basis[i];
297
+ if (totalShrink > 0)
298
+ for (size_t i = 0; i < count; i++) {
299
+ mainSizes[i] += free * (items[i]->style->flex_shrink * basis[i]) / totalShrink;
300
+ if (mainSizes[i] < 0) mainSizes[i] = 0;
231
301
  }
232
- }
233
302
  }
234
303
  } else {
235
- mainSize = sumOf(outer) + mainGap * (float)(count - 1);
304
+ mainSize = sumOf(basis) + mainGap * (float)(count - 1);
305
+ }
306
+ /* Re-lay every item out at its used height: the basis, grow and shrink
307
+ * passes above all change it. */
308
+ for (size_t i = 0; i < count; i++) {
309
+ float contentH = mainSizes[i] - verticalEdges(items[i]);
310
+ if (contentH < 0) contentH = 0;
311
+ setContainingBlock(items[i], containingBlockAncestor(items[i]));
312
+ layoutBlock(items[i], container->content_x, container->content_y, contentWidth,
313
+ definiteHeight, -1, contentH);
236
314
  }
237
315
  float freeSpace = mainSize - (sumOf(mainSizes) + mainGap * (float)(count - 1));
238
316
  float offset = 0;
239
317
  float between = mainGap;
318
+ std::vector<size_t> all(count);
319
+ for (size_t i = 0; i < count; i++) all[i] = i;
320
+ if (applyAutoMainMargins(items, all, false, freeSpace)) freeSpace = 0.0f;
240
321
  applyJustify(containerStyle.justify_content, freeSpace, count, &offset, &between);
241
322
  float cursor = container->content_y + offset;
242
323
  for (size_t k = 0; k < count; k++) {
243
324
  size_t i = reverse ? (count - 1 - k) : k;
244
- float dx = container->content_x + items[i]->margin_left - items[i]->x;
325
+ AlignItems align = items[i]->style->has_align_self ? items[i]->style->align_self
326
+ : containerStyle.align_items;
327
+ float crossSize = horizontalEdges(items[i]) + items[i]->width;
328
+ float crossOffset = crossOffsetFor(align, contentWidth, crossSize);
329
+ float dx = container->content_x + crossOffset + items[i]->margin_left - items[i]->x;
245
330
  float dy = cursor + items[i]->margin_top - items[i]->y;
246
331
  translate(items[i], dx, dy);
247
- cursor += mainSizes[i] + between;
332
+ float auto_extra = 0.0f;
333
+ if (items[i]->style->margin_top_auto) auto_extra += items[i]->margin_top;
334
+ if (items[i]->style->margin_bottom_auto) auto_extra += items[i]->margin_bottom;
335
+ cursor += mainSizes[i] + between + auto_extra;
248
336
  }
249
337
  return mainSize;
250
338
  }
251
339
 
252
340
  /* Wrapped columns: lines run top-to-bottom and are placed side by side. */
253
- std::vector<std::vector<size_t>> lines = breakIntoLines(outer, definiteHeight, mainGap, true);
341
+ std::vector<std::vector<size_t>> lines = breakIntoLines(basis, definiteHeight, mainGap, true);
254
342
  std::vector<float> mainSizes(count, 0.0f);
255
343
  std::vector<float> lineCross(lines.size(), 0.0f);
256
344
  for (size_t li = 0; li < lines.size(); li++) {
257
345
  const std::vector<size_t> &line = lines[li];
258
346
  float sumBase = 0;
259
- for (size_t i : line) sumBase += outer[i];
260
- for (size_t i : line) mainSizes[i] = outer[i];
347
+ for (size_t i : line) sumBase += basis[i];
348
+ for (size_t i : line) mainSizes[i] = basis[i];
261
349
  float free = definiteHeight - sumBase - mainGap * (float)(line.size() - 1);
262
350
  if (free > 0) {
263
351
  float totalGrow = 0;
264
352
  for (size_t i : line) totalGrow += items[i]->style->flex_grow;
265
353
  if (totalGrow > 0)
266
354
  for (size_t i : line) mainSizes[i] += free * items[i]->style->flex_grow / totalGrow;
355
+ } else if (free < 0) {
356
+ float totalShrink = 0;
357
+ for (size_t i : line) totalShrink += items[i]->style->flex_shrink * basis[i];
358
+ if (totalShrink > 0)
359
+ for (size_t i : line) {
360
+ mainSizes[i] += free * (items[i]->style->flex_shrink * basis[i]) / totalShrink;
361
+ if (mainSizes[i] < 0) mainSizes[i] = 0;
362
+ }
267
363
  }
268
364
  for (size_t i : line) {
269
365
  float contentH = mainSizes[i] - verticalEdges(items[i]);
@@ -308,14 +404,23 @@ float Layouter::layoutFlexColumn(LayoutBox *container, const std::vector<LayoutB
308
404
  float freeSpace = definiteHeight - (sumMain + mainGap * (float)(line.size() - 1));
309
405
  float offset = 0;
310
406
  float between = mainGap;
407
+ if (applyAutoMainMargins(items, line, false, freeSpace)) freeSpace = 0.0f;
311
408
  applyJustify(containerStyle.justify_content, freeSpace, line.size(), &offset, &between);
312
409
  float cursor = container->content_y + offset;
313
410
  for (size_t k = 0; k < line.size(); k++) {
314
411
  size_t i = reverse ? line[line.size() - 1 - k] : line[k];
315
- float dx = container->content_x + lineStart + items[i]->margin_left - items[i]->x;
412
+ AlignItems align = items[i]->style->has_align_self ? items[i]->style->align_self
413
+ : containerStyle.align_items;
414
+ float crossSize = horizontalEdges(items[i]) + items[i]->width;
415
+ float crossOffset = crossOffsetFor(align, lineCross[li], crossSize);
416
+ float dx =
417
+ container->content_x + lineStart + crossOffset + items[i]->margin_left - items[i]->x;
316
418
  float dy = cursor + items[i]->margin_top - items[i]->y;
317
419
  translate(items[i], dx, dy);
318
- cursor += mainSizes[i] + between;
420
+ float auto_extra = 0.0f;
421
+ if (items[i]->style->margin_top_auto) auto_extra += items[i]->margin_top;
422
+ if (items[i]->style->margin_bottom_auto) auto_extra += items[i]->margin_bottom;
423
+ cursor += mainSizes[i] + between + auto_extra;
319
424
  }
320
425
  lineOffset += lineCross[li] + lineStep;
321
426
  }
@@ -46,13 +46,19 @@ float Layouter::preferredContentWidth(const LayoutBox *box, float available) con
46
46
  if (!isAuto(style.width)) return px(style.width, available, style.font_size, 0.0f);
47
47
  if ((box->is_image || box->is_canvas || box->is_inline_svg) && box->intrinsic_width > 0)
48
48
  return box->intrinsic_width;
49
- if (box->is_text) return textWidth(box->text, style);
49
+ if (box->is_text) {
50
+ if (style.writing_mode != WritingMode::HorizontalTB) return lineHeightOf(style);
51
+ return textWidth(box->text, style);
52
+ }
50
53
  /* A row flex container's intrinsic width is the sum of its items (and gaps);
51
54
  * taking the widest child would make a shrink-to-fit toolbar narrower than
52
55
  * its buttons and then shrink every button to zero. */
53
- if (style.display == Display::Flex &&
54
- (style.flex_direction == FlexDirection::Row ||
55
- style.flex_direction == FlexDirection::RowReverse)) {
56
+ bool flex_row = style.flex_direction == FlexDirection::Row ||
57
+ style.flex_direction == FlexDirection::RowReverse;
58
+ /* In a vertical writing mode the flex row axis is vertical, so the intrinsic
59
+ * width is the widest item rather than the sum of the row. */
60
+ if (style.writing_mode != WritingMode::HorizontalTB) flex_row = !flex_row;
61
+ if (style.display == Display::Flex && flex_row) {
56
62
  float total = 0;
57
63
  int count = 0;
58
64
  for (const std::unique_ptr<LayoutBox> &child : box->children) {
@@ -70,7 +76,11 @@ float Layouter::preferredContentWidth(const LayoutBox *box, float available) con
70
76
  /* Out-of-flow children do not contribute to the parent's intrinsic width. */
71
77
  if (isAbsoluteChild(child.get())) continue;
72
78
  if (child->is_text) {
73
- best = std::max(best, textWidth(child->text, *child->style));
79
+ if (child->style->writing_mode != WritingMode::HorizontalTB) {
80
+ best = std::max(best, lineHeightOf(*child->style));
81
+ } else {
82
+ best = std::max(best, textWidth(child->text, *child->style));
83
+ }
74
84
  } else {
75
85
  best = std::max(best,
76
86
  preferredContentWidth(child.get(), available) + horizontalEdges(child.get()));
@@ -204,6 +214,9 @@ float Layouter::layoutBlock(LayoutBox *box, float x, float y, float availableWid
204
214
  * `display:grid`), and `layoutChildren` would otherwise give them no geometry
205
215
  * or fragment at all — the glyph then silently disappears from paint. */
206
216
  if (box->is_text) {
217
+ if (style.writing_mode != WritingMode::HorizontalTB) {
218
+ return layoutVerticalTextBox(box, x, y, lineHeightOf(style));
219
+ }
207
220
  float textW = textWidth(box->text, style);
208
221
  float textH = lineHeightOf(style);
209
222
  box->x = x;
@@ -260,6 +273,24 @@ float Layouter::layoutBlock(LayoutBox *box, float x, float y, float availableWid
260
273
  box->content_width = contentWidth;
261
274
  box->width =
262
275
  box->border_left + box->padding_left + contentWidth + box->padding_right + box->border_right;
276
+ /* Block-level horizontal auto margins absorb the leftover inline space; two
277
+ * auto margins centre the box. */
278
+ bool autoLeft = style.margin_left_auto;
279
+ bool autoRight = style.margin_right_auto;
280
+ if (!outOfFlow && (autoLeft || autoRight)) {
281
+ float free = availableWidth - box->width - (autoLeft ? 0.0f : box->margin_left) -
282
+ (autoRight ? 0.0f : box->margin_right);
283
+ if (free < 0.0f) free = 0.0f;
284
+ if (autoLeft && autoRight) {
285
+ box->margin_left = box->margin_right = free / 2.0f;
286
+ } else if (autoLeft) {
287
+ box->margin_left = free;
288
+ } else {
289
+ box->margin_right = free;
290
+ }
291
+ box->x = x + box->margin_left;
292
+ box->content_x = box->x + box->border_left + box->padding_left;
293
+ }
263
294
 
264
295
  float definiteHeight = -1;
265
296
  if (!isAuto(style.height) && !(style.height.unit == Unit::Percent && availableHeight < 0)) {
@@ -278,6 +309,17 @@ float Layouter::layoutBlock(LayoutBox *box, float x, float y, float availableWid
278
309
  definiteHeight = box->cb_height - top - bottom - verticalEdges(box);
279
310
  if (definiteHeight < 0) definiteHeight = 0;
280
311
  }
312
+ /* `aspect-ratio` derives the content height from the used width when the
313
+ * height is auto. (Height-to-width derivation is not implemented: no page in
314
+ * the target corpus needs it.) */
315
+ if (definiteHeight < 0 && forcedContentHeight < 0 && style.aspect_ratio > 0.0f) {
316
+ float borderWidth = box->border_left + box->padding_left + contentWidth +
317
+ box->padding_right + box->border_right;
318
+ float borderHeight = borderWidth / style.aspect_ratio;
319
+ definiteHeight = borderHeight - box->border_top - box->border_bottom - box->padding_top -
320
+ box->padding_bottom;
321
+ if (definiteHeight < 0) definiteHeight = 0;
322
+ }
281
323
 
282
324
  float contentHeight;
283
325
  if (forcedContentHeight >= 0) {
@@ -304,6 +346,8 @@ float Layouter::layoutBlock(LayoutBox *box, float x, float y, float availableWid
304
346
  box->content_height = contentHeight;
305
347
  box->height =
306
348
  box->border_top + box->padding_top + contentHeight + box->padding_bottom + box->border_bottom;
349
+ box->border_radius = xt_style_border_radius_px(style.border_radius, box->width, box->height,
350
+ em, rootFontSize, vw, vh);
307
351
  box->baseline = box->height;
308
352
  /* Now that this box has its final size, its padding box is the containing
309
353
  * block its own children resolve against — and only now can an out-of-flow
@@ -318,8 +362,11 @@ float Layouter::layoutBlock(LayoutBox *box, float x, float y, float availableWid
318
362
  }
319
363
 
320
364
  bool Layouter::isAbsoluteChild(const LayoutBox *box) {
321
- return box->style != nullptr && (box->style->position == Position::Absolute ||
322
- box->style->position == Position::Fixed);
365
+ /* A text box shares its element's style pointer, but a text node can never be
366
+ * out of flow: treating it as absolute dropped direct text out of an
367
+ * absolutely positioned box (and made a shrink-to-fit width zero). */
368
+ return box != nullptr && !box->is_text && box->style != nullptr &&
369
+ (box->style->position == Position::Absolute || box->style->position == Position::Fixed);
323
370
  }
324
371
 
325
372
  void Layouter::layoutAbsoluteChildren(LayoutBox *block, float contentWidth,
@@ -423,6 +470,10 @@ float Layouter::layoutChildren(LayoutBox *block, float contentWidth, float avail
423
470
  }
424
471
  if (display == Display::Block || display == Display::ListItem || display == Display::Flex ||
425
472
  display == Display::Grid || child->style == nullptr) {
473
+ if (childStyle != nullptr && childStyle->inline_level) {
474
+ inlineRun.push_back(child.get());
475
+ continue;
476
+ }
426
477
  flush();
427
478
  resolveEdges(child.get(), contentWidth);
428
479
  /* Adjacent vertical margins collapse: the gap is the collapsed value of
@@ -40,9 +40,15 @@ struct InlineItem {
40
40
  float width = 0;
41
41
  float height = 0;
42
42
  float ascent = 0;
43
+ /** Atomic inline boxes: `vertical-align` shift (positive raises). */
44
+ float shift = 0.0f;
43
45
  bool is_space = false;
44
46
  bool atomic = false; // inline-block
45
47
  bool force_break = false; // explicit newline (white-space: pre)
48
+ /** A character in a vertical writing mode: `width` is the column width and
49
+ * `height` the advance along the column. */
50
+ bool vertical = false;
51
+ bool upright = false;
46
52
  };
47
53
 
48
54
  /** One float taking part in the current block's formatting context. Floats are
@@ -132,6 +138,23 @@ inline float toPx(const Length &length, float base, float em, float root, float
132
138
  }
133
139
  return first ? fallback : best;
134
140
  }
141
+ case Unit::Calc: {
142
+ if (length.args.size() < 2) return fallback;
143
+ float left = toPx(length.args[0], base, em, root, vw, vh, fallback);
144
+ float right = toPx(length.args[1], base, em, root, vw, vh, fallback);
145
+ switch (length.op) {
146
+ case '+':
147
+ return left + right;
148
+ case '-':
149
+ return left - right;
150
+ case '*':
151
+ return left * right;
152
+ case '/':
153
+ return right == 0.0f ? fallback : left / right;
154
+ default:
155
+ return left;
156
+ }
157
+ }
135
158
  case Unit::Px:
136
159
  case Unit::Number:
137
160
  return length.value;
@@ -174,6 +197,7 @@ inline FontSpec specOf(const XtStyle &style) {
174
197
  spec.pixel_size = style.font_size;
175
198
  spec.weight = style.font_weight;
176
199
  spec.italic = style.font_style == FontStyle::Italic;
200
+ spec.letter_spacing = style.letter_spacing.unit == Unit::Px ? style.letter_spacing.value : 0.0f;
177
201
  return spec;
178
202
  }
179
203
 
@@ -259,6 +283,9 @@ struct Layouter {
259
283
 
260
284
  void flattenInline(LayoutBox *box, float contentWidth, std::vector<InlineItem> &out) const;
261
285
 
286
+ /** Flatten a text box in a vertical writing mode into per-character items. */
287
+ void flattenVerticalText(LayoutBox *box, std::vector<InlineItem> &out) const;
288
+
262
289
  void computeInlineGeometry(LayoutBox *box) const;
263
290
 
264
291
  /** Lay out an inline formatting context; returns its consumed height. When
@@ -267,6 +294,14 @@ struct Layouter {
267
294
  float layoutInlineRun(LayoutBox *container, const std::vector<LayoutBox *> &run,
268
295
  float contentWidth, float y, const FloatContext *floats = nullptr) const;
269
296
 
297
+ /** Vertical writing mode: place already-flattened character items in columns
298
+ * (right-to-left for `vertical-rl`). Returns the consumed height. */
299
+ float layoutVerticalRun(LayoutBox *container, const std::vector<InlineItem> &items,
300
+ float contentWidth, float y) const;
301
+
302
+ /** Lay out a text box whose style has a vertical writing mode. */
303
+ float layoutVerticalTextBox(LayoutBox *box, float x, float y, float width) const;
304
+
270
305
  float layoutFlex(LayoutBox *container, float contentWidth, float definiteHeight) const;
271
306
 
272
307
  /** Grid formatting context (explicit/implicit tracks, gaps, auto-placement
@@ -276,13 +311,13 @@ struct Layouter {
276
311
  /** Flex row main-axis layout, wrapping items into lines when `flex-wrap`
277
312
  * allows it. Returns the used cross size. */
278
313
  float layoutFlexRow(LayoutBox *container, const std::vector<LayoutBox *> &items,
279
- float contentWidth, float definiteHeight, float mainGap,
280
- float crossGap) const;
314
+ float contentWidth, float definiteHeight, float mainGap, float crossGap,
315
+ FlexDirection direction) const;
281
316
 
282
317
  /** Flex column main-axis layout (wrapping needs a definite height). */
283
318
  float layoutFlexColumn(LayoutBox *container, const std::vector<LayoutBox *> &items,
284
- float contentWidth, float definiteHeight, float mainGap,
285
- float crossGap) const;
319
+ float contentWidth, float definiteHeight, float mainGap, float crossGap,
320
+ FlexDirection direction) const;
286
321
 
287
322
  static float sumOf(const std::vector<float> &values);
288
323
 
@@ -301,6 +301,37 @@ float Layouter::layoutGrid(LayoutBox *container, float contentWidth,
301
301
  }
302
302
  }
303
303
 
304
+ /* A definite container height is the free space an `fr` row divides, exactly
305
+ * like an `fr` column above. Size the fixed/auto rows first, then share what
306
+ * is left by flex fraction; without this a grid whose only height comes from
307
+ * `aspect-ratio` collapses its `repeat(n, 1fr)` rows to their content. */
308
+ float rowFr = 0;
309
+ for (const GridTrack &row : rows) rowFr += row.fr;
310
+ if (rowFr > 0 && definiteHeight >= 0) {
311
+ float fixedRows = gapY * (float)(rows.size() - 1);
312
+ for (size_t r = 0; r < rows.size(); r++)
313
+ if (rows[r].fr <= 0) fixedRows += rowHeight[r];
314
+ float remaining = definiteHeight - fixedRows;
315
+ if (remaining < 0) remaining = 0;
316
+ for (int pass = 0; pass < 2; pass++) {
317
+ float used = 0;
318
+ for (size_t r = 0; r < rows.size(); r++) {
319
+ if (rows[r].fr <= 0) continue;
320
+ float share = remaining * rows[r].fr / rowFr;
321
+ float minimum = trackPx(*this, rows[r], false, contentWidth, style.font_size);
322
+ if (minimum > share) share = minimum;
323
+ rowHeight[r] = share;
324
+ used += share;
325
+ }
326
+ if (used <= remaining + 0.01f) break;
327
+ /* An intrinsic minimum forced a deficit; shrink the fr share back. */
328
+ float scale = remaining / used;
329
+ for (size_t r = 0; r < rows.size(); r++)
330
+ if (rows[r].fr > 0) rowHeight[r] *= scale;
331
+ break;
332
+ }
333
+ }
334
+
304
335
  float totalHeight = gapY * (float)(rows.size() - 1);
305
336
  for (float value : rowHeight) totalHeight += value;
306
337
  if (rows.size() == 1 && definiteHeight >= 0 && definiteHeight > totalHeight)