merman_render/text/
types.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
6pub enum WrapMode {
7 #[default]
8 SvgLike,
9 SvgLikeSingleRun,
14 HtmlLike,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct TextStyle {
19 pub font_family: Option<String>,
20 pub font_size: f64,
21 pub font_weight: Option<String>,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub font_style: Option<String>,
24}
25
26impl Default for TextStyle {
27 fn default() -> Self {
28 Self {
29 font_family: None,
30 font_size: 16.0,
31 font_weight: None,
32 font_style: None,
33 }
34 }
35}
36
37#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
38pub struct TextMetrics {
39 pub width: f64,
40 pub height: f64,
41 pub line_count: usize,
42}
43
44impl TextMetrics {
45 pub(crate) fn with_label_min_width(
48 mut self,
49 label: &str,
50 min_width: f64,
51 explicit_width: Option<f64>,
52 ) -> Self {
53 if !label.is_empty() && !explicit_width.is_some_and(|width| width != 0.0) {
54 self.width = self.width.max(min_width);
55 }
56 self
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::TextMetrics;
63
64 #[test]
65 fn label_minimum_preserves_empty_and_explicit_width_contracts() {
66 let metrics = TextMetrics {
67 width: 20.0,
68 height: 16.0,
69 line_count: 2,
70 };
71 for (label, explicit_width, expected) in [
72 ("X", None, 120.0),
73 ("X", Some(90.0), 20.0),
74 ("X", Some(0.0), 120.0),
75 ("", None, 20.0),
76 (" ", None, 120.0),
77 ] {
78 let actual = metrics.with_label_min_width(label, 120.0, explicit_width);
79 assert_eq!(actual.width, expected);
80 assert_eq!(actual.height, metrics.height);
81 assert_eq!(actual.line_count, metrics.line_count);
82 }
83 assert_eq!(metrics.with_label_min_width("X", 10.0, None).width, 20.0);
84 }
85}