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 = if cfg!(feature = "pro") {
91 "PRO Edition"
92 } else if cfg!(feature = "enterprise") {
93 "Enterprise Edition"
94 } else {
95 "Community Edition"
96 };
97
98 Self {
99 title: None,
100 author: None,
101 subject: None,
102 keywords: None,
103 creator: Some("oxidize_pdf".to_string()),
104 producer: Some(format!(
105 "oxidize_pdf v{} ({})",
106 env!("CARGO_PKG_VERSION"),
107 edition
108 )),
109 creation_date: Some(now),
110 modification_date: Some(now),
111 }
112 }
113}
114
115impl Document {
116 pub fn new() -> Self {
118 Self {
119 pages: Vec::new(),
120 metadata: DocumentMetadata::default(),
121 encryption: None,
122 outline: None,
123 named_destinations: None,
124 page_labels: None,
125 default_font_encoding: None,
126 acro_form: None,
127 form_manager: None,
128 compress: true, use_xref_streams: false, custom_fonts: FontCache::new(),
131 used_characters: HashSet::new(),
132 open_action: None,
133 viewer_preferences: None,
134 semantic_entities: Vec::new(),
135 struct_tree: None,
136 }
137 }
138
139 pub fn add_page(&mut self, page: Page) {
141 if let Some(used_chars) = page.get_used_characters() {
143 self.used_characters.extend(used_chars);
144 }
145 self.pages.push(page);
146 }
147
148 pub fn set_title(&mut self, title: impl Into<String>) {
150 self.metadata.title = Some(title.into());
151 }
152
153 pub fn set_author(&mut self, author: impl Into<String>) {
155 self.metadata.author = Some(author.into());
156 }
157
158 pub fn set_form_manager(&mut self, form_manager: FormManager) {
160 self.form_manager = Some(form_manager);
161 }
162
163 pub fn set_subject(&mut self, subject: impl Into<String>) {
165 self.metadata.subject = Some(subject.into());
166 }
167
168 pub fn set_keywords(&mut self, keywords: impl Into<String>) {
170 self.metadata.keywords = Some(keywords.into());
171 }
172
173 pub fn set_encryption(&mut self, encryption: DocumentEncryption) {
175 self.encryption = Some(encryption);
176 }
177
178 pub fn encrypt_with_passwords(
180 &mut self,
181 user_password: impl Into<String>,
182 owner_password: impl Into<String>,
183 ) {
184 self.encryption = Some(DocumentEncryption::with_passwords(
185 user_password,
186 owner_password,
187 ));
188 }
189
190 pub fn is_encrypted(&self) -> bool {
192 self.encryption.is_some()
193 }
194
195 pub fn set_open_action(&mut self, action: crate::actions::Action) {
197 self.open_action = Some(action);
198 }
199
200 pub fn open_action(&self) -> Option<&crate::actions::Action> {
202 self.open_action.as_ref()
203 }
204
205 pub fn set_viewer_preferences(
207 &mut self,
208 preferences: crate::viewer_preferences::ViewerPreferences,
209 ) {
210 self.viewer_preferences = Some(preferences);
211 }
212
213 pub fn viewer_preferences(&self) -> Option<&crate::viewer_preferences::ViewerPreferences> {
215 self.viewer_preferences.as_ref()
216 }
217
218 pub fn set_struct_tree(&mut self, tree: StructTree) {
244 self.struct_tree = Some(tree);
245 }
246
247 pub fn struct_tree(&self) -> Option<&StructTree> {
249 self.struct_tree.as_ref()
250 }
251
252 pub fn struct_tree_mut(&mut self) -> Option<&mut StructTree> {
254 self.struct_tree.as_mut()
255 }
256
257 pub fn get_or_create_struct_tree(&mut self) -> &mut StructTree {
274 self.struct_tree.get_or_insert_with(StructTree::new)
275 }
276
277 pub fn set_outline(&mut self, outline: OutlineTree) {
279 self.outline = Some(outline);
280 }
281
282 pub fn outline(&self) -> Option<&OutlineTree> {
284 self.outline.as_ref()
285 }
286
287 pub fn outline_mut(&mut self) -> Option<&mut OutlineTree> {
289 self.outline.as_mut()
290 }
291
292 pub fn set_named_destinations(&mut self, destinations: NamedDestinations) {
294 self.named_destinations = Some(destinations);
295 }
296
297 pub fn named_destinations(&self) -> Option<&NamedDestinations> {
299 self.named_destinations.as_ref()
300 }
301
302 pub fn named_destinations_mut(&mut self) -> Option<&mut NamedDestinations> {
304 self.named_destinations.as_mut()
305 }
306
307 pub fn set_page_labels(&mut self, labels: PageLabelTree) {
309 self.page_labels = Some(labels);
310 }
311
312 pub fn page_labels(&self) -> Option<&PageLabelTree> {
314 self.page_labels.as_ref()
315 }
316
317 pub fn page_labels_mut(&mut self) -> Option<&mut PageLabelTree> {
319 self.page_labels.as_mut()
320 }
321
322 pub fn get_page_label(&self, page_index: u32) -> String {
324 self.page_labels
325 .as_ref()
326 .and_then(|labels| labels.get_label(page_index))
327 .unwrap_or_else(|| (page_index + 1).to_string())
328 }
329
330 pub fn get_all_page_labels(&self) -> Vec<String> {
332 let page_count = self.pages.len() as u32;
333 if let Some(labels) = &self.page_labels {
334 labels.get_all_labels(page_count)
335 } else {
336 (1..=page_count).map(|i| i.to_string()).collect()
337 }
338 }
339
340 pub fn set_creator(&mut self, creator: impl Into<String>) {
342 self.metadata.creator = Some(creator.into());
343 }
344
345 pub fn set_producer(&mut self, producer: impl Into<String>) {
347 self.metadata.producer = Some(producer.into());
348 }
349
350 pub fn set_creation_date(&mut self, date: DateTime<Utc>) {
352 self.metadata.creation_date = Some(date);
353 }
354
355 pub fn set_creation_date_local(&mut self, date: DateTime<Local>) {
357 self.metadata.creation_date = Some(date.with_timezone(&Utc));
358 }
359
360 pub fn set_modification_date(&mut self, date: DateTime<Utc>) {
362 self.metadata.modification_date = Some(date);
363 }
364
365 pub fn set_modification_date_local(&mut self, date: DateTime<Local>) {
367 self.metadata.modification_date = Some(date.with_timezone(&Utc));
368 }
369
370 pub fn update_modification_date(&mut self) {
372 self.metadata.modification_date = Some(Utc::now());
373 }
374
375 pub fn set_default_font_encoding(&mut self, encoding: Option<FontEncoding>) {
390 self.default_font_encoding = encoding;
391 }
392
393 pub fn default_font_encoding(&self) -> Option<FontEncoding> {
395 self.default_font_encoding
396 }
397
398 pub fn add_font(
409 &mut self,
410 name: impl Into<String>,
411 path: impl AsRef<std::path::Path>,
412 ) -> Result<()> {
413 let name = name.into();
414 let font = CustomFont::from_file(&name, path)?;
415 self.custom_fonts.add_font(name, font)?;
416 Ok(())
417 }
418
419 pub fn add_font_from_bytes(&mut self, name: impl Into<String>, data: Vec<u8>) -> Result<()> {
431 let name = name.into();
432 let font = CustomFont::from_bytes(&name, data)?;
433
434 self.custom_fonts.add_font(name, font)?;
438 Ok(())
439 }
440
441 pub(crate) fn get_custom_font(&self, name: &str) -> Option<Arc<CustomFont>> {
443 self.custom_fonts.get_font(name)
444 }
445
446 pub fn has_custom_font(&self, name: &str) -> bool {
448 self.custom_fonts.has_font(name)
449 }
450
451 pub fn custom_font_names(&self) -> Vec<String> {
453 self.custom_fonts.font_names()
454 }
455
456 pub fn page_count(&self) -> usize {
458 self.pages.len()
459 }
460
461 pub fn acro_form(&self) -> Option<&AcroForm> {
463 self.acro_form.as_ref()
464 }
465
466 pub fn acro_form_mut(&mut self) -> Option<&mut AcroForm> {
468 self.acro_form.as_mut()
469 }
470
471 pub fn enable_forms(&mut self) -> &mut FormManager {
474 if self.acro_form.is_none() {
475 self.acro_form = Some(AcroForm::new());
476 }
477 self.form_manager.get_or_insert_with(FormManager::new)
478 }
479
480 pub fn disable_forms(&mut self) {
482 self.acro_form = None;
483 self.form_manager = None;
484 }
485
486 pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
492 self.update_modification_date();
494
495 let config = crate::writer::WriterConfig {
497 use_xref_streams: self.use_xref_streams,
498 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
500 compress_streams: self.compress,
501 incremental_update: false,
502 };
503
504 use std::io::BufWriter;
505 let file = std::fs::File::create(path)?;
506 let writer = BufWriter::with_capacity(512 * 1024, file);
509 let mut pdf_writer = PdfWriter::with_config(writer, config);
510
511 pdf_writer.write_document(self)?;
512 Ok(())
513 }
514
515 pub fn save_with_config(
521 &mut self,
522 path: impl AsRef<std::path::Path>,
523 config: crate::writer::WriterConfig,
524 ) -> Result<()> {
525 use std::io::BufWriter;
526
527 self.update_modification_date();
529
530 let file = std::fs::File::create(path)?;
533 let writer = BufWriter::with_capacity(512 * 1024, file);
535 let mut pdf_writer = PdfWriter::with_config(writer, config);
536 pdf_writer.write_document(self)?;
537 Ok(())
538 }
539
540 pub fn save_with_custom_values(
554 &mut self,
555 path: impl AsRef<std::path::Path>,
556 custom_values: &std::collections::HashMap<String, String>,
557 ) -> Result<()> {
558 let total_pages = self.pages.len();
560 for (index, page) in self.pages.iter_mut().enumerate() {
561 let page_content = page.generate_content_with_page_info(
563 Some(index + 1),
564 Some(total_pages),
565 Some(custom_values),
566 )?;
567 page.set_content(page_content);
569 }
570
571 self.save(path)
573 }
574
575 pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
581 self.update_modification_date();
583
584 let mut writer = PdfWriter::new_with_writer(buffer);
585 writer.write_document(self)?;
586 Ok(())
587 }
588
589 pub fn set_compress(&mut self, compress: bool) {
616 self.compress = compress;
617 }
618
619 pub fn enable_xref_streams(&mut self, enable: bool) -> &mut Self {
637 self.use_xref_streams = enable;
638 self
639 }
640
641 pub fn get_compress(&self) -> bool {
647 self.compress
648 }
649
650 pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
678 self.update_modification_date();
680
681 let mut buffer = Vec::new();
683
684 let config = crate::writer::WriterConfig {
686 use_xref_streams: self.use_xref_streams,
687 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
689 compress_streams: self.compress,
690 incremental_update: false,
691 };
692
693 let mut writer = PdfWriter::with_config(&mut buffer, config);
695 writer.write_document(self)?;
696
697 Ok(buffer)
698 }
699
700 pub fn to_bytes_with_config(&mut self, config: crate::writer::WriterConfig) -> Result<Vec<u8>> {
741 self.update_modification_date();
743
744 let mut buffer = Vec::new();
748
749 let mut writer = PdfWriter::with_config(&mut buffer, config);
751 writer.write_document(self)?;
752
753 Ok(buffer)
754 }
755
756 pub fn mark_entity(
782 &mut self,
783 id: impl Into<String>,
784 entity_type: EntityType,
785 bounds: BoundingBox,
786 ) -> String {
787 let entity_id = id.into();
788 let entity = SemanticEntity::new(entity_id.clone(), entity_type, bounds);
789 self.semantic_entities.push(entity);
790 entity_id
791 }
792
793 pub fn set_entity_content(&mut self, entity_id: &str, content: impl Into<String>) -> bool {
795 if let Some(entity) = self
796 .semantic_entities
797 .iter_mut()
798 .find(|e| e.id == entity_id)
799 {
800 entity.content = content.into();
801 true
802 } else {
803 false
804 }
805 }
806
807 pub fn add_entity_metadata(
809 &mut self,
810 entity_id: &str,
811 key: impl Into<String>,
812 value: impl Into<String>,
813 ) -> bool {
814 if let Some(entity) = self
815 .semantic_entities
816 .iter_mut()
817 .find(|e| e.id == entity_id)
818 {
819 entity.metadata.properties.insert(key.into(), value.into());
820 true
821 } else {
822 false
823 }
824 }
825
826 pub fn set_entity_confidence(&mut self, entity_id: &str, confidence: f32) -> bool {
828 if let Some(entity) = self
829 .semantic_entities
830 .iter_mut()
831 .find(|e| e.id == entity_id)
832 {
833 entity.metadata.confidence = Some(confidence.clamp(0.0, 1.0));
834 true
835 } else {
836 false
837 }
838 }
839
840 pub fn relate_entities(
842 &mut self,
843 from_id: &str,
844 to_id: &str,
845 relation_type: RelationType,
846 ) -> bool {
847 let target_exists = self.semantic_entities.iter().any(|e| e.id == to_id);
849 if !target_exists {
850 return false;
851 }
852
853 if let Some(entity) = self.semantic_entities.iter_mut().find(|e| e.id == from_id) {
855 entity.relationships.push(crate::semantic::EntityRelation {
856 target_id: to_id.to_string(),
857 relation_type,
858 });
859 true
860 } else {
861 false
862 }
863 }
864
865 pub fn get_semantic_entities(&self) -> &[SemanticEntity] {
867 &self.semantic_entities
868 }
869
870 pub fn get_entities_by_type(&self, entity_type: EntityType) -> Vec<&SemanticEntity> {
872 self.semantic_entities
873 .iter()
874 .filter(|e| e.entity_type == entity_type)
875 .collect()
876 }
877
878 #[cfg(feature = "semantic")]
880 pub fn export_semantic_entities_json(&self) -> Result<String> {
881 serde_json::to_string_pretty(&self.semantic_entities)
882 .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
883 }
884
885 #[cfg(feature = "semantic")]
911 pub fn export_semantic_entities_json_ld(&self) -> Result<String> {
912 use crate::semantic::{Entity, EntityMap};
913
914 let mut entity_map = EntityMap::new();
915
916 for sem_entity in &self.semantic_entities {
918 let entity = Entity {
919 id: sem_entity.id.clone(),
920 entity_type: sem_entity.entity_type.clone(),
921 bounds: (
922 sem_entity.bounds.x as f64,
923 sem_entity.bounds.y as f64,
924 sem_entity.bounds.width as f64,
925 sem_entity.bounds.height as f64,
926 ),
927 page: (sem_entity.bounds.page - 1) as usize, metadata: sem_entity.metadata.clone(),
929 };
930 entity_map.add_entity(entity);
931 }
932
933 if let Some(title) = &self.metadata.title {
935 entity_map
936 .document_metadata
937 .insert("name".to_string(), title.clone());
938 }
939 if let Some(author) = &self.metadata.author {
940 entity_map
941 .document_metadata
942 .insert("author".to_string(), author.clone());
943 }
944
945 entity_map
946 .to_json_ld()
947 .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
948 }
949
950 pub fn find_entity(&self, entity_id: &str) -> Option<&SemanticEntity> {
952 self.semantic_entities.iter().find(|e| e.id == entity_id)
953 }
954
955 pub fn remove_entity(&mut self, entity_id: &str) -> bool {
957 if let Some(pos) = self
958 .semantic_entities
959 .iter()
960 .position(|e| e.id == entity_id)
961 {
962 self.semantic_entities.remove(pos);
963 for entity in &mut self.semantic_entities {
965 entity.relationships.retain(|r| r.target_id != entity_id);
966 }
967 true
968 } else {
969 false
970 }
971 }
972
973 pub fn semantic_entity_count(&self) -> usize {
975 self.semantic_entities.len()
976 }
977
978 pub fn create_xmp_metadata(&self) -> crate::metadata::XmpMetadata {
986 let mut xmp = crate::metadata::XmpMetadata::new();
987
988 if let Some(title) = &self.metadata.title {
990 xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "title", title);
991 }
992 if let Some(author) = &self.metadata.author {
993 xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "creator", author);
994 }
995 if let Some(subject) = &self.metadata.subject {
996 xmp.set_text(
997 crate::metadata::XmpNamespace::DublinCore,
998 "description",
999 subject,
1000 );
1001 }
1002
1003 if let Some(creator) = &self.metadata.creator {
1005 xmp.set_text(
1006 crate::metadata::XmpNamespace::XmpBasic,
1007 "CreatorTool",
1008 creator,
1009 );
1010 }
1011 if let Some(creation_date) = &self.metadata.creation_date {
1012 xmp.set_date(
1013 crate::metadata::XmpNamespace::XmpBasic,
1014 "CreateDate",
1015 creation_date.to_rfc3339(),
1016 );
1017 }
1018 if let Some(mod_date) = &self.metadata.modification_date {
1019 xmp.set_date(
1020 crate::metadata::XmpNamespace::XmpBasic,
1021 "ModifyDate",
1022 mod_date.to_rfc3339(),
1023 );
1024 }
1025
1026 if let Some(producer) = &self.metadata.producer {
1028 xmp.set_text(crate::metadata::XmpNamespace::Pdf, "Producer", producer);
1029 }
1030
1031 xmp
1032 }
1033
1034 pub fn get_xmp_packet(&self) -> String {
1043 self.create_xmp_metadata().to_xmp_packet()
1044 }
1045
1046 pub fn extract_text(&self) -> Result<String> {
1048 let mut text = String::new();
1051 for (i, _page) in self.pages.iter().enumerate() {
1052 text.push_str(&format!("Text from page {} (placeholder)\n", i + 1));
1053 }
1054 Ok(text)
1055 }
1056
1057 pub fn extract_page_text(&self, page_index: usize) -> Result<String> {
1059 if page_index < self.pages.len() {
1060 Ok(format!("Text from page {} (placeholder)", page_index + 1))
1061 } else {
1062 Err(crate::error::PdfError::InvalidReference(format!(
1063 "Page index {} out of bounds",
1064 page_index
1065 )))
1066 }
1067 }
1068}
1069
1070impl Default for Document {
1071 fn default() -> Self {
1072 Self::new()
1073 }
1074}
1075
1076#[cfg(test)]
1077mod tests {
1078 use super::*;
1079
1080 #[test]
1081 fn test_document_new() {
1082 let doc = Document::new();
1083 assert!(doc.pages.is_empty());
1084 assert!(doc.metadata.title.is_none());
1085 assert!(doc.metadata.author.is_none());
1086 assert!(doc.metadata.subject.is_none());
1087 assert!(doc.metadata.keywords.is_none());
1088 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1089 assert!(doc
1090 .metadata
1091 .producer
1092 .as_ref()
1093 .unwrap()
1094 .starts_with("oxidize_pdf"));
1095 }
1096
1097 #[test]
1098 fn test_document_default() {
1099 let doc = Document::default();
1100 assert!(doc.pages.is_empty());
1101 }
1102
1103 #[test]
1104 fn test_add_page() {
1105 let mut doc = Document::new();
1106 let page1 = Page::a4();
1107 let page2 = Page::letter();
1108
1109 doc.add_page(page1);
1110 assert_eq!(doc.pages.len(), 1);
1111
1112 doc.add_page(page2);
1113 assert_eq!(doc.pages.len(), 2);
1114 }
1115
1116 #[test]
1117 fn test_set_title() {
1118 let mut doc = Document::new();
1119 assert!(doc.metadata.title.is_none());
1120
1121 doc.set_title("Test Document");
1122 assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
1123
1124 doc.set_title(String::from("Another Title"));
1125 assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
1126 }
1127
1128 #[test]
1129 fn test_set_author() {
1130 let mut doc = Document::new();
1131 assert!(doc.metadata.author.is_none());
1132
1133 doc.set_author("John Doe");
1134 assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
1135 }
1136
1137 #[test]
1138 fn test_set_subject() {
1139 let mut doc = Document::new();
1140 assert!(doc.metadata.subject.is_none());
1141
1142 doc.set_subject("Test Subject");
1143 assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
1144 }
1145
1146 #[test]
1147 fn test_set_keywords() {
1148 let mut doc = Document::new();
1149 assert!(doc.metadata.keywords.is_none());
1150
1151 doc.set_keywords("test, pdf, rust");
1152 assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
1153 }
1154
1155 #[test]
1156 fn test_metadata_default() {
1157 let metadata = DocumentMetadata::default();
1158 assert!(metadata.title.is_none());
1159 assert!(metadata.author.is_none());
1160 assert!(metadata.subject.is_none());
1161 assert!(metadata.keywords.is_none());
1162 assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
1163 assert!(metadata
1164 .producer
1165 .as_ref()
1166 .unwrap()
1167 .starts_with("oxidize_pdf"));
1168 }
1169
1170 #[test]
1171 fn test_write_to_buffer() {
1172 let mut doc = Document::new();
1173 doc.set_title("Buffer Test");
1174 doc.add_page(Page::a4());
1175
1176 let mut buffer = Vec::new();
1177 let result = doc.write(&mut buffer);
1178
1179 assert!(result.is_ok());
1180 assert!(!buffer.is_empty());
1181 assert!(buffer.starts_with(b"%PDF-1.7"));
1182 }
1183
1184 #[test]
1185 fn test_document_with_multiple_pages() {
1186 let mut doc = Document::new();
1187 doc.set_title("Multi-page Document");
1188 doc.set_author("Test Author");
1189 doc.set_subject("Testing multiple pages");
1190 doc.set_keywords("test, multiple, pages");
1191
1192 for _ in 0..5 {
1193 doc.add_page(Page::a4());
1194 }
1195
1196 assert_eq!(doc.pages.len(), 5);
1197 assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
1198 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1199 }
1200
1201 #[test]
1202 fn test_empty_document_write() {
1203 let mut doc = Document::new();
1204 let mut buffer = Vec::new();
1205
1206 let result = doc.write(&mut buffer);
1208 assert!(result.is_ok());
1209 assert!(!buffer.is_empty());
1210 assert!(buffer.starts_with(b"%PDF-1.7"));
1211 }
1212
1213 mod integration_tests {
1215 use super::*;
1216 use crate::graphics::Color;
1217 use crate::text::Font;
1218 use std::fs;
1219 use tempfile::TempDir;
1220
1221 #[test]
1222 fn test_document_writer_roundtrip() {
1223 let temp_dir = TempDir::new().unwrap();
1224 let file_path = temp_dir.path().join("test.pdf");
1225
1226 let mut doc = Document::new();
1228 doc.set_title("Integration Test");
1229 doc.set_author("Test Author");
1230 doc.set_subject("Writer Integration");
1231 doc.set_keywords("test, writer, integration");
1232
1233 let mut page = Page::a4();
1234 page.text()
1235 .set_font(Font::Helvetica, 12.0)
1236 .at(100.0, 700.0)
1237 .write("Integration Test Content")
1238 .unwrap();
1239
1240 doc.add_page(page);
1241
1242 let result = doc.save(&file_path);
1244 assert!(result.is_ok());
1245
1246 assert!(file_path.exists());
1248 let metadata = fs::metadata(&file_path).unwrap();
1249 assert!(metadata.len() > 0);
1250
1251 let content = fs::read(&file_path).unwrap();
1253 assert!(content.starts_with(b"%PDF-1.7"));
1254 assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
1256 }
1257
1258 #[test]
1259 fn test_document_with_complex_content() {
1260 let temp_dir = TempDir::new().unwrap();
1261 let file_path = temp_dir.path().join("complex.pdf");
1262
1263 let mut doc = Document::new();
1264 doc.set_title("Complex Content Test");
1265
1266 let mut page = Page::a4();
1268
1269 page.text()
1271 .set_font(Font::Helvetica, 14.0)
1272 .at(50.0, 750.0)
1273 .write("Complex Content Test")
1274 .unwrap();
1275
1276 page.graphics()
1278 .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
1279 .rectangle(50.0, 500.0, 200.0, 100.0)
1280 .fill();
1281
1282 page.graphics()
1283 .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
1284 .set_line_width(2.0)
1285 .move_to(50.0, 400.0)
1286 .line_to(250.0, 400.0)
1287 .stroke();
1288
1289 doc.add_page(page);
1290
1291 let result = doc.save(&file_path);
1293 assert!(result.is_ok());
1294 assert!(file_path.exists());
1295 }
1296
1297 #[test]
1298 fn test_document_multiple_pages_integration() {
1299 let temp_dir = TempDir::new().unwrap();
1300 let file_path = temp_dir.path().join("multipage.pdf");
1301
1302 let mut doc = Document::new();
1303 doc.set_title("Multi-page Integration Test");
1304
1305 for i in 1..=5 {
1307 let mut page = Page::a4();
1308
1309 page.text()
1310 .set_font(Font::Helvetica, 16.0)
1311 .at(50.0, 750.0)
1312 .write(&format!("Page {i}"))
1313 .unwrap();
1314
1315 page.text()
1316 .set_font(Font::Helvetica, 12.0)
1317 .at(50.0, 700.0)
1318 .write(&format!("This is the content for page {i}"))
1319 .unwrap();
1320
1321 let color = match i % 3 {
1323 0 => Color::rgb(1.0, 0.0, 0.0),
1324 1 => Color::rgb(0.0, 1.0, 0.0),
1325 _ => Color::rgb(0.0, 0.0, 1.0),
1326 };
1327
1328 page.graphics()
1329 .set_fill_color(color)
1330 .rectangle(50.0, 600.0, 100.0, 50.0)
1331 .fill();
1332
1333 doc.add_page(page);
1334 }
1335
1336 let result = doc.save(&file_path);
1338 assert!(result.is_ok());
1339 assert!(file_path.exists());
1340
1341 let metadata = fs::metadata(&file_path).unwrap();
1343 assert!(metadata.len() > 1000); }
1345
1346 #[test]
1347 fn test_document_metadata_persistence() {
1348 let temp_dir = TempDir::new().unwrap();
1349 let file_path = temp_dir.path().join("metadata.pdf");
1350
1351 let mut doc = Document::new();
1352 doc.set_title("Metadata Persistence Test");
1353 doc.set_author("Test Author");
1354 doc.set_subject("Testing metadata preservation");
1355 doc.set_keywords("metadata, persistence, test");
1356
1357 doc.add_page(Page::a4());
1358
1359 let result = doc.save(&file_path);
1361 assert!(result.is_ok());
1362
1363 let content = fs::read(&file_path).unwrap();
1365 let content_str = String::from_utf8_lossy(&content);
1366
1367 assert!(content_str.contains("Metadata Persistence Test"));
1369 assert!(content_str.contains("Test Author"));
1370 }
1371
1372 #[test]
1373 fn test_document_writer_error_handling() {
1374 let mut doc = Document::new();
1375 doc.add_page(Page::a4());
1376
1377 let result = doc.save("/invalid/path/test.pdf");
1379 assert!(result.is_err());
1380 }
1381
1382 #[test]
1383 fn test_document_page_integration() {
1384 let mut doc = Document::new();
1385
1386 let page1 = Page::a4();
1388 let page2 = Page::letter();
1389 let mut page3 = Page::new(500.0, 400.0);
1390
1391 page3
1393 .text()
1394 .set_font(Font::Helvetica, 10.0)
1395 .at(25.0, 350.0)
1396 .write("Custom size page")
1397 .unwrap();
1398
1399 doc.add_page(page1);
1400 doc.add_page(page2);
1401 doc.add_page(page3);
1402
1403 assert_eq!(doc.pages.len(), 3);
1404
1405 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); }
1413
1414 #[test]
1415 fn test_document_content_generation() {
1416 let temp_dir = TempDir::new().unwrap();
1417 let file_path = temp_dir.path().join("content.pdf");
1418
1419 let mut doc = Document::new();
1420 doc.set_title("Content Generation Test");
1421
1422 let mut page = Page::a4();
1423
1424 for i in 0..10 {
1426 let y_pos = 700.0 - (i as f64 * 30.0);
1427 page.text()
1428 .set_font(Font::Helvetica, 12.0)
1429 .at(50.0, y_pos)
1430 .write(&format!("Generated line {}", i + 1))
1431 .unwrap();
1432 }
1433
1434 doc.add_page(page);
1435
1436 let result = doc.save(&file_path);
1438 assert!(result.is_ok());
1439 assert!(file_path.exists());
1440
1441 let metadata = fs::metadata(&file_path).unwrap();
1443 assert!(metadata.len() > 500); }
1445
1446 #[test]
1447 fn test_document_buffer_vs_file_write() {
1448 let temp_dir = TempDir::new().unwrap();
1449 let file_path = temp_dir.path().join("buffer_vs_file.pdf");
1450
1451 let mut doc = Document::new();
1452 doc.set_title("Buffer vs File Test");
1453 doc.add_page(Page::a4());
1454
1455 let mut buffer = Vec::new();
1457 let buffer_result = doc.write(&mut buffer);
1458 assert!(buffer_result.is_ok());
1459
1460 let file_result = doc.save(&file_path);
1462 assert!(file_result.is_ok());
1463
1464 let file_content = fs::read(&file_path).unwrap();
1466
1467 assert!(buffer.starts_with(b"%PDF-1.7"));
1469 assert!(file_content.starts_with(b"%PDF-1.7"));
1470 assert!(buffer.ends_with(b"%%EOF\n"));
1471 assert!(file_content.ends_with(b"%%EOF\n"));
1472
1473 let buffer_str = String::from_utf8_lossy(&buffer);
1475 let file_str = String::from_utf8_lossy(&file_content);
1476 assert!(buffer_str.contains("Buffer vs File Test"));
1477 assert!(file_str.contains("Buffer vs File Test"));
1478 }
1479
1480 #[test]
1481 fn test_document_large_content_handling() {
1482 let temp_dir = TempDir::new().unwrap();
1483 let file_path = temp_dir.path().join("large_content.pdf");
1484
1485 let mut doc = Document::new();
1486 doc.set_title("Large Content Test");
1487
1488 let mut page = Page::a4();
1489
1490 let large_text =
1492 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(200);
1493 page.text()
1494 .set_font(Font::Helvetica, 10.0)
1495 .at(50.0, 750.0)
1496 .write(&large_text)
1497 .unwrap();
1498
1499 doc.add_page(page);
1500
1501 let result = doc.save(&file_path);
1503 assert!(result.is_ok());
1504 assert!(file_path.exists());
1505
1506 let metadata = fs::metadata(&file_path).unwrap();
1508 assert!(metadata.len() > 500); }
1510
1511 #[test]
1512 fn test_document_incremental_building() {
1513 let temp_dir = TempDir::new().unwrap();
1514 let file_path = temp_dir.path().join("incremental.pdf");
1515
1516 let mut doc = Document::new();
1517
1518 doc.set_title("Incremental Building Test");
1520
1521 let mut page1 = Page::a4();
1523 page1
1524 .text()
1525 .set_font(Font::Helvetica, 12.0)
1526 .at(50.0, 750.0)
1527 .write("First page content")
1528 .unwrap();
1529 doc.add_page(page1);
1530
1531 doc.set_author("Incremental Author");
1533 doc.set_subject("Incremental Subject");
1534
1535 let mut page2 = Page::a4();
1537 page2
1538 .text()
1539 .set_font(Font::Helvetica, 12.0)
1540 .at(50.0, 750.0)
1541 .write("Second page content")
1542 .unwrap();
1543 doc.add_page(page2);
1544
1545 doc.set_keywords("incremental, building, test");
1547
1548 let result = doc.save(&file_path);
1550 assert!(result.is_ok());
1551 assert!(file_path.exists());
1552
1553 assert_eq!(doc.pages.len(), 2);
1555 assert_eq!(
1556 doc.metadata.title,
1557 Some("Incremental Building Test".to_string())
1558 );
1559 assert_eq!(doc.metadata.author, Some("Incremental Author".to_string()));
1560 assert_eq!(
1561 doc.metadata.subject,
1562 Some("Incremental Subject".to_string())
1563 );
1564 assert_eq!(
1565 doc.metadata.keywords,
1566 Some("incremental, building, test".to_string())
1567 );
1568 }
1569
1570 #[test]
1571 fn test_document_concurrent_page_operations() {
1572 let mut doc = Document::new();
1573 doc.set_title("Concurrent Operations Test");
1574
1575 let mut pages = Vec::new();
1577
1578 for i in 0..5 {
1580 let mut page = Page::a4();
1581 page.text()
1582 .set_font(Font::Helvetica, 12.0)
1583 .at(50.0, 750.0)
1584 .write(&format!("Concurrent page {i}"))
1585 .unwrap();
1586 pages.push(page);
1587 }
1588
1589 for page in pages {
1591 doc.add_page(page);
1592 }
1593
1594 assert_eq!(doc.pages.len(), 5);
1595
1596 let temp_dir = TempDir::new().unwrap();
1598 let file_path = temp_dir.path().join("concurrent.pdf");
1599 let result = doc.save(&file_path);
1600 assert!(result.is_ok());
1601 }
1602
1603 #[test]
1604 fn test_document_memory_efficiency() {
1605 let mut doc = Document::new();
1606 doc.set_title("Memory Efficiency Test");
1607
1608 for i in 0..10 {
1610 let mut page = Page::a4();
1611 page.text()
1612 .set_font(Font::Helvetica, 12.0)
1613 .at(50.0, 700.0)
1614 .write(&format!("Memory test page {i}"))
1615 .unwrap();
1616 doc.add_page(page);
1617 }
1618
1619 let mut buffer = Vec::new();
1621 let result = doc.write(&mut buffer);
1622 assert!(result.is_ok());
1623 assert!(!buffer.is_empty());
1624
1625 assert!(buffer.len() < 1_000_000); }
1628
1629 #[test]
1630 fn test_document_creator_producer() {
1631 let mut doc = Document::new();
1632
1633 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1635 assert!(doc
1636 .metadata
1637 .producer
1638 .as_ref()
1639 .unwrap()
1640 .contains("oxidize_pdf"));
1641
1642 doc.set_creator("My Application");
1644 doc.set_producer("My PDF Library v1.0");
1645
1646 assert_eq!(doc.metadata.creator, Some("My Application".to_string()));
1647 assert_eq!(
1648 doc.metadata.producer,
1649 Some("My PDF Library v1.0".to_string())
1650 );
1651 }
1652
1653 #[test]
1654 fn test_document_dates() {
1655 use chrono::{TimeZone, Utc};
1656
1657 let mut doc = Document::new();
1658
1659 assert!(doc.metadata.creation_date.is_some());
1661 assert!(doc.metadata.modification_date.is_some());
1662
1663 let creation_date = Utc.with_ymd_and_hms(2023, 1, 1, 12, 0, 0).unwrap();
1665 let mod_date = Utc.with_ymd_and_hms(2023, 6, 15, 18, 30, 0).unwrap();
1666
1667 doc.set_creation_date(creation_date);
1668 doc.set_modification_date(mod_date);
1669
1670 assert_eq!(doc.metadata.creation_date, Some(creation_date));
1671 assert_eq!(doc.metadata.modification_date, Some(mod_date));
1672 }
1673
1674 #[test]
1675 fn test_document_dates_local() {
1676 use chrono::{Local, TimeZone};
1677
1678 let mut doc = Document::new();
1679
1680 let local_date = Local.with_ymd_and_hms(2023, 12, 25, 10, 30, 0).unwrap();
1682 doc.set_creation_date_local(local_date);
1683
1684 assert!(doc.metadata.creation_date.is_some());
1686 assert!(doc.metadata.creation_date.is_some());
1688 }
1689
1690 #[test]
1691 fn test_update_modification_date() {
1692 let mut doc = Document::new();
1693
1694 let initial_mod_date = doc.metadata.modification_date;
1695 assert!(initial_mod_date.is_some());
1696
1697 std::thread::sleep(std::time::Duration::from_millis(10));
1699
1700 doc.update_modification_date();
1701
1702 let new_mod_date = doc.metadata.modification_date;
1703 assert!(new_mod_date.is_some());
1704 assert!(new_mod_date.unwrap() > initial_mod_date.unwrap());
1705 }
1706
1707 #[test]
1708 fn test_document_save_updates_modification_date() {
1709 let temp_dir = TempDir::new().unwrap();
1710 let file_path = temp_dir.path().join("mod_date_test.pdf");
1711
1712 let mut doc = Document::new();
1713 doc.add_page(Page::a4());
1714
1715 let initial_mod_date = doc.metadata.modification_date;
1716
1717 std::thread::sleep(std::time::Duration::from_millis(10));
1719
1720 doc.save(&file_path).unwrap();
1721
1722 assert!(doc.metadata.modification_date.unwrap() > initial_mod_date.unwrap());
1724 }
1725
1726 #[test]
1727 fn test_document_metadata_complete() {
1728 let mut doc = Document::new();
1729
1730 doc.set_title("Complete Metadata Test");
1732 doc.set_author("Test Author");
1733 doc.set_subject("Testing all metadata fields");
1734 doc.set_keywords("test, metadata, complete");
1735 doc.set_creator("Test Application v1.0");
1736 doc.set_producer("oxidize_pdf Test Suite");
1737
1738 assert_eq!(
1740 doc.metadata.title,
1741 Some("Complete Metadata Test".to_string())
1742 );
1743 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1744 assert_eq!(
1745 doc.metadata.subject,
1746 Some("Testing all metadata fields".to_string())
1747 );
1748 assert_eq!(
1749 doc.metadata.keywords,
1750 Some("test, metadata, complete".to_string())
1751 );
1752 assert_eq!(
1753 doc.metadata.creator,
1754 Some("Test Application v1.0".to_string())
1755 );
1756 assert_eq!(
1757 doc.metadata.producer,
1758 Some("oxidize_pdf Test Suite".to_string())
1759 );
1760 assert!(doc.metadata.creation_date.is_some());
1761 assert!(doc.metadata.modification_date.is_some());
1762 }
1763
1764 #[test]
1765 fn test_document_to_bytes() {
1766 let mut doc = Document::new();
1767 doc.set_title("Test Document");
1768 doc.set_author("Test Author");
1769
1770 let page = Page::a4();
1771 doc.add_page(page);
1772
1773 let pdf_bytes = doc.to_bytes().unwrap();
1775
1776 assert!(!pdf_bytes.is_empty());
1778 assert!(pdf_bytes.len() > 100); let header = &pdf_bytes[0..5];
1782 assert_eq!(header, b"%PDF-");
1783
1784 let pdf_str = String::from_utf8_lossy(&pdf_bytes);
1786 assert!(pdf_str.contains("Test Document"));
1787 assert!(pdf_str.contains("Test Author"));
1788 }
1789
1790 #[test]
1791 fn test_document_to_bytes_with_config() {
1792 let mut doc = Document::new();
1793 doc.set_title("Test Document XRef");
1794
1795 let page = Page::a4();
1796 doc.add_page(page);
1797
1798 let config = crate::writer::WriterConfig {
1799 use_xref_streams: true,
1800 use_object_streams: false,
1801 pdf_version: "1.5".to_string(),
1802 compress_streams: true,
1803 incremental_update: false,
1804 };
1805
1806 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1808
1809 assert!(!pdf_bytes.is_empty());
1811 assert!(pdf_bytes.len() > 100);
1812
1813 let header = String::from_utf8_lossy(&pdf_bytes[0..8]);
1815 assert!(header.contains("PDF-1.5"));
1816 }
1817
1818 #[test]
1819 fn test_to_bytes_vs_save_equivalence() {
1820 use std::fs;
1821 use tempfile::NamedTempFile;
1822
1823 let mut doc1 = Document::new();
1825 doc1.set_title("Equivalence Test");
1826 doc1.add_page(Page::a4());
1827
1828 let mut doc2 = Document::new();
1829 doc2.set_title("Equivalence Test");
1830 doc2.add_page(Page::a4());
1831
1832 let pdf_bytes = doc1.to_bytes().unwrap();
1834
1835 let temp_file = NamedTempFile::new().unwrap();
1837 doc2.save(temp_file.path()).unwrap();
1838 let file_bytes = fs::read(temp_file.path()).unwrap();
1839
1840 assert!(!pdf_bytes.is_empty());
1842 assert!(!file_bytes.is_empty());
1843 assert_eq!(&pdf_bytes[0..5], &file_bytes[0..5]); }
1845
1846 #[test]
1847 fn test_document_set_compress() {
1848 let mut doc = Document::new();
1849 doc.set_title("Compression Test");
1850 doc.add_page(Page::a4());
1851
1852 assert!(doc.get_compress());
1854
1855 doc.set_compress(true);
1857 let compressed_bytes = doc.to_bytes().unwrap();
1858
1859 doc.set_compress(false);
1861 let uncompressed_bytes = doc.to_bytes().unwrap();
1862
1863 assert!(!compressed_bytes.is_empty());
1865 assert!(!uncompressed_bytes.is_empty());
1866
1867 assert_eq!(&compressed_bytes[0..5], b"%PDF-");
1869 assert_eq!(&uncompressed_bytes[0..5], b"%PDF-");
1870 }
1871
1872 #[test]
1873 fn test_document_compression_config_inheritance() {
1874 let mut doc = Document::new();
1875 doc.set_title("Config Inheritance Test");
1876 doc.add_page(Page::a4());
1877
1878 doc.set_compress(false);
1880
1881 let config = crate::writer::WriterConfig {
1883 use_xref_streams: false,
1884 use_object_streams: false,
1885 pdf_version: "1.7".to_string(),
1886 compress_streams: true,
1887 incremental_update: false,
1888 };
1889
1890 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1892
1893 assert!(!pdf_bytes.is_empty());
1895 assert_eq!(&pdf_bytes[0..5], b"%PDF-");
1896 }
1897
1898 #[test]
1899 fn test_document_metadata_all_fields() {
1900 let mut doc = Document::new();
1901
1902 doc.set_title("Test Document");
1904 doc.set_author("John Doe");
1905 doc.set_subject("Testing PDF metadata");
1906 doc.set_keywords("test, pdf, metadata");
1907 doc.set_creator("Test Suite");
1908 doc.set_producer("oxidize_pdf tests");
1909
1910 assert_eq!(doc.metadata.title.as_deref(), Some("Test Document"));
1912 assert_eq!(doc.metadata.author.as_deref(), Some("John Doe"));
1913 assert_eq!(
1914 doc.metadata.subject.as_deref(),
1915 Some("Testing PDF metadata")
1916 );
1917 assert_eq!(
1918 doc.metadata.keywords.as_deref(),
1919 Some("test, pdf, metadata")
1920 );
1921 assert_eq!(doc.metadata.creator.as_deref(), Some("Test Suite"));
1922 assert_eq!(doc.metadata.producer.as_deref(), Some("oxidize_pdf tests"));
1923 assert!(doc.metadata.creation_date.is_some());
1924 assert!(doc.metadata.modification_date.is_some());
1925 }
1926
1927 #[test]
1928 fn test_document_add_pages() {
1929 let mut doc = Document::new();
1930
1931 assert_eq!(doc.page_count(), 0);
1933
1934 let page1 = Page::a4();
1936 let page2 = Page::letter();
1937 let page3 = Page::legal();
1938
1939 doc.add_page(page1);
1940 assert_eq!(doc.page_count(), 1);
1941
1942 doc.add_page(page2);
1943 assert_eq!(doc.page_count(), 2);
1944
1945 doc.add_page(page3);
1946 assert_eq!(doc.page_count(), 3);
1947
1948 let result = doc.to_bytes();
1950 assert!(result.is_ok());
1951 }
1952
1953 #[test]
1954 fn test_document_default_font_encoding() {
1955 let mut doc = Document::new();
1956
1957 assert!(doc.default_font_encoding.is_none());
1959
1960 doc.set_default_font_encoding(Some(FontEncoding::WinAnsiEncoding));
1962 assert_eq!(
1963 doc.default_font_encoding(),
1964 Some(FontEncoding::WinAnsiEncoding)
1965 );
1966
1967 doc.set_default_font_encoding(Some(FontEncoding::MacRomanEncoding));
1969 assert_eq!(
1970 doc.default_font_encoding(),
1971 Some(FontEncoding::MacRomanEncoding)
1972 );
1973 }
1974
1975 #[test]
1976 fn test_document_compression_setting() {
1977 let mut doc = Document::new();
1978
1979 assert!(doc.compress);
1981
1982 doc.set_compress(false);
1984 assert!(!doc.compress);
1985
1986 doc.set_compress(true);
1988 assert!(doc.compress);
1989 }
1990
1991 #[test]
1992 fn test_document_with_empty_pages() {
1993 let mut doc = Document::new();
1994
1995 doc.add_page(Page::a4());
1997
1998 let result = doc.to_bytes();
2000 assert!(result.is_ok());
2001
2002 let pdf_bytes = result.unwrap();
2003 assert!(!pdf_bytes.is_empty());
2004 assert!(pdf_bytes.starts_with(b"%PDF-"));
2005 }
2006
2007 #[test]
2008 fn test_document_with_multiple_page_sizes() {
2009 let mut doc = Document::new();
2010
2011 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);
2019
2020 let result = doc.to_bytes();
2024 assert!(result.is_ok());
2025 }
2026
2027 #[test]
2028 fn test_document_metadata_dates() {
2029 use chrono::Duration;
2030
2031 let doc = Document::new();
2032
2033 assert!(doc.metadata.creation_date.is_some());
2035 assert!(doc.metadata.modification_date.is_some());
2036
2037 if let (Some(created), Some(modified)) =
2038 (doc.metadata.creation_date, doc.metadata.modification_date)
2039 {
2040 let diff = modified - created;
2042 assert!(diff < Duration::seconds(1));
2043 }
2044 }
2045
2046 #[test]
2047 fn test_document_builder_pattern() {
2048 let mut doc = Document::new();
2050 doc.set_title("Fluent");
2051 doc.set_author("Builder");
2052 doc.set_compress(true);
2053
2054 assert_eq!(doc.metadata.title.as_deref(), Some("Fluent"));
2055 assert_eq!(doc.metadata.author.as_deref(), Some("Builder"));
2056 assert!(doc.compress);
2057 }
2058
2059 #[test]
2060 fn test_xref_streams_functionality() {
2061 use crate::{Document, Font, Page};
2062
2063 let mut doc = Document::new();
2065 assert!(!doc.use_xref_streams);
2066
2067 let mut page = Page::a4();
2068 page.text()
2069 .set_font(Font::Helvetica, 12.0)
2070 .at(100.0, 700.0)
2071 .write("Testing XRef Streams")
2072 .unwrap();
2073
2074 doc.add_page(page);
2075
2076 let pdf_without_xref = doc.to_bytes().unwrap();
2078
2079 let pdf_str = String::from_utf8_lossy(&pdf_without_xref);
2081 assert!(pdf_str.contains("xref"), "Traditional xref table not found");
2082 assert!(
2083 !pdf_str.contains("/Type /XRef"),
2084 "XRef stream found when it shouldn't be"
2085 );
2086
2087 doc.enable_xref_streams(true);
2089 assert!(doc.use_xref_streams);
2090
2091 let pdf_with_xref = doc.to_bytes().unwrap();
2093
2094 let pdf_str = String::from_utf8_lossy(&pdf_with_xref);
2096 assert!(
2098 pdf_str.contains("/Type /XRef") || pdf_str.contains("stream"),
2099 "XRef stream not found when enabled"
2100 );
2101
2102 assert!(
2104 pdf_str.contains("PDF-1.5"),
2105 "PDF version not set to 1.5 for xref streams"
2106 );
2107
2108 let mut doc2 = Document::new();
2110 doc2.enable_xref_streams(true);
2111 doc2.set_title("XRef Streams Test");
2112 doc2.set_author("oxidize-pdf");
2113
2114 assert!(doc2.use_xref_streams);
2115 assert_eq!(doc2.metadata.title.as_deref(), Some("XRef Streams Test"));
2116 assert_eq!(doc2.metadata.author.as_deref(), Some("oxidize-pdf"));
2117 }
2118
2119 #[test]
2120 fn test_document_save_to_vec() {
2121 let mut doc = Document::new();
2122 doc.set_title("Test Save");
2123 doc.add_page(Page::a4());
2124
2125 let bytes_result = doc.to_bytes();
2127 assert!(bytes_result.is_ok());
2128
2129 let bytes = bytes_result.unwrap();
2130 assert!(!bytes.is_empty());
2131 assert!(bytes.starts_with(b"%PDF-"));
2132 assert!(bytes.ends_with(b"%%EOF") || bytes.ends_with(b"%%EOF\n"));
2133 }
2134
2135 #[test]
2136 fn test_document_unicode_metadata() {
2137 let mut doc = Document::new();
2138
2139 doc.set_title("日本語のタイトル");
2141 doc.set_author("作者名 😀");
2142 doc.set_subject("Тема документа");
2143 doc.set_keywords("كلمات, מפתח, 关键词");
2144
2145 assert_eq!(doc.metadata.title.as_deref(), Some("日本語のタイトル"));
2146 assert_eq!(doc.metadata.author.as_deref(), Some("作者名 😀"));
2147 assert_eq!(doc.metadata.subject.as_deref(), Some("Тема документа"));
2148 assert_eq!(
2149 doc.metadata.keywords.as_deref(),
2150 Some("كلمات, מפתח, 关键词")
2151 );
2152 }
2153
2154 #[test]
2155 fn test_document_page_iteration() {
2156 let mut doc = Document::new();
2157
2158 for i in 0..5 {
2160 let mut page = Page::a4();
2161 let gc = page.graphics();
2162 gc.begin_text();
2163 let _ = gc.show_text(&format!("Page {}", i + 1));
2164 gc.end_text();
2165 doc.add_page(page);
2166 }
2167
2168 assert_eq!(doc.page_count(), 5);
2170
2171 let result = doc.to_bytes();
2173 assert!(result.is_ok());
2174 }
2175
2176 #[test]
2177 fn test_document_with_graphics_content() {
2178 let mut doc = Document::new();
2179
2180 let mut page = Page::a4();
2181 {
2182 let gc = page.graphics();
2183
2184 gc.save_state();
2186
2187 gc.rectangle(100.0, 100.0, 200.0, 150.0);
2189 gc.stroke();
2190
2191 gc.move_to(300.0, 300.0);
2193 gc.circle(300.0, 300.0, 50.0);
2194 gc.fill();
2195
2196 gc.begin_text();
2198 gc.set_text_position(100.0, 500.0);
2199 let _ = gc.show_text("Graphics Test");
2200 gc.end_text();
2201
2202 gc.restore_state();
2203 }
2204
2205 doc.add_page(page);
2206
2207 let result = doc.to_bytes();
2209 assert!(result.is_ok());
2210 }
2211
2212 #[test]
2213 fn test_document_producer_version() {
2214 let doc = Document::new();
2215
2216 assert!(doc.metadata.producer.is_some());
2218 if let Some(producer) = &doc.metadata.producer {
2219 assert!(producer.contains("oxidize_pdf"));
2220 assert!(producer.contains(env!("CARGO_PKG_VERSION")));
2221 }
2222 }
2223
2224 #[test]
2225 fn test_document_empty_metadata_fields() {
2226 let mut doc = Document::new();
2227
2228 doc.set_title("");
2230 doc.set_author("");
2231 doc.set_subject("");
2232 doc.set_keywords("");
2233
2234 assert_eq!(doc.metadata.title.as_deref(), Some(""));
2236 assert_eq!(doc.metadata.author.as_deref(), Some(""));
2237 assert_eq!(doc.metadata.subject.as_deref(), Some(""));
2238 assert_eq!(doc.metadata.keywords.as_deref(), Some(""));
2239 }
2240
2241 #[test]
2242 fn test_document_very_long_metadata() {
2243 let mut doc = Document::new();
2244
2245 let long_title = "A".repeat(1000);
2247 let long_author = "B".repeat(500);
2248 let long_keywords = vec!["keyword"; 100].join(", ");
2249
2250 doc.set_title(&long_title);
2251 doc.set_author(&long_author);
2252 doc.set_keywords(&long_keywords);
2253
2254 assert_eq!(doc.metadata.title.as_deref(), Some(long_title.as_str()));
2255 assert_eq!(doc.metadata.author.as_deref(), Some(long_author.as_str()));
2256 assert!(doc.metadata.keywords.as_ref().unwrap().len() > 500);
2257 }
2258 }
2259}