1use crate::error::Result;
2use crate::fonts::{Font as CustomFont, FontCache};
3use crate::forms::{AcroForm, FormManager};
4use crate::objects::{Object, ObjectId};
5use crate::page::Page;
6use crate::page_labels::PageLabelTree;
7use crate::semantic::{BoundingBox, EntityType, RelationType, SemanticEntity};
8use crate::structure::{NamedDestinations, OutlineTree, PageTree};
9use crate::text::{FontEncoding, FontWithEncoding};
10use crate::writer::PdfWriter;
11use chrono::{DateTime, Local, Utc};
12use std::collections::{HashMap, HashSet};
13use std::sync::Arc;
14
15mod encryption;
16pub use encryption::{DocumentEncryption, EncryptionStrength};
17
18pub struct Document {
35 pub(crate) pages: Vec<Page>,
36 #[allow(dead_code)]
37 pub(crate) objects: HashMap<ObjectId, Object>,
38 #[allow(dead_code)]
39 pub(crate) next_object_id: u32,
40 pub(crate) metadata: DocumentMetadata,
41 pub(crate) encryption: Option<DocumentEncryption>,
42 pub(crate) outline: Option<OutlineTree>,
43 pub(crate) named_destinations: Option<NamedDestinations>,
44 #[allow(dead_code)]
45 pub(crate) page_tree: Option<PageTree>,
46 pub(crate) page_labels: Option<PageLabelTree>,
47 pub(crate) default_font_encoding: Option<FontEncoding>,
49 pub(crate) acro_form: Option<AcroForm>,
51 pub(crate) form_manager: Option<FormManager>,
53 pub(crate) compress: bool,
55 pub(crate) use_xref_streams: bool,
57 pub(crate) custom_fonts: FontCache,
59 #[allow(dead_code)]
61 pub(crate) embedded_fonts: HashMap<String, ObjectId>,
62 pub(crate) used_characters: HashSet<char>,
64 pub(crate) open_action: Option<crate::actions::Action>,
66 pub(crate) viewer_preferences: Option<crate::viewer_preferences::ViewerPreferences>,
68 pub(crate) semantic_entities: Vec<SemanticEntity>,
70}
71
72#[derive(Debug, Clone)]
74pub struct DocumentMetadata {
75 pub title: Option<String>,
77 pub author: Option<String>,
79 pub subject: Option<String>,
81 pub keywords: Option<String>,
83 pub creator: Option<String>,
85 pub producer: Option<String>,
87 pub creation_date: Option<DateTime<Utc>>,
89 pub modification_date: Option<DateTime<Utc>>,
91}
92
93impl Default for DocumentMetadata {
94 fn default() -> Self {
95 let now = Utc::now();
96
97 let edition = if cfg!(feature = "pro") {
99 "PRO Edition"
100 } else if cfg!(feature = "enterprise") {
101 "Enterprise Edition"
102 } else {
103 "Community Edition"
104 };
105
106 Self {
107 title: None,
108 author: None,
109 subject: None,
110 keywords: None,
111 creator: Some("oxidize_pdf".to_string()),
112 producer: Some(format!(
113 "oxidize_pdf v{} ({})",
114 env!("CARGO_PKG_VERSION"),
115 edition
116 )),
117 creation_date: Some(now),
118 modification_date: Some(now),
119 }
120 }
121}
122
123impl Document {
124 pub fn new() -> Self {
126 Self {
127 pages: Vec::new(),
128 objects: HashMap::new(),
129 next_object_id: 1,
130 metadata: DocumentMetadata::default(),
131 encryption: None,
132 outline: None,
133 named_destinations: None,
134 page_tree: None,
135 page_labels: None,
136 default_font_encoding: None,
137 acro_form: None,
138 form_manager: None,
139 compress: true, use_xref_streams: false, custom_fonts: FontCache::new(),
142 embedded_fonts: HashMap::new(),
143 used_characters: HashSet::new(),
144 open_action: None,
145 viewer_preferences: None,
146 semantic_entities: Vec::new(),
147 }
148 }
149
150 pub fn add_page(&mut self, page: Page) {
152 if let Some(used_chars) = page.get_used_characters() {
154 self.used_characters.extend(used_chars);
155 }
156 self.pages.push(page);
157 }
158
159 pub fn set_title(&mut self, title: impl Into<String>) {
161 self.metadata.title = Some(title.into());
162 }
163
164 pub fn set_author(&mut self, author: impl Into<String>) {
166 self.metadata.author = Some(author.into());
167 }
168
169 pub fn set_form_manager(&mut self, form_manager: FormManager) {
171 self.form_manager = Some(form_manager);
172 }
173
174 pub fn set_subject(&mut self, subject: impl Into<String>) {
176 self.metadata.subject = Some(subject.into());
177 }
178
179 pub fn set_keywords(&mut self, keywords: impl Into<String>) {
181 self.metadata.keywords = Some(keywords.into());
182 }
183
184 pub fn set_encryption(&mut self, encryption: DocumentEncryption) {
186 self.encryption = Some(encryption);
187 }
188
189 pub fn encrypt_with_passwords(
191 &mut self,
192 user_password: impl Into<String>,
193 owner_password: impl Into<String>,
194 ) {
195 self.encryption = Some(DocumentEncryption::with_passwords(
196 user_password,
197 owner_password,
198 ));
199 }
200
201 pub fn is_encrypted(&self) -> bool {
203 self.encryption.is_some()
204 }
205
206 pub fn set_open_action(&mut self, action: crate::actions::Action) {
208 self.open_action = Some(action);
209 }
210
211 pub fn open_action(&self) -> Option<&crate::actions::Action> {
213 self.open_action.as_ref()
214 }
215
216 pub fn set_viewer_preferences(
218 &mut self,
219 preferences: crate::viewer_preferences::ViewerPreferences,
220 ) {
221 self.viewer_preferences = Some(preferences);
222 }
223
224 pub fn viewer_preferences(&self) -> Option<&crate::viewer_preferences::ViewerPreferences> {
226 self.viewer_preferences.as_ref()
227 }
228
229 pub fn set_outline(&mut self, outline: OutlineTree) {
231 self.outline = Some(outline);
232 }
233
234 pub fn outline(&self) -> Option<&OutlineTree> {
236 self.outline.as_ref()
237 }
238
239 pub fn outline_mut(&mut self) -> Option<&mut OutlineTree> {
241 self.outline.as_mut()
242 }
243
244 pub fn set_named_destinations(&mut self, destinations: NamedDestinations) {
246 self.named_destinations = Some(destinations);
247 }
248
249 pub fn named_destinations(&self) -> Option<&NamedDestinations> {
251 self.named_destinations.as_ref()
252 }
253
254 pub fn named_destinations_mut(&mut self) -> Option<&mut NamedDestinations> {
256 self.named_destinations.as_mut()
257 }
258
259 pub fn set_page_labels(&mut self, labels: PageLabelTree) {
261 self.page_labels = Some(labels);
262 }
263
264 pub fn page_labels(&self) -> Option<&PageLabelTree> {
266 self.page_labels.as_ref()
267 }
268
269 pub fn page_labels_mut(&mut self) -> Option<&mut PageLabelTree> {
271 self.page_labels.as_mut()
272 }
273
274 pub fn get_page_label(&self, page_index: u32) -> String {
276 self.page_labels
277 .as_ref()
278 .and_then(|labels| labels.get_label(page_index))
279 .unwrap_or_else(|| (page_index + 1).to_string())
280 }
281
282 pub fn get_all_page_labels(&self) -> Vec<String> {
284 let page_count = self.pages.len() as u32;
285 if let Some(labels) = &self.page_labels {
286 labels.get_all_labels(page_count)
287 } else {
288 (1..=page_count).map(|i| i.to_string()).collect()
289 }
290 }
291
292 pub fn set_creator(&mut self, creator: impl Into<String>) {
294 self.metadata.creator = Some(creator.into());
295 }
296
297 pub fn set_producer(&mut self, producer: impl Into<String>) {
299 self.metadata.producer = Some(producer.into());
300 }
301
302 pub fn set_creation_date(&mut self, date: DateTime<Utc>) {
304 self.metadata.creation_date = Some(date);
305 }
306
307 pub fn set_creation_date_local(&mut self, date: DateTime<Local>) {
309 self.metadata.creation_date = Some(date.with_timezone(&Utc));
310 }
311
312 pub fn set_modification_date(&mut self, date: DateTime<Utc>) {
314 self.metadata.modification_date = Some(date);
315 }
316
317 pub fn set_modification_date_local(&mut self, date: DateTime<Local>) {
319 self.metadata.modification_date = Some(date.with_timezone(&Utc));
320 }
321
322 pub fn update_modification_date(&mut self) {
324 self.metadata.modification_date = Some(Utc::now());
325 }
326
327 pub fn set_default_font_encoding(&mut self, encoding: Option<FontEncoding>) {
342 self.default_font_encoding = encoding;
343 }
344
345 pub fn default_font_encoding(&self) -> Option<FontEncoding> {
347 self.default_font_encoding
348 }
349
350 #[allow(dead_code)]
355 pub(crate) fn get_fonts_with_encodings(&self) -> Vec<FontWithEncoding> {
356 let mut fonts_used = HashSet::new();
357
358 for page in &self.pages {
360 for font in page.get_used_fonts() {
362 let font_with_encoding = match self.default_font_encoding {
363 Some(default_encoding) => FontWithEncoding::new(font, Some(default_encoding)),
364 None => FontWithEncoding::without_encoding(font),
365 };
366 fonts_used.insert(font_with_encoding);
367 }
368 }
369
370 fonts_used.into_iter().collect()
371 }
372
373 pub fn add_font(
384 &mut self,
385 name: impl Into<String>,
386 path: impl AsRef<std::path::Path>,
387 ) -> Result<()> {
388 let name = name.into();
389 let font = CustomFont::from_file(&name, path)?;
390 self.custom_fonts.add_font(name, font)?;
391 Ok(())
392 }
393
394 pub fn add_font_from_bytes(&mut self, name: impl Into<String>, data: Vec<u8>) -> Result<()> {
406 let name = name.into();
407 let font = CustomFont::from_bytes(&name, data)?;
408
409 self.custom_fonts.add_font(name, font)?;
413 Ok(())
414 }
415
416 #[allow(dead_code)]
418 pub(crate) fn get_custom_font(&self, name: &str) -> Option<Arc<CustomFont>> {
419 self.custom_fonts.get_font(name)
420 }
421
422 pub fn has_custom_font(&self, name: &str) -> bool {
424 self.custom_fonts.has_font(name)
425 }
426
427 pub fn custom_font_names(&self) -> Vec<String> {
429 self.custom_fonts.font_names()
430 }
431
432 pub fn page_count(&self) -> usize {
434 self.pages.len()
435 }
436
437 pub fn acro_form(&self) -> Option<&AcroForm> {
439 self.acro_form.as_ref()
440 }
441
442 pub fn acro_form_mut(&mut self) -> Option<&mut AcroForm> {
444 self.acro_form.as_mut()
445 }
446
447 pub fn enable_forms(&mut self) -> &mut FormManager {
450 if self.form_manager.is_none() {
451 self.form_manager = Some(FormManager::new());
452 }
453 if self.acro_form.is_none() {
454 self.acro_form = Some(AcroForm::new());
455 }
456 self.form_manager
458 .as_mut()
459 .expect("FormManager should exist after initialization")
460 }
461
462 pub fn disable_forms(&mut self) {
464 self.acro_form = None;
465 self.form_manager = None;
466 }
467
468 pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
474 self.update_modification_date();
476
477 let config = crate::writer::WriterConfig {
479 use_xref_streams: self.use_xref_streams,
480 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
482 compress_streams: self.compress,
483 };
484
485 use std::io::BufWriter;
486 let file = std::fs::File::create(path)?;
487 let writer = BufWriter::with_capacity(512 * 1024, file);
490 let mut pdf_writer = PdfWriter::with_config(writer, config);
491
492 pdf_writer.write_document(self)?;
493 Ok(())
494 }
495
496 pub fn save_with_config(
502 &mut self,
503 path: impl AsRef<std::path::Path>,
504 config: crate::writer::WriterConfig,
505 ) -> Result<()> {
506 use std::io::BufWriter;
507
508 self.update_modification_date();
510
511 let file = std::fs::File::create(path)?;
514 let writer = BufWriter::with_capacity(512 * 1024, file);
516 let mut pdf_writer = PdfWriter::with_config(writer, config);
517 pdf_writer.write_document(self)?;
518 Ok(())
519 }
520
521 pub fn save_with_custom_values(
535 &mut self,
536 path: impl AsRef<std::path::Path>,
537 custom_values: &std::collections::HashMap<String, String>,
538 ) -> Result<()> {
539 let total_pages = self.pages.len();
541 for (index, page) in self.pages.iter_mut().enumerate() {
542 let page_content = page.generate_content_with_page_info(
544 Some(index + 1),
545 Some(total_pages),
546 Some(custom_values),
547 )?;
548 page.set_content(page_content);
550 }
551
552 self.save(path)
554 }
555
556 pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
562 self.update_modification_date();
564
565 let mut writer = PdfWriter::new_with_writer(buffer);
566 writer.write_document(self)?;
567 Ok(())
568 }
569
570 #[allow(dead_code)]
571 pub(crate) fn allocate_object_id(&mut self) -> ObjectId {
572 let id = ObjectId::new(self.next_object_id, 0);
573 self.next_object_id += 1;
574 id
575 }
576
577 #[allow(dead_code)]
578 pub(crate) fn add_object(&mut self, obj: Object) -> ObjectId {
579 let id = self.allocate_object_id();
580 self.objects.insert(id, obj);
581 id
582 }
583
584 pub fn set_compress(&mut self, compress: bool) {
611 self.compress = compress;
612 }
613
614 pub fn enable_xref_streams(&mut self, enable: bool) -> &mut Self {
632 self.use_xref_streams = enable;
633 self
634 }
635
636 pub fn get_compress(&self) -> bool {
642 self.compress
643 }
644
645 pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
673 self.update_modification_date();
675
676 let mut buffer = Vec::new();
678
679 let config = crate::writer::WriterConfig {
681 use_xref_streams: self.use_xref_streams,
682 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
684 compress_streams: self.compress,
685 };
686
687 let mut writer = PdfWriter::with_config(&mut buffer, config);
689 writer.write_document(self)?;
690
691 Ok(buffer)
692 }
693
694 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 add_xmp_metadata(&mut self, _xmp_data: &str) -> Result<ObjectId> {
973 tracing::info!("XMP metadata embedding requested but not available in community edition");
976 Ok(ObjectId::new(9999, 0)) }
978
979 pub fn get_xmp_metadata(&self) -> Result<Option<String>> {
981 tracing::info!("XMP metadata extraction requested but not available in community edition");
984 Ok(None)
985 }
986
987 pub fn extract_text(&self) -> Result<String> {
989 let mut text = String::new();
992 for (i, _page) in self.pages.iter().enumerate() {
993 text.push_str(&format!("Text from page {} (placeholder)\n", i + 1));
994 }
995 Ok(text)
996 }
997
998 pub fn extract_page_text(&self, page_index: usize) -> Result<String> {
1000 if page_index < self.pages.len() {
1001 Ok(format!("Text from page {} (placeholder)", page_index + 1))
1002 } else {
1003 Err(crate::error::PdfError::InvalidReference(format!(
1004 "Page index {} out of bounds",
1005 page_index
1006 )))
1007 }
1008 }
1009}
1010
1011impl Default for Document {
1012 fn default() -> Self {
1013 Self::new()
1014 }
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019 use super::*;
1020
1021 #[test]
1022 fn test_document_new() {
1023 let doc = Document::new();
1024 assert!(doc.pages.is_empty());
1025 assert!(doc.objects.is_empty());
1026 assert_eq!(doc.next_object_id, 1);
1027 assert!(doc.metadata.title.is_none());
1028 assert!(doc.metadata.author.is_none());
1029 assert!(doc.metadata.subject.is_none());
1030 assert!(doc.metadata.keywords.is_none());
1031 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1032 assert!(doc
1033 .metadata
1034 .producer
1035 .as_ref()
1036 .unwrap()
1037 .starts_with("oxidize_pdf"));
1038 }
1039
1040 #[test]
1041 fn test_document_default() {
1042 let doc = Document::default();
1043 assert!(doc.pages.is_empty());
1044 assert_eq!(doc.next_object_id, 1);
1045 }
1046
1047 #[test]
1048 fn test_add_page() {
1049 let mut doc = Document::new();
1050 let page1 = Page::a4();
1051 let page2 = Page::letter();
1052
1053 doc.add_page(page1);
1054 assert_eq!(doc.pages.len(), 1);
1055
1056 doc.add_page(page2);
1057 assert_eq!(doc.pages.len(), 2);
1058 }
1059
1060 #[test]
1061 fn test_set_title() {
1062 let mut doc = Document::new();
1063 assert!(doc.metadata.title.is_none());
1064
1065 doc.set_title("Test Document");
1066 assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
1067
1068 doc.set_title(String::from("Another Title"));
1069 assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
1070 }
1071
1072 #[test]
1073 fn test_set_author() {
1074 let mut doc = Document::new();
1075 assert!(doc.metadata.author.is_none());
1076
1077 doc.set_author("John Doe");
1078 assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
1079 }
1080
1081 #[test]
1082 fn test_set_subject() {
1083 let mut doc = Document::new();
1084 assert!(doc.metadata.subject.is_none());
1085
1086 doc.set_subject("Test Subject");
1087 assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
1088 }
1089
1090 #[test]
1091 fn test_set_keywords() {
1092 let mut doc = Document::new();
1093 assert!(doc.metadata.keywords.is_none());
1094
1095 doc.set_keywords("test, pdf, rust");
1096 assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
1097 }
1098
1099 #[test]
1100 fn test_metadata_default() {
1101 let metadata = DocumentMetadata::default();
1102 assert!(metadata.title.is_none());
1103 assert!(metadata.author.is_none());
1104 assert!(metadata.subject.is_none());
1105 assert!(metadata.keywords.is_none());
1106 assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
1107 assert!(metadata
1108 .producer
1109 .as_ref()
1110 .unwrap()
1111 .starts_with("oxidize_pdf"));
1112 }
1113
1114 #[test]
1115 fn test_allocate_object_id() {
1116 let mut doc = Document::new();
1117
1118 let id1 = doc.allocate_object_id();
1119 assert_eq!(id1.number(), 1);
1120 assert_eq!(id1.generation(), 0);
1121 assert_eq!(doc.next_object_id, 2);
1122
1123 let id2 = doc.allocate_object_id();
1124 assert_eq!(id2.number(), 2);
1125 assert_eq!(id2.generation(), 0);
1126 assert_eq!(doc.next_object_id, 3);
1127 }
1128
1129 #[test]
1130 fn test_add_object() {
1131 let mut doc = Document::new();
1132 assert!(doc.objects.is_empty());
1133
1134 let obj = Object::Boolean(true);
1135 let id = doc.add_object(obj.clone());
1136
1137 assert_eq!(id.number(), 1);
1138 assert_eq!(doc.objects.len(), 1);
1139 assert!(doc.objects.contains_key(&id));
1140 }
1141
1142 #[test]
1143 fn test_write_to_buffer() {
1144 let mut doc = Document::new();
1145 doc.set_title("Buffer Test");
1146 doc.add_page(Page::a4());
1147
1148 let mut buffer = Vec::new();
1149 let result = doc.write(&mut buffer);
1150
1151 assert!(result.is_ok());
1152 assert!(!buffer.is_empty());
1153 assert!(buffer.starts_with(b"%PDF-1.7"));
1154 }
1155
1156 #[test]
1157 fn test_document_with_multiple_pages() {
1158 let mut doc = Document::new();
1159 doc.set_title("Multi-page Document");
1160 doc.set_author("Test Author");
1161 doc.set_subject("Testing multiple pages");
1162 doc.set_keywords("test, multiple, pages");
1163
1164 for _ in 0..5 {
1165 doc.add_page(Page::a4());
1166 }
1167
1168 assert_eq!(doc.pages.len(), 5);
1169 assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
1170 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1171 }
1172
1173 #[test]
1174 fn test_empty_document_write() {
1175 let mut doc = Document::new();
1176 let mut buffer = Vec::new();
1177
1178 let result = doc.write(&mut buffer);
1180 assert!(result.is_ok());
1181 assert!(!buffer.is_empty());
1182 assert!(buffer.starts_with(b"%PDF-1.7"));
1183 }
1184
1185 mod integration_tests {
1187 use super::*;
1188 use crate::graphics::Color;
1189 use crate::text::Font;
1190 use std::fs;
1191 use tempfile::TempDir;
1192
1193 #[test]
1194 fn test_document_writer_roundtrip() {
1195 let temp_dir = TempDir::new().unwrap();
1196 let file_path = temp_dir.path().join("test.pdf");
1197
1198 let mut doc = Document::new();
1200 doc.set_title("Integration Test");
1201 doc.set_author("Test Author");
1202 doc.set_subject("Writer Integration");
1203 doc.set_keywords("test, writer, integration");
1204
1205 let mut page = Page::a4();
1206 page.text()
1207 .set_font(Font::Helvetica, 12.0)
1208 .at(100.0, 700.0)
1209 .write("Integration Test Content")
1210 .unwrap();
1211
1212 doc.add_page(page);
1213
1214 let result = doc.save(&file_path);
1216 assert!(result.is_ok());
1217
1218 assert!(file_path.exists());
1220 let metadata = fs::metadata(&file_path).unwrap();
1221 assert!(metadata.len() > 0);
1222
1223 let content = fs::read(&file_path).unwrap();
1225 assert!(content.starts_with(b"%PDF-1.7"));
1226 assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
1228 }
1229
1230 #[test]
1231 fn test_document_with_complex_content() {
1232 let temp_dir = TempDir::new().unwrap();
1233 let file_path = temp_dir.path().join("complex.pdf");
1234
1235 let mut doc = Document::new();
1236 doc.set_title("Complex Content Test");
1237
1238 let mut page = Page::a4();
1240
1241 page.text()
1243 .set_font(Font::Helvetica, 14.0)
1244 .at(50.0, 750.0)
1245 .write("Complex Content Test")
1246 .unwrap();
1247
1248 page.graphics()
1250 .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
1251 .rectangle(50.0, 500.0, 200.0, 100.0)
1252 .fill();
1253
1254 page.graphics()
1255 .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
1256 .set_line_width(2.0)
1257 .move_to(50.0, 400.0)
1258 .line_to(250.0, 400.0)
1259 .stroke();
1260
1261 doc.add_page(page);
1262
1263 let result = doc.save(&file_path);
1265 assert!(result.is_ok());
1266 assert!(file_path.exists());
1267 }
1268
1269 #[test]
1270 fn test_document_multiple_pages_integration() {
1271 let temp_dir = TempDir::new().unwrap();
1272 let file_path = temp_dir.path().join("multipage.pdf");
1273
1274 let mut doc = Document::new();
1275 doc.set_title("Multi-page Integration Test");
1276
1277 for i in 1..=5 {
1279 let mut page = Page::a4();
1280
1281 page.text()
1282 .set_font(Font::Helvetica, 16.0)
1283 .at(50.0, 750.0)
1284 .write(&format!("Page {i}"))
1285 .unwrap();
1286
1287 page.text()
1288 .set_font(Font::Helvetica, 12.0)
1289 .at(50.0, 700.0)
1290 .write(&format!("This is the content for page {i}"))
1291 .unwrap();
1292
1293 let color = match i % 3 {
1295 0 => Color::rgb(1.0, 0.0, 0.0),
1296 1 => Color::rgb(0.0, 1.0, 0.0),
1297 _ => Color::rgb(0.0, 0.0, 1.0),
1298 };
1299
1300 page.graphics()
1301 .set_fill_color(color)
1302 .rectangle(50.0, 600.0, 100.0, 50.0)
1303 .fill();
1304
1305 doc.add_page(page);
1306 }
1307
1308 let result = doc.save(&file_path);
1310 assert!(result.is_ok());
1311 assert!(file_path.exists());
1312
1313 let metadata = fs::metadata(&file_path).unwrap();
1315 assert!(metadata.len() > 1000); }
1317
1318 #[test]
1319 fn test_document_metadata_persistence() {
1320 let temp_dir = TempDir::new().unwrap();
1321 let file_path = temp_dir.path().join("metadata.pdf");
1322
1323 let mut doc = Document::new();
1324 doc.set_title("Metadata Persistence Test");
1325 doc.set_author("Test Author");
1326 doc.set_subject("Testing metadata preservation");
1327 doc.set_keywords("metadata, persistence, test");
1328
1329 doc.add_page(Page::a4());
1330
1331 let result = doc.save(&file_path);
1333 assert!(result.is_ok());
1334
1335 let content = fs::read(&file_path).unwrap();
1337 let content_str = String::from_utf8_lossy(&content);
1338
1339 assert!(content_str.contains("Metadata Persistence Test"));
1341 assert!(content_str.contains("Test Author"));
1342 }
1343
1344 #[test]
1345 fn test_document_writer_error_handling() {
1346 let mut doc = Document::new();
1347 doc.add_page(Page::a4());
1348
1349 let result = doc.save("/invalid/path/test.pdf");
1351 assert!(result.is_err());
1352 }
1353
1354 #[test]
1355 fn test_document_object_management() {
1356 let mut doc = Document::new();
1357
1358 let obj1 = Object::Boolean(true);
1360 let obj2 = Object::Integer(42);
1361 let obj3 = Object::Real(std::f64::consts::PI);
1362
1363 let id1 = doc.add_object(obj1.clone());
1364 let id2 = doc.add_object(obj2.clone());
1365 let id3 = doc.add_object(obj3.clone());
1366
1367 assert_eq!(id1.number(), 1);
1368 assert_eq!(id2.number(), 2);
1369 assert_eq!(id3.number(), 3);
1370
1371 assert_eq!(doc.objects.len(), 3);
1372 assert!(doc.objects.contains_key(&id1));
1373 assert!(doc.objects.contains_key(&id2));
1374 assert!(doc.objects.contains_key(&id3));
1375
1376 assert_eq!(doc.objects.get(&id1), Some(&obj1));
1378 assert_eq!(doc.objects.get(&id2), Some(&obj2));
1379 assert_eq!(doc.objects.get(&id3), Some(&obj3));
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 };
1804
1805 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1807
1808 assert!(!pdf_bytes.is_empty());
1810 assert!(pdf_bytes.len() > 100);
1811
1812 let header = String::from_utf8_lossy(&pdf_bytes[0..8]);
1814 assert!(header.contains("PDF-1.5"));
1815 }
1816
1817 #[test]
1818 fn test_to_bytes_vs_save_equivalence() {
1819 use std::fs;
1820 use tempfile::NamedTempFile;
1821
1822 let mut doc1 = Document::new();
1824 doc1.set_title("Equivalence Test");
1825 doc1.add_page(Page::a4());
1826
1827 let mut doc2 = Document::new();
1828 doc2.set_title("Equivalence Test");
1829 doc2.add_page(Page::a4());
1830
1831 let pdf_bytes = doc1.to_bytes().unwrap();
1833
1834 let temp_file = NamedTempFile::new().unwrap();
1836 doc2.save(temp_file.path()).unwrap();
1837 let file_bytes = fs::read(temp_file.path()).unwrap();
1838
1839 assert!(!pdf_bytes.is_empty());
1841 assert!(!file_bytes.is_empty());
1842 assert_eq!(&pdf_bytes[0..5], &file_bytes[0..5]); }
1844
1845 #[test]
1846 fn test_document_set_compress() {
1847 let mut doc = Document::new();
1848 doc.set_title("Compression Test");
1849 doc.add_page(Page::a4());
1850
1851 assert!(doc.get_compress());
1853
1854 doc.set_compress(true);
1856 let compressed_bytes = doc.to_bytes().unwrap();
1857
1858 doc.set_compress(false);
1860 let uncompressed_bytes = doc.to_bytes().unwrap();
1861
1862 assert!(!compressed_bytes.is_empty());
1864 assert!(!uncompressed_bytes.is_empty());
1865
1866 assert_eq!(&compressed_bytes[0..5], b"%PDF-");
1868 assert_eq!(&uncompressed_bytes[0..5], b"%PDF-");
1869 }
1870
1871 #[test]
1872 fn test_document_compression_config_inheritance() {
1873 let mut doc = Document::new();
1874 doc.set_title("Config Inheritance Test");
1875 doc.add_page(Page::a4());
1876
1877 doc.set_compress(false);
1879
1880 let config = crate::writer::WriterConfig {
1882 use_xref_streams: false,
1883 use_object_streams: false,
1884 pdf_version: "1.7".to_string(),
1885 compress_streams: true,
1886 };
1887
1888 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1890
1891 assert!(!pdf_bytes.is_empty());
1893 assert_eq!(&pdf_bytes[0..5], b"%PDF-");
1894 }
1895
1896 #[test]
1897 fn test_document_metadata_all_fields() {
1898 let mut doc = Document::new();
1899
1900 doc.set_title("Test Document");
1902 doc.set_author("John Doe");
1903 doc.set_subject("Testing PDF metadata");
1904 doc.set_keywords("test, pdf, metadata");
1905 doc.set_creator("Test Suite");
1906 doc.set_producer("oxidize_pdf tests");
1907
1908 assert_eq!(doc.metadata.title.as_deref(), Some("Test Document"));
1910 assert_eq!(doc.metadata.author.as_deref(), Some("John Doe"));
1911 assert_eq!(
1912 doc.metadata.subject.as_deref(),
1913 Some("Testing PDF metadata")
1914 );
1915 assert_eq!(
1916 doc.metadata.keywords.as_deref(),
1917 Some("test, pdf, metadata")
1918 );
1919 assert_eq!(doc.metadata.creator.as_deref(), Some("Test Suite"));
1920 assert_eq!(doc.metadata.producer.as_deref(), Some("oxidize_pdf tests"));
1921 assert!(doc.metadata.creation_date.is_some());
1922 assert!(doc.metadata.modification_date.is_some());
1923 }
1924
1925 #[test]
1926 fn test_document_add_pages() {
1927 let mut doc = Document::new();
1928
1929 assert_eq!(doc.page_count(), 0);
1931
1932 let page1 = Page::a4();
1934 let page2 = Page::letter();
1935 let page3 = Page::legal();
1936
1937 doc.add_page(page1);
1938 assert_eq!(doc.page_count(), 1);
1939
1940 doc.add_page(page2);
1941 assert_eq!(doc.page_count(), 2);
1942
1943 doc.add_page(page3);
1944 assert_eq!(doc.page_count(), 3);
1945
1946 let result = doc.to_bytes();
1948 assert!(result.is_ok());
1949 }
1950
1951 #[test]
1952 fn test_document_default_font_encoding() {
1953 let mut doc = Document::new();
1954
1955 assert!(doc.default_font_encoding.is_none());
1957
1958 doc.set_default_font_encoding(Some(FontEncoding::WinAnsiEncoding));
1960 assert_eq!(
1961 doc.default_font_encoding(),
1962 Some(FontEncoding::WinAnsiEncoding)
1963 );
1964
1965 doc.set_default_font_encoding(Some(FontEncoding::MacRomanEncoding));
1967 assert_eq!(
1968 doc.default_font_encoding(),
1969 Some(FontEncoding::MacRomanEncoding)
1970 );
1971 }
1972
1973 #[test]
1974 fn test_document_compression_setting() {
1975 let mut doc = Document::new();
1976
1977 assert!(doc.compress);
1979
1980 doc.set_compress(false);
1982 assert!(!doc.compress);
1983
1984 doc.set_compress(true);
1986 assert!(doc.compress);
1987 }
1988
1989 #[test]
1990 fn test_document_with_empty_pages() {
1991 let mut doc = Document::new();
1992
1993 doc.add_page(Page::a4());
1995
1996 let result = doc.to_bytes();
1998 assert!(result.is_ok());
1999
2000 let pdf_bytes = result.unwrap();
2001 assert!(!pdf_bytes.is_empty());
2002 assert!(pdf_bytes.starts_with(b"%PDF-"));
2003 }
2004
2005 #[test]
2006 fn test_document_with_multiple_page_sizes() {
2007 let mut doc = Document::new();
2008
2009 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);
2017
2018 let result = doc.to_bytes();
2022 assert!(result.is_ok());
2023 }
2024
2025 #[test]
2026 fn test_document_metadata_dates() {
2027 use chrono::Duration;
2028
2029 let doc = Document::new();
2030
2031 assert!(doc.metadata.creation_date.is_some());
2033 assert!(doc.metadata.modification_date.is_some());
2034
2035 if let (Some(created), Some(modified)) =
2036 (doc.metadata.creation_date, doc.metadata.modification_date)
2037 {
2038 let diff = modified - created;
2040 assert!(diff < Duration::seconds(1));
2041 }
2042 }
2043
2044 #[test]
2045 fn test_document_builder_pattern() {
2046 let mut doc = Document::new();
2048 doc.set_title("Fluent");
2049 doc.set_author("Builder");
2050 doc.set_compress(true);
2051
2052 assert_eq!(doc.metadata.title.as_deref(), Some("Fluent"));
2053 assert_eq!(doc.metadata.author.as_deref(), Some("Builder"));
2054 assert!(doc.compress);
2055 }
2056
2057 #[test]
2058 fn test_xref_streams_functionality() {
2059 use crate::{Document, Font, Page};
2060
2061 let mut doc = Document::new();
2063 assert!(!doc.use_xref_streams);
2064
2065 let mut page = Page::a4();
2066 page.text()
2067 .set_font(Font::Helvetica, 12.0)
2068 .at(100.0, 700.0)
2069 .write("Testing XRef Streams")
2070 .unwrap();
2071
2072 doc.add_page(page);
2073
2074 let pdf_without_xref = doc.to_bytes().unwrap();
2076
2077 let pdf_str = String::from_utf8_lossy(&pdf_without_xref);
2079 assert!(pdf_str.contains("xref"), "Traditional xref table not found");
2080 assert!(
2081 !pdf_str.contains("/Type /XRef"),
2082 "XRef stream found when it shouldn't be"
2083 );
2084
2085 doc.enable_xref_streams(true);
2087 assert!(doc.use_xref_streams);
2088
2089 let pdf_with_xref = doc.to_bytes().unwrap();
2091
2092 let pdf_str = String::from_utf8_lossy(&pdf_with_xref);
2094 assert!(
2096 pdf_str.contains("/Type /XRef") || pdf_str.contains("stream"),
2097 "XRef stream not found when enabled"
2098 );
2099
2100 assert!(
2102 pdf_str.contains("PDF-1.5"),
2103 "PDF version not set to 1.5 for xref streams"
2104 );
2105
2106 let mut doc2 = Document::new();
2108 doc2.enable_xref_streams(true);
2109 doc2.set_title("XRef Streams Test");
2110 doc2.set_author("oxidize-pdf");
2111
2112 assert!(doc2.use_xref_streams);
2113 assert_eq!(doc2.metadata.title.as_deref(), Some("XRef Streams Test"));
2114 assert_eq!(doc2.metadata.author.as_deref(), Some("oxidize-pdf"));
2115 }
2116
2117 #[test]
2118 fn test_document_save_to_vec() {
2119 let mut doc = Document::new();
2120 doc.set_title("Test Save");
2121 doc.add_page(Page::a4());
2122
2123 let bytes_result = doc.to_bytes();
2125 assert!(bytes_result.is_ok());
2126
2127 let bytes = bytes_result.unwrap();
2128 assert!(!bytes.is_empty());
2129 assert!(bytes.starts_with(b"%PDF-"));
2130 assert!(bytes.ends_with(b"%%EOF") || bytes.ends_with(b"%%EOF\n"));
2131 }
2132
2133 #[test]
2134 fn test_document_unicode_metadata() {
2135 let mut doc = Document::new();
2136
2137 doc.set_title("日本語のタイトル");
2139 doc.set_author("作者名 😀");
2140 doc.set_subject("Тема документа");
2141 doc.set_keywords("كلمات, מפתח, 关键词");
2142
2143 assert_eq!(doc.metadata.title.as_deref(), Some("日本語のタイトル"));
2144 assert_eq!(doc.metadata.author.as_deref(), Some("作者名 😀"));
2145 assert_eq!(doc.metadata.subject.as_deref(), Some("Тема документа"));
2146 assert_eq!(
2147 doc.metadata.keywords.as_deref(),
2148 Some("كلمات, מפתח, 关键词")
2149 );
2150 }
2151
2152 #[test]
2153 fn test_document_page_iteration() {
2154 let mut doc = Document::new();
2155
2156 for i in 0..5 {
2158 let mut page = Page::a4();
2159 let gc = page.graphics();
2160 gc.begin_text();
2161 let _ = gc.show_text(&format!("Page {}", i + 1));
2162 gc.end_text();
2163 doc.add_page(page);
2164 }
2165
2166 assert_eq!(doc.page_count(), 5);
2168
2169 let result = doc.to_bytes();
2171 assert!(result.is_ok());
2172 }
2173
2174 #[test]
2175 fn test_document_with_graphics_content() {
2176 let mut doc = Document::new();
2177
2178 let mut page = Page::a4();
2179 {
2180 let gc = page.graphics();
2181
2182 gc.save_state();
2184
2185 gc.rectangle(100.0, 100.0, 200.0, 150.0);
2187 gc.stroke();
2188
2189 gc.move_to(300.0, 300.0);
2191 gc.circle(300.0, 300.0, 50.0);
2192 gc.fill();
2193
2194 gc.begin_text();
2196 gc.set_text_position(100.0, 500.0);
2197 let _ = gc.show_text("Graphics Test");
2198 gc.end_text();
2199
2200 gc.restore_state();
2201 }
2202
2203 doc.add_page(page);
2204
2205 let result = doc.to_bytes();
2207 assert!(result.is_ok());
2208 }
2209
2210 #[test]
2211 fn test_document_producer_version() {
2212 let doc = Document::new();
2213
2214 assert!(doc.metadata.producer.is_some());
2216 if let Some(producer) = &doc.metadata.producer {
2217 assert!(producer.contains("oxidize_pdf"));
2218 assert!(producer.contains(env!("CARGO_PKG_VERSION")));
2219 }
2220 }
2221
2222 #[test]
2223 fn test_document_empty_metadata_fields() {
2224 let mut doc = Document::new();
2225
2226 doc.set_title("");
2228 doc.set_author("");
2229 doc.set_subject("");
2230 doc.set_keywords("");
2231
2232 assert_eq!(doc.metadata.title.as_deref(), Some(""));
2234 assert_eq!(doc.metadata.author.as_deref(), Some(""));
2235 assert_eq!(doc.metadata.subject.as_deref(), Some(""));
2236 assert_eq!(doc.metadata.keywords.as_deref(), Some(""));
2237 }
2238
2239 #[test]
2240 fn test_document_very_long_metadata() {
2241 let mut doc = Document::new();
2242
2243 let long_title = "A".repeat(1000);
2245 let long_author = "B".repeat(500);
2246 let long_keywords = vec!["keyword"; 100].join(", ");
2247
2248 doc.set_title(&long_title);
2249 doc.set_author(&long_author);
2250 doc.set_keywords(&long_keywords);
2251
2252 assert_eq!(doc.metadata.title.as_deref(), Some(long_title.as_str()));
2253 assert_eq!(doc.metadata.author.as_deref(), Some(long_author.as_str()));
2254 assert!(doc.metadata.keywords.as_ref().unwrap().len() > 500);
2255 }
2256 }
2257}