MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches

◆ lay_out()

MAYAFLUX_API LayoutResult MayaFlux::Portal::Text::lay_out ( std::string_view  text,
GlyphAtlas &  atlas,
float  pen_x = 0.F,
float  pen_y = 0.F,
uint32_t  wrap_w = 0,
uint32_t  tab_width = 4 
)

Lay out a UTF-8 string into a sequence of screen-space quads.

Every codepoint this text needs is rasterized (cached, if not already) before any quad is emitted, so a GlyphAtlas growth triggered partway through this text cannot invalidate the UV coordinates already baked into quads emitted earlier in the same call. This guarantee is per-call only: a LayoutResult held across later, unrelated lay_out() calls on the same atlas is not protected if the atlas grows afterward from something else.

Every codepoint for which is_control() is true is never rasterized or counted as printable content. Among those, \t advances pen_x to the next tab stop and
advances pen_y by atlas.line_height() and resets pen_x; every other control codepoint (including \r) is skipped with no side effect. This is the same is_control() identification raw-string text handling uses; only the reaction to it differs here.

Consecutive non-control glyphs are kerned via FT_Get_Kerning() when the producing face (GlyphMetrics::face) has a kerning table. The previous glyph index resets to none across any control codepoint, across a wrap-induced line break, and whenever the producing face changes between consecutive glyphs: a glyph index is only meaningful within the face that assigned it, so a codepoint resolved through GlyphAtlas's fallback chain never gets kerned against a glyph from a different face.

Wrapping at wrap_w is a pure render-bounds guarantee, not word-aware line breaking: it breaks mid-word freely, with no whitespace lookback. This is deliberate - correctness here means no glyph ever renders past wrap_w, not that words stay intact. Each glyph is checked before it is placed, against its own advance width times a 1.5x margin, so the decision to wrap accounts for the glyph about to be drawn rather than reacting one glyph late to cumulative pen_x history - the margin absorbs the gap between a glyph's advance and its true rendered width so a glyph is never placed half past the boundary. A tab is checked the same way against its own tab-stop advance (no margin, since it draws nothing itself) immediately after computing its destination, not deferred to the next glyph's turn.

Bidirectional reordering and shaping are not performed. HarfBuzz slots in before the glyph index step when needed; the quad assembly loop and GlyphAtlas remain unchanged.

Each quad's byte_offset is the position of its codepoint's first byte in text, letting a caller correlate a quad back to source text (styling, hit-testing, extraction) without re-decoding UTF-8 itself.

Parameters
textUTF-8 encoded input string.
atlasGlyphAtlas to query and populate. May be modified (dirty flag set) if new glyphs are rasterized.
pen_xStarting horizontal pen position in pixels.
pen_yStarting vertical pen position in pixels (baseline).
wrap_wColumn to wrap at in pixels, or 0 to disable wrapping.
tab_widthTab stop width in space-glyph-widths.
Returns
LayoutResult containing quads and final pen position.

Definition at line 22 of file TypeSetter.cpp.

