Skip to main content

sheetkit_core/
page_layout.rs

1//! Page layout settings for worksheets.
2//!
3//! Provides functions for configuring page margins, page setup (paper size,
4//! orientation, scaling), header/footer, print options, and page breaks.
5
6use sheetkit_xml::worksheet::{
7    Break, HeaderFooter, PageMargins, PageSetup, PrintOptions, RowBreaks, WorksheetXml,
8};
9
10use crate::error::Result;
11
12// -- Default margin values (inches, matching Excel defaults) --
13
14/// Default left margin in inches.
15pub const DEFAULT_MARGIN_LEFT: f64 = 0.7;
16/// Default right margin in inches.
17pub const DEFAULT_MARGIN_RIGHT: f64 = 0.7;
18/// Default top margin in inches.
19pub const DEFAULT_MARGIN_TOP: f64 = 0.75;
20/// Default bottom margin in inches.
21pub const DEFAULT_MARGIN_BOTTOM: f64 = 0.75;
22/// Default header margin in inches.
23pub const DEFAULT_MARGIN_HEADER: f64 = 0.3;
24/// Default footer margin in inches.
25pub const DEFAULT_MARGIN_FOOTER: f64 = 0.3;
26
27/// Page margin configuration in inches.
28#[derive(Debug, Clone)]
29pub struct PageMarginsConfig {
30    /// Left margin in inches.
31    pub left: f64,
32    /// Right margin in inches.
33    pub right: f64,
34    /// Top margin in inches.
35    pub top: f64,
36    /// Bottom margin in inches.
37    pub bottom: f64,
38    /// Header margin in inches.
39    pub header: f64,
40    /// Footer margin in inches.
41    pub footer: f64,
42}
43
44impl Default for PageMarginsConfig {
45    fn default() -> Self {
46        Self {
47            left: DEFAULT_MARGIN_LEFT,
48            right: DEFAULT_MARGIN_RIGHT,
49            top: DEFAULT_MARGIN_TOP,
50            bottom: DEFAULT_MARGIN_BOTTOM,
51            header: DEFAULT_MARGIN_HEADER,
52            footer: DEFAULT_MARGIN_FOOTER,
53        }
54    }
55}
56
57/// Page orientation.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum Orientation {
60    Portrait,
61    Landscape,
62}
63
64impl Orientation {
65    fn as_str(&self) -> &str {
66        match self {
67            Orientation::Portrait => "portrait",
68            Orientation::Landscape => "landscape",
69        }
70    }
71
72    fn from_str(s: &str) -> Option<Self> {
73        match s {
74            "portrait" => Some(Orientation::Portrait),
75            "landscape" => Some(Orientation::Landscape),
76            _ => None,
77        }
78    }
79}
80
81/// Standard paper sizes. The numeric values follow the OOXML specification.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum PaperSize {
84    Letter = 1,
85    Tabloid = 3,
86    Legal = 5,
87    A3 = 8,
88    A4 = 9,
89    A5 = 11,
90    B4 = 12,
91    B5 = 13,
92}
93
94impl PaperSize {
95    fn as_u32(self) -> u32 {
96        self as u32
97    }
98
99    /// Convert from a numeric paper size value.
100    pub fn from_u32(v: u32) -> Option<Self> {
101        match v {
102            1 => Some(PaperSize::Letter),
103            3 => Some(PaperSize::Tabloid),
104            5 => Some(PaperSize::Legal),
105            8 => Some(PaperSize::A3),
106            9 => Some(PaperSize::A4),
107            11 => Some(PaperSize::A5),
108            12 => Some(PaperSize::B4),
109            13 => Some(PaperSize::B5),
110            _ => None,
111        }
112    }
113}
114
115/// Set page margins on a worksheet.
116pub fn set_page_margins(ws: &mut WorksheetXml, margins: &PageMarginsConfig) -> Result<()> {
117    ws.page_margins = Some(PageMargins {
118        left: margins.left,
119        right: margins.right,
120        top: margins.top,
121        bottom: margins.bottom,
122        header: margins.header,
123        footer: margins.footer,
124    });
125    Ok(())
126}
127
128/// Get page margins from a worksheet, returning defaults if not set.
129pub fn get_page_margins(ws: &WorksheetXml) -> PageMarginsConfig {
130    match &ws.page_margins {
131        Some(pm) => PageMarginsConfig {
132            left: pm.left,
133            right: pm.right,
134            top: pm.top,
135            bottom: pm.bottom,
136            header: pm.header,
137            footer: pm.footer,
138        },
139        None => PageMarginsConfig::default(),
140    }
141}
142
143/// Set page setup options on a worksheet.
144///
145/// Only non-`None` parameters are applied; existing values for `None`
146/// parameters are preserved.
147pub fn set_page_setup(
148    ws: &mut WorksheetXml,
149    orientation: Option<Orientation>,
150    paper_size: Option<PaperSize>,
151    scale: Option<u32>,
152    fit_to_width: Option<u32>,
153    fit_to_height: Option<u32>,
154) -> Result<()> {
155    let setup = ws.page_setup.get_or_insert(PageSetup {
156        paper_size: None,
157        orientation: None,
158        scale: None,
159        fit_to_width: None,
160        fit_to_height: None,
161        first_page_number: None,
162        horizontal_dpi: None,
163        vertical_dpi: None,
164        r_id: None,
165    });
166
167    if let Some(o) = orientation {
168        setup.orientation = Some(o.as_str().to_string());
169    }
170    if let Some(ps) = paper_size {
171        setup.paper_size = Some(ps.as_u32());
172    }
173    if let Some(s) = scale {
174        setup.scale = Some(s);
175    }
176    if let Some(w) = fit_to_width {
177        setup.fit_to_width = Some(w);
178    }
179    if let Some(h) = fit_to_height {
180        setup.fit_to_height = Some(h);
181    }
182    Ok(())
183}
184
185/// Get the current orientation, if set.
186pub fn get_orientation(ws: &WorksheetXml) -> Option<Orientation> {
187    ws.page_setup
188        .as_ref()
189        .and_then(|ps| ps.orientation.as_deref())
190        .and_then(Orientation::from_str)
191}
192
193/// Get the current paper size, if set.
194pub fn get_paper_size(ws: &WorksheetXml) -> Option<PaperSize> {
195    ws.page_setup
196        .as_ref()
197        .and_then(|ps| ps.paper_size)
198        .and_then(PaperSize::from_u32)
199}
200
201/// Get the current scale, if set.
202pub fn get_scale(ws: &WorksheetXml) -> Option<u32> {
203    ws.page_setup.as_ref().and_then(|ps| ps.scale)
204}
205
206/// Get the current fit-to-width value, if set.
207pub fn get_fit_to_width(ws: &WorksheetXml) -> Option<u32> {
208    ws.page_setup.as_ref().and_then(|ps| ps.fit_to_width)
209}
210
211/// Get the current fit-to-height value, if set.
212pub fn get_fit_to_height(ws: &WorksheetXml) -> Option<u32> {
213    ws.page_setup.as_ref().and_then(|ps| ps.fit_to_height)
214}
215
216/// Set header and footer text for printing.
217///
218/// Both parameters are optional. Pass `None` to leave a field unchanged.
219/// Excel header/footer text uses special formatting codes (e.g., `&L`, `&C`,
220/// `&R` for left/center/right sections).
221pub fn set_header_footer(
222    ws: &mut WorksheetXml,
223    header: Option<&str>,
224    footer: Option<&str>,
225) -> Result<()> {
226    let hf = ws.header_footer.get_or_insert(HeaderFooter {
227        odd_header: None,
228        odd_footer: None,
229    });
230    if let Some(h) = header {
231        hf.odd_header = Some(h.to_string());
232    }
233    if let Some(f) = footer {
234        hf.odd_footer = Some(f.to_string());
235    }
236    Ok(())
237}
238
239/// Get the header and footer text.
240///
241/// Returns `(header, footer)` where each may be `None` if not set.
242pub fn get_header_footer(ws: &WorksheetXml) -> (Option<String>, Option<String>) {
243    match &ws.header_footer {
244        Some(hf) => (hf.odd_header.clone(), hf.odd_footer.clone()),
245        None => (None, None),
246    }
247}
248
249/// Set print options on a worksheet.
250///
251/// Only non-`None` parameters are applied; existing values for `None`
252/// parameters are preserved.
253pub fn set_print_options(
254    ws: &mut WorksheetXml,
255    grid_lines: Option<bool>,
256    headings: Option<bool>,
257    h_centered: Option<bool>,
258    v_centered: Option<bool>,
259) -> Result<()> {
260    let opts = ws.print_options.get_or_insert(PrintOptions {
261        grid_lines: None,
262        headings: None,
263        horizontal_centered: None,
264        vertical_centered: None,
265    });
266    if let Some(gl) = grid_lines {
267        opts.grid_lines = Some(gl);
268    }
269    if let Some(h) = headings {
270        opts.headings = Some(h);
271    }
272    if let Some(hc) = h_centered {
273        opts.horizontal_centered = Some(hc);
274    }
275    if let Some(vc) = v_centered {
276        opts.vertical_centered = Some(vc);
277    }
278    Ok(())
279}
280
281/// Get print options from a worksheet.
282///
283/// Returns `(grid_lines, headings, horizontal_centered, vertical_centered)`.
284pub fn get_print_options(
285    ws: &WorksheetXml,
286) -> (Option<bool>, Option<bool>, Option<bool>, Option<bool>) {
287    match &ws.print_options {
288        Some(po) => (
289            po.grid_lines,
290            po.headings,
291            po.horizontal_centered,
292            po.vertical_centered,
293        ),
294        None => (None, None, None, None),
295    }
296}
297
298/// Insert a horizontal page break before the given 1-based row number.
299///
300/// If a break already exists at this row, the call is a no-op.
301pub fn insert_page_break(ws: &mut WorksheetXml, row: u32) -> Result<()> {
302    let rb = ws.row_breaks.get_or_insert_with(|| RowBreaks {
303        count: None,
304        manual_break_count: None,
305        brk: vec![],
306    });
307
308    // Avoid duplicate breaks.
309    if rb.brk.iter().any(|b| b.id == row) {
310        return Ok(());
311    }
312
313    rb.brk.push(Break {
314        id: row,
315        max: Some(16383),
316        man: Some(true),
317    });
318
319    // Sort breaks by row number.
320    rb.brk.sort_by_key(|b| b.id);
321
322    let count = rb.brk.len() as u32;
323    rb.count = Some(count);
324    rb.manual_break_count = Some(count);
325
326    Ok(())
327}
328
329/// Remove a horizontal page break at the given 1-based row number.
330///
331/// If no break exists at this row, the call is a no-op.
332pub fn remove_page_break(ws: &mut WorksheetXml, row: u32) -> Result<()> {
333    if let Some(rb) = &mut ws.row_breaks {
334        rb.brk.retain(|b| b.id != row);
335        if rb.brk.is_empty() {
336            ws.row_breaks = None;
337        } else {
338            let count = rb.brk.len() as u32;
339            rb.count = Some(count);
340            rb.manual_break_count = Some(count);
341        }
342    }
343    Ok(())
344}
345
346/// Get all row page break positions (1-based row numbers).
347pub fn get_page_breaks(ws: &WorksheetXml) -> Vec<u32> {
348    match &ws.row_breaks {
349        Some(rb) => rb.brk.iter().map(|b| b.id).collect(),
350        None => vec![],
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357    use sheetkit_xml::worksheet::WorksheetXml;
358
359    // -- Page margins tests --
360
361    #[test]
362    fn test_set_get_page_margins() {
363        let mut ws = WorksheetXml::default();
364        let margins = PageMarginsConfig {
365            left: 1.0,
366            right: 1.0,
367            top: 1.5,
368            bottom: 1.5,
369            header: 0.5,
370            footer: 0.5,
371        };
372        set_page_margins(&mut ws, &margins).unwrap();
373
374        let result = get_page_margins(&ws);
375        assert_eq!(result.left, 1.0);
376        assert_eq!(result.right, 1.0);
377        assert_eq!(result.top, 1.5);
378        assert_eq!(result.bottom, 1.5);
379        assert_eq!(result.header, 0.5);
380        assert_eq!(result.footer, 0.5);
381    }
382
383    #[test]
384    fn test_default_page_margins() {
385        let ws = WorksheetXml::default();
386        let margins = get_page_margins(&ws);
387        assert_eq!(margins.left, DEFAULT_MARGIN_LEFT);
388        assert_eq!(margins.right, DEFAULT_MARGIN_RIGHT);
389        assert_eq!(margins.top, DEFAULT_MARGIN_TOP);
390        assert_eq!(margins.bottom, DEFAULT_MARGIN_BOTTOM);
391        assert_eq!(margins.header, DEFAULT_MARGIN_HEADER);
392        assert_eq!(margins.footer, DEFAULT_MARGIN_FOOTER);
393    }
394
395    #[test]
396    fn test_page_margins_default_values() {
397        let config = PageMarginsConfig::default();
398        assert_eq!(config.left, 0.7);
399        assert_eq!(config.right, 0.7);
400        assert_eq!(config.top, 0.75);
401        assert_eq!(config.bottom, 0.75);
402        assert_eq!(config.header, 0.3);
403        assert_eq!(config.footer, 0.3);
404    }
405
406    // -- Orientation tests --
407
408    #[test]
409    fn test_set_orientation_portrait() {
410        let mut ws = WorksheetXml::default();
411        set_page_setup(&mut ws, Some(Orientation::Portrait), None, None, None, None).unwrap();
412        assert_eq!(get_orientation(&ws), Some(Orientation::Portrait));
413    }
414
415    #[test]
416    fn test_set_orientation_landscape() {
417        let mut ws = WorksheetXml::default();
418        set_page_setup(
419            &mut ws,
420            Some(Orientation::Landscape),
421            None,
422            None,
423            None,
424            None,
425        )
426        .unwrap();
427        assert_eq!(get_orientation(&ws), Some(Orientation::Landscape));
428    }
429
430    #[test]
431    fn test_orientation_none_when_not_set() {
432        let ws = WorksheetXml::default();
433        assert_eq!(get_orientation(&ws), None);
434    }
435
436    // -- Paper size tests --
437
438    #[test]
439    fn test_set_paper_size_a4() {
440        let mut ws = WorksheetXml::default();
441        set_page_setup(&mut ws, None, Some(PaperSize::A4), None, None, None).unwrap();
442        assert_eq!(get_paper_size(&ws), Some(PaperSize::A4));
443    }
444
445    #[test]
446    fn test_set_paper_size_letter() {
447        let mut ws = WorksheetXml::default();
448        set_page_setup(&mut ws, None, Some(PaperSize::Letter), None, None, None).unwrap();
449        assert_eq!(get_paper_size(&ws), Some(PaperSize::Letter));
450    }
451
452    #[test]
453    fn test_paper_size_none_when_not_set() {
454        let ws = WorksheetXml::default();
455        assert_eq!(get_paper_size(&ws), None);
456    }
457
458    // -- Scale tests --
459
460    #[test]
461    fn test_set_scale() {
462        let mut ws = WorksheetXml::default();
463        set_page_setup(&mut ws, None, None, Some(75), None, None).unwrap();
464        assert_eq!(get_scale(&ws), Some(75));
465    }
466
467    #[test]
468    fn test_scale_none_when_not_set() {
469        let ws = WorksheetXml::default();
470        assert_eq!(get_scale(&ws), None);
471    }
472
473    // -- Fit to page tests --
474
475    #[test]
476    fn test_set_fit_to_page() {
477        let mut ws = WorksheetXml::default();
478        set_page_setup(&mut ws, None, None, None, Some(1), Some(2)).unwrap();
479        assert_eq!(get_fit_to_width(&ws), Some(1));
480        assert_eq!(get_fit_to_height(&ws), Some(2));
481    }
482
483    #[test]
484    fn test_fit_to_page_none_when_not_set() {
485        let ws = WorksheetXml::default();
486        assert_eq!(get_fit_to_width(&ws), None);
487        assert_eq!(get_fit_to_height(&ws), None);
488    }
489
490    // -- Multiple page setup options --
491
492    #[test]
493    fn test_set_page_setup_combined() {
494        let mut ws = WorksheetXml::default();
495        set_page_setup(
496            &mut ws,
497            Some(Orientation::Landscape),
498            Some(PaperSize::Legal),
499            Some(80),
500            None,
501            None,
502        )
503        .unwrap();
504
505        assert_eq!(get_orientation(&ws), Some(Orientation::Landscape));
506        assert_eq!(get_paper_size(&ws), Some(PaperSize::Legal));
507        assert_eq!(get_scale(&ws), Some(80));
508    }
509
510    #[test]
511    fn test_set_page_setup_preserves_existing() {
512        let mut ws = WorksheetXml::default();
513        set_page_setup(
514            &mut ws,
515            Some(Orientation::Landscape),
516            Some(PaperSize::A4),
517            None,
518            None,
519            None,
520        )
521        .unwrap();
522
523        // Second call should preserve orientation/paper size.
524        set_page_setup(&mut ws, None, None, Some(50), None, None).unwrap();
525
526        assert_eq!(get_orientation(&ws), Some(Orientation::Landscape));
527        assert_eq!(get_paper_size(&ws), Some(PaperSize::A4));
528        assert_eq!(get_scale(&ws), Some(50));
529    }
530
531    // -- Header footer tests --
532
533    #[test]
534    fn test_set_header_footer() {
535        let mut ws = WorksheetXml::default();
536        set_header_footer(&mut ws, Some("&CPage &P"), Some("&LFooter Left")).unwrap();
537
538        let (header, footer) = get_header_footer(&ws);
539        assert_eq!(header, Some("&CPage &P".to_string()));
540        assert_eq!(footer, Some("&LFooter Left".to_string()));
541    }
542
543    #[test]
544    fn test_set_header_only() {
545        let mut ws = WorksheetXml::default();
546        set_header_footer(&mut ws, Some("My Header"), None).unwrap();
547
548        let (header, footer) = get_header_footer(&ws);
549        assert_eq!(header, Some("My Header".to_string()));
550        assert_eq!(footer, None);
551    }
552
553    #[test]
554    fn test_set_footer_only() {
555        let mut ws = WorksheetXml::default();
556        set_header_footer(&mut ws, None, Some("My Footer")).unwrap();
557
558        let (header, footer) = get_header_footer(&ws);
559        assert_eq!(header, None);
560        assert_eq!(footer, Some("My Footer".to_string()));
561    }
562
563    #[test]
564    fn test_get_header_footer_none() {
565        let ws = WorksheetXml::default();
566        let (header, footer) = get_header_footer(&ws);
567        assert_eq!(header, None);
568        assert_eq!(footer, None);
569    }
570
571    #[test]
572    fn test_set_header_footer_preserves_existing() {
573        let mut ws = WorksheetXml::default();
574        set_header_footer(&mut ws, Some("Header1"), Some("Footer1")).unwrap();
575        // Update only header.
576        set_header_footer(&mut ws, Some("Header2"), None).unwrap();
577
578        let (header, footer) = get_header_footer(&ws);
579        assert_eq!(header, Some("Header2".to_string()));
580        assert_eq!(footer, Some("Footer1".to_string()));
581    }
582
583    // -- Print options tests --
584
585    #[test]
586    fn test_set_print_options() {
587        let mut ws = WorksheetXml::default();
588        set_print_options(&mut ws, Some(true), Some(true), Some(true), Some(false)).unwrap();
589
590        let (gl, hd, hc, vc) = get_print_options(&ws);
591        assert_eq!(gl, Some(true));
592        assert_eq!(hd, Some(true));
593        assert_eq!(hc, Some(true));
594        assert_eq!(vc, Some(false));
595    }
596
597    #[test]
598    fn test_get_print_options_none() {
599        let ws = WorksheetXml::default();
600        let (gl, hd, hc, vc) = get_print_options(&ws);
601        assert_eq!(gl, None);
602        assert_eq!(hd, None);
603        assert_eq!(hc, None);
604        assert_eq!(vc, None);
605    }
606
607    #[test]
608    fn test_set_print_options_partial() {
609        let mut ws = WorksheetXml::default();
610        set_print_options(&mut ws, Some(true), None, None, None).unwrap();
611
612        let (gl, hd, hc, vc) = get_print_options(&ws);
613        assert_eq!(gl, Some(true));
614        assert_eq!(hd, None);
615        assert_eq!(hc, None);
616        assert_eq!(vc, None);
617    }
618
619    #[test]
620    fn test_set_print_options_preserves_existing() {
621        let mut ws = WorksheetXml::default();
622        set_print_options(&mut ws, Some(true), Some(false), None, None).unwrap();
623        set_print_options(&mut ws, None, None, Some(true), None).unwrap();
624
625        let (gl, hd, hc, vc) = get_print_options(&ws);
626        assert_eq!(gl, Some(true));
627        assert_eq!(hd, Some(false));
628        assert_eq!(hc, Some(true));
629        assert_eq!(vc, None);
630    }
631
632    // -- Page break tests --
633
634    #[test]
635    fn test_insert_page_break() {
636        let mut ws = WorksheetXml::default();
637        insert_page_break(&mut ws, 10).unwrap();
638
639        let breaks = get_page_breaks(&ws);
640        assert_eq!(breaks, vec![10]);
641    }
642
643    #[test]
644    fn test_insert_multiple_page_breaks() {
645        let mut ws = WorksheetXml::default();
646        insert_page_break(&mut ws, 20).unwrap();
647        insert_page_break(&mut ws, 10).unwrap();
648        insert_page_break(&mut ws, 30).unwrap();
649
650        let breaks = get_page_breaks(&ws);
651        assert_eq!(breaks, vec![10, 20, 30]);
652    }
653
654    #[test]
655    fn test_insert_duplicate_page_break_is_noop() {
656        let mut ws = WorksheetXml::default();
657        insert_page_break(&mut ws, 10).unwrap();
658        insert_page_break(&mut ws, 10).unwrap();
659
660        let breaks = get_page_breaks(&ws);
661        assert_eq!(breaks, vec![10]);
662    }
663
664    #[test]
665    fn test_remove_page_break() {
666        let mut ws = WorksheetXml::default();
667        insert_page_break(&mut ws, 10).unwrap();
668        insert_page_break(&mut ws, 20).unwrap();
669
670        remove_page_break(&mut ws, 10).unwrap();
671
672        let breaks = get_page_breaks(&ws);
673        assert_eq!(breaks, vec![20]);
674    }
675
676    #[test]
677    fn test_remove_last_page_break_clears_element() {
678        let mut ws = WorksheetXml::default();
679        insert_page_break(&mut ws, 10).unwrap();
680        remove_page_break(&mut ws, 10).unwrap();
681
682        assert!(ws.row_breaks.is_none());
683        assert!(get_page_breaks(&ws).is_empty());
684    }
685
686    #[test]
687    fn test_remove_nonexistent_page_break_is_noop() {
688        let mut ws = WorksheetXml::default();
689        insert_page_break(&mut ws, 10).unwrap();
690        remove_page_break(&mut ws, 99).unwrap();
691
692        let breaks = get_page_breaks(&ws);
693        assert_eq!(breaks, vec![10]);
694    }
695
696    #[test]
697    fn test_get_page_breaks_empty() {
698        let ws = WorksheetXml::default();
699        assert!(get_page_breaks(&ws).is_empty());
700    }
701
702    #[test]
703    fn test_page_break_max_value() {
704        let mut ws = WorksheetXml::default();
705        insert_page_break(&mut ws, 5).unwrap();
706
707        let rb = ws.row_breaks.as_ref().unwrap();
708        assert_eq!(rb.brk[0].max, Some(16383));
709        assert_eq!(rb.brk[0].man, Some(true));
710    }
711
712    #[test]
713    fn test_page_break_count_updated() {
714        let mut ws = WorksheetXml::default();
715        insert_page_break(&mut ws, 10).unwrap();
716        insert_page_break(&mut ws, 20).unwrap();
717        insert_page_break(&mut ws, 30).unwrap();
718
719        let rb = ws.row_breaks.as_ref().unwrap();
720        assert_eq!(rb.count, Some(3));
721        assert_eq!(rb.manual_break_count, Some(3));
722
723        remove_page_break(&mut ws, 20).unwrap();
724        let rb = ws.row_breaks.as_ref().unwrap();
725        assert_eq!(rb.count, Some(2));
726        assert_eq!(rb.manual_break_count, Some(2));
727    }
728
729    // -- Paper size enum tests --
730
731    #[test]
732    fn test_paper_size_values() {
733        assert_eq!(PaperSize::Letter.as_u32(), 1);
734        assert_eq!(PaperSize::Tabloid.as_u32(), 3);
735        assert_eq!(PaperSize::Legal.as_u32(), 5);
736        assert_eq!(PaperSize::A3.as_u32(), 8);
737        assert_eq!(PaperSize::A4.as_u32(), 9);
738        assert_eq!(PaperSize::A5.as_u32(), 11);
739        assert_eq!(PaperSize::B4.as_u32(), 12);
740        assert_eq!(PaperSize::B5.as_u32(), 13);
741    }
742
743    #[test]
744    fn test_paper_size_from_u32() {
745        assert_eq!(PaperSize::from_u32(1), Some(PaperSize::Letter));
746        assert_eq!(PaperSize::from_u32(9), Some(PaperSize::A4));
747        assert_eq!(PaperSize::from_u32(99), None);
748    }
749
750    // -- Orientation enum tests --
751
752    #[test]
753    fn test_orientation_as_str() {
754        assert_eq!(Orientation::Portrait.as_str(), "portrait");
755        assert_eq!(Orientation::Landscape.as_str(), "landscape");
756    }
757
758    #[test]
759    fn test_orientation_from_str() {
760        assert_eq!(
761            Orientation::from_str("portrait"),
762            Some(Orientation::Portrait)
763        );
764        assert_eq!(
765            Orientation::from_str("landscape"),
766            Some(Orientation::Landscape)
767        );
768        assert_eq!(Orientation::from_str("unknown"), None);
769    }
770}