Skip to main content

orinium_browser/platform/renderer/
text_measurer.rs

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