29{
30 if (text.empty()) {
31 return { .quads = {}, .final_pen_x = pen_x, .final_pen_y = pen_y };
32 }
33
34 LayoutResult out;
35 out.quads.reserve(text.size());
36
37 const float origin_x = 0.F;
38
39 {
40 const auto* prewarm_bytes = reinterpret_cast<const utf8proc_uint8_t*>(text.data());
41 auto prewarm_remaining = static_cast<utf8proc_ssize_t>(text.size());
42 utf8proc_ssize_t prewarm_offset = 0;
43
44 while (prewarm_offset < prewarm_remaining) {
45 utf8proc_int32_t prewarm_codepoint = 0;
46 const utf8proc_ssize_t n = utf8proc_iterate(
47 prewarm_bytes + prewarm_offset, prewarm_remaining - prewarm_offset, &prewarm_codepoint);
48
49 if (n <= 0) {
50 prewarm_offset += 1;
51 continue;
52 }
53 prewarm_offset += n;
54
55 if (prewarm_codepoint >= 0 && !is_control(static_cast<uint32_t>(prewarm_codepoint))) {
56 atlas.get_or_rasterize(static_cast<FT_ULong>(prewarm_codepoint));
57 }
58 }
59
60 if (tab_width > 0) {
61 atlas.get_or_rasterize(static_cast<FT_ULong>(' '));
62 }
63 }
64
65 FT_UInt prev_glyph_index = 0;
66 const FontFace* prev_face = nullptr;
67
68 constexpr float k_wrap_margin = 1.5F;
69
70 const auto apply_wrap = [&](float upcoming_width) -> bool {
71 if (wrap_w > 0 && pen_x + upcoming_width * k_wrap_margin > static_cast<float>(wrap_w)) {
72 pen_x = origin_x;
73 pen_y += static_cast<float>(atlas.line_height());
74 prev_glyph_index = 0;
75 prev_face = nullptr;
76 return true;
77 }
78 return false;
79 };
80
81 const auto* bytes = reinterpret_cast<const utf8proc_uint8_t*>(text.data());
82 auto remaining = static_cast<utf8proc_ssize_t>(text.size());
83 utf8proc_ssize_t offset = 0;
84
85 while (offset < remaining) {
86 const auto codepoint_start = static_cast<size_t>(offset);
87 utf8proc_int32_t codepoint = 0;
88 const utf8proc_ssize_t n = utf8proc_iterate(bytes + offset, remaining - offset, &codepoint);
89
90 if (n <= 0) {
91 MF_WARN(Journal::Component::Portal, Journal::Context::API,
92 "TypeSetter: invalid UTF-8 sequence at byte offset {}, skipping byte",
93 static_cast<size_t>(offset));
94 offset += 1;
95 continue;
96 }
97
98 offset += n;
99
100 if (codepoint < 0) {
101 continue;
102 }
103
104 if (is_control(static_cast<uint32_t>(codepoint))) {
105 if (codepoint == '\t' && tab_width > 0) {
106 const GlyphMetrics* space = atlas.get_or_rasterize(static_cast<FT_ULong>(' '));
107 const float stop_width = space && space->advance_x > 0
108 ? static_cast<float>(space->advance_x) * static_cast<float>(tab_width)
109 : 0.F;
110
111 if (stop_width > 0.F) {
112 pen_x = (std::floor((pen_x - origin_x) / stop_width) + 1.F) * stop_width + origin_x;
113 apply_wrap(0.F);
114 }
115 } else if (codepoint == '\n') {
116 pen_x = origin_x;
117 pen_y += static_cast<float>(atlas.line_height());
118 }
119
120 prev_glyph_index = 0;
121 prev_face = nullptr;
122 continue;
123 }
124
125 const GlyphMetrics* m = atlas.get_or_rasterize(static_cast<FT_ULong>(codepoint));
126 if (!m) {
127 prev_glyph_index = 0;
128 prev_face = nullptr;
129 continue;
130 }
131
132 apply_wrap(static_cast<float>(m->advance_x));
133
134 if (prev_face == m->face && prev_glyph_index != 0 && m->glyph_index != 0
135 && m->face && FT_HAS_KERNING(m->face->get_face())) {
136 FT_Vector delta {};
137 FT_Get_Kerning(m->face->get_face(), prev_glyph_index, m->glyph_index, FT_KERNING_DEFAULT, &delta);
138 pen_x += static_cast<float>(delta.x >> 6);
139 }
140
141 if (m->width > 0 && m->height > 0) {
142 GlyphQuad q {};
143 q.x0 = pen_x + static_cast<float>(m->bearing_x);
144 q.y0 = pen_y - static_cast<float>(m->bearing_y);
145 q.x1 = q.x0 + static_cast<float>(m->width);
146 q.y1 = q.y0 + static_cast<float>(m->height);
147 q.uv_x0 = m->uv_x0;
148 q.uv_y0 = m->uv_y0;
149 q.uv_x1 = m->uv_x1;
150 q.codepoint = static_cast<uint32_t>(codepoint);
151 q.uv_y1 = m->uv_y1;
152 q.byte_offset = codepoint_start;
153 out.quads.push_back(q);
154 }
155
156 pen_x += static_cast<float>(m->advance_x);
157 prev_glyph_index = m->glyph_index;
158 prev_face = m->face;
159 }
160
161 out.final_pen_x = pen_x;
162 out.final_pen_y = pen_y;
163 return out;
164}
#define MF_WARN(comp, ctx,...)
uint64_t offset
Definition Composite.cpp:10
double q
std::vector< float > * out
uint32_t line_height() const
Line advance in pixels for this atlas's pixel_size.
const GlyphMetrics * get_or_rasterize(FT_UInt glyph_index)
Return metrics for a glyph, rasterizing it into the atlas if needed.
bool is_control(uint32_t codepoint) noexcept
True if a codepoint must never be rasterized or treated as printable content.
Definition TypeSetter.cpp:9
int32_t advance_x
Horizontal advance in pixels (26.6 fixed-point >> 6).

References MayaFlux::Portal::Text::GlyphMetrics::advance_x, MayaFlux::Journal::API, MayaFlux::Portal::Text::GlyphMetrics::bearing_x, MayaFlux::Portal::Text::GlyphMetrics::bearing_y, MayaFlux::Portal::Text::GlyphMetrics::face, MayaFlux::Portal::Text::FontFace::get_face(), MayaFlux::Portal::Text::GlyphAtlas::get_or_rasterize(), MayaFlux::Portal::Text::GlyphMetrics::glyph_index, MayaFlux::Portal::Text::GlyphMetrics::height, is_control(), MayaFlux::Portal::Text::GlyphAtlas::line_height(), MF_WARN, offset, out, MayaFlux::Journal::Portal, q, MayaFlux::Portal::Text::LayoutResult::quads, MayaFlux::Portal::Text::GlyphMetrics::uv_x0, MayaFlux::Portal::Text::GlyphMetrics::uv_x1, MayaFlux::Portal::Text::GlyphMetrics::uv_y0, MayaFlux::Portal::Text::GlyphMetrics::uv_y1, MayaFlux::Portal::Text::GlyphMetrics::width, and MayaFlux::Portal::Text::GlyphQuad::x0.

Referenced by create_layout(), impress(), MayaFlux::Portal::Text::TextRun::lay_out_text(), and x_at().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: