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