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
@@ -1,542 +1,70 @@
1
1
  /*
2
- * xbintsc GUI engine — text stack implementation.
2
+ * xbintsc GUI engine — text front-end.
3
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
- * A run is shaped with a *single* face, but that face rarely covers every
9
- * codepoint in it — a CJK face such as Microsoft YaHei / PingFang / Noto Sans
10
- * CJK has no geometric shapes (U+25AE ▮, U+25AF ▯, …), and a Latin face has no
11
- * CJK. Each codepoint therefore resolves to the first face in a small fallback
12
- * chain that has it, the run is shaped in segments (one per face), and each
13
- * shaped glyph carries the face it came from so the rasteriser draws it from
14
- * the right atlas entry. Without this a missing glyph came back as `.notdef`
15
- * (glyph 0) and the renderer silently dropped it.
4
+ * The engine always links the bundled stb_truetype backend (`text_stb.cpp`).
5
+ * The optional `shaping` link feature installs a HarfBuzz + FreeType backend
6
+ * (`text_hb.cpp`) through `TextApi`; all public text entry points dispatch
7
+ * through the active table, so the core never references either library
8
+ * directly and a Latin-only page keeps the small DOM-only binary.
16
9
  */
17
10
 
18
- #include "text.h"
11
+ #include "text_api.h"
19
12
 
20
13
  #include "perf.h"
21
-
22
- #include <ft2build.h>
23
- #include FT_FREETYPE_H
24
-
25
- #include <hb.h>
26
- #include <hb-ot.h>
27
-
28
- #include <cmath>
29
- #include <cstddef>
30
- #include <cstdint>
31
- #include <cstdio>
32
- #include <cstdlib>
33
- #include <cstring>
34
- #include <memory>
35
- #include <string>
36
- #include <unordered_map>
37
- #include <vector>
14
+ #include "text_fonts.h"
38
15
 
