1use hwpforge_foundation::{GutterType, HwpUnit};
23use schemars::JsonSchema;
24use serde::{Deserialize, Serialize};
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
50pub struct PageSettings {
51 pub width: HwpUnit,
53 pub height: HwpUnit,
55 pub margin_left: HwpUnit,
57 pub margin_right: HwpUnit,
59 pub margin_top: HwpUnit,
61 pub margin_bottom: HwpUnit,
63 pub header_margin: HwpUnit,
65 pub footer_margin: HwpUnit,
67 #[serde(default, skip_serializing_if = "HwpUnit::is_zero")]
69 pub gutter: HwpUnit,
70 #[serde(default)]
72 pub gutter_type: GutterType,
73 #[serde(default)]
75 pub mirror_margins: bool,
76 #[serde(default)]
81 pub landscape: bool,
82}
83
84const _: () = assert!(std::mem::size_of::<PageSettings>() == 40);
86
87impl PageSettings {
88 pub fn a4() -> Self {
103 Self {
108 width: HwpUnit::from_mm(210.0).unwrap(),
109 height: HwpUnit::from_mm(297.0).unwrap(),
110 margin_left: HwpUnit::from_mm(20.0).unwrap(),
111 margin_right: HwpUnit::from_mm(20.0).unwrap(),
112 margin_top: HwpUnit::from_mm(20.0).unwrap(),
113 margin_bottom: HwpUnit::from_mm(20.0).unwrap(),
114 header_margin: HwpUnit::from_mm(10.0).unwrap(),
115 footer_margin: HwpUnit::from_mm(10.0).unwrap(),
116 gutter: HwpUnit::ZERO,
117 gutter_type: GutterType::LeftOnly,
118 mirror_margins: false,
119 landscape: false,
120 }
121 }
122
123 pub fn letter() -> Self {
135 Self {
136 width: HwpUnit::from_inch(8.5).unwrap(),
137 height: HwpUnit::from_inch(11.0).unwrap(),
138 margin_left: HwpUnit::from_inch(1.0).unwrap(),
139 margin_right: HwpUnit::from_inch(1.0).unwrap(),
140 margin_top: HwpUnit::from_inch(1.0).unwrap(),
141 margin_bottom: HwpUnit::from_inch(1.0).unwrap(),
142 header_margin: HwpUnit::from_inch(0.5).unwrap(),
143 footer_margin: HwpUnit::from_inch(0.5).unwrap(),
144 gutter: HwpUnit::ZERO,
145 gutter_type: GutterType::LeftOnly,
146 mirror_margins: false,
147 landscape: false,
148 }
149 }
150
151 pub fn printable_width(&self) -> HwpUnit {
164 self.width - self.margin_left - self.margin_right
165 }
166
167 pub fn printable_height(&self) -> HwpUnit {
169 self.height - self.margin_top - self.margin_bottom
170 }
171}
172
173impl Default for PageSettings {
174 fn default() -> Self {
176 Self::a4()
177 }
178}
179
180impl std::fmt::Display for PageSettings {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 write!(f, "PageSettings({:.1}mm x {:.1}mm)", self.width.to_mm(), self.height.to_mm())
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn a4_dimensions() {
192 let a4 = PageSettings::a4();
193 assert!((a4.width.to_mm() - 210.0).abs() < 0.1, "width: {}", a4.width.to_mm());
194 assert!((a4.height.to_mm() - 297.0).abs() < 0.1, "height: {}", a4.height.to_mm());
195 }
196
197 #[test]
198 fn a4_margins() {
199 let a4 = PageSettings::a4();
200 assert!((a4.margin_left.to_mm() - 20.0).abs() < 0.1);
201 assert!((a4.margin_right.to_mm() - 20.0).abs() < 0.1);
202 assert!((a4.margin_top.to_mm() - 20.0).abs() < 0.1);
203 assert!((a4.margin_bottom.to_mm() - 20.0).abs() < 0.1);
204 }
205
206 #[test]
207 fn a4_header_footer_margins() {
208 let a4 = PageSettings::a4();
209 assert!((a4.header_margin.to_mm() - 10.0).abs() < 0.1);
210 assert!((a4.footer_margin.to_mm() - 10.0).abs() < 0.1);
211 }
212
213 #[test]
214 fn letter_dimensions() {
215 let letter = PageSettings::letter();
216 assert_eq!(letter.width.as_i32(), 61200);
217 assert_eq!(letter.height.as_i32(), 79200);
218 }
219
220 #[test]
221 fn letter_margins() {
222 let letter = PageSettings::letter();
223 assert_eq!(letter.margin_left.as_i32(), 7200);
224 assert_eq!(letter.margin_right.as_i32(), 7200);
225 assert_eq!(letter.margin_top.as_i32(), 7200);
226 assert_eq!(letter.margin_bottom.as_i32(), 7200);
227 }
228
229 #[test]
230 fn default_is_a4() {
231 assert_eq!(PageSettings::default(), PageSettings::a4());
232 }
233
234 #[test]
235 fn printable_width() {
236 let a4 = PageSettings::a4();
237 let pw = a4.printable_width();
238 assert!((pw.to_mm() - 170.0).abs() < 0.5, "printable width: {}mm", pw.to_mm());
240 }
241
242 #[test]
243 fn printable_height() {
244 let a4 = PageSettings::a4();
245 let ph = a4.printable_height();
246 assert!((ph.to_mm() - 257.0).abs() < 0.5, "printable height: {}mm", ph.to_mm());
248 }
249
250 #[test]
251 fn custom_page_with_struct_update() {
252 let custom = PageSettings {
253 width: HwpUnit::from_mm(148.0).unwrap(),
254 height: HwpUnit::from_mm(210.0).unwrap(),
255 ..PageSettings::a4()
256 };
257 assert!((custom.width.to_mm() - 148.0).abs() < 0.1);
258 assert!((custom.height.to_mm() - 210.0).abs() < 0.1);
259 assert!((custom.margin_left.to_mm() - 20.0).abs() < 0.1);
261 }
262
263 #[test]
264 fn size_assertion() {
265 assert_eq!(std::mem::size_of::<PageSettings>(), 40);
266 }
267
268 #[test]
269 fn display_format() {
270 let a4 = PageSettings::a4();
271 let s = a4.to_string();
272 assert!(s.contains("210.0"), "display: {s}");
273 assert!(s.contains("297.0"), "display: {s}");
274 }
275
276 #[test]
277 fn copy_semantics() {
278 let a = PageSettings::a4();
279 let b = a; assert_eq!(a, b);
281 }
282
283 #[test]
284 fn serde_roundtrip() {
285 let ps = PageSettings::a4();
286 let json = serde_json::to_string(&ps).unwrap();
287 let back: PageSettings = serde_json::from_str(&json).unwrap();
288 assert_eq!(ps, back);
289 }
290
291 #[test]
292 fn letter_serde_roundtrip() {
293 let ps = PageSettings::letter();
294 let json = serde_json::to_string(&ps).unwrap();
295 let back: PageSettings = serde_json::from_str(&json).unwrap();
296 assert_eq!(ps, back);
297 }
298}