1use serde::{Deserialize, Serialize};
36use std::collections::HashMap;
37
38use super::paginated::{Margins, Orientation, PageSize, Position};
39use super::style::Transform;
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct MasterPage {
52 pub name: String,
54
55 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub display_name: Option<String>,
58
59 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub page_size: Option<PageSize>,
62
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub orientation: Option<Orientation>,
66
67 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub margins: Option<Margins>,
70
71 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub header: Option<MasterPageRegion>,
74
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub footer: Option<MasterPageRegion>,
78
79 #[serde(default, skip_serializing_if = "Vec::is_empty")]
81 pub background_elements: Vec<MasterPageElement>,
82
83 #[serde(default, skip_serializing_if = "Vec::is_empty")]
85 pub foreground_elements: Vec<MasterPageElement>,
86
87 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
89 pub placeholders: HashMap<String, PlaceholderDefinition>,
90
91 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub based_on: Option<String>,
94}
95
96impl MasterPage {
97 #[must_use]
99 pub fn new(name: impl Into<String>) -> Self {
100 Self {
101 name: name.into(),
102 display_name: None,
103 page_size: None,
104 orientation: None,
105 margins: None,
106 header: None,
107 footer: None,
108 background_elements: Vec::new(),
109 foreground_elements: Vec::new(),
110 placeholders: HashMap::new(),
111 based_on: None,
112 }
113 }
114
115 #[must_use]
117 pub fn with_display_name(mut self, name: impl Into<String>) -> Self {
118 self.display_name = Some(name.into());
119 self
120 }
121
122 #[must_use]
124 pub fn with_page_size(mut self, size: PageSize) -> Self {
125 self.page_size = Some(size);
126 self
127 }
128
129 #[must_use]
131 pub fn with_orientation(mut self, orientation: Orientation) -> Self {
132 self.orientation = Some(orientation);
133 self
134 }
135
136 #[must_use]
138 pub fn with_margins(mut self, margins: Margins) -> Self {
139 self.margins = Some(margins);
140 self
141 }
142
143 #[must_use]
145 pub fn with_header(mut self, content: impl Into<String>) -> Self {
146 self.header = Some(MasterPageRegion::text(content));
147 self
148 }
149
150 #[must_use]
152 pub fn with_header_region(mut self, region: MasterPageRegion) -> Self {
153 self.header = Some(region);
154 self
155 }
156
157 #[must_use]
159 pub fn with_footer(mut self, content: impl Into<String>) -> Self {
160 self.footer = Some(MasterPageRegion::text(content));
161 self
162 }
163
164 #[must_use]
166 pub fn with_footer_region(mut self, region: MasterPageRegion) -> Self {
167 self.footer = Some(region);
168 self
169 }
170
171 #[must_use]
173 pub fn with_background_element(mut self, element: MasterPageElement) -> Self {
174 self.background_elements.push(element);
175 self
176 }
177
178 #[must_use]
180 pub fn with_foreground_element(mut self, element: MasterPageElement) -> Self {
181 self.foreground_elements.push(element);
182 self
183 }
184
185 #[must_use]
187 pub fn based_on(mut self, parent: impl Into<String>) -> Self {
188 self.based_on = Some(parent.into());
189 self
190 }
191
192 #[must_use]
194 pub fn default_master() -> Self {
195 Self::new("default").with_display_name("Default")
196 }
197
198 #[must_use]
200 pub fn odd_page() -> Self {
201 Self::new("odd").with_display_name("Odd Pages (Right)")
202 }
203
204 #[must_use]
206 pub fn even_page() -> Self {
207 Self::new("even").with_display_name("Even Pages (Left)")
208 }
209
210 #[must_use]
212 pub fn title_page() -> Self {
213 Self::new("title").with_display_name("Title Page")
214 }
215}
216
217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
219#[serde(rename_all = "camelCase")]
220pub struct MasterPageRegion {
221 pub content: String,
223
224 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub height: Option<String>,
227
228 #[serde(default, skip_serializing_if = "Option::is_none")]
230 pub style: Option<String>,
231
232 #[serde(default)]
234 pub alignment: RegionAlignment,
235}
236
237impl MasterPageRegion {
238 #[must_use]
240 pub fn text(content: impl Into<String>) -> Self {
241 Self {
242 content: content.into(),
243 height: None,
244 style: None,
245 alignment: RegionAlignment::default(),
246 }
247 }
248
249 #[must_use]
251 pub fn page_number() -> Self {
252 Self::text("{pageNumber}").with_alignment(RegionAlignment::Center)
253 }
254
255 #[must_use]
257 pub fn page_number_of_total() -> Self {
258 Self::text("{pageNumber} of {totalPages}").with_alignment(RegionAlignment::Center)
259 }
260
261 #[must_use]
263 pub fn with_height(mut self, height: impl Into<String>) -> Self {
264 self.height = Some(height.into());
265 self
266 }
267
268 #[must_use]
270 pub fn with_style(mut self, style: impl Into<String>) -> Self {
271 self.style = Some(style.into());
272 self
273 }
274
275 #[must_use]
277 pub fn with_alignment(mut self, alignment: RegionAlignment) -> Self {
278 self.alignment = alignment;
279 self
280 }
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
285#[serde(rename_all = "lowercase")]
286pub enum RegionAlignment {
287 Left,
289 #[default]
291 Center,
292 Right,
294}
295
296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
298#[serde(rename_all = "camelCase")]
299pub struct MasterPageElement {
300 pub id: String,
302
303 #[serde(rename = "type")]
305 pub element_type: MasterElementType,
306
307 pub position: Position,
309
310 #[serde(default, skip_serializing_if = "Option::is_none")]
312 pub content: Option<String>,
313
314 #[serde(default, skip_serializing_if = "Option::is_none")]
316 pub style: Option<String>,
317
318 #[serde(default, skip_serializing_if = "Option::is_none")]
320 pub transform: Option<Transform>,
321
322 #[serde(default, skip_serializing_if = "Option::is_none")]
324 pub opacity: Option<f64>,
325}
326
327impl MasterPageElement {
328 #[must_use]
330 pub fn text(id: impl Into<String>, content: impl Into<String>, position: Position) -> Self {
331 Self {
332 id: id.into(),
333 element_type: MasterElementType::Text,
334 position,
335 content: Some(content.into()),
336 style: None,
337 transform: None,
338 opacity: None,
339 }
340 }
341
342 #[must_use]
344 pub fn image(id: impl Into<String>, src: impl Into<String>, position: Position) -> Self {
345 Self {
346 id: id.into(),
347 element_type: MasterElementType::Image,
348 position,
349 content: Some(src.into()),
350 style: None,
351 transform: None,
352 opacity: None,
353 }
354 }
355
356 #[must_use]
358 pub fn shape(id: impl Into<String>, shape_type: impl Into<String>, position: Position) -> Self {
359 Self {
360 id: id.into(),
361 element_type: MasterElementType::Shape,
362 position,
363 content: Some(shape_type.into()),
364 style: None,
365 transform: None,
366 opacity: None,
367 }
368 }
369
370 #[must_use]
372 pub fn with_style(mut self, style: impl Into<String>) -> Self {
373 self.style = Some(style.into());
374 self
375 }
376
377 #[must_use]
379 pub fn with_opacity(mut self, opacity: f64) -> Self {
380 self.opacity = Some(opacity);
381 self
382 }
383}
384
385#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
387#[serde(rename_all = "lowercase")]
388pub enum MasterElementType {
389 Text,
391 Image,
393 Shape,
395 Logo,
397 PageNumber,
399}
400
401#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
403#[serde(rename_all = "camelCase")]
404pub struct PlaceholderDefinition {
405 #[serde(rename = "type")]
407 pub placeholder_type: PlaceholderType,
408
409 pub position: Position,
411
412 #[serde(default, skip_serializing_if = "Option::is_none")]
414 pub default_content: Option<String>,
415
416 #[serde(default, skip_serializing_if = "Option::is_none")]
418 pub style: Option<String>,
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
423#[serde(rename_all = "camelCase")]
424pub enum PlaceholderType {
425 Text,
427 Image,
429 Content,
431 PageNumber,
433 TotalPages,
435 Date,
437 Title,
439 Author,
441}
442
443#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
449#[serde(rename_all = "camelCase")]
450pub struct PrintSpecification {
451 #[serde(default, skip_serializing_if = "Option::is_none")]
453 pub bleed: Option<BleedBox>,
454
455 #[serde(default)]
457 pub crop_marks: CropMarkStyle,
458
459 #[serde(default)]
461 pub registration_marks: bool,
462
463 #[serde(default)]
465 pub color_bars: bool,
466
467 #[serde(default)]
469 pub page_information: bool,
470
471 #[serde(default, skip_serializing_if = "Option::is_none")]
473 pub trim_box: Option<PageBox>,
474
475 #[serde(default, skip_serializing_if = "Option::is_none")]
477 pub media_box: Option<PageBox>,
478
479 #[serde(default, skip_serializing_if = "Option::is_none")]
481 pub art_box: Option<PageBox>,
482
483 #[serde(default, skip_serializing_if = "Vec::is_empty")]
485 pub spot_colors: Vec<SpotColor>,
486
487 #[serde(default)]
489 pub color_space: ColorSpace,
490}
491
492impl Default for PrintSpecification {
493 fn default() -> Self {
494 Self {
495 bleed: None,
496 crop_marks: CropMarkStyle::None,
497 registration_marks: false,
498 color_bars: false,
499 page_information: false,
500 trim_box: None,
501 media_box: None,
502 art_box: None,
503 spot_colors: Vec::new(),
504 color_space: ColorSpace::default(),
505 }
506 }
507}
508
509impl PrintSpecification {
510 #[must_use]
512 pub fn with_bleed(mut self, bleed: BleedBox) -> Self {
513 self.bleed = Some(bleed);
514 self
515 }
516
517 #[must_use]
519 pub fn with_crop_marks(mut self, style: CropMarkStyle) -> Self {
520 self.crop_marks = style;
521 self
522 }
523
524 #[must_use]
526 pub fn with_registration_marks(mut self) -> Self {
527 self.registration_marks = true;
528 self
529 }
530
531 #[must_use]
533 pub fn with_color_bars(mut self) -> Self {
534 self.color_bars = true;
535 self
536 }
537
538 #[must_use]
540 pub fn with_page_information(mut self) -> Self {
541 self.page_information = true;
542 self
543 }
544
545 #[must_use]
547 pub fn with_color_space(mut self, color_space: ColorSpace) -> Self {
548 self.color_space = color_space;
549 self
550 }
551
552 #[must_use]
554 pub fn with_spot_color(mut self, spot_color: SpotColor) -> Self {
555 self.spot_colors.push(spot_color);
556 self
557 }
558
559 #[must_use]
561 pub fn commercial_print() -> Self {
562 Self::default()
563 .with_bleed(BleedBox::all("0.125in"))
564 .with_crop_marks(CropMarkStyle::All)
565 .with_registration_marks()
566 .with_color_bars()
567 .with_color_space(ColorSpace::Cmyk)
568 }
569}
570
571#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
573pub struct BleedBox {
574 pub top: String,
576 pub right: String,
578 pub bottom: String,
580 pub left: String,
582}
583
584impl BleedBox {
585 #[must_use]
587 pub fn all(value: impl Into<String>) -> Self {
588 let v = value.into();
589 Self {
590 top: v.clone(),
591 right: v.clone(),
592 bottom: v.clone(),
593 left: v,
594 }
595 }
596
597 #[must_use]
599 pub fn standard() -> Self {
600 Self::all("0.125in")
601 }
602
603 #[must_use]
605 pub fn new(
606 top: impl Into<String>,
607 right: impl Into<String>,
608 bottom: impl Into<String>,
609 left: impl Into<String>,
610 ) -> Self {
611 Self {
612 top: top.into(),
613 right: right.into(),
614 bottom: bottom.into(),
615 left: left.into(),
616 }
617 }
618}
619
620#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
622pub struct PageBox {
623 pub width: String,
625 pub height: String,
627}
628
629impl PageBox {
630 #[must_use]
632 pub fn new(width: impl Into<String>, height: impl Into<String>) -> Self {
633 Self {
634 width: width.into(),
635 height: height.into(),
636 }
637 }
638}
639
640#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
642#[serde(rename_all = "camelCase")]
643pub enum CropMarkStyle {
644 #[default]
646 None,
647 TrimMarks,
649 CenterMarks,
651 All,
653}
654
655#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
657#[serde(rename_all = "camelCase")]
658pub struct SpotColor {
659 pub name: String,
661
662 #[serde(rename = "type")]
664 pub color_type: SpotColorType,
665
666 #[serde(default, skip_serializing_if = "Option::is_none")]
668 pub alternate: Option<AlternateColor>,
669
670 #[serde(default = "default_tint")]
672 pub tint: f64,
673}
674
675fn default_tint() -> f64 {
676 100.0
677}
678
679impl SpotColor {
680 #[must_use]
682 pub fn pantone(name: impl Into<String>) -> Self {
683 Self {
684 name: name.into(),
685 color_type: SpotColorType::Pantone,
686 alternate: None,
687 tint: 100.0,
688 }
689 }
690
691 #[must_use]
693 pub fn custom(name: impl Into<String>) -> Self {
694 Self {
695 name: name.into(),
696 color_type: SpotColorType::Custom,
697 alternate: None,
698 tint: 100.0,
699 }
700 }
701
702 #[must_use]
704 pub fn with_cmyk_alternate(mut self, c: f64, m: f64, y: f64, k: f64) -> Self {
705 self.alternate = Some(AlternateColor::Cmyk { c, m, y, k });
706 self
707 }
708
709 #[must_use]
711 pub fn with_rgb_alternate(mut self, r: u8, g: u8, b: u8) -> Self {
712 self.alternate = Some(AlternateColor::Rgb { r, g, b });
713 self
714 }
715
716 #[must_use]
718 pub fn with_tint(mut self, tint: f64) -> Self {
719 self.tint = tint;
720 self
721 }
722}
723
724#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
726#[serde(rename_all = "lowercase")]
727pub enum SpotColorType {
728 Pantone,
730 Custom,
732 Metallic,
734 Fluorescent,
736}
737
738#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
740#[serde(rename_all = "lowercase", tag = "type")]
741pub enum AlternateColor {
742 Cmyk {
744 c: f64,
746 m: f64,
748 y: f64,
750 k: f64,
752 },
753 Rgb {
755 r: u8,
757 g: u8,
759 b: u8,
761 },
762 Lab {
764 l: f64,
766 a: f64,
768 b: f64,
770 },
771}
772
773#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
775#[serde(rename_all = "lowercase")]
776pub enum ColorSpace {
777 #[default]
779 Rgb,
780 Cmyk,
782 Grayscale,
784 Lab,
786}
787
788#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
794#[serde(rename_all = "camelCase")]
795pub struct PdfXCompliance {
796 pub level: PdfXLevel,
798
799 pub output_intent: OutputIntent,
801
802 #[serde(default = "default_true")]
804 pub fonts_embedded: bool,
805
806 #[serde(default)]
808 pub transparency_flattened: bool,
809
810 #[serde(default, skip_serializing_if = "Option::is_none")]
812 pub icc_profile: Option<String>,
813
814 #[serde(default, skip_serializing_if = "Option::is_none")]
816 pub notes: Option<String>,
817}
818
819fn default_true() -> bool {
820 true
821}
822
823impl PdfXCompliance {
824 #[must_use]
826 pub fn x1a_2001() -> Self {
827 Self {
828 level: PdfXLevel::X1a2001,
829 output_intent: OutputIntent::swop(),
830 fonts_embedded: true,
831 transparency_flattened: true,
832 icc_profile: None,
833 notes: None,
834 }
835 }
836
837 #[must_use]
839 pub fn x3_2002() -> Self {
840 Self {
841 level: PdfXLevel::X32002,
842 output_intent: OutputIntent::default(),
843 fonts_embedded: true,
844 transparency_flattened: false,
845 icc_profile: None,
846 notes: None,
847 }
848 }
849
850 #[must_use]
852 pub fn x4() -> Self {
853 Self {
854 level: PdfXLevel::X4,
855 output_intent: OutputIntent::default(),
856 fonts_embedded: true,
857 transparency_flattened: false,
858 icc_profile: None,
859 notes: None,
860 }
861 }
862
863 #[must_use]
865 pub fn with_icc_profile(mut self, profile: impl Into<String>) -> Self {
866 self.icc_profile = Some(profile.into());
867 self
868 }
869
870 #[must_use]
872 pub fn with_output_intent(mut self, intent: OutputIntent) -> Self {
873 self.output_intent = intent;
874 self
875 }
876}
877
878#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
880pub enum PdfXLevel {
881 #[serde(rename = "PDF/X-1a:2001")]
883 #[strum(serialize = "PDF/X-1a:2001")]
884 X1a2001,
885
886 #[serde(rename = "PDF/X-1a:2003")]
888 #[strum(serialize = "PDF/X-1a:2003")]
889 X1a2003,
890
891 #[serde(rename = "PDF/X-3:2002")]
893 #[strum(serialize = "PDF/X-3:2002")]
894 X32002,
895
896 #[serde(rename = "PDF/X-3:2003")]
898 #[strum(serialize = "PDF/X-3:2003")]
899 X32003,
900
901 #[serde(rename = "PDF/X-4")]
903 #[strum(serialize = "PDF/X-4")]
904 X4,
905
906 #[serde(rename = "PDF/X-4p")]
908 #[strum(serialize = "PDF/X-4p")]
909 X4p,
910
911 #[serde(rename = "PDF/X-5g")]
913 #[strum(serialize = "PDF/X-5g")]
914 X5g,
915
916 #[serde(rename = "PDF/X-5pg")]
918 #[strum(serialize = "PDF/X-5pg")]
919 X5pg,
920
921 #[serde(rename = "PDF/X-6")]
923 #[strum(serialize = "PDF/X-6")]
924 X6,
925}
926
927#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
929#[serde(rename_all = "camelCase")]
930pub struct OutputIntent {
931 pub output_condition_identifier: String,
933
934 #[serde(default, skip_serializing_if = "Option::is_none")]
936 pub output_condition: Option<String>,
937
938 #[serde(default, skip_serializing_if = "Option::is_none")]
940 pub registry_name: Option<String>,
941
942 #[serde(default, skip_serializing_if = "Option::is_none")]
944 pub info: Option<String>,
945}
946
947impl Default for OutputIntent {
948 fn default() -> Self {
949 Self {
950 output_condition_identifier: "sRGB".to_string(),
951 output_condition: Some("sRGB IEC61966-2.1".to_string()),
952 registry_name: Some("http://www.color.org".to_string()),
953 info: None,
954 }
955 }
956}
957
958impl OutputIntent {
959 #[must_use]
961 pub fn swop() -> Self {
962 Self {
963 output_condition_identifier: "CGATS TR 001".to_string(),
964 output_condition: Some("SWOP (Publication) Grade 1 Paper".to_string()),
965 registry_name: Some("http://www.color.org".to_string()),
966 info: None,
967 }
968 }
969
970 #[must_use]
972 pub fn fogra39() -> Self {
973 Self {
974 output_condition_identifier: "FOGRA39".to_string(),
975 output_condition: Some("Coated FOGRA39 (ISO 12647-2:2004)".to_string()),
976 registry_name: Some("http://www.color.org".to_string()),
977 info: None,
978 }
979 }
980
981 #[must_use]
983 pub fn gracol() -> Self {
984 Self {
985 output_condition_identifier: "CGATS TR 006".to_string(),
986 output_condition: Some("GRACoL 2006 (Coated #1)".to_string()),
987 registry_name: Some("http://www.color.org".to_string()),
988 info: None,
989 }
990 }
991
992 #[must_use]
994 pub fn custom(identifier: impl Into<String>) -> Self {
995 Self {
996 output_condition_identifier: identifier.into(),
997 output_condition: None,
998 registry_name: None,
999 info: None,
1000 }
1001 }
1002
1003 #[must_use]
1005 pub fn with_condition(mut self, condition: impl Into<String>) -> Self {
1006 self.output_condition = Some(condition.into());
1007 self
1008 }
1009
1010 #[must_use]
1012 pub fn with_registry(mut self, registry: impl Into<String>) -> Self {
1013 self.registry_name = Some(registry.into());
1014 self
1015 }
1016}
1017
1018#[cfg(test)]
1019mod tests {
1020 use super::*;
1021
1022 #[test]
1023 fn test_master_page_creation() {
1024 let master = MasterPage::new("default")
1025 .with_display_name("Default Page")
1026 .with_page_size(PageSize::a4())
1027 .with_margins(Margins::all("1in"))
1028 .with_header("Document Title")
1029 .with_footer("{pageNumber} of {totalPages}");
1030
1031 assert_eq!(master.name, "default");
1032 assert_eq!(master.display_name, Some("Default Page".to_string()));
1033 assert!(master.header.is_some());
1034 assert!(master.footer.is_some());
1035 }
1036
1037 #[test]
1038 fn test_master_page_serialization() {
1039 let master = MasterPage::new("test")
1040 .with_header("Header")
1041 .with_footer("Footer");
1042
1043 let json = serde_json::to_string_pretty(&master).unwrap();
1044 assert!(json.contains("\"name\": \"test\""));
1045
1046 let deserialized: MasterPage = serde_json::from_str(&json).unwrap();
1047 assert_eq!(deserialized.name, "test");
1048 }
1049
1050 #[test]
1051 fn test_print_specification() {
1052 let spec = PrintSpecification::commercial_print();
1053
1054 assert!(spec.bleed.is_some());
1055 assert_eq!(spec.crop_marks, CropMarkStyle::All);
1056 assert!(spec.registration_marks);
1057 assert!(spec.color_bars);
1058 assert_eq!(spec.color_space, ColorSpace::Cmyk);
1059 }
1060
1061 #[test]
1062 fn test_bleed_box() {
1063 let bleed = BleedBox::standard();
1064 assert_eq!(bleed.top, "0.125in");
1065 assert_eq!(bleed.right, "0.125in");
1066 assert_eq!(bleed.bottom, "0.125in");
1067 assert_eq!(bleed.left, "0.125in");
1068 }
1069
1070 #[test]
1071 fn test_spot_color() {
1072 let color = SpotColor::pantone("PANTONE 185 C")
1073 .with_cmyk_alternate(0.0, 91.0, 76.0, 0.0)
1074 .with_tint(100.0);
1075
1076 assert_eq!(color.name, "PANTONE 185 C");
1077 assert_eq!(color.color_type, SpotColorType::Pantone);
1078 assert!(color.alternate.is_some());
1079 }
1080
1081 #[test]
1082 fn test_pdfx_compliance() {
1083 let compliance = PdfXCompliance::x4()
1084 .with_icc_profile("sRGB IEC61966-2.1")
1085 .with_output_intent(OutputIntent::fogra39());
1086
1087 assert_eq!(compliance.level, PdfXLevel::X4);
1088 assert!(compliance.fonts_embedded);
1089 assert!(!compliance.transparency_flattened);
1090 assert_eq!(
1091 compliance.output_intent.output_condition_identifier,
1092 "FOGRA39"
1093 );
1094 }
1095
1096 #[test]
1097 fn test_pdfx_level_display() {
1098 assert_eq!(PdfXLevel::X1a2001.to_string(), "PDF/X-1a:2001");
1099 assert_eq!(PdfXLevel::X4.to_string(), "PDF/X-4");
1100 }
1101
1102 #[test]
1103 fn test_output_intent_presets() {
1104 let swop = OutputIntent::swop();
1105 assert_eq!(swop.output_condition_identifier, "CGATS TR 001");
1106
1107 let fogra = OutputIntent::fogra39();
1108 assert_eq!(fogra.output_condition_identifier, "FOGRA39");
1109
1110 let gracol = OutputIntent::gracol();
1111 assert_eq!(gracol.output_condition_identifier, "CGATS TR 006");
1112 }
1113
1114 #[test]
1115 fn test_master_page_presets() {
1116 let default = MasterPage::default_master();
1117 assert_eq!(default.name, "default");
1118
1119 let odd = MasterPage::odd_page();
1120 assert_eq!(odd.name, "odd");
1121
1122 let even = MasterPage::even_page();
1123 assert_eq!(even.name, "even");
1124
1125 let title = MasterPage::title_page();
1126 assert_eq!(title.name, "title");
1127 }
1128
1129 #[test]
1130 fn test_region_alignment() {
1131 let region = MasterPageRegion::page_number_of_total();
1132 assert_eq!(region.alignment, RegionAlignment::Center);
1133 assert_eq!(region.content, "{pageNumber} of {totalPages}");
1134 }
1135
1136 #[test]
1137 fn test_print_spec_serialization() {
1138 let spec = PrintSpecification::commercial_print();
1139 let json = serde_json::to_string_pretty(&spec).unwrap();
1140
1141 assert!(json.contains("\"cropMarks\": \"all\""));
1142 assert!(json.contains("\"colorSpace\": \"cmyk\""));
1143
1144 let deserialized: PrintSpecification = serde_json::from_str(&json).unwrap();
1145 assert_eq!(deserialized.crop_marks, CropMarkStyle::All);
1146 }
1147}