39
16
  namespace xtgui {
40
17
 
41
- /* Opaque to callers (declared in text.h); defined here so the loading helpers
42
- * in the anonymous namespace can build it. */
43
- struct Font {
44
- FT_Face ft_face = nullptr;
45
- hb_font_t *hb_font = nullptr;
46
- std::string path;
47
- bool monospace = false;
48
- float pixel_size = 16.0f;
49
- float ascent = 0;
50
- float descent = 0;
51
- float line_height = 0;
52
-
53
- ~Font() {
54
- if (hb_font != nullptr) hb_font_destroy(hb_font);
55
- if (ft_face != nullptr) FT_Done_Face(ft_face);
56
- }
57
- };
58
-
59
18
  namespace {
60
19
 
61
- const char *const kSansCandidates[] = {
62
- /* Explicit override wins (also checked per family class below). */
63
- nullptr,
64
- /* Fonts with CJK coverage come first: the default face is a single face, so
65
- * it has to be able to render Chinese/Japanese/Korean text as well as
66
- * Latin (see "Implemented text" in doc/gui.md). */
67
- "C:\\Windows\\Fonts\\msyh.ttc", /* Microsoft YaHei (Win 8.1+) */
68
- "C:\\Windows\\Fonts\\msyh.ttf", /* Microsoft YaHei (Win 7) */
69
- "C:\\Windows\\Fonts\\Deng.ttf", /* DengXian */
70
- "C:\\Windows\\Fonts\\simsun.ttc", /* SimSun */
71
- "/System/Library/Fonts/PingFang.ttc",
72
- "/System/Library/Fonts/STHeiti Light.ttc",
73
- "/System/Library/Fonts/Helvetica.ttc",
74
- "/System/Library/Fonts/Supplemental/Arial.ttf",
75
- "/System/Library/Fonts/Supplemental/Verdana.ttf",
76
- "/Library/Fonts/Arial.ttf",
77
- "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
78
- "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
79
- "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
80
- "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
81
- "/usr/share/fonts/TTF/DejaVuSans.ttf",
82
- "/usr/share/fonts/dejavu/DejaVuSans.ttf",
83
- "C:\\Windows\\Fonts\\segoeui.ttf",
84
- "C:\\Windows\\Fonts\\arial.ttf",
85
- };
86
-
87
- const char *const kMonoCandidates[] = {
88
- nullptr,
89
- "C:\\Windows\\Fonts\\msyh.ttc", /* CJK-capable fallback (see above) */
90
- "/System/Library/Fonts/PingFang.ttc",
91
- "/System/Library/Fonts/Menlo.ttc",
92
- "/System/Library/Fonts/Supplemental/Courier New.ttf",
93
- "/System/Library/Fonts/Courier.ttc",
94
- "/Library/Fonts/Courier New.ttf",
95
- "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
96
- "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
97
- "/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf",
98
- "/usr/share/fonts/TTF/DejaVuSansMono.ttf",
99
- "C:\\Windows\\Fonts\\consola.ttf",
100
- "C:\\Windows\\Fonts\\cour.ttf",
101
- };
102
-
103
- /* Faces tried after the primary one for codepoints it does not cover. Symbol
104
- * faces come first (they carry the geometric shapes, arrows, box drawing and
105
- * dingbats the CJK/Latin primary often lacks), then the other family class so
106
- * a monospace run still gets CJK and a sans run still gets monospace-only
107
- * codepoints. The primary and duplicates are dropped when the chain is built. */
108
- const char *const kSymbolCandidates[] = {
109
- nullptr,
110
- "C:\\Windows\\Fonts\\seguisym.ttf", /* Segoe UI Symbol */
111
- "C:\\Windows\\Fonts\\arialuni.ttf", /* Arial Unicode MS */
112
- "/System/Library/Fonts/Apple Symbols.ttf",
113
- "/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
114
- "/usr/share/fonts/truetype/noto/NotoSansSymbols2-Regular.ttf",
115
- "/usr/share/fonts/truetype/noto/NotoSansSymbols-Regular.ttf",
116
- "/usr/share/fonts/truetype/ancient-scripts/Symbola_hint.ttf",
117
- "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
118
- };
119
-
120
- struct Engine {
121
- bool initialized = false;
122
- FT_Library ft = nullptr;
123
- bool ft_ok = false;
124
- std::string sans_path;
125
- std::string mono_path;
126
- std::vector<std::string> sans_fallbacks;
127
- std::vector<std::string> mono_fallbacks;
128
- std::unordered_map<std::string, std::unique_ptr<Font>> cache; // key -> face (may be null)
129
- std::unordered_map<std::string, std::vector<Font *>> fallbacks; // class+size -> faces
130
- std::unordered_map<std::string, hb_face_t *> hb_faces; // path -> face
131
- /** Primary face by (family class, pixel size): `xt_text_resolve` and
132
- * `xt_text_metrics` are called per text box per layout, and building the
133
- * 512-byte path key each time showed up in the profile. */
134
- std::unordered_map<std::string, Font *> primary;
135
- };
136
-
137
- Engine &engine() {
138
- static Engine instance;
139
- return instance;
140
- }
141
-
142
- bool fileExists(const char *path) {
143
- FILE *file = fopen(path, "rb");
144
- if (file == nullptr) return false;
145
- fclose(file);
146
- return true;
147
- }
148
-
149
- /* Deliberately crude, deterministic per-byte advance (fraction of the font
150
- * size). Used only when no font file can be loaded. */
151
- float charAdvance(unsigned char c) {
152
- if (c < 0x20 || c == 0x7f) return 0.0f;
153
- if (c >= 0x80) return 0.55f;
154
- char ch = (char)c;
155
- if (ch == ' ') return 0.28f;
156
- if (std::strchr("ilj|.,:;'!`", ch) != nullptr) return 0.26f;
157
- if (std::strchr("mwMW@%", ch) != nullptr) return 0.85f;
158
- if (ch >= '0' && ch <= '9') return 0.56f;
159
- if (ch >= 'A' && ch <= 'Z') return 0.64f;
160
- if (std::strchr("()[]{}<>/\\\"", ch) != nullptr) return 0.36f;
161
- return 0.52f;
162
- }
163
-
164
- /* Decode one UTF-8 sequence starting at `index`; advances `index`. */
165
- uint32_t decodeUtf8(const std::string &text, size_t &index) {
166
- unsigned char first = (unsigned char)text[index];
167
- if (first < 0x80) {
168
- index += 1;
169
- return first;
170
- }
171
- uint32_t code_point = 0;
172
- size_t extra = 0;
173
- if ((first & 0xe0) == 0xc0) {
174
- code_point = first & 0x1f;
175
- extra = 1;
176
- } else if ((first & 0xf0) == 0xe0) {
177
- code_point = first & 0x0f;
178
- extra = 2;
179
- } else if ((first & 0xf8) == 0xf0) {
180
- code_point = first & 0x07;
181
- extra = 3;
182
- } else {
183
- index += 1;
184
- return 0xfffd;
185
- }
186
- if (index + extra >= text.size()) {
187
- index = text.size();
188
- return 0xfffd;
189
- }
190
- for (size_t i = 1; i <= extra; i++) {
191
- unsigned char next = (unsigned char)text[index + i];
192
- if ((next & 0xc0) != 0x80) {
193
- index += 1;
194
- return 0xfffd;
195
- }
196
- code_point = (code_point << 6) | (next & 0x3f);
197
- }
198
- index += extra + 1;
199
- return code_point;
200
- }
201
-
202
- float approximateWidth(const std::string &utf8, float pixel_size, const std::string &family) {
203
- bool monospace = family.find("mono") != std::string::npos ||
204
- family.find("courier") != std::string::npos;
205
- float factor = monospace ? 0.6f : 1.0f;
206
- float width = 0;
207
- for (char c : utf8) width += charAdvance((unsigned char)c) * factor;
208
- return width * pixel_size;
209
- }
210
-
211
- bool isMonospaceFamily(const std::string &family) {
212
- return family.find("mono") != std::string::npos || family.find("courier") != std::string::npos ||
213
- family.find("consol") != std::string::npos || family.find("menlo") != std::string::npos ||
214
- family.find("code") != std::string::npos;
215
- }
216
-
217
- const char *firstExisting(const char *const *candidates, size_t count) {
218
- for (size_t i = 0; i < count; i++) {
219
- if (candidates[i] != nullptr && fileExists(candidates[i])) return candidates[i];
220
- }
221
- return nullptr;
222
- }
223
-
224
- void addFallbackPath(std::vector<std::string> &paths, const char *path) {
225
- if (path == nullptr || !fileExists(path)) return;
226
- for (const std::string &entry : paths) {
227
- if (entry == path) return;
228
- }
229
- paths.push_back(path);
230
- }
231
-
232
- /* The chain for a primary face: every existing symbol face, then the other
233
- * family class (CJK coverage for a monospace primary and vice versa). The
234
- * primary itself is skipped because it is tried first, per codepoint. */
235
- void buildFallbackChain(std::vector<std::string> &paths, const std::string &primary,
236
- const std::string &otherClass) {
237
- for (const char *candidate : kSymbolCandidates) {
238
- if (candidate != nullptr && primary != candidate) addFallbackPath(paths, candidate);
239
- }
240
- if (!otherClass.empty() && otherClass != primary) addFallbackPath(paths, otherClass.c_str());
241
- }
242
-
243
- void ensureInitialized() {
244
- Engine &e = engine();
245
- if (e.initialized) return;
246
- e.initialized = true;
247
- if (FT_Init_FreeType(&e.ft) != 0) {
248
- e.ft = nullptr;
249
- return;
250
- }
251
- e.ft_ok = true;
252
-
253
- const size_t sansCount = sizeof(kSansCandidates) / sizeof(kSansCandidates[0]);
254
- const size_t monoCount = sizeof(kMonoCandidates) / sizeof(kMonoCandidates[0]);
255
-
256
- const char *env = std::getenv("XT_GUI_FONT");
257
- if (env != nullptr && env[0] != '\0' && fileExists(env)) e.sans_path = env;
258
- if (e.sans_path.empty()) {
259
- const char *path = firstExisting(kSansCandidates, sansCount);
260
- if (path != nullptr) e.sans_path = path;
261
- }
262
-
263
- const char *envMono = std::getenv("XT_GUI_FONT_MONO");
264
- if (envMono != nullptr && envMono[0] != '\0' && fileExists(envMono)) {
265
- e.mono_path = envMono;
266
- } else if (env != nullptr && env[0] != '\0' && fileExists(env) && isMonospaceFamily(env)) {
267
- e.mono_path = env;
268
- }
269
- if (e.mono_path.empty()) {
270
- const char *path = firstExisting(kMonoCandidates, monoCount);
271
- if (path != nullptr) e.mono_path = path;
272
- }
273
- /* No dedicated monospace face: reuse the sans face so metrics stay real. */
274
- if (e.mono_path.empty()) e.mono_path = e.sans_path;
275
-
276
- buildFallbackChain(e.sans_fallbacks, e.sans_path, e.mono_path);
277
- buildFallbackChain(e.mono_fallbacks, e.mono_path, e.sans_path);
278
- }
279
-
280
- hb_face_t *hbFaceForPath(const std::string &path) {
281
- Engine &e = engine();
282
- auto it = e.hb_faces.find(path);
283
- if (it != e.hb_faces.end()) return it->second;
284
- hb_blob_t *blob = hb_blob_create_from_file(path.c_str());
285
- hb_face_t *face = nullptr;
286
- if (blob != nullptr && hb_blob_get_length(blob) > 0) {
287
- face = hb_face_create(blob, 0);
288
- }
289
- if (blob != nullptr) hb_blob_destroy(blob);
290
- e.hb_faces[path] = face;
291
- return face;
292
- }
293
-
294
- std::unique_ptr<Font> createFont(const std::string &path, float pixel_size, bool monospace) {
295
- Engine &e = engine();
296
- if (!e.ft_ok || path.empty()) return nullptr;
297
-
298
- FT_Face face = nullptr;
299
- if (FT_New_Face(e.ft, path.c_str(), 0, &face) != 0) return nullptr;
300
- FT_Set_Pixel_Sizes(face, 0, (FT_UInt)(pixel_size + 0.5f));
301
-
302
- auto font = std::make_unique<Font>();
303
- font->ft_face = face;
304
- font->path = path;
305
- font->monospace = monospace;
306
- font->pixel_size = pixel_size;
307
-
308
- FT_Size_Metrics metrics = face->size->metrics;
309
- font->ascent = metrics.ascender / 64.0f;
310
- font->descent = -metrics.descender / 64.0f;
311
- font->line_height = metrics.height / 64.0f;
312
-
313
- hb_face_t *hb_face = hbFaceForPath(path);
314
- if (hb_face != nullptr) {
315
- font->hb_font = hb_font_create(hb_face);
316
- hb_ot_font_set_funcs(font->hb_font);
317
- int scale = (int)(pixel_size * 64.0f + 0.5f);
318
- hb_font_set_scale(font->hb_font, scale, scale);
319
- unsigned ppem = (unsigned)(pixel_size + 0.5f);
320
- hb_font_set_ppem(font->hb_font, ppem, ppem);
321
- }
322
- return font;
323
- }
324
-
325
- /* Resolve one face by path, keyed by path+class+size so the primary resolved
326
- * through a `FontSpec` and the same face resolved as a fallback share one
327
- * `Font` (and one glyph-atlas key). */
328
- Font *resolveFacePath(const std::string &path, float pixel_size, bool monospace) {
329
- Engine &e = engine();
330
- if (!e.ft_ok || path.empty()) return nullptr;
331
- char key[512];
332
- std::snprintf(key, sizeof(key), "p|%c|%.2f|%s", monospace ? 'm' : 's', (double)pixel_size,
333
- path.c_str());
334
- auto it = e.cache.find(key);
335
- if (it != e.cache.end()) return it->second.get();
336
- std::unique_ptr<Font> font = createFont(path, pixel_size, monospace);
337
- Font *raw = font.get();
338
- e.cache.emplace(key, std::move(font));
339
- return raw;
340
- }
341
-
342
- /* The resolved fallback faces for a primary, in order, cached per class+size. */
343
- const std::vector<Font *> &fallbackFaces(Font *font) {
344
- static const std::vector<Font *> kEmpty;
345
- if (font == nullptr) return kEmpty;
346
- Engine &e = engine();
347
- char key[80];
348
- std::snprintf(key, sizeof(key), "f|%c|%.2f", font->monospace ? 'm' : 's',
349
- (double)font->pixel_size);
350
- auto it = e.fallbacks.find(key);
351
- if (it != e.fallbacks.end()) return it->second;
352
-
353
- const std::vector<std::string> &paths =
354
- font->monospace ? e.mono_fallbacks : e.sans_fallbacks;
355
- std::vector<Font *> faces;
356
- for (const std::string &path : paths) {
357
- Font *face = resolveFacePath(path, font->pixel_size, font->monospace);
358
- if (face != nullptr) faces.push_back(face);
359
- }
360
- return e.fallbacks.emplace(key, std::move(faces)).first->second;
361
- }
362
-
363
- /* The face that draws `code_point`: the primary when it has the glyph, else the
364
- * first fallback that does, else the primary (so `.notdef` is still skipped). */
365
- Font *faceForCodePoint(Font *primary, uint32_t code_point) {
366
- if (primary != nullptr && FT_Get_Char_Index(primary->ft_face, code_point) != 0) return primary;
367
- for (Font *fallback : fallbackFaces(primary)) {
368
- if (FT_Get_Char_Index(fallback->ft_face, code_point) != 0) return fallback;
369
- }
370
- return primary;
371
- }
372
-
373
- /* Shape one face's segment of a run and append its glyphs. */
374
- float shapeSegment(const std::string &segment, Font *font, std::vector<ShapedGlyph> &out) {
375
- if (font == nullptr || segment.empty()) return 0;
376
- float total = 0;
377
- if (font->hb_font == nullptr) {
378
- /* HarfBuzz unavailable: fall back to FreeType's per-codepoint advances. */
379
- size_t index = 0;
380
- while (index < segment.size()) {
381
- uint32_t code_point = decodeUtf8(segment, index);
382
- FT_UInt glyph = FT_Get_Char_Index(font->ft_face, code_point);
383
- float advance = 0;
384
- if (FT_Load_Glyph(font->ft_face, glyph, FT_LOAD_DEFAULT) == 0) {
385
- advance = font->ft_face->glyph->advance.x / 64.0f;
386
- }
387
- ShapedGlyph shaped;
388
- shaped.glyph = glyph;
389
- shaped.x_advance = advance;
390
- shaped.font = font;
391
- total += advance;
392
- out.push_back(shaped);
393
- }
394
- return total;
395
- }
396
-
397
- hb_buffer_t *buffer = hb_buffer_create();
398
- hb_buffer_add_utf8(buffer, segment.c_str(), (int)segment.size(), 0, -1);
399
- hb_buffer_guess_segment_properties(buffer);
400
- hb_shape(font->hb_font, buffer, nullptr, 0);
401
- unsigned count = hb_buffer_get_length(buffer);
402
- hb_glyph_info_t *infos = hb_buffer_get_glyph_infos(buffer, nullptr);
403
- hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(buffer, nullptr);
404
- for (unsigned i = 0; i < count; i++) {
405
- ShapedGlyph shaped;
406
- shaped.glyph = infos[i].codepoint;
407
- shaped.x_advance = positions[i].x_advance / 64.0f;
408
- shaped.x_offset = positions[i].x_offset / 64.0f;
409
- shaped.y_offset = positions[i].y_offset / 64.0f;
410
- shaped.font = font;
411
- total += shaped.x_advance;
412
- out.push_back(shaped);
413
- }
414
- hb_buffer_destroy(buffer);
415
- return total;
416
- }
20
+ const TextApi *g_api = nullptr;
417
21
 
418
22
  } // namespace
419
23
 
420
- Font *xt_text_resolve(const FontSpec &spec) {
421
- ensureInitialized();
422
- Engine &e = engine();
423
- if (!e.ft_ok) return nullptr;
24
+ const TextApi *xt_text_api() { return g_api != nullptr ? g_api : xt_text_stb_api(); }
424
25
 
425
- bool mono = isMonospaceFamily(spec.family);
426
- char key[48];
427
- std::snprintf(key, sizeof(key), "%c|%.4f", mono ? 'm' : 's', (double)spec.pixel_size);
428
- auto cached = e.primary.find(key);
429
- if (cached != e.primary.end()) return cached->second;
26
+ void xt_text_api_install(const TextApi *api) { g_api = api; }
430
27
 
431
- const std::string &path = mono ? e.mono_path : e.sans_path;
432
- Font *font = path.empty() ? nullptr : resolveFacePath(path, spec.pixel_size, mono);
433
- e.primary.emplace(key, font);
434
- return font;
435
- }
28
+ Font *xt_text_resolve(const FontSpec &spec) { return xt_text_api()->resolve(spec); }
436
29
 
437
30
  Font *xt_text_scale(Font *font, float pixel_size) {
438
- if (font == nullptr) return nullptr;
439
- return resolveFacePath(font->path, pixel_size, font->monospace);
31
+ return xt_text_api()->scale(font, pixel_size);
440
32
  }
441
33
 
442
34
  float xt_text_measure_width(const std::string &utf8, const FontSpec &spec) {
443
- Font *font = xt_text_resolve(spec);
444
- if (font == nullptr) return approximateWidth(utf8, spec.pixel_size, spec.family);
445
35
  if (utf8.empty()) return 0.0f;
446
- const ShapedRun *run = xt_text_shape_cached(utf8, font);
36
+ Font *font = xt_text_resolve(spec);
37
+ if (font == nullptr) {
38
+ /* No font file: keep layout deterministic (bytes rather than codepoints;
39
+ * this path is only hit on hosts with no usable system font). */
40
+ return (float)utf8.size() * (spec.pixel_size * 0.55f + spec.letter_spacing);
41
+ }
42
+ const ShapedRun *run = xt_text_shape_cached(utf8, font, spec.letter_spacing);
447
43
  return run != nullptr ? run->width : 0.0f;
448
44
  }
449
45
 
450
46
  float xt_text_shape_run(const std::string &utf8, Font *font, std::vector<ShapedGlyph> &out) {
451
47
  if (font == nullptr) return 0;
452
48
  g_perf_frame.textShapeCalls += 1;
453
- float total = 0;
454
- size_t index = 0;
455
- size_t segment_start = 0;
456
- Font *segment_font = nullptr;
457
- while (index < utf8.size()) {
458
- size_t char_start = index;
459
- uint32_t code_point = decodeUtf8(utf8, index);
460
- Font *chosen = faceForCodePoint(font, code_point);
461
- if (segment_font == nullptr) {
462
- segment_font = chosen;
463
- segment_start = char_start;
464
- } else if (chosen != segment_font) {
465
- total += shapeSegment(utf8.substr(segment_start, char_start - segment_start), segment_font, out);
466
- segment_font = chosen;
467
- segment_start = char_start;
468
- }
469
- }
470
- if (segment_font != nullptr) {
471
- total += shapeSegment(utf8.substr(segment_start), segment_font, out);
472
- }
473
- return total;
49
+ return xt_text_api()->shape_run(utf8, font, out);
474
50
  }
475
51
 
476
52
  bool xt_text_rasterize(Font *font, uint32_t glyph, GlyphImage &out) {
477
- out.width = 0;
478
- out.height = 0;
479
- out.left = 0;
480
- out.top = 0;
481
- out.pixels.clear();
482
- if (font == nullptr || font->ft_face == nullptr) return false;
483
- if (FT_Load_Glyph(font->ft_face, glyph, FT_LOAD_DEFAULT) != 0) return false;
484
- if (FT_Render_Glyph(font->ft_face->glyph, FT_RENDER_MODE_NORMAL) != 0) return false;
485
- FT_GlyphSlot slot = font->ft_face->glyph;
486
- const FT_Bitmap &bitmap = slot->bitmap;
487
- out.left = slot->bitmap_left;
488
- out.top = slot->bitmap_top;
489
- if (bitmap.width == 0 || bitmap.rows == 0) return false;
490
- out.width = (int)bitmap.width;
491
- out.height = (int)bitmap.rows;
492
- out.pixels.resize((size_t)bitmap.width * bitmap.rows);
493
- for (unsigned row = 0; row < bitmap.rows; row++) {
494
- const unsigned char *source = bitmap.buffer + (ptrdiff_t)row * bitmap.pitch;
495
- std::memcpy(out.pixels.data() + (size_t)row * bitmap.width, source, bitmap.width);
496
- }
497
- return true;
53
+ return xt_text_api()->rasterize(font, glyph, out);
498
54
  }
499
55
 
500
56
  void xt_text_metrics(const FontSpec &spec, float *ascent, float *descent, float *line_height) {
501
- Font *font = xt_text_resolve(spec);
502
- if (font != nullptr) {
503
- if (ascent != nullptr) *ascent = font->ascent;
504
- if (descent != nullptr) *descent = font->descent;
505
- if (line_height != nullptr) *line_height = font->line_height;
506
- return;
507
- }
508
- if (ascent != nullptr) *ascent = spec.pixel_size * 0.8f;
509
- if (descent != nullptr) *descent = spec.pixel_size * 0.2f;
510
- if (line_height != nullptr) *line_height = spec.pixel_size * 1.2f;
57
+ xt_text_api()->metrics(spec, ascent, descent, line_height);
511
58
  }
512
59
 
513
- bool xt_text_ready() {
514
- ensureInitialized();
515
- return engine().ft_ok;
516
- }
60
+ bool xt_text_ready() { return xt_text_api()->ready(); }
517
61
 
518
62
  void xt_text_shutdown() {
519
- Engine &e = engine();
520
- /* Cached shaped runs point at faces, so drop them before the faces go. */
63
+ /* Cached shaped runs and glyphs point at backend faces; drop them first. */
521
64
  xt_text_cache_clear();
522
65
  xt_text_glyph_cache_clear();
523
- e.primary.clear();
524
- e.fallbacks.clear();
525
- e.cache.clear();
526
- for (auto &entry : e.hb_faces) {
527
- if (entry.second != nullptr) hb_face_destroy(entry.second);
528
- }
529
- e.hb_faces.clear();
530
- if (e.ft != nullptr) {
531
- FT_Done_FreeType(e.ft);
532
- e.ft = nullptr;
533
- }
534
- e.ft_ok = false;
535
- e.initialized = false;
536
- e.sans_path.clear();
537
- e.mono_path.clear();
538
- e.sans_fallbacks.clear();
539
- e.mono_fallbacks.clear();
66
+ xt_text_api()->shutdown();
67
+ xt_font_db_shutdown();
540
68
  }
541
69
 
542
70
  } // namespace xtgui
@@ -1,13 +1,19 @@
1
1
  /*
2
2
  * xbintsc GUI engine — text stack (M4).
3
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.
4
+ * Text is shaped and rasterised with the bundled stb_truetype; this module
5
+ * hides it behind a small interface so layout and paint do not see the
6
+ * third-party types. The shaper is a per-codepoint mapping plus the font's
7
+ * `kern` table: no OpenType ligatures or complex-script shaping, and only
8
+ * TrueType outlines (not CFF/OTF or variable fonts).
7
9
  *
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.
10
+ * Fonts are resolved from the installed system faces: the CSS family list is
11
+ * matched case-insensitively, generic `sans-serif`/`serif`/`monospace` map to
12
+ * platform defaults, and `font-weight` / `font-style: italic` select the real
13
+ * bold/italic face of the chosen family (falling back to the nearest weight and
14
+ * to the requested family's other styles). `XT_GUI_FONT` / `XT_GUI_FONT_MONO`
15
+ * override the generic defaults, and faces are cached per size and collection
16
+ * index.
11
17
  */
12
18
  #ifndef XT_GUI_TEXT_H
13
19
  #define XT_GUI_TEXT_H
@@ -24,6 +30,8 @@ struct FontSpec {
24
30
  float pixel_size = 16.0f;
25
31
  int weight = 400;
26
32
  bool italic = false;
33
+ /** CSS `letter-spacing` in pixels; adds to each glyph's advance. */
34
+ float letter_spacing = 0.0f;
27
35
  };
