1use std::fmt;
4
5use alpha2_text::{TextRasterError, TextRasterizer};
6use alpha2_time::MediaTime;
7
8use crate::{Baselines, CanvasRect, PictureSize};
9
10#[derive(Debug)]
12#[non_exhaustive]
13pub enum MeasureError {
14 Input(String),
16 Text(TextRasterError),
18}
19
20impl fmt::Display for MeasureError {
21 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 Self::Input(message) => formatter.write_str(message),
24 Self::Text(error) => write!(formatter, "text failed: {error}"),
25 }
26 }
27}
28
29impl std::error::Error for MeasureError {}
30
31fn baselines(baselines: Baselines) -> serde_json::Value {
32 serde_json::json!({ "first": baselines.first, "last": baselines.last })
33}
34
35fn rect(rect: CanvasRect) -> serde_json::Value {
36 serde_json::json!({
37 "left": rect.left,
38 "top": rect.top,
39 "right": rect.right,
40 "bottom": rect.bottom,
41 })
42}
43
44pub fn measure_text_json(
55 rasterizer: &TextRasterizer,
56 generator_json: &str,
57 width: u32,
58 height: u32,
59) -> Result<String, MeasureError> {
60 let generator: alpha2_model::Generator = serde_json::from_str(generator_json)
61 .map_err(|error| MeasureError::Input(format!("invalid text generator: {error}")))?;
62 if !generator.is_valid() {
63 return Err(MeasureError::Input(
64 "the text generator has a field out of range".into(),
65 ));
66 }
67 let alpha2_model::Generator::Text { text, style } = &generator else {
68 return Err(MeasureError::Input(
69 "only a text generator draws text".into(),
70 ));
71 };
72 let size = PictureSize::new(width, height)
73 .map_err(|error| MeasureError::Input(format!("invalid canvas size: {error}")))?;
74 let bounds =
75 crate::measure_text(rasterizer, &text.text(), style, size).map_err(MeasureError::Text)?;
76 Ok(serde_json::json!({
77 "layout": rect(bounds.layout),
78 "content": rect(bounds.content),
79 "ink": bounds.ink.map(rect),
80 "lineCount": bounds.line_count,
81 "baselines": baselines(bounds.baselines),
82 })
83 .to_string())
84}
85
86pub fn measure_layout_json(
99 rasterizer: &TextRasterizer,
100 generator_json: &str,
101 width: u32,
102 height: u32,
103 time: MediaTime,
104 canvas: Option<(u32, u32)>,
105) -> Result<String, MeasureError> {
106 let generator: alpha2_model::Generator = serde_json::from_str(generator_json)
107 .map_err(|error| MeasureError::Input(format!("invalid layout generator: {error}")))?;
108 let size = PictureSize::new(width, height)
109 .map_err(|error| MeasureError::Input(format!("invalid canvas size: {error}")))?;
110 let (canvas_width, canvas_height) = canvas.unwrap_or((width, height));
111 PictureSize::new(canvas_width, canvas_height)
112 .map_err(|error| MeasureError::Input(format!("invalid project canvas: {error}")))?;
113 if let Err(issue) = generator.validate_on(canvas_width, canvas_height) {
114 return Err(MeasureError::Input(match issue {
115 alpha2_model::GeneratorIssue::Layout(issue) => {
116 format!("the layout generator {} {}", issue.at, issue.problem)
117 }
118 alpha2_model::GeneratorIssue::Shape(issue) => {
119 format!(
120 "the layout generator's shape {} {}",
121 issue.at, issue.problem
122 )
123 }
124 alpha2_model::GeneratorIssue::Invalid => {
125 "the layout generator has a field out of range".into()
126 }
127 }));
128 }
129 let alpha2_model::Generator::Layout { root } = &generator else {
130 return Err(MeasureError::Input(
131 "only a layout generator lays out boxes".into(),
132 ));
133 };
134 let bounds = crate::measure_layout(rasterizer, root, time, size, (canvas_width, canvas_height))
135 .map_err(MeasureError::Text)?;
136 let nodes = bounds
137 .nodes
138 .into_iter()
139 .map(|node| {
140 serde_json::json!({
141 "at": node.at,
142 "border": rect(node.border),
143 "content": rect(node.content),
144 "overflow": node.overflow,
145 "lineCount": node.line_count,
146 "baselines": node.baselines.map(baselines),
147 })
148 })
149 .collect::<Vec<_>>();
150 Ok(serde_json::json!({ "nodes": nodes }).to_string())
151}
152
153#[cfg(test)]
154#[path = "json_tests.rs"]
155mod tests;