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,117 @@
1
+ /*
2
+ * xbintsc GUI engine — computed style (cascade + inheritance).
3
+ *
4
+ * `XtStyle` is the resolved style a layout pass consumes. Lengths keep their
5
+ * unit here (%), em, ...) because resolving them needs the containing block and
6
+ * font size, which only layout knows. `xt_style_compute` fills a map of
7
+ * element → computed style from the UA sheet, the author sheet and `style=""`
8
+ * attributes, in cascade order.
9
+ */
10
+ #ifndef XT_GUI_STYLE_H
11
+ #define XT_GUI_STYLE_H
12
+
13
+ #include <string>
14
+ #include <unordered_map>
15
+ #include <vector>
16
+
17
+ #include "css.h"
18
+ #include "dom.h"
19
+
20
+ namespace xtgui {
21
+
22
+ enum class Display { None, Block, Inline, InlineBlock, Flex, ListItem };
23
+ enum class Position { Static, Relative, Absolute, Fixed };
24
+ enum class FlexDirection { Row, RowReverse, Column, ColumnReverse };
25
+ enum class JustifyContent { FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround, SpaceEvenly };
26
+ enum class AlignItems { Stretch, FlexStart, FlexEnd, Center, Baseline };
27
+ enum class TextAlign { Start, Center, End, Justify };
28
+ enum class WhiteSpace { Normal, NoWrap, Pre, PreWrap, PreLine };
29
+ enum class Overflow { Visible, Hidden, Scroll, Auto };
30
+ enum class BorderStyle { None, Solid, Dashed, Dotted };
31
+ enum class FontStyle { Normal, Italic };
32
+ enum class TimingFunction { Linear, Ease, EaseIn, EaseOut, EaseInOut };
33
+
34
+ /** One `transition` entry: which property, how long, how it eases. */
35
+ struct TransitionSpec {
36
+ std::string property = "all";
37
+ float duration = 0.0f; // seconds
38
+ float delay = 0.0f; // seconds
39
+ TimingFunction timing = TimingFunction::Ease;
40
+ };
41
+
42
+ struct Edges {
43
+ Length top;
44
+ Length right;
45
+ Length bottom;
46
+ Length left;
47
+
48
+ void set(Length value) { top = right = bottom = left = value; }
49
+ };
50
+
51
+ struct XtStyle {
52
+ Display display = Display::Inline;
53
+ Position position = Position::Static;
54
+ Edges margin;
55
+ Edges padding;
56
+ Edges border_width;
57
+ BorderStyle border_style = BorderStyle::None;
58
+ Color border_color;
59
+ bool has_border_color = false;
60
+ float border_radius = 0.0f;
61
+
62
+ Length width;
63
+ Length height;
64
+ Length min_width;
65
+ Length min_height;
66
+ Length max_width;
67
+ Length max_height;
68
+ Length top;
69
+ Length right;
70
+ Length bottom;
71
+ Length left;
72
+
73
+ Color color; // inherited; defaults to black
74
+ Color background; // transparent by default
75
+ bool has_background = false;
76
+
77
+ float font_size = 16.0f; // inherited
78
+ int font_weight = 400; // inherited
79
+ std::string font_family = "sans-serif"; // inherited
80
+ FontStyle font_style = FontStyle::Normal; // inherited
81
+ Length line_height; // Auto = normal; Number = multiplier
82
+
83
+ TextAlign text_align = TextAlign::Start; // inherited
84
+ WhiteSpace white_space = WhiteSpace::Normal; // inherited
85
+
86
+ FlexDirection flex_direction = FlexDirection::Row;
87
+ JustifyContent justify_content = JustifyContent::FlexStart;
88
+ AlignItems align_items = AlignItems::Stretch;
89
+ Length gap;
90
+ float flex_grow = 0.0f;
91
+ float flex_shrink = 1.0f;
92
+ Length flex_basis;
93
+
94
+ float opacity = 1.0f;
95
+ Overflow overflow = Overflow::Visible;
96
+ int z_index = 0;
97
+ bool has_z_index = false;
98
+
99
+ /** `transition` specs; not inherited. Applied by `XtDocument`. */
100
+ std::vector<TransitionSpec> transitions;
101
+ };
102
+
103
+ /** Initial (non-inherited) style; inherited fields are overridden from the parent. */
104
+ XtStyle xt_style_initial();
105
+
106
+ /** Cascade UA + author + inline declarations into `out` for the document root.
107
+ * `state` supplies `:hover`/`:focus` for dynamic selector matching. */
108
+ void xt_style_compute(const Node *root, const StyleSheet &sheet,
109
+ std::unordered_map<const Node *, XtStyle> &out, float viewportWidth,
110
+ float viewportHeight, const MatchState &state = MatchState());
111
+
112
+ /** Format a computed property for diagnostics/tests (`computedStyle(sel, prop)`). */
113
+ std::string xt_style_property_to_string(const XtStyle &style, const std::string &property);
114
+
115
+ } // namespace xtgui
116
+
117
+ #endif /* XT_GUI_STYLE_H */
@@ -0,0 +1,378 @@
1
+ /*
2
+ * xbintsc GUI engine — text stack implementation.
3
+ *
4
+ * HarfBuzz does shaping (ligatures/kerning/complex scripts), FreeType owns the
5
+ * glyph outlines and the vertical metrics. We create one HarfBuzz font per
6
+ * (file, pixel size) and scale it so that advances come back in 26.6 pixels.
7
+ */
8
+
9
+ #include "text.h"
10
+
11
+ #include <ft2build.h>
12
+ #include FT_FREETYPE_H
13
+
14
+ #include <hb.h>
15
+ #include <hb-ot.h>
16
+
17
+ #include <cmath>
18
+ #include <cstddef>
19
+ #include <cstdio>
20
+ #include <cstdlib>
21
+ #include <cstring>
22
+ #include <memory>
23
+ #include <string>
24
+ #include <unordered_map>
25
+
26
+ namespace xtgui {
27
+
28
+ /* Opaque to callers (declared in text.h); defined here so the loading helpers
29
+ * in the anonymous namespace can build it. */
30
+ struct Font {
31
+ FT_Face ft_face = nullptr;
32
+ hb_font_t *hb_font = nullptr;
33
+ float pixel_size = 16.0f;
34
+ float ascent = 0;
35
+ float descent = 0;
36
+ float line_height = 0;
37
+
38
+ ~Font() {
39
+ if (hb_font != nullptr) hb_font_destroy(hb_font);
40
+ if (ft_face != nullptr) FT_Done_Face(ft_face);
41
+ }
42
+ };
43
+
44
+ namespace {
45
+
46
+ const char *const kSansCandidates[] = {
47
+ /* Explicit override wins (also checked per family class below). */
48
+ nullptr,
49
+ "/System/Library/Fonts/Helvetica.ttc",
50
+ "/System/Library/Fonts/Supplemental/Arial.ttf",
51
+ "/System/Library/Fonts/Supplemental/Verdana.ttf",
52
+ "/Library/Fonts/Arial.ttf",
53
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
54
+ "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
55
+ "/usr/share/fonts/TTF/DejaVuSans.ttf",
56
+ "/usr/share/fonts/dejavu/DejaVuSans.ttf",
57
+ "C:\\Windows\\Fonts\\segoeui.ttf",
58
+ "C:\\Windows\\Fonts\\arial.ttf",
59
+ };
60
+
61
+ const char *const kMonoCandidates[] = {
62
+ nullptr,
63
+ "/System/Library/Fonts/Menlo.ttc",
64
+ "/System/Library/Fonts/Supplemental/Courier New.ttf",
65
+ "/System/Library/Fonts/Courier.ttc",
66
+ "/Library/Fonts/Courier New.ttf",
67
+ "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
68
+ "/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf",
69
+ "/usr/share/fonts/TTF/DejaVuSansMono.ttf",
70
+ "C:\\Windows\\Fonts\\consola.ttf",
71
+ "C:\\Windows\\Fonts\\cour.ttf",
72
+ };
73
+
74
+ struct Engine {
75
+ bool initialized = false;
76
+ FT_Library ft = nullptr;
77
+ bool ft_ok = false;
78
+ std::string sans_path;
79
+ std::string mono_path;
80
+ std::unordered_map<std::string, std::unique_ptr<Font>> cache; // key -> face (may be null)
81
+ std::unordered_map<std::string, hb_face_t *> hb_faces; // path -> face
82
+ };
83
+
84
+ Engine &engine() {
85
+ static Engine instance;
86
+ return instance;
87
+ }
88
+
89
+ bool fileExists(const char *path) {
90
+ FILE *file = fopen(path, "rb");
91
+ if (file == nullptr) return false;
92
+ fclose(file);
93
+ return true;
94
+ }
95
+
96
+ /* Deliberately crude, deterministic per-byte advance (fraction of the font
97
+ * size). Used only when no font file can be loaded. */
98
+ float charAdvance(unsigned char c) {
99
+ if (c < 0x20 || c == 0x7f) return 0.0f;
100
+ if (c >= 0x80) return 0.55f;
101
+ char ch = (char)c;
102
+ if (ch == ' ') return 0.28f;
103
+ if (std::strchr("ilj|.,:;'!`", ch) != nullptr) return 0.26f;
104
+ if (std::strchr("mwMW@%", ch) != nullptr) return 0.85f;
105
+ if (ch >= '0' && ch <= '9') return 0.56f;
106
+ if (ch >= 'A' && ch <= 'Z') return 0.64f;
107
+ if (std::strchr("()[]{}<>/\\\"", ch) != nullptr) return 0.36f;
108
+ return 0.52f;
109
+ }
110
+
111
+ /* Decode one UTF-8 sequence starting at `index`; advances `index`. */
112
+ uint32_t decodeUtf8(const std::string &text, size_t &index) {
113
+ unsigned char first = (unsigned char)text[index];
114
+ if (first < 0x80) {
115
+ index += 1;
116
+ return first;
117
+ }
118
+ uint32_t code_point = 0;
119
+ size_t extra = 0;
120
+ if ((first & 0xe0) == 0xc0) {
121
+ code_point = first & 0x1f;
122
+ extra = 1;
123
+ } else if ((first & 0xf0) == 0xe0) {
124
+ code_point = first & 0x0f;
125
+ extra = 2;
126
+ } else if ((first & 0xf8) == 0xf0) {
127
+ code_point = first & 0x07;
128
+ extra = 3;
129
+ } else {
130
+ index += 1;
131
+ return 0xfffd;
132
+ }
133
+ if (index + extra >= text.size()) {
134
+ index = text.size();
135
+ return 0xfffd;
136
+ }
137
+ for (size_t i = 1; i <= extra; i++) {
138
+ unsigned char next = (unsigned char)text[index + i];
139
+ if ((next & 0xc0) != 0x80) {
140
+ index += 1;
141
+ return 0xfffd;
142
+ }
143
+ code_point = (code_point << 6) | (next & 0x3f);
144
+ }
145
+ index += extra + 1;
146
+ return code_point;
147
+ }
148
+
149
+ float approximateWidth(const std::string &utf8, float pixel_size, const std::string &family) {
150
+ bool monospace = family.find("mono") != std::string::npos ||
151
+ family.find("courier") != std::string::npos;
152
+ float factor = monospace ? 0.6f : 1.0f;
153
+ float width = 0;
154
+ for (char c : utf8) width += charAdvance((unsigned char)c) * factor;
155
+ return width * pixel_size;
156
+ }
157
+
158
+ bool isMonospaceFamily(const std::string &family) {
159
+ return family.find("mono") != std::string::npos || family.find("courier") != std::string::npos ||
160
+ family.find("consol") != std::string::npos || family.find("menlo") != std::string::npos ||
161
+ family.find("code") != std::string::npos;
162
+ }
163
+
164
+ const char *firstExisting(const char *const *candidates, size_t count) {
165
+ for (size_t i = 0; i < count; i++) {
166
+ if (candidates[i] != nullptr && fileExists(candidates[i])) return candidates[i];
167
+ }
168
+ return nullptr;
169
+ }
170
+
171
+ void ensureInitialized() {
172
+ Engine &e = engine();
173
+ if (e.initialized) return;
174
+ e.initialized = true;
175
+ if (FT_Init_FreeType(&e.ft) != 0) {
176
+ e.ft = nullptr;
177
+ return;
178
+ }
179
+ e.ft_ok = true;
180
+
181
+ const size_t sansCount = sizeof(kSansCandidates) / sizeof(kSansCandidates[0]);
182
+ const size_t monoCount = sizeof(kMonoCandidates) / sizeof(kMonoCandidates[0]);
183
+
184
+ const char *env = std::getenv("XT_GUI_FONT");
185
+ if (env != nullptr && env[0] != '\0' && fileExists(env)) e.sans_path = env;
186
+ if (e.sans_path.empty()) {
187
+ const char *path = firstExisting(kSansCandidates, sansCount);
188
+ if (path != nullptr) e.sans_path = path;
189
+ }
190
+
191
+ const char *envMono = std::getenv("XT_GUI_FONT_MONO");
192
+ if (envMono != nullptr && envMono[0] != '\0' && fileExists(envMono)) {
193
+ e.mono_path = envMono;
194
+ } else if (env != nullptr && env[0] != '\0' && fileExists(env) && isMonospaceFamily(env)) {
195
+ e.mono_path = env;
196
+ }
197
+ if (e.mono_path.empty()) {
198
+ const char *path = firstExisting(kMonoCandidates, monoCount);
199
+ if (path != nullptr) e.mono_path = path;
200
+ }
201
+ /* No dedicated monospace face: reuse the sans face so metrics stay real. */
202
+ if (e.mono_path.empty()) e.mono_path = e.sans_path;
203
+ }
204
+
205
+ hb_face_t *hbFaceForPath(const std::string &path) {
206
+ Engine &e = engine();
207
+ auto it = e.hb_faces.find(path);
208
+ if (it != e.hb_faces.end()) return it->second;
209
+ hb_blob_t *blob = hb_blob_create_from_file(path.c_str());
210
+ hb_face_t *face = nullptr;
211
+ if (blob != nullptr && hb_blob_get_length(blob) > 0) {
212
+ face = hb_face_create(blob, 0);
213
+ }
214
+ if (blob != nullptr) hb_blob_destroy(blob);
215
+ e.hb_faces[path] = face;
216
+ return face;
217
+ }
218
+
219
+ std::unique_ptr<Font> createFont(const std::string &path, float pixel_size) {
220
+ Engine &e = engine();
221
+ if (!e.ft_ok || path.empty()) return nullptr;
222
+
223
+ FT_Face face = nullptr;
224
+ if (FT_New_Face(e.ft, path.c_str(), 0, &face) != 0) return nullptr;
225
+ FT_Set_Pixel_Sizes(face, 0, (FT_UInt)(pixel_size + 0.5f));
226
+
227
+ auto font = std::make_unique<Font>();
228
+ font->ft_face = face;
229
+ font->pixel_size = pixel_size;
230
+
231
+ FT_Size_Metrics metrics = face->size->metrics;
232
+ font->ascent = metrics.ascender / 64.0f;
233
+ font->descent = -metrics.descender / 64.0f;
234
+ font->line_height = metrics.height / 64.0f;
235
+
236
+ hb_face_t *hb_face = hbFaceForPath(path);
237
+ if (hb_face != nullptr) {
238
+ font->hb_font = hb_font_create(hb_face);
239
+ hb_ot_font_set_funcs(font->hb_font);
240
+ int scale = (int)(pixel_size * 64.0f + 0.5f);
241
+ hb_font_set_scale(font->hb_font, scale, scale);
242
+ unsigned ppem = (unsigned)(pixel_size + 0.5f);
243
+ hb_font_set_ppem(font->hb_font, ppem, ppem);
244
+ }
245
+ return font;
246
+ }
247
+
248
+ } // namespace
249
+
250
+ Font *xt_text_resolve(const FontSpec &spec) {
251
+ ensureInitialized();
252
+ Engine &e = engine();
253
+ if (!e.ft_ok) return nullptr;
254
+
255
+ bool mono = isMonospaceFamily(spec.family);
256
+ const std::string &path = mono ? e.mono_path : e.sans_path;
257
+ if (path.empty()) return nullptr;
258
+
259
+ char key[80];
260
+ std::snprintf(key, sizeof(key), "%c|%.2f", mono ? 'm' : 's', (double)spec.pixel_size);
261
+ auto it = e.cache.find(key);
262
+ if (it != e.cache.end()) return it->second.get();
263
+
264
+ std::unique_ptr<Font> font = createFont(path, spec.pixel_size);
265
+ Font *raw = font.get();
266
+ e.cache.emplace(key, std::move(font));
267
+ return raw;
268
+ }
269
+
270
+ float xt_text_measure_width(const std::string &utf8, const FontSpec &spec) {
271
+ Font *font = xt_text_resolve(spec);
272
+ if (font == nullptr) return approximateWidth(utf8, spec.pixel_size, spec.family);
273
+ std::vector<ShapedGlyph> glyphs;
274
+ return xt_text_shape_run(utf8, font, glyphs);
275
+ }
276
+
277
+ float xt_text_shape_run(const std::string &utf8, Font *font, std::vector<ShapedGlyph> &out) {
278
+ if (font == nullptr) return 0;
279
+ float total = 0;
280
+ if (font->hb_font == nullptr) {
281
+ /* HarfBuzz unavailable: fall back to FreeType's per-codepoint advances. */
282
+ size_t index = 0;
283
+ while (index < utf8.size()) {
284
+ uint32_t code_point = decodeUtf8(utf8, index);
285
+ FT_UInt glyph = FT_Get_Char_Index(font->ft_face, code_point);
286
+ float advance = 0;
287
+ if (FT_Load_Glyph(font->ft_face, glyph, FT_LOAD_DEFAULT) == 0) {
288
+ advance = font->ft_face->glyph->advance.x / 64.0f;
289
+ }
290
+ ShapedGlyph shaped;
291
+ shaped.glyph = glyph;
292
+ shaped.x_advance = advance;
293
+ total += advance;
294
+ out.push_back(shaped);
295
+ }
296
+ return total;
297
+ }
298
+
299
+ hb_buffer_t *buffer = hb_buffer_create();
300
+ hb_buffer_add_utf8(buffer, utf8.c_str(), (int)utf8.size(), 0, -1);
301
+ hb_buffer_guess_segment_properties(buffer);
302
+ hb_shape(font->hb_font, buffer, nullptr, 0);
303
+ unsigned count = hb_buffer_get_length(buffer);
304
+ hb_glyph_info_t *infos = hb_buffer_get_glyph_infos(buffer, nullptr);
305
+ hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(buffer, nullptr);
306
+ for (unsigned i = 0; i < count; i++) {
307
+ ShapedGlyph shaped;
308
+ shaped.glyph = infos[i].codepoint;
309
+ shaped.x_advance = positions[i].x_advance / 64.0f;
310
+ shaped.x_offset = positions[i].x_offset / 64.0f;
311
+ shaped.y_offset = positions[i].y_offset / 64.0f;
312
+ total += shaped.x_advance;
313
+ out.push_back(shaped);
314
+ }
315
+ hb_buffer_destroy(buffer);
316
+ return total;
317
+ }
318
+
319
+ bool xt_text_rasterize(Font *font, uint32_t glyph, GlyphImage &out) {
320
+ out.width = 0;
321
+ out.height = 0;
322
+ out.left = 0;
323
+ out.top = 0;
324
+ out.pixels.clear();
325
+ if (font == nullptr || font->ft_face == nullptr) return false;
326
+ if (FT_Load_Glyph(font->ft_face, glyph, FT_LOAD_DEFAULT) != 0) return false;
327
+ if (FT_Render_Glyph(font->ft_face->glyph, FT_RENDER_MODE_NORMAL) != 0) return false;
328
+ FT_GlyphSlot slot = font->ft_face->glyph;
329
+ const FT_Bitmap &bitmap = slot->bitmap;
330
+ out.left = slot->bitmap_left;
331
+ out.top = slot->bitmap_top;
332
+ if (bitmap.width == 0 || bitmap.rows == 0) return false;
333
+ out.width = (int)bitmap.width;
334
+ out.height = (int)bitmap.rows;
335
+ out.pixels.resize((size_t)bitmap.width * bitmap.rows);
336
+ for (unsigned row = 0; row < bitmap.rows; row++) {
337
+ const unsigned char *source = bitmap.buffer + (ptrdiff_t)row * bitmap.pitch;
338
+ std::memcpy(out.pixels.data() + (size_t)row * bitmap.width, source, bitmap.width);
339
+ }
340
+ return true;
341
+ }
342
+
343
+ void xt_text_metrics(const FontSpec &spec, float *ascent, float *descent, float *line_height) {
344
+ Font *font = xt_text_resolve(spec);
345
+ if (font != nullptr) {
346
+ if (ascent != nullptr) *ascent = font->ascent;
347
+ if (descent != nullptr) *descent = font->descent;
348
+ if (line_height != nullptr) *line_height = font->line_height;
349
+ return;
350
+ }
351
+ if (ascent != nullptr) *ascent = spec.pixel_size * 0.8f;
352
+ if (descent != nullptr) *descent = spec.pixel_size * 0.2f;
353
+ if (line_height != nullptr) *line_height = spec.pixel_size * 1.2f;
354
+ }
355
+
356
+ bool xt_text_ready() {
357
+ ensureInitialized();
358
+ return engine().ft_ok;
359
+ }
360
+
361
+ void xt_text_shutdown() {
362
+ Engine &e = engine();
363
+ e.cache.clear();
364
+ for (auto &entry : e.hb_faces) {
365
+ if (entry.second != nullptr) hb_face_destroy(entry.second);
366
+ }
367
+ e.hb_faces.clear();
368
+ if (e.ft != nullptr) {
369
+ FT_Done_FreeType(e.ft);
370
+ e.ft = nullptr;
371
+ }
372
+ e.ft_ok = false;
373
+ e.initialized = false;
374
+ e.sans_path.clear();
375
+ e.mono_path.clear();
376
+ }
377
+
378
+ } // namespace xtgui
@@ -0,0 +1,78 @@
1
+ /*
2
+ * xbintsc GUI engine — text stack (M4).
3
+ *
4
+ * Text is shaped with HarfBuzz and rasterised with FreeType. Both libraries
5
+ * are statically linked into `gui.a`; this module hides them behind a small
6
+ * interface so layout and paint do not see the third-party types.
7
+ *
8
+ * Fonts are resolved lazily from well-known system locations (or a path in
9
+ * `XT_GUI_FONT` / `XT_GUI_FONT_MONO`) and cached per size. Only regular
10
+ * upright faces are used today; weight/italic selection is a later refinement.
11
+ */
12
+ #ifndef XT_GUI_TEXT_H
13
+ #define XT_GUI_TEXT_H
14
+
15
+ #include <cstdint>
16
+ #include <string>
17
+ #include <vector>
18
+
19
+ namespace xtgui {
20
+
21
+ /** Font selector derived from a computed style. */
22
+ struct FontSpec {
23
+ std::string family;
24
+ float pixel_size = 16.0f;
25
+ int weight = 400;
26
+ bool italic = false;
27
+ };
28
+
29
+ /** Opaque loaded face (defined in text.cpp). */
30
+ struct Font;
31
+
32
+ /** A positioned, shaped glyph (pixels). */
33
+ struct ShapedGlyph {
34
+ uint32_t glyph = 0;
35
+ float x_advance = 0;
36
+ float x_offset = 0;
37
+ float y_offset = 0;
38
+ };
39
+
40
+ /** An 8-bit grayscale glyph bitmap, as produced by FreeType. */
41
+ struct GlyphImage {
42
+ int width = 0;
43
+ int height = 0;
44
+ int left = 0; // x offset from the pen
45
+ int top = 0; // y offset above the baseline
46
+ std::vector<unsigned char> pixels;
47
+ };
48
+
49
+ /** Load (or fetch from the cache) the font for `spec`, or null if none is
50
+ * available. The returned pointer stays valid until `xt_text_shutdown`. */
51
+ Font *xt_text_resolve(const FontSpec &spec);
52
+
53
+ /** Shape `utf8` with `font`, appending glyphs to `out`; returns the total
54
+ * advance width in pixels. `out` is not cleared. */
55
+ float xt_text_shape_run(const std::string &utf8, Font *font, std::vector<ShapedGlyph> &out);
56
+
57
+ /** Rasterise `glyph` from `font` into a grayscale bitmap. Returns false when
58
+ * the glyph has no outline (e.g. a space) or cannot be rendered. */
59
+ bool xt_text_rasterize(Font *font, uint32_t glyph, GlyphImage &out);
60
+
61
+ /** Shaped horizontal advance of `utf8` in pixels. Falls back to a deterministic
62
+ * approximation when no font file can be loaded. */
63
+ float xt_text_measure_width(const std::string &utf8, const FontSpec &spec);
64
+
65
+ /** Vertical metrics in pixels: `ascent` upward, `descent` downward (positive),
66
+ * `line_height` for `line-height: normal`. Falls back to a deterministic
67
+ * approximation when no font file can be loaded. */
68
+ void xt_text_metrics(const FontSpec &spec, float *ascent, float *descent, float *line_height);
69
+
70
+ /** True once at least one real font file has been loaded. */
71
+ bool xt_text_ready();
72
+
73
+ /** Release every cached face (used at shutdown and by tests). */
74
+ void xt_text_shutdown();
75
+
76
+ } // namespace xtgui
77
+
78
+ #endif /* XT_GUI_TEXT_H */