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::{FontMetrics as TextMeasurementMetrics, FontMetricsStore};
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) font_metrics: FontMetricsStore,
56 pub(crate) used_characters_by_font: HashMap<String, HashSet<char>>,
62 pub(crate) open_action: Option<crate::actions::Action>,
64 pub(crate) viewer_preferences: Option<crate::viewer_preferences::ViewerPreferences>,
66 pub(crate) semantic_entities: Vec<SemanticEntity>,
68 pub(crate) struct_tree: Option<StructTree>,
70 pub(crate) cid_keyed_fonts: HashMap<String, (Vec<u8>, crate::fonts::CidMapping)>,
77}
78
79#[derive(Debug, Clone)]
81pub struct DocumentMetadata {
82 pub title: Option<String>,
84 pub author: Option<String>,
86 pub subject: Option<String>,
88 pub keywords: Option<String>,
90 pub creator: Option<String>,
92 pub producer: Option<String>,
94 pub creation_date: Option<DateTime<Utc>>,
96 pub modification_date: Option<DateTime<Utc>>,
98}
99
100impl Default for DocumentMetadata {
101 fn default() -> Self {
102 let now = Utc::now();
103
104 let edition = "MIT";
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 metadata: DocumentMetadata::default(),
129 encryption: None,
130 outline: None,
131 named_destinations: None,
132 page_labels: None,
133 default_font_encoding: None,
134 acro_form: None,
135 form_manager: None,
136 compress: true, use_xref_streams: false, custom_fonts: FontCache::new(),
139 font_metrics: FontMetricsStore::new(),
140 used_characters_by_font: HashMap::new(),
141 open_action: None,
142 viewer_preferences: None,
143 semantic_entities: Vec::new(),
144 struct_tree: None,
145 cid_keyed_fonts: HashMap::new(),
146 }
147 }
148
149 pub fn add_page(&mut self, mut page: Page) {
151 if page.font_metrics_store.is_none() {
165 page.font_metrics_store = Some(self.font_metrics.clone());
166 page.set_text_context_metrics_store(Some(self.font_metrics.clone()));
167 }
168 for (font_name, chars) in page.get_used_characters_by_font() {
172 self.used_characters_by_font
173 .entry(font_name)
174 .or_default()
175 .extend(chars);
176 }
177 self.pages.push(page);
178 }
179
180 pub fn pages(&self) -> &[Page] {
182 &self.pages
183 }
184
185 pub fn font_metrics(&self) -> &FontMetricsStore {
193 &self.font_metrics
194 }
195
196 pub fn new_page_a4(&self) -> Page {
202 Page::a4_with_metrics(self.font_metrics.clone())
203 }
204
205 pub fn new_page_letter(&self) -> Page {
207 Page::letter_with_metrics(self.font_metrics.clone())
208 }
209
210 pub fn new_page(&self, width: f64, height: f64) -> Page {
213 Page::new_with_metrics(width, height, self.font_metrics.clone())
214 }
215
216 pub fn set_title(&mut self, title: impl Into<String>) {
218 self.metadata.title = Some(title.into());
219 }
220
221 pub fn set_author(&mut self, author: impl Into<String>) {
223 self.metadata.author = Some(author.into());
224 }
225
226 pub fn set_form_manager(&mut self, form_manager: FormManager) {
228 self.form_manager = Some(form_manager);
229 }
230
231 pub fn set_subject(&mut self, subject: impl Into<String>) {
233 self.metadata.subject = Some(subject.into());
234 }
235
236 pub fn set_keywords(&mut self, keywords: impl Into<String>) {
238 self.metadata.keywords = Some(keywords.into());
239 }
240
241 pub fn set_encryption(&mut self, encryption: DocumentEncryption) {
243 self.encryption = Some(encryption);
244 }
245
246 pub fn encrypt_with_passwords(
248 &mut self,
249 user_password: impl Into<String>,
250 owner_password: impl Into<String>,
251 ) {
252 self.encryption = Some(DocumentEncryption::with_passwords(
253 user_password,
254 owner_password,
255 ));
256 }
257
258 pub fn is_encrypted(&self) -> bool {
260 self.encryption.is_some()
261 }
262
263 pub fn set_open_action(&mut self, action: crate::actions::Action) {
265 self.open_action = Some(action);
266 }
267
268 pub fn open_action(&self) -> Option<&crate::actions::Action> {
270 self.open_action.as_ref()
271 }
272
273 pub fn set_viewer_preferences(
275 &mut self,
276 preferences: crate::viewer_preferences::ViewerPreferences,
277 ) {
278 self.viewer_preferences = Some(preferences);
279 }
280
281 pub fn viewer_preferences(&self) -> Option<&crate::viewer_preferences::ViewerPreferences> {
283 self.viewer_preferences.as_ref()
284 }
285
286 pub fn set_struct_tree(&mut self, tree: StructTree) {
312 self.struct_tree = Some(tree);
313 }
314
315 pub fn struct_tree(&self) -> Option<&StructTree> {
317 self.struct_tree.as_ref()
318 }
319
320 pub fn struct_tree_mut(&mut self) -> Option<&mut StructTree> {
322 self.struct_tree.as_mut()
323 }
324
325 pub fn get_or_create_struct_tree(&mut self) -> &mut StructTree {
342 self.struct_tree.get_or_insert_with(StructTree::new)
343 }
344
345 pub fn set_outline(&mut self, outline: OutlineTree) {
347 self.outline = Some(outline);
348 }
349
350 pub fn outline(&self) -> Option<&OutlineTree> {
352 self.outline.as_ref()
353 }
354
355 pub fn outline_mut(&mut self) -> Option<&mut OutlineTree> {
357 self.outline.as_mut()
358 }
359
360 pub fn set_named_destinations(&mut self, destinations: NamedDestinations) {
362 self.named_destinations = Some(destinations);
363 }
364
365 pub fn named_destinations(&self) -> Option<&NamedDestinations> {
367 self.named_destinations.as_ref()
368 }
369
370 pub fn named_destinations_mut(&mut self) -> Option<&mut NamedDestinations> {
372 self.named_destinations.as_mut()
373 }
374
375 pub fn set_page_labels(&mut self, labels: PageLabelTree) {
377 self.page_labels = Some(labels);
378 }
379
380 pub fn page_labels(&self) -> Option<&PageLabelTree> {
382 self.page_labels.as_ref()
383 }
384
385 pub fn page_labels_mut(&mut self) -> Option<&mut PageLabelTree> {
387 self.page_labels.as_mut()
388 }
389
390 pub fn get_page_label(&self, page_index: u32) -> String {
392 self.page_labels
393 .as_ref()
394 .and_then(|labels| labels.get_label(page_index))
395 .unwrap_or_else(|| (page_index + 1).to_string())
396 }
397
398 pub fn get_all_page_labels(&self) -> Vec<String> {
400 let page_count = self.pages.len() as u32;
401 if let Some(labels) = &self.page_labels {
402 labels.get_all_labels(page_count)
403 } else {
404 (1..=page_count).map(|i| i.to_string()).collect()
405 }
406 }
407
408 pub fn set_creator(&mut self, creator: impl Into<String>) {
410 self.metadata.creator = Some(creator.into());
411 }
412
413 pub fn set_producer(&mut self, producer: impl Into<String>) {
415 self.metadata.producer = Some(producer.into());
416 }
417
418 pub fn set_creation_date(&mut self, date: DateTime<Utc>) {
420 self.metadata.creation_date = Some(date);
421 }
422
423 pub fn set_creation_date_local(&mut self, date: DateTime<Local>) {
425 self.metadata.creation_date = Some(date.with_timezone(&Utc));
426 }
427
428 pub fn set_modification_date(&mut self, date: DateTime<Utc>) {
430 self.metadata.modification_date = Some(date);
431 }
432
433 pub fn set_modification_date_local(&mut self, date: DateTime<Local>) {
435 self.metadata.modification_date = Some(date.with_timezone(&Utc));
436 }
437
438 pub fn update_modification_date(&mut self) {
440 self.metadata.modification_date = Some(Utc::now());
441 }
442
443 pub fn set_default_font_encoding(&mut self, encoding: Option<FontEncoding>) {
458 self.default_font_encoding = encoding;
459 }
460
461 pub fn default_font_encoding(&self) -> Option<FontEncoding> {
463 self.default_font_encoding
464 }
465
466 pub fn add_font(
477 &mut self,
478 name: impl Into<String>,
479 path: impl AsRef<std::path::Path>,
480 ) -> Result<()> {
481 let name = name.into();
482 let font = CustomFont::from_file(&name, path)?;
483 self.custom_fonts.add_font(name, font)?;
484 Ok(())
485 }
486
487 pub fn embedded_font(&self, name: &str) -> Option<std::sync::Arc<CustomFont>> {
494 self.custom_fonts.get_font(name)
495 }
496
497 pub fn font_missing_glyphs(&self, font_name: &str, text: &str) -> Vec<char> {
505 match self.custom_fonts.get_font(font_name) {
506 Some(font) => font.missing_glyphs(text),
507 None => Vec::new(),
508 }
509 }
510
511 pub fn add_font_from_bytes(&mut self, name: impl Into<String>, data: Vec<u8>) -> Result<()> {
523 let name = name.into();
524 let font = CustomFont::from_bytes(&name, data)?;
525
526 let units_per_em = font.metrics.units_per_em as f64;
529 let char_width_map: std::collections::HashMap<char, u16> = font
530 .glyph_mapping
531 .char_widths_iter()
532 .map(|(ch, width_font_units)| {
533 let width_1000 = ((width_font_units as f64 * 1000.0) / units_per_em).round() as u16;
534 (ch, width_1000)
535 })
536 .collect();
537
538 self.custom_fonts.add_font(name.clone(), font)?;
540
541 if !char_width_map.is_empty() {
543 let sum: u32 = char_width_map.values().map(|&w| w as u32).sum();
544 let default_width = (sum / char_width_map.len() as u32) as u16;
545 let text_metrics = TextMeasurementMetrics::from_char_map(char_width_map, default_width);
546 self.font_metrics.register(name, text_metrics);
547 }
548
549 Ok(())
550 }
551
552 pub fn add_cid_keyed_font(
572 &mut self,
573 name: impl Into<String>,
574 data: Vec<u8>,
575 mapping: crate::fonts::CidMapping,
576 ) -> Result<()> {
577 let name = name.into();
578 let font = CustomFont::from_bytes(&name, data.clone())?;
581 if font.format != crate::fonts::FontFormat::TrueType {
582 return Err(crate::error::PdfError::InvalidStructure(format!(
583 "add_cid_keyed_font: only TrueType (CIDFontType2) fonts are supported \
584 in this iteration; '{name}' is {:?}",
585 font.format
586 )));
587 }
588 self.cid_keyed_fonts.insert(name, (data, mapping));
589 Ok(())
590 }
591
592 pub(crate) fn cid_keyed_fonts(&self) -> &HashMap<String, (Vec<u8>, crate::fonts::CidMapping)> {
594 &self.cid_keyed_fonts
595 }
596
597 pub(crate) fn get_custom_font(&self, name: &str) -> Option<Arc<CustomFont>> {
599 self.custom_fonts.get_font(name)
600 }
601
602 pub fn has_custom_font(&self, name: &str) -> bool {
604 self.custom_fonts.has_font(name)
605 }
606
607 pub fn custom_font_names(&self) -> Vec<String> {
609 self.custom_fonts.font_names()
610 }
611
612 pub fn page_count(&self) -> usize {
614 self.pages.len()
615 }
616
617 pub fn page(&self, index: usize) -> Option<&Page> {
619 self.pages.get(index)
620 }
621
622 pub fn page_mut(&mut self, index: usize) -> Option<&mut Page> {
624 self.pages.get_mut(index)
625 }
626
627 pub fn acro_form(&self) -> Option<&AcroForm> {
629 self.acro_form.as_ref()
630 }
631
632 pub fn acro_form_mut(&mut self) -> Option<&mut AcroForm> {
634 self.acro_form.as_mut()
635 }
636
637 pub fn enable_forms(&mut self) -> &mut FormManager {
640 if self.acro_form.is_none() {
641 self.acro_form = Some(AcroForm::new());
642 }
643 self.form_manager.get_or_insert_with(FormManager::new)
644 }
645
646 pub fn disable_forms(&mut self) {
648 self.acro_form = None;
649 self.form_manager = None;
650 }
651
652 pub fn fill_field(&mut self, name: &str, value: impl Into<String>) -> Result<()> {
712 use crate::error::PdfError;
713 use crate::forms::FieldType;
714 use crate::objects::Object;
715
716 let value: String = value.into();
717
718 let form_manager = self.form_manager.as_mut().ok_or_else(|| {
719 PdfError::InvalidStructure(
720 "Document has no FormManager; register fields via enable_forms() or \
721 set_form_manager() before calling fill_field"
722 .to_string(),
723 )
724 })?;
725
726 let placeholder_ref = form_manager.field_ref(name);
730
731 let form_field = form_manager
732 .get_field_mut(name)
733 .ok_or_else(|| PdfError::FieldNotFound(name.to_string()))?;
734
735 let field_type = match form_field.field_dict.get("FT") {
740 Some(Object::Name(n)) => match n.as_str() {
741 "Btn" => FieldType::Button,
742 "Ch" => FieldType::Choice,
743 "Sig" => FieldType::Signature,
744 _ => FieldType::Text,
745 },
746 _ => FieldType::Text,
747 };
748
749 form_field
755 .field_dict
756 .set("V", Object::String(value.clone()));
757
758 let typed_da = form_field.default_appearance.clone();
774 let custom_font_arc = match typed_da.as_ref().and_then(|da| match &da.font {
775 crate::text::Font::Custom(name) => Some(name.clone()),
776 _ => None,
777 }) {
778 Some(name) => self.get_custom_font(&name),
779 None => None,
780 };
781
782 let form_manager = self.form_manager.as_mut().ok_or_else(|| {
786 PdfError::InvalidStructure(
787 "FormManager vanished between steps of fill_field — unreachable in single-thread"
788 .to_string(),
789 )
790 })?;
791 let form_field = form_manager
792 .get_field_mut(name)
793 .ok_or_else(|| PdfError::FieldNotFound(name.to_string()))?;
794
795 let mut ap_used_chars_by_font: std::collections::HashMap<
799 String,
800 std::collections::HashSet<char>,
801 > = std::collections::HashMap::new();
802 let custom_font_ref: Option<&crate::fonts::Font> = custom_font_arc.as_deref();
807 for widget in &mut form_field.widgets {
808 let used = widget.generate_appearance_with_font(
809 field_type,
810 Some(&value),
811 typed_da.as_ref(),
812 custom_font_ref,
813 )?;
814 for (font_name, chars) in used {
815 ap_used_chars_by_font
816 .entry(font_name)
817 .or_default()
818 .extend(chars);
819 }
820 }
821 for (font_name, chars) in ap_used_chars_by_font {
824 self.used_characters_by_font
825 .entry(font_name)
826 .or_default()
827 .extend(chars);
828 }
829
830 if let Some(placeholder) = placeholder_ref {
836 let form_field = self
838 .form_manager
839 .as_ref()
840 .and_then(|fm| fm.get_field(name))
841 .ok_or_else(|| PdfError::FieldNotFound(name.to_string()))?;
842
843 const RECT_MATCH_TOLERANCE: f64 = 1e-3;
858
859 let mut needs_need_appearances = false;
864
865 for page in self.pages.iter_mut() {
866 for annot in page.annotations_mut().iter_mut() {
867 if annot.field_parent != Some(placeholder) {
868 continue;
869 }
870 let matching_widget = form_field.widgets.iter().find(|w| {
875 (w.rect.lower_left.x - annot.rect.lower_left.x).abs() < RECT_MATCH_TOLERANCE
876 && (w.rect.lower_left.y - annot.rect.lower_left.y).abs()
877 < RECT_MATCH_TOLERANCE
878 && (w.rect.upper_right.x - annot.rect.upper_right.x).abs()
879 < RECT_MATCH_TOLERANCE
880 && (w.rect.upper_right.y - annot.rect.upper_right.y).abs()
881 < RECT_MATCH_TOLERANCE
882 });
883
884 match matching_widget.and_then(|w| w.appearance_streams.as_ref()) {
885 Some(app_dict) => {
886 annot
887 .properties
888 .set("AP", Object::Dictionary(app_dict.to_dict()));
889 }
890 None => {
891 if annot.properties.get("AP").is_some() {
902 annot.properties.remove("AP");
903 needs_need_appearances = true;
904 } else {
905 needs_need_appearances = true;
909 }
910 }
911 }
912 }
913 }
914
915 if needs_need_appearances {
916 let acro_form = self.acro_form.get_or_insert_with(AcroForm::new);
917 acro_form.need_appearances = true;
918 }
919 }
920
921 Ok(())
922 }
923
924 pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
930 self.update_modification_date();
932
933 let config = crate::writer::WriterConfig {
935 use_xref_streams: self.use_xref_streams,
936 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
938 compress_streams: self.compress,
939 incremental_update: false,
940 };
941
942 use std::io::BufWriter;
943 let file = std::fs::File::create(path)?;
944 let writer = BufWriter::with_capacity(512 * 1024, file);
947 let mut pdf_writer = PdfWriter::with_config(writer, config);
948
949 pdf_writer.write_document(self)?;
950 Ok(())
951 }
952
953 pub fn save_with_config(
959 &mut self,
960 path: impl AsRef<std::path::Path>,
961 config: crate::writer::WriterConfig,
962 ) -> Result<()> {
963 use std::io::BufWriter;
964
965 self.update_modification_date();
967
968 let file = std::fs::File::create(path)?;
971 let writer = BufWriter::with_capacity(512 * 1024, file);
973 let mut pdf_writer = PdfWriter::with_config(writer, config);
974 pdf_writer.write_document(self)?;
975 Ok(())
976 }
977
978 pub fn save_with_custom_values(
992 &mut self,
993 path: impl AsRef<std::path::Path>,
994 custom_values: &std::collections::HashMap<String, String>,
995 ) -> Result<()> {
996 let total_pages = self.pages.len();
998 for (index, page) in self.pages.iter_mut().enumerate() {
999 let page_content = page.generate_content_with_page_info(
1001 Some(index + 1),
1002 Some(total_pages),
1003 Some(custom_values),
1004 )?;
1005 page.set_content(page_content);
1007 }
1008
1009 self.save(path)
1011 }
1012
1013 pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
1019 self.update_modification_date();
1021
1022 let mut writer = PdfWriter::new_with_writer(buffer);
1023 writer.write_document(self)?;
1024 Ok(())
1025 }
1026
1027 pub fn set_compress(&mut self, compress: bool) {
1054 self.compress = compress;
1055 }
1056
1057 pub fn enable_xref_streams(&mut self, enable: bool) -> &mut Self {
1075 self.use_xref_streams = enable;
1076 self
1077 }
1078
1079 pub fn get_compress(&self) -> bool {
1085 self.compress
1086 }
1087
1088 pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
1116 self.update_modification_date();
1118
1119 let mut buffer = Vec::new();
1121
1122 let config = crate::writer::WriterConfig {
1124 use_xref_streams: self.use_xref_streams,
1125 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
1127 compress_streams: self.compress,
1128 incremental_update: false,
1129 };
1130
1131 let mut writer = PdfWriter::with_config(&mut buffer, config);
1133 writer.write_document(self)?;
1134
1135 Ok(buffer)
1136 }
1137
1138 pub fn to_bytes_with_config(&mut self, config: crate::writer::WriterConfig) -> Result<Vec<u8>> {
1179 self.update_modification_date();
1181
1182 let mut buffer = Vec::new();
1186
1187 let mut writer = PdfWriter::with_config(&mut buffer, config);
1189 writer.write_document(self)?;
1190
1191 Ok(buffer)
1192 }
1193
1194 pub fn mark_entity(
1220 &mut self,
1221 id: impl Into<String>,
1222 entity_type: EntityType,
1223 bounds: BoundingBox,
1224 ) -> String {
1225 let entity_id = id.into();
1226 let entity = SemanticEntity::new(entity_id.clone(), entity_type, bounds);
1227 self.semantic_entities.push(entity);
1228 entity_id
1229 }
1230
1231 pub fn set_entity_content(&mut self, entity_id: &str, content: impl Into<String>) -> bool {
1233 if let Some(entity) = self
1234 .semantic_entities
1235 .iter_mut()
1236 .find(|e| e.id == entity_id)
1237 {
1238 entity.content = content.into();
1239 true
1240 } else {
1241 false
1242 }
1243 }
1244
1245 pub fn add_entity_metadata(
1247 &mut self,
1248 entity_id: &str,
1249 key: impl Into<String>,
1250 value: impl Into<String>,
1251 ) -> bool {
1252 if let Some(entity) = self
1253 .semantic_entities
1254 .iter_mut()
1255 .find(|e| e.id == entity_id)
1256 {
1257 entity.metadata.properties.insert(key.into(), value.into());
1258 true
1259 } else {
1260 false
1261 }
1262 }
1263
1264 pub fn set_entity_confidence(&mut self, entity_id: &str, confidence: f32) -> bool {
1266 if let Some(entity) = self
1267 .semantic_entities
1268 .iter_mut()
1269 .find(|e| e.id == entity_id)
1270 {
1271 entity.metadata.confidence = Some(confidence.clamp(0.0, 1.0));
1272 true
1273 } else {
1274 false
1275 }
1276 }
1277
1278 pub fn relate_entities(
1280 &mut self,
1281 from_id: &str,
1282 to_id: &str,
1283 relation_type: RelationType,
1284 ) -> bool {
1285 let target_exists = self.semantic_entities.iter().any(|e| e.id == to_id);
1287 if !target_exists {
1288 return false;
1289 }
1290
1291 if let Some(entity) = self.semantic_entities.iter_mut().find(|e| e.id == from_id) {
1293 entity.relationships.push(crate::semantic::EntityRelation {
1294 target_id: to_id.to_string(),
1295 relation_type,
1296 });
1297 true
1298 } else {
1299 false
1300 }
1301 }
1302
1303 pub fn get_semantic_entities(&self) -> &[SemanticEntity] {
1305 &self.semantic_entities
1306 }
1307
1308 pub fn get_entities_by_type(&self, entity_type: EntityType) -> Vec<&SemanticEntity> {
1310 self.semantic_entities
1311 .iter()
1312 .filter(|e| e.entity_type == entity_type)
1313 .collect()
1314 }
1315
1316 #[cfg(feature = "semantic")]
1318 pub fn export_semantic_entities_json(&self) -> Result<String> {
1319 serde_json::to_string_pretty(&self.semantic_entities)
1320 .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
1321 }
1322
1323 #[cfg(feature = "semantic")]
1349 pub fn export_semantic_entities_json_ld(&self) -> Result<String> {
1350 use crate::semantic::{Entity, EntityMap};
1351
1352 let mut entity_map = EntityMap::new();
1353
1354 for sem_entity in &self.semantic_entities {
1356 let entity = Entity {
1357 id: sem_entity.id.clone(),
1358 entity_type: sem_entity.entity_type.clone(),
1359 bounds: (
1360 sem_entity.bounds.x as f64,
1361 sem_entity.bounds.y as f64,
1362 sem_entity.bounds.width as f64,
1363 sem_entity.bounds.height as f64,
1364 ),
1365 page: (sem_entity.bounds.page - 1) as usize, metadata: sem_entity.metadata.clone(),
1367 };
1368 entity_map.add_entity(entity);
1369 }
1370
1371 if let Some(title) = &self.metadata.title {
1373 entity_map
1374 .document_metadata
1375 .insert("name".to_string(), title.clone());
1376 }
1377 if let Some(author) = &self.metadata.author {
1378 entity_map
1379 .document_metadata
1380 .insert("author".to_string(), author.clone());
1381 }
1382
1383 entity_map
1384 .to_json_ld()
1385 .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
1386 }
1387
1388 pub fn find_entity(&self, entity_id: &str) -> Option<&SemanticEntity> {
1390 self.semantic_entities.iter().find(|e| e.id == entity_id)
1391 }
1392
1393 pub fn remove_entity(&mut self, entity_id: &str) -> bool {
1395 if let Some(pos) = self
1396 .semantic_entities
1397 .iter()
1398 .position(|e| e.id == entity_id)
1399 {
1400 self.semantic_entities.remove(pos);
1401 for entity in &mut self.semantic_entities {
1403 entity.relationships.retain(|r| r.target_id != entity_id);
1404 }
1405 true
1406 } else {
1407 false
1408 }
1409 }
1410
1411 pub fn semantic_entity_count(&self) -> usize {
1413 self.semantic_entities.len()
1414 }
1415
1416 pub fn create_xmp_metadata(&self) -> crate::metadata::XmpMetadata {
1424 let mut xmp = crate::metadata::XmpMetadata::new();
1425
1426 if let Some(title) = &self.metadata.title {
1428 xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "title", title);
1429 }
1430 if let Some(author) = &self.metadata.author {
1431 xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "creator", author);
1432 }
1433 if let Some(subject) = &self.metadata.subject {
1434 xmp.set_text(
1435 crate::metadata::XmpNamespace::DublinCore,
1436 "description",
1437 subject,
1438 );
1439 }
1440
1441 if let Some(creator) = &self.metadata.creator {
1443 xmp.set_text(
1444 crate::metadata::XmpNamespace::XmpBasic,
1445 "CreatorTool",
1446 creator,
1447 );
1448 }
1449 if let Some(creation_date) = &self.metadata.creation_date {
1450 xmp.set_date(
1451 crate::metadata::XmpNamespace::XmpBasic,
1452 "CreateDate",
1453 creation_date.to_rfc3339(),
1454 );
1455 }
1456 if let Some(mod_date) = &self.metadata.modification_date {
1457 xmp.set_date(
1458 crate::metadata::XmpNamespace::XmpBasic,
1459 "ModifyDate",
1460 mod_date.to_rfc3339(),
1461 );
1462 }
1463
1464 if let Some(producer) = &self.metadata.producer {
1466 xmp.set_text(crate::metadata::XmpNamespace::Pdf, "Producer", producer);
1467 }
1468
1469 xmp
1470 }
1471
1472 pub fn get_xmp_packet(&self) -> String {
1481 self.create_xmp_metadata().to_xmp_packet()
1482 }
1483
1484 pub fn extract_text(&self) -> Result<String> {
1486 let mut text = String::new();
1489 for (i, _page) in self.pages.iter().enumerate() {
1490 text.push_str(&format!("Text from page {} (placeholder)\n", i + 1));
1491 }
1492 Ok(text)
1493 }
1494
1495 pub fn extract_page_text(&self, page_index: usize) -> Result<String> {
1497 if page_index < self.pages.len() {
1498 Ok(format!("Text from page {} (placeholder)", page_index + 1))
1499 } else {
1500 Err(crate::error::PdfError::InvalidReference(format!(
1501 "Page index {} out of bounds",
1502 page_index
1503 )))
1504 }
1505 }
1506}
1507
1508impl Default for Document {
1509 fn default() -> Self {
1510 Self::new()
1511 }
1512}
1513
1514#[cfg(test)]
1515mod tests {
1516 use super::*;
1517
1518 #[test]
1519 fn test_document_new() {
1520 let doc = Document::new();
1521 assert!(doc.pages.is_empty());
1522 assert!(doc.metadata.title.is_none());
1523 assert!(doc.metadata.author.is_none());
1524 assert!(doc.metadata.subject.is_none());
1525 assert!(doc.metadata.keywords.is_none());
1526 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1527 assert!(doc
1528 .metadata
1529 .producer
1530 .as_ref()
1531 .unwrap()
1532 .starts_with("oxidize_pdf"));
1533 }
1534
1535 #[test]
1536 fn test_document_default() {
1537 let doc = Document::default();
1538 assert!(doc.pages.is_empty());
1539 }
1540
1541 #[test]
1542 fn test_add_page() {
1543 let mut doc = Document::new();
1544 let page1 = Page::a4();
1545 let page2 = Page::letter();
1546
1547 doc.add_page(page1);
1548 assert_eq!(doc.pages.len(), 1);
1549
1550 doc.add_page(page2);
1551 assert_eq!(doc.pages.len(), 2);
1552 }
1553
1554 #[test]
1555 fn test_set_title() {
1556 let mut doc = Document::new();
1557 assert!(doc.metadata.title.is_none());
1558
1559 doc.set_title("Test Document");
1560 assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
1561
1562 doc.set_title(String::from("Another Title"));
1563 assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
1564 }
1565
1566 #[test]
1567 fn test_set_author() {
1568 let mut doc = Document::new();
1569 assert!(doc.metadata.author.is_none());
1570
1571 doc.set_author("John Doe");
1572 assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
1573 }
1574
1575 #[test]
1576 fn test_set_subject() {
1577 let mut doc = Document::new();
1578 assert!(doc.metadata.subject.is_none());
1579
1580 doc.set_subject("Test Subject");
1581 assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
1582 }
1583
1584 #[test]
1585 fn test_set_keywords() {
1586 let mut doc = Document::new();
1587 assert!(doc.metadata.keywords.is_none());
1588
1589 doc.set_keywords("test, pdf, rust");
1590 assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
1591 }
1592
1593 #[test]
1594 fn test_metadata_default() {
1595 let metadata = DocumentMetadata::default();
1596 assert!(metadata.title.is_none());
1597 assert!(metadata.author.is_none());
1598 assert!(metadata.subject.is_none());
1599 assert!(metadata.keywords.is_none());
1600 assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
1601 assert!(metadata
1602 .producer
1603 .as_ref()
1604 .unwrap()
1605 .starts_with("oxidize_pdf"));
1606 }
1607
1608 #[test]
1609 fn test_write_to_buffer() {
1610 let mut doc = Document::new();
1611 doc.set_title("Buffer Test");
1612 doc.add_page(Page::a4());
1613
1614 let mut buffer = Vec::new();
1615 let result = doc.write(&mut buffer);
1616
1617 assert!(result.is_ok());
1618 assert!(!buffer.is_empty());
1619 assert!(buffer.starts_with(b"%PDF-1.7"));
1620 }
1621
1622 #[test]
1623 fn test_document_with_multiple_pages() {
1624 let mut doc = Document::new();
1625 doc.set_title("Multi-page Document");
1626 doc.set_author("Test Author");
1627 doc.set_subject("Testing multiple pages");
1628 doc.set_keywords("test, multiple, pages");
1629
1630 for _ in 0..5 {
1631 doc.add_page(Page::a4());
1632 }
1633
1634 assert_eq!(doc.pages.len(), 5);
1635 assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
1636 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1637 }
1638
1639 #[test]
1640 fn test_empty_document_write() {
1641 let mut doc = Document::new();
1642 let mut buffer = Vec::new();
1643
1644 let result = doc.write(&mut buffer);
1646 assert!(result.is_ok());
1647 assert!(!buffer.is_empty());
1648 assert!(buffer.starts_with(b"%PDF-1.7"));
1649 }
1650
1651 mod integration_tests {
1653 use super::*;
1654 use crate::graphics::Color;
1655 use crate::text::Font;
1656 use std::fs;
1657 use tempfile::TempDir;
1658
1659 #[test]
1660 fn test_document_writer_roundtrip() {
1661 let temp_dir = TempDir::new().unwrap();
1662 let file_path = temp_dir.path().join("test.pdf");
1663
1664 let mut doc = Document::new();
1666 doc.set_title("Integration Test");
1667 doc.set_author("Test Author");
1668 doc.set_subject("Writer Integration");
1669 doc.set_keywords("test, writer, integration");
1670
1671 let mut page = Page::a4();
1672 page.text()
1673 .set_font(Font::Helvetica, 12.0)
1674 .at(100.0, 700.0)
1675 .write("Integration Test Content")
1676 .unwrap();
1677
1678 doc.add_page(page);
1679
1680 let result = doc.save(&file_path);
1682 assert!(result.is_ok());
1683
1684 assert!(file_path.exists());
1686 let metadata = fs::metadata(&file_path).unwrap();
1687 assert!(metadata.len() > 0);
1688
1689 let content = fs::read(&file_path).unwrap();
1691 assert!(content.starts_with(b"%PDF-1.7"));
1692 assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
1694 }
1695
1696 #[test]
1697 fn test_document_with_complex_content() {
1698 let temp_dir = TempDir::new().unwrap();
1699 let file_path = temp_dir.path().join("complex.pdf");
1700
1701 let mut doc = Document::new();
1702 doc.set_title("Complex Content Test");
1703
1704 let mut page = Page::a4();
1706
1707 page.text()
1709 .set_font(Font::Helvetica, 14.0)
1710 .at(50.0, 750.0)
1711 .write("Complex Content Test")
1712 .unwrap();
1713
1714 page.graphics()
1716 .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
1717 .rectangle(50.0, 500.0, 200.0, 100.0)
1718 .fill();
1719
1720 page.graphics()
1721 .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
1722 .set_line_width(2.0)
1723 .move_to(50.0, 400.0)
1724 .line_to(250.0, 400.0)
1725 .stroke();
1726
1727 doc.add_page(page);
1728
1729 let result = doc.save(&file_path);
1731 assert!(result.is_ok());
1732 assert!(file_path.exists());
1733 }
1734
1735 #[test]
1736 fn test_document_multiple_pages_integration() {
1737 let temp_dir = TempDir::new().unwrap();
1738 let file_path = temp_dir.path().join("multipage.pdf");
1739
1740 let mut doc = Document::new();
1741 doc.set_title("Multi-page Integration Test");
1742
1743 for i in 1..=5 {
1745 let mut page = Page::a4();
1746
1747 page.text()
1748 .set_font(Font::Helvetica, 16.0)
1749 .at(50.0, 750.0)
1750 .write(&format!("Page {i}"))
1751 .unwrap();
1752
1753 page.text()
1754 .set_font(Font::Helvetica, 12.0)
1755 .at(50.0, 700.0)
1756 .write(&format!("This is the content for page {i}"))
1757 .unwrap();
1758
1759 let color = match i % 3 {
1761 0 => Color::rgb(1.0, 0.0, 0.0),
1762 1 => Color::rgb(0.0, 1.0, 0.0),
1763 _ => Color::rgb(0.0, 0.0, 1.0),
1764 };
1765
1766 page.graphics()
1767 .set_fill_color(color)
1768 .rectangle(50.0, 600.0, 100.0, 50.0)
1769 .fill();
1770
1771 doc.add_page(page);
1772 }
1773
1774 let result = doc.save(&file_path);
1776 assert!(result.is_ok());
1777 assert!(file_path.exists());
1778
1779 let metadata = fs::metadata(&file_path).unwrap();
1781 assert!(metadata.len() > 1000); }
1783
1784 #[test]
1785 fn test_document_metadata_persistence() {
1786 let temp_dir = TempDir::new().unwrap();
1787 let file_path = temp_dir.path().join("metadata.pdf");
1788
1789 let mut doc = Document::new();
1790 doc.set_title("Metadata Persistence Test");
1791 doc.set_author("Test Author");
1792 doc.set_subject("Testing metadata preservation");
1793 doc.set_keywords("metadata, persistence, test");
1794
1795 doc.add_page(Page::a4());
1796
1797 let result = doc.save(&file_path);
1799 assert!(result.is_ok());
1800
1801 let content = fs::read(&file_path).unwrap();
1803 let content_str = String::from_utf8_lossy(&content);
1804
1805 assert!(content_str.contains("Metadata Persistence Test"));
1807 assert!(content_str.contains("Test Author"));
1808 }
1809
1810 #[test]
1811 fn test_document_writer_error_handling() {
1812 let mut doc = Document::new();
1813 doc.add_page(Page::a4());
1814
1815 let result = doc.save("/invalid/path/test.pdf");
1817 assert!(result.is_err());
1818 }
1819
1820 #[test]
1821 fn test_document_page_integration() {
1822 let mut doc = Document::new();
1823
1824 let page1 = Page::a4();
1826 let page2 = Page::letter();
1827 let mut page3 = Page::new(500.0, 400.0);
1828
1829 page3
1831 .text()
1832 .set_font(Font::Helvetica, 10.0)
1833 .at(25.0, 350.0)
1834 .write("Custom size page")
1835 .unwrap();
1836
1837 doc.add_page(page1);
1838 doc.add_page(page2);
1839 doc.add_page(page3);
1840
1841 assert_eq!(doc.pages.len(), 3);
1842
1843 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); }
1851
1852 #[test]
1853 fn test_document_content_generation() {
1854 let temp_dir = TempDir::new().unwrap();
1855 let file_path = temp_dir.path().join("content.pdf");
1856
1857 let mut doc = Document::new();
1858 doc.set_title("Content Generation Test");
1859
1860 let mut page = Page::a4();
1861
1862 for i in 0..10 {
1864 let y_pos = 700.0 - (i as f64 * 30.0);
1865 page.text()
1866 .set_font(Font::Helvetica, 12.0)
1867 .at(50.0, y_pos)
1868 .write(&format!("Generated line {}", i + 1))
1869 .unwrap();
1870 }
1871
1872 doc.add_page(page);
1873
1874 let result = doc.save(&file_path);
1876 assert!(result.is_ok());
1877 assert!(file_path.exists());
1878
1879 let metadata = fs::metadata(&file_path).unwrap();
1881 assert!(metadata.len() > 500); }
1883
1884 #[test]
1885 fn test_document_buffer_vs_file_write() {
1886 let temp_dir = TempDir::new().unwrap();
1887 let file_path = temp_dir.path().join("buffer_vs_file.pdf");
1888
1889 let mut doc = Document::new();
1890 doc.set_title("Buffer vs File Test");
1891 doc.add_page(Page::a4());
1892
1893 let mut buffer = Vec::new();
1895 let buffer_result = doc.write(&mut buffer);
1896 assert!(buffer_result.is_ok());
1897
1898 let file_result = doc.save(&file_path);
1900 assert!(file_result.is_ok());
1901
1902 let file_content = fs::read(&file_path).unwrap();
1904
1905 assert!(buffer.starts_with(b"%PDF-1.7"));
1907 assert!(file_content.starts_with(b"%PDF-1.7"));
1908 assert!(buffer.ends_with(b"%%EOF\n"));
1909 assert!(file_content.ends_with(b"%%EOF\n"));
1910
1911 let buffer_str = String::from_utf8_lossy(&buffer);
1913 let file_str = String::from_utf8_lossy(&file_content);
1914 assert!(buffer_str.contains("Buffer vs File Test"));
1915 assert!(file_str.contains("Buffer vs File Test"));
1916 }
1917
1918 #[test]
1919 fn test_document_large_content_handling() {
1920 let temp_dir = TempDir::new().unwrap();
1921 let file_path = temp_dir.path().join("large_content.pdf");
1922
1923 let mut doc = Document::new();
1924 doc.set_title("Large Content Test");
1925
1926 let mut page = Page::a4();
1927
1928 let large_text =
1930 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(200);
1931 page.text()
1932 .set_font(Font::Helvetica, 10.0)
1933 .at(50.0, 750.0)
1934 .write(&large_text)
1935 .unwrap();
1936
1937 doc.add_page(page);
1938
1939 let result = doc.save(&file_path);
1941 assert!(result.is_ok());
1942 assert!(file_path.exists());
1943
1944 let metadata = fs::metadata(&file_path).unwrap();
1946 assert!(metadata.len() > 500); }
1948
1949 #[test]
1950 fn test_document_incremental_building() {
1951 let temp_dir = TempDir::new().unwrap();
1952 let file_path = temp_dir.path().join("incremental.pdf");
1953
1954 let mut doc = Document::new();
1955
1956 doc.set_title("Incremental Building Test");
1958
1959 let mut page1 = Page::a4();
1961 page1
1962 .text()
1963 .set_font(Font::Helvetica, 12.0)
1964 .at(50.0, 750.0)
1965 .write("First page content")
1966 .unwrap();
1967 doc.add_page(page1);
1968
1969 doc.set_author("Incremental Author");
1971 doc.set_subject("Incremental Subject");
1972
1973 let mut page2 = Page::a4();
1975 page2
1976 .text()
1977 .set_font(Font::Helvetica, 12.0)
1978 .at(50.0, 750.0)
1979 .write("Second page content")
1980 .unwrap();
1981 doc.add_page(page2);
1982
1983 doc.set_keywords("incremental, building, test");
1985
1986 let result = doc.save(&file_path);
1988 assert!(result.is_ok());
1989 assert!(file_path.exists());
1990
1991 assert_eq!(doc.pages.len(), 2);
1993 assert_eq!(
1994 doc.metadata.title,
1995 Some("Incremental Building Test".to_string())
1996 );
1997 assert_eq!(doc.metadata.author, Some("Incremental Author".to_string()));
1998 assert_eq!(
1999 doc.metadata.subject,
2000 Some("Incremental Subject".to_string())
2001 );
2002 assert_eq!(
2003 doc.metadata.keywords,
2004 Some("incremental, building, test".to_string())
2005 );
2006 }
2007
2008 #[test]
2009 fn test_document_concurrent_page_operations() {
2010 let mut doc = Document::new();
2011 doc.set_title("Concurrent Operations Test");
2012
2013 let mut pages = Vec::new();
2015
2016 for i in 0..5 {
2018 let mut page = Page::a4();
2019 page.text()
2020 .set_font(Font::Helvetica, 12.0)
2021 .at(50.0, 750.0)
2022 .write(&format!("Concurrent page {i}"))
2023 .unwrap();
2024 pages.push(page);
2025 }
2026
2027 for page in pages {
2029 doc.add_page(page);
2030 }
2031
2032 assert_eq!(doc.pages.len(), 5);
2033
2034 let temp_dir = TempDir::new().unwrap();
2036 let file_path = temp_dir.path().join("concurrent.pdf");
2037 let result = doc.save(&file_path);
2038 assert!(result.is_ok());
2039 }
2040
2041 #[test]
2042 fn test_document_memory_efficiency() {
2043 let mut doc = Document::new();
2044 doc.set_title("Memory Efficiency Test");
2045
2046 for i in 0..10 {
2048 let mut page = Page::a4();
2049 page.text()
2050 .set_font(Font::Helvetica, 12.0)
2051 .at(50.0, 700.0)
2052 .write(&format!("Memory test page {i}"))
2053 .unwrap();
2054 doc.add_page(page);
2055 }
2056
2057 let mut buffer = Vec::new();
2059 let result = doc.write(&mut buffer);
2060 assert!(result.is_ok());
2061 assert!(!buffer.is_empty());
2062
2063 assert!(buffer.len() < 1_000_000); }
2066
2067 #[test]
2068 fn test_document_creator_producer() {
2069 let mut doc = Document::new();
2070
2071 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
2073 assert!(doc
2074 .metadata
2075 .producer
2076 .as_ref()
2077 .unwrap()
2078 .contains("oxidize_pdf"));
2079
2080 doc.set_creator("My Application");
2082 doc.set_producer("My PDF Library v1.0");
2083
2084 assert_eq!(doc.metadata.creator, Some("My Application".to_string()));
2085 assert_eq!(
2086 doc.metadata.producer,
2087 Some("My PDF Library v1.0".to_string())
2088 );
2089 }
2090
2091 #[test]
2092 fn test_document_dates() {
2093 use chrono::{TimeZone, Utc};
2094
2095 let mut doc = Document::new();
2096
2097 assert!(doc.metadata.creation_date.is_some());
2099 assert!(doc.metadata.modification_date.is_some());
2100
2101 let creation_date = Utc.with_ymd_and_hms(2023, 1, 1, 12, 0, 0).unwrap();
2103 let mod_date = Utc.with_ymd_and_hms(2023, 6, 15, 18, 30, 0).unwrap();
2104
2105 doc.set_creation_date(creation_date);
2106 doc.set_modification_date(mod_date);
2107
2108 assert_eq!(doc.metadata.creation_date, Some(creation_date));
2109 assert_eq!(doc.metadata.modification_date, Some(mod_date));
2110 }
2111
2112 #[test]
2113 fn test_document_dates_local() {
2114 use chrono::{Local, TimeZone};
2115
2116 let mut doc = Document::new();
2117
2118 let local_date = Local.with_ymd_and_hms(2023, 12, 25, 10, 30, 0).unwrap();
2120 doc.set_creation_date_local(local_date);
2121
2122 assert!(doc.metadata.creation_date.is_some());
2124 assert!(doc.metadata.creation_date.is_some());
2126 }
2127
2128 #[test]
2129 fn test_update_modification_date() {
2130 let mut doc = Document::new();
2131
2132 let initial_mod_date = doc.metadata.modification_date;
2133 assert!(initial_mod_date.is_some());
2134
2135 std::thread::sleep(std::time::Duration::from_millis(10));
2137
2138 doc.update_modification_date();
2139
2140 let new_mod_date = doc.metadata.modification_date;
2141 assert!(new_mod_date.is_some());
2142 assert!(new_mod_date.unwrap() > initial_mod_date.unwrap());
2143 }
2144
2145 #[test]
2146 fn test_document_save_updates_modification_date() {
2147 let temp_dir = TempDir::new().unwrap();
2148 let file_path = temp_dir.path().join("mod_date_test.pdf");
2149
2150 let mut doc = Document::new();
2151 doc.add_page(Page::a4());
2152
2153 let initial_mod_date = doc.metadata.modification_date;
2154
2155 std::thread::sleep(std::time::Duration::from_millis(10));
2157
2158 doc.save(&file_path).unwrap();
2159
2160 assert!(doc.metadata.modification_date.unwrap() > initial_mod_date.unwrap());
2162 }
2163
2164 #[test]
2165 fn test_document_metadata_complete() {
2166 let mut doc = Document::new();
2167
2168 doc.set_title("Complete Metadata Test");
2170 doc.set_author("Test Author");
2171 doc.set_subject("Testing all metadata fields");
2172 doc.set_keywords("test, metadata, complete");
2173 doc.set_creator("Test Application v1.0");
2174 doc.set_producer("oxidize_pdf Test Suite");
2175
2176 assert_eq!(
2178 doc.metadata.title,
2179 Some("Complete Metadata Test".to_string())
2180 );
2181 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
2182 assert_eq!(
2183 doc.metadata.subject,
2184 Some("Testing all metadata fields".to_string())
2185 );
2186 assert_eq!(
2187 doc.metadata.keywords,
2188 Some("test, metadata, complete".to_string())
2189 );
2190 assert_eq!(
2191 doc.metadata.creator,
2192 Some("Test Application v1.0".to_string())
2193 );
2194 assert_eq!(
2195 doc.metadata.producer,
2196 Some("oxidize_pdf Test Suite".to_string())
2197 );
2198 assert!(doc.metadata.creation_date.is_some());
2199 assert!(doc.metadata.modification_date.is_some());
2200 }
2201
2202 #[test]
2203 fn test_document_to_bytes() {
2204 let mut doc = Document::new();
2205 doc.set_title("Test Document");
2206 doc.set_author("Test Author");
2207
2208 let page = Page::a4();
2209 doc.add_page(page);
2210
2211 let pdf_bytes = doc.to_bytes().unwrap();
2213
2214 assert!(!pdf_bytes.is_empty());
2216 assert!(pdf_bytes.len() > 100); let header = &pdf_bytes[0..5];
2220 assert_eq!(header, b"%PDF-");
2221
2222 let pdf_str = String::from_utf8_lossy(&pdf_bytes);
2224 assert!(pdf_str.contains("Test Document"));
2225 assert!(pdf_str.contains("Test Author"));
2226 }
2227
2228 #[test]
2229 fn test_document_to_bytes_with_config() {
2230 let mut doc = Document::new();
2231 doc.set_title("Test Document XRef");
2232
2233 let page = Page::a4();
2234 doc.add_page(page);
2235
2236 let config = crate::writer::WriterConfig {
2237 use_xref_streams: true,
2238 use_object_streams: false,
2239 pdf_version: "1.5".to_string(),
2240 compress_streams: true,
2241 incremental_update: false,
2242 };
2243
2244 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
2246
2247 assert!(!pdf_bytes.is_empty());
2249 assert!(pdf_bytes.len() > 100);
2250
2251 let header = String::from_utf8_lossy(&pdf_bytes[0..8]);
2253 assert!(header.contains("PDF-1.5"));
2254 }
2255
2256 #[test]
2257 fn test_to_bytes_vs_save_equivalence() {
2258 use std::fs;
2259 use tempfile::NamedTempFile;
2260
2261 let mut doc1 = Document::new();
2263 doc1.set_title("Equivalence Test");
2264 doc1.add_page(Page::a4());
2265
2266 let mut doc2 = Document::new();
2267 doc2.set_title("Equivalence Test");
2268 doc2.add_page(Page::a4());
2269
2270 let pdf_bytes = doc1.to_bytes().unwrap();
2272
2273 let temp_file = NamedTempFile::new().unwrap();
2275 doc2.save(temp_file.path()).unwrap();
2276 let file_bytes = fs::read(temp_file.path()).unwrap();
2277
2278 assert!(!pdf_bytes.is_empty());
2280 assert!(!file_bytes.is_empty());
2281 assert_eq!(&pdf_bytes[0..5], &file_bytes[0..5]); }
2283
2284 #[test]
2285 fn test_document_set_compress() {
2286 let mut doc = Document::new();
2287 doc.set_title("Compression Test");
2288 doc.add_page(Page::a4());
2289
2290 assert!(doc.get_compress());
2292
2293 doc.set_compress(true);
2295 let compressed_bytes = doc.to_bytes().unwrap();
2296
2297 doc.set_compress(false);
2299 let uncompressed_bytes = doc.to_bytes().unwrap();
2300
2301 assert!(!compressed_bytes.is_empty());
2303 assert!(!uncompressed_bytes.is_empty());
2304
2305 assert_eq!(&compressed_bytes[0..5], b"%PDF-");
2307 assert_eq!(&uncompressed_bytes[0..5], b"%PDF-");
2308 }
2309
2310 #[test]
2311 fn test_document_compression_config_inheritance() {
2312 let mut doc = Document::new();
2313 doc.set_title("Config Inheritance Test");
2314 doc.add_page(Page::a4());
2315
2316 doc.set_compress(false);
2318
2319 let config = crate::writer::WriterConfig {
2321 use_xref_streams: false,
2322 use_object_streams: false,
2323 pdf_version: "1.7".to_string(),
2324 compress_streams: true,
2325 incremental_update: false,
2326 };
2327
2328 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
2330
2331 assert!(!pdf_bytes.is_empty());
2333 assert_eq!(&pdf_bytes[0..5], b"%PDF-");
2334 }
2335
2336 #[test]
2337 fn test_document_metadata_all_fields() {
2338 let mut doc = Document::new();
2339
2340 doc.set_title("Test Document");
2342 doc.set_author("John Doe");
2343 doc.set_subject("Testing PDF metadata");
2344 doc.set_keywords("test, pdf, metadata");
2345 doc.set_creator("Test Suite");
2346 doc.set_producer("oxidize_pdf tests");
2347
2348 assert_eq!(doc.metadata.title.as_deref(), Some("Test Document"));
2350 assert_eq!(doc.metadata.author.as_deref(), Some("John Doe"));
2351 assert_eq!(
2352 doc.metadata.subject.as_deref(),
2353 Some("Testing PDF metadata")
2354 );
2355 assert_eq!(
2356 doc.metadata.keywords.as_deref(),
2357 Some("test, pdf, metadata")
2358 );
2359 assert_eq!(doc.metadata.creator.as_deref(), Some("Test Suite"));
2360 assert_eq!(doc.metadata.producer.as_deref(), Some("oxidize_pdf tests"));
2361 assert!(doc.metadata.creation_date.is_some());
2362 assert!(doc.metadata.modification_date.is_some());
2363 }
2364
2365 #[test]
2366 fn test_document_add_pages() {
2367 let mut doc = Document::new();
2368
2369 assert_eq!(doc.page_count(), 0);
2371
2372 let page1 = Page::a4();
2374 let page2 = Page::letter();
2375 let page3 = Page::legal();
2376
2377 doc.add_page(page1);
2378 assert_eq!(doc.page_count(), 1);
2379
2380 doc.add_page(page2);
2381 assert_eq!(doc.page_count(), 2);
2382
2383 doc.add_page(page3);
2384 assert_eq!(doc.page_count(), 3);
2385
2386 let result = doc.to_bytes();
2388 assert!(result.is_ok());
2389 }
2390
2391 #[test]
2392 fn test_document_default_font_encoding() {
2393 let mut doc = Document::new();
2394
2395 assert!(doc.default_font_encoding.is_none());
2397
2398 doc.set_default_font_encoding(Some(FontEncoding::WinAnsiEncoding));
2400 assert_eq!(
2401 doc.default_font_encoding(),
2402 Some(FontEncoding::WinAnsiEncoding)
2403 );
2404
2405 doc.set_default_font_encoding(Some(FontEncoding::MacRomanEncoding));
2407 assert_eq!(
2408 doc.default_font_encoding(),
2409 Some(FontEncoding::MacRomanEncoding)
2410 );
2411 }
2412
2413 #[test]
2414 fn test_document_compression_setting() {
2415 let mut doc = Document::new();
2416
2417 assert!(doc.compress);
2419
2420 doc.set_compress(false);
2422 assert!(!doc.compress);
2423
2424 doc.set_compress(true);
2426 assert!(doc.compress);
2427 }
2428
2429 #[test]
2430 fn test_document_with_empty_pages() {
2431 let mut doc = Document::new();
2432
2433 doc.add_page(Page::a4());
2435
2436 let result = doc.to_bytes();
2438 assert!(result.is_ok());
2439
2440 let pdf_bytes = result.unwrap();
2441 assert!(!pdf_bytes.is_empty());
2442 assert!(pdf_bytes.starts_with(b"%PDF-"));
2443 }
2444
2445 #[test]
2446 fn test_document_with_multiple_page_sizes() {
2447 let mut doc = Document::new();
2448
2449 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);
2457
2458 let result = doc.to_bytes();
2462 assert!(result.is_ok());
2463 }
2464
2465 #[test]
2466 fn test_document_metadata_dates() {
2467 use chrono::Duration;
2468
2469 let doc = Document::new();
2470
2471 assert!(doc.metadata.creation_date.is_some());
2473 assert!(doc.metadata.modification_date.is_some());
2474
2475 if let (Some(created), Some(modified)) =
2476 (doc.metadata.creation_date, doc.metadata.modification_date)
2477 {
2478 let diff = modified - created;
2480 assert!(diff < Duration::seconds(1));
2481 }
2482 }
2483
2484 #[test]
2485 fn test_document_builder_pattern() {
2486 let mut doc = Document::new();
2488 doc.set_title("Fluent");
2489 doc.set_author("Builder");
2490 doc.set_compress(true);
2491
2492 assert_eq!(doc.metadata.title.as_deref(), Some("Fluent"));
2493 assert_eq!(doc.metadata.author.as_deref(), Some("Builder"));
2494 assert!(doc.compress);
2495 }
2496
2497 #[test]
2498 fn test_xref_streams_functionality() {
2499 use crate::{Document, Font, Page};
2500
2501 let mut doc = Document::new();
2503 assert!(!doc.use_xref_streams);
2504
2505 let mut page = Page::a4();
2506 page.text()
2507 .set_font(Font::Helvetica, 12.0)
2508 .at(100.0, 700.0)
2509 .write("Testing XRef Streams")
2510 .unwrap();
2511
2512 doc.add_page(page);
2513
2514 let pdf_without_xref = doc.to_bytes().unwrap();
2516
2517 let pdf_str = String::from_utf8_lossy(&pdf_without_xref);
2519 assert!(pdf_str.contains("xref"), "Traditional xref table not found");
2520 assert!(
2521 !pdf_str.contains("/Type /XRef"),
2522 "XRef stream found when it shouldn't be"
2523 );
2524
2525 doc.enable_xref_streams(true);
2527 assert!(doc.use_xref_streams);
2528
2529 let pdf_with_xref = doc.to_bytes().unwrap();
2531
2532 let pdf_str = String::from_utf8_lossy(&pdf_with_xref);
2534 assert!(
2536 pdf_str.contains("/Type /XRef") || pdf_str.contains("stream"),
2537 "XRef stream not found when enabled"
2538 );
2539
2540 assert!(
2542 pdf_str.contains("PDF-1.5"),
2543 "PDF version not set to 1.5 for xref streams"
2544 );
2545
2546 let mut doc2 = Document::new();
2548 doc2.enable_xref_streams(true);
2549 doc2.set_title("XRef Streams Test");
2550 doc2.set_author("oxidize-pdf");
2551
2552 assert!(doc2.use_xref_streams);
2553 assert_eq!(doc2.metadata.title.as_deref(), Some("XRef Streams Test"));
2554 assert_eq!(doc2.metadata.author.as_deref(), Some("oxidize-pdf"));
2555 }
2556
2557 #[test]
2558 fn test_document_save_to_vec() {
2559 let mut doc = Document::new();
2560 doc.set_title("Test Save");
2561 doc.add_page(Page::a4());
2562
2563 let bytes_result = doc.to_bytes();
2565 assert!(bytes_result.is_ok());
2566
2567 let bytes = bytes_result.unwrap();
2568 assert!(!bytes.is_empty());
2569 assert!(bytes.starts_with(b"%PDF-"));
2570 assert!(bytes.ends_with(b"%%EOF") || bytes.ends_with(b"%%EOF\n"));
2571 }
2572
2573 #[test]
2574 fn test_document_unicode_metadata() {
2575 let mut doc = Document::new();
2576
2577 doc.set_title("日本語のタイトル");
2579 doc.set_author("作者名 😀");
2580 doc.set_subject("Тема документа");
2581 doc.set_keywords("كلمات, מפתח, 关键词");
2582
2583 assert_eq!(doc.metadata.title.as_deref(), Some("日本語のタイトル"));
2584 assert_eq!(doc.metadata.author.as_deref(), Some("作者名 😀"));
2585 assert_eq!(doc.metadata.subject.as_deref(), Some("Тема документа"));
2586 assert_eq!(
2587 doc.metadata.keywords.as_deref(),
2588 Some("كلمات, מפתח, 关键词")
2589 );
2590 }
2591
2592 #[test]
2593 fn test_document_page_iteration() {
2594 let mut doc = Document::new();
2595
2596 for i in 0..5 {
2598 let mut page = Page::a4();
2599 let gc = page.graphics();
2600 gc.begin_text();
2601 let _ = gc.show_text(&format!("Page {}", i + 1));
2602 gc.end_text();
2603 doc.add_page(page);
2604 }
2605
2606 assert_eq!(doc.page_count(), 5);
2608
2609 let result = doc.to_bytes();
2611 assert!(result.is_ok());
2612 }
2613
2614 #[test]
2615 fn test_document_with_graphics_content() {
2616 let mut doc = Document::new();
2617
2618 let mut page = Page::a4();
2619 {
2620 let gc = page.graphics();
2621
2622 gc.save_state();
2624
2625 gc.rectangle(100.0, 100.0, 200.0, 150.0);
2627 gc.stroke();
2628
2629 gc.move_to(300.0, 300.0);
2631 gc.circle(300.0, 300.0, 50.0);
2632 gc.fill();
2633
2634 gc.begin_text();
2636 gc.set_text_position(100.0, 500.0);
2637 let _ = gc.show_text("Graphics Test");
2638 gc.end_text();
2639
2640 gc.restore_state();
2641 }
2642
2643 doc.add_page(page);
2644
2645 let result = doc.to_bytes();
2647 assert!(result.is_ok());
2648 }
2649
2650 #[test]
2651 fn test_document_producer_version() {
2652 let doc = Document::new();
2653
2654 assert!(doc.metadata.producer.is_some());
2656 if let Some(producer) = &doc.metadata.producer {
2657 assert!(producer.contains("oxidize_pdf"));
2658 assert!(producer.contains(env!("CARGO_PKG_VERSION")));
2659 }
2660 }
2661
2662 #[test]
2663 fn test_document_empty_metadata_fields() {
2664 let mut doc = Document::new();
2665
2666 doc.set_title("");
2668 doc.set_author("");
2669 doc.set_subject("");
2670 doc.set_keywords("");
2671
2672 assert_eq!(doc.metadata.title.as_deref(), Some(""));
2674 assert_eq!(doc.metadata.author.as_deref(), Some(""));
2675 assert_eq!(doc.metadata.subject.as_deref(), Some(""));
2676 assert_eq!(doc.metadata.keywords.as_deref(), Some(""));
2677 }
2678
2679 #[test]
2680 fn test_document_very_long_metadata() {
2681 let mut doc = Document::new();
2682
2683 let long_title = "A".repeat(1000);
2685 let long_author = "B".repeat(500);
2686 let long_keywords = vec!["keyword"; 100].join(", ");
2687
2688 doc.set_title(&long_title);
2689 doc.set_author(&long_author);
2690 doc.set_keywords(&long_keywords);
2691
2692 assert_eq!(doc.metadata.title.as_deref(), Some(long_title.as_str()));
2693 assert_eq!(doc.metadata.author.as_deref(), Some(long_author.as_str()));
2694 assert!(doc.metadata.keywords.as_ref().unwrap().len() > 500);
2695 }
2696 }
2697
2698 #[test]
2699 fn test_add_font_from_bytes_writes_to_per_document_store_not_global() {
2700 let unique = format!("PerDocTask9_{}", std::process::id());
2702 #[allow(deprecated)]
2706 let before = crate::text::metrics::get_custom_font_metrics(&unique);
2707 assert!(before.is_none(), "precondition: name not in global");
2708
2709 let doc = Document::new();
2714 doc.font_metrics
2715 .register(unique.clone(), crate::text::metrics::FontMetrics::new(500));
2716
2717 assert!(doc.font_metrics.get(&unique).is_some());
2719
2720 #[allow(deprecated)]
2722 let after = crate::text::metrics::get_custom_font_metrics(&unique);
2723 assert!(after.is_none(), "global must remain untouched");
2724 }
2725
2726 #[test]
2727 fn test_new_page_a4_returns_page_bound_to_document_store() {
2728 let doc = Document::new();
2729 doc.font_metrics
2730 .register("Sentinel", crate::text::metrics::FontMetrics::new(400));
2731
2732 let page = doc.new_page_a4();
2733 assert!(page.font_metrics_store.is_some());
2734 let store = page.font_metrics_store.as_ref().unwrap();
2735 assert!(
2736 store.get("Sentinel").is_some(),
2737 "store must share with Document"
2738 );
2739 }
2740
2741 #[test]
2742 fn test_new_page_letter_and_new_page_carry_store() {
2743 let doc = Document::new();
2744 doc.font_metrics
2745 .register("S", crate::text::metrics::FontMetrics::new(400));
2746 assert!(doc.new_page_letter().font_metrics_store.is_some());
2747 assert!(doc.new_page(400.0, 600.0).font_metrics_store.is_some());
2748 }
2749
2750 #[test]
2751 fn test_add_page_injects_store_into_legacy_page() {
2752 let mut doc = Document::new();
2753 doc.font_metrics
2754 .register("Inj", crate::text::metrics::FontMetrics::new(400));
2755
2756 let page = Page::a4(); assert!(page.font_metrics_store.is_none());
2758
2759 doc.add_page(page);
2760
2761 let stored_page = doc.pages.last().expect("page added");
2762 assert!(
2763 stored_page.font_metrics_store.is_some(),
2764 "add_page must inject the Document store when page has none"
2765 );
2766 assert!(
2767 stored_page
2768 .font_metrics_store
2769 .as_ref()
2770 .unwrap()
2771 .get("Inj")
2772 .is_some(),
2773 "injected store must share state with the Document"
2774 );
2775 }
2776
2777 #[test]
2778 fn test_add_page_does_not_overwrite_existing_store() {
2779 let doc_a = Document::new();
2780 doc_a
2781 .font_metrics
2782 .register("FromA", crate::text::metrics::FontMetrics::new(400));
2783 let page = doc_a.new_page_a4(); let mut doc_b = Document::new();
2786 doc_b
2787 .font_metrics
2788 .register("FromB", crate::text::metrics::FontMetrics::new(500));
2789 doc_b.add_page(page);
2790
2791 let stored_page = doc_b.pages.last().expect("page added");
2792 let store = stored_page.font_metrics_store.as_ref().unwrap();
2793 assert!(store.get("FromA").is_some(), "page kept doc_a's store");
2794 assert!(store.get("FromB").is_none(), "doc_b did not overwrite");
2795 }
2796}