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::metrics::{register_custom_font_metrics, FontMetrics as TextMeasurementMetrics};
10use crate::text::FontEncoding;
11use crate::writer::PdfWriter;
12use chrono::{DateTime, Local, Utc};
13use std::collections::{HashMap, HashSet};
14use std::sync::Arc;
15
16mod encryption;
17pub use encryption::{DocumentEncryption, EncryptionStrength};
18
19pub struct Document {
36 pub(crate) pages: Vec<Page>,
37 pub(crate) metadata: DocumentMetadata,
38 pub(crate) encryption: Option<DocumentEncryption>,
39 pub(crate) outline: Option<OutlineTree>,
40 pub(crate) named_destinations: Option<NamedDestinations>,
41 pub(crate) page_labels: Option<PageLabelTree>,
42 pub(crate) default_font_encoding: Option<FontEncoding>,
44 pub(crate) acro_form: Option<AcroForm>,
46 pub(crate) form_manager: Option<FormManager>,
48 pub(crate) compress: bool,
50 pub(crate) use_xref_streams: bool,
52 pub(crate) custom_fonts: FontCache,
54 pub(crate) used_characters_by_font: HashMap<String, HashSet<char>>,
60 pub(crate) open_action: Option<crate::actions::Action>,
62 pub(crate) viewer_preferences: Option<crate::viewer_preferences::ViewerPreferences>,
64 pub(crate) semantic_entities: Vec<SemanticEntity>,
66 pub(crate) struct_tree: Option<StructTree>,
68}
69
70#[derive(Debug, Clone)]
72pub struct DocumentMetadata {
73 pub title: Option<String>,
75 pub author: Option<String>,
77 pub subject: Option<String>,
79 pub keywords: Option<String>,
81 pub creator: Option<String>,
83 pub producer: Option<String>,
85 pub creation_date: Option<DateTime<Utc>>,
87 pub modification_date: Option<DateTime<Utc>>,
89}
90
91impl Default for DocumentMetadata {
92 fn default() -> Self {
93 let now = Utc::now();
94
95 let edition = "MIT";
96
97 Self {
98 title: None,
99 author: None,
100 subject: None,
101 keywords: None,
102 creator: Some("oxidize_pdf".to_string()),
103 producer: Some(format!(
104 "oxidize_pdf v{} ({})",
105 env!("CARGO_PKG_VERSION"),
106 edition
107 )),
108 creation_date: Some(now),
109 modification_date: Some(now),
110 }
111 }
112}
113
114impl Document {
115 pub fn new() -> Self {
117 Self {
118 pages: Vec::new(),
119 metadata: DocumentMetadata::default(),
120 encryption: None,
121 outline: None,
122 named_destinations: None,
123 page_labels: None,
124 default_font_encoding: None,
125 acro_form: None,
126 form_manager: None,
127 compress: true, use_xref_streams: false, custom_fonts: FontCache::new(),
130 used_characters_by_font: HashMap::new(),
131 open_action: None,
132 viewer_preferences: None,
133 semantic_entities: Vec::new(),
134 struct_tree: None,
135 }
136 }
137
138 pub fn add_page(&mut self, page: Page) {
140 for (font_name, chars) in page.get_used_characters_by_font() {
144 self.used_characters_by_font
145 .entry(font_name)
146 .or_default()
147 .extend(chars);
148 }
149 self.pages.push(page);
150 }
151
152 pub fn set_title(&mut self, title: impl Into<String>) {
154 self.metadata.title = Some(title.into());
155 }
156
157 pub fn set_author(&mut self, author: impl Into<String>) {
159 self.metadata.author = Some(author.into());
160 }
161
162 pub fn set_form_manager(&mut self, form_manager: FormManager) {
164 self.form_manager = Some(form_manager);
165 }
166
167 pub fn set_subject(&mut self, subject: impl Into<String>) {
169 self.metadata.subject = Some(subject.into());
170 }
171
172 pub fn set_keywords(&mut self, keywords: impl Into<String>) {
174 self.metadata.keywords = Some(keywords.into());
175 }
176
177 pub fn set_encryption(&mut self, encryption: DocumentEncryption) {
179 self.encryption = Some(encryption);
180 }
181
182 pub fn encrypt_with_passwords(
184 &mut self,
185 user_password: impl Into<String>,
186 owner_password: impl Into<String>,
187 ) {
188 self.encryption = Some(DocumentEncryption::with_passwords(
189 user_password,
190 owner_password,
191 ));
192 }
193
194 pub fn is_encrypted(&self) -> bool {
196 self.encryption.is_some()
197 }
198
199 pub fn set_open_action(&mut self, action: crate::actions::Action) {
201 self.open_action = Some(action);
202 }
203
204 pub fn open_action(&self) -> Option<&crate::actions::Action> {
206 self.open_action.as_ref()
207 }
208
209 pub fn set_viewer_preferences(
211 &mut self,
212 preferences: crate::viewer_preferences::ViewerPreferences,
213 ) {
214 self.viewer_preferences = Some(preferences);
215 }
216
217 pub fn viewer_preferences(&self) -> Option<&crate::viewer_preferences::ViewerPreferences> {
219 self.viewer_preferences.as_ref()
220 }
221
222 pub fn set_struct_tree(&mut self, tree: StructTree) {
248 self.struct_tree = Some(tree);
249 }
250
251 pub fn struct_tree(&self) -> Option<&StructTree> {
253 self.struct_tree.as_ref()
254 }
255
256 pub fn struct_tree_mut(&mut self) -> Option<&mut StructTree> {
258 self.struct_tree.as_mut()
259 }
260
261 pub fn get_or_create_struct_tree(&mut self) -> &mut StructTree {
278 self.struct_tree.get_or_insert_with(StructTree::new)
279 }
280
281 pub fn set_outline(&mut self, outline: OutlineTree) {
283 self.outline = Some(outline);
284 }
285
286 pub fn outline(&self) -> Option<&OutlineTree> {
288 self.outline.as_ref()
289 }
290
291 pub fn outline_mut(&mut self) -> Option<&mut OutlineTree> {
293 self.outline.as_mut()
294 }
295
296 pub fn set_named_destinations(&mut self, destinations: NamedDestinations) {
298 self.named_destinations = Some(destinations);
299 }
300
301 pub fn named_destinations(&self) -> Option<&NamedDestinations> {
303 self.named_destinations.as_ref()
304 }
305
306 pub fn named_destinations_mut(&mut self) -> Option<&mut NamedDestinations> {
308 self.named_destinations.as_mut()
309 }
310
311 pub fn set_page_labels(&mut self, labels: PageLabelTree) {
313 self.page_labels = Some(labels);
314 }
315
316 pub fn page_labels(&self) -> Option<&PageLabelTree> {
318 self.page_labels.as_ref()
319 }
320
321 pub fn page_labels_mut(&mut self) -> Option<&mut PageLabelTree> {
323 self.page_labels.as_mut()
324 }
325
326 pub fn get_page_label(&self, page_index: u32) -> String {
328 self.page_labels
329 .as_ref()
330 .and_then(|labels| labels.get_label(page_index))
331 .unwrap_or_else(|| (page_index + 1).to_string())
332 }
333
334 pub fn get_all_page_labels(&self) -> Vec<String> {
336 let page_count = self.pages.len() as u32;
337 if let Some(labels) = &self.page_labels {
338 labels.get_all_labels(page_count)
339 } else {
340 (1..=page_count).map(|i| i.to_string()).collect()
341 }
342 }
343
344 pub fn set_creator(&mut self, creator: impl Into<String>) {
346 self.metadata.creator = Some(creator.into());
347 }
348
349 pub fn set_producer(&mut self, producer: impl Into<String>) {
351 self.metadata.producer = Some(producer.into());
352 }
353
354 pub fn set_creation_date(&mut self, date: DateTime<Utc>) {
356 self.metadata.creation_date = Some(date);
357 }
358
359 pub fn set_creation_date_local(&mut self, date: DateTime<Local>) {
361 self.metadata.creation_date = Some(date.with_timezone(&Utc));
362 }
363
364 pub fn set_modification_date(&mut self, date: DateTime<Utc>) {
366 self.metadata.modification_date = Some(date);
367 }
368
369 pub fn set_modification_date_local(&mut self, date: DateTime<Local>) {
371 self.metadata.modification_date = Some(date.with_timezone(&Utc));
372 }
373
374 pub fn update_modification_date(&mut self) {
376 self.metadata.modification_date = Some(Utc::now());
377 }
378
379 pub fn set_default_font_encoding(&mut self, encoding: Option<FontEncoding>) {
394 self.default_font_encoding = encoding;
395 }
396
397 pub fn default_font_encoding(&self) -> Option<FontEncoding> {
399 self.default_font_encoding
400 }
401
402 pub fn add_font(
413 &mut self,
414 name: impl Into<String>,
415 path: impl AsRef<std::path::Path>,
416 ) -> Result<()> {
417 let name = name.into();
418 let font = CustomFont::from_file(&name, path)?;
419 self.custom_fonts.add_font(name, font)?;
420 Ok(())
421 }
422
423 pub fn add_font_from_bytes(&mut self, name: impl Into<String>, data: Vec<u8>) -> Result<()> {
435 let name = name.into();
436 let font = CustomFont::from_bytes(&name, data)?;
437
438 let units_per_em = font.metrics.units_per_em as f64;
441 let char_width_map: std::collections::HashMap<char, u16> = font
442 .glyph_mapping
443 .char_widths_iter()
444 .map(|(ch, width_font_units)| {
445 let width_1000 = ((width_font_units as f64 * 1000.0) / units_per_em).round() as u16;
446 (ch, width_1000)
447 })
448 .collect();
449
450 self.custom_fonts.add_font(name.clone(), font)?;
452
453 if !char_width_map.is_empty() {
455 let sum: u32 = char_width_map.values().map(|&w| w as u32).sum();
456 let default_width = (sum / char_width_map.len() as u32) as u16;
457 let text_metrics = TextMeasurementMetrics::from_char_map(char_width_map, default_width);
458 register_custom_font_metrics(name, text_metrics);
459 }
460
461 Ok(())
462 }
463
464 pub(crate) fn get_custom_font(&self, name: &str) -> Option<Arc<CustomFont>> {
466 self.custom_fonts.get_font(name)
467 }
468
469 pub fn has_custom_font(&self, name: &str) -> bool {
471 self.custom_fonts.has_font(name)
472 }
473
474 pub fn custom_font_names(&self) -> Vec<String> {
476 self.custom_fonts.font_names()
477 }
478
479 pub fn page_count(&self) -> usize {
481 self.pages.len()
482 }
483
484 pub fn page(&self, index: usize) -> Option<&Page> {
486 self.pages.get(index)
487 }
488
489 pub fn page_mut(&mut self, index: usize) -> Option<&mut Page> {
491 self.pages.get_mut(index)
492 }
493
494 pub fn acro_form(&self) -> Option<&AcroForm> {
496 self.acro_form.as_ref()
497 }
498
499 pub fn acro_form_mut(&mut self) -> Option<&mut AcroForm> {
501 self.acro_form.as_mut()
502 }
503
504 pub fn enable_forms(&mut self) -> &mut FormManager {
507 if self.acro_form.is_none() {
508 self.acro_form = Some(AcroForm::new());
509 }
510 self.form_manager.get_or_insert_with(FormManager::new)
511 }
512
513 pub fn disable_forms(&mut self) {
515 self.acro_form = None;
516 self.form_manager = None;
517 }
518
519 pub fn fill_field(&mut self, name: &str, value: impl Into<String>) -> Result<()> {
555 use crate::error::PdfError;
556 use crate::forms::FieldType;
557 use crate::objects::Object;
558
559 let value: String = value.into();
560
561 let form_manager = self.form_manager.as_mut().ok_or_else(|| {
562 PdfError::InvalidStructure(
563 "Document has no FormManager; register fields via enable_forms() or \
564 set_form_manager() before calling fill_field"
565 .to_string(),
566 )
567 })?;
568
569 let placeholder_ref = form_manager.field_ref(name);
573
574 let form_field = form_manager
575 .get_field_mut(name)
576 .ok_or_else(|| PdfError::FieldNotFound(name.to_string()))?;
577
578 let field_type = match form_field.field_dict.get("FT") {
583 Some(Object::Name(n)) => match n.as_str() {
584 "Btn" => FieldType::Button,
585 "Ch" => FieldType::Choice,
586 "Sig" => FieldType::Signature,
587 _ => FieldType::Text,
588 },
589 _ => FieldType::Text,
590 };
591
592 form_field
598 .field_dict
599 .set("V", Object::String(value.clone()));
600
601 let typed_da = form_field.default_appearance.clone();
617 let custom_font_arc = match typed_da.as_ref().and_then(|da| match &da.font {
618 crate::text::Font::Custom(name) => Some(name.clone()),
619 _ => None,
620 }) {
621 Some(name) => self.get_custom_font(&name),
622 None => None,
623 };
624
625 let form_manager = self.form_manager.as_mut().ok_or_else(|| {
629 PdfError::InvalidStructure(
630 "FormManager vanished between steps of fill_field — unreachable in single-thread"
631 .to_string(),
632 )
633 })?;
634 let form_field = form_manager
635 .get_field_mut(name)
636 .ok_or_else(|| PdfError::FieldNotFound(name.to_string()))?;
637
638 let mut ap_used_chars_by_font: std::collections::HashMap<
642 String,
643 std::collections::HashSet<char>,
644 > = std::collections::HashMap::new();
645 let custom_font_ref: Option<&crate::fonts::Font> = custom_font_arc.as_deref();
650 for widget in &mut form_field.widgets {
651 let used = widget.generate_appearance_with_font(
652 field_type,
653 Some(&value),
654 typed_da.as_ref(),
655 custom_font_ref,
656 )?;
657 for (font_name, chars) in used {
658 ap_used_chars_by_font
659 .entry(font_name)
660 .or_default()
661 .extend(chars);
662 }
663 }
664 for (font_name, chars) in ap_used_chars_by_font {
667 self.used_characters_by_font
668 .entry(font_name)
669 .or_default()
670 .extend(chars);
671 }
672
673 if let Some(placeholder) = placeholder_ref {
679 let form_field = self
681 .form_manager
682 .as_ref()
683 .and_then(|fm| fm.get_field(name))
684 .ok_or_else(|| PdfError::FieldNotFound(name.to_string()))?;
685
686 const RECT_MATCH_TOLERANCE: f64 = 1e-3;
701
702 let mut needs_need_appearances = false;
707
708 for page in self.pages.iter_mut() {
709 for annot in page.annotations_mut().iter_mut() {
710 if annot.field_parent != Some(placeholder) {
711 continue;
712 }
713 let matching_widget = form_field.widgets.iter().find(|w| {
718 (w.rect.lower_left.x - annot.rect.lower_left.x).abs() < RECT_MATCH_TOLERANCE
719 && (w.rect.lower_left.y - annot.rect.lower_left.y).abs()
720 < RECT_MATCH_TOLERANCE
721 && (w.rect.upper_right.x - annot.rect.upper_right.x).abs()
722 < RECT_MATCH_TOLERANCE
723 && (w.rect.upper_right.y - annot.rect.upper_right.y).abs()
724 < RECT_MATCH_TOLERANCE
725 });
726
727 match matching_widget.and_then(|w| w.appearance_streams.as_ref()) {
728 Some(app_dict) => {
729 annot
730 .properties
731 .set("AP", Object::Dictionary(app_dict.to_dict()));
732 }
733 None => {
734 if annot.properties.get("AP").is_some() {
745 annot.properties.remove("AP");
746 needs_need_appearances = true;
747 } else {
748 needs_need_appearances = true;
752 }
753 }
754 }
755 }
756 }
757
758 if needs_need_appearances {
759 let acro_form = self.acro_form.get_or_insert_with(AcroForm::new);
760 acro_form.need_appearances = true;
761 }
762 }
763
764 Ok(())
765 }
766
767 pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
773 self.update_modification_date();
775
776 let config = crate::writer::WriterConfig {
778 use_xref_streams: self.use_xref_streams,
779 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
781 compress_streams: self.compress,
782 incremental_update: false,
783 };
784
785 use std::io::BufWriter;
786 let file = std::fs::File::create(path)?;
787 let writer = BufWriter::with_capacity(512 * 1024, file);
790 let mut pdf_writer = PdfWriter::with_config(writer, config);
791
792 pdf_writer.write_document(self)?;
793 Ok(())
794 }
795
796 pub fn save_with_config(
802 &mut self,
803 path: impl AsRef<std::path::Path>,
804 config: crate::writer::WriterConfig,
805 ) -> Result<()> {
806 use std::io::BufWriter;
807
808 self.update_modification_date();
810
811 let file = std::fs::File::create(path)?;
814 let writer = BufWriter::with_capacity(512 * 1024, file);
816 let mut pdf_writer = PdfWriter::with_config(writer, config);
817 pdf_writer.write_document(self)?;
818 Ok(())
819 }
820
821 pub fn save_with_custom_values(
835 &mut self,
836 path: impl AsRef<std::path::Path>,
837 custom_values: &std::collections::HashMap<String, String>,
838 ) -> Result<()> {
839 let total_pages = self.pages.len();
841 for (index, page) in self.pages.iter_mut().enumerate() {
842 let page_content = page.generate_content_with_page_info(
844 Some(index + 1),
845 Some(total_pages),
846 Some(custom_values),
847 )?;
848 page.set_content(page_content);
850 }
851
852 self.save(path)
854 }
855
856 pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
862 self.update_modification_date();
864
865 let mut writer = PdfWriter::new_with_writer(buffer);
866 writer.write_document(self)?;
867 Ok(())
868 }
869
870 pub fn set_compress(&mut self, compress: bool) {
897 self.compress = compress;
898 }
899
900 pub fn enable_xref_streams(&mut self, enable: bool) -> &mut Self {
918 self.use_xref_streams = enable;
919 self
920 }
921
922 pub fn get_compress(&self) -> bool {
928 self.compress
929 }
930
931 pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
959 self.update_modification_date();
961
962 let mut buffer = Vec::new();
964
965 let config = crate::writer::WriterConfig {
967 use_xref_streams: self.use_xref_streams,
968 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
970 compress_streams: self.compress,
971 incremental_update: false,
972 };
973
974 let mut writer = PdfWriter::with_config(&mut buffer, config);
976 writer.write_document(self)?;
977
978 Ok(buffer)
979 }
980
981 pub fn to_bytes_with_config(&mut self, config: crate::writer::WriterConfig) -> Result<Vec<u8>> {
1022 self.update_modification_date();
1024
1025 let mut buffer = Vec::new();
1029
1030 let mut writer = PdfWriter::with_config(&mut buffer, config);
1032 writer.write_document(self)?;
1033
1034 Ok(buffer)
1035 }
1036
1037 pub fn mark_entity(
1063 &mut self,
1064 id: impl Into<String>,
1065 entity_type: EntityType,
1066 bounds: BoundingBox,
1067 ) -> String {
1068 let entity_id = id.into();
1069 let entity = SemanticEntity::new(entity_id.clone(), entity_type, bounds);
1070 self.semantic_entities.push(entity);
1071 entity_id
1072 }
1073
1074 pub fn set_entity_content(&mut self, entity_id: &str, content: impl Into<String>) -> bool {
1076 if let Some(entity) = self
1077 .semantic_entities
1078 .iter_mut()
1079 .find(|e| e.id == entity_id)
1080 {
1081 entity.content = content.into();
1082 true
1083 } else {
1084 false
1085 }
1086 }
1087
1088 pub fn add_entity_metadata(
1090 &mut self,
1091 entity_id: &str,
1092 key: impl Into<String>,
1093 value: impl Into<String>,
1094 ) -> bool {
1095 if let Some(entity) = self
1096 .semantic_entities
1097 .iter_mut()
1098 .find(|e| e.id == entity_id)
1099 {
1100 entity.metadata.properties.insert(key.into(), value.into());
1101 true
1102 } else {
1103 false
1104 }
1105 }
1106
1107 pub fn set_entity_confidence(&mut self, entity_id: &str, confidence: f32) -> bool {
1109 if let Some(entity) = self
1110 .semantic_entities
1111 .iter_mut()
1112 .find(|e| e.id == entity_id)
1113 {
1114 entity.metadata.confidence = Some(confidence.clamp(0.0, 1.0));
1115 true
1116 } else {
1117 false
1118 }
1119 }
1120
1121 pub fn relate_entities(
1123 &mut self,
1124 from_id: &str,
1125 to_id: &str,
1126 relation_type: RelationType,
1127 ) -> bool {
1128 let target_exists = self.semantic_entities.iter().any(|e| e.id == to_id);
1130 if !target_exists {
1131 return false;
1132 }
1133
1134 if let Some(entity) = self.semantic_entities.iter_mut().find(|e| e.id == from_id) {
1136 entity.relationships.push(crate::semantic::EntityRelation {
1137 target_id: to_id.to_string(),
1138 relation_type,
1139 });
1140 true
1141 } else {
1142 false
1143 }
1144 }
1145
1146 pub fn get_semantic_entities(&self) -> &[SemanticEntity] {
1148 &self.semantic_entities
1149 }
1150
1151 pub fn get_entities_by_type(&self, entity_type: EntityType) -> Vec<&SemanticEntity> {
1153 self.semantic_entities
1154 .iter()
1155 .filter(|e| e.entity_type == entity_type)
1156 .collect()
1157 }
1158
1159 #[cfg(feature = "semantic")]
1161 pub fn export_semantic_entities_json(&self) -> Result<String> {
1162 serde_json::to_string_pretty(&self.semantic_entities)
1163 .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
1164 }
1165
1166 #[cfg(feature = "semantic")]
1192 pub fn export_semantic_entities_json_ld(&self) -> Result<String> {
1193 use crate::semantic::{Entity, EntityMap};
1194
1195 let mut entity_map = EntityMap::new();
1196
1197 for sem_entity in &self.semantic_entities {
1199 let entity = Entity {
1200 id: sem_entity.id.clone(),
1201 entity_type: sem_entity.entity_type.clone(),
1202 bounds: (
1203 sem_entity.bounds.x as f64,
1204 sem_entity.bounds.y as f64,
1205 sem_entity.bounds.width as f64,
1206 sem_entity.bounds.height as f64,
1207 ),
1208 page: (sem_entity.bounds.page - 1) as usize, metadata: sem_entity.metadata.clone(),
1210 };
1211 entity_map.add_entity(entity);
1212 }
1213
1214 if let Some(title) = &self.metadata.title {
1216 entity_map
1217 .document_metadata
1218 .insert("name".to_string(), title.clone());
1219 }
1220 if let Some(author) = &self.metadata.author {
1221 entity_map
1222 .document_metadata
1223 .insert("author".to_string(), author.clone());
1224 }
1225
1226 entity_map
1227 .to_json_ld()
1228 .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
1229 }
1230
1231 pub fn find_entity(&self, entity_id: &str) -> Option<&SemanticEntity> {
1233 self.semantic_entities.iter().find(|e| e.id == entity_id)
1234 }
1235
1236 pub fn remove_entity(&mut self, entity_id: &str) -> bool {
1238 if let Some(pos) = self
1239 .semantic_entities
1240 .iter()
1241 .position(|e| e.id == entity_id)
1242 {
1243 self.semantic_entities.remove(pos);
1244 for entity in &mut self.semantic_entities {
1246 entity.relationships.retain(|r| r.target_id != entity_id);
1247 }
1248 true
1249 } else {
1250 false
1251 }
1252 }
1253
1254 pub fn semantic_entity_count(&self) -> usize {
1256 self.semantic_entities.len()
1257 }
1258
1259 pub fn create_xmp_metadata(&self) -> crate::metadata::XmpMetadata {
1267 let mut xmp = crate::metadata::XmpMetadata::new();
1268
1269 if let Some(title) = &self.metadata.title {
1271 xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "title", title);
1272 }
1273 if let Some(author) = &self.metadata.author {
1274 xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "creator", author);
1275 }
1276 if let Some(subject) = &self.metadata.subject {
1277 xmp.set_text(
1278 crate::metadata::XmpNamespace::DublinCore,
1279 "description",
1280 subject,
1281 );
1282 }
1283
1284 if let Some(creator) = &self.metadata.creator {
1286 xmp.set_text(
1287 crate::metadata::XmpNamespace::XmpBasic,
1288 "CreatorTool",
1289 creator,
1290 );
1291 }
1292 if let Some(creation_date) = &self.metadata.creation_date {
1293 xmp.set_date(
1294 crate::metadata::XmpNamespace::XmpBasic,
1295 "CreateDate",
1296 creation_date.to_rfc3339(),
1297 );
1298 }
1299 if let Some(mod_date) = &self.metadata.modification_date {
1300 xmp.set_date(
1301 crate::metadata::XmpNamespace::XmpBasic,
1302 "ModifyDate",
1303 mod_date.to_rfc3339(),
1304 );
1305 }
1306
1307 if let Some(producer) = &self.metadata.producer {
1309 xmp.set_text(crate::metadata::XmpNamespace::Pdf, "Producer", producer);
1310 }
1311
1312 xmp
1313 }
1314
1315 pub fn get_xmp_packet(&self) -> String {
1324 self.create_xmp_metadata().to_xmp_packet()
1325 }
1326
1327 pub fn extract_text(&self) -> Result<String> {
1329 let mut text = String::new();
1332 for (i, _page) in self.pages.iter().enumerate() {
1333 text.push_str(&format!("Text from page {} (placeholder)\n", i + 1));
1334 }
1335 Ok(text)
1336 }
1337
1338 pub fn extract_page_text(&self, page_index: usize) -> Result<String> {
1340 if page_index < self.pages.len() {
1341 Ok(format!("Text from page {} (placeholder)", page_index + 1))
1342 } else {
1343 Err(crate::error::PdfError::InvalidReference(format!(
1344 "Page index {} out of bounds",
1345 page_index
1346 )))
1347 }
1348 }
1349}
1350
1351impl Default for Document {
1352 fn default() -> Self {
1353 Self::new()
1354 }
1355}
1356
1357#[cfg(test)]
1358mod tests {
1359 use super::*;
1360
1361 #[test]
1362 fn test_document_new() {
1363 let doc = Document::new();
1364 assert!(doc.pages.is_empty());
1365 assert!(doc.metadata.title.is_none());
1366 assert!(doc.metadata.author.is_none());
1367 assert!(doc.metadata.subject.is_none());
1368 assert!(doc.metadata.keywords.is_none());
1369 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1370 assert!(doc
1371 .metadata
1372 .producer
1373 .as_ref()
1374 .unwrap()
1375 .starts_with("oxidize_pdf"));
1376 }
1377
1378 #[test]
1379 fn test_document_default() {
1380 let doc = Document::default();
1381 assert!(doc.pages.is_empty());
1382 }
1383
1384 #[test]
1385 fn test_add_page() {
1386 let mut doc = Document::new();
1387 let page1 = Page::a4();
1388 let page2 = Page::letter();
1389
1390 doc.add_page(page1);
1391 assert_eq!(doc.pages.len(), 1);
1392
1393 doc.add_page(page2);
1394 assert_eq!(doc.pages.len(), 2);
1395 }
1396
1397 #[test]
1398 fn test_set_title() {
1399 let mut doc = Document::new();
1400 assert!(doc.metadata.title.is_none());
1401
1402 doc.set_title("Test Document");
1403 assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
1404
1405 doc.set_title(String::from("Another Title"));
1406 assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
1407 }
1408
1409 #[test]
1410 fn test_set_author() {
1411 let mut doc = Document::new();
1412 assert!(doc.metadata.author.is_none());
1413
1414 doc.set_author("John Doe");
1415 assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
1416 }
1417
1418 #[test]
1419 fn test_set_subject() {
1420 let mut doc = Document::new();
1421 assert!(doc.metadata.subject.is_none());
1422
1423 doc.set_subject("Test Subject");
1424 assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
1425 }
1426
1427 #[test]
1428 fn test_set_keywords() {
1429 let mut doc = Document::new();
1430 assert!(doc.metadata.keywords.is_none());
1431
1432 doc.set_keywords("test, pdf, rust");
1433 assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
1434 }
1435
1436 #[test]
1437 fn test_metadata_default() {
1438 let metadata = DocumentMetadata::default();
1439 assert!(metadata.title.is_none());
1440 assert!(metadata.author.is_none());
1441 assert!(metadata.subject.is_none());
1442 assert!(metadata.keywords.is_none());
1443 assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
1444 assert!(metadata
1445 .producer
1446 .as_ref()
1447 .unwrap()
1448 .starts_with("oxidize_pdf"));
1449 }
1450
1451 #[test]
1452 fn test_write_to_buffer() {
1453 let mut doc = Document::new();
1454 doc.set_title("Buffer Test");
1455 doc.add_page(Page::a4());
1456
1457 let mut buffer = Vec::new();
1458 let result = doc.write(&mut buffer);
1459
1460 assert!(result.is_ok());
1461 assert!(!buffer.is_empty());
1462 assert!(buffer.starts_with(b"%PDF-1.7"));
1463 }
1464
1465 #[test]
1466 fn test_document_with_multiple_pages() {
1467 let mut doc = Document::new();
1468 doc.set_title("Multi-page Document");
1469 doc.set_author("Test Author");
1470 doc.set_subject("Testing multiple pages");
1471 doc.set_keywords("test, multiple, pages");
1472
1473 for _ in 0..5 {
1474 doc.add_page(Page::a4());
1475 }
1476
1477 assert_eq!(doc.pages.len(), 5);
1478 assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
1479 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1480 }
1481
1482 #[test]
1483 fn test_empty_document_write() {
1484 let mut doc = Document::new();
1485 let mut buffer = Vec::new();
1486
1487 let result = doc.write(&mut buffer);
1489 assert!(result.is_ok());
1490 assert!(!buffer.is_empty());
1491 assert!(buffer.starts_with(b"%PDF-1.7"));
1492 }
1493
1494 mod integration_tests {
1496 use super::*;
1497 use crate::graphics::Color;
1498 use crate::text::Font;
1499 use std::fs;
1500 use tempfile::TempDir;
1501
1502 #[test]
1503 fn test_document_writer_roundtrip() {
1504 let temp_dir = TempDir::new().unwrap();
1505 let file_path = temp_dir.path().join("test.pdf");
1506
1507 let mut doc = Document::new();
1509 doc.set_title("Integration Test");
1510 doc.set_author("Test Author");
1511 doc.set_subject("Writer Integration");
1512 doc.set_keywords("test, writer, integration");
1513
1514 let mut page = Page::a4();
1515 page.text()
1516 .set_font(Font::Helvetica, 12.0)
1517 .at(100.0, 700.0)
1518 .write("Integration Test Content")
1519 .unwrap();
1520
1521 doc.add_page(page);
1522
1523 let result = doc.save(&file_path);
1525 assert!(result.is_ok());
1526
1527 assert!(file_path.exists());
1529 let metadata = fs::metadata(&file_path).unwrap();
1530 assert!(metadata.len() > 0);
1531
1532 let content = fs::read(&file_path).unwrap();
1534 assert!(content.starts_with(b"%PDF-1.7"));
1535 assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
1537 }
1538
1539 #[test]
1540 fn test_document_with_complex_content() {
1541 let temp_dir = TempDir::new().unwrap();
1542 let file_path = temp_dir.path().join("complex.pdf");
1543
1544 let mut doc = Document::new();
1545 doc.set_title("Complex Content Test");
1546
1547 let mut page = Page::a4();
1549
1550 page.text()
1552 .set_font(Font::Helvetica, 14.0)
1553 .at(50.0, 750.0)
1554 .write("Complex Content Test")
1555 .unwrap();
1556
1557 page.graphics()
1559 .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
1560 .rectangle(50.0, 500.0, 200.0, 100.0)
1561 .fill();
1562
1563 page.graphics()
1564 .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
1565 .set_line_width(2.0)
1566 .move_to(50.0, 400.0)
1567 .line_to(250.0, 400.0)
1568 .stroke();
1569
1570 doc.add_page(page);
1571
1572 let result = doc.save(&file_path);
1574 assert!(result.is_ok());
1575 assert!(file_path.exists());
1576 }
1577
1578 #[test]
1579 fn test_document_multiple_pages_integration() {
1580 let temp_dir = TempDir::new().unwrap();
1581 let file_path = temp_dir.path().join("multipage.pdf");
1582
1583 let mut doc = Document::new();
1584 doc.set_title("Multi-page Integration Test");
1585
1586 for i in 1..=5 {
1588 let mut page = Page::a4();
1589
1590 page.text()
1591 .set_font(Font::Helvetica, 16.0)
1592 .at(50.0, 750.0)
1593 .write(&format!("Page {i}"))
1594 .unwrap();
1595
1596 page.text()
1597 .set_font(Font::Helvetica, 12.0)
1598 .at(50.0, 700.0)
1599 .write(&format!("This is the content for page {i}"))
1600 .unwrap();
1601
1602 let color = match i % 3 {
1604 0 => Color::rgb(1.0, 0.0, 0.0),
1605 1 => Color::rgb(0.0, 1.0, 0.0),
1606 _ => Color::rgb(0.0, 0.0, 1.0),
1607 };
1608
1609 page.graphics()
1610 .set_fill_color(color)
1611 .rectangle(50.0, 600.0, 100.0, 50.0)
1612 .fill();
1613
1614 doc.add_page(page);
1615 }
1616
1617 let result = doc.save(&file_path);
1619 assert!(result.is_ok());
1620 assert!(file_path.exists());
1621
1622 let metadata = fs::metadata(&file_path).unwrap();
1624 assert!(metadata.len() > 1000); }
1626
1627 #[test]
1628 fn test_document_metadata_persistence() {
1629 let temp_dir = TempDir::new().unwrap();
1630 let file_path = temp_dir.path().join("metadata.pdf");
1631
1632 let mut doc = Document::new();
1633 doc.set_title("Metadata Persistence Test");
1634 doc.set_author("Test Author");
1635 doc.set_subject("Testing metadata preservation");
1636 doc.set_keywords("metadata, persistence, test");
1637
1638 doc.add_page(Page::a4());
1639
1640 let result = doc.save(&file_path);
1642 assert!(result.is_ok());
1643
1644 let content = fs::read(&file_path).unwrap();
1646 let content_str = String::from_utf8_lossy(&content);
1647
1648 assert!(content_str.contains("Metadata Persistence Test"));
1650 assert!(content_str.contains("Test Author"));
1651 }
1652
1653 #[test]
1654 fn test_document_writer_error_handling() {
1655 let mut doc = Document::new();
1656 doc.add_page(Page::a4());
1657
1658 let result = doc.save("/invalid/path/test.pdf");
1660 assert!(result.is_err());
1661 }
1662
1663 #[test]
1664 fn test_document_page_integration() {
1665 let mut doc = Document::new();
1666
1667 let page1 = Page::a4();
1669 let page2 = Page::letter();
1670 let mut page3 = Page::new(500.0, 400.0);
1671
1672 page3
1674 .text()
1675 .set_font(Font::Helvetica, 10.0)
1676 .at(25.0, 350.0)
1677 .write("Custom size page")
1678 .unwrap();
1679
1680 doc.add_page(page1);
1681 doc.add_page(page2);
1682 doc.add_page(page3);
1683
1684 assert_eq!(doc.pages.len(), 3);
1685
1686 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); }
1694
1695 #[test]
1696 fn test_document_content_generation() {
1697 let temp_dir = TempDir::new().unwrap();
1698 let file_path = temp_dir.path().join("content.pdf");
1699
1700 let mut doc = Document::new();
1701 doc.set_title("Content Generation Test");
1702
1703 let mut page = Page::a4();
1704
1705 for i in 0..10 {
1707 let y_pos = 700.0 - (i as f64 * 30.0);
1708 page.text()
1709 .set_font(Font::Helvetica, 12.0)
1710 .at(50.0, y_pos)
1711 .write(&format!("Generated line {}", i + 1))
1712 .unwrap();
1713 }
1714
1715 doc.add_page(page);
1716
1717 let result = doc.save(&file_path);
1719 assert!(result.is_ok());
1720 assert!(file_path.exists());
1721
1722 let metadata = fs::metadata(&file_path).unwrap();
1724 assert!(metadata.len() > 500); }
1726
1727 #[test]
1728 fn test_document_buffer_vs_file_write() {
1729 let temp_dir = TempDir::new().unwrap();
1730 let file_path = temp_dir.path().join("buffer_vs_file.pdf");
1731
1732 let mut doc = Document::new();
1733 doc.set_title("Buffer vs File Test");
1734 doc.add_page(Page::a4());
1735
1736 let mut buffer = Vec::new();
1738 let buffer_result = doc.write(&mut buffer);
1739 assert!(buffer_result.is_ok());
1740
1741 let file_result = doc.save(&file_path);
1743 assert!(file_result.is_ok());
1744
1745 let file_content = fs::read(&file_path).unwrap();
1747
1748 assert!(buffer.starts_with(b"%PDF-1.7"));
1750 assert!(file_content.starts_with(b"%PDF-1.7"));
1751 assert!(buffer.ends_with(b"%%EOF\n"));
1752 assert!(file_content.ends_with(b"%%EOF\n"));
1753
1754 let buffer_str = String::from_utf8_lossy(&buffer);
1756 let file_str = String::from_utf8_lossy(&file_content);
1757 assert!(buffer_str.contains("Buffer vs File Test"));
1758 assert!(file_str.contains("Buffer vs File Test"));
1759 }
1760
1761 #[test]
1762 fn test_document_large_content_handling() {
1763 let temp_dir = TempDir::new().unwrap();
1764 let file_path = temp_dir.path().join("large_content.pdf");
1765
1766 let mut doc = Document::new();
1767 doc.set_title("Large Content Test");
1768
1769 let mut page = Page::a4();
1770
1771 let large_text =
1773 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(200);
1774 page.text()
1775 .set_font(Font::Helvetica, 10.0)
1776 .at(50.0, 750.0)
1777 .write(&large_text)
1778 .unwrap();
1779
1780 doc.add_page(page);
1781
1782 let result = doc.save(&file_path);
1784 assert!(result.is_ok());
1785 assert!(file_path.exists());
1786
1787 let metadata = fs::metadata(&file_path).unwrap();
1789 assert!(metadata.len() > 500); }
1791
1792 #[test]
1793 fn test_document_incremental_building() {
1794 let temp_dir = TempDir::new().unwrap();
1795 let file_path = temp_dir.path().join("incremental.pdf");
1796
1797 let mut doc = Document::new();
1798
1799 doc.set_title("Incremental Building Test");
1801
1802 let mut page1 = Page::a4();
1804 page1
1805 .text()
1806 .set_font(Font::Helvetica, 12.0)
1807 .at(50.0, 750.0)
1808 .write("First page content")
1809 .unwrap();
1810 doc.add_page(page1);
1811
1812 doc.set_author("Incremental Author");
1814 doc.set_subject("Incremental Subject");
1815
1816 let mut page2 = Page::a4();
1818 page2
1819 .text()
1820 .set_font(Font::Helvetica, 12.0)
1821 .at(50.0, 750.0)
1822 .write("Second page content")
1823 .unwrap();
1824 doc.add_page(page2);
1825
1826 doc.set_keywords("incremental, building, test");
1828
1829 let result = doc.save(&file_path);
1831 assert!(result.is_ok());
1832 assert!(file_path.exists());
1833
1834 assert_eq!(doc.pages.len(), 2);
1836 assert_eq!(
1837 doc.metadata.title,
1838 Some("Incremental Building Test".to_string())
1839 );
1840 assert_eq!(doc.metadata.author, Some("Incremental Author".to_string()));
1841 assert_eq!(
1842 doc.metadata.subject,
1843 Some("Incremental Subject".to_string())
1844 );
1845 assert_eq!(
1846 doc.metadata.keywords,
1847 Some("incremental, building, test".to_string())
1848 );
1849 }
1850
1851 #[test]
1852 fn test_document_concurrent_page_operations() {
1853 let mut doc = Document::new();
1854 doc.set_title("Concurrent Operations Test");
1855
1856 let mut pages = Vec::new();
1858
1859 for i in 0..5 {
1861 let mut page = Page::a4();
1862 page.text()
1863 .set_font(Font::Helvetica, 12.0)
1864 .at(50.0, 750.0)
1865 .write(&format!("Concurrent page {i}"))
1866 .unwrap();
1867 pages.push(page);
1868 }
1869
1870 for page in pages {
1872 doc.add_page(page);
1873 }
1874
1875 assert_eq!(doc.pages.len(), 5);
1876
1877 let temp_dir = TempDir::new().unwrap();
1879 let file_path = temp_dir.path().join("concurrent.pdf");
1880 let result = doc.save(&file_path);
1881 assert!(result.is_ok());
1882 }
1883
1884 #[test]
1885 fn test_document_memory_efficiency() {
1886 let mut doc = Document::new();
1887 doc.set_title("Memory Efficiency Test");
1888
1889 for i in 0..10 {
1891 let mut page = Page::a4();
1892 page.text()
1893 .set_font(Font::Helvetica, 12.0)
1894 .at(50.0, 700.0)
1895 .write(&format!("Memory test page {i}"))
1896 .unwrap();
1897 doc.add_page(page);
1898 }
1899
1900 let mut buffer = Vec::new();
1902 let result = doc.write(&mut buffer);
1903 assert!(result.is_ok());
1904 assert!(!buffer.is_empty());
1905
1906 assert!(buffer.len() < 1_000_000); }
1909
1910 #[test]
1911 fn test_document_creator_producer() {
1912 let mut doc = Document::new();
1913
1914 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1916 assert!(doc
1917 .metadata
1918 .producer
1919 .as_ref()
1920 .unwrap()
1921 .contains("oxidize_pdf"));
1922
1923 doc.set_creator("My Application");
1925 doc.set_producer("My PDF Library v1.0");
1926
1927 assert_eq!(doc.metadata.creator, Some("My Application".to_string()));
1928 assert_eq!(
1929 doc.metadata.producer,
1930 Some("My PDF Library v1.0".to_string())
1931 );
1932 }
1933
1934 #[test]
1935 fn test_document_dates() {
1936 use chrono::{TimeZone, Utc};
1937
1938 let mut doc = Document::new();
1939
1940 assert!(doc.metadata.creation_date.is_some());
1942 assert!(doc.metadata.modification_date.is_some());
1943
1944 let creation_date = Utc.with_ymd_and_hms(2023, 1, 1, 12, 0, 0).unwrap();
1946 let mod_date = Utc.with_ymd_and_hms(2023, 6, 15, 18, 30, 0).unwrap();
1947
1948 doc.set_creation_date(creation_date);
1949 doc.set_modification_date(mod_date);
1950
1951 assert_eq!(doc.metadata.creation_date, Some(creation_date));
1952 assert_eq!(doc.metadata.modification_date, Some(mod_date));
1953 }
1954
1955 #[test]
1956 fn test_document_dates_local() {
1957 use chrono::{Local, TimeZone};
1958
1959 let mut doc = Document::new();
1960
1961 let local_date = Local.with_ymd_and_hms(2023, 12, 25, 10, 30, 0).unwrap();
1963 doc.set_creation_date_local(local_date);
1964
1965 assert!(doc.metadata.creation_date.is_some());
1967 assert!(doc.metadata.creation_date.is_some());
1969 }
1970
1971 #[test]
1972 fn test_update_modification_date() {
1973 let mut doc = Document::new();
1974
1975 let initial_mod_date = doc.metadata.modification_date;
1976 assert!(initial_mod_date.is_some());
1977
1978 std::thread::sleep(std::time::Duration::from_millis(10));
1980
1981 doc.update_modification_date();
1982
1983 let new_mod_date = doc.metadata.modification_date;
1984 assert!(new_mod_date.is_some());
1985 assert!(new_mod_date.unwrap() > initial_mod_date.unwrap());
1986 }
1987
1988 #[test]
1989 fn test_document_save_updates_modification_date() {
1990 let temp_dir = TempDir::new().unwrap();
1991 let file_path = temp_dir.path().join("mod_date_test.pdf");
1992
1993 let mut doc = Document::new();
1994 doc.add_page(Page::a4());
1995
1996 let initial_mod_date = doc.metadata.modification_date;
1997
1998 std::thread::sleep(std::time::Duration::from_millis(10));
2000
2001 doc.save(&file_path).unwrap();
2002
2003 assert!(doc.metadata.modification_date.unwrap() > initial_mod_date.unwrap());
2005 }
2006
2007 #[test]
2008 fn test_document_metadata_complete() {
2009 let mut doc = Document::new();
2010
2011 doc.set_title("Complete Metadata Test");
2013 doc.set_author("Test Author");
2014 doc.set_subject("Testing all metadata fields");
2015 doc.set_keywords("test, metadata, complete");
2016 doc.set_creator("Test Application v1.0");
2017 doc.set_producer("oxidize_pdf Test Suite");
2018
2019 assert_eq!(
2021 doc.metadata.title,
2022 Some("Complete Metadata Test".to_string())
2023 );
2024 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
2025 assert_eq!(
2026 doc.metadata.subject,
2027 Some("Testing all metadata fields".to_string())
2028 );
2029 assert_eq!(
2030 doc.metadata.keywords,
2031 Some("test, metadata, complete".to_string())
2032 );
2033 assert_eq!(
2034 doc.metadata.creator,
2035 Some("Test Application v1.0".to_string())
2036 );
2037 assert_eq!(
2038 doc.metadata.producer,
2039 Some("oxidize_pdf Test Suite".to_string())
2040 );
2041 assert!(doc.metadata.creation_date.is_some());
2042 assert!(doc.metadata.modification_date.is_some());
2043 }
2044
2045 #[test]
2046 fn test_document_to_bytes() {
2047 let mut doc = Document::new();
2048 doc.set_title("Test Document");
2049 doc.set_author("Test Author");
2050
2051 let page = Page::a4();
2052 doc.add_page(page);
2053
2054 let pdf_bytes = doc.to_bytes().unwrap();
2056
2057 assert!(!pdf_bytes.is_empty());
2059 assert!(pdf_bytes.len() > 100); let header = &pdf_bytes[0..5];
2063 assert_eq!(header, b"%PDF-");
2064
2065 let pdf_str = String::from_utf8_lossy(&pdf_bytes);
2067 assert!(pdf_str.contains("Test Document"));
2068 assert!(pdf_str.contains("Test Author"));
2069 }
2070
2071 #[test]
2072 fn test_document_to_bytes_with_config() {
2073 let mut doc = Document::new();
2074 doc.set_title("Test Document XRef");
2075
2076 let page = Page::a4();
2077 doc.add_page(page);
2078
2079 let config = crate::writer::WriterConfig {
2080 use_xref_streams: true,
2081 use_object_streams: false,
2082 pdf_version: "1.5".to_string(),
2083 compress_streams: true,
2084 incremental_update: false,
2085 };
2086
2087 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
2089
2090 assert!(!pdf_bytes.is_empty());
2092 assert!(pdf_bytes.len() > 100);
2093
2094 let header = String::from_utf8_lossy(&pdf_bytes[0..8]);
2096 assert!(header.contains("PDF-1.5"));
2097 }
2098
2099 #[test]
2100 fn test_to_bytes_vs_save_equivalence() {
2101 use std::fs;
2102 use tempfile::NamedTempFile;
2103
2104 let mut doc1 = Document::new();
2106 doc1.set_title("Equivalence Test");
2107 doc1.add_page(Page::a4());
2108
2109 let mut doc2 = Document::new();
2110 doc2.set_title("Equivalence Test");
2111 doc2.add_page(Page::a4());
2112
2113 let pdf_bytes = doc1.to_bytes().unwrap();
2115
2116 let temp_file = NamedTempFile::new().unwrap();
2118 doc2.save(temp_file.path()).unwrap();
2119 let file_bytes = fs::read(temp_file.path()).unwrap();
2120
2121 assert!(!pdf_bytes.is_empty());
2123 assert!(!file_bytes.is_empty());
2124 assert_eq!(&pdf_bytes[0..5], &file_bytes[0..5]); }
2126
2127 #[test]
2128 fn test_document_set_compress() {
2129 let mut doc = Document::new();
2130 doc.set_title("Compression Test");
2131 doc.add_page(Page::a4());
2132
2133 assert!(doc.get_compress());
2135
2136 doc.set_compress(true);
2138 let compressed_bytes = doc.to_bytes().unwrap();
2139
2140 doc.set_compress(false);
2142 let uncompressed_bytes = doc.to_bytes().unwrap();
2143
2144 assert!(!compressed_bytes.is_empty());
2146 assert!(!uncompressed_bytes.is_empty());
2147
2148 assert_eq!(&compressed_bytes[0..5], b"%PDF-");
2150 assert_eq!(&uncompressed_bytes[0..5], b"%PDF-");
2151 }
2152
2153 #[test]
2154 fn test_document_compression_config_inheritance() {
2155 let mut doc = Document::new();
2156 doc.set_title("Config Inheritance Test");
2157 doc.add_page(Page::a4());
2158
2159 doc.set_compress(false);
2161
2162 let config = crate::writer::WriterConfig {
2164 use_xref_streams: false,
2165 use_object_streams: false,
2166 pdf_version: "1.7".to_string(),
2167 compress_streams: true,
2168 incremental_update: false,
2169 };
2170
2171 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
2173
2174 assert!(!pdf_bytes.is_empty());
2176 assert_eq!(&pdf_bytes[0..5], b"%PDF-");
2177 }
2178
2179 #[test]
2180 fn test_document_metadata_all_fields() {
2181 let mut doc = Document::new();
2182
2183 doc.set_title("Test Document");
2185 doc.set_author("John Doe");
2186 doc.set_subject("Testing PDF metadata");
2187 doc.set_keywords("test, pdf, metadata");
2188 doc.set_creator("Test Suite");
2189 doc.set_producer("oxidize_pdf tests");
2190
2191 assert_eq!(doc.metadata.title.as_deref(), Some("Test Document"));
2193 assert_eq!(doc.metadata.author.as_deref(), Some("John Doe"));
2194 assert_eq!(
2195 doc.metadata.subject.as_deref(),
2196 Some("Testing PDF metadata")
2197 );
2198 assert_eq!(
2199 doc.metadata.keywords.as_deref(),
2200 Some("test, pdf, metadata")
2201 );
2202 assert_eq!(doc.metadata.creator.as_deref(), Some("Test Suite"));
2203 assert_eq!(doc.metadata.producer.as_deref(), Some("oxidize_pdf tests"));
2204 assert!(doc.metadata.creation_date.is_some());
2205 assert!(doc.metadata.modification_date.is_some());
2206 }
2207
2208 #[test]
2209 fn test_document_add_pages() {
2210 let mut doc = Document::new();
2211
2212 assert_eq!(doc.page_count(), 0);
2214
2215 let page1 = Page::a4();
2217 let page2 = Page::letter();
2218 let page3 = Page::legal();
2219
2220 doc.add_page(page1);
2221 assert_eq!(doc.page_count(), 1);
2222
2223 doc.add_page(page2);
2224 assert_eq!(doc.page_count(), 2);
2225
2226 doc.add_page(page3);
2227 assert_eq!(doc.page_count(), 3);
2228
2229 let result = doc.to_bytes();
2231 assert!(result.is_ok());
2232 }
2233
2234 #[test]
2235 fn test_document_default_font_encoding() {
2236 let mut doc = Document::new();
2237
2238 assert!(doc.default_font_encoding.is_none());
2240
2241 doc.set_default_font_encoding(Some(FontEncoding::WinAnsiEncoding));
2243 assert_eq!(
2244 doc.default_font_encoding(),
2245 Some(FontEncoding::WinAnsiEncoding)
2246 );
2247
2248 doc.set_default_font_encoding(Some(FontEncoding::MacRomanEncoding));
2250 assert_eq!(
2251 doc.default_font_encoding(),
2252 Some(FontEncoding::MacRomanEncoding)
2253 );
2254 }
2255
2256 #[test]
2257 fn test_document_compression_setting() {
2258 let mut doc = Document::new();
2259
2260 assert!(doc.compress);
2262
2263 doc.set_compress(false);
2265 assert!(!doc.compress);
2266
2267 doc.set_compress(true);
2269 assert!(doc.compress);
2270 }
2271
2272 #[test]
2273 fn test_document_with_empty_pages() {
2274 let mut doc = Document::new();
2275
2276 doc.add_page(Page::a4());
2278
2279 let result = doc.to_bytes();
2281 assert!(result.is_ok());
2282
2283 let pdf_bytes = result.unwrap();
2284 assert!(!pdf_bytes.is_empty());
2285 assert!(pdf_bytes.starts_with(b"%PDF-"));
2286 }
2287
2288 #[test]
2289 fn test_document_with_multiple_page_sizes() {
2290 let mut doc = Document::new();
2291
2292 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);
2300
2301 let result = doc.to_bytes();
2305 assert!(result.is_ok());
2306 }
2307
2308 #[test]
2309 fn test_document_metadata_dates() {
2310 use chrono::Duration;
2311
2312 let doc = Document::new();
2313
2314 assert!(doc.metadata.creation_date.is_some());
2316 assert!(doc.metadata.modification_date.is_some());
2317
2318 if let (Some(created), Some(modified)) =
2319 (doc.metadata.creation_date, doc.metadata.modification_date)
2320 {
2321 let diff = modified - created;
2323 assert!(diff < Duration::seconds(1));
2324 }
2325 }
2326
2327 #[test]
2328 fn test_document_builder_pattern() {
2329 let mut doc = Document::new();
2331 doc.set_title("Fluent");
2332 doc.set_author("Builder");
2333 doc.set_compress(true);
2334
2335 assert_eq!(doc.metadata.title.as_deref(), Some("Fluent"));
2336 assert_eq!(doc.metadata.author.as_deref(), Some("Builder"));
2337 assert!(doc.compress);
2338 }
2339
2340 #[test]
2341 fn test_xref_streams_functionality() {
2342 use crate::{Document, Font, Page};
2343
2344 let mut doc = Document::new();
2346 assert!(!doc.use_xref_streams);
2347
2348 let mut page = Page::a4();
2349 page.text()
2350 .set_font(Font::Helvetica, 12.0)
2351 .at(100.0, 700.0)
2352 .write("Testing XRef Streams")
2353 .unwrap();
2354
2355 doc.add_page(page);
2356
2357 let pdf_without_xref = doc.to_bytes().unwrap();
2359
2360 let pdf_str = String::from_utf8_lossy(&pdf_without_xref);
2362 assert!(pdf_str.contains("xref"), "Traditional xref table not found");
2363 assert!(
2364 !pdf_str.contains("/Type /XRef"),
2365 "XRef stream found when it shouldn't be"
2366 );
2367
2368 doc.enable_xref_streams(true);
2370 assert!(doc.use_xref_streams);
2371
2372 let pdf_with_xref = doc.to_bytes().unwrap();
2374
2375 let pdf_str = String::from_utf8_lossy(&pdf_with_xref);
2377 assert!(
2379 pdf_str.contains("/Type /XRef") || pdf_str.contains("stream"),
2380 "XRef stream not found when enabled"
2381 );
2382
2383 assert!(
2385 pdf_str.contains("PDF-1.5"),
2386 "PDF version not set to 1.5 for xref streams"
2387 );
2388
2389 let mut doc2 = Document::new();
2391 doc2.enable_xref_streams(true);
2392 doc2.set_title("XRef Streams Test");
2393 doc2.set_author("oxidize-pdf");
2394
2395 assert!(doc2.use_xref_streams);
2396 assert_eq!(doc2.metadata.title.as_deref(), Some("XRef Streams Test"));
2397 assert_eq!(doc2.metadata.author.as_deref(), Some("oxidize-pdf"));
2398 }
2399
2400 #[test]
2401 fn test_document_save_to_vec() {
2402 let mut doc = Document::new();
2403 doc.set_title("Test Save");
2404 doc.add_page(Page::a4());
2405
2406 let bytes_result = doc.to_bytes();
2408 assert!(bytes_result.is_ok());
2409
2410 let bytes = bytes_result.unwrap();
2411 assert!(!bytes.is_empty());
2412 assert!(bytes.starts_with(b"%PDF-"));
2413 assert!(bytes.ends_with(b"%%EOF") || bytes.ends_with(b"%%EOF\n"));
2414 }
2415
2416 #[test]
2417 fn test_document_unicode_metadata() {
2418 let mut doc = Document::new();
2419
2420 doc.set_title("日本語のタイトル");
2422 doc.set_author("作者名 😀");
2423 doc.set_subject("Тема документа");
2424 doc.set_keywords("كلمات, מפתח, 关键词");
2425
2426 assert_eq!(doc.metadata.title.as_deref(), Some("日本語のタイトル"));
2427 assert_eq!(doc.metadata.author.as_deref(), Some("作者名 😀"));
2428 assert_eq!(doc.metadata.subject.as_deref(), Some("Тема документа"));
2429 assert_eq!(
2430 doc.metadata.keywords.as_deref(),
2431 Some("كلمات, מפתח, 关键词")
2432 );
2433 }
2434
2435 #[test]
2436 fn test_document_page_iteration() {
2437 let mut doc = Document::new();
2438
2439 for i in 0..5 {
2441 let mut page = Page::a4();
2442 let gc = page.graphics();
2443 gc.begin_text();
2444 let _ = gc.show_text(&format!("Page {}", i + 1));
2445 gc.end_text();
2446 doc.add_page(page);
2447 }
2448
2449 assert_eq!(doc.page_count(), 5);
2451
2452 let result = doc.to_bytes();
2454 assert!(result.is_ok());
2455 }
2456
2457 #[test]
2458 fn test_document_with_graphics_content() {
2459 let mut doc = Document::new();
2460
2461 let mut page = Page::a4();
2462 {
2463 let gc = page.graphics();
2464
2465 gc.save_state();
2467
2468 gc.rectangle(100.0, 100.0, 200.0, 150.0);
2470 gc.stroke();
2471
2472 gc.move_to(300.0, 300.0);
2474 gc.circle(300.0, 300.0, 50.0);
2475 gc.fill();
2476
2477 gc.begin_text();
2479 gc.set_text_position(100.0, 500.0);
2480 let _ = gc.show_text("Graphics Test");
2481 gc.end_text();
2482
2483 gc.restore_state();
2484 }
2485
2486 doc.add_page(page);
2487
2488 let result = doc.to_bytes();
2490 assert!(result.is_ok());
2491 }
2492
2493 #[test]
2494 fn test_document_producer_version() {
2495 let doc = Document::new();
2496
2497 assert!(doc.metadata.producer.is_some());
2499 if let Some(producer) = &doc.metadata.producer {
2500 assert!(producer.contains("oxidize_pdf"));
2501 assert!(producer.contains(env!("CARGO_PKG_VERSION")));
2502 }
2503 }
2504
2505 #[test]
2506 fn test_document_empty_metadata_fields() {
2507 let mut doc = Document::new();
2508
2509 doc.set_title("");
2511 doc.set_author("");
2512 doc.set_subject("");
2513 doc.set_keywords("");
2514
2515 assert_eq!(doc.metadata.title.as_deref(), Some(""));
2517 assert_eq!(doc.metadata.author.as_deref(), Some(""));
2518 assert_eq!(doc.metadata.subject.as_deref(), Some(""));
2519 assert_eq!(doc.metadata.keywords.as_deref(), Some(""));
2520 }
2521
2522 #[test]
2523 fn test_document_very_long_metadata() {
2524 let mut doc = Document::new();
2525
2526 let long_title = "A".repeat(1000);
2528 let long_author = "B".repeat(500);
2529 let long_keywords = vec!["keyword"; 100].join(", ");
2530
2531 doc.set_title(&long_title);
2532 doc.set_author(&long_author);
2533 doc.set_keywords(&long_keywords);
2534
2535 assert_eq!(doc.metadata.title.as_deref(), Some(long_title.as_str()));
2536 assert_eq!(doc.metadata.author.as_deref(), Some(long_author.as_str()));
2537 assert!(doc.metadata.keywords.as_ref().unwrap().len() > 500);
2538 }
2539 }
2540}