1use sheetkit_xml::worksheet::{
7 Break, HeaderFooter, PageMargins, PageSetup, PrintOptions, RowBreaks, WorksheetXml,
8};
9
10use crate::error::Result;
11
12pub const DEFAULT_MARGIN_LEFT: f64 = 0.7;
16pub const DEFAULT_MARGIN_RIGHT: f64 = 0.7;
18pub const DEFAULT_MARGIN_TOP: f64 = 0.75;
20pub const DEFAULT_MARGIN_BOTTOM: f64 = 0.75;
22pub const DEFAULT_MARGIN_HEADER: f64 = 0.3;
24pub const DEFAULT_MARGIN_FOOTER: f64 = 0.3;
26
27#[derive(Debug, Clone)]
29pub struct PageMarginsConfig {
30 pub left: f64,
32 pub right: f64,
34 pub top: f64,
36 pub bottom: f64,
38 pub header: f64,
40 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#[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#[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 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
115pub 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
128pub 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
143pub 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
185pub 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
193pub 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
201pub fn get_scale(ws: &WorksheetXml) -> Option<u32> {
203 ws.page_setup.as_ref().and_then(|ps| ps.scale)
204}
205
206pub fn get_fit_to_width(ws: &WorksheetXml) -> Option<u32> {
208 ws.page_setup.as_ref().and_then(|ps| ps.fit_to_width)
209}
210
211pub fn get_fit_to_height(ws: &WorksheetXml) -> Option<u32> {
213 ws.page_setup.as_ref().and_then(|ps| ps.fit_to_height)
214}
215
216pub 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
239pub 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
249pub 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
281pub 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
298pub 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 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 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
329pub 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
346pub 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 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 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 #[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 #[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 #[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 #[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}