Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
text_shaper.hpp
Go to the documentation of this file.
1#pragma once
2
3// TextShaper — measure-once, reflow-forever text layout.
4// Inspired by Cheng Lou's PreText pattern: expensive text shaping runs once,
5// then line breaking uses just arithmetic over cached segment widths.
6//
7// Default ON when PULP_TEXT_SHAPING is enabled (requires Skia with SkParagraph).
8// Falls back to character-width estimation when disabled.
9
12#include <string>
13#include <string_view>
14#include <vector>
15#include <unordered_map>
16#include <memory>
17#include <cstdint>
18#include <mutex>
19
20namespace pulp::canvas {
21
24 std::string text;
25 float width = 0; // Measured width in pixels
26 float ascent = 0; // Distance above baseline
27 float descent = 0; // Distance below baseline
28 float leading = 0; // Extra inter-line gap
29 bool is_whitespace = false; // Can break here
30 bool is_newline = false; // Hard line break
31 // This segment begins in the same logical word as its predecessor. Rich
32 // text style boundaries are not CSS line-break opportunities.
33 bool joins_previous_word = false;
34 int style_index = -1; // AttributedString span; -1 for plain text
35};
36
39public:
40 PreparedText() = default;
41
43 const std::vector<ShapedSegment>& segments() const { return segments_; }
44
46 float line_height() const { return line_height_; }
47
56 float ascent() const { return ascent_; }
57 float descent() const { return descent_; }
58 float leading() const { return leading_; }
59 bool metrics_are_real() const { return metrics_real_; }
60
68 float typographic_ascent() const {
69 return typographic_ascent_;
70 }
71 float typographic_descent() const {
72 return typographic_descent_;
73 }
74
81 int font_weight() const { return font_weight_; }
82
84 float total_width() const;
85
87 bool empty() const { return segments_.empty(); }
88
89private:
90 friend class TextShaper;
91 std::vector<ShapedSegment> segments_;
92 float line_height_ = 0;
93 float ascent_ = 0;
94 float descent_ = 0;
95 float leading_ = 0;
96 float typographic_ascent_ = 0;
97 float typographic_descent_ = 0;
98 bool metrics_real_ = false;
99 std::string font_family_;
100 float font_size_ = 0;
101 int font_weight_ = 400;
102};
103
106 struct Line {
107 struct Fragment {
108 std::string text;
109 float width = 0;
110 int style_index = -1;
111 };
112
113 float width = 0;
114 float y = 0;
115 float ascent = 0;
116 float descent = 0;
117 float leading = 0;
118 float height = 0;
124 float x_offset = 0;
127 std::string text; // Materialized line text (optional)
128 // Exact styled pieces on this line. Populated by
129 // layout_with_lines(); empty for the allocation-free layout() path.
130 // Unlike first_segment/segment_count this also represents fragments
131 // created by break-word/anywhere inside one shaped segment.
132 std::vector<Fragment> fragments;
133 };
134
135 std::vector<Line> lines;
136 float total_width = 0;
137 float total_height = 0;
138 int line_count = 0;
139};
140
167
177public:
180
192 std::string_view text, std::string_view font_family, float font_size,
193 int font_weight = 400, int font_slant = 0,
194 float letter_spacing = 0.0f,
195 const std::vector<Canvas::FontFeature>& font_features = {});
196
201 const AttributedString& text,
202 const std::vector<Canvas::FontFeature>& font_features = {});
203
214 ShapedLayout layout(const PreparedText& prepared, float max_width,
215 float line_height = 0, int max_lines = 0,
216 BreakMode break_mode = BreakMode::normal) const;
217
219 ShapedLayout layout_with_lines(const PreparedText& prepared, float max_width,
220 float line_height = 0, int max_lines = 0,
221 BreakMode break_mode = BreakMode::normal) const;
222
224 float measure_height(const PreparedText& prepared, float max_width,
225 float line_height = 0) const;
226
228 int count_lines(const PreparedText& prepared, float max_width) const;
229
232
234 bool uses_real_shaping() const;
235
236private:
237 struct Impl;
238 std::unique_ptr<Impl> impl_;
239};
240
245 float ascent = 0.0f;
246 float descent = 0.0f;
247 float height = 0.0f;
248};
249
252 float ascent = 0.0f;
253 float descent = 0.0f;
254 float leading = 0.0f;
255 bool real = false;
256
257 float line_height() const {
258 return ascent + descent + leading;
259 }
260};
261
266ParagraphTextExtents paragraph_text_extents(const std::vector<ParagraphLineExtent>& lines,
267 float font_size);
268
271
280
281} // namespace pulp::canvas
Attributed string — a sequence of styled text spans.
Definition attributed_string.hpp:40
Prepared text — shaped once, can be reflowed at any width.
Definition text_shaper.hpp:38
bool empty() const
Whether this prepared text has content.
Definition text_shaper.hpp:87
float ascent() const
SkFontMetrics-derived line box of the resolved typeface, as positive distances above (ascent) and bel...
Definition text_shaper.hpp:56
float leading() const
Definition text_shaper.hpp:58
float line_height() const
Line height for this text.
Definition text_shaper.hpp:46
float descent() const
Definition text_shaper.hpp:57
const std::vector< ShapedSegment > & segments() const
The segments (measured).
Definition text_shaper.hpp:43
bool metrics_are_real() const
Definition text_shaper.hpp:59
float total_width() const
Total width if laid out on a single line.
float typographic_ascent() const
The face's TYPOGRAPHIC ascent and descent (Skia's fAscent/fDescent, flipped positive) — the metrics C...
Definition text_shaper.hpp:68
int font_weight() const
The CSS numeric weight these segments were measured at.
Definition text_shaper.hpp:81
friend class TextShaper
Definition text_shaper.hpp:90
float typographic_descent() const
Definition text_shaper.hpp:71
Text shaper — the PreText-style measure-once-reflow-forever engine.
Definition text_shaper.hpp:176
PreparedText prepare(const AttributedString &text, const std::vector< Canvas::FontFeature > &font_features={})
Prepare an attributed string (mixed styles).
bool uses_real_shaping() const
Whether this shaper uses real font shaping (SkParagraph) or estimation.
ShapedLayout layout(const PreparedText &prepared, float max_width, float line_height=0, int max_lines=0, BreakMode break_mode=BreakMode::normal) const
Layout prepared text at a specific width — this is the cheap call.
int count_lines(const PreparedText &prepared, float max_width) const
Quick line count.
ShapedLayout layout_with_lines(const PreparedText &prepared, float max_width, float line_height=0, int max_lines=0, BreakMode break_mode=BreakMode::normal) const
Layout and materialize line text (slightly more expensive than layout()).
PreparedText prepare(std::string_view text, std::string_view font_family, float font_size, int font_weight=400, int font_slant=0, float letter_spacing=0.0f, const std::vector< Canvas::FontFeature > &font_features={})
Prepare text for layout — this is the expensive call.
float measure_height(const PreparedText &prepared, float max_width, float line_height=0) const
Quick height calculation (fastest path — just returns height, no line details).
void clear_cache()
Clear the segment measurement cache.
Definition affine_transform.hpp:24
TextShaper & global_text_shaper()
Global text shaper singleton (uses the best available backend).
std::uint64_t text_shaper_prepare_call_count()
Monotonic count of TextShaper::prepare() invocations across all shapers.
ParagraphTextExtents paragraph_text_extents(const std::vector< ParagraphLineExtent > &lines, float font_size)
Takes the max ascent, descent and leading over lines, so a later line whose fallback face is taller t...
@ text
Definition scene_node.hpp:96
BreakMode
CSS overflow-wrap / word-break break opportunity mode.
Definition text_shaper.hpp:162
@ anywhere
Definition text_shaper.hpp:165
@ break_word
Definition text_shaper.hpp:164
@ normal
Definition text_shaper.hpp:163
One shaped paragraph line's own vertical metrics, as SkParagraph's LineMetrics reports them.
Definition text_shaper.hpp:244
float descent
Definition text_shaper.hpp:246
float ascent
Definition text_shaper.hpp:245
float height
Definition text_shaper.hpp:247
Worst-case vertical extents over every line of a shaped paragraph.
Definition text_shaper.hpp:251
float line_height() const
Definition text_shaper.hpp:257
float leading
max over lines of height - ascent - descent
Definition text_shaper.hpp:254
float ascent
Definition text_shaper.hpp:252
float descent
Definition text_shaper.hpp:253
bool real
at least one line had usable metrics
Definition text_shaper.hpp:255
Definition text_shaper.hpp:107
std::string text
Definition text_shaper.hpp:108
int style_index
Definition text_shaper.hpp:110
float width
Definition text_shaper.hpp:109
Definition text_shaper.hpp:106
float y
Definition text_shaper.hpp:114
float width
Definition text_shaper.hpp:113
std::vector< Fragment > fragments
Definition text_shaper.hpp:132
int segment_count
Definition text_shaper.hpp:126
float descent
Definition text_shaper.hpp:116
float x_offset
Horizontal offset from the block's own left edge.
Definition text_shaper.hpp:124
int first_segment
Definition text_shaper.hpp:125
float leading
Definition text_shaper.hpp:117
float ascent
Definition text_shaper.hpp:115
std::string text
Definition text_shaper.hpp:127
float height
Definition text_shaper.hpp:118
Result of laying out prepared text at a specific width.
Definition text_shaper.hpp:105
std::vector< Line > lines
Definition text_shaper.hpp:135
float total_width
Definition text_shaper.hpp:136
float total_height
Definition text_shaper.hpp:137
int line_count
Definition text_shaper.hpp:138
A measured text segment — cached after shaping.
Definition text_shaper.hpp:23
float ascent
Definition text_shaper.hpp:26
int style_index
Definition text_shaper.hpp:34
std::string text
Definition text_shaper.hpp:24
float descent
Definition text_shaper.hpp:27
bool joins_previous_word
Definition text_shaper.hpp:33
float width
Definition text_shaper.hpp:25
float leading
Definition text_shaper.hpp:28
bool is_newline
Definition text_shaper.hpp:30
bool is_whitespace
Definition text_shaper.hpp:29