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.
- package/AGENTS.md +1 -1
- package/README.md +5 -4
- package/README.zh-CN.md +4 -3
- package/dist/src/cli/extensions.d.ts +24 -0
- package/dist/src/cli/extensions.js +62 -0
- package/dist/src/cli/extensions.js.map +1 -0
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/cli/help.js +3 -1
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hints.js +8 -3
- package/dist/src/cli/hints.js.map +1 -1
- package/dist/src/cli/main.js +21 -28
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/diagnostics/diagnostic.d.ts +3 -1
- package/dist/src/diagnostics/diagnostic.js +2 -0
- package/dist/src/diagnostics/diagnostic.js.map +1 -1
- package/dist/src/driver/build-key.d.ts +2 -0
- package/dist/src/driver/build-key.js +1 -0
- package/dist/src/driver/build-key.js.map +1 -1
- package/dist/src/driver/compiler.js +61 -8
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/config.d.ts +4 -0
- package/dist/src/driver/config.js +2 -0
- package/dist/src/driver/config.js.map +1 -1
- package/dist/src/driver/link-features.d.ts +37 -0
- package/dist/src/driver/link-features.js +123 -0
- package/dist/src/driver/link-features.js.map +1 -0
- package/dist/src/driver/options.d.ts +10 -0
- package/dist/src/driver/runtime-objects.d.ts +5 -0
- package/dist/src/driver/runtime-objects.js +20 -1
- package/dist/src/driver/runtime-objects.js.map +1 -1
- package/dist/src/driver/usage.d.ts +23 -0
- package/dist/src/driver/usage.js +353 -0
- package/dist/src/driver/usage.js.map +1 -0
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/catalog.js +1 -2
- package/dist/src/extensions/catalog.js.map +1 -1
- package/dist/src/extensions/gui/features.d.ts +34 -0
- package/dist/src/extensions/gui/features.js +165 -0
- package/dist/src/extensions/gui/features.js.map +1 -0
- package/dist/src/extensions/gui/index.js +10 -4
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/link-feature.d.ts +70 -0
- package/dist/src/extensions/link-feature.js +18 -0
- package/dist/src/extensions/link-feature.js.map +1 -0
- package/dist/src/extensions/registry.d.ts +24 -4
- package/dist/src/extensions/registry.js +26 -3
- package/dist/src/extensions/registry.js.map +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +0 -1
- package/dist/src/index.js.map +1 -1
- package/dist/tests/cli/hints.test.js +5 -7
- package/dist/tests/cli/hints.test.js.map +1 -1
- package/dist/tests/driver/link-features.test.js +105 -0
- package/dist/tests/driver/link-features.test.js.map +1 -0
- package/dist/tests/driver/usage.test.d.ts +1 -0
- package/dist/tests/driver/usage.test.js +88 -0
- package/dist/tests/driver/usage.test.js.map +1 -0
- package/dist/tests/e2e/gui-audio.test.js +4 -4
- package/dist/tests/e2e/gui-audio.test.js.map +1 -1
- package/dist/tests/e2e/gui-css-coverage.test.d.ts +9 -0
- package/dist/tests/e2e/gui-css-coverage.test.js +333 -0
- package/dist/tests/e2e/gui-css-coverage.test.js.map +1 -0
- package/dist/tests/e2e/gui-css-features.test.d.ts +6 -0
- package/dist/tests/e2e/gui-css-features.test.js +270 -0
- package/dist/tests/e2e/gui-css-features.test.js.map +1 -0
- package/dist/tests/e2e/gui-effects.test.js +92 -11
- package/dist/tests/e2e/gui-effects.test.js.map +1 -1
- package/dist/tests/e2e/gui-feature-linking.test.d.ts +9 -0
- package/dist/tests/e2e/gui-feature-linking.test.js +68 -0
- package/dist/tests/e2e/gui-feature-linking.test.js.map +1 -0
- package/dist/tests/e2e/gui-font-face.test.d.ts +10 -0
- package/dist/tests/e2e/gui-font-face.test.js +142 -0
- package/dist/tests/e2e/gui-font-face.test.js.map +1 -0
- package/dist/tests/e2e/gui-fonts.test.d.ts +10 -0
- package/dist/tests/e2e/gui-fonts.test.js +150 -0
- package/dist/tests/e2e/gui-fonts.test.js.map +1 -0
- package/dist/tests/e2e/gui-helpers.d.ts +10 -1
- package/dist/tests/e2e/gui-helpers.js +6 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-layout.test.js +7 -3
- package/dist/tests/e2e/gui-layout.test.js.map +1 -1
- package/dist/tests/e2e/gui-media-disabled.test.d.ts +5 -4
- package/dist/tests/e2e/gui-media-disabled.test.js +7 -6
- package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.d.ts +5 -4
- package/dist/tests/e2e/gui-media.test.js +9 -9
- package/dist/tests/e2e/gui-media.test.js.map +1 -1
- package/dist/tests/e2e/gui-paint.test.js +5 -2
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/dist/tests/e2e/gui-shaping.test.d.ts +5 -0
- package/dist/tests/e2e/gui-shaping.test.js +47 -0
- package/dist/tests/e2e/gui-shaping.test.js.map +1 -0
- package/dist/tests/e2e/gui-track-sizing.test.d.ts +6 -0
- package/dist/tests/e2e/gui-track-sizing.test.js +173 -0
- package/dist/tests/e2e/gui-track-sizing.test.js.map +1 -0
- package/dist/tests/e2e/gui-transform.test.js +3 -2
- package/dist/tests/e2e/gui-transform.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl-pipeline-cache.test.d.ts +18 -0
- package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js +120 -0
- package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js.map +1 -0
- package/dist/tests/extensions/gui-features.test.d.ts +1 -0
- package/dist/tests/extensions/gui-features.test.js +175 -0
- package/dist/tests/extensions/gui-features.test.js.map +1 -0
- package/doc/ai/cli.md +5 -1
- package/doc/ai/extensions.md +56 -26
- package/doc/ai/troubleshooting.md +10 -7
- package/doc/ai/zh-CN/cli.md +5 -1
- package/doc/ai/zh-CN/extensions.md +48 -22
- package/doc/ai/zh-CN/troubleshooting.md +6 -5
- package/doc/gui/animation.md +39 -9
- package/doc/gui/api.md +2 -1
- package/doc/gui/build.md +137 -19
- package/doc/gui/canvas.md +1 -1
- package/doc/gui/dom.md +20 -4
- package/doc/gui/known-issues.md +63 -26
- package/doc/gui/layout.md +31 -8
- package/doc/gui/media.md +13 -7
- package/doc/gui/rendering.md +114 -39
- package/doc/gui/runtime.md +5 -0
- package/doc/gui/webgl.md +24 -6
- package/doc/gui.md +110 -47
- package/doc/implemented/cli.md +2 -0
- package/doc/implemented/extensions.md +9 -3
- package/doc/npm-package.md +4 -4
- package/doc/xbintsc.config.schema.json +10 -0
- package/doc/zh-CN/gui/animation.md +33 -10
- package/doc/zh-CN/gui/api.md +2 -1
- package/doc/zh-CN/gui/build.md +110 -16
- package/doc/zh-CN/gui/canvas.md +1 -1
- package/doc/zh-CN/gui/dom.md +16 -3
- package/doc/zh-CN/gui/known-issues.md +45 -15
- package/doc/zh-CN/gui/layout.md +23 -6
- package/doc/zh-CN/gui/media.md +11 -6
- package/doc/zh-CN/gui/rendering.md +84 -26
- package/doc/zh-CN/gui/runtime.md +3 -0
- package/doc/zh-CN/gui/webgl.md +3 -3
- package/doc/zh-CN/gui.md +92 -41
- package/doc/zh-CN/implemented/cli.md +2 -0
- package/doc/zh-CN/implemented/extensions.md +9 -3
- package/doc/zh-CN/npm-package.md +4 -3
- package/package.json +7 -7
- package/runtime/ext_gui/animation_values.cpp +425 -0
- package/runtime/ext_gui/animation_values.h +64 -0
- package/runtime/ext_gui/browser.cpp +22 -15
- package/runtime/ext_gui/browser_loop.cpp +13 -1
- package/runtime/ext_gui/canvas.h +1 -1
- package/runtime/ext_gui/canvas_raster.cpp +1 -1
- package/runtime/ext_gui/canvas_text.cpp +1 -1
- package/runtime/ext_gui/css.cpp +172 -21
- package/runtime/ext_gui/css.h +75 -4
- package/runtime/ext_gui/css_match.cpp +4 -0
- package/runtime/ext_gui/css_sheet.cpp +218 -2
- package/runtime/ext_gui/document.cpp +42 -192
- package/runtime/ext_gui/document.h +25 -12
- package/runtime/ext_gui/document_animation.cpp +428 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +15 -8
- package/runtime/ext_gui/dom_api_canvas_extra.cpp +44 -20
- package/runtime/ext_gui/dom_api_canvas_image.cpp +3 -1
- package/runtime/ext_gui/dom_api_node_read.cpp +10 -0
- package/runtime/ext_gui/dom_api_offscreen.cpp +3 -1
- package/runtime/ext_gui/dom_api_style.cpp +7 -0
- package/runtime/ext_gui/dom_api_webgl_internal.h +4 -0
- package/runtime/ext_gui/dom_api_webgl_source.cpp +5 -2
- package/runtime/ext_gui/effect_values.cpp +245 -21
- package/runtime/ext_gui/gpu_bridge.c +28 -0
- package/runtime/ext_gui/gpu_feature.cpp +30 -0
- package/runtime/ext_gui/gpu_shader.cpp +22 -0
- package/runtime/ext_gui/gpu_shader_api.h +50 -0
- package/runtime/ext_gui/gradient_values.cpp +24 -0
- package/runtime/ext_gui/grid_values.cpp +23 -0
- package/runtime/ext_gui/gui.cpp +6 -37
- package/runtime/ext_gui/gui_canvas.cpp +14 -6
- package/runtime/ext_gui/gui_cursor.cpp +50 -0
- package/runtime/ext_gui/gui_engine.h +1 -18
- package/runtime/ext_gui/gui_events.cpp +19 -6
- package/runtime/ext_gui/gui_internal.h +3 -0
- package/runtime/ext_gui/gui_webgl.cpp +25 -10
- package/runtime/ext_gui/image_api.cpp +18 -0
- package/runtime/ext_gui/image_api.h +43 -0
- package/runtime/ext_gui/image_bitmap_browser.cpp +20 -11
- package/runtime/ext_gui/image_bridge.c +22 -0
- package/runtime/ext_gui/image_feature.cpp +107 -0
- package/runtime/ext_gui/inline_svg.cpp +95 -8
- package/runtime/ext_gui/inline_svg.h +5 -1
- package/runtime/ext_gui/layout.cpp +15 -9
- package/runtime/ext_gui/layout.h +8 -2
- package/runtime/ext_gui/layout_flex.cpp +134 -29
- package/runtime/ext_gui/layout_flow.cpp +58 -7
- package/runtime/ext_gui/layout_flow_internal.h +39 -4
- package/runtime/ext_gui/layout_grid.cpp +31 -0
- package/runtime/ext_gui/layout_inline.cpp +26 -2
- package/runtime/ext_gui/layout_vertical.cpp +196 -0
- package/runtime/ext_gui/media.cpp +4 -2
- package/runtime/ext_gui/media.h +3 -3
- package/runtime/ext_gui/media_api.h +7 -6
- package/runtime/ext_gui/paint.cpp +67 -9
- package/runtime/ext_gui/paint.h +61 -8
- package/runtime/ext_gui/paint_backdrop.cpp +211 -0
- package/runtime/ext_gui/paint_background.cpp +175 -97
- package/runtime/ext_gui/paint_effects.cpp +12 -6
- package/runtime/ext_gui/paint_internal.h +27 -0
- package/runtime/ext_gui/paint_outline.cpp +44 -0
- package/runtime/ext_gui/paint_shadow.cpp +368 -0
- package/runtime/ext_gui/renderer.cpp +15 -7
- package/runtime/ext_gui/renderer.h +11 -2
- package/runtime/ext_gui/renderer_backdrop.cpp +374 -0
- package/runtime/ext_gui/renderer_bitmap.cpp +82 -0
- package/runtime/ext_gui/renderer_gradient.cpp +51 -17
- package/runtime/ext_gui/renderer_image.cpp +19 -7
- package/runtime/ext_gui/renderer_internal.h +21 -0
- package/runtime/ext_gui/renderer_layer.cpp +37 -6
- package/runtime/ext_gui/renderer_text.cpp +2 -1
- package/runtime/ext_gui/stb_truetype.h +5077 -0
- package/runtime/ext_gui/style.cpp +62 -8
- package/runtime/ext_gui/style.h +111 -4
- package/runtime/ext_gui/style_cascade.cpp +83 -0
- package/runtime/ext_gui/style_internal.h +13 -1
- package/runtime/ext_gui/style_length.cpp +92 -0
- package/runtime/ext_gui/style_print.cpp +83 -2
- package/runtime/ext_gui/style_svg.cpp +40 -0
- package/runtime/ext_gui/style_text.cpp +302 -0
- package/runtime/ext_gui/style_ui.cpp +150 -0
- package/runtime/ext_gui/style_values.cpp +76 -62
- package/runtime/ext_gui/style_values_internal.h +23 -0
- package/runtime/ext_gui/text.cpp +27 -499
- package/runtime/ext_gui/text.h +18 -9
- package/runtime/ext_gui/text_api.h +45 -0
- package/runtime/ext_gui/text_bridge.c +23 -0
- package/runtime/ext_gui/text_cache.cpp +12 -3
- package/runtime/ext_gui/text_font_db.cpp +427 -0
- package/runtime/ext_gui/text_font_paths.cpp +250 -0
- package/runtime/ext_gui/text_font_paths.h +39 -0
- package/runtime/ext_gui/text_font_sfnt.cpp +396 -0
- package/runtime/ext_gui/text_font_sfnt.h +44 -0
- package/runtime/ext_gui/text_fonts.cpp +348 -0
- package/runtime/ext_gui/text_fonts.h +78 -0
- package/runtime/ext_gui/text_fonts_internal.h +77 -0
- package/runtime/ext_gui/text_glyph_cache.cpp +2 -2
- package/runtime/ext_gui/text_hb.cpp +438 -0
- package/runtime/ext_gui/text_stb.cpp +369 -0
- package/runtime/ext_gui/transform.cpp +107 -9
- package/runtime/ext_gui/transform.h +5 -0
- package/runtime/ext_gui/webgl_api.cpp +19 -0
- package/runtime/ext_gui/webgl_api.h +53 -0
- package/runtime/ext_gui/webgl_bridge.c +25 -0
- package/runtime/ext_gui/webgl_draw_setup.cpp +42 -1
- package/runtime/ext_gui/webgl_feature.cpp +82 -0
- package/runtime/ext_gui/webgl_feature.h +28 -0
- package/runtime/ext_gui/webgl_msl.cpp +41 -0
- package/runtime/ext_gui/webgl_msl.h +6 -0
- package/runtime/ext_gui/webgl_shader.cpp +40 -32
- package/runtime/ext_gui/window.cpp +7 -7
- package/runtime/ext_media/decode_audio.cpp +1 -1
- package/runtime/ext_media/media.cpp +1 -1
- package/runtime/ext_media/media_bridge.c +7 -7
- package/scripts/build-gui.ts +97 -98
- package/scripts/check-file-length.ts +1 -0
- package/scripts/gui-build-options.ts +122 -0
- package/src/cli/extensions.ts +72 -0
- package/src/cli/help.ts +3 -1
- package/src/cli/hints.ts +9 -3
- package/src/cli/main.ts +21 -27
- package/src/diagnostics/diagnostic.ts +2 -0
- package/src/driver/build-key.ts +3 -0
- package/src/driver/compiler.ts +63 -8
- package/src/driver/config.ts +6 -0
- package/src/driver/link-features.ts +163 -0
- package/src/driver/options.ts +10 -0
- package/src/driver/runtime-objects.ts +26 -1
- package/src/driver/usage.ts +376 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/catalog.ts +1 -2
- package/src/extensions/gui/features.ts +166 -0
- package/src/extensions/gui/index.ts +10 -4
- package/src/extensions/link-feature.ts +72 -0
- package/src/extensions/registry.ts +45 -4
- package/src/index.ts +3 -2
- package/dist/src/extensions/media/index.d.ts +0 -28
- package/dist/src/extensions/media/index.js +0 -59
- package/dist/src/extensions/media/index.js.map +0 -1
- package/dist/tests/extensions/media.test.js +0 -63
- package/dist/tests/extensions/media.test.js.map +0 -1
- package/src/extensions/media/index.ts +0 -65
- /package/dist/tests/{extensions/media.test.d.ts → driver/link-features.test.d.ts} +0 -0
package/runtime/ext_gui/text.cpp
CHANGED
|
@@ -1,542 +1,70 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* xbintsc GUI engine — text
|
|
2
|
+
* xbintsc GUI engine — text front-end.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* (
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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 "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
524
|
-
|
|
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
|
package/runtime/ext_gui/text.h
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* xbintsc GUI engine — text stack (M4).
|
|
3
3
|
*
|
|
4
|
-
* Text is shaped
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
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
|
|
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 */
|