Skip to main content

orinium_browser/platform/renderer/
text_measurer.rs

1use 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}