28
36
 
29
37
  /** Opaque loaded face (defined in text.cpp). */
@@ -46,7 +54,7 @@ struct ShapedRun {
46
54
  std::vector<ShapedGlyph> glyphs;
47
55
  };
48
56
 
49
- /** An 8-bit grayscale glyph bitmap, as produced by FreeType. */
57
+ /** An 8-bit grayscale glyph bitmap, as produced by stb_truetype. */
50
58
  struct GlyphImage {
51
59
  int width = 0;
52
60
  int height = 0;
@@ -70,7 +78,8 @@ float xt_text_shape_run(const std::string &utf8, Font *font, std::vector<ShapedG
70
78
  * next cache generation is dropped); callers must not hold it across frames.
71
79
  * Layout and paint both go through this, so a word is shaped once instead of
72
80
  * once per layout pass and again per repaint. `nullptr` for a null face. */
73
- const ShapedRun *xt_text_shape_cached(const std::string &utf8, Font *font);
81
+ const ShapedRun *xt_text_shape_cached(const std::string &utf8, Font *font,
82
+ float letter_spacing = 0.0f);
74
83
 
75
84
  /** Current cache occupancy (bytes of keys + glyphs, number of entries). */
76
85
  uint64_t xt_text_cache_bytes();
@@ -87,7 +96,7 @@ Font *xt_text_scale(Font *font, float pixel_size);
87
96
  bool xt_text_rasterize(Font *font, uint32_t glyph, GlyphImage &out);
88
97
 
89
98
  /** `xt_text_rasterize` through a bounded, two-generation cache. A canvas that
90
- * redraws the same text every frame otherwise runs FreeType for every glyph of
99
+ * redraws the same text every frame otherwise rasterises with stb_truetype for every glyph of
91
100
  * every `fillText`. The pointer stays valid for the rest of the frame (until a
92
101
  * later call fills a generation and drops the spare one); callers must not hold
93
102
  * it across frames. Returns null for a glyph without a bitmap. */
@@ -0,0 +1,45 @@
1
+ /*
2
+ * xbintsc GUI engine — text backend table.
3
+ *
4
+ * The default backend is the bundled stb_truetype implementation
5
+ * (`text_stb.cpp`). The optional `shaping` link feature installs a
6
+ * HarfBuzz + FreeType backend (`text_hb.cpp`) through this table, so the core
7
+ * never references either library directly. A page with only Latin text can
8
+ * therefore keep the ~3 MiB DOM-only binary, while a complex-script page can
9
+ * opt into full OpenType shaping.
10
+ */
11
+ #ifndef XT_GUI_TEXT_API_H
12
+ #define XT_GUI_TEXT_API_H
13
+
14
+ #include <string>
15
+ #include <vector>
16
+
17
+ #include "text.h"
18
+
19
+ namespace xtgui {
20
+
21
+ struct TextApi {
22
+ Font *(*resolve)(const FontSpec &spec);
23
+ Font *(*scale)(Font *font, float pixel_size);
24
+ float (*shape_run)(const std::string &utf8, Font *font, std::vector<ShapedGlyph> &out);
25
+ bool (*rasterize)(Font *font, uint32_t glyph, GlyphImage &out);
26
+ void (*metrics)(const FontSpec &spec, float *ascent, float *descent, float *line_height);
27
+ bool (*ready)();
28
+ void (*shutdown)();
29
+ };
30
+
31
+ /** The built-in stb_truetype backend, always linked. */
32
+ const TextApi *xt_text_stb_api();
33
+
34
+ /** Install the optional backend (called by the `shaping` feature bridge). */
35
+ void xt_text_api_install(const TextApi *api);
36
+
37
+ /** The installed backend, or the stb_truetype default. */
38
+ const TextApi *xt_text_api();
39
+
40
+ } // namespace xtgui
41
+
42
+ /** Implemented by `text_hb.cpp`; called by the feature's C bridge. */
43
+ extern "C" void xt_text_feature_register(void);
44
+
45
+ #endif /* XT_GUI_TEXT_API_H */