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
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — optional HarfBuzz + FreeType shaping backend.
|
|
3
|
+
*
|
|
4
|
+
* Installed by the `shaping` link feature through `text_bridge.c`. HarfBuzz does
|
|
5
|
+
* shaping (ligatures/kerning/complex scripts), FreeType owns the glyph outlines
|
|
6
|
+
* and the vertical metrics. We create one HarfBuzz font per (file, collection
|
|
7
|
+
* index, pixel size) and scale it so that advances come back in 26.6 pixels.
|
|
8
|
+
*
|
|
9
|
+
* A run is shaped with a *single* face, but that face rarely covers every
|
|
10
|
+
* codepoint in it — a CJK face such as Microsoft YaHei / PingFang / Noto Sans
|
|
11
|
+
* CJK has no geometric shapes (U+25AE ▮, U+25AF ▯, …), and a Latin face has no
|
|
12
|
+
* CJK. Each codepoint therefore resolves to the first face in a small fallback
|
|
13
|
+
* chain that has it, the run is shaped in segments (one per face), and each
|
|
14
|
+
* shaped glyph carries the face it came from so the rasteriser draws it from
|
|
15
|
+
* the right atlas entry. Without this a missing glyph came back as `.notdef`
|
|
16
|
+
* (glyph 0) and the renderer silently dropped it.
|
|
17
|
+
*
|
|
18
|
+
* The CSS family/weight/style lookup lives in `text_fonts.cpp`; this backend
|
|
19
|
+
* only loads the concrete `(path, collection index)` candidates it is given, so
|
|
20
|
+
* CFF/OTF and arbitrary system families work here even though the default
|
|
21
|
+
* stb_truetype backend cannot outline them.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
#include "text_api.h"
|
|
25
|
+
#include "text_fonts.h"
|
|
26
|
+
|
|
27
|
+
#include <ft2build.h>
|
|
28
|
+
#include FT_FREETYPE_H
|
|
29
|
+
|
|
30
|
+
#include <hb.h>
|
|
31
|
+
#include <hb-ot.h>
|
|
32
|
+
|
|
33
|
+
#include <cstddef>
|
|
34
|
+
#include <cstdint>
|
|
35
|
+
#include <cstdio>
|
|
36
|
+
#include <cstring>
|
|
37
|
+
#include <memory>
|
|
38
|
+
#include <string>
|
|
39
|
+
#include <unordered_map>
|
|
40
|
+
#include <vector>
|
|
41
|
+
|
|
42
|
+
namespace xtgui {
|
|
43
|
+
|
|
44
|
+
/* Opaque to callers (declared in text.h); defined here so the loading helpers
|
|
45
|
+
* in the anonymous namespace can build it. */
|
|
46
|
+
struct HbFont {
|
|
47
|
+
FT_Face ft_face = nullptr;
|
|
48
|
+
hb_font_t *hb_font = nullptr;
|
|
49
|
+
std::string path;
|
|
50
|
+
int face_index = 0;
|
|
51
|
+
float pixel_size = 16.0f;
|
|
52
|
+
float ascent = 0;
|
|
53
|
+
float descent = 0;
|
|
54
|
+
float line_height = 0;
|
|
55
|
+
|
|
56
|
+
~HbFont() {
|
|
57
|
+
if (hb_font != nullptr) hb_font_destroy(hb_font);
|
|
58
|
+
if (ft_face != nullptr) FT_Done_Face(ft_face);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
namespace {
|
|
63
|
+
|
|
64
|
+
struct Engine {
|
|
65
|
+
bool initialized = false;
|
|
66
|
+
FT_Library ft = nullptr;
|
|
67
|
+
bool ft_ok = false;
|
|
68
|
+
std::unordered_map<std::string, std::unique_ptr<HbFont>> cache; // key -> face (may be null)
|
|
69
|
+
std::unordered_map<std::string, std::vector<HbFont *>> fallbacks; // face+size -> faces
|
|
70
|
+
std::unordered_map<std::string, hb_face_t *> hb_faces; // path#index -> face
|
|
71
|
+
/** Primary face by (family, weight, style, pixel size): `resolve` and
|
|
72
|
+
* `metrics` are called per text box per layout, and rebuilding the key each
|
|
73
|
+
* time showed up in the profile. */
|
|
74
|
+
std::unordered_map<std::string, HbFont *> primary;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
Engine &engine() {
|
|
78
|
+
static Engine instance;
|
|
79
|
+
return instance;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Decode one UTF-8 sequence starting at `index`; advances `index`. */
|
|
83
|
+
uint32_t decodeUtf8(const std::string &text, size_t &index) {
|
|
84
|
+
unsigned char first = (unsigned char)text[index];
|
|
85
|
+
if (first < 0x80) {
|
|
86
|
+
index += 1;
|
|
87
|
+
return first;
|
|
88
|
+
}
|
|
89
|
+
uint32_t code_point = 0;
|
|
90
|
+
size_t extra = 0;
|
|
91
|
+
if ((first & 0xe0) == 0xc0) {
|
|
92
|
+
code_point = first & 0x1f;
|
|
93
|
+
extra = 1;
|
|
94
|
+
} else if ((first & 0xf0) == 0xe0) {
|
|
95
|
+
code_point = first & 0x0f;
|
|
96
|
+
extra = 2;
|
|
97
|
+
} else if ((first & 0xf8) == 0xf0) {
|
|
98
|
+
code_point = first & 0x07;
|
|
99
|
+
extra = 3;
|
|
100
|
+
} else {
|
|
101
|
+
index += 1;
|
|
102
|
+
return 0xfffd;
|
|
103
|
+
}
|
|
104
|
+
if (index + extra >= text.size()) {
|
|
105
|
+
index = text.size();
|
|
106
|
+
return 0xfffd;
|
|
107
|
+
}
|
|
108
|
+
for (size_t i = 1; i <= extra; i++) {
|
|
109
|
+
unsigned char next = (unsigned char)text[index + i];
|
|
110
|
+
if ((next & 0xc0) != 0x80) {
|
|
111
|
+
index += 1;
|
|
112
|
+
return 0xfffd;
|
|
113
|
+
}
|
|
114
|
+
code_point = (code_point << 6) | (next & 0x3f);
|
|
115
|
+
}
|
|
116
|
+
index += extra + 1;
|
|
117
|
+
return code_point;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void ensureInitialized() {
|
|
121
|
+
Engine &e = engine();
|
|
122
|
+
if (e.initialized) return;
|
|
123
|
+
e.initialized = true;
|
|
124
|
+
if (FT_Init_FreeType(&e.ft) != 0) {
|
|
125
|
+
e.ft = nullptr;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
e.ft_ok = true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
hb_face_t *hbFaceForPath(const std::string &path, int face_index) {
|
|
132
|
+
Engine &e = engine();
|
|
133
|
+
std::string key = path;
|
|
134
|
+
key += '#';
|
|
135
|
+
key += std::to_string(face_index);
|
|
136
|
+
auto it = e.hb_faces.find(key);
|
|
137
|
+
if (it != e.hb_faces.end()) return it->second;
|
|
138
|
+
hb_blob_t *blob = hb_blob_create_from_file(path.c_str());
|
|
139
|
+
hb_face_t *face = nullptr;
|
|
140
|
+
if (blob != nullptr && hb_blob_get_length(blob) > 0) {
|
|
141
|
+
face = hb_face_create(blob, (unsigned)face_index);
|
|
142
|
+
}
|
|
143
|
+
if (blob != nullptr) hb_blob_destroy(blob);
|
|
144
|
+
e.hb_faces.emplace(std::move(key), face);
|
|
145
|
+
return face;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
std::unique_ptr<HbFont> createFont(const std::string &path, int face_index, float pixel_size) {
|
|
149
|
+
Engine &e = engine();
|
|
150
|
+
if (!e.ft_ok || path.empty()) return nullptr;
|
|
151
|
+
|
|
152
|
+
FT_Face face = nullptr;
|
|
153
|
+
if (FT_New_Face(e.ft, path.c_str(), face_index, &face) != 0) return nullptr;
|
|
154
|
+
FT_Set_Pixel_Sizes(face, 0, (FT_UInt)(pixel_size + 0.5f));
|
|
155
|
+
|
|
156
|
+
auto font = std::make_unique<HbFont>();
|
|
157
|
+
font->ft_face = face;
|
|
158
|
+
font->path = path;
|
|
159
|
+
font->face_index = face_index;
|
|
160
|
+
font->pixel_size = pixel_size;
|
|
161
|
+
|
|
162
|
+
FT_Size_Metrics metrics = face->size->metrics;
|
|
163
|
+
font->ascent = metrics.ascender / 64.0f;
|
|
164
|
+
font->descent = -metrics.descender / 64.0f;
|
|
165
|
+
font->line_height = metrics.height / 64.0f;
|
|
166
|
+
|
|
167
|
+
hb_face_t *hb_face = hbFaceForPath(path, face_index);
|
|
168
|
+
if (hb_face != nullptr) {
|
|
169
|
+
font->hb_font = hb_font_create(hb_face);
|
|
170
|
+
hb_ot_font_set_funcs(font->hb_font);
|
|
171
|
+
int scale = (int)(pixel_size * 64.0f + 0.5f);
|
|
172
|
+
hb_font_set_scale(font->hb_font, scale, scale);
|
|
173
|
+
unsigned ppem = (unsigned)(pixel_size + 0.5f);
|
|
174
|
+
hb_font_set_ppem(font->hb_font, ppem, ppem);
|
|
175
|
+
}
|
|
176
|
+
return font;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Resolve one face by path+index, keyed by path+collection index+size so the
|
|
180
|
+
* primary resolved through a `FontSpec` and the same face resolved as a
|
|
181
|
+
* fallback share one `HbFont` (and one glyph-atlas key). */
|
|
182
|
+
HbFont *resolveFacePath(const std::string &path, int face_index, float pixel_size) {
|
|
183
|
+
Engine &e = engine();
|
|
184
|
+
if (!e.ft_ok || path.empty()) return nullptr;
|
|
185
|
+
char header[64];
|
|
186
|
+
std::snprintf(header, sizeof(header), "p|%d|%.2f|", face_index, (double)pixel_size);
|
|
187
|
+
std::string key = header;
|
|
188
|
+
key += path;
|
|
189
|
+
auto it = e.cache.find(key);
|
|
190
|
+
if (it != e.cache.end()) return it->second.get();
|
|
191
|
+
std::unique_ptr<HbFont> font = createFont(path, face_index, pixel_size);
|
|
192
|
+
HbFont *raw = font.get();
|
|
193
|
+
e.cache.emplace(std::move(key), std::move(font));
|
|
194
|
+
return raw;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* The resolved fallback faces for a primary, in order, cached per face+size. */
|
|
198
|
+
const std::vector<HbFont *> &fallbackFaces(HbFont *font) {
|
|
199
|
+
static const std::vector<HbFont *> kEmpty;
|
|
200
|
+
if (font == nullptr) return kEmpty;
|
|
201
|
+
Engine &e = engine();
|
|
202
|
+
char header[64];
|
|
203
|
+
std::snprintf(header, sizeof(header), "f|%.2f|", (double)font->pixel_size);
|
|
204
|
+
std::string key = header;
|
|
205
|
+
key += font->path;
|
|
206
|
+
key += '#';
|
|
207
|
+
key += std::to_string(font->face_index);
|
|
208
|
+
auto it = e.fallbacks.find(key);
|
|
209
|
+
if (it != e.fallbacks.end()) return it->second;
|
|
210
|
+
|
|
211
|
+
std::vector<FontFaceRef> candidates;
|
|
212
|
+
xt_font_fallback_faces(FontFaceRef{font->path, font->face_index}, false, candidates);
|
|
213
|
+
std::vector<HbFont *> faces;
|
|
214
|
+
for (const FontFaceRef &ref : candidates) {
|
|
215
|
+
HbFont *face = resolveFacePath(ref.path, ref.index, font->pixel_size);
|
|
216
|
+
if (face != nullptr) faces.push_back(face);
|
|
217
|
+
}
|
|
218
|
+
return e.fallbacks.emplace(std::move(key), std::move(faces)).first->second;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* The face that draws `code_point`: the primary when it has the glyph, else the
|
|
222
|
+
* first fallback that does, else the primary (so `.notdef` is still skipped). */
|
|
223
|
+
HbFont *faceForCodePoint(HbFont *primary, uint32_t code_point) {
|
|
224
|
+
if (primary != nullptr && FT_Get_Char_Index(primary->ft_face, code_point) != 0) return primary;
|
|
225
|
+
for (HbFont *fallback : fallbackFaces(primary)) {
|
|
226
|
+
if (FT_Get_Char_Index(fallback->ft_face, code_point) != 0) return fallback;
|
|
227
|
+
}
|
|
228
|
+
return primary;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* Shape one face's segment of a run and append its glyphs. */
|
|
232
|
+
float shapeSegment(const std::string &segment, HbFont *font, std::vector<ShapedGlyph> &out) {
|
|
233
|
+
if (font == nullptr || segment.empty()) return 0;
|
|
234
|
+
float total = 0;
|
|
235
|
+
if (font->hb_font == nullptr) {
|
|
236
|
+
/* HarfBuzz unavailable: fall back to FreeType's per-codepoint advances. */
|
|
237
|
+
size_t index = 0;
|
|
238
|
+
while (index < segment.size()) {
|
|
239
|
+
uint32_t code_point = decodeUtf8(segment, index);
|
|
240
|
+
FT_UInt glyph = FT_Get_Char_Index(font->ft_face, code_point);
|
|
241
|
+
float advance = 0;
|
|
242
|
+
if (FT_Load_Glyph(font->ft_face, glyph, FT_LOAD_DEFAULT) == 0) {
|
|
243
|
+
advance = font->ft_face->glyph->advance.x / 64.0f;
|
|
244
|
+
}
|
|
245
|
+
ShapedGlyph shaped;
|
|
246
|
+
shaped.glyph = glyph;
|
|
247
|
+
shaped.x_advance = advance;
|
|
248
|
+
shaped.font = reinterpret_cast<Font *>(font);
|
|
249
|
+
total += advance;
|
|
250
|
+
out.push_back(shaped);
|
|
251
|
+
}
|
|
252
|
+
return total;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
hb_buffer_t *buffer = hb_buffer_create();
|
|
256
|
+
hb_buffer_add_utf8(buffer, segment.c_str(), (int)segment.size(), 0, -1);
|
|
257
|
+
hb_buffer_guess_segment_properties(buffer);
|
|
258
|
+
hb_shape(font->hb_font, buffer, nullptr, 0);
|
|
259
|
+
unsigned count = hb_buffer_get_length(buffer);
|
|
260
|
+
hb_glyph_info_t *infos = hb_buffer_get_glyph_infos(buffer, nullptr);
|
|
261
|
+
hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(buffer, nullptr);
|
|
262
|
+
for (unsigned i = 0; i < count; i++) {
|
|
263
|
+
ShapedGlyph shaped;
|
|
264
|
+
shaped.glyph = infos[i].codepoint;
|
|
265
|
+
shaped.x_advance = positions[i].x_advance / 64.0f;
|
|
266
|
+
shaped.x_offset = positions[i].x_offset / 64.0f;
|
|
267
|
+
shaped.y_offset = positions[i].y_offset / 64.0f;
|
|
268
|
+
shaped.font = reinterpret_cast<Font *>(font);
|
|
269
|
+
total += shaped.x_advance;
|
|
270
|
+
out.push_back(shaped);
|
|
271
|
+
}
|
|
272
|
+
hb_buffer_destroy(buffer);
|
|
273
|
+
return total;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
} // namespace
|
|
277
|
+
|
|
278
|
+
HbFont *xt_text_hb_resolve(const FontSpec &spec) {
|
|
279
|
+
ensureInitialized();
|
|
280
|
+
Engine &e = engine();
|
|
281
|
+
if (!e.ft_ok) return nullptr;
|
|
282
|
+
|
|
283
|
+
char suffix[80];
|
|
284
|
+
std::snprintf(suffix, sizeof(suffix), "|%d|%d|%.4f", spec.weight, spec.italic ? 1 : 0,
|
|
285
|
+
(double)spec.pixel_size);
|
|
286
|
+
std::string key = spec.family;
|
|
287
|
+
key += suffix;
|
|
288
|
+
auto cached = e.primary.find(key);
|
|
289
|
+
if (cached != e.primary.end()) return cached->second;
|
|
290
|
+
|
|
291
|
+
std::vector<FontFaceRef> faces;
|
|
292
|
+
xt_font_face_candidates(spec, false, faces);
|
|
293
|
+
HbFont *font = nullptr;
|
|
294
|
+
for (const FontFaceRef &ref : faces) {
|
|
295
|
+
font = resolveFacePath(ref.path, ref.index, spec.pixel_size);
|
|
296
|
+
if (font != nullptr) break;
|
|
297
|
+
}
|
|
298
|
+
e.primary.emplace(std::move(key), font);
|
|
299
|
+
return font;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
HbFont *xt_text_hb_scale(HbFont *font, float pixel_size) {
|
|
303
|
+
if (font == nullptr) return nullptr;
|
|
304
|
+
return resolveFacePath(font->path, font->face_index, pixel_size);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
float xt_text_hb_shape(const std::string &utf8, HbFont *font, std::vector<ShapedGlyph> &out) {
|
|
308
|
+
if (font == nullptr) return 0;
|
|
309
|
+
float total = 0;
|
|
310
|
+
size_t index = 0;
|
|
311
|
+
size_t segment_start = 0;
|
|
312
|
+
HbFont *segment_font = nullptr;
|
|
313
|
+
while (index < utf8.size()) {
|
|
314
|
+
size_t char_start = index;
|
|
315
|
+
uint32_t code_point = decodeUtf8(utf8, index);
|
|
316
|
+
HbFont *chosen = faceForCodePoint(font, code_point);
|
|
317
|
+
if (segment_font == nullptr) {
|
|
318
|
+
segment_font = chosen;
|
|
319
|
+
segment_start = char_start;
|
|
320
|
+
} else if (chosen != segment_font) {
|
|
321
|
+
total += shapeSegment(utf8.substr(segment_start, char_start - segment_start), segment_font, out);
|
|
322
|
+
segment_font = chosen;
|
|
323
|
+
segment_start = char_start;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (segment_font != nullptr) {
|
|
327
|
+
total += shapeSegment(utf8.substr(segment_start), segment_font, out);
|
|
328
|
+
}
|
|
329
|
+
return total;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
bool xt_text_hb_rasterize(HbFont *font, uint32_t glyph, GlyphImage &out) {
|
|
333
|
+
out.width = 0;
|
|
334
|
+
out.height = 0;
|
|
335
|
+
out.left = 0;
|
|
336
|
+
out.top = 0;
|
|
337
|
+
out.pixels.clear();
|
|
338
|
+
if (font == nullptr || font->ft_face == nullptr) return false;
|
|
339
|
+
if (FT_Load_Glyph(font->ft_face, glyph, FT_LOAD_DEFAULT) != 0) return false;
|
|
340
|
+
if (FT_Render_Glyph(font->ft_face->glyph, FT_RENDER_MODE_NORMAL) != 0) return false;
|
|
341
|
+
FT_GlyphSlot slot = font->ft_face->glyph;
|
|
342
|
+
const FT_Bitmap &bitmap = slot->bitmap;
|
|
343
|
+
out.left = slot->bitmap_left;
|
|
344
|
+
out.top = slot->bitmap_top;
|
|
345
|
+
if (bitmap.width == 0 || bitmap.rows == 0) return false;
|
|
346
|
+
out.width = (int)bitmap.width;
|
|
347
|
+
out.height = (int)bitmap.rows;
|
|
348
|
+
out.pixels.resize((size_t)bitmap.width * bitmap.rows);
|
|
349
|
+
for (unsigned row = 0; row < bitmap.rows; row++) {
|
|
350
|
+
const unsigned char *source = bitmap.buffer + (ptrdiff_t)row * bitmap.pitch;
|
|
351
|
+
std::memcpy(out.pixels.data() + (size_t)row * bitmap.width, source, bitmap.width);
|
|
352
|
+
}
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
void xt_text_hb_metrics(const FontSpec &spec, float *ascent, float *descent, float *line_height) {
|
|
357
|
+
HbFont *font = xt_text_hb_resolve(spec);
|
|
358
|
+
if (font != nullptr) {
|
|
359
|
+
if (ascent != nullptr) *ascent = font->ascent;
|
|
360
|
+
if (descent != nullptr) *descent = font->descent;
|
|
361
|
+
if (line_height != nullptr) *line_height = font->line_height;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (ascent != nullptr) *ascent = spec.pixel_size * 0.8f;
|
|
365
|
+
if (descent != nullptr) *descent = spec.pixel_size * 0.2f;
|
|
366
|
+
if (line_height != nullptr) *line_height = spec.pixel_size * 1.2f;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
bool xt_text_hb_ready() {
|
|
370
|
+
FontSpec spec;
|
|
371
|
+
return xt_text_hb_resolve(spec) != nullptr;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
void xt_text_hb_shutdown() {
|
|
375
|
+
Engine &e = engine();
|
|
376
|
+
/* Cached shaped runs point at faces, so drop them before the faces go. */
|
|
377
|
+
e.primary.clear();
|
|
378
|
+
e.fallbacks.clear();
|
|
379
|
+
e.cache.clear();
|
|
380
|
+
for (auto &entry : e.hb_faces) {
|
|
381
|
+
if (entry.second != nullptr) hb_face_destroy(entry.second);
|
|
382
|
+
}
|
|
383
|
+
e.hb_faces.clear();
|
|
384
|
+
if (e.ft != nullptr) {
|
|
385
|
+
FT_Done_FreeType(e.ft);
|
|
386
|
+
e.ft = nullptr;
|
|
387
|
+
}
|
|
388
|
+
e.ft_ok = false;
|
|
389
|
+
e.initialized = false;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
} // namespace xtgui
|
|
393
|
+
namespace xtgui {
|
|
394
|
+
|
|
395
|
+
namespace {
|
|
396
|
+
|
|
397
|
+
Font *api_resolve(const FontSpec &spec) {
|
|
398
|
+
return reinterpret_cast<Font *>(xt_text_hb_resolve(spec));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
Font *api_scale(Font *font, float pixel_size) {
|
|
402
|
+
return reinterpret_cast<Font *>(
|
|
403
|
+
xt_text_hb_scale(reinterpret_cast<HbFont *>(font), pixel_size));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
float api_shape(const std::string &utf8, Font *font, std::vector<ShapedGlyph> &out) {
|
|
407
|
+
return xt_text_hb_shape(utf8, reinterpret_cast<HbFont *>(font), out);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
bool api_rasterize(Font *font, uint32_t glyph, GlyphImage &out) {
|
|
411
|
+
return xt_text_hb_rasterize(reinterpret_cast<HbFont *>(font), glyph, out);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
void api_metrics(const FontSpec &spec, float *ascent, float *descent, float *line_height) {
|
|
415
|
+
xt_text_hb_metrics(spec, ascent, descent, line_height);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
bool api_ready() { return xt_text_hb_ready(); }
|
|
419
|
+
|
|
420
|
+
void api_shutdown() { xt_text_hb_shutdown(); }
|
|
421
|
+
|
|
422
|
+
} // namespace
|
|
423
|
+
|
|
424
|
+
const TextApi kTextApi = {
|
|
425
|
+
api_resolve,
|
|
426
|
+
api_scale,
|
|
427
|
+
api_shape,
|
|
428
|
+
api_rasterize,
|
|
429
|
+
api_metrics,
|
|
430
|
+
api_ready,
|
|
431
|
+
api_shutdown,
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
} // namespace xtgui
|
|
435
|
+
|
|
436
|
+
extern "C" void xt_text_feature_register(void) {
|
|
437
|
+
xtgui::xt_text_api_install(&xtgui::kTextApi);
|
|
438
|
+
}
|