Skip to main content

orinium_browser/platform/renderer/
text_measurer.rs

1//! Text measurement interface and implementations.
2
3use crate::engine::bridge::text::{
4    MeasuredFragment, TextMeasureError, TextMeasureRequest, TextMeasurer,
5};
6use crate::engine::layouter::types::{FontStyle, LineHeight, TextStyle as EngineTextStyle};
7
8use std::env;
9use std::sync::Mutex;
10
11use orinium_text::TextStyle as OriTextStyle;
12use orinium_text::{
13    BidiMode, Color as OriColor, FontKey, FontStyle as OriFontStyle, FontSystem,
14    FontWeight as OriFontWeight, TextLayouter, fontdb,
15};
16
17/// Platform-backed text measurer using orinium_text.
18///
19/// This measurer performs real text shaping and line layout,
20/// and is intended for production use.
21pub struct PlatformTextMeasurer {
22    font_sys: Mutex<FontSystem>,
23    font_keys: Vec<FontKey>,
24}
25
26impl PlatformTextMeasurer {
27    pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
28        let mut maybe_bytes: Option<Vec<u8>> = None;
29
30        if let Ok(p) = env::var("ORINIUM_FONT")
31            && let Ok(b) = std::fs::read(&p)
32        {
33            maybe_bytes = Some(b);
34        }
35
36        if maybe_bytes.is_none() {
37            for p in crate::platform::font::system_font_candidates()? {
38                if let Ok(b) = std::fs::read(p) {
39                    maybe_bytes = Some(b);
40                    break;
41                }
42            }
43        }
44
45        let (font_sys, font_keys) = match maybe_bytes {
46            Some(bytes) => {
47                let mut sys = FontSystem::new();
48                let keys = sys.load_font_data(bytes);
49                (sys, keys)
50            }
51            None => return Err("no system font found".into()),
52        };
53
54        Ok(Self {
55            font_sys: Mutex::new(font_sys),
56            font_keys,
57        })
58    }
59
60    pub fn from_bytes(_id: &str, bytes: Vec<u8>) -> Result<Self, Box<dyn std::error::Error>> {
61        let mut font_sys = FontSystem::new();
62        let font_keys = font_sys.load_font_data(bytes);
63        Ok(Self {
64            font_sys: Mutex::new(font_sys),
65            font_keys,
66        })
67    }
68}
69
70impl TextMeasurer<EngineTextStyle> for PlatformTextMeasurer {
71    fn measure(
72        &self,
73        req: &TextMeasureRequest<EngineTextStyle>,
74    ) -> Result<Vec<MeasuredFragment>, TextMeasureError> {
75        let _t0 = std::time::Instant::now();
76
77        let font_size = req.style.font_size.max(1.0);
78
79        let mut fs = self
80            .font_sys
81            .lock()
82            .map_err(|e| TextMeasureError::Internal(format!("font_sys lock poisoned: {}", e)))?;
83        let t_lock = _t0.elapsed();
84
85        let line_height_ratio = match req.style.line_height {
86            LineHeight::Normal => 1.2,
87            LineHeight::Number(n) => n,
88            LineHeight::Px(px) => px / font_size,
89        };
90
91        let ori_style = OriTextStyle {
92            font_size,
93            color: OriColor(
94                req.style.color.0,
95                req.style.color.1,
96                req.style.color.2,
97                req.style.color.3,
98            ),
99            font_weight: OriFontWeight(req.style.font_weight.0),
100            font_style: match req.style.font_style {
101                FontStyle::Normal => OriFontStyle::Normal,
102                FontStyle::Italic => OriFontStyle::Italic,
103                FontStyle::Oblique => OriFontStyle::Oblique,
104            },
105            line_height: line_height_ratio,
106            bidi_mode: BidiMode::Auto,
107            font_families: vec![fontdb::Family::SansSerif],
108            exact_fonts: self.font_keys.clone(),
109        };
110
111        let mut layouter = TextLayouter::new();
112
113        let t_shape = std::time::Instant::now();
114        let shaped = layouter.shape_text(&mut *fs, &req.text, &ori_style);
115        let t_shape = t_shape.elapsed();
116
117        let line_ranges: Vec<(usize, usize)> = req
118            .text
119            .split('\n')
120            .scan(0usize, |offset, line| {
121                let start = *offset;
122                *offset += line.len() + 1;
123                let end = start + line.len();
124                Some((start, end))
125            })
126            .collect();
127
128        let t_layout = std::time::Instant::now();
129        let layout = layouter.layout_lines(&mut *fs, &shaped, &line_ranges, &ori_style);
130        let t_layout = t_layout.elapsed();
131
132        let fragments: Vec<MeasuredFragment> = layout
133            .lines
134            .iter()
135            .enumerate()
136            .map(|(i, line)| {
137                let line_text = line_ranges[i];
138                MeasuredFragment {
139                    text: req.text[line_text.0..line_text.1].to_string(),
140                    width: line.width,
141                    height: line.height,
142                }
143            })
144            .collect();
145
146        let total = _t0.elapsed();
147        let preview = if req.text.len() > 40 {
148            let cut = req.text.floor_char_boundary(40);
149            format!("{}...", &req.text[..cut])
150        } else {
151            req.text.clone()
152        };
153        log::info!(
154            target: "TextMeasurer",
155            "measure: text={:?} len={} font_size={}  lock={:?}  shape={:?}  layout={:?}  total={:?}  fragments={}",
156            preview,
157            req.text.len(),
158            font_size,
159            t_lock,
160            t_shape,
161            t_layout,
162            total,
163            fragments.len(),
164        );
165        Ok(fragments)
166    }
167}