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) use_xref_streams: bool,
56 pub(crate) custom_fonts: FontCache,
58 #[allow(dead_code)]
60 pub(crate) embedded_fonts: HashMap<String, ObjectId>,
61 pub(crate) used_characters: HashSet<char>,
63 pub(crate) open_action: Option<crate::actions::Action>,
65 pub(crate) viewer_preferences: Option<crate::viewer_preferences::ViewerPreferences>,
67}
68
69#[derive(Debug, Clone)]
71pub struct DocumentMetadata {
72 pub title: Option<String>,
74 pub author: Option<String>,
76 pub subject: Option<String>,
78 pub keywords: Option<String>,
80 pub creator: Option<String>,
82 pub producer: Option<String>,
84 pub creation_date: Option<DateTime<Utc>>,
86 pub modification_date: Option<DateTime<Utc>>,
88}
89
90impl Default for DocumentMetadata {
91 fn default() -> Self {
92 let now = Utc::now();
93 Self {
94 title: None,
95 author: None,
96 subject: None,
97 keywords: None,
98 creator: Some("oxidize_pdf".to_string()),
99 producer: Some(format!("oxidize_pdf v{}", env!("CARGO_PKG_VERSION"))),
100 creation_date: Some(now),
101 modification_date: Some(now),
102 }
103 }
104}
105
106impl Document {
107 pub fn new() -> Self {
109 Self {
110 pages: Vec::new(),
111 objects: HashMap::new(),
112 next_object_id: 1,
113 metadata: DocumentMetadata::default(),
114 encryption: None,
115 outline: None,
116 named_destinations: None,
117 page_tree: None,
118 page_labels: None,
119 default_font_encoding: None,
120 acro_form: None,
121 form_manager: None,
122 compress: true, use_xref_streams: false, custom_fonts: FontCache::new(),
125 embedded_fonts: HashMap::new(),
126 used_characters: HashSet::new(),
127 open_action: None,
128 viewer_preferences: None,
129 }
130 }
131
132 pub fn add_page(&mut self, page: Page) {
134 if let Some(used_chars) = page.get_used_characters() {
136 self.used_characters.extend(used_chars);
137 }
138 self.pages.push(page);
139 }
140
141 pub fn set_title(&mut self, title: impl Into<String>) {
143 self.metadata.title = Some(title.into());
144 }
145
146 pub fn set_author(&mut self, author: impl Into<String>) {
148 self.metadata.author = Some(author.into());
149 }
150
151 pub fn set_form_manager(&mut self, form_manager: FormManager) {
153 self.form_manager = Some(form_manager);
154 }
155
156 pub fn set_subject(&mut self, subject: impl Into<String>) {
158 self.metadata.subject = Some(subject.into());
159 }
160
161 pub fn set_keywords(&mut self, keywords: impl Into<String>) {
163 self.metadata.keywords = Some(keywords.into());
164 }
165
166 pub fn set_encryption(&mut self, encryption: DocumentEncryption) {
168 self.encryption = Some(encryption);
169 }
170
171 pub fn encrypt_with_passwords(
173 &mut self,
174 user_password: impl Into<String>,
175 owner_password: impl Into<String>,
176 ) {
177 self.encryption = Some(DocumentEncryption::with_passwords(
178 user_password,
179 owner_password,
180 ));
181 }
182
183 pub fn is_encrypted(&self) -> bool {
185 self.encryption.is_some()
186 }
187
188 pub fn set_open_action(&mut self, action: crate::actions::Action) {
190 self.open_action = Some(action);
191 }
192
193 pub fn open_action(&self) -> Option<&crate::actions::Action> {
195 self.open_action.as_ref()
196 }
197
198 pub fn set_viewer_preferences(
200 &mut self,
201 preferences: crate::viewer_preferences::ViewerPreferences,
202 ) {
203 self.viewer_preferences = Some(preferences);
204 }
205
206 pub fn viewer_preferences(&self) -> Option<&crate::viewer_preferences::ViewerPreferences> {
208 self.viewer_preferences.as_ref()
209 }
210
211 pub fn set_outline(&mut self, outline: OutlineTree) {
213 self.outline = Some(outline);
214 }
215
216 pub fn outline(&self) -> Option<&OutlineTree> {
218 self.outline.as_ref()
219 }
220
221 pub fn outline_mut(&mut self) -> Option<&mut OutlineTree> {
223 self.outline.as_mut()
224 }
225
226 pub fn set_named_destinations(&mut self, destinations: NamedDestinations) {
228 self.named_destinations = Some(destinations);
229 }
230
231 pub fn named_destinations(&self) -> Option<&NamedDestinations> {
233 self.named_destinations.as_ref()
234 }
235
236 pub fn named_destinations_mut(&mut self) -> Option<&mut NamedDestinations> {
238 self.named_destinations.as_mut()
239 }
240
241 pub fn set_page_labels(&mut self, labels: PageLabelTree) {
243 self.page_labels = Some(labels);
244 }
245
246 pub fn page_labels(&self) -> Option<&PageLabelTree> {
248 self.page_labels.as_ref()
249 }
250
251 pub fn page_labels_mut(&mut self) -> Option<&mut PageLabelTree> {
253 self.page_labels.as_mut()
254 }
255
256 pub fn get_page_label(&self, page_index: u32) -> String {
258 self.page_labels
259 .as_ref()
260 .and_then(|labels| labels.get_label(page_index))
261 .unwrap_or_else(|| (page_index + 1).to_string())
262 }
263
264 pub fn get_all_page_labels(&self) -> Vec<String> {
266 let page_count = self.pages.len() as u32;
267 if let Some(labels) = &self.page_labels {
268 labels.get_all_labels(page_count)
269 } else {
270 (1..=page_count).map(|i| i.to_string()).collect()
271 }
272 }
273
274 pub fn set_creator(&mut self, creator: impl Into<String>) {
276 self.metadata.creator = Some(creator.into());
277 }
278
279 pub fn set_producer(&mut self, producer: impl Into<String>) {
281 self.metadata.producer = Some(producer.into());
282 }
283
284 pub fn set_creation_date(&mut self, date: DateTime<Utc>) {
286 self.metadata.creation_date = Some(date);
287 }
288
289 pub fn set_creation_date_local(&mut self, date: DateTime<Local>) {
291 self.metadata.creation_date = Some(date.with_timezone(&Utc));
292 }
293
294 pub fn set_modification_date(&mut self, date: DateTime<Utc>) {
296 self.metadata.modification_date = Some(date);
297 }
298
299 pub fn set_modification_date_local(&mut self, date: DateTime<Local>) {
301 self.metadata.modification_date = Some(date.with_timezone(&Utc));
302 }
303
304 pub fn update_modification_date(&mut self) {
306 self.metadata.modification_date = Some(Utc::now());
307 }
308
309 pub fn set_default_font_encoding(&mut self, encoding: Option<FontEncoding>) {
324 self.default_font_encoding = encoding;
325 }
326
327 pub fn default_font_encoding(&self) -> Option<FontEncoding> {
329 self.default_font_encoding
330 }
331
332 #[allow(dead_code)]
337 pub(crate) fn get_fonts_with_encodings(&self) -> Vec<FontWithEncoding> {
338 let mut fonts_used = HashSet::new();
339
340 for page in &self.pages {
342 for font in page.get_used_fonts() {
344 let font_with_encoding = match self.default_font_encoding {
345 Some(default_encoding) => FontWithEncoding::new(font, Some(default_encoding)),
346 None => FontWithEncoding::without_encoding(font),
347 };
348 fonts_used.insert(font_with_encoding);
349 }
350 }
351
352 fonts_used.into_iter().collect()
353 }
354
355 pub fn add_font(
366 &mut self,
367 name: impl Into<String>,
368 path: impl AsRef<std::path::Path>,
369 ) -> Result<()> {
370 let name = name.into();
371 let font = CustomFont::from_file(&name, path)?;
372 self.custom_fonts.add_font(name, font)?;
373 Ok(())
374 }
375
376 pub fn add_font_from_bytes(&mut self, name: impl Into<String>, data: Vec<u8>) -> Result<()> {
388 let name = name.into();
389 let font = CustomFont::from_bytes(&name, data)?;
390 self.custom_fonts.add_font(name, font)?;
391 Ok(())
392 }
393
394 #[allow(dead_code)]
396 pub(crate) fn get_custom_font(&self, name: &str) -> Option<Arc<CustomFont>> {
397 self.custom_fonts.get_font(name)
398 }
399
400 pub fn has_custom_font(&self, name: &str) -> bool {
402 self.custom_fonts.has_font(name)
403 }
404
405 pub fn custom_font_names(&self) -> Vec<String> {
407 self.custom_fonts.font_names()
408 }
409
410 pub fn page_count(&self) -> usize {
412 self.pages.len()
413 }
414
415 pub fn acro_form(&self) -> Option<&AcroForm> {
417 self.acro_form.as_ref()
418 }
419
420 pub fn acro_form_mut(&mut self) -> Option<&mut AcroForm> {
422 self.acro_form.as_mut()
423 }
424
425 pub fn enable_forms(&mut self) -> &mut FormManager {
428 if self.form_manager.is_none() {
429 self.form_manager = Some(FormManager::new());
430 }
431 if self.acro_form.is_none() {
432 self.acro_form = Some(AcroForm::new());
433 }
434 self.form_manager
436 .as_mut()
437 .expect("FormManager should exist after initialization")
438 }
439
440 pub fn disable_forms(&mut self) {
442 self.acro_form = None;
443 self.form_manager = None;
444 }
445
446 pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
452 self.update_modification_date();
454
455 let config = crate::writer::WriterConfig {
457 use_xref_streams: self.use_xref_streams,
458 pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
459 compress_streams: self.compress,
460 };
461
462 use std::io::BufWriter;
463 let file = std::fs::File::create(path)?;
464 let writer = BufWriter::new(file);
465 let mut pdf_writer = PdfWriter::with_config(writer, config);
466
467 pdf_writer.write_document(self)?;
468 Ok(())
469 }
470
471 pub fn save_with_config(
477 &mut self,
478 path: impl AsRef<std::path::Path>,
479 config: crate::writer::WriterConfig,
480 ) -> Result<()> {
481 use std::io::BufWriter;
482
483 self.update_modification_date();
485
486 let file = std::fs::File::create(path)?;
489 let writer = BufWriter::new(file);
490 let mut pdf_writer = PdfWriter::with_config(writer, config);
491 pdf_writer.write_document(self)?;
492 Ok(())
493 }
494
495 pub fn save_with_custom_values(
509 &mut self,
510 path: impl AsRef<std::path::Path>,
511 custom_values: &std::collections::HashMap<String, String>,
512 ) -> Result<()> {
513 let total_pages = self.pages.len();
515 for (index, page) in self.pages.iter_mut().enumerate() {
516 let page_content = page.generate_content_with_page_info(
518 Some(index + 1),
519 Some(total_pages),
520 Some(custom_values),
521 )?;
522 page.set_content(page_content);
524 }
525
526 self.save(path)
528 }
529
530 pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
536 self.update_modification_date();
538
539 let mut writer = PdfWriter::new_with_writer(buffer);
540 writer.write_document(self)?;
541 Ok(())
542 }
543
544 #[allow(dead_code)]
545 pub(crate) fn allocate_object_id(&mut self) -> ObjectId {
546 let id = ObjectId::new(self.next_object_id, 0);
547 self.next_object_id += 1;
548 id
549 }
550
551 #[allow(dead_code)]
552 pub(crate) fn add_object(&mut self, obj: Object) -> ObjectId {
553 let id = self.allocate_object_id();
554 self.objects.insert(id, obj);
555 id
556 }
557
558 pub fn set_compress(&mut self, compress: bool) {
585 self.compress = compress;
586 }
587
588 pub fn enable_xref_streams(&mut self, enable: bool) -> &mut Self {
606 self.use_xref_streams = enable;
607 self
608 }
609
610 pub fn get_compress(&self) -> bool {
616 self.compress
617 }
618
619 pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
647 self.update_modification_date();
649
650 let mut buffer = Vec::new();
652
653 let config = crate::writer::WriterConfig {
655 use_xref_streams: self.use_xref_streams,
656 pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
657 compress_streams: self.compress,
658 };
659
660 let mut writer = PdfWriter::with_config(&mut buffer, config);
662 writer.write_document(self)?;
663
664 Ok(buffer)
665 }
666
667 pub fn to_bytes_with_config(&mut self, config: crate::writer::WriterConfig) -> Result<Vec<u8>> {
706 self.update_modification_date();
708
709 let mut buffer = Vec::new();
713
714 let mut writer = PdfWriter::with_config(&mut buffer, config);
716 writer.write_document(self)?;
717
718 Ok(buffer)
719 }
720}
721
722impl Default for Document {
723 fn default() -> Self {
724 Self::new()
725 }
726}
727
728#[cfg(test)]
729mod tests {
730 use super::*;
731
732 #[test]
733 fn test_document_new() {
734 let doc = Document::new();
735 assert!(doc.pages.is_empty());
736 assert!(doc.objects.is_empty());
737 assert_eq!(doc.next_object_id, 1);
738 assert!(doc.metadata.title.is_none());
739 assert!(doc.metadata.author.is_none());
740 assert!(doc.metadata.subject.is_none());
741 assert!(doc.metadata.keywords.is_none());
742 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
743 assert!(doc
744 .metadata
745 .producer
746 .as_ref()
747 .unwrap()
748 .starts_with("oxidize_pdf"));
749 }
750
751 #[test]
752 fn test_document_default() {
753 let doc = Document::default();
754 assert!(doc.pages.is_empty());
755 assert_eq!(doc.next_object_id, 1);
756 }
757
758 #[test]
759 fn test_add_page() {
760 let mut doc = Document::new();
761 let page1 = Page::a4();
762 let page2 = Page::letter();
763
764 doc.add_page(page1);
765 assert_eq!(doc.pages.len(), 1);
766
767 doc.add_page(page2);
768 assert_eq!(doc.pages.len(), 2);
769 }
770
771 #[test]
772 fn test_set_title() {
773 let mut doc = Document::new();
774 assert!(doc.metadata.title.is_none());
775
776 doc.set_title("Test Document");
777 assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
778
779 doc.set_title(String::from("Another Title"));
780 assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
781 }
782
783 #[test]
784 fn test_set_author() {
785 let mut doc = Document::new();
786 assert!(doc.metadata.author.is_none());
787
788 doc.set_author("John Doe");
789 assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
790 }
791
792 #[test]
793 fn test_set_subject() {
794 let mut doc = Document::new();
795 assert!(doc.metadata.subject.is_none());
796
797 doc.set_subject("Test Subject");
798 assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
799 }
800
801 #[test]
802 fn test_set_keywords() {
803 let mut doc = Document::new();
804 assert!(doc.metadata.keywords.is_none());
805
806 doc.set_keywords("test, pdf, rust");
807 assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
808 }
809
810 #[test]
811 fn test_metadata_default() {
812 let metadata = DocumentMetadata::default();
813 assert!(metadata.title.is_none());
814 assert!(metadata.author.is_none());
815 assert!(metadata.subject.is_none());
816 assert!(metadata.keywords.is_none());
817 assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
818 assert!(metadata
819 .producer
820 .as_ref()
821 .unwrap()
822 .starts_with("oxidize_pdf"));
823 }
824
825 #[test]
826 fn test_allocate_object_id() {
827 let mut doc = Document::new();
828
829 let id1 = doc.allocate_object_id();
830 assert_eq!(id1.number(), 1);
831 assert_eq!(id1.generation(), 0);
832 assert_eq!(doc.next_object_id, 2);
833
834 let id2 = doc.allocate_object_id();
835 assert_eq!(id2.number(), 2);
836 assert_eq!(id2.generation(), 0);
837 assert_eq!(doc.next_object_id, 3);
838 }
839
840 #[test]
841 fn test_add_object() {
842 let mut doc = Document::new();
843 assert!(doc.objects.is_empty());
844
845 let obj = Object::Boolean(true);
846 let id = doc.add_object(obj.clone());
847
848 assert_eq!(id.number(), 1);
849 assert_eq!(doc.objects.len(), 1);
850 assert!(doc.objects.contains_key(&id));
851 }
852
853 #[test]
854 fn test_write_to_buffer() {
855 let mut doc = Document::new();
856 doc.set_title("Buffer Test");
857 doc.add_page(Page::a4());
858
859 let mut buffer = Vec::new();
860 let result = doc.write(&mut buffer);
861
862 assert!(result.is_ok());
863 assert!(!buffer.is_empty());
864 assert!(buffer.starts_with(b"%PDF-1.7"));
865 }
866
867 #[test]
868 fn test_document_with_multiple_pages() {
869 let mut doc = Document::new();
870 doc.set_title("Multi-page Document");
871 doc.set_author("Test Author");
872 doc.set_subject("Testing multiple pages");
873 doc.set_keywords("test, multiple, pages");
874
875 for _ in 0..5 {
876 doc.add_page(Page::a4());
877 }
878
879 assert_eq!(doc.pages.len(), 5);
880 assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
881 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
882 }
883
884 #[test]
885 fn test_empty_document_write() {
886 let mut doc = Document::new();
887 let mut buffer = Vec::new();
888
889 let result = doc.write(&mut buffer);
891 assert!(result.is_ok());
892 assert!(!buffer.is_empty());
893 assert!(buffer.starts_with(b"%PDF-1.7"));
894 }
895
896 mod integration_tests {
898 use super::*;
899 use crate::graphics::Color;
900 use crate::text::Font;
901 use std::fs;
902 use tempfile::TempDir;
903
904 #[test]
905 fn test_document_writer_roundtrip() {
906 let temp_dir = TempDir::new().unwrap();
907 let file_path = temp_dir.path().join("test.pdf");
908
909 let mut doc = Document::new();
911 doc.set_title("Integration Test");
912 doc.set_author("Test Author");
913 doc.set_subject("Writer Integration");
914 doc.set_keywords("test, writer, integration");
915
916 let mut page = Page::a4();
917 page.text()
918 .set_font(Font::Helvetica, 12.0)
919 .at(100.0, 700.0)
920 .write("Integration Test Content")
921 .unwrap();
922
923 doc.add_page(page);
924
925 let result = doc.save(&file_path);
927 assert!(result.is_ok());
928
929 assert!(file_path.exists());
931 let metadata = fs::metadata(&file_path).unwrap();
932 assert!(metadata.len() > 0);
933
934 let content = fs::read(&file_path).unwrap();
936 assert!(content.starts_with(b"%PDF-1.7"));
937 assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
939 }
940
941 #[test]
942 fn test_document_with_complex_content() {
943 let temp_dir = TempDir::new().unwrap();
944 let file_path = temp_dir.path().join("complex.pdf");
945
946 let mut doc = Document::new();
947 doc.set_title("Complex Content Test");
948
949 let mut page = Page::a4();
951
952 page.text()
954 .set_font(Font::Helvetica, 14.0)
955 .at(50.0, 750.0)
956 .write("Complex Content Test")
957 .unwrap();
958
959 page.graphics()
961 .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
962 .rectangle(50.0, 500.0, 200.0, 100.0)
963 .fill();
964
965 page.graphics()
966 .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
967 .set_line_width(2.0)
968 .move_to(50.0, 400.0)
969 .line_to(250.0, 400.0)
970 .stroke();
971
972 doc.add_page(page);
973
974 let result = doc.save(&file_path);
976 assert!(result.is_ok());
977 assert!(file_path.exists());
978 }
979
980 #[test]
981 fn test_document_multiple_pages_integration() {
982 let temp_dir = TempDir::new().unwrap();
983 let file_path = temp_dir.path().join("multipage.pdf");
984
985 let mut doc = Document::new();
986 doc.set_title("Multi-page Integration Test");
987
988 for i in 1..=5 {
990 let mut page = Page::a4();
991
992 page.text()
993 .set_font(Font::Helvetica, 16.0)
994 .at(50.0, 750.0)
995 .write(&format!("Page {i}"))
996 .unwrap();
997
998 page.text()
999 .set_font(Font::Helvetica, 12.0)
1000 .at(50.0, 700.0)
1001 .write(&format!("This is the content for page {i}"))
1002 .unwrap();
1003
1004 let color = match i % 3 {
1006 0 => Color::rgb(1.0, 0.0, 0.0),
1007 1 => Color::rgb(0.0, 1.0, 0.0),
1008 _ => Color::rgb(0.0, 0.0, 1.0),
1009 };
1010
1011 page.graphics()
1012 .set_fill_color(color)
1013 .rectangle(50.0, 600.0, 100.0, 50.0)
1014 .fill();
1015
1016 doc.add_page(page);
1017 }
1018
1019 let result = doc.save(&file_path);
1021 assert!(result.is_ok());
1022 assert!(file_path.exists());
1023
1024 let metadata = fs::metadata(&file_path).unwrap();
1026 assert!(metadata.len() > 1000); }
1028
1029 #[test]
1030 fn test_document_metadata_persistence() {
1031 let temp_dir = TempDir::new().unwrap();
1032 let file_path = temp_dir.path().join("metadata.pdf");
1033
1034 let mut doc = Document::new();
1035 doc.set_title("Metadata Persistence Test");
1036 doc.set_author("Test Author");
1037 doc.set_subject("Testing metadata preservation");
1038 doc.set_keywords("metadata, persistence, test");
1039
1040 doc.add_page(Page::a4());
1041
1042 let result = doc.save(&file_path);
1044 assert!(result.is_ok());
1045
1046 let content = fs::read(&file_path).unwrap();
1048 let content_str = String::from_utf8_lossy(&content);
1049
1050 assert!(content_str.contains("Metadata Persistence Test"));
1052 assert!(content_str.contains("Test Author"));
1053 }
1054
1055 #[test]
1056 fn test_document_writer_error_handling() {
1057 let mut doc = Document::new();
1058 doc.add_page(Page::a4());
1059
1060 let result = doc.save("/invalid/path/test.pdf");
1062 assert!(result.is_err());
1063 }
1064
1065 #[test]
1066 fn test_document_object_management() {
1067 let mut doc = Document::new();
1068
1069 let obj1 = Object::Boolean(true);
1071 let obj2 = Object::Integer(42);
1072 let obj3 = Object::Real(std::f64::consts::PI);
1073
1074 let id1 = doc.add_object(obj1.clone());
1075 let id2 = doc.add_object(obj2.clone());
1076 let id3 = doc.add_object(obj3.clone());
1077
1078 assert_eq!(id1.number(), 1);
1079 assert_eq!(id2.number(), 2);
1080 assert_eq!(id3.number(), 3);
1081
1082 assert_eq!(doc.objects.len(), 3);
1083 assert!(doc.objects.contains_key(&id1));
1084 assert!(doc.objects.contains_key(&id2));
1085 assert!(doc.objects.contains_key(&id3));
1086
1087 assert_eq!(doc.objects.get(&id1), Some(&obj1));
1089 assert_eq!(doc.objects.get(&id2), Some(&obj2));
1090 assert_eq!(doc.objects.get(&id3), Some(&obj3));
1091 }
1092
1093 #[test]
1094 fn test_document_page_integration() {
1095 let mut doc = Document::new();
1096
1097 let page1 = Page::a4();
1099 let page2 = Page::letter();
1100 let mut page3 = Page::new(500.0, 400.0);
1101
1102 page3
1104 .text()
1105 .set_font(Font::Helvetica, 10.0)
1106 .at(25.0, 350.0)
1107 .write("Custom size page")
1108 .unwrap();
1109
1110 doc.add_page(page1);
1111 doc.add_page(page2);
1112 doc.add_page(page3);
1113
1114 assert_eq!(doc.pages.len(), 3);
1115
1116 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); }
1124
1125 #[test]
1126 fn test_document_content_generation() {
1127 let temp_dir = TempDir::new().unwrap();
1128 let file_path = temp_dir.path().join("content.pdf");
1129
1130 let mut doc = Document::new();
1131 doc.set_title("Content Generation Test");
1132
1133 let mut page = Page::a4();
1134
1135 for i in 0..10 {
1137 let y_pos = 700.0 - (i as f64 * 30.0);
1138 page.text()
1139 .set_font(Font::Helvetica, 12.0)
1140 .at(50.0, y_pos)
1141 .write(&format!("Generated line {}", i + 1))
1142 .unwrap();
1143 }
1144
1145 doc.add_page(page);
1146
1147 let result = doc.save(&file_path);
1149 assert!(result.is_ok());
1150 assert!(file_path.exists());
1151
1152 let metadata = fs::metadata(&file_path).unwrap();
1154 assert!(metadata.len() > 500); }
1156
1157 #[test]
1158 fn test_document_buffer_vs_file_write() {
1159 let temp_dir = TempDir::new().unwrap();
1160 let file_path = temp_dir.path().join("buffer_vs_file.pdf");
1161
1162 let mut doc = Document::new();
1163 doc.set_title("Buffer vs File Test");
1164 doc.add_page(Page::a4());
1165
1166 let mut buffer = Vec::new();
1168 let buffer_result = doc.write(&mut buffer);
1169 assert!(buffer_result.is_ok());
1170
1171 let file_result = doc.save(&file_path);
1173 assert!(file_result.is_ok());
1174
1175 let file_content = fs::read(&file_path).unwrap();
1177
1178 assert!(buffer.starts_with(b"%PDF-1.7"));
1180 assert!(file_content.starts_with(b"%PDF-1.7"));
1181 assert!(buffer.ends_with(b"%%EOF\n"));
1182 assert!(file_content.ends_with(b"%%EOF\n"));
1183
1184 let buffer_str = String::from_utf8_lossy(&buffer);
1186 let file_str = String::from_utf8_lossy(&file_content);
1187 assert!(buffer_str.contains("Buffer vs File Test"));
1188 assert!(file_str.contains("Buffer vs File Test"));
1189 }
1190
1191 #[test]
1192 fn test_document_large_content_handling() {
1193 let temp_dir = TempDir::new().unwrap();
1194 let file_path = temp_dir.path().join("large_content.pdf");
1195
1196 let mut doc = Document::new();
1197 doc.set_title("Large Content Test");
1198
1199 let mut page = Page::a4();
1200
1201 let large_text =
1203 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(200);
1204 page.text()
1205 .set_font(Font::Helvetica, 10.0)
1206 .at(50.0, 750.0)
1207 .write(&large_text)
1208 .unwrap();
1209
1210 doc.add_page(page);
1211
1212 let result = doc.save(&file_path);
1214 assert!(result.is_ok());
1215 assert!(file_path.exists());
1216
1217 let metadata = fs::metadata(&file_path).unwrap();
1219 assert!(metadata.len() > 500); }
1221
1222 #[test]
1223 fn test_document_incremental_building() {
1224 let temp_dir = TempDir::new().unwrap();
1225 let file_path = temp_dir.path().join("incremental.pdf");
1226
1227 let mut doc = Document::new();
1228
1229 doc.set_title("Incremental Building Test");
1231
1232 let mut page1 = Page::a4();
1234 page1
1235 .text()
1236 .set_font(Font::Helvetica, 12.0)
1237 .at(50.0, 750.0)
1238 .write("First page content")
1239 .unwrap();
1240 doc.add_page(page1);
1241
1242 doc.set_author("Incremental Author");
1244 doc.set_subject("Incremental Subject");
1245
1246 let mut page2 = Page::a4();
1248 page2
1249 .text()
1250 .set_font(Font::Helvetica, 12.0)
1251 .at(50.0, 750.0)
1252 .write("Second page content")
1253 .unwrap();
1254 doc.add_page(page2);
1255
1256 doc.set_keywords("incremental, building, test");
1258
1259 let result = doc.save(&file_path);
1261 assert!(result.is_ok());
1262 assert!(file_path.exists());
1263
1264 assert_eq!(doc.pages.len(), 2);
1266 assert_eq!(
1267 doc.metadata.title,
1268 Some("Incremental Building Test".to_string())
1269 );
1270 assert_eq!(doc.metadata.author, Some("Incremental Author".to_string()));
1271 assert_eq!(
1272 doc.metadata.subject,
1273 Some("Incremental Subject".to_string())
1274 );
1275 assert_eq!(
1276 doc.metadata.keywords,
1277 Some("incremental, building, test".to_string())
1278 );
1279 }
1280
1281 #[test]
1282 fn test_document_concurrent_page_operations() {
1283 let mut doc = Document::new();
1284 doc.set_title("Concurrent Operations Test");
1285
1286 let mut pages = Vec::new();
1288
1289 for i in 0..5 {
1291 let mut page = Page::a4();
1292 page.text()
1293 .set_font(Font::Helvetica, 12.0)
1294 .at(50.0, 750.0)
1295 .write(&format!("Concurrent page {i}"))
1296 .unwrap();
1297 pages.push(page);
1298 }
1299
1300 for page in pages {
1302 doc.add_page(page);
1303 }
1304
1305 assert_eq!(doc.pages.len(), 5);
1306
1307 let temp_dir = TempDir::new().unwrap();
1309 let file_path = temp_dir.path().join("concurrent.pdf");
1310 let result = doc.save(&file_path);
1311 assert!(result.is_ok());
1312 }
1313
1314 #[test]
1315 fn test_document_memory_efficiency() {
1316 let mut doc = Document::new();
1317 doc.set_title("Memory Efficiency Test");
1318
1319 for i in 0..10 {
1321 let mut page = Page::a4();
1322 page.text()
1323 .set_font(Font::Helvetica, 12.0)
1324 .at(50.0, 700.0)
1325 .write(&format!("Memory test page {i}"))
1326 .unwrap();
1327 doc.add_page(page);
1328 }
1329
1330 let mut buffer = Vec::new();
1332 let result = doc.write(&mut buffer);
1333 assert!(result.is_ok());
1334 assert!(!buffer.is_empty());
1335
1336 assert!(buffer.len() < 1_000_000); }
1339
1340 #[test]
1341 fn test_document_creator_producer() {
1342 let mut doc = Document::new();
1343
1344 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1346 assert!(doc
1347 .metadata
1348 .producer
1349 .as_ref()
1350 .unwrap()
1351 .contains("oxidize_pdf"));
1352
1353 doc.set_creator("My Application");
1355 doc.set_producer("My PDF Library v1.0");
1356
1357 assert_eq!(doc.metadata.creator, Some("My Application".to_string()));
1358 assert_eq!(
1359 doc.metadata.producer,
1360 Some("My PDF Library v1.0".to_string())
1361 );
1362 }
1363
1364 #[test]
1365 fn test_document_dates() {
1366 use chrono::{TimeZone, Utc};
1367
1368 let mut doc = Document::new();
1369
1370 assert!(doc.metadata.creation_date.is_some());
1372 assert!(doc.metadata.modification_date.is_some());
1373
1374 let creation_date = Utc.with_ymd_and_hms(2023, 1, 1, 12, 0, 0).unwrap();
1376 let mod_date = Utc.with_ymd_and_hms(2023, 6, 15, 18, 30, 0).unwrap();
1377
1378 doc.set_creation_date(creation_date);
1379 doc.set_modification_date(mod_date);
1380
1381 assert_eq!(doc.metadata.creation_date, Some(creation_date));
1382 assert_eq!(doc.metadata.modification_date, Some(mod_date));
1383 }
1384
1385 #[test]
1386 fn test_document_dates_local() {
1387 use chrono::{Local, TimeZone};
1388
1389 let mut doc = Document::new();
1390
1391 let local_date = Local.with_ymd_and_hms(2023, 12, 25, 10, 30, 0).unwrap();
1393 doc.set_creation_date_local(local_date);
1394
1395 assert!(doc.metadata.creation_date.is_some());
1397 assert!(doc.metadata.creation_date.is_some());
1399 }
1400
1401 #[test]
1402 fn test_update_modification_date() {
1403 let mut doc = Document::new();
1404
1405 let initial_mod_date = doc.metadata.modification_date;
1406 assert!(initial_mod_date.is_some());
1407
1408 std::thread::sleep(std::time::Duration::from_millis(10));
1410
1411 doc.update_modification_date();
1412
1413 let new_mod_date = doc.metadata.modification_date;
1414 assert!(new_mod_date.is_some());
1415 assert!(new_mod_date.unwrap() > initial_mod_date.unwrap());
1416 }
1417
1418 #[test]
1419 fn test_document_save_updates_modification_date() {
1420 let temp_dir = TempDir::new().unwrap();
1421 let file_path = temp_dir.path().join("mod_date_test.pdf");
1422
1423 let mut doc = Document::new();
1424 doc.add_page(Page::a4());
1425
1426 let initial_mod_date = doc.metadata.modification_date;
1427
1428 std::thread::sleep(std::time::Duration::from_millis(10));
1430
1431 doc.save(&file_path).unwrap();
1432
1433 assert!(doc.metadata.modification_date.unwrap() > initial_mod_date.unwrap());
1435 }
1436
1437 #[test]
1438 fn test_document_metadata_complete() {
1439 let mut doc = Document::new();
1440
1441 doc.set_title("Complete Metadata Test");
1443 doc.set_author("Test Author");
1444 doc.set_subject("Testing all metadata fields");
1445 doc.set_keywords("test, metadata, complete");
1446 doc.set_creator("Test Application v1.0");
1447 doc.set_producer("oxidize_pdf Test Suite");
1448
1449 assert_eq!(
1451 doc.metadata.title,
1452 Some("Complete Metadata Test".to_string())
1453 );
1454 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1455 assert_eq!(
1456 doc.metadata.subject,
1457 Some("Testing all metadata fields".to_string())
1458 );
1459 assert_eq!(
1460 doc.metadata.keywords,
1461 Some("test, metadata, complete".to_string())
1462 );
1463 assert_eq!(
1464 doc.metadata.creator,
1465 Some("Test Application v1.0".to_string())
1466 );
1467 assert_eq!(
1468 doc.metadata.producer,
1469 Some("oxidize_pdf Test Suite".to_string())
1470 );
1471 assert!(doc.metadata.creation_date.is_some());
1472 assert!(doc.metadata.modification_date.is_some());
1473 }
1474
1475 #[test]
1476 fn test_document_to_bytes() {
1477 let mut doc = Document::new();
1478 doc.set_title("Test Document");
1479 doc.set_author("Test Author");
1480
1481 let page = Page::a4();
1482 doc.add_page(page);
1483
1484 let pdf_bytes = doc.to_bytes().unwrap();
1486
1487 assert!(!pdf_bytes.is_empty());
1489 assert!(pdf_bytes.len() > 100); let header = &pdf_bytes[0..5];
1493 assert_eq!(header, b"%PDF-");
1494
1495 let pdf_str = String::from_utf8_lossy(&pdf_bytes);
1497 assert!(pdf_str.contains("Test Document"));
1498 assert!(pdf_str.contains("Test Author"));
1499 }
1500
1501 #[test]
1502 fn test_document_to_bytes_with_config() {
1503 let mut doc = Document::new();
1504 doc.set_title("Test Document XRef");
1505
1506 let page = Page::a4();
1507 doc.add_page(page);
1508
1509 let config = crate::writer::WriterConfig {
1510 use_xref_streams: true,
1511 pdf_version: "1.5".to_string(),
1512 compress_streams: true,
1513 };
1514
1515 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1517
1518 assert!(!pdf_bytes.is_empty());
1520 assert!(pdf_bytes.len() > 100);
1521
1522 let header = String::from_utf8_lossy(&pdf_bytes[0..8]);
1524 assert!(header.contains("PDF-1.5"));
1525 }
1526
1527 #[test]
1528 fn test_to_bytes_vs_save_equivalence() {
1529 use std::fs;
1530 use tempfile::NamedTempFile;
1531
1532 let mut doc1 = Document::new();
1534 doc1.set_title("Equivalence Test");
1535 doc1.add_page(Page::a4());
1536
1537 let mut doc2 = Document::new();
1538 doc2.set_title("Equivalence Test");
1539 doc2.add_page(Page::a4());
1540
1541 let pdf_bytes = doc1.to_bytes().unwrap();
1543
1544 let temp_file = NamedTempFile::new().unwrap();
1546 doc2.save(temp_file.path()).unwrap();
1547 let file_bytes = fs::read(temp_file.path()).unwrap();
1548
1549 assert!(!pdf_bytes.is_empty());
1551 assert!(!file_bytes.is_empty());
1552 assert_eq!(&pdf_bytes[0..5], &file_bytes[0..5]); }
1554
1555 #[test]
1556 fn test_document_set_compress() {
1557 let mut doc = Document::new();
1558 doc.set_title("Compression Test");
1559 doc.add_page(Page::a4());
1560
1561 assert!(doc.get_compress());
1563
1564 doc.set_compress(true);
1566 let compressed_bytes = doc.to_bytes().unwrap();
1567
1568 doc.set_compress(false);
1570 let uncompressed_bytes = doc.to_bytes().unwrap();
1571
1572 assert!(!compressed_bytes.is_empty());
1574 assert!(!uncompressed_bytes.is_empty());
1575
1576 assert_eq!(&compressed_bytes[0..5], b"%PDF-");
1578 assert_eq!(&uncompressed_bytes[0..5], b"%PDF-");
1579 }
1580
1581 #[test]
1582 fn test_document_compression_config_inheritance() {
1583 let mut doc = Document::new();
1584 doc.set_title("Config Inheritance Test");
1585 doc.add_page(Page::a4());
1586
1587 doc.set_compress(false);
1589
1590 let config = crate::writer::WriterConfig {
1592 use_xref_streams: false,
1593 pdf_version: "1.7".to_string(),
1594 compress_streams: true,
1595 };
1596
1597 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1599
1600 assert!(!pdf_bytes.is_empty());
1602 assert_eq!(&pdf_bytes[0..5], b"%PDF-");
1603 }
1604
1605 #[test]
1606 fn test_document_metadata_all_fields() {
1607 let mut doc = Document::new();
1608
1609 doc.set_title("Test Document");
1611 doc.set_author("John Doe");
1612 doc.set_subject("Testing PDF metadata");
1613 doc.set_keywords("test, pdf, metadata");
1614 doc.set_creator("Test Suite");
1615 doc.set_producer("oxidize_pdf tests");
1616
1617 assert_eq!(doc.metadata.title.as_deref(), Some("Test Document"));
1619 assert_eq!(doc.metadata.author.as_deref(), Some("John Doe"));
1620 assert_eq!(
1621 doc.metadata.subject.as_deref(),
1622 Some("Testing PDF metadata")
1623 );
1624 assert_eq!(
1625 doc.metadata.keywords.as_deref(),
1626 Some("test, pdf, metadata")
1627 );
1628 assert_eq!(doc.metadata.creator.as_deref(), Some("Test Suite"));
1629 assert_eq!(doc.metadata.producer.as_deref(), Some("oxidize_pdf tests"));
1630 assert!(doc.metadata.creation_date.is_some());
1631 assert!(doc.metadata.modification_date.is_some());
1632 }
1633
1634 #[test]
1635 fn test_document_add_pages() {
1636 let mut doc = Document::new();
1637
1638 assert_eq!(doc.page_count(), 0);
1640
1641 let page1 = Page::a4();
1643 let page2 = Page::letter();
1644 let page3 = Page::legal();
1645
1646 doc.add_page(page1);
1647 assert_eq!(doc.page_count(), 1);
1648
1649 doc.add_page(page2);
1650 assert_eq!(doc.page_count(), 2);
1651
1652 doc.add_page(page3);
1653 assert_eq!(doc.page_count(), 3);
1654
1655 let result = doc.to_bytes();
1657 assert!(result.is_ok());
1658 }
1659
1660 #[test]
1661 fn test_document_default_font_encoding() {
1662 let mut doc = Document::new();
1663
1664 assert!(doc.default_font_encoding.is_none());
1666
1667 doc.set_default_font_encoding(Some(FontEncoding::WinAnsiEncoding));
1669 assert_eq!(
1670 doc.default_font_encoding(),
1671 Some(FontEncoding::WinAnsiEncoding)
1672 );
1673
1674 doc.set_default_font_encoding(Some(FontEncoding::MacRomanEncoding));
1676 assert_eq!(
1677 doc.default_font_encoding(),
1678 Some(FontEncoding::MacRomanEncoding)
1679 );
1680 }
1681
1682 #[test]
1683 fn test_document_compression_setting() {
1684 let mut doc = Document::new();
1685
1686 assert!(doc.compress);
1688
1689 doc.set_compress(false);
1691 assert!(!doc.compress);
1692
1693 doc.set_compress(true);
1695 assert!(doc.compress);
1696 }
1697
1698 #[test]
1699 fn test_document_with_empty_pages() {
1700 let mut doc = Document::new();
1701
1702 doc.add_page(Page::a4());
1704
1705 let result = doc.to_bytes();
1707 assert!(result.is_ok());
1708
1709 let pdf_bytes = result.unwrap();
1710 assert!(!pdf_bytes.is_empty());
1711 assert!(pdf_bytes.starts_with(b"%PDF-"));
1712 }
1713
1714 #[test]
1715 fn test_document_with_multiple_page_sizes() {
1716 let mut doc = Document::new();
1717
1718 doc.add_page(Page::a4()); doc.add_page(Page::letter()); doc.add_page(Page::legal()); doc.add_page(Page::a4()); doc.add_page(Page::new(200.0, 300.0)); assert_eq!(doc.page_count(), 5);
1726
1727 let result = doc.to_bytes();
1731 assert!(result.is_ok());
1732 }
1733
1734 #[test]
1735 fn test_document_metadata_dates() {
1736 use chrono::Duration;
1737
1738 let doc = Document::new();
1739
1740 assert!(doc.metadata.creation_date.is_some());
1742 assert!(doc.metadata.modification_date.is_some());
1743
1744 if let (Some(created), Some(modified)) =
1745 (doc.metadata.creation_date, doc.metadata.modification_date)
1746 {
1747 let diff = modified - created;
1749 assert!(diff < Duration::seconds(1));
1750 }
1751 }
1752
1753 #[test]
1754 fn test_document_builder_pattern() {
1755 let mut doc = Document::new();
1757 doc.set_title("Fluent");
1758 doc.set_author("Builder");
1759 doc.set_compress(true);
1760
1761 assert_eq!(doc.metadata.title.as_deref(), Some("Fluent"));
1762 assert_eq!(doc.metadata.author.as_deref(), Some("Builder"));
1763 assert!(doc.compress);
1764 }
1765
1766 #[test]
1767 fn test_xref_streams_functionality() {
1768 use crate::{Document, Font, Page};
1769
1770 let mut doc = Document::new();
1772 assert!(!doc.use_xref_streams);
1773
1774 let mut page = Page::a4();
1775 page.text()
1776 .set_font(Font::Helvetica, 12.0)
1777 .at(100.0, 700.0)
1778 .write("Testing XRef Streams")
1779 .unwrap();
1780
1781 doc.add_page(page);
1782
1783 let pdf_without_xref = doc.to_bytes().unwrap();
1785
1786 let pdf_str = String::from_utf8_lossy(&pdf_without_xref);
1788 assert!(pdf_str.contains("xref"), "Traditional xref table not found");
1789 assert!(
1790 !pdf_str.contains("/Type /XRef"),
1791 "XRef stream found when it shouldn't be"
1792 );
1793
1794 doc.enable_xref_streams(true);
1796 assert!(doc.use_xref_streams);
1797
1798 let pdf_with_xref = doc.to_bytes().unwrap();
1800
1801 let pdf_str = String::from_utf8_lossy(&pdf_with_xref);
1803 assert!(
1805 pdf_str.contains("/Type /XRef") || pdf_str.contains("stream"),
1806 "XRef stream not found when enabled"
1807 );
1808
1809 assert!(
1811 pdf_str.contains("PDF-1.5"),
1812 "PDF version not set to 1.5 for xref streams"
1813 );
1814
1815 let mut doc2 = Document::new();
1817 doc2.enable_xref_streams(true);
1818 doc2.set_title("XRef Streams Test");
1819 doc2.set_author("oxidize-pdf");
1820
1821 assert!(doc2.use_xref_streams);
1822 assert_eq!(doc2.metadata.title.as_deref(), Some("XRef Streams Test"));
1823 assert_eq!(doc2.metadata.author.as_deref(), Some("oxidize-pdf"));
1824 }
1825
1826 #[test]
1827 fn test_document_save_to_vec() {
1828 let mut doc = Document::new();
1829 doc.set_title("Test Save");
1830 doc.add_page(Page::a4());
1831
1832 let bytes_result = doc.to_bytes();
1834 assert!(bytes_result.is_ok());
1835
1836 let bytes = bytes_result.unwrap();
1837 assert!(!bytes.is_empty());
1838 assert!(bytes.starts_with(b"%PDF-"));
1839 assert!(bytes.ends_with(b"%%EOF") || bytes.ends_with(b"%%EOF\n"));
1840 }
1841
1842 #[test]
1843 fn test_document_unicode_metadata() {
1844 let mut doc = Document::new();
1845
1846 doc.set_title("日本語のタイトル");
1848 doc.set_author("作者名 😀");
1849 doc.set_subject("Тема документа");
1850 doc.set_keywords("كلمات, מפתח, 关键词");
1851
1852 assert_eq!(doc.metadata.title.as_deref(), Some("日本語のタイトル"));
1853 assert_eq!(doc.metadata.author.as_deref(), Some("作者名 😀"));
1854 assert_eq!(doc.metadata.subject.as_deref(), Some("Тема документа"));
1855 assert_eq!(
1856 doc.metadata.keywords.as_deref(),
1857 Some("كلمات, מפתח, 关键词")
1858 );
1859 }
1860
1861 #[test]
1862 fn test_document_page_iteration() {
1863 let mut doc = Document::new();
1864
1865 for i in 0..5 {
1867 let mut page = Page::a4();
1868 let gc = page.graphics();
1869 gc.begin_text();
1870 let _ = gc.show_text(&format!("Page {}", i + 1));
1871 gc.end_text();
1872 doc.add_page(page);
1873 }
1874
1875 assert_eq!(doc.page_count(), 5);
1877
1878 let result = doc.to_bytes();
1880 assert!(result.is_ok());
1881 }
1882
1883 #[test]
1884 fn test_document_with_graphics_content() {
1885 let mut doc = Document::new();
1886
1887 let mut page = Page::a4();
1888 {
1889 let gc = page.graphics();
1890
1891 gc.save_state();
1893
1894 gc.rectangle(100.0, 100.0, 200.0, 150.0);
1896 gc.stroke();
1897
1898 gc.move_to(300.0, 300.0);
1900 gc.circle(300.0, 300.0, 50.0);
1901 gc.fill();
1902
1903 gc.begin_text();
1905 gc.set_text_position(100.0, 500.0);
1906 let _ = gc.show_text("Graphics Test");
1907 gc.end_text();
1908
1909 gc.restore_state();
1910 }
1911
1912 doc.add_page(page);
1913
1914 let result = doc.to_bytes();
1916 assert!(result.is_ok());
1917 }
1918
1919 #[test]
1920 fn test_document_producer_version() {
1921 let doc = Document::new();
1922
1923 assert!(doc.metadata.producer.is_some());
1925 if let Some(producer) = &doc.metadata.producer {
1926 assert!(producer.contains("oxidize_pdf"));
1927 assert!(producer.contains(env!("CARGO_PKG_VERSION")));
1928 }
1929 }
1930
1931 #[test]
1932 fn test_document_empty_metadata_fields() {
1933 let mut doc = Document::new();
1934
1935 doc.set_title("");
1937 doc.set_author("");
1938 doc.set_subject("");
1939 doc.set_keywords("");
1940
1941 assert_eq!(doc.metadata.title.as_deref(), Some(""));
1943 assert_eq!(doc.metadata.author.as_deref(), Some(""));
1944 assert_eq!(doc.metadata.subject.as_deref(), Some(""));
1945 assert_eq!(doc.metadata.keywords.as_deref(), Some(""));
1946 }
1947
1948 #[test]
1949 fn test_document_very_long_metadata() {
1950 let mut doc = Document::new();
1951
1952 let long_title = "A".repeat(1000);
1954 let long_author = "B".repeat(500);
1955 let long_keywords = vec!["keyword"; 100].join(", ");
1956
1957 doc.set_title(&long_title);
1958 doc.set_author(&long_author);
1959 doc.set_keywords(&long_keywords);
1960
1961 assert_eq!(doc.metadata.title.as_deref(), Some(long_title.as_str()));
1962 assert_eq!(doc.metadata.author.as_deref(), Some(long_author.as_str()));
1963 assert!(doc.metadata.keywords.as_ref().unwrap().len() > 500);
1964 }
1965 }
1966}