xbintsc 0.3.14 → 0.3.16

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 (201) hide show
  1. package/README.md +22 -22
  2. package/README.zh-CN.md +20 -19
  3. package/bin/xbintsc.js +1 -2
  4. package/dist/src/cli/main.js +8 -5
  5. package/dist/src/cli/main.js.map +1 -1
  6. package/dist/src/codegen/generator/state.js +8 -0
  7. package/dist/src/codegen/generator/state.js.map +1 -1
  8. package/dist/src/codegen/generator/tables.d.ts +5 -6
  9. package/dist/src/codegen/generator/tables.js +13 -6
  10. package/dist/src/codegen/generator/tables.js.map +1 -1
  11. package/dist/src/driver/bundler/graph.d.ts +8 -0
  12. package/dist/src/driver/bundler/graph.js +85 -0
  13. package/dist/src/driver/bundler/graph.js.map +1 -0
  14. package/dist/src/driver/bundler/merge.d.ts +11 -0
  15. package/dist/src/driver/bundler/merge.js +288 -0
  16. package/dist/src/driver/bundler/merge.js.map +1 -0
  17. package/dist/src/driver/bundler/resolve.d.ts +12 -0
  18. package/dist/src/driver/bundler/resolve.js +246 -0
  19. package/dist/src/driver/bundler/resolve.js.map +1 -0
  20. package/dist/src/driver/bundler/symbols.d.ts +15 -0
  21. package/dist/src/driver/bundler/symbols.js +51 -0
  22. package/dist/src/driver/bundler/symbols.js.map +1 -0
  23. package/dist/src/driver/bundler/types.d.ts +36 -0
  24. package/dist/src/driver/bundler/types.js +5 -0
  25. package/dist/src/driver/bundler/types.js.map +1 -0
  26. package/dist/src/driver/index.d.ts +1 -2
  27. package/dist/src/driver/index.js +1 -2
  28. package/dist/src/driver/index.js.map +1 -1
  29. package/dist/src/driver/modules.d.ts +9 -16
  30. package/dist/src/driver/modules.js +8 -644
  31. package/dist/src/driver/modules.js.map +1 -1
  32. package/dist/src/driver/paths.d.ts +0 -9
  33. package/dist/src/driver/paths.js +0 -31
  34. package/dist/src/driver/paths.js.map +1 -1
  35. package/dist/src/driver/toolchain-provider.d.ts +10 -10
  36. package/dist/src/driver/toolchain-provider.js +9 -38
  37. package/dist/src/driver/toolchain-provider.js.map +1 -1
  38. package/dist/src/driver/toolchain.js +8 -2
  39. package/dist/src/driver/toolchain.js.map +1 -1
  40. package/dist/src/extensions/catalog.js +2 -1
  41. package/dist/src/extensions/catalog.js.map +1 -1
  42. package/dist/src/extensions/gui/index.d.ts +24 -0
  43. package/dist/src/extensions/gui/index.js +101 -0
  44. package/dist/src/extensions/gui/index.js.map +1 -0
  45. package/dist/src/extensions/native.d.ts +2 -3
  46. package/dist/src/extensions/native.js +2 -3
  47. package/dist/src/extensions/native.js.map +1 -1
  48. package/dist/tests/driver/paths.test.js +1 -9
  49. package/dist/tests/driver/paths.test.js.map +1 -1
  50. package/dist/tests/driver/toolchain-provider.test.js +12 -79
  51. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  52. package/dist/tests/e2e/compile-basics.test.d.ts +4 -0
  53. package/dist/tests/e2e/compile-basics.test.js +134 -0
  54. package/dist/tests/e2e/compile-basics.test.js.map +1 -0
  55. package/dist/tests/e2e/compile-builtins.test.d.ts +4 -0
  56. package/dist/tests/e2e/compile-builtins.test.js +191 -0
  57. package/dist/tests/e2e/compile-builtins.test.js.map +1 -0
  58. package/dist/tests/e2e/compile-control-flow.test.d.ts +4 -0
  59. package/dist/tests/e2e/compile-control-flow.test.js +190 -0
  60. package/dist/tests/e2e/compile-control-flow.test.js.map +1 -0
  61. package/dist/tests/e2e/compile-modules.test.d.ts +4 -0
  62. package/dist/tests/e2e/compile-modules.test.js +48 -0
  63. package/dist/tests/e2e/compile-modules.test.js.map +1 -0
  64. package/dist/tests/e2e/compile-objects-classes.test.d.ts +4 -0
  65. package/dist/tests/e2e/compile-objects-classes.test.js +362 -0
  66. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -0
  67. package/dist/tests/e2e/differential-helpers.d.ts +12 -0
  68. package/dist/tests/e2e/differential-helpers.js +16 -0
  69. package/dist/tests/e2e/differential-helpers.js.map +1 -0
  70. package/dist/tests/e2e/differential-language.test.d.ts +4 -0
  71. package/dist/tests/e2e/{differential.test.js → differential-language.test.js} +5 -287
  72. package/dist/tests/e2e/differential-language.test.js.map +1 -0
  73. package/dist/tests/e2e/differential-symbols.test.d.ts +4 -0
  74. package/dist/tests/e2e/differential-symbols.test.js +92 -0
  75. package/dist/tests/e2e/differential-symbols.test.js.map +1 -0
  76. package/dist/tests/e2e/differential-values.test.d.ts +4 -0
  77. package/dist/tests/e2e/differential-values.test.js +197 -0
  78. package/dist/tests/e2e/differential-values.test.js.map +1 -0
  79. package/dist/tests/e2e/gui.test.d.ts +12 -0
  80. package/dist/tests/e2e/gui.test.js +493 -0
  81. package/dist/tests/e2e/gui.test.js.map +1 -0
  82. package/dist/tests/e2e/native-extensions.test.d.ts +4 -5
  83. package/dist/tests/e2e/native-extensions.test.js +6 -7
  84. package/dist/tests/e2e/native-extensions.test.js.map +1 -1
  85. package/dist/tests/e2e/runtime-coverage-buffer.test.d.ts +4 -0
  86. package/dist/tests/e2e/runtime-coverage-buffer.test.js +97 -0
  87. package/dist/tests/e2e/runtime-coverage-buffer.test.js.map +1 -0
  88. package/dist/tests/e2e/runtime-coverage-globals.test.d.ts +4 -0
  89. package/dist/tests/e2e/runtime-coverage-globals.test.js +149 -0
  90. package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -0
  91. package/dist/tests/e2e/runtime-coverage-node-modules.test.d.ts +4 -0
  92. package/dist/tests/e2e/runtime-coverage-node-modules.test.js +177 -0
  93. package/dist/tests/e2e/runtime-coverage-node-modules.test.js.map +1 -0
  94. package/dist/tests/e2e/runtime-coverage-streams.test.d.ts +4 -0
  95. package/dist/tests/e2e/runtime-coverage-streams.test.js +161 -0
  96. package/dist/tests/e2e/runtime-coverage-streams.test.js.map +1 -0
  97. package/dist/tests/e2e/runtime-coverage-system.test.d.ts +4 -0
  98. package/dist/tests/e2e/runtime-coverage-system.test.js +91 -0
  99. package/dist/tests/e2e/runtime-coverage-system.test.js.map +1 -0
  100. package/dist/tests/e2e/timers.test.d.ts +7 -0
  101. package/dist/tests/e2e/timers.test.js +63 -0
  102. package/dist/tests/e2e/timers.test.js.map +1 -0
  103. package/package.json +2 -2
  104. package/runtime/ext_gui/css.cpp +743 -0
  105. package/runtime/ext_gui/css.h +98 -0
  106. package/runtime/ext_gui/document.cpp +264 -0
  107. package/runtime/ext_gui/document.h +89 -0
  108. package/runtime/ext_gui/dom.cpp +472 -0
  109. package/runtime/ext_gui/dom.h +71 -0
  110. package/runtime/ext_gui/gui.cpp +457 -0
  111. package/runtime/ext_gui/gui_engine.h +81 -0
  112. package/runtime/ext_gui/image.cpp +125 -0
  113. package/runtime/ext_gui/image.h +42 -0
  114. package/runtime/ext_gui/layout.cpp +748 -0
  115. package/runtime/ext_gui/layout.h +93 -0
  116. package/runtime/ext_gui/paint.cpp +137 -0
  117. package/runtime/ext_gui/paint.h +75 -0
  118. package/runtime/ext_gui/renderer.cpp +959 -0
  119. package/runtime/ext_gui/renderer.h +68 -0
  120. package/runtime/ext_gui/stb_image.h +7988 -0
  121. package/runtime/ext_gui/style.cpp +771 -0
  122. package/runtime/ext_gui/style.h +117 -0
  123. package/runtime/ext_gui/text.cpp +378 -0
  124. package/runtime/ext_gui/text.h +78 -0
  125. package/runtime/ext_gui/window.cpp +405 -0
  126. package/runtime/ext_node/fs/fs_common.h +13 -527
  127. package/runtime/ext_node/fs/meta_ops.c +3 -3
  128. package/runtime/ext_node/fs/parts/buffer-bridge.inc +6 -0
  129. package/runtime/ext_node/fs/parts/builtins.inc +69 -0
  130. package/runtime/ext_node/fs/parts/dir.inc +47 -0
  131. package/runtime/ext_node/fs/parts/encoding.inc +186 -0
  132. package/runtime/ext_node/fs/parts/errors.inc +66 -0
  133. package/runtime/ext_node/fs/parts/flags.inc +39 -0
  134. package/runtime/ext_node/fs/parts/platform.inc +86 -0
  135. package/runtime/ext_node/fs/parts/promisify.inc +15 -0
  136. package/runtime/ext_node/fs/parts/time.inc +9 -0
  137. package/runtime/rt.h +36 -4
  138. package/runtime/rt_internal.h +10 -23
  139. package/runtime/xt_builtins.c +4 -0
  140. package/runtime/xt_containers/access.inc +168 -0
  141. package/runtime/xt_containers/arrays.inc +108 -0
  142. package/runtime/xt_containers/boxes.inc +11 -0
  143. package/runtime/xt_containers/objects.inc +326 -0
  144. package/runtime/xt_containers.c +8 -618
  145. package/runtime/xt_ext.h +0 -1
  146. package/runtime/xt_io/base64.inc +87 -0
  147. package/runtime/xt_io/console.inc +119 -0
  148. package/runtime/xt_io/exception.inc +142 -0
  149. package/runtime/xt_io/inspect.inc +212 -0
  150. package/runtime/xt_io.c +11 -556
  151. package/runtime/xt_loop.c +262 -24
  152. package/runtime/xt_regex/api.inc +58 -0
  153. package/runtime/xt_regex/helpers.inc +68 -0
  154. package/runtime/xt_regex/matcher.inc +248 -0
  155. package/runtime/xt_regex/parser.inc +236 -0
  156. package/runtime/xt_regex/types.inc +58 -0
  157. package/runtime/xt_regex.h +9 -672
  158. package/runtime/xt_stdlib2/helpers.inc +2 -4
  159. package/runtime/xt_values/arithmetic.inc +150 -0
  160. package/runtime/xt_values/comparison.inc +112 -0
  161. package/runtime/xt_values/constructors.inc +41 -0
  162. package/runtime/xt_values/conversions.inc +115 -0
  163. package/runtime/xt_values/number-format.inc +180 -0
  164. package/runtime/xt_values/strings.inc +22 -0
  165. package/runtime/xt_values.c +10 -626
  166. package/scripts/build-gui.ts +317 -0
  167. package/scripts/build-runtime.ts +7 -8
  168. package/scripts/coverage-runtime.ts +4 -4
  169. package/scripts/package-release.ts +12 -84
  170. package/src/cli/main.ts +8 -4
  171. package/src/codegen/generator/state.ts +8 -0
  172. package/src/codegen/generator/tables.ts +13 -6
  173. package/src/driver/bundler/graph.ts +95 -0
  174. package/src/driver/bundler/merge.ts +312 -0
  175. package/src/driver/bundler/resolve.ts +240 -0
  176. package/src/driver/bundler/symbols.ts +55 -0
  177. package/src/driver/bundler/types.ts +37 -0
  178. package/src/driver/index.ts +1 -9
  179. package/src/driver/modules.ts +9 -693
  180. package/src/driver/paths.ts +0 -31
  181. package/src/driver/toolchain-provider.ts +12 -45
  182. package/src/driver/toolchain.ts +7 -2
  183. package/src/extensions/catalog.ts +2 -1
  184. package/src/extensions/gui/index.ts +107 -0
  185. package/src/extensions/native.ts +2 -3
  186. package/dist/src/driver/toolchain-download.d.ts +0 -50
  187. package/dist/src/driver/toolchain-download.js +0 -102
  188. package/dist/src/driver/toolchain-download.js.map +0 -1
  189. package/dist/tests/driver/toolchain-download.test.d.ts +0 -1
  190. package/dist/tests/driver/toolchain-download.test.js +0 -53
  191. package/dist/tests/driver/toolchain-download.test.js.map +0 -1
  192. package/dist/tests/e2e/compile.test.d.ts +0 -8
  193. package/dist/tests/e2e/compile.test.js +0 -893
  194. package/dist/tests/e2e/compile.test.js.map +0 -1
  195. package/dist/tests/e2e/differential.test.d.ts +0 -7
  196. package/dist/tests/e2e/differential.test.js.map +0 -1
  197. package/dist/tests/e2e/runtime-coverage.test.d.ts +0 -11
  198. package/dist/tests/e2e/runtime-coverage.test.js +0 -646
  199. package/dist/tests/e2e/runtime-coverage.test.js.map +0 -1
  200. package/scripts/fetch-toolchain.ts +0 -101
  201. package/src/driver/toolchain-download.ts +0 -135
