telar_renderer_core/
metrics.rs1use std::sync::{Arc, RwLock};
2
3use crate::{TextRun, TextStyle};
4
5pub trait TextMetrics: Send + Sync + 'static {
10 fn measure(&self, text: &str, max_width: f32, style: &TextStyle) -> (f32, f32);
13
14 fn measure_runs(&self, runs: &[TextRun], max_width: f32, base: &TextStyle) -> (f32, f32);
16
17 fn ink_bounds(&self, text: &str, max_width: f32, style: &TextStyle) -> (f32, f32);
20
21 fn line_height(&self, font_size: f32) -> f32;
24}
25
26static TEXT_METRICS: RwLock<Option<Arc<dyn TextMetrics>>> = RwLock::new(None);
27
28pub fn set_text_metrics(metrics: impl TextMetrics) {
30 *TEXT_METRICS.write().expect("text metrics lock") = Some(Arc::new(metrics));
31}
32
33pub fn set_default_text_metrics(metrics: impl TextMetrics) -> bool {
38 let mut slot = TEXT_METRICS.write().expect("text metrics lock");
39 if slot.is_some() {
40 return false;
41 }
42 *slot = Some(Arc::new(metrics));
43 true
44}
45
46fn metrics() -> Arc<dyn TextMetrics> {
47 TEXT_METRICS
48 .read()
49 .expect("text metrics lock")
50 .as_ref()
51 .cloned()
52 .expect(
53 "no TextMetrics installed, so nothing can size text: install one with \
54 renderer_core::set_text_metrics (renderer_text::ShaperMetrics is the raster default, and \
55 renderer_text::set_measure_font_config installs it for you)",
56 )
57}
58
59pub fn measure_text(text: &str, max_width: f32, style: &TextStyle) -> (f32, f32) {
61 metrics().measure(text, max_width, style)
62}
63
64pub fn measure_rich_text(runs: &[TextRun], max_width: f32, base: &TextStyle) -> (f32, f32) {
66 metrics().measure_runs(runs, max_width, base)
67}
68
69pub fn measure_ink_bounds(text: &str, max_width: f32, style: &TextStyle) -> (f32, f32) {
71 metrics().ink_bounds(text, max_width, style)
72}
73
74pub fn line_height(font_size: f32) -> f32 {
76 metrics().line_height(font_size)
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 struct Fixed(f32);
84
85 impl TextMetrics for Fixed {
86 fn measure(&self, text: &str, _max_width: f32, _style: &TextStyle) -> (f32, f32) {
87 (text.chars().count() as f32 * self.0, self.0)
88 }
89 fn measure_runs(
90 &self,
91 _runs: &[TextRun],
92 _max_width: f32,
93 _base: &TextStyle,
94 ) -> (f32, f32) {
95 (0.0, self.0)
96 }
97 fn ink_bounds(&self, _text: &str, _max_width: f32, _style: &TextStyle) -> (f32, f32) {
98 (0.0, self.0)
99 }
100 fn line_height(&self, _font_size: f32) -> f32 {
101 self.0
102 }
103 }
104
105 #[test]
107 fn a_frontends_own_metrics_survive_the_runtimes_default() {
108 let style = TextStyle::new(14.0, crate::Color::BLACK);
109
110 assert!(
111 set_default_text_metrics(Fixed(10.0)),
112 "the first default install takes"
113 );
114 assert_eq!(measure_text("ab", 1.0e6, &style).0, 20.0);
115
116 assert!(
117 !set_default_text_metrics(Fixed(1.0)),
118 "a second default must not displace metrics already in force — that is the whole point of it"
119 );
120 assert_eq!(line_height(14.0), 10.0);
121
122 set_text_metrics(Fixed(2.0));
123 assert_eq!(
124 line_height(14.0),
125 2.0,
126 "an explicit install replaces, so a frontend can change its mind"
127 );
128 }
129}