1use crate::error::Result;
2use crate::fonts::{Font as CustomFont, FontCache};
3use crate::forms::{AcroForm, FormManager};
4use crate::page::Page;
5use crate::page_labels::PageLabelTree;
6use crate::semantic::{BoundingBox, EntityType, RelationType, SemanticEntity};
7use crate::structure::{NamedDestinations, OutlineTree, StructTree};
8use crate::text::FontEncoding;
9use crate::writer::PdfWriter;
10use chrono::{DateTime, Local, Utc};
11use std::collections::HashSet;
12use std::sync::Arc;
13
14mod encryption;
15pub use encryption::{DocumentEncryption, EncryptionStrength};
16
17pub struct Document {
34 pub(crate) pages: Vec<Page>,
35 pub(crate) metadata: DocumentMetadata,
36 pub(crate) encryption: Option<DocumentEncryption>,
37 pub(crate) outline: Option<OutlineTree>,
38 pub(crate) named_destinations: Option<NamedDestinations>,
39 pub(crate) page_labels: Option<PageLabelTree>,
40 pub(crate) default_font_encoding: Option<FontEncoding>,
42 pub(crate) acro_form: Option<AcroForm>,
44 pub(crate) form_manager: Option<FormManager>,
46 pub(crate) compress: bool,
48 pub(crate) use_xref_streams: bool,
50 pub(crate) custom_fonts: FontCache,
52 pub(crate) used_characters: HashSet<char>,
54 pub(crate) open_action: Option<crate::actions::Action>,
56 pub(crate) viewer_preferences: Option<crate::viewer_preferences::ViewerPreferences>,
58 pub(crate) semantic_entities: Vec<SemanticEntity>,
60 pub(crate) struct_tree: Option<StructTree>,
62}
63
64#[derive(Debug, Clone)]
66pub struct DocumentMetadata {
67 pub title: Option<String>,
69 pub author: Option<String>,
71 pub subject: Option<String>,
73 pub keywords: Option<String>,
75 pub creator: Option<String>,
77 pub producer: Option<String>,
79 pub creation_date: Option<DateTime<Utc>>,
81 pub modification_date: Option<DateTime<Utc>>,
83}
84
85impl Default for DocumentMetadata {
86 fn default() -> Self {
87 let now = Utc::now();
88
89 let edition = "MIT";
90
91 Self {
92 title: None,
93 author: None,
94 subject: None,
95 keywords: None,
96 creator: Some("oxidize_pdf".to_string()),
97 producer: Some(format!(
98 "oxidize_pdf v{} ({})",
99 env!("CARGO_PKG_VERSION"),
100 edition
101 )),
102 creation_date: Some(now),
103 modification_date: Some(now),
104 }
105 }
106}
107
108impl Document {
109 pub fn new() -> Self {
111 Self {
112 pages: Vec::new(),
113 metadata: DocumentMetadata::default(),
114 encryption: None,
115 outline: None,
116 named_destinations: None,
117 page_labels: None,
118 default_font_encoding: None,
119 acro_form: None,
120 form_manager: None,
121 compress: true, use_xref_streams: false, custom_fonts: FontCache::new(),
124 used_characters: HashSet::new(),
125 open_action: None,
126 viewer_preferences: None,
127 semantic_entities: Vec::new(),
128 struct_tree: 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_struct_tree(&mut self, tree: StructTree) {
237 self.struct_tree = Some(tree);
238 }
239
240 pub fn struct_tree(&self) -> Option<&StructTree> {
242 self.struct_tree.as_ref()
243 }
244
245 pub fn struct_tree_mut(&mut self) -> Option<&mut StructTree> {
247 self.struct_tree.as_mut()
248 }
249
250 pub fn get_or_create_struct_tree(&mut self) -> &mut StructTree {
267 self.struct_tree.get_or_insert_with(StructTree::new)
268 }
269
270 pub fn set_outline(&mut self, outline: OutlineTree) {
272 self.outline = Some(outline);
273 }
274
275 pub fn outline(&self) -> Option<&OutlineTree> {
277 self.outline.as_ref()
278 }
279
280 pub fn outline_mut(&mut self) -> Option<&mut OutlineTree> {
282 self.outline.as_mut()
283 }
284
285 pub fn set_named_destinations(&mut self, destinations: NamedDestinations) {
287 self.named_destinations = Some(destinations);
288 }
289
290 pub fn named_destinations(&self) -> Option<&NamedDestinations> {
292 self.named_destinations.as_ref()
293 }
294
295 pub fn named_destinations_mut(&mut self) -> Option<&mut NamedDestinations> {
297 self.named_destinations.as_mut()
298 }
299
300 pub fn set_page_labels(&mut self, labels: PageLabelTree) {
302 self.page_labels = Some(labels);
303 }
304
305 pub fn page_labels(&self) -> Option<&PageLabelTree> {
307 self.page_labels.as_ref()
308 }
309
310 pub fn page_labels_mut(&mut self) -> Option<&mut PageLabelTree> {
312 self.page_labels.as_mut()
313 }
314
315 pub fn get_page_label(&self, page_index: u32) -> String {
317 self.page_labels
318 .as_ref()
319 .and_then(|labels| labels.get_label(page_index))
320 .unwrap_or_else(|| (page_index + 1).to_string())
321 }
322
323 pub fn get_all_page_labels(&self) -> Vec<String> {
325 let page_count = self.pages.len() as u32;
326 if let Some(labels) = &self.page_labels {
327 labels.get_all_labels(page_count)
328 } else {
329 (1..=page_count).map(|i| i.to_string()).collect()
330 }
331 }
332
333 pub fn set_creator(&mut self, creator: impl Into<String>) {
335 self.metadata.creator = Some(creator.into());
336 }
337
338 pub fn set_producer(&mut self, producer: impl Into<String>) {
340 self.metadata.producer = Some(producer.into());
341 }
342
343 pub fn set_creation_date(&mut self, date: DateTime<Utc>) {
345 self.metadata.creation_date = Some(date);
346 }
347
348 pub fn set_creation_date_local(&mut self, date: DateTime<Local>) {
350 self.metadata.creation_date = Some(date.with_timezone(&Utc));
351 }
352
353 pub fn set_modification_date(&mut self, date: DateTime<Utc>) {
355 self.metadata.modification_date = Some(date);
356 }
357
358 pub fn set_modification_date_local(&mut self, date: DateTime<Local>) {
360 self.metadata.modification_date = Some(date.with_timezone(&Utc));
361 }
362
363 pub fn update_modification_date(&mut self) {
365 self.metadata.modification_date = Some(Utc::now());
366 }
367
368 pub fn set_default_font_encoding(&mut self, encoding: Option<FontEncoding>) {
383 self.default_font_encoding = encoding;
384 }
385
386 pub fn default_font_encoding(&self) -> Option<FontEncoding> {
388 self.default_font_encoding
389 }
390
391 pub fn add_font(
402 &mut self,
403 name: impl Into<String>,
404 path: impl AsRef<std::path::Path>,
405 ) -> Result<()> {
406 let name = name.into();
407 let font = CustomFont::from_file(&name, path)?;
408 self.custom_fonts.add_font(name, font)?;
409 Ok(())
410 }
411
412 pub fn add_font_from_bytes(&mut self, name: impl Into<String>, data: Vec<u8>) -> Result<()> {
424 let name = name.into();
425 let font = CustomFont::from_bytes(&name, data)?;
426
427 self.custom_fonts.add_font(name, font)?;
431 Ok(())
432 }
433
434 pub(crate) fn get_custom_font(&self, name: &str) -> Option<Arc<CustomFont>> {
436 self.custom_fonts.get_font(name)
437 }
438
439 pub fn has_custom_font(&self, name: &str) -> bool {
441 self.custom_fonts.has_font(name)
442 }
443
444 pub fn custom_font_names(&self) -> Vec<String> {
446 self.custom_fonts.font_names()
447 }
448
449 pub fn page_count(&self) -> usize {
451 self.pages.len()
452 }
453
454 pub fn acro_form(&self) -> Option<&AcroForm> {
456 self.acro_form.as_ref()
457 }
458
459 pub fn acro_form_mut(&mut self) -> Option<&mut AcroForm> {
461 self.acro_form.as_mut()
462 }
463
464 pub fn enable_forms(&mut self) -> &mut FormManager {
467 if self.acro_form.is_none() {
468 self.acro_form = Some(AcroForm::new());
469 }
470 self.form_manager.get_or_insert_with(FormManager::new)
471 }
472
473 pub fn disable_forms(&mut self) {
475 self.acro_form = None;
476 self.form_manager = None;
477 }
478
479 pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
485 self.update_modification_date();
487
488 let config = crate::writer::WriterConfig {
490 use_xref_streams: self.use_xref_streams,
491 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
493 compress_streams: self.compress,
494 incremental_update: false,
495 };
496
497 use std::io::BufWriter;
498 let file = std::fs::File::create(path)?;
499 let writer = BufWriter::with_capacity(512 * 1024, file);
502 let mut pdf_writer = PdfWriter::with_config(writer, config);
503
504 pdf_writer.write_document(self)?;
505 Ok(())
506 }
507
508 pub fn save_with_config(
514 &mut self,
515 path: impl AsRef<std::path::Path>,
516 config: crate::writer::WriterConfig,
517 ) -> Result<()> {
518 use std::io::BufWriter;
519
520 self.update_modification_date();
522
523 let file = std::fs::File::create(path)?;
526 let writer = BufWriter::with_capacity(512 * 1024, file);
528 let mut pdf_writer = PdfWriter::with_config(writer, config);
529 pdf_writer.write_document(self)?;
530 Ok(())
531 }
532
533 pub fn save_with_custom_values(
547 &mut self,
548 path: impl AsRef<std::path::Path>,
549 custom_values: &std::collections::HashMap<String, String>,
550 ) -> Result<()> {
551 let total_pages = self.pages.len();
553 for (index, page) in self.pages.iter_mut().enumerate() {
554 let page_content = page.generate_content_with_page_info(
556 Some(index + 1),
557 Some(total_pages),
558 Some(custom_values),
559 )?;
560 page.set_content(page_content);
562 }
563
564 self.save(path)
566 }
567
568 pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
574 self.update_modification_date();
576
577 let mut writer = PdfWriter::new_with_writer(buffer);
578 writer.write_document(self)?;
579 Ok(())
580 }
581
582 pub fn set_compress(&mut self, compress: bool) {
609 self.compress = compress;
610 }
611
612 pub fn enable_xref_streams(&mut self, enable: bool) -> &mut Self {
630 self.use_xref_streams = enable;
631 self
632 }
633
634 pub fn get_compress(&self) -> bool {
640 self.compress
641 }
642
643 pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
671 self.update_modification_date();
673
674 let mut buffer = Vec::new();
676
677 let config = crate::writer::WriterConfig {
679 use_xref_streams: self.use_xref_streams,
680 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
682 compress_streams: self.compress,
683 incremental_update: false,
684 };
685
686 let mut writer = PdfWriter::with_config(&mut buffer, config);
688 writer.write_document(self)?;
689
690 Ok(buffer)
691 }
692
693 pub fn to_bytes_with_config(&mut self, config: crate::writer::WriterConfig) -> Result<Vec<u8>> {
734 self.update_modification_date();
736
737 let mut buffer = Vec::new();
741
742 let mut writer = PdfWriter::with_config(&mut buffer, config);
744 writer.write_document(self)?;
745
746 Ok(buffer)
747 }
748
749 pub fn mark_entity(
775 &mut self,
776 id: impl Into<String>,
777 entity_type: EntityType,
778 bounds: BoundingBox,
779 ) -> String {
780 let entity_id = id.into();
781 let entity = SemanticEntity::new(entity_id.clone(), entity_type, bounds);
782 self.semantic_entities.push(entity);
783 entity_id
784 }
785
786 pub fn set_entity_content(&mut self, entity_id: &str, content: impl Into<String>) -> bool {
788 if let Some(entity) = self
789 .semantic_entities
790 .iter_mut()
791 .find(|e| e.id == entity_id)
792 {
793 entity.content = content.into();
794 true
795 } else {
796 false
797 }
798 }
799
800 pub fn add_entity_metadata(
802 &mut self,
803 entity_id: &str,
804 key: impl Into<String>,
805 value: impl Into<String>,
806 ) -> bool {
807 if let Some(entity) = self
808 .semantic_entities
809 .iter_mut()
810 .find(|e| e.id == entity_id)
811 {
812 entity.metadata.properties.insert(key.into(), value.into());
813 true
814 } else {
815 false
816 }
817 }
818
819 pub fn set_entity_confidence(&mut self, entity_id: &str, confidence: f32) -> bool {
821 if let Some(entity) = self
822 .semantic_entities
823 .iter_mut()
824 .find(|e| e.id == entity_id)
825 {
826 entity.metadata.confidence = Some(confidence.clamp(0.0, 1.0));
827 true
828 } else {
829 false
830 }
831 }
832
833 pub fn relate_entities(
835 &mut self,
836 from_id: &str,
837 to_id: &str,
838 relation_type: RelationType,
839 ) -> bool {
840 let target_exists = self.semantic_entities.iter().any(|e| e.id == to_id);
842 if !target_exists {
843 return false;
844 }
845
846 if let Some(entity) = self.semantic_entities.iter_mut().find(|e| e.id == from_id) {
848 entity.relationships.push(crate::semantic::EntityRelation {
849 target_id: to_id.to_string(),
850 relation_type,
851 });
852 true
853 } else {
854 false
855 }
856 }
857
858 pub fn get_semantic_entities(&self) -> &[SemanticEntity] {
860 &self.semantic_entities
861 }
862
863 pub fn get_entities_by_type(&self, entity_type: EntityType) -> Vec<&SemanticEntity> {
865 self.semantic_entities
866 .iter()
867 .filter(|e| e.entity_type == entity_type)
868 .collect()
869 }
870
871 #[cfg(feature = "semantic")]
873 pub fn export_semantic_entities_json(&self) -> Result<String> {
874 serde_json::to_string_pretty(&self.semantic_entities)
875 .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
876 }
877
878 #[cfg(feature = "semantic")]
904 pub fn export_semantic_entities_json_ld(&self) -> Result<String> {
905 use crate::semantic::{Entity, EntityMap};
906
907 let mut entity_map = EntityMap::new();
908
909 for sem_entity in &self.semantic_entities {
911 let entity = Entity {
912 id: sem_entity.id.clone(),
913 entity_type: sem_entity.entity_type.clone(),
914 bounds: (
915 sem_entity.bounds.x as f64,
916 sem_entity.bounds.y as f64,
917 sem_entity.bounds.width as f64,
918 sem_entity.bounds.height as f64,
919 ),
920 page: (sem_entity.bounds.page - 1) as usize, metadata: sem_entity.metadata.clone(),
922 };
923 entity_map.add_entity(entity);
924 }
925
926 if let Some(title) = &self.metadata.title {
928 entity_map
929 .document_metadata
930 .insert("name".to_string(), title.clone());
931 }
932 if let Some(author) = &self.metadata.author {
933 entity_map
934 .document_metadata
935 .insert("author".to_string(), author.clone());
936 }
937
938 entity_map
939 .to_json_ld()
940 .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
941 }
942
943 pub fn find_entity(&self, entity_id: &str) -> Option<&SemanticEntity> {
945 self.semantic_entities.iter().find(|e| e.id == entity_id)
946 }
947
948 pub fn remove_entity(&mut self, entity_id: &str) -> bool {
950 if let Some(pos) = self
951 .semantic_entities
952 .iter()
953 .position(|e| e.id == entity_id)
954 {
955 self.semantic_entities.remove(pos);
956 for entity in &mut self.semantic_entities {
958 entity.relationships.retain(|r| r.target_id != entity_id);
959 }
960 true
961 } else {
962 false
963 }
964 }
965
966 pub fn semantic_entity_count(&self) -> usize {
968 self.semantic_entities.len()
969 }
970
971 pub fn create_xmp_metadata(&self) -> crate::metadata::XmpMetadata {
979 let mut xmp = crate::metadata::XmpMetadata::new();
980
981 if let Some(title) = &self.metadata.title {
983 xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "title", title);
984 }
985 if let Some(author) = &self.metadata.author {
986 xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "creator", author);
987 }
988 if let Some(subject) = &self.metadata.subject {
989 xmp.set_text(
990 crate::metadata::XmpNamespace::DublinCore,
991 "description",
992 subject,
993 );
994 }
995
996 if let Some(creator) = &self.metadata.creator {
998 xmp.set_text(
999 crate::metadata::XmpNamespace::XmpBasic,
1000 "CreatorTool",
1001 creator,
1002 );
1003 }
1004 if let Some(creation_date) = &self.metadata.creation_date {
1005 xmp.set_date(
1006 crate::metadata::XmpNamespace::XmpBasic,
1007 "CreateDate",
1008 creation_date.to_rfc3339(),
1009 );
1010 }
1011 if let Some(mod_date) = &self.metadata.modification_date {
1012 xmp.set_date(
1013 crate::metadata::XmpNamespace::XmpBasic,
1014 "ModifyDate",
1015 mod_date.to_rfc3339(),
1016 );
1017 }
1018
1019 if let Some(producer) = &self.metadata.producer {
1021 xmp.set_text(crate::metadata::XmpNamespace::Pdf, "Producer", producer);
1022 }
1023
1024 xmp
1025 }
1026
1027 pub fn get_xmp_packet(&self) -> String {
1036 self.create_xmp_metadata().to_xmp_packet()
1037 }
1038
1039 pub fn extract_text(&self) -> Result<String> {
1041 let mut text = String::new();
1044 for (i, _page) in self.pages.iter().enumerate() {
1045 text.push_str(&format!("Text from page {} (placeholder)\n", i + 1));
1046 }
1047 Ok(text)
1048 }
1049
1050 pub fn extract_page_text(&self, page_index: usize) -> Result<String> {
1052 if page_index < self.pages.len() {
1053 Ok(format!("Text from page {} (placeholder)", page_index + 1))
1054 } else {
1055 Err(crate::error::PdfError::InvalidReference(format!(
1056 "Page index {} out of bounds",
1057 page_index
1058 )))
1059 }
1060 }
1061}
1062
1063impl Default for Document {
1064 fn default() -> Self {
1065 Self::new()
1066 }
1067}
1068
1069#[cfg(test)]
1070mod tests {
1071 use super::*;
1072
1073 #[test]
1074 fn test_document_new() {
1075 let doc = Document::new();
1076 assert!(doc.pages.is_empty());
1077 assert!(doc.metadata.title.is_none());
1078 assert!(doc.metadata.author.is_none());
1079 assert!(doc.metadata.subject.is_none());
1080 assert!(doc.metadata.keywords.is_none());
1081 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1082 assert!(doc
1083 .metadata
1084 .producer
1085 .as_ref()
1086 .unwrap()
1087 .starts_with("oxidize_pdf"));
1088 }
1089
1090 #[test]
1091 fn test_document_default() {
1092 let doc = Document::default();
1093 assert!(doc.pages.is_empty());
1094 }
1095
1096 #[test]
1097 fn test_add_page() {
1098 let mut doc = Document::new();
1099 let page1 = Page::a4();
1100 let page2 = Page::letter();
1101
1102 doc.add_page(page1);
1103 assert_eq!(doc.pages.len(), 1);
1104
1105 doc.add_page(page2);
1106 assert_eq!(doc.pages.len(), 2);
1107 }
1108
1109 #[test]
1110 fn test_set_title() {
1111 let mut doc = Document::new();
1112 assert!(doc.metadata.title.is_none());
1113
1114 doc.set_title("Test Document");
1115 assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
1116
1117 doc.set_title(String::from("Another Title"));
1118 assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
1119 }
1120
1121 #[test]
1122 fn test_set_author() {
1123 let mut doc = Document::new();
1124 assert!(doc.metadata.author.is_none());
1125
1126 doc.set_author("John Doe");
1127 assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
1128 }
1129
1130 #[test]
1131 fn test_set_subject() {
1132 let mut doc = Document::new();
1133 assert!(doc.metadata.subject.is_none());
1134
1135 doc.set_subject("Test Subject");
1136 assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
1137 }
1138
1139 #[test]
1140 fn test_set_keywords() {
1141 let mut doc = Document::new();
1142 assert!(doc.metadata.keywords.is_none());
1143
1144 doc.set_keywords("test, pdf, rust");
1145 assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
1146 }
1147
1148 #[test]
1149 fn test_metadata_default() {
1150 let metadata = DocumentMetadata::default();
1151 assert!(metadata.title.is_none());
1152 assert!(metadata.author.is_none());
1153 assert!(metadata.subject.is_none());
1154 assert!(metadata.keywords.is_none());
1155 assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
1156 assert!(metadata
1157 .producer
1158 .as_ref()
1159 .unwrap()
1160 .starts_with("oxidize_pdf"));
1161 }
1162
1163 #[test]
1164 fn test_write_to_buffer() {
1165 let mut doc = Document::new();
1166 doc.set_title("Buffer Test");
1167 doc.add_page(Page::a4());
1168
1169 let mut buffer = Vec::new();
1170 let result = doc.write(&mut buffer);
1171
1172 assert!(result.is_ok());
1173 assert!(!buffer.is_empty());
1174 assert!(buffer.starts_with(b"%PDF-1.7"));
1175 }
1176
1177 #[test]
1178 fn test_document_with_multiple_pages() {
1179 let mut doc = Document::new();
1180 doc.set_title("Multi-page Document");
1181 doc.set_author("Test Author");
1182 doc.set_subject("Testing multiple pages");
1183 doc.set_keywords("test, multiple, pages");
1184
1185 for _ in 0..5 {
1186 doc.add_page(Page::a4());
1187 }
1188
1189 assert_eq!(doc.pages.len(), 5);
1190 assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
1191 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1192 }
1193
1194 #[test]
1195 fn test_empty_document_write() {
1196 let mut doc = Document::new();
1197 let mut buffer = Vec::new();
1198
1199 let result = doc.write(&mut buffer);
1201 assert!(result.is_ok());
1202 assert!(!buffer.is_empty());
1203 assert!(buffer.starts_with(b"%PDF-1.7"));
1204 }
1205
1206 mod integration_tests {
1208 use super::*;
1209 use crate::graphics::Color;
1210 use crate::text::Font;
1211 use std::fs;
1212 use tempfile::TempDir;
1213
1214 #[test]
1215 fn test_document_writer_roundtrip() {
1216 let temp_dir = TempDir::new().unwrap();
1217 let file_path = temp_dir.path().join("test.pdf");
1218
1219 let mut doc = Document::new();
1221 doc.set_title("Integration Test");
1222 doc.set_author("Test Author");
1223 doc.set_subject("Writer Integration");
1224 doc.set_keywords("test, writer, integration");
1225
1226 let mut page = Page::a4();
1227 page.text()
1228 .set_font(Font::Helvetica, 12.0)
1229 .at(100.0, 700.0)
1230 .write("Integration Test Content")
1231 .unwrap();
1232
1233 doc.add_page(page);
1234
1235 let result = doc.save(&file_path);
1237 assert!(result.is_ok());
1238
1239 assert!(file_path.exists());
1241 let metadata = fs::metadata(&file_path).unwrap();
1242 assert!(metadata.len() > 0);
1243
1244 let content = fs::read(&file_path).unwrap();
1246 assert!(content.starts_with(b"%PDF-1.7"));
1247 assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
1249 }
1250
1251 #[test]
1252 fn test_document_with_complex_content() {
1253 let temp_dir = TempDir::new().unwrap();
1254 let file_path = temp_dir.path().join("complex.pdf");
1255
1256 let mut doc = Document::new();
1257 doc.set_title("Complex Content Test");
1258
1259 let mut page = Page::a4();
1261
1262 page.text()
1264 .set_font(Font::Helvetica, 14.0)
1265 .at(50.0, 750.0)
1266 .write("Complex Content Test")
1267 .unwrap();
1268
1269 page.graphics()
1271 .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
1272 .rectangle(50.0, 500.0, 200.0, 100.0)
1273 .fill();
1274
1275 page.graphics()
1276 .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
1277 .set_line_width(2.0)
1278 .move_to(50.0, 400.0)
1279 .line_to(250.0, 400.0)
1280 .stroke();
1281
1282 doc.add_page(page);
1283
1284 let result = doc.save(&file_path);
1286 assert!(result.is_ok());
1287 assert!(file_path.exists());
1288 }
1289
1290 #[test]
1291 fn test_document_multiple_pages_integration() {
1292 let temp_dir = TempDir::new().unwrap();
1293 let file_path = temp_dir.path().join("multipage.pdf");
1294
1295 let mut doc = Document::new();
1296 doc.set_title("Multi-page Integration Test");
1297
1298 for i in 1..=5 {
1300 let mut page = Page::a4();
1301
1302 page.text()
1303 .set_font(Font::Helvetica, 16.0)
1304 .at(50.0, 750.0)
1305 .write(&format!("Page {i}"))
1306 .unwrap();
1307
1308 page.text()
1309 .set_font(Font::Helvetica, 12.0)
1310 .at(50.0, 700.0)
1311 .write(&format!("This is the content for page {i}"))
1312 .unwrap();
1313
1314 let color = match i % 3 {
1316 0 => Color::rgb(1.0, 0.0, 0.0),
1317 1 => Color::rgb(0.0, 1.0, 0.0),
1318 _ => Color::rgb(0.0, 0.0, 1.0),
1319 };
1320
1321 page.graphics()
1322 .set_fill_color(color)
1323 .rectangle(50.0, 600.0, 100.0, 50.0)
1324 .fill();
1325
1326 doc.add_page(page);
1327 }
1328
1329 let result = doc.save(&file_path);
1331 assert!(result.is_ok());
1332 assert!(file_path.exists());
1333
1334 let metadata = fs::metadata(&file_path).unwrap();
1336 assert!(metadata.len() > 1000); }
1338
1339 #[test]
1340 fn test_document_metadata_persistence() {
1341 let temp_dir = TempDir::new().unwrap();
1342 let file_path = temp_dir.path().join("metadata.pdf");
1343
1344 let mut doc = Document::new();
1345 doc.set_title("Metadata Persistence Test");
1346 doc.set_author("Test Author");
1347 doc.set_subject("Testing metadata preservation");
1348 doc.set_keywords("metadata, persistence, test");
1349
1350 doc.add_page(Page::a4());
1351
1352 let result = doc.save(&file_path);
1354 assert!(result.is_ok());
1355
1356 let content = fs::read(&file_path).unwrap();
1358 let content_str = String::from_utf8_lossy(&content);
1359
1360 assert!(content_str.contains("Metadata Persistence Test"));
1362 assert!(content_str.contains("Test Author"));
1363 }
1364
1365 #[test]
1366 fn test_document_writer_error_handling() {
1367 let mut doc = Document::new();
1368 doc.add_page(Page::a4());
1369
1370 let result = doc.save("/invalid/path/test.pdf");
1372 assert!(result.is_err());
1373 }
1374
1375 #[test]
1376 fn test_document_page_integration() {
1377 let mut doc = Document::new();
1378
1379 let page1 = Page::a4();
1381 let page2 = Page::letter();
1382 let mut page3 = Page::new(500.0, 400.0);
1383
1384 page3
1386 .text()
1387 .set_font(Font::Helvetica, 10.0)
1388 .at(25.0, 350.0)
1389 .write("Custom size page")
1390 .unwrap();
1391
1392 doc.add_page(page1);
1393 doc.add_page(page2);
1394 doc.add_page(page3);
1395
1396 assert_eq!(doc.pages.len(), 3);
1397
1398 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); }
1406
1407 #[test]
1408 fn test_document_content_generation() {
1409 let temp_dir = TempDir::new().unwrap();
1410 let file_path = temp_dir.path().join("content.pdf");
1411
1412 let mut doc = Document::new();
1413 doc.set_title("Content Generation Test");
1414
1415 let mut page = Page::a4();
1416
1417 for i in 0..10 {
1419 let y_pos = 700.0 - (i as f64 * 30.0);
1420 page.text()
1421 .set_font(Font::Helvetica, 12.0)
1422 .at(50.0, y_pos)
1423 .write(&format!("Generated line {}", i + 1))
1424 .unwrap();
1425 }
1426
1427 doc.add_page(page);
1428
1429 let result = doc.save(&file_path);
1431 assert!(result.is_ok());
1432 assert!(file_path.exists());
1433
1434 let metadata = fs::metadata(&file_path).unwrap();
1436 assert!(metadata.len() > 500); }
1438
1439 #[test]
1440 fn test_document_buffer_vs_file_write() {
1441 let temp_dir = TempDir::new().unwrap();
1442 let file_path = temp_dir.path().join("buffer_vs_file.pdf");
1443
1444 let mut doc = Document::new();
1445 doc.set_title("Buffer vs File Test");
1446 doc.add_page(Page::a4());
1447
1448 let mut buffer = Vec::new();
1450 let buffer_result = doc.write(&mut buffer);
1451 assert!(buffer_result.is_ok());
1452
1453 let file_result = doc.save(&file_path);
1455 assert!(file_result.is_ok());
1456
1457 let file_content = fs::read(&file_path).unwrap();
1459
1460 assert!(buffer.starts_with(b"%PDF-1.7"));
1462 assert!(file_content.starts_with(b"%PDF-1.7"));
1463 assert!(buffer.ends_with(b"%%EOF\n"));
1464 assert!(file_content.ends_with(b"%%EOF\n"));
1465
1466 let buffer_str = String::from_utf8_lossy(&buffer);
1468 let file_str = String::from_utf8_lossy(&file_content);
1469 assert!(buffer_str.contains("Buffer vs File Test"));
1470 assert!(file_str.contains("Buffer vs File Test"));
1471 }
1472
1473 #[test]
1474 fn test_document_large_content_handling() {
1475 let temp_dir = TempDir::new().unwrap();
1476 let file_path = temp_dir.path().join("large_content.pdf");
1477
1478 let mut doc = Document::new();
1479 doc.set_title("Large Content Test");
1480
1481 let mut page = Page::a4();
1482
1483 let large_text =
1485 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(200);
1486 page.text()
1487 .set_font(Font::Helvetica, 10.0)
1488 .at(50.0, 750.0)
1489 .write(&large_text)
1490 .unwrap();
1491
1492 doc.add_page(page);
1493
1494 let result = doc.save(&file_path);
1496 assert!(result.is_ok());
1497 assert!(file_path.exists());
1498
1499 let metadata = fs::metadata(&file_path).unwrap();
1501 assert!(metadata.len() > 500); }
1503
1504 #[test]
1505 fn test_document_incremental_building() {
1506 let temp_dir = TempDir::new().unwrap();
1507 let file_path = temp_dir.path().join("incremental.pdf");
1508
1509 let mut doc = Document::new();
1510
1511 doc.set_title("Incremental Building Test");
1513
1514 let mut page1 = Page::a4();
1516 page1
1517 .text()
1518 .set_font(Font::Helvetica, 12.0)
1519 .at(50.0, 750.0)
1520 .write("First page content")
1521 .unwrap();
1522 doc.add_page(page1);
1523
1524 doc.set_author("Incremental Author");
1526 doc.set_subject("Incremental Subject");
1527
1528 let mut page2 = Page::a4();
1530 page2
1531 .text()
1532 .set_font(Font::Helvetica, 12.0)
1533 .at(50.0, 750.0)
1534 .write("Second page content")
1535 .unwrap();
1536 doc.add_page(page2);
1537
1538 doc.set_keywords("incremental, building, test");
1540
1541 let result = doc.save(&file_path);
1543 assert!(result.is_ok());
1544 assert!(file_path.exists());
1545
1546 assert_eq!(doc.pages.len(), 2);
1548 assert_eq!(
1549 doc.metadata.title,
1550 Some("Incremental Building Test".to_string())
1551 );
1552 assert_eq!(doc.metadata.author, Some("Incremental Author".to_string()));
1553 assert_eq!(
1554 doc.metadata.subject,
1555 Some("Incremental Subject".to_string())
1556 );
1557 assert_eq!(
1558 doc.metadata.keywords,
1559 Some("incremental, building, test".to_string())
1560 );
1561 }
1562
1563 #[test]
1564 fn test_document_concurrent_page_operations() {
1565 let mut doc = Document::new();
1566 doc.set_title("Concurrent Operations Test");
1567
1568 let mut pages = Vec::new();
1570
1571 for i in 0..5 {
1573 let mut page = Page::a4();
1574 page.text()
1575 .set_font(Font::Helvetica, 12.0)
1576 .at(50.0, 750.0)
1577 .write(&format!("Concurrent page {i}"))
1578 .unwrap();
1579 pages.push(page);
1580 }
1581
1582 for page in pages {
1584 doc.add_page(page);
1585 }
1586
1587 assert_eq!(doc.pages.len(), 5);
1588
1589 let temp_dir = TempDir::new().unwrap();
1591 let file_path = temp_dir.path().join("concurrent.pdf");
1592 let result = doc.save(&file_path);
1593 assert!(result.is_ok());
1594 }
1595
1596 #[test]
1597 fn test_document_memory_efficiency() {
1598 let mut doc = Document::new();
1599 doc.set_title("Memory Efficiency Test");
1600
1601 for i in 0..10 {
1603 let mut page = Page::a4();
1604 page.text()
1605 .set_font(Font::Helvetica, 12.0)
1606 .at(50.0, 700.0)
1607 .write(&format!("Memory test page {i}"))
1608 .unwrap();
1609 doc.add_page(page);
1610 }
1611
1612 let mut buffer = Vec::new();
1614 let result = doc.write(&mut buffer);
1615 assert!(result.is_ok());
1616 assert!(!buffer.is_empty());
1617
1618 assert!(buffer.len() < 1_000_000); }
1621
1622 #[test]
1623 fn test_document_creator_producer() {
1624 let mut doc = Document::new();
1625
1626 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1628 assert!(doc
1629 .metadata
1630 .producer
1631 .as_ref()
1632 .unwrap()
1633 .contains("oxidize_pdf"));
1634
1635 doc.set_creator("My Application");
1637 doc.set_producer("My PDF Library v1.0");
1638
1639 assert_eq!(doc.metadata.creator, Some("My Application".to_string()));
1640 assert_eq!(
1641 doc.metadata.producer,
1642 Some("My PDF Library v1.0".to_string())
1643 );
1644 }
1645
1646 #[test]
1647 fn test_document_dates() {
1648 use chrono::{TimeZone, Utc};
1649
1650 let mut doc = Document::new();
1651
1652 assert!(doc.metadata.creation_date.is_some());
1654 assert!(doc.metadata.modification_date.is_some());
1655
1656 let creation_date = Utc.with_ymd_and_hms(2023, 1, 1, 12, 0, 0).unwrap();
1658 let mod_date = Utc.with_ymd_and_hms(2023, 6, 15, 18, 30, 0).unwrap();
1659
1660 doc.set_creation_date(creation_date);
1661 doc.set_modification_date(mod_date);
1662
1663 assert_eq!(doc.metadata.creation_date, Some(creation_date));
1664 assert_eq!(doc.metadata.modification_date, Some(mod_date));
1665 }
1666
1667 #[test]
1668 fn test_document_dates_local() {
1669 use chrono::{Local, TimeZone};
1670
1671 let mut doc = Document::new();
1672
1673 let local_date = Local.with_ymd_and_hms(2023, 12, 25, 10, 30, 0).unwrap();
1675 doc.set_creation_date_local(local_date);
1676
1677 assert!(doc.metadata.creation_date.is_some());
1679 assert!(doc.metadata.creation_date.is_some());
1681 }
1682
1683 #[test]
1684 fn test_update_modification_date() {
1685 let mut doc = Document::new();
1686
1687 let initial_mod_date = doc.metadata.modification_date;
1688 assert!(initial_mod_date.is_some());
1689
1690 std::thread::sleep(std::time::Duration::from_millis(10));
1692
1693 doc.update_modification_date();
1694
1695 let new_mod_date = doc.metadata.modification_date;
1696 assert!(new_mod_date.is_some());
1697 assert!(new_mod_date.unwrap() > initial_mod_date.unwrap());
1698 }
1699
1700 #[test]
1701 fn test_document_save_updates_modification_date() {
1702 let temp_dir = TempDir::new().unwrap();
1703 let file_path = temp_dir.path().join("mod_date_test.pdf");
1704
1705 let mut doc = Document::new();
1706 doc.add_page(Page::a4());
1707
1708 let initial_mod_date = doc.metadata.modification_date;
1709
1710 std::thread::sleep(std::time::Duration::from_millis(10));
1712
1713 doc.save(&file_path).unwrap();
1714
1715 assert!(doc.metadata.modification_date.unwrap() > initial_mod_date.unwrap());
1717 }
1718
1719 #[test]
1720 fn test_document_metadata_complete() {
1721 let mut doc = Document::new();
1722
1723 doc.set_title("Complete Metadata Test");
1725 doc.set_author("Test Author");
1726 doc.set_subject("Testing all metadata fields");
1727 doc.set_keywords("test, metadata, complete");
1728 doc.set_creator("Test Application v1.0");
1729 doc.set_producer("oxidize_pdf Test Suite");
1730
1731 assert_eq!(
1733 doc.metadata.title,
1734 Some("Complete Metadata Test".to_string())
1735 );
1736 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1737 assert_eq!(
1738 doc.metadata.subject,
1739 Some("Testing all metadata fields".to_string())
1740 );
1741 assert_eq!(
1742 doc.metadata.keywords,
1743 Some("test, metadata, complete".to_string())
1744 );
1745 assert_eq!(
1746 doc.metadata.creator,
1747 Some("Test Application v1.0".to_string())
1748 );
1749 assert_eq!(
1750 doc.metadata.producer,
1751 Some("oxidize_pdf Test Suite".to_string())
1752 );
1753 assert!(doc.metadata.creation_date.is_some());
1754 assert!(doc.metadata.modification_date.is_some());
1755 }
1756
1757 #[test]
1758 fn test_document_to_bytes() {
1759 let mut doc = Document::new();
1760 doc.set_title("Test Document");
1761 doc.set_author("Test Author");
1762
1763 let page = Page::a4();
1764 doc.add_page(page);
1765
1766 let pdf_bytes = doc.to_bytes().unwrap();
1768
1769 assert!(!pdf_bytes.is_empty());
1771 assert!(pdf_bytes.len() > 100); let header = &pdf_bytes[0..5];
1775 assert_eq!(header, b"%PDF-");
1776
1777 let pdf_str = String::from_utf8_lossy(&pdf_bytes);
1779 assert!(pdf_str.contains("Test Document"));
1780 assert!(pdf_str.contains("Test Author"));
1781 }
1782
1783 #[test]
1784 fn test_document_to_bytes_with_config() {
1785 let mut doc = Document::new();
1786 doc.set_title("Test Document XRef");
1787
1788 let page = Page::a4();
1789 doc.add_page(page);
1790
1791 let config = crate::writer::WriterConfig {
1792 use_xref_streams: true,
1793 use_object_streams: false,
1794 pdf_version: "1.5".to_string(),
1795 compress_streams: true,
1796 incremental_update: false,
1797 };
1798
1799 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1801
1802 assert!(!pdf_bytes.is_empty());
1804 assert!(pdf_bytes.len() > 100);
1805
1806 let header = String::from_utf8_lossy(&pdf_bytes[0..8]);
1808 assert!(header.contains("PDF-1.5"));
1809 }
1810
1811 #[test]
1812 fn test_to_bytes_vs_save_equivalence() {
1813 use std::fs;
1814 use tempfile::NamedTempFile;
1815
1816 let mut doc1 = Document::new();
1818 doc1.set_title("Equivalence Test");
1819 doc1.add_page(Page::a4());
1820
1821 let mut doc2 = Document::new();
1822 doc2.set_title("Equivalence Test");
1823 doc2.add_page(Page::a4());
1824
1825 let pdf_bytes = doc1.to_bytes().unwrap();
1827
1828 let temp_file = NamedTempFile::new().unwrap();
1830 doc2.save(temp_file.path()).unwrap();
1831 let file_bytes = fs::read(temp_file.path()).unwrap();
1832
1833 assert!(!pdf_bytes.is_empty());
1835 assert!(!file_bytes.is_empty());
1836 assert_eq!(&pdf_bytes[0..5], &file_bytes[0..5]); }
1838
1839 #[test]
1840 fn test_document_set_compress() {
1841 let mut doc = Document::new();
1842 doc.set_title("Compression Test");
1843 doc.add_page(Page::a4());
1844
1845 assert!(doc.get_compress());
1847
1848 doc.set_compress(true);
1850 let compressed_bytes = doc.to_bytes().unwrap();
1851
1852 doc.set_compress(false);
1854 let uncompressed_bytes = doc.to_bytes().unwrap();
1855
1856 assert!(!compressed_bytes.is_empty());
1858 assert!(!uncompressed_bytes.is_empty());
1859
1860 assert_eq!(&compressed_bytes[0..5], b"%PDF-");
1862 assert_eq!(&uncompressed_bytes[0..5], b"%PDF-");
1863 }
1864
1865 #[test]
1866 fn test_document_compression_config_inheritance() {
1867 let mut doc = Document::new();
1868 doc.set_title("Config Inheritance Test");
1869 doc.add_page(Page::a4());
1870
1871 doc.set_compress(false);
1873
1874 let config = crate::writer::WriterConfig {
1876 use_xref_streams: false,
1877 use_object_streams: false,
1878 pdf_version: "1.7".to_string(),
1879 compress_streams: true,
1880 incremental_update: false,
1881 };
1882
1883 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1885
1886 assert!(!pdf_bytes.is_empty());
1888 assert_eq!(&pdf_bytes[0..5], b"%PDF-");
1889 }
1890
1891 #[test]
1892 fn test_document_metadata_all_fields() {
1893 let mut doc = Document::new();
1894
1895 doc.set_title("Test Document");
1897 doc.set_author("John Doe");
1898 doc.set_subject("Testing PDF metadata");
1899 doc.set_keywords("test, pdf, metadata");
1900 doc.set_creator("Test Suite");
1901 doc.set_producer("oxidize_pdf tests");
1902
1903 assert_eq!(doc.metadata.title.as_deref(), Some("Test Document"));
1905 assert_eq!(doc.metadata.author.as_deref(), Some("John Doe"));
1906 assert_eq!(
1907 doc.metadata.subject.as_deref(),
1908 Some("Testing PDF metadata")
1909 );
1910 assert_eq!(
1911 doc.metadata.keywords.as_deref(),
1912 Some("test, pdf, metadata")
1913 );
1914 assert_eq!(doc.metadata.creator.as_deref(), Some("Test Suite"));
1915 assert_eq!(doc.metadata.producer.as_deref(), Some("oxidize_pdf tests"));
1916 assert!(doc.metadata.creation_date.is_some());
1917 assert!(doc.metadata.modification_date.is_some());
1918 }
1919
1920 #[test]
1921 fn test_document_add_pages() {
1922 let mut doc = Document::new();
1923
1924 assert_eq!(doc.page_count(), 0);
1926
1927 let page1 = Page::a4();
1929 let page2 = Page::letter();
1930 let page3 = Page::legal();
1931
1932 doc.add_page(page1);
1933 assert_eq!(doc.page_count(), 1);
1934
1935 doc.add_page(page2);
1936 assert_eq!(doc.page_count(), 2);
1937
1938 doc.add_page(page3);
1939 assert_eq!(doc.page_count(), 3);
1940
1941 let result = doc.to_bytes();
1943 assert!(result.is_ok());
1944 }
1945
1946 #[test]
1947 fn test_document_default_font_encoding() {
1948 let mut doc = Document::new();
1949
1950 assert!(doc.default_font_encoding.is_none());
1952
1953 doc.set_default_font_encoding(Some(FontEncoding::WinAnsiEncoding));
1955 assert_eq!(
1956 doc.default_font_encoding(),
1957 Some(FontEncoding::WinAnsiEncoding)
1958 );
1959
1960 doc.set_default_font_encoding(Some(FontEncoding::MacRomanEncoding));
1962 assert_eq!(
1963 doc.default_font_encoding(),
1964 Some(FontEncoding::MacRomanEncoding)
1965 );
1966 }
1967
1968 #[test]
1969 fn test_document_compression_setting() {
1970 let mut doc = Document::new();
1971
1972 assert!(doc.compress);
1974
1975 doc.set_compress(false);
1977 assert!(!doc.compress);
1978
1979 doc.set_compress(true);
1981 assert!(doc.compress);
1982 }
1983
1984 #[test]
1985 fn test_document_with_empty_pages() {
1986 let mut doc = Document::new();
1987
1988 doc.add_page(Page::a4());
1990
1991 let result = doc.to_bytes();
1993 assert!(result.is_ok());
1994
1995 let pdf_bytes = result.unwrap();
1996 assert!(!pdf_bytes.is_empty());
1997 assert!(pdf_bytes.starts_with(b"%PDF-"));
1998 }
1999
2000 #[test]
2001 fn test_document_with_multiple_page_sizes() {
2002 let mut doc = Document::new();
2003
2004 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);
2012
2013 let result = doc.to_bytes();
2017 assert!(result.is_ok());
2018 }
2019
2020 #[test]
2021 fn test_document_metadata_dates() {
2022 use chrono::Duration;
2023
2024 let doc = Document::new();
2025
2026 assert!(doc.metadata.creation_date.is_some());
2028 assert!(doc.metadata.modification_date.is_some());
2029
2030 if let (Some(created), Some(modified)) =
2031 (doc.metadata.creation_date, doc.metadata.modification_date)
2032 {
2033 let diff = modified - created;
2035 assert!(diff < Duration::seconds(1));
2036 }
2037 }
2038
2039 #[test]
2040 fn test_document_builder_pattern() {
2041 let mut doc = Document::new();
2043 doc.set_title("Fluent");
2044 doc.set_author("Builder");
2045 doc.set_compress(true);
2046
2047 assert_eq!(doc.metadata.title.as_deref(), Some("Fluent"));
2048 assert_eq!(doc.metadata.author.as_deref(), Some("Builder"));
2049 assert!(doc.compress);
2050 }
2051
2052 #[test]
2053 fn test_xref_streams_functionality() {
2054 use crate::{Document, Font, Page};
2055
2056 let mut doc = Document::new();
2058 assert!(!doc.use_xref_streams);
2059
2060 let mut page = Page::a4();
2061 page.text()
2062 .set_font(Font::Helvetica, 12.0)
2063 .at(100.0, 700.0)
2064 .write("Testing XRef Streams")
2065 .unwrap();
2066
2067 doc.add_page(page);
2068
2069 let pdf_without_xref = doc.to_bytes().unwrap();
2071
2072 let pdf_str = String::from_utf8_lossy(&pdf_without_xref);
2074 assert!(pdf_str.contains("xref"), "Traditional xref table not found");
2075 assert!(
2076 !pdf_str.contains("/Type /XRef"),
2077 "XRef stream found when it shouldn't be"
2078 );
2079
2080 doc.enable_xref_streams(true);
2082 assert!(doc.use_xref_streams);
2083
2084 let pdf_with_xref = doc.to_bytes().unwrap();
2086
2087 let pdf_str = String::from_utf8_lossy(&pdf_with_xref);
2089 assert!(
2091 pdf_str.contains("/Type /XRef") || pdf_str.contains("stream"),
2092 "XRef stream not found when enabled"
2093 );
2094
2095 assert!(
2097 pdf_str.contains("PDF-1.5"),
2098 "PDF version not set to 1.5 for xref streams"
2099 );
2100
2101 let mut doc2 = Document::new();
2103 doc2.enable_xref_streams(true);
2104 doc2.set_title("XRef Streams Test");
2105 doc2.set_author("oxidize-pdf");
2106
2107 assert!(doc2.use_xref_streams);
2108 assert_eq!(doc2.metadata.title.as_deref(), Some("XRef Streams Test"));
2109 assert_eq!(doc2.metadata.author.as_deref(), Some("oxidize-pdf"));
2110 }
2111
2112 #[test]
2113 fn test_document_save_to_vec() {
2114 let mut doc = Document::new();
2115 doc.set_title("Test Save");
2116 doc.add_page(Page::a4());
2117
2118 let bytes_result = doc.to_bytes();
2120 assert!(bytes_result.is_ok());
2121
2122 let bytes = bytes_result.unwrap();
2123 assert!(!bytes.is_empty());
2124 assert!(bytes.starts_with(b"%PDF-"));
2125 assert!(bytes.ends_with(b"%%EOF") || bytes.ends_with(b"%%EOF\n"));
2126 }
2127
2128 #[test]
2129 fn test_document_unicode_metadata() {
2130 let mut doc = Document::new();
2131
2132 doc.set_title("日本語のタイトル");
2134 doc.set_author("作者名 😀");
2135 doc.set_subject("Тема документа");
2136 doc.set_keywords("كلمات, מפתח, 关键词");
2137
2138 assert_eq!(doc.metadata.title.as_deref(), Some("日本語のタイトル"));
2139 assert_eq!(doc.metadata.author.as_deref(), Some("作者名 😀"));
2140 assert_eq!(doc.metadata.subject.as_deref(), Some("Тема документа"));
2141 assert_eq!(
2142 doc.metadata.keywords.as_deref(),
2143 Some("كلمات, מפתח, 关键词")
2144 );
2145 }
2146
2147 #[test]
2148 fn test_document_page_iteration() {
2149 let mut doc = Document::new();
2150
2151 for i in 0..5 {
2153 let mut page = Page::a4();
2154 let gc = page.graphics();
2155 gc.begin_text();
2156 let _ = gc.show_text(&format!("Page {}", i + 1));
2157 gc.end_text();
2158 doc.add_page(page);
2159 }
2160
2161 assert_eq!(doc.page_count(), 5);
2163
2164 let result = doc.to_bytes();
2166 assert!(result.is_ok());
2167 }
2168
2169 #[test]
2170 fn test_document_with_graphics_content() {
2171 let mut doc = Document::new();
2172
2173 let mut page = Page::a4();
2174 {
2175 let gc = page.graphics();
2176
2177 gc.save_state();
2179
2180 gc.rectangle(100.0, 100.0, 200.0, 150.0);
2182 gc.stroke();
2183
2184 gc.move_to(300.0, 300.0);
2186 gc.circle(300.0, 300.0, 50.0);
2187 gc.fill();
2188
2189 gc.begin_text();
2191 gc.set_text_position(100.0, 500.0);
2192 let _ = gc.show_text("Graphics Test");
2193 gc.end_text();
2194
2195 gc.restore_state();
2196 }
2197
2198 doc.add_page(page);
2199
2200 let result = doc.to_bytes();
2202 assert!(result.is_ok());
2203 }
2204
2205 #[test]
2206 fn test_document_producer_version() {
2207 let doc = Document::new();
2208
2209 assert!(doc.metadata.producer.is_some());
2211 if let Some(producer) = &doc.metadata.producer {
2212 assert!(producer.contains("oxidize_pdf"));
2213 assert!(producer.contains(env!("CARGO_PKG_VERSION")));
2214 }
2215 }
2216
2217 #[test]
2218 fn test_document_empty_metadata_fields() {
2219 let mut doc = Document::new();
2220
2221 doc.set_title("");
2223 doc.set_author("");
2224 doc.set_subject("");
2225 doc.set_keywords("");
2226
2227 assert_eq!(doc.metadata.title.as_deref(), Some(""));
2229 assert_eq!(doc.metadata.author.as_deref(), Some(""));
2230 assert_eq!(doc.metadata.subject.as_deref(), Some(""));
2231 assert_eq!(doc.metadata.keywords.as_deref(), Some(""));
2232 }
2233
2234 #[test]
2235 fn test_document_very_long_metadata() {
2236 let mut doc = Document::new();
2237
2238 let long_title = "A".repeat(1000);
2240 let long_author = "B".repeat(500);
2241 let long_keywords = vec!["keyword"; 100].join(", ");
2242
2243 doc.set_title(&long_title);
2244 doc.set_author(&long_author);
2245 doc.set_keywords(&long_keywords);
2246
2247 assert_eq!(doc.metadata.title.as_deref(), Some(long_title.as_str()));
2248 assert_eq!(doc.metadata.author.as_deref(), Some(long_author.as_str()));
2249 assert!(doc.metadata.keywords.as_ref().unwrap().len() > 500);
2250 }
2251 }
2252}