1use crate::annotations::Annotation;
2use crate::error::Result;
3use crate::fonts::type0_parsing::{detect_type0_font, resolve_type0_hierarchy};
4use crate::forms::Widget;
5use crate::graphics::{GraphicsContext, Image};
6use crate::objects::{Array, Dictionary, Object, ObjectReference};
7use crate::text::{HeaderFooter, Table, TextContext, TextFlowContext};
8use std::collections::{HashMap, HashSet};
9
10#[derive(Clone, Debug)]
12pub struct Margins {
13 pub left: f64,
15 pub right: f64,
17 pub top: f64,
19 pub bottom: f64,
21}
22
23impl Default for Margins {
24 fn default() -> Self {
25 Self {
26 left: 72.0, right: 72.0, top: 72.0, bottom: 72.0, }
31 }
32}
33
34#[derive(Clone)]
60pub struct Page {
61 width: f64,
62 height: f64,
63 margins: Margins,
64 content: Vec<u8>,
65 graphics_context: GraphicsContext,
66 text_context: TextContext,
67 images: HashMap<String, Image>,
68 header: Option<HeaderFooter>,
69 footer: Option<HeaderFooter>,
70 annotations: Vec<Annotation>,
71 coordinate_system: crate::coordinate_system::CoordinateSystem,
72 rotation: i32, next_mcid: u32,
75 marked_content_stack: Vec<String>,
77 preserved_resources: Option<crate::pdf_objects::Dictionary>,
80}
81
82impl Page {
83 pub fn new(width: f64, height: f64) -> Self {
87 Self {
88 width,
89 height,
90 margins: Margins::default(),
91 content: Vec::new(),
92 graphics_context: GraphicsContext::new(),
93 text_context: TextContext::new(),
94 images: HashMap::new(),
95 header: None,
96 footer: None,
97 annotations: Vec::new(),
98 coordinate_system: crate::coordinate_system::CoordinateSystem::PdfStandard,
99 rotation: 0, next_mcid: 0,
101 marked_content_stack: Vec::new(),
102 preserved_resources: None,
103 }
104 }
105
106 pub fn from_parsed(parsed_page: &crate::parser::page_tree::ParsedPage) -> Result<Self> {
147 let media_box = parsed_page.media_box;
149 let width = media_box[2] - media_box[0];
150 let height = media_box[3] - media_box[1];
151
152 let rotation = parsed_page.rotation;
154
155 let mut page = Self::new(width, height);
157 page.rotation = rotation;
158
159 Ok(page)
168 }
169
170 pub fn from_parsed_with_content<R: std::io::Read + std::io::Seek>(
207 parsed_page: &crate::parser::page_tree::ParsedPage,
208 document: &crate::parser::document::PdfDocument<R>,
209 ) -> Result<Self> {
210 let media_box = parsed_page.media_box;
212 let width = media_box[2] - media_box[0];
213 let height = media_box[3] - media_box[1];
214
215 let rotation = parsed_page.rotation;
217
218 let mut page = Self::new(width, height);
220 page.rotation = rotation;
221
222 let content_streams = parsed_page.content_streams_with_document(document)?;
224
225 let mut preserved_content = Vec::new();
227 for stream in content_streams {
228 preserved_content.extend_from_slice(&stream);
229 preserved_content.push(b'\n');
231 }
232
233 page.content = preserved_content;
236
237 if let Some(resources) = parsed_page.get_resources() {
239 let mut unified_resources = Self::convert_parser_dict_to_unified(resources);
240
241 if let Some(crate::pdf_objects::Object::Dictionary(fonts)) =
245 unified_resources.get("Font")
246 {
247 let fonts_clone = fonts.clone();
248 let mut resolved_fonts = crate::pdf_objects::Dictionary::new();
249
250 for (font_name, font_obj) in fonts_clone.iter() {
251 let font_dict = match font_obj {
253 crate::pdf_objects::Object::Reference(id) => {
254 match document.get_object(id.number(), id.generation()) {
256 Ok(resolved_obj) => {
257 match Self::convert_parser_object_to_unified(&resolved_obj) {
259 crate::pdf_objects::Object::Dictionary(dict) => dict,
260 _ => {
261 resolved_fonts.set(font_name.clone(), font_obj.clone());
263 continue;
264 }
265 }
266 }
267 Err(_) => {
268 resolved_fonts.set(font_name.clone(), font_obj.clone());
270 continue;
271 }
272 }
273 }
274 crate::pdf_objects::Object::Dictionary(dict) => dict.clone(),
275 _ => {
276 resolved_fonts.set(font_name.clone(), font_obj.clone());
278 continue;
279 }
280 };
281
282 match Self::resolve_font_streams(&font_dict, document) {
284 Ok(resolved_dict) => {
285 resolved_fonts.set(
286 font_name.clone(),
287 crate::pdf_objects::Object::Dictionary(resolved_dict),
288 );
289 }
290 Err(_) => {
291 resolved_fonts.set(
293 font_name.clone(),
294 crate::pdf_objects::Object::Dictionary(font_dict),
295 );
296 }
297 }
298 }
299
300 unified_resources.set(
302 "Font",
303 crate::pdf_objects::Object::Dictionary(resolved_fonts),
304 );
305 }
306
307 page.preserved_resources = Some(unified_resources);
308 }
309
310 Ok(page)
311 }
312
313 pub fn a4() -> Self {
315 Self::new(595.0, 842.0)
316 }
317
318 pub fn a4_landscape() -> Self {
320 Self::new(842.0, 595.0)
321 }
322
323 pub fn letter() -> Self {
325 Self::new(612.0, 792.0)
326 }
327
328 pub fn letter_landscape() -> Self {
330 Self::new(792.0, 612.0)
331 }
332
333 pub fn legal() -> Self {
335 Self::new(612.0, 1008.0)
336 }
337
338 pub fn legal_landscape() -> Self {
340 Self::new(1008.0, 612.0)
341 }
342
343 pub fn graphics(&mut self) -> &mut GraphicsContext {
345 &mut self.graphics_context
346 }
347
348 pub fn text(&mut self) -> &mut TextContext {
350 &mut self.text_context
351 }
352
353 pub fn set_margins(&mut self, left: f64, right: f64, top: f64, bottom: f64) {
354 self.margins = Margins {
355 left,
356 right,
357 top,
358 bottom,
359 };
360 }
361
362 pub fn margins(&self) -> &Margins {
363 &self.margins
364 }
365
366 pub fn content_width(&self) -> f64 {
367 self.width - self.margins.left - self.margins.right
368 }
369
370 pub fn content_height(&self) -> f64 {
371 self.height - self.margins.top - self.margins.bottom
372 }
373
374 pub fn content_area(&self) -> (f64, f64, f64, f64) {
375 (
376 self.margins.left,
377 self.margins.bottom,
378 self.width - self.margins.right,
379 self.height - self.margins.top,
380 )
381 }
382
383 pub fn width(&self) -> f64 {
384 self.width
385 }
386
387 pub fn height(&self) -> f64 {
388 self.height
389 }
390
391 pub fn coordinate_system(&self) -> crate::coordinate_system::CoordinateSystem {
393 self.coordinate_system
394 }
395
396 pub fn set_coordinate_system(
398 &mut self,
399 coordinate_system: crate::coordinate_system::CoordinateSystem,
400 ) -> &mut Self {
401 self.coordinate_system = coordinate_system;
402 self
403 }
404
405 pub fn set_rotation(&mut self, rotation: i32) {
409 let normalized = rotation.rem_euclid(360); self.rotation = match normalized {
412 0..=44 | 316..=360 => 0,
413 45..=134 => 90,
414 135..=224 => 180,
415 225..=315 => 270,
416 _ => 0, };
418 }
419
420 fn convert_parser_dict_to_unified(
422 parser_dict: &crate::parser::objects::PdfDictionary,
423 ) -> crate::pdf_objects::Dictionary {
424 use crate::pdf_objects::{Dictionary, Name};
425
426 let mut unified_dict = Dictionary::new();
427
428 for (key, value) in &parser_dict.0 {
429 let unified_key = Name::new(key.as_str());
430 let unified_value = Self::convert_parser_object_to_unified(value);
431 unified_dict.set(unified_key, unified_value);
432 }
433
434 unified_dict
435 }
436
437 fn convert_parser_object_to_unified(
439 parser_obj: &crate::parser::objects::PdfObject,
440 ) -> crate::pdf_objects::Object {
441 use crate::parser::objects::PdfObject;
442 use crate::pdf_objects::{Array, BinaryString, Name, Object, ObjectId, Stream};
443
444 match parser_obj {
445 PdfObject::Null => Object::Null,
446 PdfObject::Boolean(b) => Object::Boolean(*b),
447 PdfObject::Integer(i) => Object::Integer(*i),
448 PdfObject::Real(f) => Object::Real(*f),
449 PdfObject::String(s) => Object::String(BinaryString::new(s.as_bytes().to_vec())),
450 PdfObject::Name(n) => Object::Name(Name::new(n.as_str())),
451 PdfObject::Array(arr) => {
452 let mut unified_arr = Array::new();
453 for item in &arr.0 {
454 unified_arr.push(Self::convert_parser_object_to_unified(item));
455 }
456 Object::Array(unified_arr)
457 }
458 PdfObject::Dictionary(dict) => {
459 Object::Dictionary(Self::convert_parser_dict_to_unified(dict))
460 }
461 PdfObject::Stream(stream) => {
462 let dict = Self::convert_parser_dict_to_unified(&stream.dict);
463 let data = stream.data.clone();
464 Object::Stream(Stream::new(dict, data))
465 }
466 PdfObject::Reference(num, gen) => Object::Reference(ObjectId::new(*num, *gen)),
467 }
468 }
469
470 fn resolve_font_streams<R: std::io::Read + std::io::Seek>(
482 font_dict: &crate::pdf_objects::Dictionary,
483 document: &crate::parser::document::PdfDocument<R>,
484 ) -> Result<crate::pdf_objects::Dictionary> {
485 use crate::pdf_objects::Object;
486
487 let mut resolved_dict = font_dict.clone();
488
489 if detect_type0_font(font_dict) {
491 let resolver =
493 |id: crate::pdf_objects::ObjectId| -> Option<crate::pdf_objects::Object> {
494 match document.get_object(id.number(), id.generation()) {
495 Ok(parser_obj) => Some(Self::convert_parser_object_to_unified(&parser_obj)),
496 Err(_) => None,
497 }
498 };
499
500 if let Some(info) = resolve_type0_hierarchy(font_dict, resolver) {
502 if let Some(cidfont) = info.cidfont_dict {
504 let mut resolved_cidfont = cidfont;
505
506 if let Some(descriptor) = info.font_descriptor {
508 let mut resolved_descriptor = descriptor;
509
510 if let Some(stream) = info.font_stream {
512 let key = match info.font_file_type {
514 Some(crate::fonts::type0_parsing::FontFileType::TrueType) => {
515 "FontFile2"
516 }
517 Some(crate::fonts::type0_parsing::FontFileType::CFF) => "FontFile3",
518 Some(crate::fonts::type0_parsing::FontFileType::Type1) => {
519 "FontFile"
520 }
521 None => "FontFile2", };
523 resolved_descriptor.set(key, Object::Stream(stream));
524 }
525
526 resolved_cidfont
527 .set("FontDescriptor", Object::Dictionary(resolved_descriptor));
528 }
529
530 let mut descendants = crate::pdf_objects::Array::new();
532 descendants.push(Object::Dictionary(resolved_cidfont));
533 resolved_dict.set("DescendantFonts", Object::Array(descendants));
534 }
535
536 if let Some(tounicode) = info.tounicode_stream {
538 resolved_dict.set("ToUnicode", Object::Stream(tounicode));
539 }
540 }
541
542 return Ok(resolved_dict);
543 }
544
545 if let Some(Object::Reference(descriptor_id)) = font_dict.get("FontDescriptor") {
548 let descriptor_obj =
550 document.get_object(descriptor_id.number(), descriptor_id.generation())?;
551
552 let descriptor_unified = Self::convert_parser_object_to_unified(&descriptor_obj);
554
555 if let Object::Dictionary(mut descriptor_dict) = descriptor_unified {
556 let font_file_keys = ["FontFile", "FontFile2", "FontFile3"];
558 let mut stream_resolved = false;
559
560 for key in &font_file_keys {
561 if let Some(Object::Reference(stream_id)) = descriptor_dict.get(*key) {
562 match document.get_object(stream_id.number(), stream_id.generation()) {
564 Ok(stream_obj) => {
565 let stream_unified =
567 Self::convert_parser_object_to_unified(&stream_obj);
568
569 descriptor_dict.set(*key, stream_unified);
571 stream_resolved = true;
572 }
573 Err(_) => {
574 continue;
576 }
577 }
578 }
579 }
580
581 if stream_resolved {
583 resolved_dict.set("FontDescriptor", Object::Dictionary(descriptor_dict));
584 }
585 }
586 }
587
588 Ok(resolved_dict)
589 }
590
591 pub fn get_preserved_resources(&self) -> Option<&crate::pdf_objects::Dictionary> {
593 self.preserved_resources.as_ref()
594 }
595
596 pub fn get_rotation(&self) -> i32 {
598 self.rotation
599 }
600
601 pub fn effective_width(&self) -> f64 {
604 match self.rotation {
605 90 | 270 => self.height,
606 _ => self.width,
607 }
608 }
609
610 pub fn effective_height(&self) -> f64 {
613 match self.rotation {
614 90 | 270 => self.width,
615 _ => self.height,
616 }
617 }
618
619 pub fn text_flow(&self) -> TextFlowContext {
620 TextFlowContext::new(self.width, self.height, self.margins.clone())
621 }
622
623 pub fn add_text_flow(&mut self, text_flow: &TextFlowContext) {
624 let operations = text_flow.generate_operations();
625 self.content.extend_from_slice(&operations);
626 }
627
628 pub fn add_image(&mut self, name: impl Into<String>, image: Image) {
629 self.images.insert(name.into(), image);
630 }
631
632 pub fn draw_image(
633 &mut self,
634 name: &str,
635 x: f64,
636 y: f64,
637 width: f64,
638 height: f64,
639 ) -> Result<()> {
640 if self.images.contains_key(name) {
641 self.graphics_context.draw_image(name, x, y, width, height);
643 Ok(())
644 } else {
645 Err(crate::PdfError::InvalidReference(format!(
646 "Image '{name}' not found"
647 )))
648 }
649 }
650
651 pub(crate) fn images(&self) -> &HashMap<String, Image> {
652 &self.images
653 }
654
655 pub fn add_table(&mut self, table: &Table) -> Result<()> {
695 self.graphics_context.render_table(table)
696 }
697
698 pub fn get_extgstate_resources(
700 &self,
701 ) -> Option<&std::collections::HashMap<String, crate::graphics::ExtGState>> {
702 if self.graphics_context.has_extgstates() {
703 Some(self.graphics_context.extgstate_manager().states())
704 } else {
705 None
706 }
707 }
708
709 pub fn add_annotation(&mut self, annotation: Annotation) {
711 self.annotations.push(annotation);
712 }
713
714 pub fn annotations(&self) -> &[Annotation] {
716 &self.annotations
717 }
718
719 pub fn annotations_mut(&mut self) -> &mut Vec<Annotation> {
721 &mut self.annotations
722 }
723
724 pub fn add_form_widget(&mut self, widget: Widget) -> ObjectReference {
749 let widget_ref = ObjectReference::new(
753 0, 0,
755 );
756
757 let mut annot = Annotation::new(crate::annotations::AnnotationType::Widget, widget.rect);
759
760 for (key, value) in widget.to_annotation_dict().iter() {
762 annot.properties.set(key, value.clone());
763 }
764
765 self.annotations.push(annot);
767
768 widget_ref
769 }
770
771 pub fn set_header(&mut self, header: HeaderFooter) {
782 self.header = Some(header);
783 }
784
785 pub fn set_footer(&mut self, footer: HeaderFooter) {
796 self.footer = Some(footer);
797 }
798
799 pub fn header(&self) -> Option<&HeaderFooter> {
801 self.header.as_ref()
802 }
803
804 pub fn footer(&self) -> Option<&HeaderFooter> {
806 self.footer.as_ref()
807 }
808
809 pub(crate) fn set_content(&mut self, content: Vec<u8>) {
813 self.content = content;
814 }
815
816 pub(crate) fn generate_content(&mut self) -> Result<Vec<u8>> {
817 self.generate_content_with_page_info(None, None, None)
819 }
820
821 pub(crate) fn generate_content_with_page_info(
826 &mut self,
827 page_number: Option<usize>,
828 total_pages: Option<usize>,
829 custom_values: Option<&HashMap<String, String>>,
830 ) -> Result<Vec<u8>> {
831 let mut final_content = Vec::new();
832
833 if let Some(header) = &self.header {
835 if let (Some(page_num), Some(total)) = (page_number, total_pages) {
836 let header_content =
837 self.render_header_footer(header, page_num, total, custom_values)?;
838 final_content.extend_from_slice(&header_content);
839 }
840 }
841
842 final_content.extend_from_slice(&self.graphics_context.generate_operations()?);
844
845 final_content.extend_from_slice(&self.text_context.generate_operations()?);
847
848 let content_to_add = if let Some(ref preserved_res) = self.preserved_resources {
851 if let Some(fonts_dict) = preserved_res.get("Font") {
853 if let crate::pdf_objects::Object::Dictionary(ref fonts) = fonts_dict {
854 let mut font_mapping = std::collections::HashMap::new();
856 for (original_name, _) in fonts.iter() {
857 let new_name = format!("Orig{}", original_name.as_str());
858 font_mapping.insert(original_name.as_str().to_string(), new_name);
859 }
860
861 if !font_mapping.is_empty() && !self.content.is_empty() {
863 use crate::writer::rewrite_font_references;
864 rewrite_font_references(&self.content, &font_mapping)
865 } else {
866 self.content.clone()
867 }
868 } else {
869 self.content.clone()
870 }
871 } else {
872 self.content.clone()
873 }
874 } else {
875 self.content.clone()
876 };
877
878 final_content.extend_from_slice(&content_to_add);
879
880 if let Some(footer) = &self.footer {
882 if let (Some(page_num), Some(total)) = (page_number, total_pages) {
883 let footer_content =
884 self.render_header_footer(footer, page_num, total, custom_values)?;
885 final_content.extend_from_slice(&footer_content);
886 }
887 }
888
889 Ok(final_content)
890 }
891
892 fn render_header_footer(
894 &self,
895 header_footer: &HeaderFooter,
896 page_number: usize,
897 total_pages: usize,
898 custom_values: Option<&HashMap<String, String>>,
899 ) -> Result<Vec<u8>> {
900 use crate::text::measure_text;
901
902 let content = header_footer.render(page_number, total_pages, custom_values);
904
905 let text_width = measure_text(
907 &content,
908 header_footer.options().font.clone(),
909 header_footer.options().font_size,
910 );
911
912 let x = header_footer.calculate_x_position(self.width, text_width);
914 let y = header_footer.calculate_y_position(self.height);
915
916 let mut text_ctx = TextContext::new();
918 text_ctx
919 .set_font(
920 header_footer.options().font.clone(),
921 header_footer.options().font_size,
922 )
923 .at(x, y)
924 .write(&content)?;
925
926 text_ctx.generate_operations()
927 }
928
929 pub(crate) fn to_dict(&self) -> Dictionary {
931 let mut dict = Dictionary::new();
932
933 let media_box = Array::from(vec![
935 Object::Real(0.0),
936 Object::Real(0.0),
937 Object::Real(self.width),
938 Object::Real(self.height),
939 ]);
940 dict.set("MediaBox", Object::Array(media_box.into()));
941
942 if self.rotation != 0 {
944 dict.set("Rotate", Object::Integer(self.rotation as i64));
945 }
946
947 let resources = Dictionary::new();
949 dict.set("Resources", Object::Dictionary(resources));
950
951 dict
963 }
964
965 pub(crate) fn get_used_characters(&self) -> Option<HashSet<char>> {
970 let graphics_chars = self.graphics_context.get_used_characters();
971 let text_chars = self.text_context.get_used_characters();
972
973 match (graphics_chars, text_chars) {
974 (None, None) => None,
975 (Some(chars), None) | (None, Some(chars)) => Some(chars),
976 (Some(mut g_chars), Some(t_chars)) => {
977 g_chars.extend(t_chars);
978 Some(g_chars)
979 }
980 }
981 }
982
983 pub fn begin_marked_content(&mut self, tag: &str) -> Result<u32> {
1024 let mcid = self.next_mcid;
1025 self.next_mcid += 1;
1026
1027 let bdc_op = format!("/{} <</MCID {}>> BDC\n", tag, mcid);
1030 self.text_context.append_raw_operation(&bdc_op);
1031
1032 self.marked_content_stack.push(tag.to_string());
1033
1034 Ok(mcid)
1035 }
1036
1037 pub fn end_marked_content(&mut self) -> Result<()> {
1046 if self.marked_content_stack.is_empty() {
1047 return Err(crate::PdfError::InvalidOperation(
1048 "No marked content sequence to end (EMC without BDC)".to_string(),
1049 ));
1050 }
1051
1052 self.marked_content_stack.pop();
1053
1054 self.text_context.append_raw_operation("EMC\n");
1056
1057 Ok(())
1058 }
1059
1060 pub fn next_mcid(&self) -> u32 {
1064 self.next_mcid
1065 }
1066
1067 pub fn marked_content_depth(&self) -> usize {
1069 self.marked_content_stack.len()
1070 }
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075 use super::*;
1076 use crate::graphics::Color;
1077 use crate::text::Font;
1078
1079 #[test]
1080 fn test_page_new() {
1081 let page = Page::new(100.0, 200.0);
1082 assert_eq!(page.width(), 100.0);
1083 assert_eq!(page.height(), 200.0);
1084 assert_eq!(page.margins().left, 72.0);
1085 assert_eq!(page.margins().right, 72.0);
1086 assert_eq!(page.margins().top, 72.0);
1087 assert_eq!(page.margins().bottom, 72.0);
1088 }
1089
1090 #[test]
1091 fn test_page_a4() {
1092 let page = Page::a4();
1093 assert_eq!(page.width(), 595.0);
1094 assert_eq!(page.height(), 842.0);
1095 }
1096
1097 #[test]
1098 fn test_page_letter() {
1099 let page = Page::letter();
1100 assert_eq!(page.width(), 612.0);
1101 assert_eq!(page.height(), 792.0);
1102 }
1103
1104 #[test]
1105 fn test_set_margins() {
1106 let mut page = Page::a4();
1107 page.set_margins(10.0, 20.0, 30.0, 40.0);
1108
1109 assert_eq!(page.margins().left, 10.0);
1110 assert_eq!(page.margins().right, 20.0);
1111 assert_eq!(page.margins().top, 30.0);
1112 assert_eq!(page.margins().bottom, 40.0);
1113 }
1114
1115 #[test]
1116 fn test_content_dimensions() {
1117 let mut page = Page::new(300.0, 400.0);
1118 page.set_margins(50.0, 50.0, 50.0, 50.0);
1119
1120 assert_eq!(page.content_width(), 200.0);
1121 assert_eq!(page.content_height(), 300.0);
1122 }
1123
1124 #[test]
1125 fn test_content_area() {
1126 let mut page = Page::new(300.0, 400.0);
1127 page.set_margins(10.0, 20.0, 30.0, 40.0);
1128
1129 let (left, bottom, right, top) = page.content_area();
1130 assert_eq!(left, 10.0);
1131 assert_eq!(bottom, 40.0);
1132 assert_eq!(right, 280.0);
1133 assert_eq!(top, 370.0);
1134 }
1135
1136 #[test]
1137 fn test_graphics_context() {
1138 let mut page = Page::a4();
1139 let graphics = page.graphics();
1140 graphics.set_fill_color(Color::red());
1141 graphics.rect(100.0, 100.0, 200.0, 150.0);
1142 graphics.fill();
1143
1144 assert!(page.generate_content().is_ok());
1146 }
1147
1148 #[test]
1149 fn test_text_context() {
1150 let mut page = Page::a4();
1151 let text = page.text();
1152 text.set_font(Font::Helvetica, 12.0);
1153 text.at(100.0, 700.0);
1154 text.write("Hello World").unwrap();
1155
1156 assert!(page.generate_content().is_ok());
1158 }
1159
1160 #[test]
1161 fn test_text_flow() {
1162 let page = Page::a4();
1163 let text_flow = page.text_flow();
1164
1165 drop(text_flow);
1168 }
1169
1170 #[test]
1171 fn test_add_text_flow() {
1172 let mut page = Page::a4();
1173 let mut text_flow = page.text_flow();
1174 text_flow.at(100.0, 700.0);
1175 text_flow.set_font(Font::TimesRoman, 14.0);
1176 text_flow.write_wrapped("Test text flow").unwrap();
1177
1178 page.add_text_flow(&text_flow);
1179
1180 let content = page.generate_content().unwrap();
1181 assert!(!content.is_empty());
1182 }
1183
1184 #[test]
1185 fn test_add_image() {
1186 let mut page = Page::a4();
1187 let jpeg_data = vec![
1189 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0xFF, 0xD9, ];
1198 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1199
1200 page.add_image("test_image", image);
1201 assert!(page.images().contains_key("test_image"));
1202 assert_eq!(page.images().len(), 1);
1203 }
1204
1205 #[test]
1206 fn test_draw_image() {
1207 let mut page = Page::a4();
1208 let jpeg_data = vec![
1210 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0xFF, 0xD9, ];
1219 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1220
1221 page.add_image("test_image", image);
1222 let result = page.draw_image("test_image", 50.0, 50.0, 200.0, 200.0);
1223 assert!(result.is_ok());
1224 }
1225
1226 #[test]
1227 fn test_draw_nonexistent_image() {
1228 let mut page = Page::a4();
1229 let result = page.draw_image("nonexistent", 50.0, 50.0, 200.0, 200.0);
1230 assert!(result.is_err());
1231 }
1232
1233 #[test]
1234 fn test_generate_content() {
1235 let mut page = Page::a4();
1236
1237 page.graphics()
1239 .set_fill_color(Color::blue())
1240 .circle(200.0, 400.0, 50.0)
1241 .fill();
1242
1243 page.text()
1245 .set_font(Font::Courier, 10.0)
1246 .at(50.0, 650.0)
1247 .write("Test content")
1248 .unwrap();
1249
1250 let content = page.generate_content().unwrap();
1251 assert!(!content.is_empty());
1252 }
1253
1254 #[test]
1255 fn test_margins_default() {
1256 let margins = Margins::default();
1257 assert_eq!(margins.left, 72.0);
1258 assert_eq!(margins.right, 72.0);
1259 assert_eq!(margins.top, 72.0);
1260 assert_eq!(margins.bottom, 72.0);
1261 }
1262
1263 #[test]
1264 fn test_page_clone() {
1265 let mut page1 = Page::a4();
1266 page1.set_margins(10.0, 20.0, 30.0, 40.0);
1267 let jpeg_data = vec![
1269 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x32, 0x03, 0xFF, 0xD9, ];
1278 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1279 page1.add_image("img1", image);
1280
1281 let page2 = page1.clone();
1282 assert_eq!(page2.width(), page1.width());
1283 assert_eq!(page2.height(), page1.height());
1284 assert_eq!(page2.margins().left, page1.margins().left);
1285 assert_eq!(page2.images().len(), page1.images().len());
1286 }
1287
1288 #[test]
1289 fn test_header_footer_basic() {
1290 use crate::text::HeaderFooter;
1291
1292 let mut page = Page::a4();
1293
1294 let header = HeaderFooter::new_header("Test Header");
1295 let footer = HeaderFooter::new_footer("Test Footer");
1296
1297 page.set_header(header);
1298 page.set_footer(footer);
1299
1300 assert!(page.header().is_some());
1301 assert!(page.footer().is_some());
1302 assert_eq!(page.header().unwrap().content(), "Test Header");
1303 assert_eq!(page.footer().unwrap().content(), "Test Footer");
1304 }
1305
1306 #[test]
1307 fn test_header_footer_with_page_numbers() {
1308 use crate::text::HeaderFooter;
1309
1310 let mut page = Page::a4();
1311
1312 let footer = HeaderFooter::new_footer("Page {{page_number}} of {{total_pages}}");
1313 page.set_footer(footer);
1314
1315 let content = page
1317 .generate_content_with_page_info(Some(3), Some(10), None)
1318 .unwrap();
1319 assert!(!content.is_empty());
1320
1321 let content_str = String::from_utf8_lossy(&content);
1323 assert!(content_str.contains("Page 3 of 10"));
1324 }
1325
1326 #[test]
1327 fn test_page_content_with_headers_footers() {
1328 use crate::text::{HeaderFooter, TextAlign};
1329
1330 let mut page = Page::a4();
1331
1332 let header = HeaderFooter::new_header("Document Title")
1334 .with_font(Font::HelveticaBold, 14.0)
1335 .with_alignment(TextAlign::Center);
1336 page.set_header(header);
1337
1338 let footer = HeaderFooter::new_footer("Page {{page_number}}")
1340 .with_font(Font::Helvetica, 10.0)
1341 .with_alignment(TextAlign::Right);
1342 page.set_footer(footer);
1343
1344 page.text()
1346 .set_font(Font::TimesRoman, 12.0)
1347 .at(100.0, 700.0)
1348 .write("Main content here")
1349 .unwrap();
1350
1351 let content = page
1353 .generate_content_with_page_info(Some(1), Some(5), None)
1354 .unwrap();
1355 assert!(!content.is_empty());
1356
1357 assert!(content.len() > 100); }
1362
1363 #[test]
1364 fn test_no_headers_footers() {
1365 let mut page = Page::a4();
1366
1367 assert!(page.header().is_none());
1369 assert!(page.footer().is_none());
1370
1371 let content = page
1373 .generate_content_with_page_info(Some(1), Some(1), None)
1374 .unwrap();
1375 assert!(content.is_empty() || !content.is_empty()); }
1377
1378 #[test]
1379 fn test_header_footer_custom_values() {
1380 use crate::text::HeaderFooter;
1381 use std::collections::HashMap;
1382
1383 let mut page = Page::a4();
1384
1385 let header = HeaderFooter::new_header("{{company}} - {{title}}");
1386 page.set_header(header);
1387
1388 let mut custom_values = HashMap::new();
1389 custom_values.insert("company".to_string(), "ACME Corp".to_string());
1390 custom_values.insert("title".to_string(), "Annual Report".to_string());
1391
1392 let content = page
1393 .generate_content_with_page_info(Some(1), Some(1), Some(&custom_values))
1394 .unwrap();
1395 let content_str = String::from_utf8_lossy(&content);
1396 assert!(content_str.contains("ACME Corp - Annual Report"));
1397 }
1398
1399 mod integration_tests {
1401 use super::*;
1402 use crate::document::Document;
1403 use crate::writer::PdfWriter;
1404 use std::fs;
1405 use tempfile::TempDir;
1406
1407 #[test]
1408 fn test_page_document_integration() {
1409 let mut doc = Document::new();
1410 doc.set_title("Page Integration Test");
1411
1412 let page1 = Page::a4();
1414 let page2 = Page::letter();
1415 let mut page3 = Page::new(400.0, 600.0);
1416
1417 page3.set_margins(20.0, 20.0, 20.0, 20.0);
1419 page3
1420 .text()
1421 .set_font(Font::Helvetica, 14.0)
1422 .at(50.0, 550.0)
1423 .write("Custom page content")
1424 .unwrap();
1425
1426 doc.add_page(page1);
1427 doc.add_page(page2);
1428 doc.add_page(page3);
1429
1430 assert_eq!(doc.pages.len(), 3);
1431
1432 assert_eq!(doc.pages[0].width(), 595.0); assert_eq!(doc.pages[1].width(), 612.0); assert_eq!(doc.pages[2].width(), 400.0); let mut page_copy = doc.pages[2].clone();
1439 let content = page_copy.generate_content().unwrap();
1440 assert!(!content.is_empty());
1441 }
1442
1443 #[test]
1444 fn test_page_writer_integration() {
1445 let temp_dir = TempDir::new().unwrap();
1446 let file_path = temp_dir.path().join("page_writer_test.pdf");
1447
1448 let mut doc = Document::new();
1449 doc.set_title("Page Writer Integration");
1450
1451 let mut page = Page::a4();
1453 page.set_margins(50.0, 50.0, 50.0, 50.0);
1454
1455 page.text()
1457 .set_font(Font::Helvetica, 16.0)
1458 .at(100.0, 750.0)
1459 .write("Integration Test Header")
1460 .unwrap();
1461
1462 page.text()
1463 .set_font(Font::TimesRoman, 12.0)
1464 .at(100.0, 700.0)
1465 .write("This is body text for the integration test.")
1466 .unwrap();
1467
1468 page.graphics()
1470 .set_fill_color(Color::rgb(0.2, 0.6, 0.9))
1471 .rect(100.0, 600.0, 200.0, 50.0)
1472 .fill();
1473
1474 page.graphics()
1475 .set_stroke_color(Color::rgb(0.8, 0.2, 0.2))
1476 .set_line_width(3.0)
1477 .circle(300.0, 500.0, 40.0)
1478 .stroke();
1479
1480 doc.add_page(page);
1481
1482 let mut writer = PdfWriter::new(&file_path).unwrap();
1484 writer.write_document(&mut doc).unwrap();
1485
1486 assert!(file_path.exists());
1488 let metadata = fs::metadata(&file_path).unwrap();
1489 assert!(metadata.len() > 1000); let content = fs::read(&file_path).unwrap();
1493 let content_str = String::from_utf8_lossy(&content);
1494 assert!(content_str.contains("obj")); assert!(content_str.contains("stream")); }
1497
1498 #[test]
1499 fn test_page_margins_integration() {
1500 let temp_dir = TempDir::new().unwrap();
1501 let file_path = temp_dir.path().join("margins_test.pdf");
1502
1503 let mut doc = Document::new();
1504 doc.set_title("Margins Integration Test");
1505
1506 let mut page1 = Page::a4();
1508 page1.set_margins(10.0, 20.0, 30.0, 40.0);
1509
1510 let mut page2 = Page::letter();
1511 page2.set_margins(72.0, 72.0, 72.0, 72.0); let mut page3 = Page::new(500.0, 700.0);
1514 page3.set_margins(0.0, 0.0, 0.0, 0.0); for (i, page) in [&mut page1, &mut page2, &mut page3].iter_mut().enumerate() {
1518 let (left, bottom, right, top) = page.content_area();
1519
1520 page.text()
1522 .set_font(Font::Helvetica, 10.0)
1523 .at(left, top - 20.0)
1524 .write(&format!(
1525 "Page {} - Content area: ({:.1}, {:.1}, {:.1}, {:.1})",
1526 i + 1,
1527 left,
1528 bottom,
1529 right,
1530 top
1531 ))
1532 .unwrap();
1533
1534 page.graphics()
1536 .set_stroke_color(Color::rgb(0.5, 0.5, 0.5))
1537 .set_line_width(1.0)
1538 .rect(left, bottom, right - left, top - bottom)
1539 .stroke();
1540 }
1541
1542 doc.add_page(page1);
1543 doc.add_page(page2);
1544 doc.add_page(page3);
1545
1546 let mut writer = PdfWriter::new(&file_path).unwrap();
1548 writer.write_document(&mut doc).unwrap();
1549
1550 assert!(file_path.exists());
1551 let metadata = fs::metadata(&file_path).unwrap();
1552 assert!(metadata.len() > 500); }
1554
1555 #[test]
1556 fn test_page_image_integration() {
1557 let temp_dir = TempDir::new().unwrap();
1558 let file_path = temp_dir.path().join("image_test.pdf");
1559
1560 let mut doc = Document::new();
1561 doc.set_title("Image Integration Test");
1562
1563 let mut page = Page::a4();
1564
1565 let jpeg_data1 = vec![
1567 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0xFF, 0xD9,
1568 ];
1569 let image1 = Image::from_jpeg_data(jpeg_data1).unwrap();
1570
1571 let jpeg_data2 = vec![
1572 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x32, 0x01, 0xFF, 0xD9,
1573 ];
1574 let image2 = Image::from_jpeg_data(jpeg_data2).unwrap();
1575
1576 page.add_image("image1", image1);
1578 page.add_image("image2", image2);
1579
1580 page.draw_image("image1", 100.0, 600.0, 200.0, 100.0)
1582 .unwrap();
1583 page.draw_image("image2", 350.0, 600.0, 50.0, 50.0).unwrap();
1584
1585 page.text()
1587 .set_font(Font::Helvetica, 12.0)
1588 .at(100.0, 580.0)
1589 .write("Image 1 (200x100)")
1590 .unwrap();
1591
1592 page.text()
1593 .set_font(Font::Helvetica, 12.0)
1594 .at(350.0, 580.0)
1595 .write("Image 2 (50x50)")
1596 .unwrap();
1597
1598 assert_eq!(page.images().len(), 2, "Two images should be added to page");
1600
1601 doc.add_page(page);
1602
1603 let mut writer = PdfWriter::new(&file_path).unwrap();
1605 writer.write_document(&mut doc).unwrap();
1606
1607 assert!(file_path.exists());
1608 let metadata = fs::metadata(&file_path).unwrap();
1609 assert!(metadata.len() > 500); let content = fs::read(&file_path).unwrap();
1613 let content_str = String::from_utf8_lossy(&content);
1614
1615 tracing::debug!("PDF size: {} bytes", content.len());
1617 tracing::debug!("Contains 'XObject': {}", content_str.contains("XObject"));
1618 tracing::debug!("Contains '/XObject': {}", content_str.contains("/XObject"));
1619
1620 if content_str.contains("/Type /Image") || content_str.contains("DCTDecode") {
1622 tracing::debug!("Found image-related content but no XObject dictionary");
1623 }
1624
1625 assert!(content_str.contains("XObject"));
1627 }
1628
1629 #[test]
1630 fn test_page_text_flow_integration() {
1631 let temp_dir = TempDir::new().unwrap();
1632 let file_path = temp_dir.path().join("text_flow_test.pdf");
1633
1634 let mut doc = Document::new();
1635 doc.set_title("Text Flow Integration Test");
1636
1637 let mut page = Page::a4();
1638 page.set_margins(50.0, 50.0, 50.0, 50.0);
1639
1640 let mut text_flow = page.text_flow();
1642 text_flow.set_font(Font::TimesRoman, 12.0);
1643 text_flow.at(100.0, 700.0);
1644
1645 let long_text =
1646 "This is a long paragraph that should demonstrate text flow capabilities. "
1647 .repeat(10);
1648 text_flow.write_wrapped(&long_text).unwrap();
1649
1650 page.add_text_flow(&text_flow);
1652
1653 page.text()
1655 .set_font(Font::Helvetica, 14.0)
1656 .at(100.0, 750.0)
1657 .write("Regular Text Above Text Flow")
1658 .unwrap();
1659
1660 doc.add_page(page);
1661
1662 let mut writer = PdfWriter::new(&file_path).unwrap();
1664 writer.write_document(&mut doc).unwrap();
1665
1666 assert!(file_path.exists());
1667 let metadata = fs::metadata(&file_path).unwrap();
1668 assert!(metadata.len() > 1000); let content = fs::read(&file_path).unwrap();
1672 let content_str = String::from_utf8_lossy(&content);
1673 assert!(content_str.contains("obj")); assert!(content_str.contains("stream")); }
1676
1677 #[test]
1678 fn test_page_complex_content_integration() {
1679 let temp_dir = TempDir::new().unwrap();
1680 let file_path = temp_dir.path().join("complex_content_test.pdf");
1681
1682 let mut doc = Document::new();
1683 doc.set_title("Complex Content Integration Test");
1684
1685 let mut page = Page::a4();
1686 page.set_margins(40.0, 40.0, 40.0, 40.0);
1687
1688 page.graphics()
1692 .set_fill_color(Color::rgb(0.95, 0.95, 0.95))
1693 .rect(50.0, 50.0, 495.0, 742.0)
1694 .fill();
1695
1696 page.graphics()
1698 .set_fill_color(Color::rgb(0.2, 0.4, 0.8))
1699 .rect(50.0, 750.0, 495.0, 42.0)
1700 .fill();
1701
1702 page.text()
1703 .set_font(Font::HelveticaBold, 18.0)
1704 .at(60.0, 765.0)
1705 .write("Complex Content Integration Test")
1706 .unwrap();
1707
1708 let mut y_pos = 700.0;
1710 for i in 1..=3 {
1711 page.graphics()
1713 .set_fill_color(Color::rgb(0.8, 0.8, 0.9))
1714 .rect(60.0, y_pos, 475.0, 20.0)
1715 .fill();
1716
1717 page.text()
1718 .set_font(Font::HelveticaBold, 12.0)
1719 .at(70.0, y_pos + 5.0)
1720 .write(&format!("Section {i}"))
1721 .unwrap();
1722
1723 y_pos -= 30.0;
1724
1725 page.text()
1727 .set_font(Font::TimesRoman, 10.0)
1728 .at(70.0, y_pos)
1729 .write(&format!(
1730 "This is the content for section {i}. It demonstrates mixed content."
1731 ))
1732 .unwrap();
1733
1734 page.graphics()
1736 .set_stroke_color(Color::rgb(0.6, 0.2, 0.2))
1737 .set_line_width(2.0)
1738 .move_to(70.0, y_pos - 10.0)
1739 .line_to(530.0, y_pos - 10.0)
1740 .stroke();
1741
1742 y_pos -= 50.0;
1743 }
1744
1745 page.graphics()
1747 .set_fill_color(Color::rgb(0.3, 0.3, 0.3))
1748 .rect(50.0, 50.0, 495.0, 30.0)
1749 .fill();
1750
1751 page.text()
1752 .set_font(Font::Helvetica, 10.0)
1753 .at(60.0, 60.0)
1754 .write("Generated by oxidize-pdf integration test")
1755 .unwrap();
1756
1757 doc.add_page(page);
1758
1759 let mut writer = PdfWriter::new(&file_path).unwrap();
1761 writer.write_document(&mut doc).unwrap();
1762
1763 assert!(file_path.exists());
1764 let metadata = fs::metadata(&file_path).unwrap();
1765 assert!(metadata.len() > 500); let content = fs::read(&file_path).unwrap();
1769 let content_str = String::from_utf8_lossy(&content);
1770 assert!(content_str.contains("obj")); assert!(content_str.contains("stream")); assert!(content_str.contains("endobj")); }
1774
1775 #[test]
1776 fn test_page_content_generation_performance() {
1777 let mut page = Page::a4();
1778
1779 for i in 0..100 {
1781 let y = 800.0 - (i as f64 * 7.0);
1782 if y > 50.0 {
1783 page.text()
1784 .set_font(Font::Helvetica, 8.0)
1785 .at(50.0, y)
1786 .write(&format!("Performance test line {i}"))
1787 .unwrap();
1788 }
1789 }
1790
1791 for i in 0..50 {
1793 let x = 50.0 + (i as f64 * 10.0);
1794 if x < 550.0 {
1795 page.graphics()
1796 .set_fill_color(Color::rgb(0.5, 0.5, 0.8))
1797 .rect(x, 400.0, 8.0, 8.0)
1798 .fill();
1799 }
1800 }
1801
1802 let start = std::time::Instant::now();
1804 let content = page.generate_content().unwrap();
1805 let duration = start.elapsed();
1806
1807 assert!(!content.is_empty());
1808 assert!(duration.as_millis() < 1000); }
1810
1811 #[test]
1812 fn test_page_error_handling() {
1813 let mut page = Page::a4();
1814
1815 let result = page.draw_image("nonexistent", 100.0, 100.0, 50.0, 50.0);
1817 assert!(result.is_err());
1818
1819 let result = page.draw_image("still_nonexistent", -100.0, -100.0, 0.0, 0.0);
1821 assert!(result.is_err());
1822
1823 let jpeg_data = vec![
1825 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x32, 0x01, 0xFF, 0xD9,
1826 ];
1827 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1828 page.add_image("valid_image", image);
1829
1830 let result = page.draw_image("valid_image", 100.0, 100.0, 50.0, 50.0);
1831 assert!(result.is_ok());
1832 }
1833
1834 #[test]
1835 fn test_page_memory_management() {
1836 let mut pages = Vec::new();
1837
1838 for i in 0..100 {
1840 let mut page = Page::a4();
1841 page.set_margins(i as f64, i as f64, i as f64, i as f64);
1842
1843 page.text()
1844 .set_font(Font::Helvetica, 12.0)
1845 .at(100.0, 700.0)
1846 .write(&format!("Page {i}"))
1847 .unwrap();
1848
1849 pages.push(page);
1850 }
1851
1852 assert_eq!(pages.len(), 100);
1854
1855 for page in pages.iter_mut() {
1857 let content = page.generate_content().unwrap();
1858 assert!(!content.is_empty());
1859 }
1860 }
1861
1862 #[test]
1863 fn test_page_standard_sizes() {
1864 let a4 = Page::a4();
1865 let letter = Page::letter();
1866 let custom = Page::new(200.0, 300.0);
1867
1868 assert_eq!(a4.width(), 595.0);
1870 assert_eq!(a4.height(), 842.0);
1871 assert_eq!(letter.width(), 612.0);
1872 assert_eq!(letter.height(), 792.0);
1873 assert_eq!(custom.width(), 200.0);
1874 assert_eq!(custom.height(), 300.0);
1875
1876 let a4_content_width = a4.content_width();
1878 let letter_content_width = letter.content_width();
1879 let custom_content_width = custom.content_width();
1880
1881 assert_eq!(a4_content_width, 595.0 - 144.0); assert_eq!(letter_content_width, 612.0 - 144.0); assert_eq!(custom_content_width, 200.0 - 144.0); }
1885
1886 #[test]
1887 fn test_header_footer_document_integration() {
1888 use crate::text::{HeaderFooter, TextAlign};
1889
1890 let temp_dir = TempDir::new().unwrap();
1891 let file_path = temp_dir.path().join("header_footer_test.pdf");
1892
1893 let mut doc = Document::new();
1894 doc.set_title("Header Footer Integration Test");
1895
1896 for i in 1..=3 {
1898 let mut page = Page::a4();
1899
1900 let header = HeaderFooter::new_header(format!("Chapter {i}"))
1902 .with_font(Font::HelveticaBold, 16.0)
1903 .with_alignment(TextAlign::Center);
1904 page.set_header(header);
1905
1906 let footer = HeaderFooter::new_footer("Page {{page_number}} of {{total_pages}}")
1908 .with_font(Font::Helvetica, 10.0)
1909 .with_alignment(TextAlign::Center);
1910 page.set_footer(footer);
1911
1912 page.text()
1914 .set_font(Font::TimesRoman, 12.0)
1915 .at(100.0, 700.0)
1916 .write(&format!("This is the content of chapter {i}"))
1917 .unwrap();
1918
1919 doc.add_page(page);
1920 }
1921
1922 let mut writer = PdfWriter::new(&file_path).unwrap();
1924 writer.write_document(&mut doc).unwrap();
1925
1926 assert!(file_path.exists());
1928 let metadata = fs::metadata(&file_path).unwrap();
1929 assert!(metadata.len() > 1000);
1930
1931 let content = fs::read(&file_path).unwrap();
1933 let content_str = String::from_utf8_lossy(&content);
1934
1935 assert!(content.len() > 2000);
1937 assert!(content_str.contains("%PDF"));
1939 assert!(content_str.contains("endobj"));
1940
1941 }
1944
1945 #[test]
1946 fn test_header_footer_alignment_integration() {
1947 use crate::text::{HeaderFooter, TextAlign};
1948
1949 let temp_dir = TempDir::new().unwrap();
1950 let file_path = temp_dir.path().join("alignment_test.pdf");
1951
1952 let mut doc = Document::new();
1953
1954 let mut page = Page::a4();
1955
1956 let header = HeaderFooter::new_header("Left Header")
1958 .with_font(Font::Helvetica, 12.0)
1959 .with_alignment(TextAlign::Left)
1960 .with_margin(50.0);
1961 page.set_header(header);
1962
1963 let footer = HeaderFooter::new_footer("Right Footer - Page {{page_number}}")
1965 .with_font(Font::Helvetica, 10.0)
1966 .with_alignment(TextAlign::Right)
1967 .with_margin(50.0);
1968 page.set_footer(footer);
1969
1970 doc.add_page(page);
1971
1972 let mut writer = PdfWriter::new(&file_path).unwrap();
1974 writer.write_document(&mut doc).unwrap();
1975
1976 assert!(file_path.exists());
1977 }
1978
1979 #[test]
1980 fn test_header_footer_date_time_integration() {
1981 use crate::text::HeaderFooter;
1982
1983 let temp_dir = TempDir::new().unwrap();
1984 let file_path = temp_dir.path().join("date_time_test.pdf");
1985
1986 let mut doc = Document::new();
1987
1988 let mut page = Page::a4();
1989
1990 let header = HeaderFooter::new_header("Report generated on {{date}} at {{time}}")
1992 .with_font(Font::Helvetica, 11.0);
1993 page.set_header(header);
1994
1995 let footer =
1997 HeaderFooter::new_footer("© {{year}} Company Name").with_font(Font::Helvetica, 9.0);
1998 page.set_footer(footer);
1999
2000 doc.add_page(page);
2001
2002 let mut writer = PdfWriter::new(&file_path).unwrap();
2004 writer.write_document(&mut doc).unwrap();
2005
2006 assert!(file_path.exists());
2007
2008 let content = fs::read(&file_path).unwrap();
2010 assert!(content.len() > 500);
2011
2012 let content_str = String::from_utf8_lossy(&content);
2014 assert!(content_str.contains("%PDF"));
2015 assert!(content_str.contains("endobj"));
2016
2017 }
2020 }
2021}
2022
2023#[cfg(test)]
2024mod unit_tests {
2025 use super::*;
2026 use crate::graphics::Color;
2027 use crate::text::Font;
2028
2029 #[test]
2032 fn test_new_page_dimensions() {
2033 let page = Page::new(100.0, 200.0);
2034 assert_eq!(page.width(), 100.0);
2035 assert_eq!(page.height(), 200.0);
2036 }
2037
2038 #[test]
2039 fn test_a4_page_dimensions() {
2040 let page = Page::a4();
2041 assert_eq!(page.width(), 595.0);
2042 assert_eq!(page.height(), 842.0);
2043 }
2044
2045 #[test]
2046 fn test_letter_page_dimensions() {
2047 let page = Page::letter();
2048 assert_eq!(page.width(), 612.0);
2049 assert_eq!(page.height(), 792.0);
2050 }
2051
2052 #[test]
2053 fn test_legal_page_dimensions() {
2054 let page = Page::legal();
2055 assert_eq!(page.width(), 612.0);
2056 assert_eq!(page.height(), 1008.0);
2057 }
2058
2059 #[test]
2062 fn test_default_margins() {
2063 let page = Page::a4();
2064 let margins = page.margins();
2065 assert_eq!(margins.left, 72.0);
2066 assert_eq!(margins.right, 72.0);
2067 assert_eq!(margins.top, 72.0);
2068 assert_eq!(margins.bottom, 72.0);
2069 }
2070
2071 #[test]
2072 fn test_set_margins() {
2073 let mut page = Page::a4();
2074 page.set_margins(10.0, 20.0, 30.0, 40.0);
2075
2076 let margins = page.margins();
2077 assert_eq!(margins.left, 10.0);
2078 assert_eq!(margins.right, 20.0);
2079 assert_eq!(margins.top, 30.0);
2080 assert_eq!(margins.bottom, 40.0);
2081 }
2082
2083 #[test]
2084 fn test_content_width() {
2085 let mut page = Page::new(600.0, 800.0);
2086 page.set_margins(50.0, 50.0, 0.0, 0.0);
2087 assert_eq!(page.content_width(), 500.0);
2088 }
2089
2090 #[test]
2091 fn test_content_height() {
2092 let mut page = Page::new(600.0, 800.0);
2093 page.set_margins(0.0, 0.0, 100.0, 100.0);
2094 assert_eq!(page.content_height(), 600.0);
2095 }
2096
2097 #[test]
2098 fn test_content_area() {
2099 let mut page = Page::new(600.0, 800.0);
2100 page.set_margins(50.0, 60.0, 70.0, 80.0);
2101
2102 let (x, y, right, top) = page.content_area();
2103 assert_eq!(x, 50.0); assert_eq!(y, 80.0); assert_eq!(right, 540.0); assert_eq!(top, 730.0); }
2108
2109 #[test]
2112 fn test_graphics_context_access() {
2113 let mut page = Page::a4();
2114 let gc = page.graphics();
2115
2116 gc.move_to(0.0, 0.0);
2118 gc.line_to(100.0, 100.0);
2119
2120 let ops = gc.get_operations();
2122 assert!(!ops.is_empty());
2123 }
2124
2125 #[test]
2126 fn test_graphics_operations_chain() {
2127 let mut page = Page::a4();
2128
2129 page.graphics()
2130 .set_fill_color(Color::red())
2131 .rectangle(10.0, 10.0, 100.0, 50.0)
2132 .fill();
2133
2134 let ops = page.graphics().get_operations();
2135 assert!(ops.contains("re")); assert!(ops.contains("f")); }
2138
2139 #[test]
2142 fn test_text_context_access() {
2143 let mut page = Page::a4();
2144 let tc = page.text();
2145
2146 tc.set_font(Font::Helvetica, 12.0);
2147 tc.at(100.0, 100.0);
2148
2149 let result = tc.write("Test text");
2151 assert!(result.is_ok());
2152 }
2153
2154 #[test]
2156 fn test_get_used_characters_from_text_context() {
2157 let mut page = Page::a4();
2158
2159 page.text().write("ABC").unwrap();
2161
2162 let chars = page.get_used_characters();
2164 assert!(chars.is_some());
2165 let chars = chars.unwrap();
2166 assert!(chars.contains(&'A'));
2167 assert!(chars.contains(&'B'));
2168 assert!(chars.contains(&'C'));
2169 }
2170
2171 #[test]
2172 fn test_get_used_characters_combines_both_contexts() {
2173 let mut page = Page::a4();
2174
2175 page.text().write("AB").unwrap();
2177
2178 let _ = page.graphics().draw_text("CD", 100.0, 100.0);
2180
2181 let chars = page.get_used_characters();
2183 assert!(chars.is_some());
2184 let chars = chars.unwrap();
2185 assert!(chars.contains(&'A'));
2186 assert!(chars.contains(&'B'));
2187 assert!(chars.contains(&'C'));
2188 assert!(chars.contains(&'D'));
2189 }
2190
2191 #[test]
2192 fn test_get_used_characters_cjk_via_text_context() {
2193 let mut page = Page::a4();
2194
2195 page.text()
2197 .set_font(Font::Custom("NotoSansCJK".to_string()), 12.0);
2198 page.text().write("䏿–‡").unwrap();
2199
2200 let chars = page.get_used_characters();
2201 assert!(chars.is_some());
2202 let chars = chars.unwrap();
2203 assert!(chars.contains(&'ä¸'));
2204 assert!(chars.contains(&'æ–‡'));
2205 }
2206
2207 #[test]
2208 fn test_text_flow_creation() {
2209 let page = Page::a4();
2210 let text_flow = page.text_flow();
2211
2212 let _ = text_flow; }
2217
2218 #[test]
2221 fn test_add_image() {
2222 let mut page = Page::a4();
2223
2224 let image_data = vec![
2226 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x10, 0x00, 0x10, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x00, 0x03, 0x11, 0x00, 0xFF, 0xD9, ];
2238
2239 let image = Image::from_jpeg_data(image_data).unwrap();
2240 page.add_image("test_image", image);
2241
2242 assert!(page.images.contains_key("test_image"));
2244 }
2245
2246 #[test]
2247 fn test_draw_image_simple() {
2248 let mut page = Page::a4();
2249
2250 let image_data = vec![
2252 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x10, 0x00, 0x10, 0x03, 0x01, 0x11,
2253 0x00, 0x02, 0x11, 0x00, 0x03, 0x11, 0x00, 0xFF, 0xD9,
2254 ];
2255
2256 let image = Image::from_jpeg_data(image_data).unwrap();
2257 page.add_image("img1", image);
2258
2259 let result = page.draw_image("img1", 100.0, 100.0, 200.0, 200.0);
2261 assert!(result.is_ok());
2262 }
2263
2264 #[test]
2267 fn test_add_annotation() {
2268 use crate::annotations::{Annotation, AnnotationType};
2269 use crate::geometry::{Point, Rectangle};
2270
2271 let mut page = Page::a4();
2272 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 150.0));
2273 let annotation = Annotation::new(AnnotationType::Text, rect);
2274
2275 page.add_annotation(annotation);
2276 assert_eq!(page.annotations().len(), 1);
2277 }
2278
2279 #[test]
2280 fn test_annotations_mut() {
2281 use crate::annotations::{Annotation, AnnotationType};
2282 use crate::geometry::{Point, Rectangle};
2283
2284 let mut page = Page::a4();
2285 let _rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 150.0));
2286
2287 for i in 0..3 {
2289 let annotation = Annotation::new(
2290 AnnotationType::Text,
2291 Rectangle::new(
2292 Point::new(100.0 + i as f64 * 10.0, 100.0),
2293 Point::new(200.0 + i as f64 * 10.0, 150.0),
2294 ),
2295 );
2296 page.add_annotation(annotation);
2297 }
2298
2299 let annotations = page.annotations_mut();
2301 annotations.clear();
2302 assert_eq!(page.annotations().len(), 0);
2303 }
2304
2305 #[test]
2308 fn test_add_form_widget() {
2309 use crate::forms::Widget;
2310 use crate::geometry::{Point, Rectangle};
2311
2312 let mut page = Page::a4();
2313 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 120.0));
2314 let widget = Widget::new(rect);
2315
2316 let obj_ref = page.add_form_widget(widget);
2317 assert_eq!(obj_ref.number(), 0);
2318 assert_eq!(obj_ref.generation(), 0);
2319
2320 assert_eq!(page.annotations().len(), 1);
2322 }
2323
2324 #[test]
2327 fn test_set_header() {
2328 use crate::text::HeaderFooter;
2329
2330 let mut page = Page::a4();
2331 let header = HeaderFooter::new_header("Test Header");
2332
2333 page.set_header(header);
2334 assert!(page.header().is_some());
2335
2336 if let Some(h) = page.header() {
2337 assert_eq!(h.content(), "Test Header");
2338 }
2339 }
2340
2341 #[test]
2342 fn test_set_footer() {
2343 use crate::text::HeaderFooter;
2344
2345 let mut page = Page::a4();
2346 let footer = HeaderFooter::new_footer("Page {{page}} of {{total}}");
2347
2348 page.set_footer(footer);
2349 assert!(page.footer().is_some());
2350
2351 if let Some(f) = page.footer() {
2352 assert_eq!(f.content(), "Page {{page}} of {{total}}");
2353 }
2354 }
2355
2356 #[test]
2357 fn test_header_footer_rendering() {
2358 use crate::text::HeaderFooter;
2359
2360 let mut page = Page::a4();
2361
2362 page.set_header(HeaderFooter::new_header("Header"));
2364 page.set_footer(HeaderFooter::new_footer("Footer"));
2365
2366 let result = page.generate_content_with_page_info(Some(1), Some(1), None);
2368 assert!(result.is_ok());
2369
2370 let content = result.unwrap();
2371 assert!(!content.is_empty());
2372 }
2373
2374 #[test]
2377 fn test_add_table() {
2378 use crate::text::Table;
2379
2380 let mut page = Page::a4();
2381 let mut table = Table::with_equal_columns(2, 200.0);
2382
2383 table
2385 .add_row(vec!["Cell 1".to_string(), "Cell 2".to_string()])
2386 .unwrap();
2387 table
2388 .add_row(vec!["Cell 3".to_string(), "Cell 4".to_string()])
2389 .unwrap();
2390
2391 let result = page.add_table(&table);
2392 assert!(result.is_ok());
2393 }
2394
2395 #[test]
2398 fn test_generate_operations_empty() {
2399 let page = Page::a4();
2400 let ops = page.graphics_context.generate_operations();
2402
2403 assert!(ops.is_ok());
2405 }
2406
2407 #[test]
2408 fn test_generate_operations_with_graphics() {
2409 let mut page = Page::a4();
2410
2411 page.graphics().rectangle(50.0, 50.0, 100.0, 100.0).fill();
2412
2413 let ops = page.graphics_context.generate_operations();
2415 assert!(ops.is_ok());
2416
2417 let content = ops.unwrap();
2418 let content_str = String::from_utf8_lossy(&content);
2419 assert!(content_str.contains("re")); assert!(content_str.contains("f")); }
2422
2423 #[test]
2424 fn test_generate_operations_with_text() {
2425 let mut page = Page::a4();
2426
2427 page.text()
2428 .set_font(Font::Helvetica, 12.0)
2429 .at(100.0, 700.0)
2430 .write("Hello")
2431 .unwrap();
2432
2433 let ops = page.text_context.generate_operations();
2435 assert!(ops.is_ok());
2436
2437 let content = ops.unwrap();
2438 let content_str = String::from_utf8_lossy(&content);
2439 assert!(content_str.contains("BT")); assert!(content_str.contains("ET")); }
2442
2443 #[test]
2446 fn test_negative_margins() {
2447 let mut page = Page::a4();
2448 page.set_margins(-10.0, -20.0, -30.0, -40.0);
2449
2450 let margins = page.margins();
2452 assert_eq!(margins.left, -10.0);
2453 assert_eq!(margins.right, -20.0);
2454 }
2455
2456 #[test]
2457 fn test_zero_dimensions() {
2458 let page = Page::new(0.0, 0.0);
2459 assert_eq!(page.width(), 0.0);
2460 assert_eq!(page.height(), 0.0);
2461
2462 let (_, _, width, height) = page.content_area();
2464 assert!(width < 0.0);
2465 assert!(height < 0.0);
2466 }
2467
2468 #[test]
2469 fn test_huge_dimensions() {
2470 let page = Page::new(1_000_000.0, 1_000_000.0);
2471 assert_eq!(page.width(), 1_000_000.0);
2472 assert_eq!(page.height(), 1_000_000.0);
2473 }
2474
2475 #[test]
2476 fn test_draw_nonexistent_image() {
2477 let mut page = Page::a4();
2478
2479 let result = page.draw_image("nonexistent", 100.0, 100.0, 200.0, 200.0);
2481
2482 assert!(result.is_err());
2484 }
2485
2486 #[test]
2487 fn test_clone_page() {
2488 let mut page = Page::a4();
2489 page.set_margins(10.0, 20.0, 30.0, 40.0);
2490
2491 page.graphics().rectangle(50.0, 50.0, 100.0, 100.0).fill();
2492
2493 let cloned = page.clone();
2494 assert_eq!(cloned.width(), page.width());
2495 assert_eq!(cloned.height(), page.height());
2496 assert_eq!(cloned.margins().left, page.margins().left);
2497 }
2498
2499 #[test]
2500 fn test_page_from_parsed_basic() {
2501 use crate::parser::objects::PdfDictionary;
2502 use crate::parser::page_tree::ParsedPage;
2503
2504 let parsed_page = ParsedPage {
2506 obj_ref: (1, 0),
2507 dict: PdfDictionary::new(),
2508 inherited_resources: None,
2509 media_box: [0.0, 0.0, 612.0, 792.0], crop_box: None,
2511 rotation: 0,
2512 annotations: None,
2513 };
2514
2515 let page = Page::from_parsed(&parsed_page).unwrap();
2517
2518 assert_eq!(page.width(), 612.0);
2520 assert_eq!(page.height(), 792.0);
2521 assert_eq!(page.get_rotation(), 0);
2522 }
2523
2524 #[test]
2525 fn test_page_from_parsed_with_rotation() {
2526 use crate::parser::objects::PdfDictionary;
2527 use crate::parser::page_tree::ParsedPage;
2528
2529 let parsed_page = ParsedPage {
2531 obj_ref: (1, 0),
2532 dict: PdfDictionary::new(),
2533 inherited_resources: None,
2534 media_box: [0.0, 0.0, 595.0, 842.0], crop_box: None,
2536 rotation: 90,
2537 annotations: None,
2538 };
2539
2540 let page = Page::from_parsed(&parsed_page).unwrap();
2542
2543 assert_eq!(page.get_rotation(), 90);
2545 assert_eq!(page.width(), 595.0);
2546 assert_eq!(page.height(), 842.0);
2547
2548 assert_eq!(page.effective_width(), 842.0);
2550 assert_eq!(page.effective_height(), 595.0);
2551 }
2552
2553 #[test]
2554 fn test_page_from_parsed_with_cropbox() {
2555 use crate::parser::objects::PdfDictionary;
2556 use crate::parser::page_tree::ParsedPage;
2557
2558 let parsed_page = ParsedPage {
2560 obj_ref: (1, 0),
2561 dict: PdfDictionary::new(),
2562 inherited_resources: None,
2563 media_box: [0.0, 0.0, 612.0, 792.0],
2564 crop_box: Some([10.0, 10.0, 602.0, 782.0]),
2565 rotation: 0,
2566 annotations: None,
2567 };
2568
2569 let page = Page::from_parsed(&parsed_page).unwrap();
2571
2572 assert_eq!(page.width(), 612.0);
2574 assert_eq!(page.height(), 792.0);
2575 }
2576
2577 #[test]
2578 fn test_page_from_parsed_small_mediabox() {
2579 use crate::parser::objects::PdfDictionary;
2580 use crate::parser::page_tree::ParsedPage;
2581
2582 let parsed_page = ParsedPage {
2584 obj_ref: (1, 0),
2585 dict: PdfDictionary::new(),
2586 inherited_resources: None,
2587 media_box: [0.0, 0.0, 200.0, 300.0],
2588 crop_box: None,
2589 rotation: 0,
2590 annotations: None,
2591 };
2592
2593 let page = Page::from_parsed(&parsed_page).unwrap();
2595
2596 assert_eq!(page.width(), 200.0);
2597 assert_eq!(page.height(), 300.0);
2598 }
2599
2600 #[test]
2601 fn test_page_from_parsed_non_zero_origin() {
2602 use crate::parser::objects::PdfDictionary;
2603 use crate::parser::page_tree::ParsedPage;
2604
2605 let parsed_page = ParsedPage {
2607 obj_ref: (1, 0),
2608 dict: PdfDictionary::new(),
2609 inherited_resources: None,
2610 media_box: [10.0, 20.0, 610.0, 820.0], crop_box: None,
2612 rotation: 0,
2613 annotations: None,
2614 };
2615
2616 let page = Page::from_parsed(&parsed_page).unwrap();
2618
2619 assert_eq!(page.width(), 600.0); assert_eq!(page.height(), 800.0); }
2623
2624 #[test]
2625 fn test_page_rotation() {
2626 let mut page = Page::a4();
2627
2628 assert_eq!(page.get_rotation(), 0);
2630
2631 page.set_rotation(90);
2633 assert_eq!(page.get_rotation(), 90);
2634
2635 page.set_rotation(180);
2636 assert_eq!(page.get_rotation(), 180);
2637
2638 page.set_rotation(270);
2639 assert_eq!(page.get_rotation(), 270);
2640
2641 page.set_rotation(360);
2642 assert_eq!(page.get_rotation(), 0);
2643
2644 page.set_rotation(45);
2646 assert_eq!(page.get_rotation(), 90);
2647
2648 page.set_rotation(135);
2649 assert_eq!(page.get_rotation(), 180);
2650
2651 page.set_rotation(-90);
2652 assert_eq!(page.get_rotation(), 270);
2653 }
2654
2655 #[test]
2656 fn test_effective_dimensions() {
2657 let mut page = Page::new(600.0, 800.0);
2658
2659 assert_eq!(page.effective_width(), 600.0);
2661 assert_eq!(page.effective_height(), 800.0);
2662
2663 page.set_rotation(90);
2665 assert_eq!(page.effective_width(), 800.0);
2666 assert_eq!(page.effective_height(), 600.0);
2667
2668 page.set_rotation(180);
2670 assert_eq!(page.effective_width(), 600.0);
2671 assert_eq!(page.effective_height(), 800.0);
2672
2673 page.set_rotation(270);
2675 assert_eq!(page.effective_width(), 800.0);
2676 assert_eq!(page.effective_height(), 600.0);
2677 }
2678
2679 #[test]
2680 fn test_rotation_in_pdf_dict() {
2681 let mut page = Page::a4();
2682
2683 let dict = page.to_dict();
2685 assert!(dict.get("Rotate").is_none());
2686
2687 page.set_rotation(90);
2689 let dict = page.to_dict();
2690 assert_eq!(dict.get("Rotate"), Some(&Object::Integer(90)));
2691
2692 page.set_rotation(270);
2693 let dict = page.to_dict();
2694 assert_eq!(dict.get("Rotate"), Some(&Object::Integer(270)));
2695 }
2696}
2697
2698#[derive(Debug)]
2703pub struct LayoutManager {
2704 pub coordinate_system: crate::coordinate_system::CoordinateSystem,
2706 pub current_y: f64,
2708 pub page_width: f64,
2710 pub page_height: f64,
2711 pub margins: Margins,
2713 pub element_spacing: f64,
2715}
2716
2717impl LayoutManager {
2718 pub fn new(page: &Page, coordinate_system: crate::coordinate_system::CoordinateSystem) -> Self {
2720 let current_y = match coordinate_system {
2721 crate::coordinate_system::CoordinateSystem::PdfStandard => {
2722 page.height() - page.margins().top
2724 }
2725 crate::coordinate_system::CoordinateSystem::ScreenSpace => {
2726 page.margins().top
2728 }
2729 crate::coordinate_system::CoordinateSystem::Custom(_) => {
2730 page.height() / 2.0
2732 }
2733 };
2734
2735 Self {
2736 coordinate_system,
2737 current_y,
2738 page_width: page.width(),
2739 page_height: page.height(),
2740 margins: page.margins().clone(),
2741 element_spacing: 10.0,
2742 }
2743 }
2744
2745 pub fn with_element_spacing(mut self, spacing: f64) -> Self {
2747 self.element_spacing = spacing;
2748 self
2749 }
2750
2751 pub fn will_fit(&self, element_height: f64) -> bool {
2753 let required_space = element_height + self.element_spacing;
2754
2755 match self.coordinate_system {
2756 crate::coordinate_system::CoordinateSystem::PdfStandard => {
2757 self.current_y - required_space >= self.margins.bottom
2759 }
2760 crate::coordinate_system::CoordinateSystem::ScreenSpace => {
2761 self.current_y + required_space <= self.page_height - self.margins.bottom
2763 }
2764 crate::coordinate_system::CoordinateSystem::Custom(_) => {
2765 required_space <= (self.page_height - self.margins.top - self.margins.bottom) / 2.0
2767 }
2768 }
2769 }
2770
2771 pub fn remaining_space(&self) -> f64 {
2773 match self.coordinate_system {
2774 crate::coordinate_system::CoordinateSystem::PdfStandard => {
2775 (self.current_y - self.margins.bottom).max(0.0)
2776 }
2777 crate::coordinate_system::CoordinateSystem::ScreenSpace => {
2778 (self.page_height - self.margins.bottom - self.current_y).max(0.0)
2779 }
2780 crate::coordinate_system::CoordinateSystem::Custom(_) => {
2781 self.page_height / 2.0 }
2783 }
2784 }
2785
2786 pub fn add_element(&mut self, element_height: f64) -> Option<f64> {
2792 if !self.will_fit(element_height) {
2793 return None;
2794 }
2795
2796 let position_y = match self.coordinate_system {
2797 crate::coordinate_system::CoordinateSystem::PdfStandard => {
2798 let y_position = self.current_y - element_height;
2801 self.current_y = y_position - self.element_spacing;
2802 self.current_y + element_height }
2804 crate::coordinate_system::CoordinateSystem::ScreenSpace => {
2805 let y_position = self.current_y;
2808 self.current_y += element_height + self.element_spacing;
2809 y_position
2810 }
2811 crate::coordinate_system::CoordinateSystem::Custom(_) => {
2812 let y_position = self.current_y;
2814 self.current_y -= element_height + self.element_spacing;
2815 y_position
2816 }
2817 };
2818
2819 Some(position_y)
2820 }
2821
2822 pub fn new_page(&mut self) {
2824 self.current_y = match self.coordinate_system {
2825 crate::coordinate_system::CoordinateSystem::PdfStandard => {
2826 self.page_height - self.margins.top
2827 }
2828 crate::coordinate_system::CoordinateSystem::ScreenSpace => self.margins.top,
2829 crate::coordinate_system::CoordinateSystem::Custom(_) => self.page_height / 2.0,
2830 };
2831 }
2832
2833 pub fn center_x(&self, element_width: f64) -> f64 {
2835 let available_width = self.page_width - self.margins.left - self.margins.right;
2836 self.margins.left + (available_width - element_width) / 2.0
2837 }
2838
2839 pub fn left_x(&self) -> f64 {
2841 self.margins.left
2842 }
2843
2844 pub fn right_x(&self, element_width: f64) -> f64 {
2846 self.page_width - self.margins.right - element_width
2847 }
2848}
2849
2850#[cfg(test)]
2851mod layout_manager_tests {
2852 use super::*;
2853 use crate::coordinate_system::CoordinateSystem;
2854
2855 #[test]
2856 fn test_layout_manager_pdf_standard() {
2857 let page = Page::a4(); let mut layout = LayoutManager::new(&page, CoordinateSystem::PdfStandard);
2859
2860 assert!(layout.current_y > 750.0); let element_height = 100.0;
2866 let position = layout.add_element(element_height);
2867
2868 assert!(position.is_some());
2869 let y_pos = position.unwrap();
2870 assert!(y_pos > 700.0); assert!(layout.current_y < y_pos);
2874 }
2875
2876 #[test]
2877 fn test_layout_manager_screen_space() {
2878 let page = Page::a4();
2879 let mut layout = LayoutManager::new(&page, CoordinateSystem::ScreenSpace);
2880
2881 assert!(layout.current_y < 100.0); let element_height = 100.0;
2886 let position = layout.add_element(element_height);
2887
2888 assert!(position.is_some());
2889 let y_pos = position.unwrap();
2890 assert!(y_pos < 100.0); assert!(layout.current_y > y_pos);
2894 }
2895
2896 #[test]
2897 fn test_layout_manager_overflow() {
2898 let page = Page::a4();
2899 let mut layout = LayoutManager::new(&page, CoordinateSystem::PdfStandard);
2900
2901 let huge_element = 900.0; let position = layout.add_element(huge_element);
2904
2905 assert!(position.is_none()); let mut count = 0;
2909 while layout.add_element(50.0).is_some() {
2910 count += 1;
2911 if count > 100 {
2912 break;
2913 } }
2915
2916 assert!(count > 5);
2918
2919 assert!(layout.add_element(50.0).is_none());
2921 }
2922
2923 #[test]
2924 fn test_layout_manager_centering() {
2925 let page = Page::a4();
2926 let layout = LayoutManager::new(&page, CoordinateSystem::PdfStandard);
2927
2928 let element_width = 200.0;
2929 let center_x = layout.center_x(element_width);
2930
2931 let expected_center = page.margins().left
2933 + (page.width() - page.margins().left - page.margins().right - element_width) / 2.0;
2934 assert_eq!(center_x, expected_center);
2935 }
2936}