@@ -0,0 +1,748 @@
1
+ /*
2
+ * xbintsc GUI engine — layout implementation.
3
+ *
4
+ * The algorithm is intentionally a pragmatic subset:
5
+ * - Block flow: consecutive inline-level children form an anonymous inline
6
+ * formatting context; block-level children are stacked vertically.
7
+ * - Inline flow: greedy, word-based line breaking with `text-align`; text is
8
+ * measured with the HarfBuzz/FreeType stack in `text.h`.
9
+ * - Flexbox: single-line row/column with grow/shrink, gap, justify-content
10
+ * and align-items.
11
+ * Margins do not collapse yet, and only static positioning is honoured.
12
+ */
13
+
14
+ #include "layout.h"
15
+
16
+ #include <algorithm>
17
+ #include <cfloat>
18
+ #include <cstring>
19
+
20
+ #include "text.h"
21
+ #include "image.h"
22
+
23
+ namespace xtgui {
24
+ namespace {
25
+
26
+ constexpr float kHuge = 1e18f;
27
+
28
+ struct InlineItem {
29
+ LayoutBox *box = nullptr;
30
+ const XtStyle *style = nullptr;
31
+ std::string text;
32
+ float width = 0;
33
+ float height = 0;
34
+ float ascent = 0;
35
+ bool is_space = false;
36
+ bool atomic = false; // inline-block
37
+ bool force_break = false; // explicit newline (white-space: pre)
38
+ };
39
+
40
+ float toPx(const Length &length, float base, float em, float root, float vw, float vh, float fallback) {
41
+ switch (length.unit) {
42
+ case Unit::Px:
43
+ case Unit::Number:
44
+ return length.value;
45
+ case Unit::Percent:
46
+ return base * length.value / 100.0f;
47
+ case Unit::Em:
48
+ return em * length.value;
49
+ case Unit::Rem:
50
+ return root * length.value;
51
+ case Unit::Vw:
52
+ return vw * length.value / 100.0f;
53
+ case Unit::Vh:
54
+ return vh * length.value / 100.0f;
55
+ default:
56
+ return fallback;
57
+ }
58
+ }
59
+
60
+ bool isAuto(const Length &length) {
61
+ return length.unit == Unit::Auto || length.unit == Unit::Invalid || length.unit == Unit::None;
62
+ }
63
+
64
+ /* Per-character advance as a fraction of the font size is no longer needed
65
+ * here: text metrics come from the shaping stack (see `text.h`). */
66
+
67
+ FontSpec specOf(const XtStyle &style) {
68
+ FontSpec spec;
69
+ spec.family = style.font_family;
70
+ spec.pixel_size = style.font_size;
71
+ spec.weight = style.font_weight;
72
+ spec.italic = style.font_style == FontStyle::Italic;
73
+ return spec;
74
+ }
75
+
76
+ float lineHeightOf(const XtStyle &style) {
77
+ if (style.line_height.unit == Unit::Number) {
78
+ return style.line_height.value > 0 ? style.line_height.value * style.font_size : style.font_size * 1.2f;
79
+ }
80
+ if (style.line_height.unit == Unit::Px) return style.line_height.value;
81
+ float lineHeight = 0;
82
+ xt_text_metrics(specOf(style), nullptr, nullptr, &lineHeight);
83
+ return lineHeight > 0 ? lineHeight : style.font_size * 1.2f;
84
+ }
85
+
86
+ float ascentOf(const XtStyle &style) {
87
+ float ascent = 0;
88
+ xt_text_metrics(specOf(style), &ascent, nullptr, nullptr);
89
+ return ascent > 0 ? ascent : style.font_size * 0.8f;
90
+ }
91
+
92
+ struct Layouter {
93
+ const std::unordered_map<const Node *, XtStyle> *styles = nullptr;
94
+ LayoutTree *tree = nullptr;
95
+ float vw = 0;
96
+ float vh = 0;
97
+ float rootFontSize = 16.0f;
98
+
99
+ const XtStyle *styleOf(const Node *node) const {
100
+ auto it = styles->find(node);
101
+ return it == styles->end() ? nullptr : &it->second;
102
+ }
103
+
104
+ float px(const Length &length, float base, float em, float fallback = 0.0f) const {
105
+ return toPx(length, base, em, rootFontSize, vw, vh, fallback);
106
+ }
107
+
108
+ float clampSize(float value, const Length &minLength, const Length &maxLength, float base,
109
+ float em) const {
110
+ if (!isAuto(minLength)) value = std::max(value, px(minLength, base, em, value));
111
+ if (!isAuto(maxLength)) value = std::min(value, px(maxLength, base, em, value));
112
+ return value;
113
+ }
114
+
115
+ float textWidth(const std::string &text, const XtStyle &style) const {
116
+ return xt_text_measure_width(text, specOf(style));
117
+ }
118
+
119
+ float horizontalEdges(const LayoutBox *box) const {
120
+ return box->margin_left + box->margin_right + box->border_left + box->border_right +
121
+ box->padding_left + box->padding_right;
122
+ }
123
+
124
+ float verticalEdges(const LayoutBox *box) const {
125
+ return box->margin_top + box->margin_bottom + box->border_top + box->border_bottom +
126
+ box->padding_top + box->padding_bottom;
127
+ }
128
+
129
+ float preferredContentWidth(const LayoutBox *box, float available) const {
130
+ const XtStyle &style = *box->style;
131
+ if (!isAuto(style.width)) return px(style.width, available, style.font_size, 0.0f);
132
+ if (box->is_image && box->intrinsic_width > 0) return box->intrinsic_width;
133
+ float best = 0;
134
+ for (const std::unique_ptr<LayoutBox> &child : box->children) {
135
+ if (child->display == Display::None) continue;
136
+ if (child->is_text) {
137
+ best = std::max(best, textWidth(child->text, *child->style));
138
+ } else {
139
+ best = std::max(best, preferredContentWidth(child.get(), available) + horizontalEdges(child.get()));
140
+ }
141
+ }
142
+ return best;
143
+ }
144
+
145
+ void translate(LayoutBox *box, float dx, float dy) const {
146
+ box->x += dx;
147
+ box->y += dy;
148
+ box->content_x += dx;
149
+ box->content_y += dy;
150
+ for (TextFragment &fragment : box->fragments) {
151
+ fragment.x += dx;
152
+ fragment.y += dy;
153
+ }
154
+ for (std::unique_ptr<LayoutBox> &child : box->children) translate(child.get(), dx, dy);
155
+ }
156
+
157
+ void resolveEdges(LayoutBox *box, float containingWidth) const {
158
+ const XtStyle &style = *box->style;
159
+ float em = style.font_size;
160
+ box->margin_top = px(style.margin.top, containingWidth, em);
161
+ box->margin_right = px(style.margin.right, containingWidth, em);
162
+ box->margin_bottom = px(style.margin.bottom, containingWidth, em);
163
+ box->margin_left = px(style.margin.left, containingWidth, em);
164
+ box->padding_top = px(style.padding.top, containingWidth, em);
165
+ box->padding_right = px(style.padding.right, containingWidth, em);
166
+ box->padding_bottom = px(style.padding.bottom, containingWidth, em);
167
+ box->padding_left = px(style.padding.left, containingWidth, em);
168
+ if (style.border_style == BorderStyle::None) {
169
+ box->border_top = box->border_right = box->border_bottom = box->border_left = 0;
170
+ } else {
171
+ box->border_top = px(style.border_width.top, containingWidth, em);
172
+ box->border_right = px(style.border_width.right, containingWidth, em);
173
+ box->border_bottom = px(style.border_width.bottom, containingWidth, em);
174
+ box->border_left = px(style.border_width.left, containingWidth, em);
175
+ }
176
+ }
177
+
178
+ /** Lay out `box` at (x, y) and return its outer height (margins included). */
179
+ float layoutBlock(LayoutBox *box, float x, float y, float availableWidth, float availableHeight,
180
+ float forcedContentWidth = -1, float forcedContentHeight = -1) const {
181
+ const XtStyle &style = *box->style;
182
+ float em = style.font_size;
183
+ resolveEdges(box, availableWidth);
184
+
185
+ float contentWidth;
186
+ if (forcedContentWidth >= 0) {
187
+ contentWidth = forcedContentWidth;
188
+ } else if (isAuto(style.width)) {
189
+ contentWidth = availableWidth - horizontalEdges(box);
190
+ } else {
191
+ contentWidth = px(style.width, availableWidth, em);
192
+ }
193
+ contentWidth = clampSize(contentWidth, style.min_width, style.max_width, availableWidth, em);
194
+ if (contentWidth < 0) contentWidth = 0;
195
+
196
+ box->x = x + box->margin_left;
197
+ box->y = y + box->margin_top;
198
+ box->content_x = box->x + box->border_left + box->padding_left;
199
+ box->content_y = box->y + box->border_top + box->padding_top;
200
+ box->content_width = contentWidth;
201
+ box->width = box->border_left + box->padding_left + contentWidth + box->padding_right + box->border_right;
202
+
203
+ float definiteHeight = -1;
204
+ if (!isAuto(style.height) && !(style.height.unit == Unit::Percent && availableHeight < 0)) {
205
+ definiteHeight = px(style.height, availableHeight, em, -1);
206
+ }
207
+
208
+ float contentHeight;
209
+ if (forcedContentHeight >= 0) {
210
+ layoutChildren(box, contentWidth, availableHeight, forcedContentHeight);
211
+ contentHeight = forcedContentHeight;
212
+ } else {
213
+ contentHeight = layoutChildren(box, contentWidth, availableHeight, definiteHeight);
214
+ if (definiteHeight >= 0) contentHeight = definiteHeight;
215
+ contentHeight = clampSize(contentHeight, style.min_height, style.max_height,
216
+ availableHeight < 0 ? contentHeight : availableHeight, em);
217
+ if (contentHeight < 0) contentHeight = 0;
218
+ }
219
+ /* Replaced elements keep their aspect ratio when only one axis is set. */
220
+ if (box->is_image && forcedContentHeight < 0 && isAuto(style.height) &&
221
+ box->intrinsic_width > 0 && box->intrinsic_height > 0) {
222
+ contentHeight = contentWidth * box->intrinsic_height / box->intrinsic_width;
223
+ }
224
+ box->content_height = contentHeight;
225
+ box->height = box->border_top + box->padding_top + contentHeight + box->padding_bottom + box->border_bottom;
226
+ box->baseline = box->height;
227
+ return box->margin_top + box->height + box->margin_bottom;
228
+ }
229
+
230
+ /** Lay out the children of `block`; returns the used content height. */
231
+ float layoutChildren(LayoutBox *block, float contentWidth, float availableHeight,
232
+ float definiteHeight) const {
233
+ if (block->style->display == Display::Flex) return layoutFlex(block, contentWidth, definiteHeight);
234
+
235
+ float y = block->content_y;
236
+ float startY = y;
237
+ std::vector<LayoutBox *> inlineRun;
238
+ auto flush = [&]() {
239
+ if (inlineRun.empty()) return;
240
+ y += layoutInlineRun(block, inlineRun, contentWidth, y);
241
+ inlineRun.clear();
242
+ };
243
+ for (const std::unique_ptr<LayoutBox> &child : block->children) {
244
+ Display display = child->display;
245
+ if (display == Display::None) continue;
246
+ if (display == Display::Block || display == Display::ListItem || display == Display::Flex) {
247
+ flush();
248
+ y += layoutBlock(child.get(), block->content_x, y, contentWidth, availableHeight);
249
+ } else {
250
+ inlineRun.push_back(child.get());
251
+ }
252
+ }
253
+ flush();
254
+ return y - startY;
255
+ }
256
+
257
+ void flattenInline(LayoutBox *box, float contentWidth, std::vector<InlineItem> &out) const {
258
+ const XtStyle &style = *box->style;
259
+ if (box->is_text) {
260
+ float height = lineHeightOf(style);
261
+ float ascent = ascentOf(style);
262
+ const std::string &text = box->text;
263
+ size_t i = 0;
264
+ while (i < text.size()) {
265
+ if (text[i] == '\n') {
266
+ InlineItem br;
267
+ br.box = box;
268
+ br.style = &style;
269
+ br.force_break = true;
270
+ out.push_back(std::move(br));
271
+ i++;
272
+ continue;
273
+ }
274
+ bool space = text[i] == ' ' || text[i] == '\t';
275
+ size_t j = i;
276
+ while (j < text.size() && text[j] != '\n' && (text[j] == ' ' || text[j] == '\t') == space) j++;
277
+ InlineItem item;
278
+ item.box = box;
279
+ item.style = &style;
280
+ item.text = text.substr(i, j - i);
281
+ item.is_space = space;
282
+ item.width = textWidth(item.text, style);
283
+ item.height = height;
284
+ item.ascent = ascent;
285
+ out.push_back(std::move(item));
286
+ i = j;
287
+ }
288
+ return;
289
+ }
290
+ if (box->display == Display::InlineBlock) {
291
+ float available = contentWidth;
292
+ float intrinsic = preferredContentWidth(box, available);
293
+ layoutBlock(box, 0, 0, available, -1, intrinsic);
294
+ InlineItem item;
295
+ item.box = box;
296
+ item.style = &style;
297
+ item.atomic = true;
298
+ item.width = box->margin_left + box->width + box->margin_right;
299
+ item.height = box->margin_top + box->height + box->margin_bottom;
300
+ item.ascent = item.height;
301
+ out.push_back(std::move(item));
302
+ return;
303
+ }
304
+ /* Inline element: flatten its subtree. */
305
+ for (const std::unique_ptr<LayoutBox> &child : box->children) {
306
+ if (child->display == Display::None) continue;
307
+ flattenInline(child.get(), contentWidth, out);
308
+ }
309
+ }
310
+
311
+ void computeInlineGeometry(LayoutBox *box) const {
312
+ if (box->is_text) {
313
+ if (box->fragments.empty()) return;
314
+ float minx = kHuge, miny = kHuge, maxx = -kHuge, maxy = -kHuge;
315
+ for (const TextFragment &fragment : box->fragments) {
316
+ minx = std::min(minx, fragment.x);
317
+ miny = std::min(miny, fragment.y);
318
+ maxx = std::max(maxx, fragment.x + fragment.width);
319
+ maxy = std::max(maxy, fragment.y + fragment.height);
320
+ }
321
+ box->x = minx;
322
+ box->y = miny;
323
+ box->width = maxx - minx;
324
+ box->height = maxy - miny;
325
+ box->content_x = minx;
326
+ box->content_y = miny;
327
+ box->content_width = box->width;
328
+ box->content_height = box->height;
329
+ return;
330
+ }
331
+ float minx = kHuge, miny = kHuge, maxx = -kHuge, maxy = -kHuge;
332
+ bool any = false;
333
+ for (const std::unique_ptr<LayoutBox> &child : box->children) {
334
+ if (child->display == Display::None) continue;
335
+ computeInlineGeometry(child.get());
336
+ if (child->is_text && child->fragments.empty()) continue;
337
+ if (child->width <= 0 && child->height <= 0 && !child->is_text && child->children.empty()) continue;
338
+ any = true;
339
+ minx = std::min(minx, child->x);
340
+ miny = std::min(miny, child->y);
341
+ maxx = std::max(maxx, child->x + child->width);
342
+ maxy = std::max(maxy, child->y + child->height);
343
+ }
344
+ if (!any) return;
345
+ box->x = minx;
346
+ box->y = miny;
347
+ box->width = maxx - minx;
348
+ box->height = maxy - miny;
349
+ box->content_x = minx;
350
+ box->content_y = miny;
351
+ box->content_width = box->width;
352
+ box->content_height = box->height;
353
+ }
354
+
355
+ /** Lay out an inline formatting context; returns its consumed height. */
356
+ float layoutInlineRun(LayoutBox *container, const std::vector<LayoutBox *> &run, float contentWidth,
357
+ float y) const {
358
+ std::vector<InlineItem> items;
359
+ for (LayoutBox *box : run) flattenInline(box, contentWidth, items);
360
+ if (items.empty()) return 0;
361
+
362
+ std::vector<std::vector<InlineItem *>> lines;
363
+ std::vector<InlineItem *> line;
364
+ float lineWidth = 0;
365
+ auto pushLine = [&]() {
366
+ while (!line.empty() && line.back()->is_space) {
367
+ lineWidth -= line.back()->width;
368
+ line.pop_back();
369
+ }
370
+ if (!line.empty()) lines.push_back(line);
371
+ line.clear();
372
+ lineWidth = 0;
373
+ };
374
+ for (InlineItem &item : items) {
375
+ if (item.force_break) {
376
+ pushLine();
377
+ continue;
378
+ }
379
+ if (item.is_space && line.empty()) continue; // no leading space on a line
380
+ if (lineWidth + item.width > contentWidth && !line.empty()) pushLine();
381
+ if (item.is_space && line.empty()) continue;
382
+ line.push_back(&item);
383
+ lineWidth += item.width;
384
+ }
385
+ pushLine();
386
+
387
+ const XtStyle &containerStyle = *container->style;
388
+ float cursorY = y;
389
+ for (size_t index = 0; index < lines.size(); index++) {
390
+ std::vector<InlineItem *> &current = lines[index];
391
+ float width = 0;
392
+ float ascent = 0;
393
+ float descent = 0;
394
+ for (InlineItem *item : current) {
395
+ width += item->width;
396
+ ascent = std::max(ascent, item->ascent);
397
+ descent = std::max(descent, item->height - item->ascent);
398
+ }
399
+ float height = ascent + descent;
400
+ float x = container->content_x;
401
+ if (containerStyle.text_align == TextAlign::Center) {
402
+ x += (contentWidth - width) / 2.0f;
403
+ } else if (containerStyle.text_align == TextAlign::End) {
404
+ x += contentWidth - width;
405
+ }
406
+ for (InlineItem *item : current) {
407
+ if (item->atomic) {
408
+ float dx = x + item->box->margin_left - item->box->x;
409
+ float dy = cursorY + item->box->margin_top - item->box->y;
410
+ translate(item->box, dx, dy);
411
+ } else {
412
+ item->box->fragments.push_back({x, cursorY, item->width, height, item->text});
413
+ }
414
+ x += item->width;
415
+ }
416
+ cursorY += height;
417
+ }
418
+ for (LayoutBox *box : run) computeInlineGeometry(box);
419
+ return cursorY - y;
420
+ }
421
+
422
+ float layoutFlex(LayoutBox *container, float contentWidth, float definiteHeight) const {
423
+ const XtStyle &containerStyle = *container->style;
424
+ bool isRow = containerStyle.flex_direction == FlexDirection::Row ||
425
+ containerStyle.flex_direction == FlexDirection::RowReverse;
426
+ bool reverse = containerStyle.flex_direction == FlexDirection::RowReverse ||
427
+ containerStyle.flex_direction == FlexDirection::ColumnReverse;
428
+ float gap = px(containerStyle.gap, contentWidth, containerStyle.font_size, 0.0f);
429
+
430
+ std::vector<LayoutBox *> items;
431
+ for (const std::unique_ptr<LayoutBox> &child : container->children) {
432
+ if (child->display != Display::None) items.push_back(child.get());
433
+ }
434
+ if (items.empty()) return 0;
435
+ size_t count = items.size();
436
+
437
+ if (isRow) {
438
+ float mainSize = contentWidth;
439
+ std::vector<float> outer(count, 0.0f);
440
+ std::vector<float> mainSizes(count, 0.0f);
441
+ float sumBase = 0;
442
+ for (size_t i = 0; i < count; i++) {
443
+ const XtStyle &style = *items[i]->style;
444
+ float basis;
445
+ if (isAuto(style.flex_basis)) {
446
+ if (!isAuto(style.width)) basis = px(style.width, mainSize, style.font_size, 0.0f);
447
+ else basis = preferredContentWidth(items[i], mainSize);
448
+ } else {
449
+ basis = px(style.flex_basis, mainSize, style.font_size, 0.0f);
450
+ }
451
+ resolveEdges(items[i], mainSize);
452
+ float edges = horizontalEdges(items[i]);
453
+ outer[i] = basis + edges;
454
+ mainSizes[i] = outer[i];
455
+ sumBase += outer[i];
456
+ }
457
+ float free = mainSize - sumBase - gap * (float)(count - 1);
458
+ if (free > 0) {
459
+ float totalGrow = 0;
460
+ for (LayoutBox *item : items) totalGrow += item->style->flex_grow;
461
+ if (totalGrow > 0) {
462
+ for (size_t i = 0; i < count; i++) mainSizes[i] += free * items[i]->style->flex_grow / totalGrow;
463
+ }
464
+ } else if (free < 0) {
465
+ float totalShrink = 0;
466
+ for (size_t i = 0; i < count; i++) totalShrink += items[i]->style->flex_shrink * outer[i];
467
+ if (totalShrink > 0) {
468
+ for (size_t i = 0; i < count; i++) {
469
+ mainSizes[i] += free * (items[i]->style->flex_shrink * outer[i]) / totalShrink;
470
+ mainSizes[i] = std::max(0.0f, mainSizes[i]);
471
+ }
472
+ }
473
+ }
474
+
475
+ /* Cross sizes: lay out once to measure, then apply cross alignment. */
476
+ float crossSize = definiteHeight >= 0 ? definiteHeight : 0;
477
+ std::vector<float> cross(count, 0.0f);
478
+ for (size_t i = 0; i < count; i++) {
479
+ float contentW = mainSizes[i] - horizontalEdges(items[i]);
480
+ if (contentW < 0) contentW = 0;
481
+ layoutBlock(items[i], container->content_x, container->content_y, contentWidth, definiteHeight,
482
+ contentW);
483
+ cross[i] = verticalEdges(items[i]) + items[i]->height;
484
+ if (definiteHeight < 0) crossSize = std::max(crossSize, cross[i]);
485
+ }
486
+ if (containerStyle.align_items == AlignItems::Stretch && definiteHeight >= 0) {
487
+ for (size_t i = 0; i < count; i++) {
488
+ if (isAuto(items[i]->style->height)) {
489
+ float contentH = crossSize - verticalEdges(items[i]);
490
+ if (contentH < 0) contentH = 0;
491
+ float contentW = mainSizes[i] - horizontalEdges(items[i]);
492
+ layoutBlock(items[i], container->content_x, container->content_y, contentWidth, definiteHeight,
493
+ contentW, contentH);
494
+ }
495
+ }
496
+ }
497
+
498
+ /* justify-content: place the main axis. */
499
+ float freeSpace = mainSize - (sumOf(mainSizes) + gap * (float)(count - 1));
500
+ float offset = 0;
501
+ float between = gap;
502
+ applyJustify(containerStyle.justify_content, freeSpace, count, &offset, &between);
503
+
504
+ float cursor = container->content_x + offset;
505
+ for (size_t k = 0; k < count; k++) {
506
+ size_t i = reverse ? (count - 1 - k) : k;
507
+ float crossOffset = crossOffsetFor(containerStyle.align_items, crossSize, cross[i]);
508
+ float dx = cursor - items[i]->x;
509
+ float dy = container->content_y + crossOffset - items[i]->y;
510
+ translate(items[i], dx, dy);
511
+ cursor += mainSizes[i] + between;
512
+ }
513
+ return crossSize;
514
+ }
515
+
516
+ /* Column flex. */
517
+ float mainSize = definiteHeight >= 0 ? definiteHeight : -1;
518
+ std::vector<float> outer(count, 0.0f);
519
+ float sumBase = 0;
520
+ for (size_t i = 0; i < count; i++) {
521
+ layoutBlock(items[i], container->content_x, container->content_y, contentWidth, definiteHeight);
522
+ outer[i] = verticalEdges(items[i]) + items[i]->height;
523
+ sumBase += outer[i];
524
+ }
525
+ std::vector<float> mainSizes = outer;
526
+ if (mainSize >= 0) {
527
+ float free = mainSize - sumBase - gap * (float)(count - 1);
528
+ if (free > 0) {
529
+ float totalGrow = 0;
530
+ for (LayoutBox *item : items) totalGrow += item->style->flex_grow;
531
+ if (totalGrow > 0) {
532
+ for (size_t i = 0; i < count; i++) {
533
+ mainSizes[i] += free * items[i]->style->flex_grow / totalGrow;
534
+ float contentH = mainSizes[i] - verticalEdges(items[i]);
535
+ if (contentH < 0) contentH = 0;
536
+ layoutBlock(items[i], container->content_x, container->content_y, contentWidth, definiteHeight, -1,
537
+ contentH);
538
+ }
539
+ }
540
+ }
541
+ } else {
542
+ mainSize = sumBase + gap * (float)(count - 1);
543
+ }
544
+ float freeSpace = mainSize - (sumOf(mainSizes) + gap * (float)(count - 1));
545
+ float offset = 0;
546
+ float between = gap;
547
+ applyJustify(containerStyle.justify_content, freeSpace, count, &offset, &between);
548
+
549
+ float cursor = container->content_y + offset;
550
+ for (size_t k = 0; k < count; k++) {
551
+ size_t i = reverse ? (count - 1 - k) : k;
552
+ float dx = container->content_x - items[i]->x;
553
+ float dy = cursor - items[i]->y;
554
+ translate(items[i], dx, dy);
555
+ cursor += mainSizes[i] + between;
556
+ }
557
+ return mainSize;
558
+ }
559
+
560
+ static float sumOf(const std::vector<float> &values) {
561
+ float total = 0;
562
+ for (float value : values) total += value;
563
+ return total;
564
+ }
565
+
566
+ static void applyJustify(JustifyContent justify, float freeSpace, size_t count, float *offset,
567
+ float *between) {
568
+ if (count == 0) return;
569
+ if (freeSpace < 0) freeSpace = 0;
570
+ switch (justify) {
571
+ case JustifyContent::FlexEnd:
572
+ *offset = freeSpace;
573
+ break;
574
+ case JustifyContent::Center:
575
+ *offset = freeSpace / 2.0f;
576
+ break;
577
+ case JustifyContent::SpaceBetween:
578
+ if (count > 1) *between += freeSpace / (float)(count - 1);
579
+ break;
580
+ case JustifyContent::SpaceAround:
581
+ *between += freeSpace / (float)count;
582
+ *offset = freeSpace / (2.0f * (float)count);
583
+ break;
584
+ case JustifyContent::SpaceEvenly:
585
+ *between += freeSpace / (float)(count + 1);
586
+ *offset = freeSpace / (float)(count + 1);
587
+ break;
588
+ default:
589
+ break;
590
+ }
591
+ }
592
+
593
+ static float crossOffsetFor(AlignItems align, float crossSize, float itemCross) {
594
+ switch (align) {
595
+ case AlignItems::FlexEnd:
596
+ return crossSize - itemCross;
597
+ case AlignItems::Center:
598
+ return (crossSize - itemCross) / 2.0f;
599
+ case AlignItems::Baseline:
600
+ return crossSize - itemCross;
601
+ default:
602
+ return 0.0f;
603
+ }
604
+ }
605
+ };
606
+
607
+ std::unique_ptr<LayoutBox> buildBoxTree(const Node *node,
608
+ const std::unordered_map<const Node *, XtStyle> &styles,
609
+ const XtStyle *inherited) {
610
+ if (node->isText()) {
611
+ auto box = std::make_unique<LayoutBox>();
612
+ box->node = node;
613
+ box->is_text = true;
614
+ box->text = node->text;
615
+ box->style = inherited;
616
+ box->display = Display::Inline;
617
+ return box;
618
+ }
619
+ auto it = styles.find(node);
620
+ const XtStyle *style = it == styles.end() ? inherited : &it->second;
621
+ if (style != nullptr && style->display == Display::None) return nullptr;
622
+
623
+ auto box = std::make_unique<LayoutBox>();
624
+ box->node = node;
625
+ box->style = style;
626
+ box->display = style != nullptr ? style->display : Display::Block;
627
+ if (node->isElement() && node->isTag("img")) {
628
+ box->is_image = true;
629
+ const std::string *src = node->attr("src");
630
+ if (src != nullptr) {
631
+ box->image_src = *src;
632
+ int width = 0;
633
+ int height = 0;
634
+ if (xt_image_size(*src, &width, &height)) {
635
+ box->intrinsic_width = (float)width;
636
+ box->intrinsic_height = (float)height;
637
+ }
638
+ }
639
+ }
640
+ for (const std::unique_ptr<Node> &child : node->children) {
641
+ std::unique_ptr<LayoutBox> childBox = buildBoxTree(child.get(), styles, style);
642
+ if (childBox != nullptr) {
643
+ childBox->parent = box.get();
644
+ box->children.push_back(std::move(childBox));
645
+ }
646
+ }
647
+ return box;
648
+ }
649
+
650
+ std::string dumpBox(const LayoutBox *box, int depth) {
651
+ std::string out(depth * 2, ' ');
652
+ if (box->is_text) {
653
+ out += "#text";
654
+ } else if (box->node != nullptr && box->node->isElement()) {
655
+ out += "<" + box->node->tag + ">";
656
+ } else {
657
+ out += "#root";
658
+ }
659
+ char buffer[128];
660
+ std::snprintf(buffer, sizeof(buffer), " x=%.1f y=%.1f w=%.1f h=%.1f\n", box->x, box->y, box->width,
661
+ box->height);
662
+ out += buffer;
663
+ for (const std::unique_ptr<LayoutBox> &child : box->children) out += dumpBox(child.get(), depth + 1);
664
+ return out;
665
+ }
666
+
667
+ } // namespace
668
+
669
+ float xt_layout_text_width(const std::string &text, const XtStyle &style) {
670
+ return xt_text_measure_width(text, specOf(style));
671
+ }
672
+
673
+ void LayoutTree::compute(const Node *root, const std::unordered_map<const Node *, XtStyle> &styles,
674
+ float viewportWidth, float viewportHeight) {
675
+ root_.reset();
676
+ index_.clear();
677
+ root_style_ = xt_style_initial();
678
+ root_style_.display = Display::Block;
679
+
680
+ Layouter layouter;
681
+ layouter.styles = &styles;
682
+ layouter.tree = this;
683
+ layouter.vw = viewportWidth;
684
+ layouter.vh = viewportHeight;
685
+
686
+ root_ = std::make_unique<LayoutBox>();
687
+ root_->node = root;
688
+ root_->style = &root_style_;
689
+ root_->display = Display::Block;
690
+ if (root != nullptr) {
691
+ if (root->type == NodeType::Document) {
692
+ for (const std::unique_ptr<Node> &child : root->children) {
693
+ std::unique_ptr<LayoutBox> box = buildBoxTree(child.get(), styles, &root_style_);
694
+ if (box != nullptr) {
695
+ box->parent = root_.get();
696
+ root_->children.push_back(std::move(box));
697
+ }
698
+ }
699
+ } else {
700
+ std::unique_ptr<LayoutBox> box = buildBoxTree(root, styles, &root_style_);
701
+ if (box != nullptr) {
702
+ box->parent = root_.get();
703
+ root_->children.push_back(std::move(box));
704
+ }
705
+ }
706
+ }
707
+ layouter.layoutBlock(root_.get(), 0, 0, viewportWidth, viewportHeight);
708
+
709
+ std::vector<const LayoutBox *> stack;
710
+ stack.push_back(root_.get());
711
+ while (!stack.empty()) {
712
+ const LayoutBox *box = stack.back();
713
+ stack.pop_back();
714
+ if (box->node != nullptr) index_[box->node] = box;
715
+ for (const std::unique_ptr<LayoutBox> &child : box->children) stack.push_back(child.get());
716
+ }
717
+ }
718
+
719
+ const LayoutBox *LayoutTree::find(const Node *node) const {
720
+ auto it = index_.find(node);
721
+ return it == index_.end() ? nullptr : it->second;
722
+ }
723
+
724
+ namespace {
725
+
726
+ const LayoutBox *hitTestBox(const LayoutBox *box, float x, float y) {
727
+ if (box == nullptr || box->display == Display::None) return nullptr;
728
+ /* Later siblings paint on top, so probe them first. */
729
+ for (auto it = box->children.rbegin(); it != box->children.rend(); ++it) {
730
+ if (const LayoutBox *hit = hitTestBox(it->get(), x, y)) return hit;
731
+ }
732
+ if (x >= box->x && x < box->x + box->width && y >= box->y && y < box->y + box->height) {
733
+ return box;
734
+ }
735
+ return nullptr;
736
+ }
737
+
738
+ } // namespace
739
+
740
+ const LayoutBox *LayoutTree::hitTest(float x, float y) const {
741
+ return hitTestBox(root_.get(), x, y);
742
+ }
743
+
744
+ std::string LayoutTree::dump() const {
745
+ return root_ == nullptr ? std::string() : dumpBox(root_.get(), 0);
746
+ }
747
+
748
+ } // namespace xtgui