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 form_xobjects: HashMap<String, crate::graphics::FormXObject>,
69 header: Option<HeaderFooter>,
70 footer: Option<HeaderFooter>,
71 annotations: Vec<Annotation>,
72 coordinate_system: crate::coordinate_system::CoordinateSystem,
73 rotation: i32, next_mcid: u32,
76 marked_content_stack: Vec<String>,
78 preserved_resources: Option<crate::pdf_objects::Dictionary>,
81}
82
83impl Page {
84 pub fn new(width: f64, height: f64) -> Self {
88 Self {
89 width,
90 height,
91 margins: Margins::default(),
92 content: Vec::new(),
93 graphics_context: GraphicsContext::new(),
94 text_context: TextContext::new(),
95 images: HashMap::new(),
96 form_xobjects: HashMap::new(),
97 header: None,
98 footer: None,
99 annotations: Vec::new(),
100 coordinate_system: crate::coordinate_system::CoordinateSystem::PdfStandard,
101 rotation: 0, next_mcid: 0,
103 marked_content_stack: Vec::new(),
104 preserved_resources: None,
105 }
106 }
107
108 pub fn from_parsed(parsed_page: &crate::parser::page_tree::ParsedPage) -> Result<Self> {
149 let media_box = parsed_page.media_box;
151 let width = media_box[2] - media_box[0];
152 let height = media_box[3] - media_box[1];
153
154 let rotation = parsed_page.rotation;
156
157 let mut page = Self::new(width, height);
159 page.rotation = rotation;
160
161 Ok(page)
170 }
171
172 pub fn from_parsed_with_content<R: std::io::Read + std::io::Seek>(
209 parsed_page: &crate::parser::page_tree::ParsedPage,
210 document: &crate::parser::document::PdfDocument<R>,
211 ) -> Result<Self> {
212 let media_box = parsed_page.media_box;
214 let width = media_box[2] - media_box[0];
215 let height = media_box[3] - media_box[1];
216
217 let rotation = parsed_page.rotation;
219
220 let mut page = Self::new(width, height);
222 page.rotation = rotation;
223
224 let content_streams = parsed_page.content_streams_with_document(document)?;
226
227 let mut preserved_content = Vec::new();
229 for stream in content_streams {
230 preserved_content.extend_from_slice(&stream);
231 preserved_content.push(b'\n');
233 }
234
235 page.content = preserved_content;
238
239 if let Some(resources) = parsed_page.get_resources() {
241 let mut unified_resources = Self::convert_parser_dict_to_unified(resources);
242
243 if let Some(crate::pdf_objects::Object::Dictionary(fonts)) =
247 unified_resources.get("Font")
248 {
249 let fonts_clone = fonts.clone();
250 let mut resolved_fonts = crate::pdf_objects::Dictionary::new();
251
252 for (font_name, font_obj) in fonts_clone.iter() {
253 let font_dict = match font_obj {
255 crate::pdf_objects::Object::Reference(id) => {
256 match document.get_object(id.number(), id.generation()) {
258 Ok(resolved_obj) => {
259 match Self::convert_parser_object_to_unified(&resolved_obj) {
261 crate::pdf_objects::Object::Dictionary(dict) => dict,
262 _ => {
263 resolved_fonts.set(font_name.clone(), font_obj.clone());
265 continue;
266 }
267 }
268 }
269 Err(_) => {
270 resolved_fonts.set(font_name.clone(), font_obj.clone());
272 continue;
273 }
274 }
275 }
276 crate::pdf_objects::Object::Dictionary(dict) => dict.clone(),
277 _ => {
278 resolved_fonts.set(font_name.clone(), font_obj.clone());
280 continue;
281 }
282 };
283
284 match Self::resolve_font_streams(&font_dict, document) {
286 Ok(resolved_dict) => {
287 resolved_fonts.set(
288 font_name.clone(),
289 crate::pdf_objects::Object::Dictionary(resolved_dict),
290 );
291 }
292 Err(_) => {
293 resolved_fonts.set(
295 font_name.clone(),
296 crate::pdf_objects::Object::Dictionary(font_dict),
297 );
298 }
299 }
300 }
301
302 unified_resources.set(
304 "Font",
305 crate::pdf_objects::Object::Dictionary(resolved_fonts),
306 );
307 }
308
309 if let Some(crate::pdf_objects::Object::Dictionary(xobjects)) =
312 unified_resources.get("XObject")
313 {
314 let xobjects_clone = xobjects.clone();
315 let mut resolved_xobjects = crate::pdf_objects::Dictionary::new();
316
317 for (xobj_name, xobj_obj) in xobjects_clone.iter() {
318 let resolved = match xobj_obj {
319 crate::pdf_objects::Object::Reference(id) => {
320 match document.get_object(id.number(), id.generation()) {
322 Ok(resolved_obj) => {
323 Self::convert_parser_object_to_unified(&resolved_obj)
324 }
325 Err(_) => {
326 xobj_obj.clone()
328 }
329 }
330 }
331 _ => xobj_obj.clone(),
332 };
333 resolved_xobjects.set(xobj_name.clone(), resolved);
334 }
335
336 unified_resources.set(
337 "XObject",
338 crate::pdf_objects::Object::Dictionary(resolved_xobjects),
339 );
340 }
341
342 if let Some(crate::pdf_objects::Object::Dictionary(extgstates)) =
344 unified_resources.get("ExtGState")
345 {
346 let extgstates_clone = extgstates.clone();
347 let mut resolved_extgstates = crate::pdf_objects::Dictionary::new();
348
349 for (gs_name, gs_obj) in extgstates_clone.iter() {
350 let resolved = match gs_obj {
351 crate::pdf_objects::Object::Reference(id) => {
352 match document.get_object(id.number(), id.generation()) {
353 Ok(resolved_obj) => {
354 Self::convert_parser_object_to_unified(&resolved_obj)
355 }
356 Err(_) => gs_obj.clone(),
357 }
358 }
359 _ => gs_obj.clone(),
360 };
361 resolved_extgstates.set(gs_name.clone(), resolved);
362 }
363
364 unified_resources.set(
365 "ExtGState",
366 crate::pdf_objects::Object::Dictionary(resolved_extgstates),
367 );
368 }
369
370 if let Some(crate::pdf_objects::Object::Dictionary(colorspaces)) =
372 unified_resources.get("ColorSpace")
373 {
374 let colorspaces_clone = colorspaces.clone();
375 let mut resolved_colorspaces = crate::pdf_objects::Dictionary::new();
376
377 for (cs_name, cs_obj) in colorspaces_clone.iter() {
378 let resolved = match cs_obj {
379 crate::pdf_objects::Object::Reference(id) => {
380 match document.get_object(id.number(), id.generation()) {
381 Ok(resolved_obj) => {
382 Self::convert_parser_object_to_unified(&resolved_obj)
383 }
384 Err(_) => cs_obj.clone(),
385 }
386 }
387 _ => cs_obj.clone(),
388 };
389 resolved_colorspaces.set(cs_name.clone(), resolved);
390 }
391
392 unified_resources.set(
393 "ColorSpace",
394 crate::pdf_objects::Object::Dictionary(resolved_colorspaces),
395 );
396 }
397
398 if let Some(crate::pdf_objects::Object::Dictionary(patterns)) =
400 unified_resources.get("Pattern")
401 {
402 let patterns_clone = patterns.clone();
403 let mut resolved_patterns = crate::pdf_objects::Dictionary::new();
404
405 for (pat_name, pat_obj) in patterns_clone.iter() {
406 let resolved = match pat_obj {
407 crate::pdf_objects::Object::Reference(id) => {
408 match document.get_object(id.number(), id.generation()) {
409 Ok(resolved_obj) => {
410 Self::convert_parser_object_to_unified(&resolved_obj)
411 }
412 Err(_) => pat_obj.clone(),
413 }
414 }
415 _ => pat_obj.clone(),
416 };
417 resolved_patterns.set(pat_name.clone(), resolved);
418 }
419
420 unified_resources.set(
421 "Pattern",
422 crate::pdf_objects::Object::Dictionary(resolved_patterns),
423 );
424 }
425
426 if let Some(crate::pdf_objects::Object::Dictionary(shadings)) =
428 unified_resources.get("Shading")
429 {
430 let shadings_clone = shadings.clone();
431 let mut resolved_shadings = crate::pdf_objects::Dictionary::new();
432
433 for (sh_name, sh_obj) in shadings_clone.iter() {
434 let resolved = match sh_obj {
435 crate::pdf_objects::Object::Reference(id) => {
436 match document.get_object(id.number(), id.generation()) {
437 Ok(resolved_obj) => {
438 Self::convert_parser_object_to_unified(&resolved_obj)
439 }
440 Err(_) => sh_obj.clone(),
441 }
442 }
443 _ => sh_obj.clone(),
444 };
445 resolved_shadings.set(sh_name.clone(), resolved);
446 }
447
448 unified_resources.set(
449 "Shading",
450 crate::pdf_objects::Object::Dictionary(resolved_shadings),
451 );
452 }
453
454 page.preserved_resources = Some(unified_resources);
455 }
456
457 Ok(page)
458 }
459
460 pub fn a4() -> Self {
462 Self::new(595.0, 842.0)
463 }
464
465 pub fn a4_landscape() -> Self {
467 Self::new(842.0, 595.0)
468 }
469
470 pub fn letter() -> Self {
472 Self::new(612.0, 792.0)
473 }
474
475 pub fn letter_landscape() -> Self {
477 Self::new(792.0, 612.0)
478 }
479
480 pub fn legal() -> Self {
482 Self::new(612.0, 1008.0)
483 }
484
485 pub fn legal_landscape() -> Self {
487 Self::new(1008.0, 612.0)
488 }
489
490 pub fn graphics(&mut self) -> &mut GraphicsContext {
492 &mut self.graphics_context
493 }
494
495 pub fn text(&mut self) -> &mut TextContext {
497 &mut self.text_context
498 }
499
500 pub fn set_margins(&mut self, left: f64, right: f64, top: f64, bottom: f64) {
501 self.margins = Margins {
502 left,
503 right,
504 top,
505 bottom,
506 };
507 }
508
509 pub fn margins(&self) -> &Margins {
510 &self.margins
511 }
512
513 pub fn content_width(&self) -> f64 {
514 self.width - self.margins.left - self.margins.right
515 }
516
517 pub fn content_height(&self) -> f64 {
518 self.height - self.margins.top - self.margins.bottom
519 }
520
521 pub fn content_area(&self) -> (f64, f64, f64, f64) {
522 (
523 self.margins.left,
524 self.margins.bottom,
525 self.width - self.margins.right,
526 self.height - self.margins.top,
527 )
528 }
529
530 pub fn width(&self) -> f64 {
531 self.width
532 }
533
534 pub fn height(&self) -> f64 {
535 self.height
536 }
537
538 pub fn coordinate_system(&self) -> crate::coordinate_system::CoordinateSystem {
540 self.coordinate_system
541 }
542
543 pub fn set_coordinate_system(
545 &mut self,
546 coordinate_system: crate::coordinate_system::CoordinateSystem,
547 ) -> &mut Self {
548 self.coordinate_system = coordinate_system;
549 self
550 }
551
552 pub fn set_rotation(&mut self, rotation: i32) {
556 let normalized = rotation.rem_euclid(360); self.rotation = match normalized {
559 0..=44 | 316..=360 => 0,
560 45..=134 => 90,
561 135..=224 => 180,
562 225..=315 => 270,
563 _ => 0, };
565 }
566
567 fn convert_parser_dict_to_unified(
569 parser_dict: &crate::parser::objects::PdfDictionary,
570 ) -> crate::pdf_objects::Dictionary {
571 use crate::pdf_objects::{Dictionary, Name};
572
573 let mut unified_dict = Dictionary::new();
574
575 for (key, value) in &parser_dict.0 {
576 let unified_key = Name::new(key.as_str());
577 let unified_value = Self::convert_parser_object_to_unified(value);
578 unified_dict.set(unified_key, unified_value);
579 }
580
581 unified_dict
582 }
583
584 fn convert_parser_object_to_unified(
586 parser_obj: &crate::parser::objects::PdfObject,
587 ) -> crate::pdf_objects::Object {
588 use crate::parser::objects::PdfObject;
589 use crate::pdf_objects::{Array, BinaryString, Name, Object, ObjectId, Stream};
590
591 match parser_obj {
592 PdfObject::Null => Object::Null,
593 PdfObject::Boolean(b) => Object::Boolean(*b),
594 PdfObject::Integer(i) => Object::Integer(*i),
595 PdfObject::Real(f) => Object::Real(*f),
596 PdfObject::String(s) => Object::String(BinaryString::new(s.as_bytes().to_vec())),
597 PdfObject::Name(n) => Object::Name(Name::new(n.as_str())),
598 PdfObject::Array(arr) => {
599 let mut unified_arr = Array::new();
600 for item in &arr.0 {
601 unified_arr.push(Self::convert_parser_object_to_unified(item));
602 }
603 Object::Array(unified_arr)
604 }
605 PdfObject::Dictionary(dict) => {
606 Object::Dictionary(Self::convert_parser_dict_to_unified(dict))
607 }
608 PdfObject::Stream(stream) => {
609 let dict = Self::convert_parser_dict_to_unified(&stream.dict);
610 let data = stream.data.clone();
611 Object::Stream(Stream::new(dict, data))
612 }
613 PdfObject::Reference(num, gen) => Object::Reference(ObjectId::new(*num, *gen)),
614 }
615 }
616
617 fn resolve_font_streams<R: std::io::Read + std::io::Seek>(
629 font_dict: &crate::pdf_objects::Dictionary,
630 document: &crate::parser::document::PdfDocument<R>,
631 ) -> Result<crate::pdf_objects::Dictionary> {
632 use crate::pdf_objects::Object;
633
634 let mut resolved_dict = font_dict.clone();
635
636 if detect_type0_font(font_dict) {
638 let resolver =
640 |id: crate::pdf_objects::ObjectId| -> Option<crate::pdf_objects::Object> {
641 match document.get_object(id.number(), id.generation()) {
642 Ok(parser_obj) => Some(Self::convert_parser_object_to_unified(&parser_obj)),
643 Err(_) => None,
644 }
645 };
646
647 if let Some(info) = resolve_type0_hierarchy(font_dict, resolver) {
649 if let Some(cidfont) = info.cidfont_dict {
651 let mut resolved_cidfont = cidfont;
652
653 if let Some(descriptor) = info.font_descriptor {
655 let mut resolved_descriptor = descriptor;
656
657 if let Some(stream) = info.font_stream {
659 let key = match info.font_file_type {
661 Some(crate::fonts::type0_parsing::FontFileType::TrueType) => {
662 "FontFile2"
663 }
664 Some(crate::fonts::type0_parsing::FontFileType::CFF) => "FontFile3",
665 Some(crate::fonts::type0_parsing::FontFileType::Type1) => {
666 "FontFile"
667 }
668 None => "FontFile2", };
670 resolved_descriptor.set(key, Object::Stream(stream));
671 }
672
673 resolved_cidfont
674 .set("FontDescriptor", Object::Dictionary(resolved_descriptor));
675 }
676
677 let mut descendants = crate::pdf_objects::Array::new();
679 descendants.push(Object::Dictionary(resolved_cidfont));
680 resolved_dict.set("DescendantFonts", Object::Array(descendants));
681 }
682
683 if let Some(tounicode) = info.tounicode_stream {
685 resolved_dict.set("ToUnicode", Object::Stream(tounicode));
686 }
687 }
688
689 return Ok(resolved_dict);
690 }
691
692 if let Some(Object::Reference(descriptor_id)) = font_dict.get("FontDescriptor") {
695 let descriptor_obj =
697 document.get_object(descriptor_id.number(), descriptor_id.generation())?;
698
699 let descriptor_unified = Self::convert_parser_object_to_unified(&descriptor_obj);
701
702 if let Object::Dictionary(mut descriptor_dict) = descriptor_unified {
703 let font_file_keys = ["FontFile", "FontFile2", "FontFile3"];
705 let mut stream_resolved = false;
706
707 for key in &font_file_keys {
708 if let Some(Object::Reference(stream_id)) = descriptor_dict.get(*key) {
709 match document.get_object(stream_id.number(), stream_id.generation()) {
711 Ok(stream_obj) => {
712 let stream_unified =
714 Self::convert_parser_object_to_unified(&stream_obj);
715
716 descriptor_dict.set(*key, stream_unified);
718 stream_resolved = true;
719 }
720 Err(_) => {
721 continue;
723 }
724 }
725 }
726 }
727
728 if stream_resolved {
730 resolved_dict.set("FontDescriptor", Object::Dictionary(descriptor_dict));
731 }
732 }
733 }
734
735 Ok(resolved_dict)
736 }
737
738 pub fn get_preserved_resources(&self) -> Option<&crate::pdf_objects::Dictionary> {
740 self.preserved_resources.as_ref()
741 }
742
743 pub fn get_rotation(&self) -> i32 {
745 self.rotation
746 }
747
748 pub fn effective_width(&self) -> f64 {
751 match self.rotation {
752 90 | 270 => self.height,
753 _ => self.width,
754 }
755 }
756
757 pub fn effective_height(&self) -> f64 {
760 match self.rotation {
761 90 | 270 => self.width,
762 _ => self.height,
763 }
764 }
765
766 pub fn text_flow(&self) -> TextFlowContext {
767 TextFlowContext::new(self.width, self.height, self.margins.clone())
768 }
769
770 pub fn add_text_flow(&mut self, text_flow: &TextFlowContext) {
771 let operations = text_flow.generate_operations();
772 self.content.extend_from_slice(&operations);
773 }
774
775 pub fn add_image(&mut self, name: impl Into<String>, image: Image) {
776 self.images.insert(name.into(), image);
777 }
778
779 pub fn draw_image(
780 &mut self,
781 name: &str,
782 x: f64,
783 y: f64,
784 width: f64,
785 height: f64,
786 ) -> Result<()> {
787 if self.images.contains_key(name) {
788 self.graphics_context.draw_image(name, x, y, width, height);
790 Ok(())
791 } else {
792 Err(crate::PdfError::InvalidReference(format!(
793 "Image '{name}' not found"
794 )))
795 }
796 }
797
798 pub(crate) fn images(&self) -> &HashMap<String, Image> {
799 &self.images
800 }
801
802 pub(crate) fn add_form_xobject(
805 &mut self,
806 name: impl Into<String>,
807 form: crate::graphics::FormXObject,
808 ) {
809 self.form_xobjects.insert(name.into(), form);
810 }
811
812 pub(crate) fn form_xobjects(&self) -> &HashMap<String, crate::graphics::FormXObject> {
814 &self.form_xobjects
815 }
816
817 pub(crate) fn append_raw_content(&mut self, data: &[u8]) {
820 self.content.extend_from_slice(data);
821 }
822
823 pub fn add_table(&mut self, table: &Table) -> Result<()> {
863 self.graphics_context.render_table(table)
864 }
865
866 pub fn get_extgstate_resources(
868 &self,
869 ) -> Option<&std::collections::HashMap<String, crate::graphics::ExtGState>> {
870 if self.graphics_context.has_extgstates() {
871 Some(self.graphics_context.extgstate_manager().states())
872 } else {
873 None
874 }
875 }
876
877 pub fn add_annotation(&mut self, annotation: Annotation) {
879 self.annotations.push(annotation);
880 }
881
882 pub fn annotations(&self) -> &[Annotation] {
884 &self.annotations
885 }
886
887 pub fn annotations_mut(&mut self) -> &mut Vec<Annotation> {
889 &mut self.annotations
890 }
891
892 pub fn add_form_widget(&mut self, widget: Widget) -> ObjectReference {
917 let widget_ref = ObjectReference::new(
921 0, 0,
923 );
924
925 let mut annot = Annotation::new(crate::annotations::AnnotationType::Widget, widget.rect);
927
928 for (key, value) in widget.to_annotation_dict().iter() {
930 annot.properties.set(key, value.clone());
931 }
932
933 self.annotations.push(annot);
935
936 widget_ref
937 }
938
939 pub fn set_header(&mut self, header: HeaderFooter) {
950 self.header = Some(header);
951 }
952
953 pub fn set_footer(&mut self, footer: HeaderFooter) {
964 self.footer = Some(footer);
965 }
966
967 pub fn header(&self) -> Option<&HeaderFooter> {
969 self.header.as_ref()
970 }
971
972 pub fn footer(&self) -> Option<&HeaderFooter> {
974 self.footer.as_ref()
975 }
976
977 pub(crate) fn set_content(&mut self, content: Vec<u8>) {
981 self.content = content;
982 }
983
984 pub(crate) fn generate_content(&mut self) -> Result<Vec<u8>> {
985 self.generate_content_with_page_info(None, None, None)
987 }
988
989 pub(crate) fn generate_content_with_page_info(
994 &mut self,
995 page_number: Option<usize>,
996 total_pages: Option<usize>,
997 custom_values: Option<&HashMap<String, String>>,
998 ) -> Result<Vec<u8>> {
999 let mut final_content = Vec::new();
1000
1001 if let Some(header) = &self.header {
1003 if let (Some(page_num), Some(total)) = (page_number, total_pages) {
1004 let header_content =
1005 self.render_header_footer(header, page_num, total, custom_values)?;
1006 final_content.extend_from_slice(&header_content);
1007 }
1008 }
1009
1010 final_content.extend_from_slice(&self.graphics_context.generate_operations()?);
1012
1013 final_content.extend_from_slice(&self.text_context.generate_operations()?);
1015
1016 let content_to_add = if let Some(ref preserved_res) = self.preserved_resources {
1019 if let Some(fonts_dict) = preserved_res.get("Font") {
1021 if let crate::pdf_objects::Object::Dictionary(ref fonts) = fonts_dict {
1022 let mut font_mapping = std::collections::HashMap::new();
1024 for (original_name, _) in fonts.iter() {
1025 let new_name = format!("Orig{}", original_name.as_str());
1026 font_mapping.insert(original_name.as_str().to_string(), new_name);
1027 }
1028
1029 if !font_mapping.is_empty() && !self.content.is_empty() {
1031 use crate::writer::rewrite_font_references;
1032 rewrite_font_references(&self.content, &font_mapping)
1033 } else {
1034 self.content.clone()
1035 }
1036 } else {
1037 self.content.clone()
1038 }
1039 } else {
1040 self.content.clone()
1041 }
1042 } else {
1043 self.content.clone()
1044 };
1045
1046 final_content.extend_from_slice(&content_to_add);
1047
1048 if let Some(footer) = &self.footer {
1050 if let (Some(page_num), Some(total)) = (page_number, total_pages) {
1051 let footer_content =
1052 self.render_header_footer(footer, page_num, total, custom_values)?;
1053 final_content.extend_from_slice(&footer_content);
1054 }
1055 }
1056
1057 Ok(final_content)
1058 }
1059
1060 fn render_header_footer(
1062 &self,
1063 header_footer: &HeaderFooter,
1064 page_number: usize,
1065 total_pages: usize,
1066 custom_values: Option<&HashMap<String, String>>,
1067 ) -> Result<Vec<u8>> {
1068 use crate::text::measure_text;
1069
1070 let content = header_footer.render(page_number, total_pages, custom_values);
1072
1073 let text_width = measure_text(
1075 &content,
1076 header_footer.options().font.clone(),
1077 header_footer.options().font_size,
1078 );
1079
1080 let x = header_footer.calculate_x_position(self.width, text_width);
1082 let y = header_footer.calculate_y_position(self.height);
1083
1084 let mut text_ctx = TextContext::new();
1086 text_ctx
1087 .set_font(
1088 header_footer.options().font.clone(),
1089 header_footer.options().font_size,
1090 )
1091 .at(x, y)
1092 .write(&content)?;
1093
1094 text_ctx.generate_operations()
1095 }
1096
1097 pub(crate) fn to_dict(&self) -> Dictionary {
1099 let mut dict = Dictionary::new();
1100
1101 let media_box = Array::from(vec![
1103 Object::Real(0.0),
1104 Object::Real(0.0),
1105 Object::Real(self.width),
1106 Object::Real(self.height),
1107 ]);
1108 dict.set("MediaBox", Object::Array(media_box.into()));
1109
1110 if self.rotation != 0 {
1112 dict.set("Rotate", Object::Integer(self.rotation as i64));
1113 }
1114
1115 let resources = Dictionary::new();
1117 dict.set("Resources", Object::Dictionary(resources));
1118
1119 dict
1131 }
1132
1133 pub(crate) fn get_used_characters(&self) -> Option<HashSet<char>> {
1138 let graphics_chars = self.graphics_context.get_used_characters();
1139 let text_chars = self.text_context.get_used_characters();
1140
1141 match (graphics_chars, text_chars) {
1142 (None, None) => None,
1143 (Some(chars), None) | (None, Some(chars)) => Some(chars),
1144 (Some(mut g_chars), Some(t_chars)) => {
1145 g_chars.extend(t_chars);
1146 Some(g_chars)
1147 }
1148 }
1149 }
1150
1151 pub fn begin_marked_content(&mut self, tag: &str) -> Result<u32> {
1192 let mcid = self.next_mcid;
1193 self.next_mcid += 1;
1194
1195 let bdc_op = format!("/{} <</MCID {}>> BDC\n", tag, mcid);
1198 self.text_context.append_raw_operation(&bdc_op);
1199
1200 self.marked_content_stack.push(tag.to_string());
1201
1202 Ok(mcid)
1203 }
1204
1205 pub fn end_marked_content(&mut self) -> Result<()> {
1214 if self.marked_content_stack.is_empty() {
1215 return Err(crate::PdfError::InvalidOperation(
1216 "No marked content sequence to end (EMC without BDC)".to_string(),
1217 ));
1218 }
1219
1220 self.marked_content_stack.pop();
1221
1222 self.text_context.append_raw_operation("EMC\n");
1224
1225 Ok(())
1226 }
1227
1228 pub fn next_mcid(&self) -> u32 {
1232 self.next_mcid
1233 }
1234
1235 pub fn marked_content_depth(&self) -> usize {
1237 self.marked_content_stack.len()
1238 }
1239}
1240
1241#[cfg(test)]
1242mod tests {
1243 use super::*;
1244 use crate::graphics::Color;
1245 use crate::text::Font;
1246
1247 #[test]
1248 fn test_page_new() {
1249 let page = Page::new(100.0, 200.0);
1250 assert_eq!(page.width(), 100.0);
1251 assert_eq!(page.height(), 200.0);
1252 assert_eq!(page.margins().left, 72.0);
1253 assert_eq!(page.margins().right, 72.0);
1254 assert_eq!(page.margins().top, 72.0);
1255 assert_eq!(page.margins().bottom, 72.0);
1256 }
1257
1258 #[test]
1259 fn test_page_a4() {
1260 let page = Page::a4();
1261 assert_eq!(page.width(), 595.0);
1262 assert_eq!(page.height(), 842.0);
1263 }
1264
1265 #[test]
1266 fn test_page_letter() {
1267 let page = Page::letter();
1268 assert_eq!(page.width(), 612.0);
1269 assert_eq!(page.height(), 792.0);
1270 }
1271
1272 #[test]
1273 fn test_set_margins() {
1274 let mut page = Page::a4();
1275 page.set_margins(10.0, 20.0, 30.0, 40.0);
1276
1277 assert_eq!(page.margins().left, 10.0);
1278 assert_eq!(page.margins().right, 20.0);
1279 assert_eq!(page.margins().top, 30.0);
1280 assert_eq!(page.margins().bottom, 40.0);
1281 }
1282
1283 #[test]
1284 fn test_content_dimensions() {
1285 let mut page = Page::new(300.0, 400.0);
1286 page.set_margins(50.0, 50.0, 50.0, 50.0);
1287
1288 assert_eq!(page.content_width(), 200.0);
1289 assert_eq!(page.content_height(), 300.0);
1290 }
1291
1292 #[test]
1293 fn test_content_area() {
1294 let mut page = Page::new(300.0, 400.0);
1295 page.set_margins(10.0, 20.0, 30.0, 40.0);
1296
1297 let (left, bottom, right, top) = page.content_area();
1298 assert_eq!(left, 10.0);
1299 assert_eq!(bottom, 40.0);
1300 assert_eq!(right, 280.0);
1301 assert_eq!(top, 370.0);
1302 }
1303
1304 #[test]
1305 fn test_graphics_context() {
1306 let mut page = Page::a4();
1307 let graphics = page.graphics();
1308 graphics.set_fill_color(Color::red());
1309 graphics.rect(100.0, 100.0, 200.0, 150.0);
1310 graphics.fill();
1311
1312 assert!(page.generate_content().is_ok());
1314 }
1315
1316 #[test]
1317 fn test_text_context() {
1318 let mut page = Page::a4();
1319 let text = page.text();
1320 text.set_font(Font::Helvetica, 12.0);
1321 text.at(100.0, 700.0);
1322 text.write("Hello World").unwrap();
1323
1324 assert!(page.generate_content().is_ok());
1326 }
1327
1328 #[test]
1329 fn test_text_flow() {
1330 let page = Page::a4();
1331 let text_flow = page.text_flow();
1332
1333 drop(text_flow);
1336 }
1337
1338 #[test]
1339 fn test_add_text_flow() {
1340 let mut page = Page::a4();
1341 let mut text_flow = page.text_flow();
1342 text_flow.at(100.0, 700.0);
1343 text_flow.set_font(Font::TimesRoman, 14.0);
1344 text_flow.write_wrapped("Test text flow").unwrap();
1345
1346 page.add_text_flow(&text_flow);
1347
1348 let content = page.generate_content().unwrap();
1349 assert!(!content.is_empty());
1350 }
1351
1352 #[test]
1353 fn test_add_image() {
1354 let mut page = Page::a4();
1355 let jpeg_data = vec![
1357 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0xFF, 0xD9, ];
1366 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1367
1368 page.add_image("test_image", image);
1369 assert!(page.images().contains_key("test_image"));
1370 assert_eq!(page.images().len(), 1);
1371 }
1372
1373 #[test]
1374 fn test_draw_image() {
1375 let mut page = Page::a4();
1376 let jpeg_data = vec![
1378 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0xFF, 0xD9, ];
1387 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1388
1389 page.add_image("test_image", image);
1390 let result = page.draw_image("test_image", 50.0, 50.0, 200.0, 200.0);
1391 assert!(result.is_ok());
1392 }
1393
1394 #[test]
1395 fn test_draw_nonexistent_image() {
1396 let mut page = Page::a4();
1397 let result = page.draw_image("nonexistent", 50.0, 50.0, 200.0, 200.0);
1398 assert!(result.is_err());
1399 }
1400
1401 #[test]
1402 fn test_generate_content() {
1403 let mut page = Page::a4();
1404
1405 page.graphics()
1407 .set_fill_color(Color::blue())
1408 .circle(200.0, 400.0, 50.0)
1409 .fill();
1410
1411 page.text()
1413 .set_font(Font::Courier, 10.0)
1414 .at(50.0, 650.0)
1415 .write("Test content")
1416 .unwrap();
1417
1418 let content = page.generate_content().unwrap();
1419 assert!(!content.is_empty());
1420 }
1421
1422 #[test]
1423 fn test_margins_default() {
1424 let margins = Margins::default();
1425 assert_eq!(margins.left, 72.0);
1426 assert_eq!(margins.right, 72.0);
1427 assert_eq!(margins.top, 72.0);
1428 assert_eq!(margins.bottom, 72.0);
1429 }
1430
1431 #[test]
1432 fn test_page_clone() {
1433 let mut page1 = Page::a4();
1434 page1.set_margins(10.0, 20.0, 30.0, 40.0);
1435 let jpeg_data = vec![
1437 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x32, 0x03, 0xFF, 0xD9, ];
1446 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1447 page1.add_image("img1", image);
1448
1449 let page2 = page1.clone();
1450 assert_eq!(page2.width(), page1.width());
1451 assert_eq!(page2.height(), page1.height());
1452 assert_eq!(page2.margins().left, page1.margins().left);
1453 assert_eq!(page2.images().len(), page1.images().len());
1454 }
1455
1456 #[test]
1457 fn test_header_footer_basic() {
1458 use crate::text::HeaderFooter;
1459
1460 let mut page = Page::a4();
1461
1462 let header = HeaderFooter::new_header("Test Header");
1463 let footer = HeaderFooter::new_footer("Test Footer");
1464
1465 page.set_header(header);
1466 page.set_footer(footer);
1467
1468 assert!(page.header().is_some());
1469 assert!(page.footer().is_some());
1470 assert_eq!(page.header().unwrap().content(), "Test Header");
1471 assert_eq!(page.footer().unwrap().content(), "Test Footer");
1472 }
1473
1474 #[test]
1475 fn test_header_footer_with_page_numbers() {
1476 use crate::text::HeaderFooter;
1477
1478 let mut page = Page::a4();
1479
1480 let footer = HeaderFooter::new_footer("Page {{page_number}} of {{total_pages}}");
1481 page.set_footer(footer);
1482
1483 let content = page
1485 .generate_content_with_page_info(Some(3), Some(10), None)
1486 .unwrap();
1487 assert!(!content.is_empty());
1488
1489 let content_str = String::from_utf8_lossy(&content);
1491 assert!(content_str.contains("Page 3 of 10"));
1492 }
1493
1494 #[test]
1495 fn test_page_content_with_headers_footers() {
1496 use crate::text::{HeaderFooter, TextAlign};
1497
1498 let mut page = Page::a4();
1499
1500 let header = HeaderFooter::new_header("Document Title")
1502 .with_font(Font::HelveticaBold, 14.0)
1503 .with_alignment(TextAlign::Center);
1504 page.set_header(header);
1505
1506 let footer = HeaderFooter::new_footer("Page {{page_number}}")
1508 .with_font(Font::Helvetica, 10.0)
1509 .with_alignment(TextAlign::Right);
1510 page.set_footer(footer);
1511
1512 page.text()
1514 .set_font(Font::TimesRoman, 12.0)
1515 .at(100.0, 700.0)
1516 .write("Main content here")
1517 .unwrap();
1518
1519 let content = page
1521 .generate_content_with_page_info(Some(1), Some(5), None)
1522 .unwrap();
1523 assert!(!content.is_empty());
1524
1525 assert!(content.len() > 100); }
1530
1531 #[test]
1532 fn test_no_headers_footers() {
1533 let mut page = Page::a4();
1534
1535 assert!(page.header().is_none());
1537 assert!(page.footer().is_none());
1538
1539 let content = page
1541 .generate_content_with_page_info(Some(1), Some(1), None)
1542 .unwrap();
1543 assert!(content.is_empty() || !content.is_empty()); }
1545
1546 #[test]
1547 fn test_header_footer_custom_values() {
1548 use crate::text::HeaderFooter;
1549 use std::collections::HashMap;
1550
1551 let mut page = Page::a4();
1552
1553 let header = HeaderFooter::new_header("{{company}} - {{title}}");
1554 page.set_header(header);
1555
1556 let mut custom_values = HashMap::new();
1557 custom_values.insert("company".to_string(), "ACME Corp".to_string());
1558 custom_values.insert("title".to_string(), "Annual Report".to_string());
1559
1560 let content = page
1561 .generate_content_with_page_info(Some(1), Some(1), Some(&custom_values))
1562 .unwrap();
1563 let content_str = String::from_utf8_lossy(&content);
1564 assert!(content_str.contains("ACME Corp - Annual Report"));
1565 }
1566
1567 mod integration_tests {
1569 use super::*;
1570 use crate::document::Document;
1571 use crate::writer::PdfWriter;
1572 use std::fs;
1573 use tempfile::TempDir;
1574
1575 #[test]
1576 fn test_page_document_integration() {
1577 let mut doc = Document::new();
1578 doc.set_title("Page Integration Test");
1579
1580 let page1 = Page::a4();
1582 let page2 = Page::letter();
1583 let mut page3 = Page::new(400.0, 600.0);
1584
1585 page3.set_margins(20.0, 20.0, 20.0, 20.0);
1587 page3
1588 .text()
1589 .set_font(Font::Helvetica, 14.0)
1590 .at(50.0, 550.0)
1591 .write("Custom page content")
1592 .unwrap();
1593
1594 doc.add_page(page1);
1595 doc.add_page(page2);
1596 doc.add_page(page3);
1597
1598 assert_eq!(doc.pages.len(), 3);
1599
1600 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();
1607 let content = page_copy.generate_content().unwrap();
1608 assert!(!content.is_empty());
1609 }
1610
1611 #[test]
1612 fn test_page_writer_integration() {
1613 let temp_dir = TempDir::new().unwrap();
1614 let file_path = temp_dir.path().join("page_writer_test.pdf");
1615
1616 let mut doc = Document::new();
1617 doc.set_title("Page Writer Integration");
1618
1619 let mut page = Page::a4();
1621 page.set_margins(50.0, 50.0, 50.0, 50.0);
1622
1623 page.text()
1625 .set_font(Font::Helvetica, 16.0)
1626 .at(100.0, 750.0)
1627 .write("Integration Test Header")
1628 .unwrap();
1629
1630 page.text()
1631 .set_font(Font::TimesRoman, 12.0)
1632 .at(100.0, 700.0)
1633 .write("This is body text for the integration test.")
1634 .unwrap();
1635
1636 page.graphics()
1638 .set_fill_color(Color::rgb(0.2, 0.6, 0.9))
1639 .rect(100.0, 600.0, 200.0, 50.0)
1640 .fill();
1641
1642 page.graphics()
1643 .set_stroke_color(Color::rgb(0.8, 0.2, 0.2))
1644 .set_line_width(3.0)
1645 .circle(300.0, 500.0, 40.0)
1646 .stroke();
1647
1648 doc.add_page(page);
1649
1650 let mut writer = PdfWriter::new(&file_path).unwrap();
1652 writer.write_document(&mut doc).unwrap();
1653
1654 assert!(file_path.exists());
1656 let metadata = fs::metadata(&file_path).unwrap();
1657 assert!(metadata.len() > 1000); let content = fs::read(&file_path).unwrap();
1661 let content_str = String::from_utf8_lossy(&content);
1662 assert!(content_str.contains("obj")); assert!(content_str.contains("stream")); }
1665
1666 #[test]
1667 fn test_page_margins_integration() {
1668 let temp_dir = TempDir::new().unwrap();
1669 let file_path = temp_dir.path().join("margins_test.pdf");
1670
1671 let mut doc = Document::new();
1672 doc.set_title("Margins Integration Test");
1673
1674 let mut page1 = Page::a4();
1676 page1.set_margins(10.0, 20.0, 30.0, 40.0);
1677
1678 let mut page2 = Page::letter();
1679 page2.set_margins(72.0, 72.0, 72.0, 72.0); let mut page3 = Page::new(500.0, 700.0);
1682 page3.set_margins(0.0, 0.0, 0.0, 0.0); for (i, page) in [&mut page1, &mut page2, &mut page3].iter_mut().enumerate() {
1686 let (left, bottom, right, top) = page.content_area();
1687
1688 page.text()
1690 .set_font(Font::Helvetica, 10.0)
1691 .at(left, top - 20.0)
1692 .write(&format!(
1693 "Page {} - Content area: ({:.1}, {:.1}, {:.1}, {:.1})",
1694 i + 1,
1695 left,
1696 bottom,
1697 right,
1698 top
1699 ))
1700 .unwrap();
1701
1702 page.graphics()
1704 .set_stroke_color(Color::rgb(0.5, 0.5, 0.5))
1705 .set_line_width(1.0)
1706 .rect(left, bottom, right - left, top - bottom)
1707 .stroke();
1708 }
1709
1710 doc.add_page(page1);
1711 doc.add_page(page2);
1712 doc.add_page(page3);
1713
1714 let mut writer = PdfWriter::new(&file_path).unwrap();
1716 writer.write_document(&mut doc).unwrap();
1717
1718 assert!(file_path.exists());
1719 let metadata = fs::metadata(&file_path).unwrap();
1720 assert!(metadata.len() > 500); }
1722
1723 #[test]
1724 fn test_page_image_integration() {
1725 let temp_dir = TempDir::new().unwrap();
1726 let file_path = temp_dir.path().join("image_test.pdf");
1727
1728 let mut doc = Document::new();
1729 doc.set_title("Image Integration Test");
1730
1731 let mut page = Page::a4();
1732
1733 let jpeg_data1 = vec![
1735 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0xFF, 0xD9,
1736 ];
1737 let image1 = Image::from_jpeg_data(jpeg_data1).unwrap();
1738
1739 let jpeg_data2 = vec![
1740 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x32, 0x01, 0xFF, 0xD9,
1741 ];
1742 let image2 = Image::from_jpeg_data(jpeg_data2).unwrap();
1743
1744 page.add_image("image1", image1);
1746 page.add_image("image2", image2);
1747
1748 page.draw_image("image1", 100.0, 600.0, 200.0, 100.0)
1750 .unwrap();
1751 page.draw_image("image2", 350.0, 600.0, 50.0, 50.0).unwrap();
1752
1753 page.text()
1755 .set_font(Font::Helvetica, 12.0)
1756 .at(100.0, 580.0)
1757 .write("Image 1 (200x100)")
1758 .unwrap();
1759
1760 page.text()
1761 .set_font(Font::Helvetica, 12.0)
1762 .at(350.0, 580.0)
1763 .write("Image 2 (50x50)")
1764 .unwrap();
1765
1766 assert_eq!(page.images().len(), 2, "Two images should be added to page");
1768
1769 doc.add_page(page);
1770
1771 let mut writer = PdfWriter::new(&file_path).unwrap();
1773 writer.write_document(&mut doc).unwrap();
1774
1775 assert!(file_path.exists());
1776 let metadata = fs::metadata(&file_path).unwrap();
1777 assert!(metadata.len() > 500); let content = fs::read(&file_path).unwrap();
1781 let content_str = String::from_utf8_lossy(&content);
1782
1783 tracing::debug!("PDF size: {} bytes", content.len());
1785 tracing::debug!("Contains 'XObject': {}", content_str.contains("XObject"));
1786 tracing::debug!("Contains '/XObject': {}", content_str.contains("/XObject"));
1787
1788 if content_str.contains("/Type /Image") || content_str.contains("DCTDecode") {
1790 tracing::debug!("Found image-related content but no XObject dictionary");
1791 }
1792
1793 assert!(content_str.contains("XObject"));
1795 }
1796
1797 #[test]
1798 fn test_page_text_flow_integration() {
1799 let temp_dir = TempDir::new().unwrap();
1800 let file_path = temp_dir.path().join("text_flow_test.pdf");
1801
1802 let mut doc = Document::new();
1803 doc.set_title("Text Flow Integration Test");
1804
1805 let mut page = Page::a4();
1806 page.set_margins(50.0, 50.0, 50.0, 50.0);
1807
1808 let mut text_flow = page.text_flow();
1810 text_flow.set_font(Font::TimesRoman, 12.0);
1811 text_flow.at(100.0, 700.0);
1812
1813 let long_text =
1814 "This is a long paragraph that should demonstrate text flow capabilities. "
1815 .repeat(10);
1816 text_flow.write_wrapped(&long_text).unwrap();
1817
1818 page.add_text_flow(&text_flow);
1820
1821 page.text()
1823 .set_font(Font::Helvetica, 14.0)
1824 .at(100.0, 750.0)
1825 .write("Regular Text Above Text Flow")
1826 .unwrap();
1827
1828 doc.add_page(page);
1829
1830 let mut writer = PdfWriter::new(&file_path).unwrap();
1832 writer.write_document(&mut doc).unwrap();
1833
1834 assert!(file_path.exists());
1835 let metadata = fs::metadata(&file_path).unwrap();
1836 assert!(metadata.len() > 1000); let content = fs::read(&file_path).unwrap();
1840 let content_str = String::from_utf8_lossy(&content);
1841 assert!(content_str.contains("obj")); assert!(content_str.contains("stream")); }
1844
1845 #[test]
1846 fn test_page_complex_content_integration() {
1847 let temp_dir = TempDir::new().unwrap();
1848 let file_path = temp_dir.path().join("complex_content_test.pdf");
1849
1850 let mut doc = Document::new();
1851 doc.set_title("Complex Content Integration Test");
1852
1853 let mut page = Page::a4();
1854 page.set_margins(40.0, 40.0, 40.0, 40.0);
1855
1856 page.graphics()
1860 .set_fill_color(Color::rgb(0.95, 0.95, 0.95))
1861 .rect(50.0, 50.0, 495.0, 742.0)
1862 .fill();
1863
1864 page.graphics()
1866 .set_fill_color(Color::rgb(0.2, 0.4, 0.8))
1867 .rect(50.0, 750.0, 495.0, 42.0)
1868 .fill();
1869
1870 page.text()
1871 .set_font(Font::HelveticaBold, 18.0)
1872 .at(60.0, 765.0)
1873 .write("Complex Content Integration Test")
1874 .unwrap();
1875
1876 let mut y_pos = 700.0;
1878 for i in 1..=3 {
1879 page.graphics()
1881 .set_fill_color(Color::rgb(0.8, 0.8, 0.9))
1882 .rect(60.0, y_pos, 475.0, 20.0)
1883 .fill();
1884
1885 page.text()
1886 .set_font(Font::HelveticaBold, 12.0)
1887 .at(70.0, y_pos + 5.0)
1888 .write(&format!("Section {i}"))
1889 .unwrap();
1890
1891 y_pos -= 30.0;
1892
1893 page.text()
1895 .set_font(Font::TimesRoman, 10.0)
1896 .at(70.0, y_pos)
1897 .write(&format!(
1898 "This is the content for section {i}. It demonstrates mixed content."
1899 ))
1900 .unwrap();
1901
1902 page.graphics()
1904 .set_stroke_color(Color::rgb(0.6, 0.2, 0.2))
1905 .set_line_width(2.0)
1906 .move_to(70.0, y_pos - 10.0)
1907 .line_to(530.0, y_pos - 10.0)
1908 .stroke();
1909
1910 y_pos -= 50.0;
1911 }
1912
1913 page.graphics()
1915 .set_fill_color(Color::rgb(0.3, 0.3, 0.3))
1916 .rect(50.0, 50.0, 495.0, 30.0)
1917 .fill();
1918
1919 page.text()
1920 .set_font(Font::Helvetica, 10.0)
1921 .at(60.0, 60.0)
1922 .write("Generated by oxidize-pdf integration test")
1923 .unwrap();
1924
1925 doc.add_page(page);
1926
1927 let mut writer = PdfWriter::new(&file_path).unwrap();
1929 writer.write_document(&mut doc).unwrap();
1930
1931 assert!(file_path.exists());
1932 let metadata = fs::metadata(&file_path).unwrap();
1933 assert!(metadata.len() > 500); let content = fs::read(&file_path).unwrap();
1937 let content_str = String::from_utf8_lossy(&content);
1938 assert!(content_str.contains("obj")); assert!(content_str.contains("stream")); assert!(content_str.contains("endobj")); }
1942
1943 #[test]
1944 fn test_page_content_generation_performance() {
1945 let mut page = Page::a4();
1946
1947 for i in 0..100 {
1949 let y = 800.0 - (i as f64 * 7.0);
1950 if y > 50.0 {
1951 page.text()
1952 .set_font(Font::Helvetica, 8.0)
1953 .at(50.0, y)
1954 .write(&format!("Performance test line {i}"))
1955 .unwrap();
1956 }
1957 }
1958
1959 for i in 0..50 {
1961 let x = 50.0 + (i as f64 * 10.0);
1962 if x < 550.0 {
1963 page.graphics()
1964 .set_fill_color(Color::rgb(0.5, 0.5, 0.8))
1965 .rect(x, 400.0, 8.0, 8.0)
1966 .fill();
1967 }
1968 }
1969
1970 let start = std::time::Instant::now();
1972 let content = page.generate_content().unwrap();
1973 let duration = start.elapsed();
1974
1975 assert!(!content.is_empty());
1976 assert!(duration.as_millis() < 1000); }
1978
1979 #[test]
1980 fn test_page_error_handling() {
1981 let mut page = Page::a4();
1982
1983 let result = page.draw_image("nonexistent", 100.0, 100.0, 50.0, 50.0);
1985 assert!(result.is_err());
1986
1987 let result = page.draw_image("still_nonexistent", -100.0, -100.0, 0.0, 0.0);
1989 assert!(result.is_err());
1990
1991 let jpeg_data = vec![
1993 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x32, 0x01, 0xFF, 0xD9,
1994 ];
1995 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1996 page.add_image("valid_image", image);
1997
1998 let result = page.draw_image("valid_image", 100.0, 100.0, 50.0, 50.0);
1999 assert!(result.is_ok());
2000 }
2001
2002 #[test]
2003 fn test_page_memory_management() {
2004 let mut pages = Vec::new();
2005
2006 for i in 0..100 {
2008 let mut page = Page::a4();
2009 page.set_margins(i as f64, i as f64, i as f64, i as f64);
2010
2011 page.text()
2012 .set_font(Font::Helvetica, 12.0)
2013 .at(100.0, 700.0)
2014 .write(&format!("Page {i}"))
2015 .unwrap();
2016
2017 pages.push(page);
2018 }
2019
2020 assert_eq!(pages.len(), 100);
2022
2023 for page in pages.iter_mut() {
2025 let content = page.generate_content().unwrap();
2026 assert!(!content.is_empty());
2027 }
2028 }
2029
2030 #[test]
2031 fn test_page_standard_sizes() {
2032 let a4 = Page::a4();
2033 let letter = Page::letter();
2034 let custom = Page::new(200.0, 300.0);
2035
2036 assert_eq!(a4.width(), 595.0);
2038 assert_eq!(a4.height(), 842.0);
2039 assert_eq!(letter.width(), 612.0);
2040 assert_eq!(letter.height(), 792.0);
2041 assert_eq!(custom.width(), 200.0);
2042 assert_eq!(custom.height(), 300.0);
2043
2044 let a4_content_width = a4.content_width();
2046 let letter_content_width = letter.content_width();
2047 let custom_content_width = custom.content_width();
2048
2049 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); }
2053
2054 #[test]
2055 fn test_header_footer_document_integration() {
2056 use crate::text::{HeaderFooter, TextAlign};
2057
2058 let temp_dir = TempDir::new().unwrap();
2059 let file_path = temp_dir.path().join("header_footer_test.pdf");
2060
2061 let mut doc = Document::new();
2062 doc.set_title("Header Footer Integration Test");
2063
2064 for i in 1..=3 {
2066 let mut page = Page::a4();
2067
2068 let header = HeaderFooter::new_header(format!("Chapter {i}"))
2070 .with_font(Font::HelveticaBold, 16.0)
2071 .with_alignment(TextAlign::Center);
2072 page.set_header(header);
2073
2074 let footer = HeaderFooter::new_footer("Page {{page_number}} of {{total_pages}}")
2076 .with_font(Font::Helvetica, 10.0)
2077 .with_alignment(TextAlign::Center);
2078 page.set_footer(footer);
2079
2080 page.text()
2082 .set_font(Font::TimesRoman, 12.0)
2083 .at(100.0, 700.0)
2084 .write(&format!("This is the content of chapter {i}"))
2085 .unwrap();
2086
2087 doc.add_page(page);
2088 }
2089
2090 let mut writer = PdfWriter::new(&file_path).unwrap();
2092 writer.write_document(&mut doc).unwrap();
2093
2094 assert!(file_path.exists());
2096 let metadata = fs::metadata(&file_path).unwrap();
2097 assert!(metadata.len() > 1000);
2098
2099 let content = fs::read(&file_path).unwrap();
2101 let content_str = String::from_utf8_lossy(&content);
2102
2103 assert!(content.len() > 2000);
2105 assert!(content_str.contains("%PDF"));
2107 assert!(content_str.contains("endobj"));
2108
2109 }
2112
2113 #[test]
2114 fn test_header_footer_alignment_integration() {
2115 use crate::text::{HeaderFooter, TextAlign};
2116
2117 let temp_dir = TempDir::new().unwrap();
2118 let file_path = temp_dir.path().join("alignment_test.pdf");
2119
2120 let mut doc = Document::new();
2121
2122 let mut page = Page::a4();
2123
2124 let header = HeaderFooter::new_header("Left Header")
2126 .with_font(Font::Helvetica, 12.0)
2127 .with_alignment(TextAlign::Left)
2128 .with_margin(50.0);
2129 page.set_header(header);
2130
2131 let footer = HeaderFooter::new_footer("Right Footer - Page {{page_number}}")
2133 .with_font(Font::Helvetica, 10.0)
2134 .with_alignment(TextAlign::Right)
2135 .with_margin(50.0);
2136 page.set_footer(footer);
2137
2138 doc.add_page(page);
2139
2140 let mut writer = PdfWriter::new(&file_path).unwrap();
2142 writer.write_document(&mut doc).unwrap();
2143
2144 assert!(file_path.exists());
2145 }
2146
2147 #[test]
2148 fn test_header_footer_date_time_integration() {
2149 use crate::text::HeaderFooter;
2150
2151 let temp_dir = TempDir::new().unwrap();
2152 let file_path = temp_dir.path().join("date_time_test.pdf");
2153
2154 let mut doc = Document::new();
2155
2156 let mut page = Page::a4();
2157
2158 let header = HeaderFooter::new_header("Report generated on {{date}} at {{time}}")
2160 .with_font(Font::Helvetica, 11.0);
2161 page.set_header(header);
2162
2163 let footer =
2165 HeaderFooter::new_footer("© {{year}} Company Name").with_font(Font::Helvetica, 9.0);
2166 page.set_footer(footer);
2167
2168 doc.add_page(page);
2169
2170 let mut writer = PdfWriter::new(&file_path).unwrap();
2172 writer.write_document(&mut doc).unwrap();
2173
2174 assert!(file_path.exists());
2175
2176 let content = fs::read(&file_path).unwrap();
2178 assert!(content.len() > 500);
2179
2180 let content_str = String::from_utf8_lossy(&content);
2182 assert!(content_str.contains("%PDF"));
2183 assert!(content_str.contains("endobj"));
2184
2185 }
2188 }
2189}
2190
2191#[cfg(test)]
2192mod unit_tests {
2193 use super::*;
2194 use crate::graphics::Color;
2195 use crate::text::Font;
2196
2197 #[test]
2200 fn test_new_page_dimensions() {
2201 let page = Page::new(100.0, 200.0);
2202 assert_eq!(page.width(), 100.0);
2203 assert_eq!(page.height(), 200.0);
2204 }
2205
2206 #[test]
2207 fn test_a4_page_dimensions() {
2208 let page = Page::a4();
2209 assert_eq!(page.width(), 595.0);
2210 assert_eq!(page.height(), 842.0);
2211 }
2212
2213 #[test]
2214 fn test_letter_page_dimensions() {
2215 let page = Page::letter();
2216 assert_eq!(page.width(), 612.0);
2217 assert_eq!(page.height(), 792.0);
2218 }
2219
2220 #[test]
2221 fn test_legal_page_dimensions() {
2222 let page = Page::legal();
2223 assert_eq!(page.width(), 612.0);
2224 assert_eq!(page.height(), 1008.0);
2225 }
2226
2227 #[test]
2230 fn test_default_margins() {
2231 let page = Page::a4();
2232 let margins = page.margins();
2233 assert_eq!(margins.left, 72.0);
2234 assert_eq!(margins.right, 72.0);
2235 assert_eq!(margins.top, 72.0);
2236 assert_eq!(margins.bottom, 72.0);
2237 }
2238
2239 #[test]
2240 fn test_set_margins() {
2241 let mut page = Page::a4();
2242 page.set_margins(10.0, 20.0, 30.0, 40.0);
2243
2244 let margins = page.margins();
2245 assert_eq!(margins.left, 10.0);
2246 assert_eq!(margins.right, 20.0);
2247 assert_eq!(margins.top, 30.0);
2248 assert_eq!(margins.bottom, 40.0);
2249 }
2250
2251 #[test]
2252 fn test_content_width() {
2253 let mut page = Page::new(600.0, 800.0);
2254 page.set_margins(50.0, 50.0, 0.0, 0.0);
2255 assert_eq!(page.content_width(), 500.0);
2256 }
2257
2258 #[test]
2259 fn test_content_height() {
2260 let mut page = Page::new(600.0, 800.0);
2261 page.set_margins(0.0, 0.0, 100.0, 100.0);
2262 assert_eq!(page.content_height(), 600.0);
2263 }
2264
2265 #[test]
2266 fn test_content_area() {
2267 let mut page = Page::new(600.0, 800.0);
2268 page.set_margins(50.0, 60.0, 70.0, 80.0);
2269
2270 let (x, y, right, top) = page.content_area();
2271 assert_eq!(x, 50.0); assert_eq!(y, 80.0); assert_eq!(right, 540.0); assert_eq!(top, 730.0); }
2276
2277 #[test]
2280 fn test_graphics_context_access() {
2281 let mut page = Page::a4();
2282 let gc = page.graphics();
2283
2284 gc.move_to(0.0, 0.0);
2286 gc.line_to(100.0, 100.0);
2287
2288 let ops = gc.get_operations();
2290 assert!(!ops.is_empty());
2291 }
2292
2293 #[test]
2294 fn test_graphics_operations_chain() {
2295 let mut page = Page::a4();
2296
2297 page.graphics()
2298 .set_fill_color(Color::red())
2299 .rectangle(10.0, 10.0, 100.0, 50.0)
2300 .fill();
2301
2302 let ops = page.graphics().get_operations();
2303 assert!(ops.contains("re")); assert!(ops.contains("f")); }
2306
2307 #[test]
2310 fn test_text_context_access() {
2311 let mut page = Page::a4();
2312 let tc = page.text();
2313
2314 tc.set_font(Font::Helvetica, 12.0);
2315 tc.at(100.0, 100.0);
2316
2317 let result = tc.write("Test text");
2319 assert!(result.is_ok());
2320 }
2321
2322 #[test]
2324 fn test_get_used_characters_from_text_context() {
2325 let mut page = Page::a4();
2326
2327 page.text().write("ABC").unwrap();
2329
2330 let chars = page.get_used_characters();
2332 assert!(chars.is_some());
2333 let chars = chars.unwrap();
2334 assert!(chars.contains(&'A'));
2335 assert!(chars.contains(&'B'));
2336 assert!(chars.contains(&'C'));
2337 }
2338
2339 #[test]
2340 fn test_get_used_characters_combines_both_contexts() {
2341 let mut page = Page::a4();
2342
2343 page.text().write("AB").unwrap();
2345
2346 let _ = page.graphics().draw_text("CD", 100.0, 100.0);
2348
2349 let chars = page.get_used_characters();
2351 assert!(chars.is_some());
2352 let chars = chars.unwrap();
2353 assert!(chars.contains(&'A'));
2354 assert!(chars.contains(&'B'));
2355 assert!(chars.contains(&'C'));
2356 assert!(chars.contains(&'D'));
2357 }
2358
2359 #[test]
2360 fn test_get_used_characters_cjk_via_text_context() {
2361 let mut page = Page::a4();
2362
2363 page.text()
2365 .set_font(Font::Custom("NotoSansCJK".to_string()), 12.0);
2366 page.text().write("䏿–‡").unwrap();
2367
2368 let chars = page.get_used_characters();
2369 assert!(chars.is_some());
2370 let chars = chars.unwrap();
2371 assert!(chars.contains(&'ä¸'));
2372 assert!(chars.contains(&'æ–‡'));
2373 }
2374
2375 #[test]
2376 fn test_text_flow_creation() {
2377 let page = Page::a4();
2378 let text_flow = page.text_flow();
2379
2380 let _ = text_flow; }
2385
2386 #[test]
2389 fn test_add_image() {
2390 let mut page = Page::a4();
2391
2392 let image_data = vec![
2394 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x10, 0x00, 0x10, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x00, 0x03, 0x11, 0x00, 0xFF, 0xD9, ];
2406
2407 let image = Image::from_jpeg_data(image_data).unwrap();
2408 page.add_image("test_image", image);
2409
2410 assert!(page.images.contains_key("test_image"));
2412 }
2413
2414 #[test]
2415 fn test_draw_image_simple() {
2416 let mut page = Page::a4();
2417
2418 let image_data = vec![
2420 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x10, 0x00, 0x10, 0x03, 0x01, 0x11,
2421 0x00, 0x02, 0x11, 0x00, 0x03, 0x11, 0x00, 0xFF, 0xD9,
2422 ];
2423
2424 let image = Image::from_jpeg_data(image_data).unwrap();
2425 page.add_image("img1", image);
2426
2427 let result = page.draw_image("img1", 100.0, 100.0, 200.0, 200.0);
2429 assert!(result.is_ok());
2430 }
2431
2432 #[test]
2435 fn test_add_annotation() {
2436 use crate::annotations::{Annotation, AnnotationType};
2437 use crate::geometry::{Point, Rectangle};
2438
2439 let mut page = Page::a4();
2440 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 150.0));
2441 let annotation = Annotation::new(AnnotationType::Text, rect);
2442
2443 page.add_annotation(annotation);
2444 assert_eq!(page.annotations().len(), 1);
2445 }
2446
2447 #[test]
2448 fn test_annotations_mut() {
2449 use crate::annotations::{Annotation, AnnotationType};
2450 use crate::geometry::{Point, Rectangle};
2451
2452 let mut page = Page::a4();
2453 let _rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 150.0));
2454
2455 for i in 0..3 {
2457 let annotation = Annotation::new(
2458 AnnotationType::Text,
2459 Rectangle::new(
2460 Point::new(100.0 + i as f64 * 10.0, 100.0),
2461 Point::new(200.0 + i as f64 * 10.0, 150.0),
2462 ),
2463 );
2464 page.add_annotation(annotation);
2465 }
2466
2467 let annotations = page.annotations_mut();
2469 annotations.clear();
2470 assert_eq!(page.annotations().len(), 0);
2471 }
2472
2473 #[test]
2476 fn test_add_form_widget() {
2477 use crate::forms::Widget;
2478 use crate::geometry::{Point, Rectangle};
2479
2480 let mut page = Page::a4();
2481 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 120.0));
2482 let widget = Widget::new(rect);
2483
2484 let obj_ref = page.add_form_widget(widget);
2485 assert_eq!(obj_ref.number(), 0);
2486 assert_eq!(obj_ref.generation(), 0);
2487
2488 assert_eq!(page.annotations().len(), 1);
2490 }
2491
2492 #[test]
2495 fn test_set_header() {
2496 use crate::text::HeaderFooter;
2497
2498 let mut page = Page::a4();
2499 let header = HeaderFooter::new_header("Test Header");
2500
2501 page.set_header(header);
2502 assert!(page.header().is_some());
2503
2504 if let Some(h) = page.header() {
2505 assert_eq!(h.content(), "Test Header");
2506 }
2507 }
2508
2509 #[test]
2510 fn test_set_footer() {
2511 use crate::text::HeaderFooter;
2512
2513 let mut page = Page::a4();
2514 let footer = HeaderFooter::new_footer("Page {{page}} of {{total}}");
2515
2516 page.set_footer(footer);
2517 assert!(page.footer().is_some());
2518
2519 if let Some(f) = page.footer() {
2520 assert_eq!(f.content(), "Page {{page}} of {{total}}");
2521 }
2522 }
2523
2524 #[test]
2525 fn test_header_footer_rendering() {
2526 use crate::text::HeaderFooter;
2527
2528 let mut page = Page::a4();
2529
2530 page.set_header(HeaderFooter::new_header("Header"));
2532 page.set_footer(HeaderFooter::new_footer("Footer"));
2533
2534 let result = page.generate_content_with_page_info(Some(1), Some(1), None);
2536 assert!(result.is_ok());
2537
2538 let content = result.unwrap();
2539 assert!(!content.is_empty());
2540 }
2541
2542 #[test]
2545 fn test_add_table() {
2546 use crate::text::Table;
2547
2548 let mut page = Page::a4();
2549 let mut table = Table::with_equal_columns(2, 200.0);
2550
2551 table
2553 .add_row(vec!["Cell 1".to_string(), "Cell 2".to_string()])
2554 .unwrap();
2555 table
2556 .add_row(vec!["Cell 3".to_string(), "Cell 4".to_string()])
2557 .unwrap();
2558
2559 let result = page.add_table(&table);
2560 assert!(result.is_ok());
2561 }
2562
2563 #[test]
2566 fn test_generate_operations_empty() {
2567 let page = Page::a4();
2568 let ops = page.graphics_context.generate_operations();
2570
2571 assert!(ops.is_ok());
2573 }
2574
2575 #[test]
2576 fn test_generate_operations_with_graphics() {
2577 let mut page = Page::a4();
2578
2579 page.graphics().rectangle(50.0, 50.0, 100.0, 100.0).fill();
2580
2581 let ops = page.graphics_context.generate_operations();
2583 assert!(ops.is_ok());
2584
2585 let content = ops.unwrap();
2586 let content_str = String::from_utf8_lossy(&content);
2587 assert!(content_str.contains("re")); assert!(content_str.contains("f")); }
2590
2591 #[test]
2592 fn test_generate_operations_with_text() {
2593 let mut page = Page::a4();
2594
2595 page.text()
2596 .set_font(Font::Helvetica, 12.0)
2597 .at(100.0, 700.0)
2598 .write("Hello")
2599 .unwrap();
2600
2601 let ops = page.text_context.generate_operations();
2603 assert!(ops.is_ok());
2604
2605 let content = ops.unwrap();
2606 let content_str = String::from_utf8_lossy(&content);
2607 assert!(content_str.contains("BT")); assert!(content_str.contains("ET")); }
2610
2611 #[test]
2614 fn test_negative_margins() {
2615 let mut page = Page::a4();
2616 page.set_margins(-10.0, -20.0, -30.0, -40.0);
2617
2618 let margins = page.margins();
2620 assert_eq!(margins.left, -10.0);
2621 assert_eq!(margins.right, -20.0);
2622 }
2623
2624 #[test]
2625 fn test_zero_dimensions() {
2626 let page = Page::new(0.0, 0.0);
2627 assert_eq!(page.width(), 0.0);
2628 assert_eq!(page.height(), 0.0);
2629
2630 let (_, _, width, height) = page.content_area();
2632 assert!(width < 0.0);
2633 assert!(height < 0.0);
2634 }
2635
2636 #[test]
2637 fn test_huge_dimensions() {
2638 let page = Page::new(1_000_000.0, 1_000_000.0);
2639 assert_eq!(page.width(), 1_000_000.0);
2640 assert_eq!(page.height(), 1_000_000.0);
2641 }
2642
2643 #[test]
2644 fn test_draw_nonexistent_image() {
2645 let mut page = Page::a4();
2646
2647 let result = page.draw_image("nonexistent", 100.0, 100.0, 200.0, 200.0);
2649
2650 assert!(result.is_err());
2652 }
2653
2654 #[test]
2655 fn test_clone_page() {
2656 let mut page = Page::a4();
2657 page.set_margins(10.0, 20.0, 30.0, 40.0);
2658
2659 page.graphics().rectangle(50.0, 50.0, 100.0, 100.0).fill();
2660
2661 let cloned = page.clone();
2662 assert_eq!(cloned.width(), page.width());
2663 assert_eq!(cloned.height(), page.height());
2664 assert_eq!(cloned.margins().left, page.margins().left);
2665 }
2666
2667 #[test]
2668 fn test_page_from_parsed_basic() {
2669 use crate::parser::objects::PdfDictionary;
2670 use crate::parser::page_tree::ParsedPage;
2671
2672 let parsed_page = ParsedPage {
2674 obj_ref: (1, 0),
2675 dict: PdfDictionary::new(),
2676 inherited_resources: None,
2677 media_box: [0.0, 0.0, 612.0, 792.0], crop_box: None,
2679 rotation: 0,
2680 annotations: None,
2681 };
2682
2683 let page = Page::from_parsed(&parsed_page).unwrap();
2685
2686 assert_eq!(page.width(), 612.0);
2688 assert_eq!(page.height(), 792.0);
2689 assert_eq!(page.get_rotation(), 0);
2690 }
2691
2692 #[test]
2693 fn test_page_from_parsed_with_rotation() {
2694 use crate::parser::objects::PdfDictionary;
2695 use crate::parser::page_tree::ParsedPage;
2696
2697 let parsed_page = ParsedPage {
2699 obj_ref: (1, 0),
2700 dict: PdfDictionary::new(),
2701 inherited_resources: None,
2702 media_box: [0.0, 0.0, 595.0, 842.0], crop_box: None,
2704 rotation: 90,
2705 annotations: None,
2706 };
2707
2708 let page = Page::from_parsed(&parsed_page).unwrap();
2710
2711 assert_eq!(page.get_rotation(), 90);
2713 assert_eq!(page.width(), 595.0);
2714 assert_eq!(page.height(), 842.0);
2715
2716 assert_eq!(page.effective_width(), 842.0);
2718 assert_eq!(page.effective_height(), 595.0);
2719 }
2720
2721 #[test]
2722 fn test_page_from_parsed_with_cropbox() {
2723 use crate::parser::objects::PdfDictionary;
2724 use crate::parser::page_tree::ParsedPage;
2725
2726 let parsed_page = ParsedPage {
2728 obj_ref: (1, 0),
2729 dict: PdfDictionary::new(),
2730 inherited_resources: None,
2731 media_box: [0.0, 0.0, 612.0, 792.0],
2732 crop_box: Some([10.0, 10.0, 602.0, 782.0]),
2733 rotation: 0,
2734 annotations: None,
2735 };
2736
2737 let page = Page::from_parsed(&parsed_page).unwrap();
2739
2740 assert_eq!(page.width(), 612.0);
2742 assert_eq!(page.height(), 792.0);
2743 }
2744
2745 #[test]
2746 fn test_page_from_parsed_small_mediabox() {
2747 use crate::parser::objects::PdfDictionary;
2748 use crate::parser::page_tree::ParsedPage;
2749
2750 let parsed_page = ParsedPage {
2752 obj_ref: (1, 0),
2753 dict: PdfDictionary::new(),
2754 inherited_resources: None,
2755 media_box: [0.0, 0.0, 200.0, 300.0],
2756 crop_box: None,
2757 rotation: 0,
2758 annotations: None,
2759 };
2760
2761 let page = Page::from_parsed(&parsed_page).unwrap();
2763
2764 assert_eq!(page.width(), 200.0);
2765 assert_eq!(page.height(), 300.0);
2766 }
2767
2768 #[test]
2769 fn test_page_from_parsed_non_zero_origin() {
2770 use crate::parser::objects::PdfDictionary;
2771 use crate::parser::page_tree::ParsedPage;
2772
2773 let parsed_page = ParsedPage {
2775 obj_ref: (1, 0),
2776 dict: PdfDictionary::new(),
2777 inherited_resources: None,
2778 media_box: [10.0, 20.0, 610.0, 820.0], crop_box: None,
2780 rotation: 0,
2781 annotations: None,
2782 };
2783
2784 let page = Page::from_parsed(&parsed_page).unwrap();
2786
2787 assert_eq!(page.width(), 600.0); assert_eq!(page.height(), 800.0); }
2791
2792 #[test]
2793 fn test_page_rotation() {
2794 let mut page = Page::a4();
2795
2796 assert_eq!(page.get_rotation(), 0);
2798
2799 page.set_rotation(90);
2801 assert_eq!(page.get_rotation(), 90);
2802
2803 page.set_rotation(180);
2804 assert_eq!(page.get_rotation(), 180);
2805
2806 page.set_rotation(270);
2807 assert_eq!(page.get_rotation(), 270);
2808
2809 page.set_rotation(360);
2810 assert_eq!(page.get_rotation(), 0);
2811
2812 page.set_rotation(45);
2814 assert_eq!(page.get_rotation(), 90);
2815
2816 page.set_rotation(135);
2817 assert_eq!(page.get_rotation(), 180);
2818
2819 page.set_rotation(-90);
2820 assert_eq!(page.get_rotation(), 270);
2821 }
2822
2823 #[test]
2824 fn test_effective_dimensions() {
2825 let mut page = Page::new(600.0, 800.0);
2826
2827 assert_eq!(page.effective_width(), 600.0);
2829 assert_eq!(page.effective_height(), 800.0);
2830
2831 page.set_rotation(90);
2833 assert_eq!(page.effective_width(), 800.0);
2834 assert_eq!(page.effective_height(), 600.0);
2835
2836 page.set_rotation(180);
2838 assert_eq!(page.effective_width(), 600.0);
2839 assert_eq!(page.effective_height(), 800.0);
2840
2841 page.set_rotation(270);
2843 assert_eq!(page.effective_width(), 800.0);
2844 assert_eq!(page.effective_height(), 600.0);
2845 }
2846
2847 #[test]
2848 fn test_rotation_in_pdf_dict() {
2849 let mut page = Page::a4();
2850
2851 let dict = page.to_dict();
2853 assert!(dict.get("Rotate").is_none());
2854
2855 page.set_rotation(90);
2857 let dict = page.to_dict();
2858 assert_eq!(dict.get("Rotate"), Some(&Object::Integer(90)));
2859
2860 page.set_rotation(270);
2861 let dict = page.to_dict();
2862 assert_eq!(dict.get("Rotate"), Some(&Object::Integer(270)));
2863 }
2864}
2865
2866#[derive(Debug)]
2871pub struct LayoutManager {
2872 pub coordinate_system: crate::coordinate_system::CoordinateSystem,
2874 pub current_y: f64,
2876 pub page_width: f64,
2878 pub page_height: f64,
2879 pub margins: Margins,
2881 pub element_spacing: f64,
2883}
2884
2885impl LayoutManager {
2886 pub fn new(page: &Page, coordinate_system: crate::coordinate_system::CoordinateSystem) -> Self {
2888 let current_y = match coordinate_system {
2889 crate::coordinate_system::CoordinateSystem::PdfStandard => {
2890 page.height() - page.margins().top
2892 }
2893 crate::coordinate_system::CoordinateSystem::ScreenSpace => {
2894 page.margins().top
2896 }
2897 crate::coordinate_system::CoordinateSystem::Custom(_) => {
2898 page.height() / 2.0
2900 }
2901 };
2902
2903 Self {
2904 coordinate_system,
2905 current_y,
2906 page_width: page.width(),
2907 page_height: page.height(),
2908 margins: page.margins().clone(),
2909 element_spacing: 10.0,
2910 }
2911 }
2912
2913 pub fn with_element_spacing(mut self, spacing: f64) -> Self {
2915 self.element_spacing = spacing;
2916 self
2917 }
2918
2919 pub fn will_fit(&self, element_height: f64) -> bool {
2921 let required_space = element_height + self.element_spacing;
2922
2923 match self.coordinate_system {
2924 crate::coordinate_system::CoordinateSystem::PdfStandard => {
2925 self.current_y - required_space >= self.margins.bottom
2927 }
2928 crate::coordinate_system::CoordinateSystem::ScreenSpace => {
2929 self.current_y + required_space <= self.page_height - self.margins.bottom
2931 }
2932 crate::coordinate_system::CoordinateSystem::Custom(_) => {
2933 required_space <= (self.page_height - self.margins.top - self.margins.bottom) / 2.0
2935 }
2936 }
2937 }
2938
2939 pub fn remaining_space(&self) -> f64 {
2941 match self.coordinate_system {
2942 crate::coordinate_system::CoordinateSystem::PdfStandard => {
2943 (self.current_y - self.margins.bottom).max(0.0)
2944 }
2945 crate::coordinate_system::CoordinateSystem::ScreenSpace => {
2946 (self.page_height - self.margins.bottom - self.current_y).max(0.0)
2947 }
2948 crate::coordinate_system::CoordinateSystem::Custom(_) => {
2949 self.page_height / 2.0 }
2951 }
2952 }
2953
2954 pub fn add_element(&mut self, element_height: f64) -> Option<f64> {
2960 if !self.will_fit(element_height) {
2961 return None;
2962 }
2963
2964 let position_y = match self.coordinate_system {
2965 crate::coordinate_system::CoordinateSystem::PdfStandard => {
2966 let y_position = self.current_y - element_height;
2969 self.current_y = y_position - self.element_spacing;
2970 self.current_y + element_height }
2972 crate::coordinate_system::CoordinateSystem::ScreenSpace => {
2973 let y_position = self.current_y;
2976 self.current_y += element_height + self.element_spacing;
2977 y_position
2978 }
2979 crate::coordinate_system::CoordinateSystem::Custom(_) => {
2980 let y_position = self.current_y;
2982 self.current_y -= element_height + self.element_spacing;
2983 y_position
2984 }
2985 };
2986
2987 Some(position_y)
2988 }
2989
2990 pub fn new_page(&mut self) {
2992 self.current_y = match self.coordinate_system {
2993 crate::coordinate_system::CoordinateSystem::PdfStandard => {
2994 self.page_height - self.margins.top
2995 }
2996 crate::coordinate_system::CoordinateSystem::ScreenSpace => self.margins.top,
2997 crate::coordinate_system::CoordinateSystem::Custom(_) => self.page_height / 2.0,
2998 };
2999 }
3000
3001 pub fn center_x(&self, element_width: f64) -> f64 {
3003 let available_width = self.page_width - self.margins.left - self.margins.right;
3004 self.margins.left + (available_width - element_width) / 2.0
3005 }
3006
3007 pub fn left_x(&self) -> f64 {
3009 self.margins.left
3010 }
3011
3012 pub fn right_x(&self, element_width: f64) -> f64 {
3014 self.page_width - self.margins.right - element_width
3015 }
3016}
3017
3018#[cfg(test)]
3019mod layout_manager_tests {
3020 use super::*;
3021 use crate::coordinate_system::CoordinateSystem;
3022
3023 #[test]
3024 fn test_layout_manager_pdf_standard() {
3025 let page = Page::a4(); let mut layout = LayoutManager::new(&page, CoordinateSystem::PdfStandard);
3027
3028 assert!(layout.current_y > 750.0); let element_height = 100.0;
3034 let position = layout.add_element(element_height);
3035
3036 assert!(position.is_some());
3037 let y_pos = position.unwrap();
3038 assert!(y_pos > 700.0); assert!(layout.current_y < y_pos);
3042 }
3043
3044 #[test]
3045 fn test_layout_manager_screen_space() {
3046 let page = Page::a4();
3047 let mut layout = LayoutManager::new(&page, CoordinateSystem::ScreenSpace);
3048
3049 assert!(layout.current_y < 100.0); let element_height = 100.0;
3054 let position = layout.add_element(element_height);
3055
3056 assert!(position.is_some());
3057 let y_pos = position.unwrap();
3058 assert!(y_pos < 100.0); assert!(layout.current_y > y_pos);
3062 }
3063
3064 #[test]
3065 fn test_layout_manager_overflow() {
3066 let page = Page::a4();
3067 let mut layout = LayoutManager::new(&page, CoordinateSystem::PdfStandard);
3068
3069 let huge_element = 900.0; let position = layout.add_element(huge_element);
3072
3073 assert!(position.is_none()); let mut count = 0;
3077 while layout.add_element(50.0).is_some() {
3078 count += 1;
3079 if count > 100 {
3080 break;
3081 } }
3083
3084 assert!(count > 5);
3086
3087 assert!(layout.add_element(50.0).is_none());
3089 }
3090
3091 #[test]
3092 fn test_layout_manager_centering() {
3093 let page = Page::a4();
3094 let layout = LayoutManager::new(&page, CoordinateSystem::PdfStandard);
3095
3096 let element_width = 200.0;
3097 let center_x = layout.center_x(element_width);
3098
3099 let expected_center = page.margins().left
3101 + (page.width() - page.margins().left - page.margins().right - element_width) / 2.0;
3102 assert_eq!(center_x, expected_center);
3103 }
3104}