cdx_core/presentation/
continuous.rs1use serde::{Deserialize, Serialize};
4
5use super::style::{CssValue, Style, StyleMap};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct Continuous {
10 pub version: String,
12
13 #[serde(rename = "type")]
15 pub presentation_type: String,
16
17 pub defaults: ContinuousDefaults,
19
20 #[serde(default, skip_serializing_if = "Vec::is_empty")]
22 pub sections: Vec<Section>,
23
24 #[serde(default, skip_serializing_if = "StyleMap::is_empty")]
26 pub styles: StyleMap,
27}
28
29impl Default for Continuous {
30 fn default() -> Self {
31 Self {
32 version: crate::SPEC_VERSION.to_string(),
33 presentation_type: "continuous".to_string(),
34 defaults: ContinuousDefaults::default(),
35 sections: Vec::new(),
36 styles: StyleMap::new(),
37 }
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct ContinuousDefaults {
45 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub max_width: Option<CssValue>,
48
49 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub padding: Option<CssValue>,
52
53 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub font_family: Option<String>,
56
57 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub font_size: Option<CssValue>,
60
61 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub line_height: Option<CssValue>,
64}
65
66impl Default for ContinuousDefaults {
67 fn default() -> Self {
68 Self {
69 max_width: Some(CssValue::String("800px".to_string())),
70 padding: Some(CssValue::String("24px".to_string())),
71 font_family: None,
72 font_size: None,
73 line_height: None,
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct Section {
82 pub block_refs: Vec<String>,
84
85 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub style: Option<String>,
88
89 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub attributes: Option<Style>,
92}
93
94impl Section {
95 #[must_use]
97 pub fn new(block_refs: Vec<String>) -> Self {
98 Self {
99 block_refs,
100 style: None,
101 attributes: None,
102 }
103 }
104
105 #[must_use]
107 pub fn with_style(block_refs: Vec<String>, style: impl Into<String>) -> Self {
108 Self {
109 block_refs,
110 style: Some(style.into()),
111 attributes: None,
112 }
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_continuous_default() {
122 let c = Continuous::default();
123 assert_eq!(c.presentation_type, "continuous");
124 assert!(matches!(
125 c.defaults.max_width,
126 Some(CssValue::String(ref s)) if s == "800px"
127 ));
128 }
129
130 #[test]
131 fn test_section() {
132 let section = Section::with_style(vec!["block-1".to_string()], "intro");
133 assert_eq!(section.block_refs, vec!["block-1"]);
134 assert_eq!(section.style, Some("intro".to_string()));
135 }
136
137 #[test]
138 fn test_serialization() {
139 let c = Continuous::default();
140 let json = serde_json::to_string_pretty(&c).unwrap();
141 assert!(json.contains("\"type\": \"continuous\""));
142 assert!(json.contains("\"maxWidth\": \"800px\""));
143 }
144
145 #[test]
146 fn test_deserialization() {
147 let json = r#"{
148 "version": "0.1",
149 "type": "continuous",
150 "defaults": {
151 "maxWidth": "720px",
152 "padding": "20px",
153 "fontFamily": "Georgia, serif",
154 "fontSize": "18px",
155 "lineHeight": 1.7
156 },
157 "styles": {
158 "heading1": {
159 "fontSize": "2.5rem",
160 "fontWeight": 700
161 }
162 }
163 }"#;
164
165 let c: Continuous = serde_json::from_str(json).unwrap();
166 assert_eq!(c.defaults.font_family, Some("Georgia, serif".to_string()));
167 assert!(c.styles.contains_key("heading1"));
168 }
169}