orinium_browser/platform/renderer/
text_measurer.rs1use 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
17pub 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}