1use crate::error::Result;
2use crate::fonts::{Font as CustomFont, FontCache};
3use crate::forms::{AcroForm, FormManager};
4use crate::objects::{Object, ObjectId};
5use crate::page::Page;
6use crate::page_labels::PageLabelTree;
7use crate::structure::{NamedDestinations, OutlineTree, PageTree};
8use crate::text::{FontEncoding, FontWithEncoding};
9use crate::writer::PdfWriter;
10use chrono::{DateTime, Local, Utc};
11use std::collections::{HashMap, HashSet};
12use std::sync::Arc;
13
14mod encryption;
15pub use encryption::{DocumentEncryption, EncryptionStrength};
16
17pub struct Document {
34 pub(crate) pages: Vec<Page>,
35 #[allow(dead_code)]
36 pub(crate) objects: HashMap<ObjectId, Object>,
37 #[allow(dead_code)]
38 pub(crate) next_object_id: u32,
39 pub(crate) metadata: DocumentMetadata,
40 pub(crate) encryption: Option<DocumentEncryption>,
41 pub(crate) outline: Option<OutlineTree>,
42 pub(crate) named_destinations: Option<NamedDestinations>,
43 #[allow(dead_code)]
44 pub(crate) page_tree: Option<PageTree>,
45 pub(crate) page_labels: Option<PageLabelTree>,
46 pub(crate) default_font_encoding: Option<FontEncoding>,
48 pub(crate) acro_form: Option<AcroForm>,
50 pub(crate) form_manager: Option<FormManager>,
52 pub(crate) compress: bool,
54 pub(crate) custom_fonts: FontCache,
56 #[allow(dead_code)]
58 pub(crate) embedded_fonts: HashMap<String, ObjectId>,
59}
60
61#[derive(Debug, Clone)]
63pub struct DocumentMetadata {
64 pub title: Option<String>,
66 pub author: Option<String>,
68 pub subject: Option<String>,
70 pub keywords: Option<String>,
72 pub creator: Option<String>,
74 pub producer: Option<String>,
76 pub creation_date: Option<DateTime<Utc>>,
78 pub modification_date: Option<DateTime<Utc>>,
80}
81
82impl Default for DocumentMetadata {
83 fn default() -> Self {
84 let now = Utc::now();
85 Self {
86 title: None,
87 author: None,
88 subject: None,
89 keywords: None,
90 creator: Some("oxidize_pdf".to_string()),
91 producer: Some(format!("oxidize_pdf v{}", env!("CARGO_PKG_VERSION"))),
92 creation_date: Some(now),
93 modification_date: Some(now),
94 }
95 }
96}
97
98impl Document {
99 pub fn new() -> Self {
101 Self {
102 pages: Vec::new(),
103 objects: HashMap::new(),
104 next_object_id: 1,
105 metadata: DocumentMetadata::default(),
106 encryption: None,
107 outline: None,
108 named_destinations: None,
109 page_tree: None,
110 page_labels: None,
111 default_font_encoding: None,
112 acro_form: None,
113 form_manager: None,
114 compress: true, custom_fonts: FontCache::new(),
116 embedded_fonts: HashMap::new(),
117 }
118 }
119
120 pub fn add_page(&mut self, page: Page) {
122 self.pages.push(page);
123 }
124
125 pub fn set_title(&mut self, title: impl Into<String>) {
127 self.metadata.title = Some(title.into());
128 }
129
130 pub fn set_author(&mut self, author: impl Into<String>) {
132 self.metadata.author = Some(author.into());
133 }
134
135 pub fn set_subject(&mut self, subject: impl Into<String>) {
137 self.metadata.subject = Some(subject.into());
138 }
139
140 pub fn set_keywords(&mut self, keywords: impl Into<String>) {
142 self.metadata.keywords = Some(keywords.into());
143 }
144
145 pub fn set_encryption(&mut self, encryption: DocumentEncryption) {
147 self.encryption = Some(encryption);
148 }
149
150 pub fn encrypt_with_passwords(
152 &mut self,
153 user_password: impl Into<String>,
154 owner_password: impl Into<String>,
155 ) {
156 self.encryption = Some(DocumentEncryption::with_passwords(
157 user_password,
158 owner_password,
159 ));
160 }
161
162 pub fn is_encrypted(&self) -> bool {
164 self.encryption.is_some()
165 }
166
167 pub fn set_outline(&mut self, outline: OutlineTree) {
169 self.outline = Some(outline);
170 }
171
172 pub fn outline(&self) -> Option<&OutlineTree> {
174 self.outline.as_ref()
175 }
176
177 pub fn outline_mut(&mut self) -> Option<&mut OutlineTree> {
179 self.outline.as_mut()
180 }
181
182 pub fn set_named_destinations(&mut self, destinations: NamedDestinations) {
184 self.named_destinations = Some(destinations);
185 }
186
187 pub fn named_destinations(&self) -> Option<&NamedDestinations> {
189 self.named_destinations.as_ref()
190 }
191
192 pub fn named_destinations_mut(&mut self) -> Option<&mut NamedDestinations> {
194 self.named_destinations.as_mut()
195 }
196
197 pub fn set_page_labels(&mut self, labels: PageLabelTree) {
199 self.page_labels = Some(labels);
200 }
201
202 pub fn page_labels(&self) -> Option<&PageLabelTree> {
204 self.page_labels.as_ref()
205 }
206
207 pub fn page_labels_mut(&mut self) -> Option<&mut PageLabelTree> {
209 self.page_labels.as_mut()
210 }
211
212 pub fn get_page_label(&self, page_index: u32) -> String {
214 self.page_labels
215 .as_ref()
216 .and_then(|labels| labels.get_label(page_index))
217 .unwrap_or_else(|| (page_index + 1).to_string())
218 }
219
220 pub fn get_all_page_labels(&self) -> Vec<String> {
222 let page_count = self.pages.len() as u32;
223 if let Some(labels) = &self.page_labels {
224 labels.get_all_labels(page_count)
225 } else {
226 (1..=page_count).map(|i| i.to_string()).collect()
227 }
228 }
229
230 pub fn set_creator(&mut self, creator: impl Into<String>) {
232 self.metadata.creator = Some(creator.into());
233 }
234
235 pub fn set_producer(&mut self, producer: impl Into<String>) {
237 self.metadata.producer = Some(producer.into());
238 }
239
240 pub fn set_creation_date(&mut self, date: DateTime<Utc>) {
242 self.metadata.creation_date = Some(date);
243 }
244
245 pub fn set_creation_date_local(&mut self, date: DateTime<Local>) {
247 self.metadata.creation_date = Some(date.with_timezone(&Utc));
248 }
249
250 pub fn set_modification_date(&mut self, date: DateTime<Utc>) {
252 self.metadata.modification_date = Some(date);
253 }
254
255 pub fn set_modification_date_local(&mut self, date: DateTime<Local>) {
257 self.metadata.modification_date = Some(date.with_timezone(&Utc));
258 }
259
260 pub fn update_modification_date(&mut self) {
262 self.metadata.modification_date = Some(Utc::now());
263 }
264
265 pub fn set_default_font_encoding(&mut self, encoding: Option<FontEncoding>) {
280 self.default_font_encoding = encoding;
281 }
282
283 pub fn default_font_encoding(&self) -> Option<FontEncoding> {
285 self.default_font_encoding
286 }
287
288 pub(crate) fn get_fonts_with_encodings(&self) -> Vec<FontWithEncoding> {
293 let mut fonts_used = HashSet::new();
294
295 for page in &self.pages {
297 for font in page.get_used_fonts() {
299 let font_with_encoding = match self.default_font_encoding {
300 Some(default_encoding) => FontWithEncoding::new(font, Some(default_encoding)),
301 None => FontWithEncoding::without_encoding(font),
302 };
303 fonts_used.insert(font_with_encoding);
304 }
305 }
306
307 fonts_used.into_iter().collect()
308 }
309
310 pub fn add_font(
321 &mut self,
322 name: impl Into<String>,
323 path: impl AsRef<std::path::Path>,
324 ) -> Result<()> {
325 let name = name.into();
326 let font = CustomFont::from_file(&name, path)?;
327 self.custom_fonts.add_font(name, font)?;
328 Ok(())
329 }
330
331 pub fn add_font_from_bytes(&mut self, name: impl Into<String>, data: Vec<u8>) -> Result<()> {
343 let name = name.into();
344 let font = CustomFont::from_bytes(&name, data)?;
345 self.custom_fonts.add_font(name, font)?;
346 Ok(())
347 }
348
349 #[allow(dead_code)]
351 pub(crate) fn get_custom_font(&self, name: &str) -> Option<Arc<CustomFont>> {
352 self.custom_fonts.get_font(name)
353 }
354
355 pub fn has_custom_font(&self, name: &str) -> bool {
357 self.custom_fonts.has_font(name)
358 }
359
360 pub fn custom_font_names(&self) -> Vec<String> {
362 self.custom_fonts.font_names()
363 }
364
365 pub fn page_count(&self) -> usize {
367 self.pages.len()
368 }
369
370 pub fn acro_form(&self) -> Option<&AcroForm> {
372 self.acro_form.as_ref()
373 }
374
375 pub fn acro_form_mut(&mut self) -> Option<&mut AcroForm> {
377 self.acro_form.as_mut()
378 }
379
380 pub fn enable_forms(&mut self) -> &mut FormManager {
383 if self.form_manager.is_none() {
384 self.form_manager = Some(FormManager::new());
385 }
386 if self.acro_form.is_none() {
387 self.acro_form = Some(AcroForm::new());
388 }
389 self.form_manager.as_mut().unwrap()
390 }
391
392 pub fn disable_forms(&mut self) {
394 self.acro_form = None;
395 self.form_manager = None;
396 }
397
398 pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
404 self.update_modification_date();
406
407 let config = crate::writer::WriterConfig {
409 use_xref_streams: false,
410 pdf_version: "1.7".to_string(),
411 compress_streams: self.compress,
412 };
413
414 use std::io::BufWriter;
415 let file = std::fs::File::create(path)?;
416 let writer = BufWriter::new(file);
417 let mut pdf_writer = PdfWriter::with_config(writer, config);
418
419 pdf_writer.write_document(self)?;
420 Ok(())
421 }
422
423 pub fn save_with_config(
429 &mut self,
430 path: impl AsRef<std::path::Path>,
431 config: crate::writer::WriterConfig,
432 ) -> Result<()> {
433 use std::io::BufWriter;
434
435 self.update_modification_date();
437
438 let file = std::fs::File::create(path)?;
441 let writer = BufWriter::new(file);
442 let mut pdf_writer = PdfWriter::with_config(writer, config);
443 pdf_writer.write_document(self)?;
444 Ok(())
445 }
446
447 pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
453 self.update_modification_date();
455
456 let mut writer = PdfWriter::new_with_writer(buffer);
457 writer.write_document(self)?;
458 Ok(())
459 }
460
461 #[allow(dead_code)]
462 pub(crate) fn allocate_object_id(&mut self) -> ObjectId {
463 let id = ObjectId::new(self.next_object_id, 0);
464 self.next_object_id += 1;
465 id
466 }
467
468 #[allow(dead_code)]
469 pub(crate) fn add_object(&mut self, obj: Object) -> ObjectId {
470 let id = self.allocate_object_id();
471 self.objects.insert(id, obj);
472 id
473 }
474
475 pub fn set_compress(&mut self, compress: bool) {
502 self.compress = compress;
503 }
504
505 pub fn get_compress(&self) -> bool {
511 self.compress
512 }
513
514 pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
542 self.update_modification_date();
544
545 let mut buffer = Vec::new();
547
548 let config = crate::writer::WriterConfig {
550 use_xref_streams: false,
551 pdf_version: "1.7".to_string(),
552 compress_streams: self.compress,
553 };
554
555 let mut writer = PdfWriter::with_config(&mut buffer, config);
557 writer.write_document(self)?;
558
559 Ok(buffer)
560 }
561
562 pub fn to_bytes_with_config(&mut self, config: crate::writer::WriterConfig) -> Result<Vec<u8>> {
601 self.update_modification_date();
603
604 let mut buffer = Vec::new();
608
609 let mut writer = PdfWriter::with_config(&mut buffer, config);
611 writer.write_document(self)?;
612
613 Ok(buffer)
614 }
615}
616
617impl Default for Document {
618 fn default() -> Self {
619 Self::new()
620 }
621}
622
623#[cfg(test)]
624mod tests {
625 use super::*;
626
627 #[test]
628 fn test_document_new() {
629 let doc = Document::new();
630 assert!(doc.pages.is_empty());
631 assert!(doc.objects.is_empty());
632 assert_eq!(doc.next_object_id, 1);
633 assert!(doc.metadata.title.is_none());
634 assert!(doc.metadata.author.is_none());
635 assert!(doc.metadata.subject.is_none());
636 assert!(doc.metadata.keywords.is_none());
637 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
638 assert!(doc
639 .metadata
640 .producer
641 .as_ref()
642 .unwrap()
643 .starts_with("oxidize_pdf"));
644 }
645
646 #[test]
647 fn test_document_default() {
648 let doc = Document::default();
649 assert!(doc.pages.is_empty());
650 assert_eq!(doc.next_object_id, 1);
651 }
652
653 #[test]
654 fn test_add_page() {
655 let mut doc = Document::new();
656 let page1 = Page::a4();
657 let page2 = Page::letter();
658
659 doc.add_page(page1);
660 assert_eq!(doc.pages.len(), 1);
661
662 doc.add_page(page2);
663 assert_eq!(doc.pages.len(), 2);
664 }
665
666 #[test]
667 fn test_set_title() {
668 let mut doc = Document::new();
669 assert!(doc.metadata.title.is_none());
670
671 doc.set_title("Test Document");
672 assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
673
674 doc.set_title(String::from("Another Title"));
675 assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
676 }
677
678 #[test]
679 fn test_set_author() {
680 let mut doc = Document::new();
681 assert!(doc.metadata.author.is_none());
682
683 doc.set_author("John Doe");
684 assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
685 }
686
687 #[test]
688 fn test_set_subject() {
689 let mut doc = Document::new();
690 assert!(doc.metadata.subject.is_none());
691
692 doc.set_subject("Test Subject");
693 assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
694 }
695
696 #[test]
697 fn test_set_keywords() {
698 let mut doc = Document::new();
699 assert!(doc.metadata.keywords.is_none());
700
701 doc.set_keywords("test, pdf, rust");
702 assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
703 }
704
705 #[test]
706 fn test_metadata_default() {
707 let metadata = DocumentMetadata::default();
708 assert!(metadata.title.is_none());
709 assert!(metadata.author.is_none());
710 assert!(metadata.subject.is_none());
711 assert!(metadata.keywords.is_none());
712 assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
713 assert!(metadata
714 .producer
715 .as_ref()
716 .unwrap()
717 .starts_with("oxidize_pdf"));
718 }
719
720 #[test]
721 fn test_allocate_object_id() {
722 let mut doc = Document::new();
723
724 let id1 = doc.allocate_object_id();
725 assert_eq!(id1.number(), 1);
726 assert_eq!(id1.generation(), 0);
727 assert_eq!(doc.next_object_id, 2);
728
729 let id2 = doc.allocate_object_id();
730 assert_eq!(id2.number(), 2);
731 assert_eq!(id2.generation(), 0);
732 assert_eq!(doc.next_object_id, 3);
733 }
734
735 #[test]
736 fn test_add_object() {
737 let mut doc = Document::new();
738 assert!(doc.objects.is_empty());
739
740 let obj = Object::Boolean(true);
741 let id = doc.add_object(obj.clone());
742
743 assert_eq!(id.number(), 1);
744 assert_eq!(doc.objects.len(), 1);
745 assert!(doc.objects.contains_key(&id));
746 }
747
748 #[test]
749 fn test_write_to_buffer() {
750 let mut doc = Document::new();
751 doc.set_title("Buffer Test");
752 doc.add_page(Page::a4());
753
754 let mut buffer = Vec::new();
755 let result = doc.write(&mut buffer);
756
757 assert!(result.is_ok());
758 assert!(!buffer.is_empty());
759 assert!(buffer.starts_with(b"%PDF-1.7"));
760 }
761
762 #[test]
763 fn test_document_with_multiple_pages() {
764 let mut doc = Document::new();
765 doc.set_title("Multi-page Document");
766 doc.set_author("Test Author");
767 doc.set_subject("Testing multiple pages");
768 doc.set_keywords("test, multiple, pages");
769
770 for _ in 0..5 {
771 doc.add_page(Page::a4());
772 }
773
774 assert_eq!(doc.pages.len(), 5);
775 assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
776 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
777 }
778
779 #[test]
780 fn test_empty_document_write() {
781 let mut doc = Document::new();
782 let mut buffer = Vec::new();
783
784 let result = doc.write(&mut buffer);
786 assert!(result.is_ok());
787 assert!(!buffer.is_empty());
788 assert!(buffer.starts_with(b"%PDF-1.7"));
789 }
790
791 mod integration_tests {
793 use super::*;
794 use crate::graphics::Color;
795 use crate::text::Font;
796 use std::fs;
797 use tempfile::TempDir;
798
799 #[test]
800 fn test_document_writer_roundtrip() {
801 let temp_dir = TempDir::new().unwrap();
802 let file_path = temp_dir.path().join("test.pdf");
803
804 let mut doc = Document::new();
806 doc.set_title("Integration Test");
807 doc.set_author("Test Author");
808 doc.set_subject("Writer Integration");
809 doc.set_keywords("test, writer, integration");
810
811 let mut page = Page::a4();
812 page.text()
813 .set_font(Font::Helvetica, 12.0)
814 .at(100.0, 700.0)
815 .write("Integration Test Content")
816 .unwrap();
817
818 doc.add_page(page);
819
820 let result = doc.save(&file_path);
822 assert!(result.is_ok());
823
824 assert!(file_path.exists());
826 let metadata = fs::metadata(&file_path).unwrap();
827 assert!(metadata.len() > 0);
828
829 let content = fs::read(&file_path).unwrap();
831 assert!(content.starts_with(b"%PDF-1.7"));
832 assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
834 }
835
836 #[test]
837 fn test_document_with_complex_content() {
838 let temp_dir = TempDir::new().unwrap();
839 let file_path = temp_dir.path().join("complex.pdf");
840
841 let mut doc = Document::new();
842 doc.set_title("Complex Content Test");
843
844 let mut page = Page::a4();
846
847 page.text()
849 .set_font(Font::Helvetica, 14.0)
850 .at(50.0, 750.0)
851 .write("Complex Content Test")
852 .unwrap();
853
854 page.graphics()
856 .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
857 .rectangle(50.0, 500.0, 200.0, 100.0)
858 .fill();
859
860 page.graphics()
861 .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
862 .set_line_width(2.0)
863 .move_to(50.0, 400.0)
864 .line_to(250.0, 400.0)
865 .stroke();
866
867 doc.add_page(page);
868
869 let result = doc.save(&file_path);
871 assert!(result.is_ok());
872 assert!(file_path.exists());
873 }
874
875 #[test]
876 fn test_document_multiple_pages_integration() {
877 let temp_dir = TempDir::new().unwrap();
878 let file_path = temp_dir.path().join("multipage.pdf");
879
880 let mut doc = Document::new();
881 doc.set_title("Multi-page Integration Test");
882
883 for i in 1..=5 {
885 let mut page = Page::a4();
886
887 page.text()
888 .set_font(Font::Helvetica, 16.0)
889 .at(50.0, 750.0)
890 .write(&format!("Page {}", i))
891 .unwrap();
892
893 page.text()
894 .set_font(Font::Helvetica, 12.0)
895 .at(50.0, 700.0)
896 .write(&format!("This is the content for page {}", i))
897 .unwrap();
898
899 let color = match i % 3 {
901 0 => Color::rgb(1.0, 0.0, 0.0),
902 1 => Color::rgb(0.0, 1.0, 0.0),
903 _ => Color::rgb(0.0, 0.0, 1.0),
904 };
905
906 page.graphics()
907 .set_fill_color(color)
908 .rectangle(50.0, 600.0, 100.0, 50.0)
909 .fill();
910
911 doc.add_page(page);
912 }
913
914 let result = doc.save(&file_path);
916 assert!(result.is_ok());
917 assert!(file_path.exists());
918
919 let metadata = fs::metadata(&file_path).unwrap();
921 assert!(metadata.len() > 1000); }
923
924 #[test]
925 fn test_document_metadata_persistence() {
926 let temp_dir = TempDir::new().unwrap();
927 let file_path = temp_dir.path().join("metadata.pdf");
928
929 let mut doc = Document::new();
930 doc.set_title("Metadata Persistence Test");
931 doc.set_author("Test Author");
932 doc.set_subject("Testing metadata preservation");
933 doc.set_keywords("metadata, persistence, test");
934
935 doc.add_page(Page::a4());
936
937 let result = doc.save(&file_path);
939 assert!(result.is_ok());
940
941 let content = fs::read(&file_path).unwrap();
943 let content_str = String::from_utf8_lossy(&content);
944
945 assert!(content_str.contains("Metadata Persistence Test"));
947 assert!(content_str.contains("Test Author"));
948 }
949
950 #[test]
951 fn test_document_writer_error_handling() {
952 let mut doc = Document::new();
953 doc.add_page(Page::a4());
954
955 let result = doc.save("/invalid/path/test.pdf");
957 assert!(result.is_err());
958 }
959
960 #[test]
961 fn test_document_object_management() {
962 let mut doc = Document::new();
963
964 let obj1 = Object::Boolean(true);
966 let obj2 = Object::Integer(42);
967 let obj3 = Object::Real(std::f64::consts::PI);
968
969 let id1 = doc.add_object(obj1.clone());
970 let id2 = doc.add_object(obj2.clone());
971 let id3 = doc.add_object(obj3.clone());
972
973 assert_eq!(id1.number(), 1);
974 assert_eq!(id2.number(), 2);
975 assert_eq!(id3.number(), 3);
976
977 assert_eq!(doc.objects.len(), 3);
978 assert!(doc.objects.contains_key(&id1));
979 assert!(doc.objects.contains_key(&id2));
980 assert!(doc.objects.contains_key(&id3));
981
982 assert_eq!(doc.objects.get(&id1), Some(&obj1));
984 assert_eq!(doc.objects.get(&id2), Some(&obj2));
985 assert_eq!(doc.objects.get(&id3), Some(&obj3));
986 }
987
988 #[test]
989 fn test_document_page_integration() {
990 let mut doc = Document::new();
991
992 let page1 = Page::a4();
994 let page2 = Page::letter();
995 let mut page3 = Page::new(500.0, 400.0);
996
997 page3
999 .text()
1000 .set_font(Font::Helvetica, 10.0)
1001 .at(25.0, 350.0)
1002 .write("Custom size page")
1003 .unwrap();
1004
1005 doc.add_page(page1);
1006 doc.add_page(page2);
1007 doc.add_page(page3);
1008
1009 assert_eq!(doc.pages.len(), 3);
1010
1011 assert!(doc.pages[0].width() > 500.0); assert!(doc.pages[0].height() > 700.0); assert!(doc.pages[1].width() > 500.0); assert!(doc.pages[1].height() > 700.0); assert_eq!(doc.pages[2].width(), 500.0); assert_eq!(doc.pages[2].height(), 400.0); }
1019
1020 #[test]
1021 fn test_document_content_generation() {
1022 let temp_dir = TempDir::new().unwrap();
1023 let file_path = temp_dir.path().join("content.pdf");
1024
1025 let mut doc = Document::new();
1026 doc.set_title("Content Generation Test");
1027
1028 let mut page = Page::a4();
1029
1030 for i in 0..10 {
1032 let y_pos = 700.0 - (i as f64 * 30.0);
1033 page.text()
1034 .set_font(Font::Helvetica, 12.0)
1035 .at(50.0, y_pos)
1036 .write(&format!("Generated line {}", i + 1))
1037 .unwrap();
1038 }
1039
1040 doc.add_page(page);
1041
1042 let result = doc.save(&file_path);
1044 assert!(result.is_ok());
1045 assert!(file_path.exists());
1046
1047 let metadata = fs::metadata(&file_path).unwrap();
1049 assert!(metadata.len() > 500); }
1051
1052 #[test]
1053 fn test_document_buffer_vs_file_write() {
1054 let temp_dir = TempDir::new().unwrap();
1055 let file_path = temp_dir.path().join("buffer_vs_file.pdf");
1056
1057 let mut doc = Document::new();
1058 doc.set_title("Buffer vs File Test");
1059 doc.add_page(Page::a4());
1060
1061 let mut buffer = Vec::new();
1063 let buffer_result = doc.write(&mut buffer);
1064 assert!(buffer_result.is_ok());
1065
1066 let file_result = doc.save(&file_path);
1068 assert!(file_result.is_ok());
1069
1070 let file_content = fs::read(&file_path).unwrap();
1072
1073 assert!(buffer.starts_with(b"%PDF-1.7"));
1075 assert!(file_content.starts_with(b"%PDF-1.7"));
1076 assert!(buffer.ends_with(b"%%EOF\n"));
1077 assert!(file_content.ends_with(b"%%EOF\n"));
1078
1079 let buffer_str = String::from_utf8_lossy(&buffer);
1081 let file_str = String::from_utf8_lossy(&file_content);
1082 assert!(buffer_str.contains("Buffer vs File Test"));
1083 assert!(file_str.contains("Buffer vs File Test"));
1084 }
1085
1086 #[test]
1087 fn test_document_large_content_handling() {
1088 let temp_dir = TempDir::new().unwrap();
1089 let file_path = temp_dir.path().join("large_content.pdf");
1090
1091 let mut doc = Document::new();
1092 doc.set_title("Large Content Test");
1093
1094 let mut page = Page::a4();
1095
1096 let large_text =
1098 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(200);
1099 page.text()
1100 .set_font(Font::Helvetica, 10.0)
1101 .at(50.0, 750.0)
1102 .write(&large_text)
1103 .unwrap();
1104
1105 doc.add_page(page);
1106
1107 let result = doc.save(&file_path);
1109 assert!(result.is_ok());
1110 assert!(file_path.exists());
1111
1112 let metadata = fs::metadata(&file_path).unwrap();
1114 assert!(metadata.len() > 500); }
1116
1117 #[test]
1118 fn test_document_incremental_building() {
1119 let temp_dir = TempDir::new().unwrap();
1120 let file_path = temp_dir.path().join("incremental.pdf");
1121
1122 let mut doc = Document::new();
1123
1124 doc.set_title("Incremental Building Test");
1126
1127 let mut page1 = Page::a4();
1129 page1
1130 .text()
1131 .set_font(Font::Helvetica, 12.0)
1132 .at(50.0, 750.0)
1133 .write("First page content")
1134 .unwrap();
1135 doc.add_page(page1);
1136
1137 doc.set_author("Incremental Author");
1139 doc.set_subject("Incremental Subject");
1140
1141 let mut page2 = Page::a4();
1143 page2
1144 .text()
1145 .set_font(Font::Helvetica, 12.0)
1146 .at(50.0, 750.0)
1147 .write("Second page content")
1148 .unwrap();
1149 doc.add_page(page2);
1150
1151 doc.set_keywords("incremental, building, test");
1153
1154 let result = doc.save(&file_path);
1156 assert!(result.is_ok());
1157 assert!(file_path.exists());
1158
1159 assert_eq!(doc.pages.len(), 2);
1161 assert_eq!(
1162 doc.metadata.title,
1163 Some("Incremental Building Test".to_string())
1164 );
1165 assert_eq!(doc.metadata.author, Some("Incremental Author".to_string()));
1166 assert_eq!(
1167 doc.metadata.subject,
1168 Some("Incremental Subject".to_string())
1169 );
1170 assert_eq!(
1171 doc.metadata.keywords,
1172 Some("incremental, building, test".to_string())
1173 );
1174 }
1175
1176 #[test]
1177 fn test_document_concurrent_page_operations() {
1178 let mut doc = Document::new();
1179 doc.set_title("Concurrent Operations Test");
1180
1181 let mut pages = Vec::new();
1183
1184 for i in 0..5 {
1186 let mut page = Page::a4();
1187 page.text()
1188 .set_font(Font::Helvetica, 12.0)
1189 .at(50.0, 750.0)
1190 .write(&format!("Concurrent page {}", i))
1191 .unwrap();
1192 pages.push(page);
1193 }
1194
1195 for page in pages {
1197 doc.add_page(page);
1198 }
1199
1200 assert_eq!(doc.pages.len(), 5);
1201
1202 let temp_dir = TempDir::new().unwrap();
1204 let file_path = temp_dir.path().join("concurrent.pdf");
1205 let result = doc.save(&file_path);
1206 assert!(result.is_ok());
1207 }
1208
1209 #[test]
1210 fn test_document_memory_efficiency() {
1211 let mut doc = Document::new();
1212 doc.set_title("Memory Efficiency Test");
1213
1214 for i in 0..10 {
1216 let mut page = Page::a4();
1217 page.text()
1218 .set_font(Font::Helvetica, 12.0)
1219 .at(50.0, 700.0)
1220 .write(&format!("Memory test page {}", i))
1221 .unwrap();
1222 doc.add_page(page);
1223 }
1224
1225 let mut buffer = Vec::new();
1227 let result = doc.write(&mut buffer);
1228 assert!(result.is_ok());
1229 assert!(!buffer.is_empty());
1230
1231 assert!(buffer.len() < 1_000_000); }
1234
1235 #[test]
1236 fn test_document_creator_producer() {
1237 let mut doc = Document::new();
1238
1239 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1241 assert!(doc
1242 .metadata
1243 .producer
1244 .as_ref()
1245 .unwrap()
1246 .contains("oxidize_pdf"));
1247
1248 doc.set_creator("My Application");
1250 doc.set_producer("My PDF Library v1.0");
1251
1252 assert_eq!(doc.metadata.creator, Some("My Application".to_string()));
1253 assert_eq!(
1254 doc.metadata.producer,
1255 Some("My PDF Library v1.0".to_string())
1256 );
1257 }
1258
1259 #[test]
1260 fn test_document_dates() {
1261 use chrono::{TimeZone, Utc};
1262
1263 let mut doc = Document::new();
1264
1265 assert!(doc.metadata.creation_date.is_some());
1267 assert!(doc.metadata.modification_date.is_some());
1268
1269 let creation_date = Utc.with_ymd_and_hms(2023, 1, 1, 12, 0, 0).unwrap();
1271 let mod_date = Utc.with_ymd_and_hms(2023, 6, 15, 18, 30, 0).unwrap();
1272
1273 doc.set_creation_date(creation_date);
1274 doc.set_modification_date(mod_date);
1275
1276 assert_eq!(doc.metadata.creation_date, Some(creation_date));
1277 assert_eq!(doc.metadata.modification_date, Some(mod_date));
1278 }
1279
1280 #[test]
1281 fn test_document_dates_local() {
1282 use chrono::{Local, TimeZone};
1283
1284 let mut doc = Document::new();
1285
1286 let local_date = Local.with_ymd_and_hms(2023, 12, 25, 10, 30, 0).unwrap();
1288 doc.set_creation_date_local(local_date);
1289
1290 assert!(doc.metadata.creation_date.is_some());
1292 assert!(doc.metadata.creation_date.is_some());
1294 }
1295
1296 #[test]
1297 fn test_update_modification_date() {
1298 let mut doc = Document::new();
1299
1300 let initial_mod_date = doc.metadata.modification_date;
1301 assert!(initial_mod_date.is_some());
1302
1303 std::thread::sleep(std::time::Duration::from_millis(10));
1305
1306 doc.update_modification_date();
1307
1308 let new_mod_date = doc.metadata.modification_date;
1309 assert!(new_mod_date.is_some());
1310 assert!(new_mod_date.unwrap() > initial_mod_date.unwrap());
1311 }
1312
1313 #[test]
1314 fn test_document_save_updates_modification_date() {
1315 let temp_dir = TempDir::new().unwrap();
1316 let file_path = temp_dir.path().join("mod_date_test.pdf");
1317
1318 let mut doc = Document::new();
1319 doc.add_page(Page::a4());
1320
1321 let initial_mod_date = doc.metadata.modification_date;
1322
1323 std::thread::sleep(std::time::Duration::from_millis(10));
1325
1326 doc.save(&file_path).unwrap();
1327
1328 assert!(doc.metadata.modification_date.unwrap() > initial_mod_date.unwrap());
1330 }
1331
1332 #[test]
1333 fn test_document_metadata_complete() {
1334 let mut doc = Document::new();
1335
1336 doc.set_title("Complete Metadata Test");
1338 doc.set_author("Test Author");
1339 doc.set_subject("Testing all metadata fields");
1340 doc.set_keywords("test, metadata, complete");
1341 doc.set_creator("Test Application v1.0");
1342 doc.set_producer("oxidize_pdf Test Suite");
1343
1344 assert_eq!(
1346 doc.metadata.title,
1347 Some("Complete Metadata Test".to_string())
1348 );
1349 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1350 assert_eq!(
1351 doc.metadata.subject,
1352 Some("Testing all metadata fields".to_string())
1353 );
1354 assert_eq!(
1355 doc.metadata.keywords,
1356 Some("test, metadata, complete".to_string())
1357 );
1358 assert_eq!(
1359 doc.metadata.creator,
1360 Some("Test Application v1.0".to_string())
1361 );
1362 assert_eq!(
1363 doc.metadata.producer,
1364 Some("oxidize_pdf Test Suite".to_string())
1365 );
1366 assert!(doc.metadata.creation_date.is_some());
1367 assert!(doc.metadata.modification_date.is_some());
1368 }
1369
1370 #[test]
1371 fn test_document_to_bytes() {
1372 let mut doc = Document::new();
1373 doc.set_title("Test Document");
1374 doc.set_author("Test Author");
1375
1376 let page = Page::a4();
1377 doc.add_page(page);
1378
1379 let pdf_bytes = doc.to_bytes().unwrap();
1381
1382 assert!(!pdf_bytes.is_empty());
1384 assert!(pdf_bytes.len() > 100); let header = &pdf_bytes[0..5];
1388 assert_eq!(header, b"%PDF-");
1389
1390 let pdf_str = String::from_utf8_lossy(&pdf_bytes);
1392 assert!(pdf_str.contains("Test Document"));
1393 assert!(pdf_str.contains("Test Author"));
1394 }
1395
1396 #[test]
1397 fn test_document_to_bytes_with_config() {
1398 let mut doc = Document::new();
1399 doc.set_title("Test Document XRef");
1400
1401 let page = Page::a4();
1402 doc.add_page(page);
1403
1404 let config = crate::writer::WriterConfig {
1405 use_xref_streams: true,
1406 pdf_version: "1.5".to_string(),
1407 compress_streams: true,
1408 };
1409
1410 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1412
1413 assert!(!pdf_bytes.is_empty());
1415 assert!(pdf_bytes.len() > 100);
1416
1417 let header = String::from_utf8_lossy(&pdf_bytes[0..8]);
1419 assert!(header.contains("PDF-1.5"));
1420 }
1421
1422 #[test]
1423 fn test_to_bytes_vs_save_equivalence() {
1424 use std::fs;
1425 use tempfile::NamedTempFile;
1426
1427 let mut doc1 = Document::new();
1429 doc1.set_title("Equivalence Test");
1430 doc1.add_page(Page::a4());
1431
1432 let mut doc2 = Document::new();
1433 doc2.set_title("Equivalence Test");
1434 doc2.add_page(Page::a4());
1435
1436 let pdf_bytes = doc1.to_bytes().unwrap();
1438
1439 let temp_file = NamedTempFile::new().unwrap();
1441 doc2.save(temp_file.path()).unwrap();
1442 let file_bytes = fs::read(temp_file.path()).unwrap();
1443
1444 assert!(!pdf_bytes.is_empty());
1446 assert!(!file_bytes.is_empty());
1447 assert_eq!(&pdf_bytes[0..5], &file_bytes[0..5]); }
1449
1450 #[test]
1451 fn test_document_set_compress() {
1452 let mut doc = Document::new();
1453 doc.set_title("Compression Test");
1454 doc.add_page(Page::a4());
1455
1456 assert!(doc.get_compress());
1458
1459 doc.set_compress(true);
1461 let compressed_bytes = doc.to_bytes().unwrap();
1462
1463 doc.set_compress(false);
1465 let uncompressed_bytes = doc.to_bytes().unwrap();
1466
1467 assert!(!compressed_bytes.is_empty());
1469 assert!(!uncompressed_bytes.is_empty());
1470
1471 assert_eq!(&compressed_bytes[0..5], b"%PDF-");
1473 assert_eq!(&uncompressed_bytes[0..5], b"%PDF-");
1474 }
1475
1476 #[test]
1477 fn test_document_compression_config_inheritance() {
1478 let mut doc = Document::new();
1479 doc.set_title("Config Inheritance Test");
1480 doc.add_page(Page::a4());
1481
1482 doc.set_compress(false);
1484
1485 let config = crate::writer::WriterConfig {
1487 use_xref_streams: false,
1488 pdf_version: "1.7".to_string(),
1489 compress_streams: true,
1490 };
1491
1492 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1494
1495 assert!(!pdf_bytes.is_empty());
1497 assert_eq!(&pdf_bytes[0..5], b"%PDF-");
1498 }
1499 }
1500}