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