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::semantic::{BoundingBox, EntityType, RelationType, SemanticEntity};
8use crate::structure::{NamedDestinations, OutlineTree, PageTree};
9use crate::text::{FontEncoding, FontWithEncoding};
10use crate::writer::PdfWriter;
11use chrono::{DateTime, Local, Utc};
12use std::collections::{HashMap, HashSet};
13use std::sync::Arc;
14
15mod encryption;
16pub use encryption::{DocumentEncryption, EncryptionStrength};
17
18pub struct Document {
35 pub(crate) pages: Vec<Page>,
36 #[allow(dead_code)]
37 pub(crate) objects: HashMap<ObjectId, Object>,
38 #[allow(dead_code)]
39 pub(crate) next_object_id: u32,
40 pub(crate) metadata: DocumentMetadata,
41 pub(crate) encryption: Option<DocumentEncryption>,
42 pub(crate) outline: Option<OutlineTree>,
43 pub(crate) named_destinations: Option<NamedDestinations>,
44 #[allow(dead_code)]
45 pub(crate) page_tree: Option<PageTree>,
46 pub(crate) page_labels: Option<PageLabelTree>,
47 pub(crate) default_font_encoding: Option<FontEncoding>,
49 pub(crate) acro_form: Option<AcroForm>,
51 pub(crate) form_manager: Option<FormManager>,
53 pub(crate) compress: bool,
55 pub(crate) use_xref_streams: bool,
57 pub(crate) custom_fonts: FontCache,
59 #[allow(dead_code)]
61 pub(crate) embedded_fonts: HashMap<String, ObjectId>,
62 pub(crate) used_characters: HashSet<char>,
64 pub(crate) open_action: Option<crate::actions::Action>,
66 pub(crate) viewer_preferences: Option<crate::viewer_preferences::ViewerPreferences>,
68 pub(crate) semantic_entities: Vec<SemanticEntity>,
70}
71
72#[derive(Debug, Clone)]
74pub struct DocumentMetadata {
75 pub title: Option<String>,
77 pub author: Option<String>,
79 pub subject: Option<String>,
81 pub keywords: Option<String>,
83 pub creator: Option<String>,
85 pub producer: Option<String>,
87 pub creation_date: Option<DateTime<Utc>>,
89 pub modification_date: Option<DateTime<Utc>>,
91}
92
93impl Default for DocumentMetadata {
94 fn default() -> Self {
95 let now = Utc::now();
96 Self {
97 title: None,
98 author: None,
99 subject: None,
100 keywords: None,
101 creator: Some("oxidize_pdf".to_string()),
102 producer: Some(format!("oxidize_pdf v{}", env!("CARGO_PKG_VERSION"))),
103 creation_date: Some(now),
104 modification_date: Some(now),
105 }
106 }
107}
108
109impl Document {
110 pub fn new() -> Self {
112 Self {
113 pages: Vec::new(),
114 objects: HashMap::new(),
115 next_object_id: 1,
116 metadata: DocumentMetadata::default(),
117 encryption: None,
118 outline: None,
119 named_destinations: None,
120 page_tree: None,
121 page_labels: None,
122 default_font_encoding: None,
123 acro_form: None,
124 form_manager: None,
125 compress: true, use_xref_streams: false, custom_fonts: FontCache::new(),
128 embedded_fonts: HashMap::new(),
129 used_characters: HashSet::new(),
130 open_action: None,
131 viewer_preferences: None,
132 semantic_entities: Vec::new(),
133 }
134 }
135
136 pub fn add_page(&mut self, page: Page) {
138 if let Some(used_chars) = page.get_used_characters() {
140 self.used_characters.extend(used_chars);
141 }
142 self.pages.push(page);
143 }
144
145 pub fn set_title(&mut self, title: impl Into<String>) {
147 self.metadata.title = Some(title.into());
148 }
149
150 pub fn set_author(&mut self, author: impl Into<String>) {
152 self.metadata.author = Some(author.into());
153 }
154
155 pub fn set_form_manager(&mut self, form_manager: FormManager) {
157 self.form_manager = Some(form_manager);
158 }
159
160 pub fn set_subject(&mut self, subject: impl Into<String>) {
162 self.metadata.subject = Some(subject.into());
163 }
164
165 pub fn set_keywords(&mut self, keywords: impl Into<String>) {
167 self.metadata.keywords = Some(keywords.into());
168 }
169
170 pub fn set_encryption(&mut self, encryption: DocumentEncryption) {
172 self.encryption = Some(encryption);
173 }
174
175 pub fn encrypt_with_passwords(
177 &mut self,
178 user_password: impl Into<String>,
179 owner_password: impl Into<String>,
180 ) {
181 self.encryption = Some(DocumentEncryption::with_passwords(
182 user_password,
183 owner_password,
184 ));
185 }
186
187 pub fn is_encrypted(&self) -> bool {
189 self.encryption.is_some()
190 }
191
192 pub fn set_open_action(&mut self, action: crate::actions::Action) {
194 self.open_action = Some(action);
195 }
196
197 pub fn open_action(&self) -> Option<&crate::actions::Action> {
199 self.open_action.as_ref()
200 }
201
202 pub fn set_viewer_preferences(
204 &mut self,
205 preferences: crate::viewer_preferences::ViewerPreferences,
206 ) {
207 self.viewer_preferences = Some(preferences);
208 }
209
210 pub fn viewer_preferences(&self) -> Option<&crate::viewer_preferences::ViewerPreferences> {
212 self.viewer_preferences.as_ref()
213 }
214
215 pub fn set_outline(&mut self, outline: OutlineTree) {
217 self.outline = Some(outline);
218 }
219
220 pub fn outline(&self) -> Option<&OutlineTree> {
222 self.outline.as_ref()
223 }
224
225 pub fn outline_mut(&mut self) -> Option<&mut OutlineTree> {
227 self.outline.as_mut()
228 }
229
230 pub fn set_named_destinations(&mut self, destinations: NamedDestinations) {
232 self.named_destinations = Some(destinations);
233 }
234
235 pub fn named_destinations(&self) -> Option<&NamedDestinations> {
237 self.named_destinations.as_ref()
238 }
239
240 pub fn named_destinations_mut(&mut self) -> Option<&mut NamedDestinations> {
242 self.named_destinations.as_mut()
243 }
244
245 pub fn set_page_labels(&mut self, labels: PageLabelTree) {
247 self.page_labels = Some(labels);
248 }
249
250 pub fn page_labels(&self) -> Option<&PageLabelTree> {
252 self.page_labels.as_ref()
253 }
254
255 pub fn page_labels_mut(&mut self) -> Option<&mut PageLabelTree> {
257 self.page_labels.as_mut()
258 }
259
260 pub fn get_page_label(&self, page_index: u32) -> String {
262 self.page_labels
263 .as_ref()
264 .and_then(|labels| labels.get_label(page_index))
265 .unwrap_or_else(|| (page_index + 1).to_string())
266 }
267
268 pub fn get_all_page_labels(&self) -> Vec<String> {
270 let page_count = self.pages.len() as u32;
271 if let Some(labels) = &self.page_labels {
272 labels.get_all_labels(page_count)
273 } else {
274 (1..=page_count).map(|i| i.to_string()).collect()
275 }
276 }
277
278 pub fn set_creator(&mut self, creator: impl Into<String>) {
280 self.metadata.creator = Some(creator.into());
281 }
282
283 pub fn set_producer(&mut self, producer: impl Into<String>) {
285 self.metadata.producer = Some(producer.into());
286 }
287
288 pub fn set_creation_date(&mut self, date: DateTime<Utc>) {
290 self.metadata.creation_date = Some(date);
291 }
292
293 pub fn set_creation_date_local(&mut self, date: DateTime<Local>) {
295 self.metadata.creation_date = Some(date.with_timezone(&Utc));
296 }
297
298 pub fn set_modification_date(&mut self, date: DateTime<Utc>) {
300 self.metadata.modification_date = Some(date);
301 }
302
303 pub fn set_modification_date_local(&mut self, date: DateTime<Local>) {
305 self.metadata.modification_date = Some(date.with_timezone(&Utc));
306 }
307
308 pub fn update_modification_date(&mut self) {
310 self.metadata.modification_date = Some(Utc::now());
311 }
312
313 pub fn set_default_font_encoding(&mut self, encoding: Option<FontEncoding>) {
328 self.default_font_encoding = encoding;
329 }
330
331 pub fn default_font_encoding(&self) -> Option<FontEncoding> {
333 self.default_font_encoding
334 }
335
336 #[allow(dead_code)]
341 pub(crate) fn get_fonts_with_encodings(&self) -> Vec<FontWithEncoding> {
342 let mut fonts_used = HashSet::new();
343
344 for page in &self.pages {
346 for font in page.get_used_fonts() {
348 let font_with_encoding = match self.default_font_encoding {
349 Some(default_encoding) => FontWithEncoding::new(font, Some(default_encoding)),
350 None => FontWithEncoding::without_encoding(font),
351 };
352 fonts_used.insert(font_with_encoding);
353 }
354 }
355
356 fonts_used.into_iter().collect()
357 }
358
359 pub fn add_font(
370 &mut self,
371 name: impl Into<String>,
372 path: impl AsRef<std::path::Path>,
373 ) -> Result<()> {
374 let name = name.into();
375 let font = CustomFont::from_file(&name, path)?;
376 self.custom_fonts.add_font(name, font)?;
377 Ok(())
378 }
379
380 pub fn add_font_from_bytes(&mut self, name: impl Into<String>, data: Vec<u8>) -> Result<()> {
392 let name = name.into();
393 let font = CustomFont::from_bytes(&name, data)?;
394 self.custom_fonts.add_font(name, font)?;
395 Ok(())
396 }
397
398 #[allow(dead_code)]
400 pub(crate) fn get_custom_font(&self, name: &str) -> Option<Arc<CustomFont>> {
401 self.custom_fonts.get_font(name)
402 }
403
404 pub fn has_custom_font(&self, name: &str) -> bool {
406 self.custom_fonts.has_font(name)
407 }
408
409 pub fn custom_font_names(&self) -> Vec<String> {
411 self.custom_fonts.font_names()
412 }
413
414 pub fn page_count(&self) -> usize {
416 self.pages.len()
417 }
418
419 pub fn acro_form(&self) -> Option<&AcroForm> {
421 self.acro_form.as_ref()
422 }
423
424 pub fn acro_form_mut(&mut self) -> Option<&mut AcroForm> {
426 self.acro_form.as_mut()
427 }
428
429 pub fn enable_forms(&mut self) -> &mut FormManager {
432 if self.form_manager.is_none() {
433 self.form_manager = Some(FormManager::new());
434 }
435 if self.acro_form.is_none() {
436 self.acro_form = Some(AcroForm::new());
437 }
438 self.form_manager
440 .as_mut()
441 .expect("FormManager should exist after initialization")
442 }
443
444 pub fn disable_forms(&mut self) {
446 self.acro_form = None;
447 self.form_manager = None;
448 }
449
450 pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
456 self.update_modification_date();
458
459 let config = crate::writer::WriterConfig {
461 use_xref_streams: self.use_xref_streams,
462 pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
463 compress_streams: self.compress,
464 };
465
466 use std::io::BufWriter;
467 let file = std::fs::File::create(path)?;
468 let writer = BufWriter::new(file);
469 let mut pdf_writer = PdfWriter::with_config(writer, config);
470
471 pdf_writer.write_document(self)?;
472 Ok(())
473 }
474
475 pub fn save_with_config(
481 &mut self,
482 path: impl AsRef<std::path::Path>,
483 config: crate::writer::WriterConfig,
484 ) -> Result<()> {
485 use std::io::BufWriter;
486
487 self.update_modification_date();
489
490 let file = std::fs::File::create(path)?;
493 let writer = BufWriter::new(file);
494 let mut pdf_writer = PdfWriter::with_config(writer, config);
495 pdf_writer.write_document(self)?;
496 Ok(())
497 }
498
499 pub fn save_with_custom_values(
513 &mut self,
514 path: impl AsRef<std::path::Path>,
515 custom_values: &std::collections::HashMap<String, String>,
516 ) -> Result<()> {
517 let total_pages = self.pages.len();
519 for (index, page) in self.pages.iter_mut().enumerate() {
520 let page_content = page.generate_content_with_page_info(
522 Some(index + 1),
523 Some(total_pages),
524 Some(custom_values),
525 )?;
526 page.set_content(page_content);
528 }
529
530 self.save(path)
532 }
533
534 pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
540 self.update_modification_date();
542
543 let mut writer = PdfWriter::new_with_writer(buffer);
544 writer.write_document(self)?;
545 Ok(())
546 }
547
548 #[allow(dead_code)]
549 pub(crate) fn allocate_object_id(&mut self) -> ObjectId {
550 let id = ObjectId::new(self.next_object_id, 0);
551 self.next_object_id += 1;
552 id
553 }
554
555 #[allow(dead_code)]
556 pub(crate) fn add_object(&mut self, obj: Object) -> ObjectId {
557 let id = self.allocate_object_id();
558 self.objects.insert(id, obj);
559 id
560 }
561
562 pub fn set_compress(&mut self, compress: bool) {
589 self.compress = compress;
590 }
591
592 pub fn enable_xref_streams(&mut self, enable: bool) -> &mut Self {
610 self.use_xref_streams = enable;
611 self
612 }
613
614 pub fn get_compress(&self) -> bool {
620 self.compress
621 }
622
623 pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
651 self.update_modification_date();
653
654 let mut buffer = Vec::new();
656
657 let config = crate::writer::WriterConfig {
659 use_xref_streams: self.use_xref_streams,
660 pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
661 compress_streams: self.compress,
662 };
663
664 let mut writer = PdfWriter::with_config(&mut buffer, config);
666 writer.write_document(self)?;
667
668 Ok(buffer)
669 }
670
671 pub fn to_bytes_with_config(&mut self, config: crate::writer::WriterConfig) -> Result<Vec<u8>> {
710 self.update_modification_date();
712
713 let mut buffer = Vec::new();
717
718 let mut writer = PdfWriter::with_config(&mut buffer, config);
720 writer.write_document(self)?;
721
722 Ok(buffer)
723 }
724
725 pub fn mark_entity(
751 &mut self,
752 id: impl Into<String>,
753 entity_type: EntityType,
754 bounds: BoundingBox,
755 ) -> String {
756 let entity_id = id.into();
757 let entity = SemanticEntity::new(entity_id.clone(), entity_type, bounds);
758 self.semantic_entities.push(entity);
759 entity_id
760 }
761
762 pub fn set_entity_content(&mut self, entity_id: &str, content: impl Into<String>) -> bool {
764 if let Some(entity) = self
765 .semantic_entities
766 .iter_mut()
767 .find(|e| e.id == entity_id)
768 {
769 entity.content = content.into();
770 true
771 } else {
772 false
773 }
774 }
775
776 pub fn add_entity_metadata(
778 &mut self,
779 entity_id: &str,
780 key: impl Into<String>,
781 value: impl Into<String>,
782 ) -> bool {
783 if let Some(entity) = self
784 .semantic_entities
785 .iter_mut()
786 .find(|e| e.id == entity_id)
787 {
788 entity.metadata.properties.insert(key.into(), value.into());
789 true
790 } else {
791 false
792 }
793 }
794
795 pub fn set_entity_confidence(&mut self, entity_id: &str, confidence: f32) -> bool {
797 if let Some(entity) = self
798 .semantic_entities
799 .iter_mut()
800 .find(|e| e.id == entity_id)
801 {
802 entity.metadata.confidence = Some(confidence.clamp(0.0, 1.0));
803 true
804 } else {
805 false
806 }
807 }
808
809 pub fn relate_entities(
811 &mut self,
812 from_id: &str,
813 to_id: &str,
814 relation_type: RelationType,
815 ) -> bool {
816 let target_exists = self.semantic_entities.iter().any(|e| e.id == to_id);
818 if !target_exists {
819 return false;
820 }
821
822 if let Some(entity) = self.semantic_entities.iter_mut().find(|e| e.id == from_id) {
824 entity.relationships.push(crate::semantic::EntityRelation {
825 target_id: to_id.to_string(),
826 relation_type,
827 });
828 true
829 } else {
830 false
831 }
832 }
833
834 pub fn get_semantic_entities(&self) -> &[SemanticEntity] {
836 &self.semantic_entities
837 }
838
839 pub fn get_entities_by_type(&self, entity_type: EntityType) -> Vec<&SemanticEntity> {
841 self.semantic_entities
842 .iter()
843 .filter(|e| e.entity_type == entity_type)
844 .collect()
845 }
846
847 #[cfg(feature = "semantic")]
849 pub fn export_semantic_entities_json(&self) -> Result<String> {
850 serde_json::to_string_pretty(&self.semantic_entities)
851 .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
852 }
853
854 pub fn find_entity(&self, entity_id: &str) -> Option<&SemanticEntity> {
856 self.semantic_entities.iter().find(|e| e.id == entity_id)
857 }
858
859 pub fn remove_entity(&mut self, entity_id: &str) -> bool {
861 if let Some(pos) = self
862 .semantic_entities
863 .iter()
864 .position(|e| e.id == entity_id)
865 {
866 self.semantic_entities.remove(pos);
867 for entity in &mut self.semantic_entities {
869 entity.relationships.retain(|r| r.target_id != entity_id);
870 }
871 true
872 } else {
873 false
874 }
875 }
876
877 pub fn semantic_entity_count(&self) -> usize {
879 self.semantic_entities.len()
880 }
881
882 pub fn add_xmp_metadata(&mut self, _xmp_data: &str) -> Result<ObjectId> {
884 tracing::info!("XMP metadata embedding requested but not available in community edition");
887 Ok(ObjectId::new(9999, 0)) }
889
890 pub fn get_xmp_metadata(&self) -> Result<Option<String>> {
892 tracing::info!("XMP metadata extraction requested but not available in community edition");
895 Ok(None)
896 }
897
898 pub fn extract_text(&self) -> Result<String> {
900 let mut text = String::new();
903 for (i, _page) in self.pages.iter().enumerate() {
904 text.push_str(&format!("Text from page {} (placeholder)\n", i + 1));
905 }
906 Ok(text)
907 }
908
909 pub fn extract_page_text(&self, page_index: usize) -> Result<String> {
911 if page_index < self.pages.len() {
912 Ok(format!("Text from page {} (placeholder)", page_index + 1))
913 } else {
914 Err(crate::error::PdfError::InvalidReference(format!(
915 "Page index {} out of bounds",
916 page_index
917 )))
918 }
919 }
920}
921
922impl Default for Document {
923 fn default() -> Self {
924 Self::new()
925 }
926}
927
928#[cfg(test)]
929mod tests {
930 use super::*;
931
932 #[test]
933 fn test_document_new() {
934 let doc = Document::new();
935 assert!(doc.pages.is_empty());
936 assert!(doc.objects.is_empty());
937 assert_eq!(doc.next_object_id, 1);
938 assert!(doc.metadata.title.is_none());
939 assert!(doc.metadata.author.is_none());
940 assert!(doc.metadata.subject.is_none());
941 assert!(doc.metadata.keywords.is_none());
942 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
943 assert!(doc
944 .metadata
945 .producer
946 .as_ref()
947 .unwrap()
948 .starts_with("oxidize_pdf"));
949 }
950
951 #[test]
952 fn test_document_default() {
953 let doc = Document::default();
954 assert!(doc.pages.is_empty());
955 assert_eq!(doc.next_object_id, 1);
956 }
957
958 #[test]
959 fn test_add_page() {
960 let mut doc = Document::new();
961 let page1 = Page::a4();
962 let page2 = Page::letter();
963
964 doc.add_page(page1);
965 assert_eq!(doc.pages.len(), 1);
966
967 doc.add_page(page2);
968 assert_eq!(doc.pages.len(), 2);
969 }
970
971 #[test]
972 fn test_set_title() {
973 let mut doc = Document::new();
974 assert!(doc.metadata.title.is_none());
975
976 doc.set_title("Test Document");
977 assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
978
979 doc.set_title(String::from("Another Title"));
980 assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
981 }
982
983 #[test]
984 fn test_set_author() {
985 let mut doc = Document::new();
986 assert!(doc.metadata.author.is_none());
987
988 doc.set_author("John Doe");
989 assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
990 }
991
992 #[test]
993 fn test_set_subject() {
994 let mut doc = Document::new();
995 assert!(doc.metadata.subject.is_none());
996
997 doc.set_subject("Test Subject");
998 assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
999 }
1000
1001 #[test]
1002 fn test_set_keywords() {
1003 let mut doc = Document::new();
1004 assert!(doc.metadata.keywords.is_none());
1005
1006 doc.set_keywords("test, pdf, rust");
1007 assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
1008 }
1009
1010 #[test]
1011 fn test_metadata_default() {
1012 let metadata = DocumentMetadata::default();
1013 assert!(metadata.title.is_none());
1014 assert!(metadata.author.is_none());
1015 assert!(metadata.subject.is_none());
1016 assert!(metadata.keywords.is_none());
1017 assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
1018 assert!(metadata
1019 .producer
1020 .as_ref()
1021 .unwrap()
1022 .starts_with("oxidize_pdf"));
1023 }
1024
1025 #[test]
1026 fn test_allocate_object_id() {
1027 let mut doc = Document::new();
1028
1029 let id1 = doc.allocate_object_id();
1030 assert_eq!(id1.number(), 1);
1031 assert_eq!(id1.generation(), 0);
1032 assert_eq!(doc.next_object_id, 2);
1033
1034 let id2 = doc.allocate_object_id();
1035 assert_eq!(id2.number(), 2);
1036 assert_eq!(id2.generation(), 0);
1037 assert_eq!(doc.next_object_id, 3);
1038 }
1039
1040 #[test]
1041 fn test_add_object() {
1042 let mut doc = Document::new();
1043 assert!(doc.objects.is_empty());
1044
1045 let obj = Object::Boolean(true);
1046 let id = doc.add_object(obj.clone());
1047
1048 assert_eq!(id.number(), 1);
1049 assert_eq!(doc.objects.len(), 1);
1050 assert!(doc.objects.contains_key(&id));
1051 }
1052
1053 #[test]
1054 fn test_write_to_buffer() {
1055 let mut doc = Document::new();
1056 doc.set_title("Buffer Test");
1057 doc.add_page(Page::a4());
1058
1059 let mut buffer = Vec::new();
1060 let result = doc.write(&mut buffer);
1061
1062 assert!(result.is_ok());
1063 assert!(!buffer.is_empty());
1064 assert!(buffer.starts_with(b"%PDF-1.7"));
1065 }
1066
1067 #[test]
1068 fn test_document_with_multiple_pages() {
1069 let mut doc = Document::new();
1070 doc.set_title("Multi-page Document");
1071 doc.set_author("Test Author");
1072 doc.set_subject("Testing multiple pages");
1073 doc.set_keywords("test, multiple, pages");
1074
1075 for _ in 0..5 {
1076 doc.add_page(Page::a4());
1077 }
1078
1079 assert_eq!(doc.pages.len(), 5);
1080 assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
1081 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1082 }
1083
1084 #[test]
1085 fn test_empty_document_write() {
1086 let mut doc = Document::new();
1087 let mut buffer = Vec::new();
1088
1089 let result = doc.write(&mut buffer);
1091 assert!(result.is_ok());
1092 assert!(!buffer.is_empty());
1093 assert!(buffer.starts_with(b"%PDF-1.7"));
1094 }
1095
1096 mod integration_tests {
1098 use super::*;
1099 use crate::graphics::Color;
1100 use crate::text::Font;
1101 use std::fs;
1102 use tempfile::TempDir;
1103
1104 #[test]
1105 fn test_document_writer_roundtrip() {
1106 let temp_dir = TempDir::new().unwrap();
1107 let file_path = temp_dir.path().join("test.pdf");
1108
1109 let mut doc = Document::new();
1111 doc.set_title("Integration Test");
1112 doc.set_author("Test Author");
1113 doc.set_subject("Writer Integration");
1114 doc.set_keywords("test, writer, integration");
1115
1116 let mut page = Page::a4();
1117 page.text()
1118 .set_font(Font::Helvetica, 12.0)
1119 .at(100.0, 700.0)
1120 .write("Integration Test Content")
1121 .unwrap();
1122
1123 doc.add_page(page);
1124
1125 let result = doc.save(&file_path);
1127 assert!(result.is_ok());
1128
1129 assert!(file_path.exists());
1131 let metadata = fs::metadata(&file_path).unwrap();
1132 assert!(metadata.len() > 0);
1133
1134 let content = fs::read(&file_path).unwrap();
1136 assert!(content.starts_with(b"%PDF-1.7"));
1137 assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
1139 }
1140
1141 #[test]
1142 fn test_document_with_complex_content() {
1143 let temp_dir = TempDir::new().unwrap();
1144 let file_path = temp_dir.path().join("complex.pdf");
1145
1146 let mut doc = Document::new();
1147 doc.set_title("Complex Content Test");
1148
1149 let mut page = Page::a4();
1151
1152 page.text()
1154 .set_font(Font::Helvetica, 14.0)
1155 .at(50.0, 750.0)
1156 .write("Complex Content Test")
1157 .unwrap();
1158
1159 page.graphics()
1161 .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
1162 .rectangle(50.0, 500.0, 200.0, 100.0)
1163 .fill();
1164
1165 page.graphics()
1166 .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
1167 .set_line_width(2.0)
1168 .move_to(50.0, 400.0)
1169 .line_to(250.0, 400.0)
1170 .stroke();
1171
1172 doc.add_page(page);
1173
1174 let result = doc.save(&file_path);
1176 assert!(result.is_ok());
1177 assert!(file_path.exists());
1178 }
1179
1180 #[test]
1181 fn test_document_multiple_pages_integration() {
1182 let temp_dir = TempDir::new().unwrap();
1183 let file_path = temp_dir.path().join("multipage.pdf");
1184
1185 let mut doc = Document::new();
1186 doc.set_title("Multi-page Integration Test");
1187
1188 for i in 1..=5 {
1190 let mut page = Page::a4();
1191
1192 page.text()
1193 .set_font(Font::Helvetica, 16.0)
1194 .at(50.0, 750.0)
1195 .write(&format!("Page {i}"))
1196 .unwrap();
1197
1198 page.text()
1199 .set_font(Font::Helvetica, 12.0)
1200 .at(50.0, 700.0)
1201 .write(&format!("This is the content for page {i}"))
1202 .unwrap();
1203
1204 let color = match i % 3 {
1206 0 => Color::rgb(1.0, 0.0, 0.0),
1207 1 => Color::rgb(0.0, 1.0, 0.0),
1208 _ => Color::rgb(0.0, 0.0, 1.0),
1209 };
1210
1211 page.graphics()
1212 .set_fill_color(color)
1213 .rectangle(50.0, 600.0, 100.0, 50.0)
1214 .fill();
1215
1216 doc.add_page(page);
1217 }
1218
1219 let result = doc.save(&file_path);
1221 assert!(result.is_ok());
1222 assert!(file_path.exists());
1223
1224 let metadata = fs::metadata(&file_path).unwrap();
1226 assert!(metadata.len() > 1000); }
1228
1229 #[test]
1230 fn test_document_metadata_persistence() {
1231 let temp_dir = TempDir::new().unwrap();
1232 let file_path = temp_dir.path().join("metadata.pdf");
1233
1234 let mut doc = Document::new();
1235 doc.set_title("Metadata Persistence Test");
1236 doc.set_author("Test Author");
1237 doc.set_subject("Testing metadata preservation");
1238 doc.set_keywords("metadata, persistence, test");
1239
1240 doc.add_page(Page::a4());
1241
1242 let result = doc.save(&file_path);
1244 assert!(result.is_ok());
1245
1246 let content = fs::read(&file_path).unwrap();
1248 let content_str = String::from_utf8_lossy(&content);
1249
1250 assert!(content_str.contains("Metadata Persistence Test"));
1252 assert!(content_str.contains("Test Author"));
1253 }
1254
1255 #[test]
1256 fn test_document_writer_error_handling() {
1257 let mut doc = Document::new();
1258 doc.add_page(Page::a4());
1259
1260 let result = doc.save("/invalid/path/test.pdf");
1262 assert!(result.is_err());
1263 }
1264
1265 #[test]
1266 fn test_document_object_management() {
1267 let mut doc = Document::new();
1268
1269 let obj1 = Object::Boolean(true);
1271 let obj2 = Object::Integer(42);
1272 let obj3 = Object::Real(std::f64::consts::PI);
1273
1274 let id1 = doc.add_object(obj1.clone());
1275 let id2 = doc.add_object(obj2.clone());
1276 let id3 = doc.add_object(obj3.clone());
1277
1278 assert_eq!(id1.number(), 1);
1279 assert_eq!(id2.number(), 2);
1280 assert_eq!(id3.number(), 3);
1281
1282 assert_eq!(doc.objects.len(), 3);
1283 assert!(doc.objects.contains_key(&id1));
1284 assert!(doc.objects.contains_key(&id2));
1285 assert!(doc.objects.contains_key(&id3));
1286
1287 assert_eq!(doc.objects.get(&id1), Some(&obj1));
1289 assert_eq!(doc.objects.get(&id2), Some(&obj2));
1290 assert_eq!(doc.objects.get(&id3), Some(&obj3));
1291 }
1292
1293 #[test]
1294 fn test_document_page_integration() {
1295 let mut doc = Document::new();
1296
1297 let page1 = Page::a4();
1299 let page2 = Page::letter();
1300 let mut page3 = Page::new(500.0, 400.0);
1301
1302 page3
1304 .text()
1305 .set_font(Font::Helvetica, 10.0)
1306 .at(25.0, 350.0)
1307 .write("Custom size page")
1308 .unwrap();
1309
1310 doc.add_page(page1);
1311 doc.add_page(page2);
1312 doc.add_page(page3);
1313
1314 assert_eq!(doc.pages.len(), 3);
1315
1316 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); }
1324
1325 #[test]
1326 fn test_document_content_generation() {
1327 let temp_dir = TempDir::new().unwrap();
1328 let file_path = temp_dir.path().join("content.pdf");
1329
1330 let mut doc = Document::new();
1331 doc.set_title("Content Generation Test");
1332
1333 let mut page = Page::a4();
1334
1335 for i in 0..10 {
1337 let y_pos = 700.0 - (i as f64 * 30.0);
1338 page.text()
1339 .set_font(Font::Helvetica, 12.0)
1340 .at(50.0, y_pos)
1341 .write(&format!("Generated line {}", i + 1))
1342 .unwrap();
1343 }
1344
1345 doc.add_page(page);
1346
1347 let result = doc.save(&file_path);
1349 assert!(result.is_ok());
1350 assert!(file_path.exists());
1351
1352 let metadata = fs::metadata(&file_path).unwrap();
1354 assert!(metadata.len() > 500); }
1356
1357 #[test]
1358 fn test_document_buffer_vs_file_write() {
1359 let temp_dir = TempDir::new().unwrap();
1360 let file_path = temp_dir.path().join("buffer_vs_file.pdf");
1361
1362 let mut doc = Document::new();
1363 doc.set_title("Buffer vs File Test");
1364 doc.add_page(Page::a4());
1365
1366 let mut buffer = Vec::new();
1368 let buffer_result = doc.write(&mut buffer);
1369 assert!(buffer_result.is_ok());
1370
1371 let file_result = doc.save(&file_path);
1373 assert!(file_result.is_ok());
1374
1375 let file_content = fs::read(&file_path).unwrap();
1377
1378 assert!(buffer.starts_with(b"%PDF-1.7"));
1380 assert!(file_content.starts_with(b"%PDF-1.7"));
1381 assert!(buffer.ends_with(b"%%EOF\n"));
1382 assert!(file_content.ends_with(b"%%EOF\n"));
1383
1384 let buffer_str = String::from_utf8_lossy(&buffer);
1386 let file_str = String::from_utf8_lossy(&file_content);
1387 assert!(buffer_str.contains("Buffer vs File Test"));
1388 assert!(file_str.contains("Buffer vs File Test"));
1389 }
1390
1391 #[test]
1392 fn test_document_large_content_handling() {
1393 let temp_dir = TempDir::new().unwrap();
1394 let file_path = temp_dir.path().join("large_content.pdf");
1395
1396 let mut doc = Document::new();
1397 doc.set_title("Large Content Test");
1398
1399 let mut page = Page::a4();
1400
1401 let large_text =
1403 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(200);
1404 page.text()
1405 .set_font(Font::Helvetica, 10.0)
1406 .at(50.0, 750.0)
1407 .write(&large_text)
1408 .unwrap();
1409
1410 doc.add_page(page);
1411
1412 let result = doc.save(&file_path);
1414 assert!(result.is_ok());
1415 assert!(file_path.exists());
1416
1417 let metadata = fs::metadata(&file_path).unwrap();
1419 assert!(metadata.len() > 500); }
1421
1422 #[test]
1423 fn test_document_incremental_building() {
1424 let temp_dir = TempDir::new().unwrap();
1425 let file_path = temp_dir.path().join("incremental.pdf");
1426
1427 let mut doc = Document::new();
1428
1429 doc.set_title("Incremental Building Test");
1431
1432 let mut page1 = Page::a4();
1434 page1
1435 .text()
1436 .set_font(Font::Helvetica, 12.0)
1437 .at(50.0, 750.0)
1438 .write("First page content")
1439 .unwrap();
1440 doc.add_page(page1);
1441
1442 doc.set_author("Incremental Author");
1444 doc.set_subject("Incremental Subject");
1445
1446 let mut page2 = Page::a4();
1448 page2
1449 .text()
1450 .set_font(Font::Helvetica, 12.0)
1451 .at(50.0, 750.0)
1452 .write("Second page content")
1453 .unwrap();
1454 doc.add_page(page2);
1455
1456 doc.set_keywords("incremental, building, test");
1458
1459 let result = doc.save(&file_path);
1461 assert!(result.is_ok());
1462 assert!(file_path.exists());
1463
1464 assert_eq!(doc.pages.len(), 2);
1466 assert_eq!(
1467 doc.metadata.title,
1468 Some("Incremental Building Test".to_string())
1469 );
1470 assert_eq!(doc.metadata.author, Some("Incremental Author".to_string()));
1471 assert_eq!(
1472 doc.metadata.subject,
1473 Some("Incremental Subject".to_string())
1474 );
1475 assert_eq!(
1476 doc.metadata.keywords,
1477 Some("incremental, building, test".to_string())
1478 );
1479 }
1480
1481 #[test]
1482 fn test_document_concurrent_page_operations() {
1483 let mut doc = Document::new();
1484 doc.set_title("Concurrent Operations Test");
1485
1486 let mut pages = Vec::new();
1488
1489 for i in 0..5 {
1491 let mut page = Page::a4();
1492 page.text()
1493 .set_font(Font::Helvetica, 12.0)
1494 .at(50.0, 750.0)
1495 .write(&format!("Concurrent page {i}"))
1496 .unwrap();
1497 pages.push(page);
1498 }
1499
1500 for page in pages {
1502 doc.add_page(page);
1503 }
1504
1505 assert_eq!(doc.pages.len(), 5);
1506
1507 let temp_dir = TempDir::new().unwrap();
1509 let file_path = temp_dir.path().join("concurrent.pdf");
1510 let result = doc.save(&file_path);
1511 assert!(result.is_ok());
1512 }
1513
1514 #[test]
1515 fn test_document_memory_efficiency() {
1516 let mut doc = Document::new();
1517 doc.set_title("Memory Efficiency Test");
1518
1519 for i in 0..10 {
1521 let mut page = Page::a4();
1522 page.text()
1523 .set_font(Font::Helvetica, 12.0)
1524 .at(50.0, 700.0)
1525 .write(&format!("Memory test page {i}"))
1526 .unwrap();
1527 doc.add_page(page);
1528 }
1529
1530 let mut buffer = Vec::new();
1532 let result = doc.write(&mut buffer);
1533 assert!(result.is_ok());
1534 assert!(!buffer.is_empty());
1535
1536 assert!(buffer.len() < 1_000_000); }
1539
1540 #[test]
1541 fn test_document_creator_producer() {
1542 let mut doc = Document::new();
1543
1544 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1546 assert!(doc
1547 .metadata
1548 .producer
1549 .as_ref()
1550 .unwrap()
1551 .contains("oxidize_pdf"));
1552
1553 doc.set_creator("My Application");
1555 doc.set_producer("My PDF Library v1.0");
1556
1557 assert_eq!(doc.metadata.creator, Some("My Application".to_string()));
1558 assert_eq!(
1559 doc.metadata.producer,
1560 Some("My PDF Library v1.0".to_string())
1561 );
1562 }
1563
1564 #[test]
1565 fn test_document_dates() {
1566 use chrono::{TimeZone, Utc};
1567
1568 let mut doc = Document::new();
1569
1570 assert!(doc.metadata.creation_date.is_some());
1572 assert!(doc.metadata.modification_date.is_some());
1573
1574 let creation_date = Utc.with_ymd_and_hms(2023, 1, 1, 12, 0, 0).unwrap();
1576 let mod_date = Utc.with_ymd_and_hms(2023, 6, 15, 18, 30, 0).unwrap();
1577
1578 doc.set_creation_date(creation_date);
1579 doc.set_modification_date(mod_date);
1580
1581 assert_eq!(doc.metadata.creation_date, Some(creation_date));
1582 assert_eq!(doc.metadata.modification_date, Some(mod_date));
1583 }
1584
1585 #[test]
1586 fn test_document_dates_local() {
1587 use chrono::{Local, TimeZone};
1588
1589 let mut doc = Document::new();
1590
1591 let local_date = Local.with_ymd_and_hms(2023, 12, 25, 10, 30, 0).unwrap();
1593 doc.set_creation_date_local(local_date);
1594
1595 assert!(doc.metadata.creation_date.is_some());
1597 assert!(doc.metadata.creation_date.is_some());
1599 }
1600
1601 #[test]
1602 fn test_update_modification_date() {
1603 let mut doc = Document::new();
1604
1605 let initial_mod_date = doc.metadata.modification_date;
1606 assert!(initial_mod_date.is_some());
1607
1608 std::thread::sleep(std::time::Duration::from_millis(10));
1610
1611 doc.update_modification_date();
1612
1613 let new_mod_date = doc.metadata.modification_date;
1614 assert!(new_mod_date.is_some());
1615 assert!(new_mod_date.unwrap() > initial_mod_date.unwrap());
1616 }
1617
1618 #[test]
1619 fn test_document_save_updates_modification_date() {
1620 let temp_dir = TempDir::new().unwrap();
1621 let file_path = temp_dir.path().join("mod_date_test.pdf");
1622
1623 let mut doc = Document::new();
1624 doc.add_page(Page::a4());
1625
1626 let initial_mod_date = doc.metadata.modification_date;
1627
1628 std::thread::sleep(std::time::Duration::from_millis(10));
1630
1631 doc.save(&file_path).unwrap();
1632
1633 assert!(doc.metadata.modification_date.unwrap() > initial_mod_date.unwrap());
1635 }
1636
1637 #[test]
1638 fn test_document_metadata_complete() {
1639 let mut doc = Document::new();
1640
1641 doc.set_title("Complete Metadata Test");
1643 doc.set_author("Test Author");
1644 doc.set_subject("Testing all metadata fields");
1645 doc.set_keywords("test, metadata, complete");
1646 doc.set_creator("Test Application v1.0");
1647 doc.set_producer("oxidize_pdf Test Suite");
1648
1649 assert_eq!(
1651 doc.metadata.title,
1652 Some("Complete Metadata Test".to_string())
1653 );
1654 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1655 assert_eq!(
1656 doc.metadata.subject,
1657 Some("Testing all metadata fields".to_string())
1658 );
1659 assert_eq!(
1660 doc.metadata.keywords,
1661 Some("test, metadata, complete".to_string())
1662 );
1663 assert_eq!(
1664 doc.metadata.creator,
1665 Some("Test Application v1.0".to_string())
1666 );
1667 assert_eq!(
1668 doc.metadata.producer,
1669 Some("oxidize_pdf Test Suite".to_string())
1670 );
1671 assert!(doc.metadata.creation_date.is_some());
1672 assert!(doc.metadata.modification_date.is_some());
1673 }
1674
1675 #[test]
1676 fn test_document_to_bytes() {
1677 let mut doc = Document::new();
1678 doc.set_title("Test Document");
1679 doc.set_author("Test Author");
1680
1681 let page = Page::a4();
1682 doc.add_page(page);
1683
1684 let pdf_bytes = doc.to_bytes().unwrap();
1686
1687 assert!(!pdf_bytes.is_empty());
1689 assert!(pdf_bytes.len() > 100); let header = &pdf_bytes[0..5];
1693 assert_eq!(header, b"%PDF-");
1694
1695 let pdf_str = String::from_utf8_lossy(&pdf_bytes);
1697 assert!(pdf_str.contains("Test Document"));
1698 assert!(pdf_str.contains("Test Author"));
1699 }
1700
1701 #[test]
1702 fn test_document_to_bytes_with_config() {
1703 let mut doc = Document::new();
1704 doc.set_title("Test Document XRef");
1705
1706 let page = Page::a4();
1707 doc.add_page(page);
1708
1709 let config = crate::writer::WriterConfig {
1710 use_xref_streams: true,
1711 pdf_version: "1.5".to_string(),
1712 compress_streams: true,
1713 };
1714
1715 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1717
1718 assert!(!pdf_bytes.is_empty());
1720 assert!(pdf_bytes.len() > 100);
1721
1722 let header = String::from_utf8_lossy(&pdf_bytes[0..8]);
1724 assert!(header.contains("PDF-1.5"));
1725 }
1726
1727 #[test]
1728 fn test_to_bytes_vs_save_equivalence() {
1729 use std::fs;
1730 use tempfile::NamedTempFile;
1731
1732 let mut doc1 = Document::new();
1734 doc1.set_title("Equivalence Test");
1735 doc1.add_page(Page::a4());
1736
1737 let mut doc2 = Document::new();
1738 doc2.set_title("Equivalence Test");
1739 doc2.add_page(Page::a4());
1740
1741 let pdf_bytes = doc1.to_bytes().unwrap();
1743
1744 let temp_file = NamedTempFile::new().unwrap();
1746 doc2.save(temp_file.path()).unwrap();
1747 let file_bytes = fs::read(temp_file.path()).unwrap();
1748
1749 assert!(!pdf_bytes.is_empty());
1751 assert!(!file_bytes.is_empty());
1752 assert_eq!(&pdf_bytes[0..5], &file_bytes[0..5]); }
1754
1755 #[test]
1756 fn test_document_set_compress() {
1757 let mut doc = Document::new();
1758 doc.set_title("Compression Test");
1759 doc.add_page(Page::a4());
1760
1761 assert!(doc.get_compress());
1763
1764 doc.set_compress(true);
1766 let compressed_bytes = doc.to_bytes().unwrap();
1767
1768 doc.set_compress(false);
1770 let uncompressed_bytes = doc.to_bytes().unwrap();
1771
1772 assert!(!compressed_bytes.is_empty());
1774 assert!(!uncompressed_bytes.is_empty());
1775
1776 assert_eq!(&compressed_bytes[0..5], b"%PDF-");
1778 assert_eq!(&uncompressed_bytes[0..5], b"%PDF-");
1779 }
1780
1781 #[test]
1782 fn test_document_compression_config_inheritance() {
1783 let mut doc = Document::new();
1784 doc.set_title("Config Inheritance Test");
1785 doc.add_page(Page::a4());
1786
1787 doc.set_compress(false);
1789
1790 let config = crate::writer::WriterConfig {
1792 use_xref_streams: false,
1793 pdf_version: "1.7".to_string(),
1794 compress_streams: true,
1795 };
1796
1797 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1799
1800 assert!(!pdf_bytes.is_empty());
1802 assert_eq!(&pdf_bytes[0..5], b"%PDF-");
1803 }
1804
1805 #[test]
1806 fn test_document_metadata_all_fields() {
1807 let mut doc = Document::new();
1808
1809 doc.set_title("Test Document");
1811 doc.set_author("John Doe");
1812 doc.set_subject("Testing PDF metadata");
1813 doc.set_keywords("test, pdf, metadata");
1814 doc.set_creator("Test Suite");
1815 doc.set_producer("oxidize_pdf tests");
1816
1817 assert_eq!(doc.metadata.title.as_deref(), Some("Test Document"));
1819 assert_eq!(doc.metadata.author.as_deref(), Some("John Doe"));
1820 assert_eq!(
1821 doc.metadata.subject.as_deref(),
1822 Some("Testing PDF metadata")
1823 );
1824 assert_eq!(
1825 doc.metadata.keywords.as_deref(),
1826 Some("test, pdf, metadata")
1827 );
1828 assert_eq!(doc.metadata.creator.as_deref(), Some("Test Suite"));
1829 assert_eq!(doc.metadata.producer.as_deref(), Some("oxidize_pdf tests"));
1830 assert!(doc.metadata.creation_date.is_some());
1831 assert!(doc.metadata.modification_date.is_some());
1832 }
1833
1834 #[test]
1835 fn test_document_add_pages() {
1836 let mut doc = Document::new();
1837
1838 assert_eq!(doc.page_count(), 0);
1840
1841 let page1 = Page::a4();
1843 let page2 = Page::letter();
1844 let page3 = Page::legal();
1845
1846 doc.add_page(page1);
1847 assert_eq!(doc.page_count(), 1);
1848
1849 doc.add_page(page2);
1850 assert_eq!(doc.page_count(), 2);
1851
1852 doc.add_page(page3);
1853 assert_eq!(doc.page_count(), 3);
1854
1855 let result = doc.to_bytes();
1857 assert!(result.is_ok());
1858 }
1859
1860 #[test]
1861 fn test_document_default_font_encoding() {
1862 let mut doc = Document::new();
1863
1864 assert!(doc.default_font_encoding.is_none());
1866
1867 doc.set_default_font_encoding(Some(FontEncoding::WinAnsiEncoding));
1869 assert_eq!(
1870 doc.default_font_encoding(),
1871 Some(FontEncoding::WinAnsiEncoding)
1872 );
1873
1874 doc.set_default_font_encoding(Some(FontEncoding::MacRomanEncoding));
1876 assert_eq!(
1877 doc.default_font_encoding(),
1878 Some(FontEncoding::MacRomanEncoding)
1879 );
1880 }
1881
1882 #[test]
1883 fn test_document_compression_setting() {
1884 let mut doc = Document::new();
1885
1886 assert!(doc.compress);
1888
1889 doc.set_compress(false);
1891 assert!(!doc.compress);
1892
1893 doc.set_compress(true);
1895 assert!(doc.compress);
1896 }
1897
1898 #[test]
1899 fn test_document_with_empty_pages() {
1900 let mut doc = Document::new();
1901
1902 doc.add_page(Page::a4());
1904
1905 let result = doc.to_bytes();
1907 assert!(result.is_ok());
1908
1909 let pdf_bytes = result.unwrap();
1910 assert!(!pdf_bytes.is_empty());
1911 assert!(pdf_bytes.starts_with(b"%PDF-"));
1912 }
1913
1914 #[test]
1915 fn test_document_with_multiple_page_sizes() {
1916 let mut doc = Document::new();
1917
1918 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);
1926
1927 let result = doc.to_bytes();
1931 assert!(result.is_ok());
1932 }
1933
1934 #[test]
1935 fn test_document_metadata_dates() {
1936 use chrono::Duration;
1937
1938 let doc = Document::new();
1939
1940 assert!(doc.metadata.creation_date.is_some());
1942 assert!(doc.metadata.modification_date.is_some());
1943
1944 if let (Some(created), Some(modified)) =
1945 (doc.metadata.creation_date, doc.metadata.modification_date)
1946 {
1947 let diff = modified - created;
1949 assert!(diff < Duration::seconds(1));
1950 }
1951 }
1952
1953 #[test]
1954 fn test_document_builder_pattern() {
1955 let mut doc = Document::new();
1957 doc.set_title("Fluent");
1958 doc.set_author("Builder");
1959 doc.set_compress(true);
1960
1961 assert_eq!(doc.metadata.title.as_deref(), Some("Fluent"));
1962 assert_eq!(doc.metadata.author.as_deref(), Some("Builder"));
1963 assert!(doc.compress);
1964 }
1965
1966 #[test]
1967 fn test_xref_streams_functionality() {
1968 use crate::{Document, Font, Page};
1969
1970 let mut doc = Document::new();
1972 assert!(!doc.use_xref_streams);
1973
1974 let mut page = Page::a4();
1975 page.text()
1976 .set_font(Font::Helvetica, 12.0)
1977 .at(100.0, 700.0)
1978 .write("Testing XRef Streams")
1979 .unwrap();
1980
1981 doc.add_page(page);
1982
1983 let pdf_without_xref = doc.to_bytes().unwrap();
1985
1986 let pdf_str = String::from_utf8_lossy(&pdf_without_xref);
1988 assert!(pdf_str.contains("xref"), "Traditional xref table not found");
1989 assert!(
1990 !pdf_str.contains("/Type /XRef"),
1991 "XRef stream found when it shouldn't be"
1992 );
1993
1994 doc.enable_xref_streams(true);
1996 assert!(doc.use_xref_streams);
1997
1998 let pdf_with_xref = doc.to_bytes().unwrap();
2000
2001 let pdf_str = String::from_utf8_lossy(&pdf_with_xref);
2003 assert!(
2005 pdf_str.contains("/Type /XRef") || pdf_str.contains("stream"),
2006 "XRef stream not found when enabled"
2007 );
2008
2009 assert!(
2011 pdf_str.contains("PDF-1.5"),
2012 "PDF version not set to 1.5 for xref streams"
2013 );
2014
2015 let mut doc2 = Document::new();
2017 doc2.enable_xref_streams(true);
2018 doc2.set_title("XRef Streams Test");
2019 doc2.set_author("oxidize-pdf");
2020
2021 assert!(doc2.use_xref_streams);
2022 assert_eq!(doc2.metadata.title.as_deref(), Some("XRef Streams Test"));
2023 assert_eq!(doc2.metadata.author.as_deref(), Some("oxidize-pdf"));
2024 }
2025
2026 #[test]
2027 fn test_document_save_to_vec() {
2028 let mut doc = Document::new();
2029 doc.set_title("Test Save");
2030 doc.add_page(Page::a4());
2031
2032 let bytes_result = doc.to_bytes();
2034 assert!(bytes_result.is_ok());
2035
2036 let bytes = bytes_result.unwrap();
2037 assert!(!bytes.is_empty());
2038 assert!(bytes.starts_with(b"%PDF-"));
2039 assert!(bytes.ends_with(b"%%EOF") || bytes.ends_with(b"%%EOF\n"));
2040 }
2041
2042 #[test]
2043 fn test_document_unicode_metadata() {
2044 let mut doc = Document::new();
2045
2046 doc.set_title("日本語のタイトル");
2048 doc.set_author("作者名 😀");
2049 doc.set_subject("Тема документа");
2050 doc.set_keywords("كلمات, מפתח, 关键词");
2051
2052 assert_eq!(doc.metadata.title.as_deref(), Some("日本語のタイトル"));
2053 assert_eq!(doc.metadata.author.as_deref(), Some("作者名 😀"));
2054 assert_eq!(doc.metadata.subject.as_deref(), Some("Тема документа"));
2055 assert_eq!(
2056 doc.metadata.keywords.as_deref(),
2057 Some("كلمات, מפתח, 关键词")
2058 );
2059 }
2060
2061 #[test]
2062 fn test_document_page_iteration() {
2063 let mut doc = Document::new();
2064
2065 for i in 0..5 {
2067 let mut page = Page::a4();
2068 let gc = page.graphics();
2069 gc.begin_text();
2070 let _ = gc.show_text(&format!("Page {}", i + 1));
2071 gc.end_text();
2072 doc.add_page(page);
2073 }
2074
2075 assert_eq!(doc.page_count(), 5);
2077
2078 let result = doc.to_bytes();
2080 assert!(result.is_ok());
2081 }
2082
2083 #[test]
2084 fn test_document_with_graphics_content() {
2085 let mut doc = Document::new();
2086
2087 let mut page = Page::a4();
2088 {
2089 let gc = page.graphics();
2090
2091 gc.save_state();
2093
2094 gc.rectangle(100.0, 100.0, 200.0, 150.0);
2096 gc.stroke();
2097
2098 gc.move_to(300.0, 300.0);
2100 gc.circle(300.0, 300.0, 50.0);
2101 gc.fill();
2102
2103 gc.begin_text();
2105 gc.set_text_position(100.0, 500.0);
2106 let _ = gc.show_text("Graphics Test");
2107 gc.end_text();
2108
2109 gc.restore_state();
2110 }
2111
2112 doc.add_page(page);
2113
2114 let result = doc.to_bytes();
2116 assert!(result.is_ok());
2117 }
2118
2119 #[test]
2120 fn test_document_producer_version() {
2121 let doc = Document::new();
2122
2123 assert!(doc.metadata.producer.is_some());
2125 if let Some(producer) = &doc.metadata.producer {
2126 assert!(producer.contains("oxidize_pdf"));
2127 assert!(producer.contains(env!("CARGO_PKG_VERSION")));
2128 }
2129 }
2130
2131 #[test]
2132 fn test_document_empty_metadata_fields() {
2133 let mut doc = Document::new();
2134
2135 doc.set_title("");
2137 doc.set_author("");
2138 doc.set_subject("");
2139 doc.set_keywords("");
2140
2141 assert_eq!(doc.metadata.title.as_deref(), Some(""));
2143 assert_eq!(doc.metadata.author.as_deref(), Some(""));
2144 assert_eq!(doc.metadata.subject.as_deref(), Some(""));
2145 assert_eq!(doc.metadata.keywords.as_deref(), Some(""));
2146 }
2147
2148 #[test]
2149 fn test_document_very_long_metadata() {
2150 let mut doc = Document::new();
2151
2152 let long_title = "A".repeat(1000);
2154 let long_author = "B".repeat(500);
2155 let long_keywords = vec!["keyword"; 100].join(", ");
2156
2157 doc.set_title(&long_title);
2158 doc.set_author(&long_author);
2159 doc.set_keywords(&long_keywords);
2160
2161 assert_eq!(doc.metadata.title.as_deref(), Some(long_title.as_str()));
2162 assert_eq!(doc.metadata.author.as_deref(), Some(long_author.as_str()));
2163 assert!(doc.metadata.keywords.as_ref().unwrap().len() > 500);
2164 }
2165 }
2166}