orinium_browser/platform/renderer/
text_measurer.rs1use crate::engine::bridge::text::{
2 GlyphCluster, MeasuredFragment, TextMeasureError, TextMeasureRequest, TextMeasurer,
3};
4use crate::engine::layouter::types::{FontStyle, LineHeight};
5use crate::platform::renderer::text::global_font;
6use crate::platform::renderer::text::text_renderer::*;
7use crate::platform::renderer::text_cache::TextShapeCache;
8use crate::{perf_scope, profile_log};
9
10use orinium_text::TextStyle as OriTextStyle;
11use orinium_text::{
12 BidiMode, Color as OriColor, FontStyle as OriFontStyle, FontWeight as OriFontWeight,
13 TextLayouter,
14};
15
16fn quantize_font_size(px: f32) -> f32 {
17 (px * 64.0).round() / 64.0
18}
19
20pub struct PlatformTextMeasurer {
21 cache: TextShapeCache,
22}
23
24impl PlatformTextMeasurer {
25 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
26 if global_font::global_font_system_ready() {
27 Ok(Self {
28 cache: TextShapeCache::new(),
29 })
30 } else {
31 Err("no system font found".into())
32 }
33 }
34
35 pub fn from_bytes(_id: &str, _bytes: Vec<u8>) -> Result<Self, Box<dyn std::error::Error>> {
36 Ok(Self {
37 cache: TextShapeCache::new(),
38 })
39 }
40}
41
42impl TextMeasurer for PlatformTextMeasurer {
43 fn measure(&self, req: &TextMeasureRequest) -> Result<Vec<MeasuredFragment>, TextMeasureError> {
44 perf_scope!(total);
45
46 let style = req.attribute.style.clone();
47 let flow_style = req.attribute.flow_style;
48
49 let font_size = quantize_font_size(flow_style.font_size.max(1.0));
50
51 let line_height_ratio = match flow_style.line_height {
52 LineHeight::Normal => 1.2,
53 LineHeight::Number(n) => n,
54 LineHeight::Px(px) => px / font_size,
55 };
56
57 let font_families = build_family_list(&style.font_families);
58
59 let ori_style = OriTextStyle {
60 font_size,
61 color: OriColor(style.color.0, style.color.1, style.color.2, style.color.3),
62 font_weight: OriFontWeight(style.font_weight.0),
63 font_style: match style.font_style {
64 FontStyle::Normal => OriFontStyle::Normal,
65 FontStyle::Italic => OriFontStyle::Italic,
66 FontStyle::Oblique => OriFontStyle::Oblique,
67 },
68 line_height: line_height_ratio,
69 bidi_mode: BidiMode::Auto,
70 font_families,
71 exact_fonts: Vec::new(),
72 variant: orinium_text::FontVariant::Normal,
73 };
74
75 let mut layouter = TextLayouter::new();
76
77 perf_scope!(shape);
78 let shaped = if let Some(shaped) = self.cache.get(&req.text, &ori_style) {
79 shaped
80 } else {
81 let shaped = global_font::with_global_font_system(|fs| {
82 layouter.shape_text(fs, &req.text, &ori_style)
83 });
84
85 self.cache.insert(&req.text, &ori_style, shaped.clone());
86 shaped
87 };
88 #[cfg(any(feature = "profile", debug_assertions))]
89 let shape_time = shape.elapsed();
90
91 let line_ranges: Vec<(usize, usize)> = req
92 .text
93 .split('\n')
94 .scan(0usize, |offset, line| {
95 let start = *offset;
96 *offset += line.len() + 1;
97 let end = start + line.len();
98 Some((start, end))
99 })
100 .collect();
101
102 perf_scope!(layout_pass);
103 let layout = global_font::with_global_font_system(|fs| {
104 layouter.layout_lines(fs, &shaped, &line_ranges, &ori_style)
105 });
106 #[cfg(any(feature = "profile", debug_assertions))]
107 let layout_time = layout_pass.elapsed();
108
109 let fragments: Vec<MeasuredFragment> = layout
110 .lines
111 .iter()
112 .enumerate()
113 .map(|(i, line)| {
114 let line_text = line_ranges[i];
115 MeasuredFragment {
116 text: req.text[line_text.0..line_text.1].to_string(),
117 width: line.width,
118 height: line.height,
119 }
120 })
121 .collect();
122
123 profile_log!(
124 target: "TextMeasurer",
125 log::Level::Info,
126 "measure: text={:?} len={} font_size={} shape={:?} layout={:?} total={:?} fragments={}",
127 crate::profile::text_preview(&req.text),
128 req.text.len(),
129 font_size,
130 shape_time,
131 layout_time,
132 total.elapsed(),
133 fragments.len(),
134 );
135 Ok(fragments)
136 }
137
138 fn measure_shaped(
139 &self,
140 req: &TextMeasureRequest,
141 ) -> Result<Vec<GlyphCluster>, TextMeasureError> {
142 perf_scope!(total);
143
144 let style = req.attribute.style.clone();
145 let flow_style = req.attribute.flow_style;
146
147 let font_size = quantize_font_size(flow_style.font_size.max(1.0));
148
149 let line_height_ratio = match flow_style.line_height {
150 LineHeight::Normal => 1.2,
151 LineHeight::Number(n) => n,
152 LineHeight::Px(px) => px / font_size,
153 };
154
155 let font_families = build_family_list(&style.font_families);
156
157 let ori_style = OriTextStyle {
158 font_size,
159 color: OriColor(style.color.0, style.color.1, style.color.2, style.color.3),
160 font_weight: OriFontWeight(style.font_weight.0),
161 font_style: match style.font_style {
162 FontStyle::Normal => OriFontStyle::Normal,
163 FontStyle::Italic => OriFontStyle::Italic,
164 FontStyle::Oblique => OriFontStyle::Oblique,
165 },
166 line_height: line_height_ratio,
167 bidi_mode: BidiMode::Auto,
168 font_families,
169 exact_fonts: Vec::new(),
170 variant: orinium_text::FontVariant::Normal,
171 };
172
173 let mut layouter = TextLayouter::new();
174
175 let shaped = if let Some(shaped) = self.cache.get(&req.text, &ori_style) {
176 shaped
177 } else {
178 let shaped = global_font::with_global_font_system(|fs| {
179 layouter.shape_text(fs, &req.text, &ori_style)
180 });
181
182 self.cache.insert(&req.text, &ori_style, shaped.clone());
183 shaped
184 };
185
186 let clusters: Vec<GlyphCluster> = shaped
187 .fragments
188 .iter()
189 .map(|f| GlyphCluster {
190 byte_offset: f.cluster,
191 width: f.width,
192 break_allowed: f.break_after,
193 })
194 .collect();
195
196 profile_log!(
197 target: "TextMeasurer",
198 log::Level::Info,
199 "measure_shaped: text={:?} len={} font_size={} total={:?} clusters={}",
200 crate::profile::text_preview(&req.text),
201 req.text.len(),
202 font_size,
203 total.elapsed(),
204 clusters.len(),
205 );
206
207 Ok(clusters)
208 }
209}