orinium_browser/platform/renderer/
text_measurer.rs1use crate::engine::bridge::text::{
4 TextMeasureError, TextMeasureRequest, TextMeasurer, TextMetrics,
5};
6use crate::engine::layouter::types::TextStyle;
7
8use std::env;
9use std::sync::{Arc, Mutex};
10
11use glyphon::{Attrs, Buffer, Color as GlyphColor, FontSystem, Metrics, Shaping, Style, Weight};
12
13pub struct PlatformTextMeasurer {
18 font_sys: Mutex<FontSystem>,
20}
21
22impl PlatformTextMeasurer {
23 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
29 let mut maybe_bytes: Option<Vec<u8>> = None;
30
31 if let Ok(p) = env::var("ORINIUM_FONT")
32 && let Ok(b) = std::fs::read(&p)
33 {
34 maybe_bytes = Some(b);
35 }
36
37 if maybe_bytes.is_none() {
38 for p in crate::platform::font::system_font_candidates()? {
39 if let Ok(b) = std::fs::read(p) {
40 maybe_bytes = Some(b);
41 break;
42 }
43 }
44 }
45
46 if let Some(bytes) = maybe_bytes {
47 let font_source = Arc::new(bytes);
48 let font = glyphon::fontdb::Source::Binary(font_source);
49 let font_sys = FontSystem::new_with_fonts(vec![font]);
50
51 return Ok(Self {
52 font_sys: Mutex::new(font_sys),
53 });
54 }
55
56 Err("no system font found".into())
57 }
58
59 pub fn from_bytes(_id: &str, bytes: Vec<u8>) -> Result<Self, Box<dyn std::error::Error>> {
61 let font_source = Arc::new(bytes);
62 let font = glyphon::fontdb::Source::Binary(font_source);
63 let font_sys = FontSystem::new_with_fonts(vec![font]);
64
65 Ok(Self {
66 font_sys: Mutex::new(font_sys),
67 })
68 }
69}
70
71impl TextMeasurer<TextStyle> for PlatformTextMeasurer {
72 fn measure(
78 &self,
79 req: &TextMeasureRequest<TextStyle>,
80 ) -> Result<TextMetrics, TextMeasureError> {
81 let font_size = req.style.font_size.max(1.0);
82
83 let mut fs = self
84 .font_sys
85 .lock()
86 .map_err(|e| TextMeasureError::Internal(format!("font_sys lock poisoned: {}", e)))?;
87
88 let metrics = Metrics::relative(font_size, 1.2);
90 let mut buffer = Buffer::new(&mut fs, metrics);
91
92 let attrs = Attrs::new()
94 .metrics(metrics)
95 .color(GlyphColor::rgba(0, 0, 0, 255))
96 .weight(Weight(req.style.font_weight.0))
97 .style(Style::from(req.style.font_style));
98
99 buffer.set_text(&mut fs, &req.text, &attrs, Shaping::Advanced, None);
100
101 let mut max_width: f32 = 0.0;
102 let mut line_count: usize = 0;
103
104 for para_i in 0..buffer.lines.len() {
106 if let Some(layout_lines) = buffer.line_layout(&mut fs, para_i) {
107 for line in layout_lines {
108 max_width = max_width.max(line.w);
109 line_count += 1;
110 }
111 }
112 }
113
114 if line_count == 0 {
115 return Ok(TextMetrics {
117 width: 0.0,
118 height: metrics.line_height,
119 baseline: font_size * 0.8,
120 line_count: 1,
121 });
122 }
123
124 if let Some(max_width_limit) = req.max_width {
126 max_width = max_width.min(max_width_limit);
127 }
128
129 let height = metrics.line_height * line_count as f32;
130
131 Ok(TextMetrics {
132 width: max_width,
133 height,
134 baseline: font_size * 0.8, line_count,
136 })
137 }
138}