1use serde::{Deserialize, Serialize};
4
5use super::style::{StyleMap, Transform};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct Paginated {
10 pub version: String,
12
13 #[serde(rename = "type")]
15 pub presentation_type: String,
16
17 pub defaults: PaginatedDefaults,
19
20 #[serde(default, skip_serializing_if = "Vec::is_empty")]
22 pub pages: Vec<Page>,
23
24 #[serde(default, skip_serializing_if = "StyleMap::is_empty")]
26 pub styles: StyleMap,
27}
28
29impl Default for Paginated {
30 fn default() -> Self {
31 Self {
32 version: crate::SPEC_VERSION.to_string(),
33 presentation_type: "paginated".to_string(),
34 defaults: PaginatedDefaults::default(),
35 pages: Vec::new(),
36 styles: StyleMap::new(),
37 }
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct PaginatedDefaults {
45 pub page_size: PageSize,
47
48 pub orientation: Orientation,
50
51 pub margins: Margins,
53}
54
55impl Default for PaginatedDefaults {
56 fn default() -> Self {
57 Self {
58 page_size: PageSize::letter(),
59 orientation: Orientation::Portrait,
60 margins: Margins::default(),
61 }
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67#[serde(untagged)]
68pub enum PageSize {
69 Named(StandardPageSize),
71 Custom {
73 width: String,
75 height: String,
77 },
78}
79
80impl PageSize {
81 #[must_use]
83 pub fn letter() -> Self {
84 Self::Named(StandardPageSize::Letter)
85 }
86
87 #[must_use]
89 pub fn legal() -> Self {
90 Self::Named(StandardPageSize::Legal)
91 }
92
93 #[must_use]
95 pub fn a4() -> Self {
96 Self::Named(StandardPageSize::A4)
97 }
98
99 #[must_use]
101 pub fn a5() -> Self {
102 Self::Named(StandardPageSize::A5)
103 }
104}
105
106impl Default for PageSize {
107 fn default() -> Self {
108 Self::letter()
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "lowercase")]
115pub enum StandardPageSize {
116 Letter,
118 Legal,
120 A4,
122 A5,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
128#[serde(rename_all = "lowercase")]
129pub enum Orientation {
130 #[default]
132 Portrait,
133 Landscape,
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
139pub struct Margins {
140 pub top: String,
142 pub right: String,
144 pub bottom: String,
146 pub left: String,
148}
149
150impl Default for Margins {
151 fn default() -> Self {
152 Self {
153 top: "1in".to_string(),
154 right: "1in".to_string(),
155 bottom: "1in".to_string(),
156 left: "1in".to_string(),
157 }
158 }
159}
160
161impl Margins {
162 #[must_use]
164 pub fn all(value: impl Into<String>) -> Self {
165 let v = value.into();
166 Self {
167 top: v.clone(),
168 right: v.clone(),
169 bottom: v.clone(),
170 left: v,
171 }
172 }
173}
174
175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
177pub struct Page {
178 pub number: u32,
180
181 #[serde(default)]
183 pub elements: Vec<PageElement>,
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
188#[serde(rename_all = "camelCase")]
189pub struct PageElement {
190 pub block_id: String,
192
193 pub position: Position,
195
196 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub style: Option<String>,
199
200 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub overflow: Option<String>,
203
204 #[serde(default, skip_serializing_if = "Option::is_none")]
206 pub transform: Option<Transform>,
207}
208
209#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
211pub struct Position {
212 pub x: String,
214 pub y: String,
216 pub width: String,
218 pub height: String,
220}
221
222impl Position {
223 #[must_use]
225 pub fn new(x: impl Into<String>, y: impl Into<String>, width: impl Into<String>) -> Self {
226 Self {
227 x: x.into(),
228 y: y.into(),
229 width: width.into(),
230 height: "auto".to_string(),
231 }
232 }
233}
234
235#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct FlowElement {
239 #[serde(rename = "type")]
241 pub element_type: String,
242
243 pub block_ids: Vec<String>,
245
246 #[serde(default = "default_columns")]
248 pub columns: u32,
249
250 pub start_page: u32,
252
253 pub regions: Vec<FlowRegion>,
255}
256
257fn default_columns() -> u32 {
258 1
259}
260
261#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
263pub struct FlowRegion {
264 pub page: u32,
266 pub position: Position,
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn test_paginated_default() {
276 let p = Paginated::default();
277 assert_eq!(p.presentation_type, "paginated");
278 assert_eq!(p.defaults.page_size, PageSize::letter());
279 assert_eq!(p.defaults.orientation, Orientation::Portrait);
280 }
281
282 #[test]
283 fn test_margins() {
284 let m = Margins::all("0.5in");
285 assert_eq!(m.top, "0.5in");
286 assert_eq!(m.right, "0.5in");
287 assert_eq!(m.bottom, "0.5in");
288 assert_eq!(m.left, "0.5in");
289 }
290
291 #[test]
292 fn test_serialization() {
293 let p = Paginated::default();
294 let json = serde_json::to_string_pretty(&p).unwrap();
295 assert!(json.contains("\"type\": \"paginated\""));
296 assert!(json.contains("\"pageSize\": \"letter\""));
297 }
298
299 #[test]
300 fn test_custom_page_size() {
301 let size = PageSize::Custom {
302 width: "8in".to_string(),
303 height: "10in".to_string(),
304 };
305 let json = serde_json::to_string(&size).unwrap();
306 assert!(json.contains("\"width\":\"8in\""));
307 }
308}