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}
71
72#[derive(Debug, Clone)]
74pub struct DocumentMetadata {
75 pub title: Option<String>,
77 pub author: Option<String>,
79 pub subject: Option<String>,
81 pub keywords: Option<String>,
83 pub creator: Option<String>,
85 pub producer: Option<String>,
87 pub creation_date: Option<DateTime<Utc>>,
89 pub modification_date: Option<DateTime<Utc>>,
91}
92
93impl Default for DocumentMetadata {
94 fn default() -> Self {
95 let now = Utc::now();
96
97 let edition = "MIT";
98
99 Self {
100 title: None,
101 author: None,
102 subject: None,
103 keywords: None,
104 creator: Some("oxidize_pdf".to_string()),
105 producer: Some(format!(
106 "oxidize_pdf v{} ({})",
107 env!("CARGO_PKG_VERSION"),
108 edition
109 )),
110 creation_date: Some(now),
111 modification_date: Some(now),
112 }
113 }
114}
115
116impl Document {
117 pub fn new() -> Self {
119 Self {
120 pages: Vec::new(),
121 metadata: DocumentMetadata::default(),
122 encryption: None,
123 outline: None,
124 named_destinations: None,
125 page_labels: None,
126 default_font_encoding: None,
127 acro_form: None,
128 form_manager: None,
129 compress: true, use_xref_streams: false, custom_fonts: FontCache::new(),
132 font_metrics: FontMetricsStore::new(),
133 used_characters_by_font: HashMap::new(),
134 open_action: None,
135 viewer_preferences: None,
136 semantic_entities: Vec::new(),
137 struct_tree: None,
138 }
139 }
140
141 pub fn add_page(&mut self, mut page: Page) {
143 if page.font_metrics_store.is_none() {
151 page.font_metrics_store = Some(self.font_metrics.clone());
152 }
153 for (font_name, chars) in page.get_used_characters_by_font() {
157 self.used_characters_by_font
158 .entry(font_name)
159 .or_default()
160 .extend(chars);
161 }
162 self.pages.push(page);
163 }
164
165 pub fn pages(&self) -> &[Page] {
167 &self.pages
168 }
169
170 pub fn font_metrics(&self) -> &FontMetricsStore {
178 &self.font_metrics
179 }
180
181 pub fn new_page_a4(&self) -> Page {
187 Page::a4_with_metrics(self.font_metrics.clone())
188 }
189
190 pub fn new_page_letter(&self) -> Page {
192 Page::letter_with_metrics(self.font_metrics.clone())
193 }
194
195 pub fn new_page(&self, width: f64, height: f64) -> Page {
198 Page::new_with_metrics(width, height, self.font_metrics.clone())
199 }
200
201 pub fn set_title(&mut self, title: impl Into<String>) {
203 self.metadata.title = Some(title.into());
204 }
205
206 pub fn set_author(&mut self, author: impl Into<String>) {
208 self.metadata.author = Some(author.into());
209 }
210
211 pub fn set_form_manager(&mut self, form_manager: FormManager) {
213 self.form_manager = Some(form_manager);
214 }
215
216 pub fn set_subject(&mut self, subject: impl Into<String>) {
218 self.metadata.subject = Some(subject.into());
219 }
220
221 pub fn set_keywords(&mut self, keywords: impl Into<String>) {
223 self.metadata.keywords = Some(keywords.into());
224 }
225
226 pub fn set_encryption(&mut self, encryption: DocumentEncryption) {
228 self.encryption = Some(encryption);
229 }
230
231 pub fn encrypt_with_passwords(
233 &mut self,
234 user_password: impl Into<String>,
235 owner_password: impl Into<String>,
236 ) {
237 self.encryption = Some(DocumentEncryption::with_passwords(
238 user_password,
239 owner_password,
240 ));
241 }
242
243 pub fn is_encrypted(&self) -> bool {
245 self.encryption.is_some()
246 }
247
248 pub fn set_open_action(&mut self, action: crate::actions::Action) {
250 self.open_action = Some(action);
251 }
252
253 pub fn open_action(&self) -> Option<&crate::actions::Action> {
255 self.open_action.as_ref()
256 }
257
258 pub fn set_viewer_preferences(
260 &mut self,
261 preferences: crate::viewer_preferences::ViewerPreferences,
262 ) {
263 self.viewer_preferences = Some(preferences);
264 }
265
266 pub fn viewer_preferences(&self) -> Option<&crate::viewer_preferences::ViewerPreferences> {
268 self.viewer_preferences.as_ref()
269 }
270
271 pub fn set_struct_tree(&mut self, tree: StructTree) {
297 self.struct_tree = Some(tree);
298 }
299
300 pub fn struct_tree(&self) -> Option<&StructTree> {
302 self.struct_tree.as_ref()
303 }
304
305 pub fn struct_tree_mut(&mut self) -> Option<&mut StructTree> {
307 self.struct_tree.as_mut()
308 }
309
310 pub fn get_or_create_struct_tree(&mut self) -> &mut StructTree {
327 self.struct_tree.get_or_insert_with(StructTree::new)
328 }
329
330 pub fn set_outline(&mut self, outline: OutlineTree) {
332 self.outline = Some(outline);
333 }
334
335 pub fn outline(&self) -> Option<&OutlineTree> {
337 self.outline.as_ref()
338 }
339
340 pub fn outline_mut(&mut self) -> Option<&mut OutlineTree> {
342 self.outline.as_mut()
343 }
344
345 pub fn set_named_destinations(&mut self, destinations: NamedDestinations) {
347 self.named_destinations = Some(destinations);
348 }
349
350 pub fn named_destinations(&self) -> Option<&NamedDestinations> {
352 self.named_destinations.as_ref()
353 }
354
355 pub fn named_destinations_mut(&mut self) -> Option<&mut NamedDestinations> {
357 self.named_destinations.as_mut()
358 }
359
360 pub fn set_page_labels(&mut self, labels: PageLabelTree) {
362 self.page_labels = Some(labels);
363 }
364
365 pub fn page_labels(&self) -> Option<&PageLabelTree> {
367 self.page_labels.as_ref()
368 }
369
370 pub fn page_labels_mut(&mut self) -> Option<&mut PageLabelTree> {
372 self.page_labels.as_mut()
373 }
374
375 pub fn get_page_label(&self, page_index: u32) -> String {
377 self.page_labels
378 .as_ref()
379 .and_then(|labels| labels.get_label(page_index))
380 .unwrap_or_else(|| (page_index + 1).to_string())
381 }
382
383 pub fn get_all_page_labels(&self) -> Vec<String> {
385 let page_count = self.pages.len() as u32;
386 if let Some(labels) = &self.page_labels {
387 labels.get_all_labels(page_count)
388 } else {
389 (1..=page_count).map(|i| i.to_string()).collect()
390 }
391 }
392
393 pub fn set_creator(&mut self, creator: impl Into<String>) {
395 self.metadata.creator = Some(creator.into());
396 }
397
398 pub fn set_producer(&mut self, producer: impl Into<String>) {
400 self.metadata.producer = Some(producer.into());
401 }
402
403 pub fn set_creation_date(&mut self, date: DateTime<Utc>) {
405 self.metadata.creation_date = Some(date);
406 }
407
408 pub fn set_creation_date_local(&mut self, date: DateTime<Local>) {
410 self.metadata.creation_date = Some(date.with_timezone(&Utc));
411 }
412
413 pub fn set_modification_date(&mut self, date: DateTime<Utc>) {
415 self.metadata.modification_date = Some(date);
416 }
417
418 pub fn set_modification_date_local(&mut self, date: DateTime<Local>) {
420 self.metadata.modification_date = Some(date.with_timezone(&Utc));
421 }
422
423 pub fn update_modification_date(&mut self) {
425 self.metadata.modification_date = Some(Utc::now());
426 }
427
428 pub fn set_default_font_encoding(&mut self, encoding: Option<FontEncoding>) {
443 self.default_font_encoding = encoding;
444 }
445
446 pub fn default_font_encoding(&self) -> Option<FontEncoding> {
448 self.default_font_encoding
449 }
450
451 pub fn add_font(
462 &mut self,
463 name: impl Into<String>,
464 path: impl AsRef<std::path::Path>,
465 ) -> Result<()> {
466 let name = name.into();
467 let font = CustomFont::from_file(&name, path)?;
468 self.custom_fonts.add_font(name, font)?;
469 Ok(())
470 }
471
472 pub fn add_font_from_bytes(&mut self, name: impl Into<String>, data: Vec<u8>) -> Result<()> {
484 let name = name.into();
485 let font = CustomFont::from_bytes(&name, data)?;
486
487 let units_per_em = font.metrics.units_per_em as f64;
490 let char_width_map: std::collections::HashMap<char, u16> = font
491 .glyph_mapping
492 .char_widths_iter()
493 .map(|(ch, width_font_units)| {
494 let width_1000 = ((width_font_units as f64 * 1000.0) / units_per_em).round() as u16;
495 (ch, width_1000)
496 })
497 .collect();
498
499 self.custom_fonts.add_font(name.clone(), font)?;
501
502 if !char_width_map.is_empty() {
504 let sum: u32 = char_width_map.values().map(|&w| w as u32).sum();
505 let default_width = (sum / char_width_map.len() as u32) as u16;
506 let text_metrics = TextMeasurementMetrics::from_char_map(char_width_map, default_width);
507 self.font_metrics.register(name, text_metrics);
508 }
509
510 Ok(())
511 }
512
513 pub(crate) fn get_custom_font(&self, name: &str) -> Option<Arc<CustomFont>> {
515 self.custom_fonts.get_font(name)
516 }
517
518 pub fn has_custom_font(&self, name: &str) -> bool {
520 self.custom_fonts.has_font(name)
521 }
522
523 pub fn custom_font_names(&self) -> Vec<String> {
525 self.custom_fonts.font_names()
526 }
527
528 pub fn page_count(&self) -> usize {
530 self.pages.len()
531 }
532
533 pub fn page(&self, index: usize) -> Option<&Page> {
535 self.pages.get(index)
536 }
537
538 pub fn page_mut(&mut self, index: usize) -> Option<&mut Page> {
540 self.pages.get_mut(index)
541 }
542
543 pub fn acro_form(&self) -> Option<&AcroForm> {
545 self.acro_form.as_ref()
546 }
547
548 pub fn acro_form_mut(&mut self) -> Option<&mut AcroForm> {
550 self.acro_form.as_mut()
551 }
552
553 pub fn enable_forms(&mut self) -> &mut FormManager {
556 if self.acro_form.is_none() {
557 self.acro_form = Some(AcroForm::new());
558 }
559 self.form_manager.get_or_insert_with(FormManager::new)
560 }
561
562 pub fn disable_forms(&mut self) {
564 self.acro_form = None;
565 self.form_manager = None;
566 }
567
568 pub fn fill_field(&mut self, name: &str, value: impl Into<String>) -> Result<()> {
628 use crate::error::PdfError;
629 use crate::forms::FieldType;
630 use crate::objects::Object;
631
632 let value: String = value.into();
633
634 let form_manager = self.form_manager.as_mut().ok_or_else(|| {
635 PdfError::InvalidStructure(
636 "Document has no FormManager; register fields via enable_forms() or \
637 set_form_manager() before calling fill_field"
638 .to_string(),
639 )
640 })?;
641
642 let placeholder_ref = form_manager.field_ref(name);
646
647 let form_field = form_manager
648 .get_field_mut(name)
649 .ok_or_else(|| PdfError::FieldNotFound(name.to_string()))?;
650
651 let field_type = match form_field.field_dict.get("FT") {
656 Some(Object::Name(n)) => match n.as_str() {
657 "Btn" => FieldType::Button,
658 "Ch" => FieldType::Choice,
659 "Sig" => FieldType::Signature,
660 _ => FieldType::Text,
661 },
662 _ => FieldType::Text,
663 };
664
665 form_field
671 .field_dict
672 .set("V", Object::String(value.clone()));
673
674 let typed_da = form_field.default_appearance.clone();
690 let custom_font_arc = match typed_da.as_ref().and_then(|da| match &da.font {
691 crate::text::Font::Custom(name) => Some(name.clone()),
692 _ => None,
693 }) {
694 Some(name) => self.get_custom_font(&name),
695 None => None,
696 };
697
698 let form_manager = self.form_manager.as_mut().ok_or_else(|| {
702 PdfError::InvalidStructure(
703 "FormManager vanished between steps of fill_field — unreachable in single-thread"
704 .to_string(),
705 )
706 })?;
707 let form_field = form_manager
708 .get_field_mut(name)
709 .ok_or_else(|| PdfError::FieldNotFound(name.to_string()))?;
710
711 let mut ap_used_chars_by_font: std::collections::HashMap<
715 String,
716 std::collections::HashSet<char>,
717 > = std::collections::HashMap::new();
718 let custom_font_ref: Option<&crate::fonts::Font> = custom_font_arc.as_deref();
723 for widget in &mut form_field.widgets {
724 let used = widget.generate_appearance_with_font(
725 field_type,
726 Some(&value),
727 typed_da.as_ref(),
728 custom_font_ref,
729 )?;
730 for (font_name, chars) in used {
731 ap_used_chars_by_font
732 .entry(font_name)
733 .or_default()
734 .extend(chars);
735 }
736 }
737 for (font_name, chars) in ap_used_chars_by_font {
740 self.used_characters_by_font
741 .entry(font_name)
742 .or_default()
743 .extend(chars);
744 }
745
746 if let Some(placeholder) = placeholder_ref {
752 let form_field = self
754 .form_manager
755 .as_ref()
756 .and_then(|fm| fm.get_field(name))
757 .ok_or_else(|| PdfError::FieldNotFound(name.to_string()))?;
758
759 const RECT_MATCH_TOLERANCE: f64 = 1e-3;
774
775 let mut needs_need_appearances = false;
780
781 for page in self.pages.iter_mut() {
782 for annot in page.annotations_mut().iter_mut() {
783 if annot.field_parent != Some(placeholder) {
784 continue;
785 }
786 let matching_widget = form_field.widgets.iter().find(|w| {
791 (w.rect.lower_left.x - annot.rect.lower_left.x).abs() < RECT_MATCH_TOLERANCE
792 && (w.rect.lower_left.y - annot.rect.lower_left.y).abs()
793 < RECT_MATCH_TOLERANCE
794 && (w.rect.upper_right.x - annot.rect.upper_right.x).abs()
795 < RECT_MATCH_TOLERANCE
796 && (w.rect.upper_right.y - annot.rect.upper_right.y).abs()
797 < RECT_MATCH_TOLERANCE
798 });
799
800 match matching_widget.and_then(|w| w.appearance_streams.as_ref()) {
801 Some(app_dict) => {
802 annot
803 .properties
804 .set("AP", Object::Dictionary(app_dict.to_dict()));
805 }
806 None => {
807 if annot.properties.get("AP").is_some() {
818 annot.properties.remove("AP");
819 needs_need_appearances = true;
820 } else {
821 needs_need_appearances = true;
825 }
826 }
827 }
828 }
829 }
830
831 if needs_need_appearances {
832 let acro_form = self.acro_form.get_or_insert_with(AcroForm::new);
833 acro_form.need_appearances = true;
834 }
835 }
836
837 Ok(())
838 }
839
840 pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
846 self.update_modification_date();
848
849 let config = crate::writer::WriterConfig {
851 use_xref_streams: self.use_xref_streams,
852 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
854 compress_streams: self.compress,
855 incremental_update: false,
856 };
857
858 use std::io::BufWriter;
859 let file = std::fs::File::create(path)?;
860 let writer = BufWriter::with_capacity(512 * 1024, file);
863 let mut pdf_writer = PdfWriter::with_config(writer, config);
864
865 pdf_writer.write_document(self)?;
866 Ok(())
867 }
868
869 pub fn save_with_config(
875 &mut self,
876 path: impl AsRef<std::path::Path>,
877 config: crate::writer::WriterConfig,
878 ) -> Result<()> {
879 use std::io::BufWriter;
880
881 self.update_modification_date();
883
884 let file = std::fs::File::create(path)?;
887 let writer = BufWriter::with_capacity(512 * 1024, file);
889 let mut pdf_writer = PdfWriter::with_config(writer, config);
890 pdf_writer.write_document(self)?;
891 Ok(())
892 }
893
894 pub fn save_with_custom_values(
908 &mut self,
909 path: impl AsRef<std::path::Path>,
910 custom_values: &std::collections::HashMap<String, String>,
911 ) -> Result<()> {
912 let total_pages = self.pages.len();
914 for (index, page) in self.pages.iter_mut().enumerate() {
915 let page_content = page.generate_content_with_page_info(
917 Some(index + 1),
918 Some(total_pages),
919 Some(custom_values),
920 )?;
921 page.set_content(page_content);
923 }
924
925 self.save(path)
927 }
928
929 pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
935 self.update_modification_date();
937
938 let mut writer = PdfWriter::new_with_writer(buffer);
939 writer.write_document(self)?;
940 Ok(())
941 }
942
943 pub fn set_compress(&mut self, compress: bool) {
970 self.compress = compress;
971 }
972
973 pub fn enable_xref_streams(&mut self, enable: bool) -> &mut Self {
991 self.use_xref_streams = enable;
992 self
993 }
994
995 pub fn get_compress(&self) -> bool {
1001 self.compress
1002 }
1003
1004 pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
1032 self.update_modification_date();
1034
1035 let mut buffer = Vec::new();
1037
1038 let config = crate::writer::WriterConfig {
1040 use_xref_streams: self.use_xref_streams,
1041 use_object_streams: false, pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
1043 compress_streams: self.compress,
1044 incremental_update: false,
1045 };
1046
1047 let mut writer = PdfWriter::with_config(&mut buffer, config);
1049 writer.write_document(self)?;
1050
1051 Ok(buffer)
1052 }
1053
1054 pub fn to_bytes_with_config(&mut self, config: crate::writer::WriterConfig) -> Result<Vec<u8>> {
1095 self.update_modification_date();
1097
1098 let mut buffer = Vec::new();
1102
1103 let mut writer = PdfWriter::with_config(&mut buffer, config);
1105 writer.write_document(self)?;
1106
1107 Ok(buffer)
1108 }
1109
1110 pub fn mark_entity(
1136 &mut self,
1137 id: impl Into<String>,
1138 entity_type: EntityType,
1139 bounds: BoundingBox,
1140 ) -> String {
1141 let entity_id = id.into();
1142 let entity = SemanticEntity::new(entity_id.clone(), entity_type, bounds);
1143 self.semantic_entities.push(entity);
1144 entity_id
1145 }
1146
1147 pub fn set_entity_content(&mut self, entity_id: &str, content: impl Into<String>) -> bool {
1149 if let Some(entity) = self
1150 .semantic_entities
1151 .iter_mut()
1152 .find(|e| e.id == entity_id)
1153 {
1154 entity.content = content.into();
1155 true
1156 } else {
1157 false
1158 }
1159 }
1160
1161 pub fn add_entity_metadata(
1163 &mut self,
1164 entity_id: &str,
1165 key: impl Into<String>,
1166 value: impl Into<String>,
1167 ) -> bool {
1168 if let Some(entity) = self
1169 .semantic_entities
1170 .iter_mut()
1171 .find(|e| e.id == entity_id)
1172 {
1173 entity.metadata.properties.insert(key.into(), value.into());
1174 true
1175 } else {
1176 false
1177 }
1178 }
1179
1180 pub fn set_entity_confidence(&mut self, entity_id: &str, confidence: f32) -> bool {
1182 if let Some(entity) = self
1183 .semantic_entities
1184 .iter_mut()
1185 .find(|e| e.id == entity_id)
1186 {
1187 entity.metadata.confidence = Some(confidence.clamp(0.0, 1.0));
1188 true
1189 } else {
1190 false
1191 }
1192 }
1193
1194 pub fn relate_entities(
1196 &mut self,
1197 from_id: &str,
1198 to_id: &str,
1199 relation_type: RelationType,
1200 ) -> bool {
1201 let target_exists = self.semantic_entities.iter().any(|e| e.id == to_id);
1203 if !target_exists {
1204 return false;
1205 }
1206
1207 if let Some(entity) = self.semantic_entities.iter_mut().find(|e| e.id == from_id) {
1209 entity.relationships.push(crate::semantic::EntityRelation {
1210 target_id: to_id.to_string(),
1211 relation_type,
1212 });
1213 true
1214 } else {
1215 false
1216 }
1217 }
1218
1219 pub fn get_semantic_entities(&self) -> &[SemanticEntity] {
1221 &self.semantic_entities
1222 }
1223
1224 pub fn get_entities_by_type(&self, entity_type: EntityType) -> Vec<&SemanticEntity> {
1226 self.semantic_entities
1227 .iter()
1228 .filter(|e| e.entity_type == entity_type)
1229 .collect()
1230 }
1231
1232 #[cfg(feature = "semantic")]
1234 pub fn export_semantic_entities_json(&self) -> Result<String> {
1235 serde_json::to_string_pretty(&self.semantic_entities)
1236 .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
1237 }
1238
1239 #[cfg(feature = "semantic")]
1265 pub fn export_semantic_entities_json_ld(&self) -> Result<String> {
1266 use crate::semantic::{Entity, EntityMap};
1267
1268 let mut entity_map = EntityMap::new();
1269
1270 for sem_entity in &self.semantic_entities {
1272 let entity = Entity {
1273 id: sem_entity.id.clone(),
1274 entity_type: sem_entity.entity_type.clone(),
1275 bounds: (
1276 sem_entity.bounds.x as f64,
1277 sem_entity.bounds.y as f64,
1278 sem_entity.bounds.width as f64,
1279 sem_entity.bounds.height as f64,
1280 ),
1281 page: (sem_entity.bounds.page - 1) as usize, metadata: sem_entity.metadata.clone(),
1283 };
1284 entity_map.add_entity(entity);
1285 }
1286
1287 if let Some(title) = &self.metadata.title {
1289 entity_map
1290 .document_metadata
1291 .insert("name".to_string(), title.clone());
1292 }
1293 if let Some(author) = &self.metadata.author {
1294 entity_map
1295 .document_metadata
1296 .insert("author".to_string(), author.clone());
1297 }
1298
1299 entity_map
1300 .to_json_ld()
1301 .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
1302 }
1303
1304 pub fn find_entity(&self, entity_id: &str) -> Option<&SemanticEntity> {
1306 self.semantic_entities.iter().find(|e| e.id == entity_id)
1307 }
1308
1309 pub fn remove_entity(&mut self, entity_id: &str) -> bool {
1311 if let Some(pos) = self
1312 .semantic_entities
1313 .iter()
1314 .position(|e| e.id == entity_id)
1315 {
1316 self.semantic_entities.remove(pos);
1317 for entity in &mut self.semantic_entities {
1319 entity.relationships.retain(|r| r.target_id != entity_id);
1320 }
1321 true
1322 } else {
1323 false
1324 }
1325 }
1326
1327 pub fn semantic_entity_count(&self) -> usize {
1329 self.semantic_entities.len()
1330 }
1331
1332 pub fn create_xmp_metadata(&self) -> crate::metadata::XmpMetadata {
1340 let mut xmp = crate::metadata::XmpMetadata::new();
1341
1342 if let Some(title) = &self.metadata.title {
1344 xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "title", title);
1345 }
1346 if let Some(author) = &self.metadata.author {
1347 xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "creator", author);
1348 }
1349 if let Some(subject) = &self.metadata.subject {
1350 xmp.set_text(
1351 crate::metadata::XmpNamespace::DublinCore,
1352 "description",
1353 subject,
1354 );
1355 }
1356
1357 if let Some(creator) = &self.metadata.creator {
1359 xmp.set_text(
1360 crate::metadata::XmpNamespace::XmpBasic,
1361 "CreatorTool",
1362 creator,
1363 );
1364 }
1365 if let Some(creation_date) = &self.metadata.creation_date {
1366 xmp.set_date(
1367 crate::metadata::XmpNamespace::XmpBasic,
1368 "CreateDate",
1369 creation_date.to_rfc3339(),
1370 );
1371 }
1372 if let Some(mod_date) = &self.metadata.modification_date {
1373 xmp.set_date(
1374 crate::metadata::XmpNamespace::XmpBasic,
1375 "ModifyDate",
1376 mod_date.to_rfc3339(),
1377 );
1378 }
1379
1380 if let Some(producer) = &self.metadata.producer {
1382 xmp.set_text(crate::metadata::XmpNamespace::Pdf, "Producer", producer);
1383 }
1384
1385 xmp
1386 }
1387
1388 pub fn get_xmp_packet(&self) -> String {
1397 self.create_xmp_metadata().to_xmp_packet()
1398 }
1399
1400 pub fn extract_text(&self) -> Result<String> {
1402 let mut text = String::new();
1405 for (i, _page) in self.pages.iter().enumerate() {
1406 text.push_str(&format!("Text from page {} (placeholder)\n", i + 1));
1407 }
1408 Ok(text)
1409 }
1410
1411 pub fn extract_page_text(&self, page_index: usize) -> Result<String> {
1413 if page_index < self.pages.len() {
1414 Ok(format!("Text from page {} (placeholder)", page_index + 1))
1415 } else {
1416 Err(crate::error::PdfError::InvalidReference(format!(
1417 "Page index {} out of bounds",
1418 page_index
1419 )))
1420 }
1421 }
1422}
1423
1424impl Default for Document {
1425 fn default() -> Self {
1426 Self::new()
1427 }
1428}
1429
1430#[cfg(test)]
1431mod tests {
1432 use super::*;
1433
1434 #[test]
1435 fn test_document_new() {
1436 let doc = Document::new();
1437 assert!(doc.pages.is_empty());
1438 assert!(doc.metadata.title.is_none());
1439 assert!(doc.metadata.author.is_none());
1440 assert!(doc.metadata.subject.is_none());
1441 assert!(doc.metadata.keywords.is_none());
1442 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1443 assert!(doc
1444 .metadata
1445 .producer
1446 .as_ref()
1447 .unwrap()
1448 .starts_with("oxidize_pdf"));
1449 }
1450
1451 #[test]
1452 fn test_document_default() {
1453 let doc = Document::default();
1454 assert!(doc.pages.is_empty());
1455 }
1456
1457 #[test]
1458 fn test_add_page() {
1459 let mut doc = Document::new();
1460 let page1 = Page::a4();
1461 let page2 = Page::letter();
1462
1463 doc.add_page(page1);
1464 assert_eq!(doc.pages.len(), 1);
1465
1466 doc.add_page(page2);
1467 assert_eq!(doc.pages.len(), 2);
1468 }
1469
1470 #[test]
1471 fn test_set_title() {
1472 let mut doc = Document::new();
1473 assert!(doc.metadata.title.is_none());
1474
1475 doc.set_title("Test Document");
1476 assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
1477
1478 doc.set_title(String::from("Another Title"));
1479 assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
1480 }
1481
1482 #[test]
1483 fn test_set_author() {
1484 let mut doc = Document::new();
1485 assert!(doc.metadata.author.is_none());
1486
1487 doc.set_author("John Doe");
1488 assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
1489 }
1490
1491 #[test]
1492 fn test_set_subject() {
1493 let mut doc = Document::new();
1494 assert!(doc.metadata.subject.is_none());
1495
1496 doc.set_subject("Test Subject");
1497 assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
1498 }
1499
1500 #[test]
1501 fn test_set_keywords() {
1502 let mut doc = Document::new();
1503 assert!(doc.metadata.keywords.is_none());
1504
1505 doc.set_keywords("test, pdf, rust");
1506 assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
1507 }
1508
1509 #[test]
1510 fn test_metadata_default() {
1511 let metadata = DocumentMetadata::default();
1512 assert!(metadata.title.is_none());
1513 assert!(metadata.author.is_none());
1514 assert!(metadata.subject.is_none());
1515 assert!(metadata.keywords.is_none());
1516 assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
1517 assert!(metadata
1518 .producer
1519 .as_ref()
1520 .unwrap()
1521 .starts_with("oxidize_pdf"));
1522 }
1523
1524 #[test]
1525 fn test_write_to_buffer() {
1526 let mut doc = Document::new();
1527 doc.set_title("Buffer Test");
1528 doc.add_page(Page::a4());
1529
1530 let mut buffer = Vec::new();
1531 let result = doc.write(&mut buffer);
1532
1533 assert!(result.is_ok());
1534 assert!(!buffer.is_empty());
1535 assert!(buffer.starts_with(b"%PDF-1.7"));
1536 }
1537
1538 #[test]
1539 fn test_document_with_multiple_pages() {
1540 let mut doc = Document::new();
1541 doc.set_title("Multi-page Document");
1542 doc.set_author("Test Author");
1543 doc.set_subject("Testing multiple pages");
1544 doc.set_keywords("test, multiple, pages");
1545
1546 for _ in 0..5 {
1547 doc.add_page(Page::a4());
1548 }
1549
1550 assert_eq!(doc.pages.len(), 5);
1551 assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
1552 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1553 }
1554
1555 #[test]
1556 fn test_empty_document_write() {
1557 let mut doc = Document::new();
1558 let mut buffer = Vec::new();
1559
1560 let result = doc.write(&mut buffer);
1562 assert!(result.is_ok());
1563 assert!(!buffer.is_empty());
1564 assert!(buffer.starts_with(b"%PDF-1.7"));
1565 }
1566
1567 mod integration_tests {
1569 use super::*;
1570 use crate::graphics::Color;
1571 use crate::text::Font;
1572 use std::fs;
1573 use tempfile::TempDir;
1574
1575 #[test]
1576 fn test_document_writer_roundtrip() {
1577 let temp_dir = TempDir::new().unwrap();
1578 let file_path = temp_dir.path().join("test.pdf");
1579
1580 let mut doc = Document::new();
1582 doc.set_title("Integration Test");
1583 doc.set_author("Test Author");
1584 doc.set_subject("Writer Integration");
1585 doc.set_keywords("test, writer, integration");
1586
1587 let mut page = Page::a4();
1588 page.text()
1589 .set_font(Font::Helvetica, 12.0)
1590 .at(100.0, 700.0)
1591 .write("Integration Test Content")
1592 .unwrap();
1593
1594 doc.add_page(page);
1595
1596 let result = doc.save(&file_path);
1598 assert!(result.is_ok());
1599
1600 assert!(file_path.exists());
1602 let metadata = fs::metadata(&file_path).unwrap();
1603 assert!(metadata.len() > 0);
1604
1605 let content = fs::read(&file_path).unwrap();
1607 assert!(content.starts_with(b"%PDF-1.7"));
1608 assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
1610 }
1611
1612 #[test]
1613 fn test_document_with_complex_content() {
1614 let temp_dir = TempDir::new().unwrap();
1615 let file_path = temp_dir.path().join("complex.pdf");
1616
1617 let mut doc = Document::new();
1618 doc.set_title("Complex Content Test");
1619
1620 let mut page = Page::a4();
1622
1623 page.text()
1625 .set_font(Font::Helvetica, 14.0)
1626 .at(50.0, 750.0)
1627 .write("Complex Content Test")
1628 .unwrap();
1629
1630 page.graphics()
1632 .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
1633 .rectangle(50.0, 500.0, 200.0, 100.0)
1634 .fill();
1635
1636 page.graphics()
1637 .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
1638 .set_line_width(2.0)
1639 .move_to(50.0, 400.0)
1640 .line_to(250.0, 400.0)
1641 .stroke();
1642
1643 doc.add_page(page);
1644
1645 let result = doc.save(&file_path);
1647 assert!(result.is_ok());
1648 assert!(file_path.exists());
1649 }
1650
1651 #[test]
1652 fn test_document_multiple_pages_integration() {
1653 let temp_dir = TempDir::new().unwrap();
1654 let file_path = temp_dir.path().join("multipage.pdf");
1655
1656 let mut doc = Document::new();
1657 doc.set_title("Multi-page Integration Test");
1658
1659 for i in 1..=5 {
1661 let mut page = Page::a4();
1662
1663 page.text()
1664 .set_font(Font::Helvetica, 16.0)
1665 .at(50.0, 750.0)
1666 .write(&format!("Page {i}"))
1667 .unwrap();
1668
1669 page.text()
1670 .set_font(Font::Helvetica, 12.0)
1671 .at(50.0, 700.0)
1672 .write(&format!("This is the content for page {i}"))
1673 .unwrap();
1674
1675 let color = match i % 3 {
1677 0 => Color::rgb(1.0, 0.0, 0.0),
1678 1 => Color::rgb(0.0, 1.0, 0.0),
1679 _ => Color::rgb(0.0, 0.0, 1.0),
1680 };
1681
1682 page.graphics()
1683 .set_fill_color(color)
1684 .rectangle(50.0, 600.0, 100.0, 50.0)
1685 .fill();
1686
1687 doc.add_page(page);
1688 }
1689
1690 let result = doc.save(&file_path);
1692 assert!(result.is_ok());
1693 assert!(file_path.exists());
1694
1695 let metadata = fs::metadata(&file_path).unwrap();
1697 assert!(metadata.len() > 1000); }
1699
1700 #[test]
1701 fn test_document_metadata_persistence() {
1702 let temp_dir = TempDir::new().unwrap();
1703 let file_path = temp_dir.path().join("metadata.pdf");
1704
1705 let mut doc = Document::new();
1706 doc.set_title("Metadata Persistence Test");
1707 doc.set_author("Test Author");
1708 doc.set_subject("Testing metadata preservation");
1709 doc.set_keywords("metadata, persistence, test");
1710
1711 doc.add_page(Page::a4());
1712
1713 let result = doc.save(&file_path);
1715 assert!(result.is_ok());
1716
1717 let content = fs::read(&file_path).unwrap();
1719 let content_str = String::from_utf8_lossy(&content);
1720
1721 assert!(content_str.contains("Metadata Persistence Test"));
1723 assert!(content_str.contains("Test Author"));
1724 }
1725
1726 #[test]
1727 fn test_document_writer_error_handling() {
1728 let mut doc = Document::new();
1729 doc.add_page(Page::a4());
1730
1731 let result = doc.save("/invalid/path/test.pdf");
1733 assert!(result.is_err());
1734 }
1735
1736 #[test]
1737 fn test_document_page_integration() {
1738 let mut doc = Document::new();
1739
1740 let page1 = Page::a4();
1742 let page2 = Page::letter();
1743 let mut page3 = Page::new(500.0, 400.0);
1744
1745 page3
1747 .text()
1748 .set_font(Font::Helvetica, 10.0)
1749 .at(25.0, 350.0)
1750 .write("Custom size page")
1751 .unwrap();
1752
1753 doc.add_page(page1);
1754 doc.add_page(page2);
1755 doc.add_page(page3);
1756
1757 assert_eq!(doc.pages.len(), 3);
1758
1759 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); }
1767
1768 #[test]
1769 fn test_document_content_generation() {
1770 let temp_dir = TempDir::new().unwrap();
1771 let file_path = temp_dir.path().join("content.pdf");
1772
1773 let mut doc = Document::new();
1774 doc.set_title("Content Generation Test");
1775
1776 let mut page = Page::a4();
1777
1778 for i in 0..10 {
1780 let y_pos = 700.0 - (i as f64 * 30.0);
1781 page.text()
1782 .set_font(Font::Helvetica, 12.0)
1783 .at(50.0, y_pos)
1784 .write(&format!("Generated line {}", i + 1))
1785 .unwrap();
1786 }
1787
1788 doc.add_page(page);
1789
1790 let result = doc.save(&file_path);
1792 assert!(result.is_ok());
1793 assert!(file_path.exists());
1794
1795 let metadata = fs::metadata(&file_path).unwrap();
1797 assert!(metadata.len() > 500); }
1799
1800 #[test]
1801 fn test_document_buffer_vs_file_write() {
1802 let temp_dir = TempDir::new().unwrap();
1803 let file_path = temp_dir.path().join("buffer_vs_file.pdf");
1804
1805 let mut doc = Document::new();
1806 doc.set_title("Buffer vs File Test");
1807 doc.add_page(Page::a4());
1808
1809 let mut buffer = Vec::new();
1811 let buffer_result = doc.write(&mut buffer);
1812 assert!(buffer_result.is_ok());
1813
1814 let file_result = doc.save(&file_path);
1816 assert!(file_result.is_ok());
1817
1818 let file_content = fs::read(&file_path).unwrap();
1820
1821 assert!(buffer.starts_with(b"%PDF-1.7"));
1823 assert!(file_content.starts_with(b"%PDF-1.7"));
1824 assert!(buffer.ends_with(b"%%EOF\n"));
1825 assert!(file_content.ends_with(b"%%EOF\n"));
1826
1827 let buffer_str = String::from_utf8_lossy(&buffer);
1829 let file_str = String::from_utf8_lossy(&file_content);
1830 assert!(buffer_str.contains("Buffer vs File Test"));
1831 assert!(file_str.contains("Buffer vs File Test"));
1832 }
1833
1834 #[test]
1835 fn test_document_large_content_handling() {
1836 let temp_dir = TempDir::new().unwrap();
1837 let file_path = temp_dir.path().join("large_content.pdf");
1838
1839 let mut doc = Document::new();
1840 doc.set_title("Large Content Test");
1841
1842 let mut page = Page::a4();
1843
1844 let large_text =
1846 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(200);
1847 page.text()
1848 .set_font(Font::Helvetica, 10.0)
1849 .at(50.0, 750.0)
1850 .write(&large_text)
1851 .unwrap();
1852
1853 doc.add_page(page);
1854
1855 let result = doc.save(&file_path);
1857 assert!(result.is_ok());
1858 assert!(file_path.exists());
1859
1860 let metadata = fs::metadata(&file_path).unwrap();
1862 assert!(metadata.len() > 500); }
1864
1865 #[test]
1866 fn test_document_incremental_building() {
1867 let temp_dir = TempDir::new().unwrap();
1868 let file_path = temp_dir.path().join("incremental.pdf");
1869
1870 let mut doc = Document::new();
1871
1872 doc.set_title("Incremental Building Test");
1874
1875 let mut page1 = Page::a4();
1877 page1
1878 .text()
1879 .set_font(Font::Helvetica, 12.0)
1880 .at(50.0, 750.0)
1881 .write("First page content")
1882 .unwrap();
1883 doc.add_page(page1);
1884
1885 doc.set_author("Incremental Author");
1887 doc.set_subject("Incremental Subject");
1888
1889 let mut page2 = Page::a4();
1891 page2
1892 .text()
1893 .set_font(Font::Helvetica, 12.0)
1894 .at(50.0, 750.0)
1895 .write("Second page content")
1896 .unwrap();
1897 doc.add_page(page2);
1898
1899 doc.set_keywords("incremental, building, test");
1901
1902 let result = doc.save(&file_path);
1904 assert!(result.is_ok());
1905 assert!(file_path.exists());
1906
1907 assert_eq!(doc.pages.len(), 2);
1909 assert_eq!(
1910 doc.metadata.title,
1911 Some("Incremental Building Test".to_string())
1912 );
1913 assert_eq!(doc.metadata.author, Some("Incremental Author".to_string()));
1914 assert_eq!(
1915 doc.metadata.subject,
1916 Some("Incremental Subject".to_string())
1917 );
1918 assert_eq!(
1919 doc.metadata.keywords,
1920 Some("incremental, building, test".to_string())
1921 );
1922 }
1923
1924 #[test]
1925 fn test_document_concurrent_page_operations() {
1926 let mut doc = Document::new();
1927 doc.set_title("Concurrent Operations Test");
1928
1929 let mut pages = Vec::new();
1931
1932 for i in 0..5 {
1934 let mut page = Page::a4();
1935 page.text()
1936 .set_font(Font::Helvetica, 12.0)
1937 .at(50.0, 750.0)
1938 .write(&format!("Concurrent page {i}"))
1939 .unwrap();
1940 pages.push(page);
1941 }
1942
1943 for page in pages {
1945 doc.add_page(page);
1946 }
1947
1948 assert_eq!(doc.pages.len(), 5);
1949
1950 let temp_dir = TempDir::new().unwrap();
1952 let file_path = temp_dir.path().join("concurrent.pdf");
1953 let result = doc.save(&file_path);
1954 assert!(result.is_ok());
1955 }
1956
1957 #[test]
1958 fn test_document_memory_efficiency() {
1959 let mut doc = Document::new();
1960 doc.set_title("Memory Efficiency Test");
1961
1962 for i in 0..10 {
1964 let mut page = Page::a4();
1965 page.text()
1966 .set_font(Font::Helvetica, 12.0)
1967 .at(50.0, 700.0)
1968 .write(&format!("Memory test page {i}"))
1969 .unwrap();
1970 doc.add_page(page);
1971 }
1972
1973 let mut buffer = Vec::new();
1975 let result = doc.write(&mut buffer);
1976 assert!(result.is_ok());
1977 assert!(!buffer.is_empty());
1978
1979 assert!(buffer.len() < 1_000_000); }
1982
1983 #[test]
1984 fn test_document_creator_producer() {
1985 let mut doc = Document::new();
1986
1987 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1989 assert!(doc
1990 .metadata
1991 .producer
1992 .as_ref()
1993 .unwrap()
1994 .contains("oxidize_pdf"));
1995
1996 doc.set_creator("My Application");
1998 doc.set_producer("My PDF Library v1.0");
1999
2000 assert_eq!(doc.metadata.creator, Some("My Application".to_string()));
2001 assert_eq!(
2002 doc.metadata.producer,
2003 Some("My PDF Library v1.0".to_string())
2004 );
2005 }
2006
2007 #[test]
2008 fn test_document_dates() {
2009 use chrono::{TimeZone, Utc};
2010
2011 let mut doc = Document::new();
2012
2013 assert!(doc.metadata.creation_date.is_some());
2015 assert!(doc.metadata.modification_date.is_some());
2016
2017 let creation_date = Utc.with_ymd_and_hms(2023, 1, 1, 12, 0, 0).unwrap();
2019 let mod_date = Utc.with_ymd_and_hms(2023, 6, 15, 18, 30, 0).unwrap();
2020
2021 doc.set_creation_date(creation_date);
2022 doc.set_modification_date(mod_date);
2023
2024 assert_eq!(doc.metadata.creation_date, Some(creation_date));
2025 assert_eq!(doc.metadata.modification_date, Some(mod_date));
2026 }
2027
2028 #[test]
2029 fn test_document_dates_local() {
2030 use chrono::{Local, TimeZone};
2031
2032 let mut doc = Document::new();
2033
2034 let local_date = Local.with_ymd_and_hms(2023, 12, 25, 10, 30, 0).unwrap();
2036 doc.set_creation_date_local(local_date);
2037
2038 assert!(doc.metadata.creation_date.is_some());
2040 assert!(doc.metadata.creation_date.is_some());
2042 }
2043
2044 #[test]
2045 fn test_update_modification_date() {
2046 let mut doc = Document::new();
2047
2048 let initial_mod_date = doc.metadata.modification_date;
2049 assert!(initial_mod_date.is_some());
2050
2051 std::thread::sleep(std::time::Duration::from_millis(10));
2053
2054 doc.update_modification_date();
2055
2056 let new_mod_date = doc.metadata.modification_date;
2057 assert!(new_mod_date.is_some());
2058 assert!(new_mod_date.unwrap() > initial_mod_date.unwrap());
2059 }
2060
2061 #[test]
2062 fn test_document_save_updates_modification_date() {
2063 let temp_dir = TempDir::new().unwrap();
2064 let file_path = temp_dir.path().join("mod_date_test.pdf");
2065
2066 let mut doc = Document::new();
2067 doc.add_page(Page::a4());
2068
2069 let initial_mod_date = doc.metadata.modification_date;
2070
2071 std::thread::sleep(std::time::Duration::from_millis(10));
2073
2074 doc.save(&file_path).unwrap();
2075
2076 assert!(doc.metadata.modification_date.unwrap() > initial_mod_date.unwrap());
2078 }
2079
2080 #[test]
2081 fn test_document_metadata_complete() {
2082 let mut doc = Document::new();
2083
2084 doc.set_title("Complete Metadata Test");
2086 doc.set_author("Test Author");
2087 doc.set_subject("Testing all metadata fields");
2088 doc.set_keywords("test, metadata, complete");
2089 doc.set_creator("Test Application v1.0");
2090 doc.set_producer("oxidize_pdf Test Suite");
2091
2092 assert_eq!(
2094 doc.metadata.title,
2095 Some("Complete Metadata Test".to_string())
2096 );
2097 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
2098 assert_eq!(
2099 doc.metadata.subject,
2100 Some("Testing all metadata fields".to_string())
2101 );
2102 assert_eq!(
2103 doc.metadata.keywords,
2104 Some("test, metadata, complete".to_string())
2105 );
2106 assert_eq!(
2107 doc.metadata.creator,
2108 Some("Test Application v1.0".to_string())
2109 );
2110 assert_eq!(
2111 doc.metadata.producer,
2112 Some("oxidize_pdf Test Suite".to_string())
2113 );
2114 assert!(doc.metadata.creation_date.is_some());
2115 assert!(doc.metadata.modification_date.is_some());
2116 }
2117
2118 #[test]
2119 fn test_document_to_bytes() {
2120 let mut doc = Document::new();
2121 doc.set_title("Test Document");
2122 doc.set_author("Test Author");
2123
2124 let page = Page::a4();
2125 doc.add_page(page);
2126
2127 let pdf_bytes = doc.to_bytes().unwrap();
2129
2130 assert!(!pdf_bytes.is_empty());
2132 assert!(pdf_bytes.len() > 100); let header = &pdf_bytes[0..5];
2136 assert_eq!(header, b"%PDF-");
2137
2138 let pdf_str = String::from_utf8_lossy(&pdf_bytes);
2140 assert!(pdf_str.contains("Test Document"));
2141 assert!(pdf_str.contains("Test Author"));
2142 }
2143
2144 #[test]
2145 fn test_document_to_bytes_with_config() {
2146 let mut doc = Document::new();
2147 doc.set_title("Test Document XRef");
2148
2149 let page = Page::a4();
2150 doc.add_page(page);
2151
2152 let config = crate::writer::WriterConfig {
2153 use_xref_streams: true,
2154 use_object_streams: false,
2155 pdf_version: "1.5".to_string(),
2156 compress_streams: true,
2157 incremental_update: false,
2158 };
2159
2160 let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
2162
2163 assert!(!pdf_bytes.is_empty());
2165 assert!(pdf_bytes.len() > 100);
2166
2167 let header = String::from_utf8_lossy(&pdf_bytes[0..8]);
2169 assert!(header.contains("PDF-1.5"));
2170 }
2171
2172 #[test]
2173 fn test_to_bytes_vs_save_equivalence() {
2174 use std::fs;
2175 use tempfile::NamedTempFile;
2176
2177 let mut doc1 = Document::new();
2179 doc1.set_title("Equivalence Test");
2180 doc1.add_page(Page::a4());
2181
2182 let mut doc2 = Document::new();
2183 doc2.set_title("Equivalence Test");
2184 doc2.add_page(Page::a4());
2185
2186 let pdf_bytes = doc1.to_bytes().unwrap();
2188
2189 let temp_file = NamedTempFile::new().unwrap();
2191 doc2.save(temp_file.path()).unwrap();
2192 let file_bytes = fs::read(temp_file.path()).unwrap();
2193
2194 assert!(!pdf_bytes.is_empty());
2196 assert!(!file_bytes.is_empty());
2197 assert_eq!(&pdf_bytes[0..5], &file_bytes[0..5]); }
2199
2200 #[test]
2201 fn test_document_set_compress() {
2202 let mut doc = Document::new();
2203 doc.set_title("Compression Test");
2204 doc.add_page(Page::a4());
2205
2206 assert!(doc.get_compress());
2208
2209 doc.set_compress(true);
2211 let compressed_bytes = doc.to_bytes().unwrap();
2212
2213 doc.set_compress(false);
2215 let uncompressed_bytes = doc.to_bytes().unwrap();
2216
2217 assert!(!compressed_bytes.is_empty());
2219 assert!(!uncompressed_bytes.is_empty());
2220
2221 assert_eq!(&compressed_bytes[0..5], b"%PDF-");
2223 assert_eq!(&uncompressed_bytes[0..5], b"%PDF-");
2224 }
2225
2226 #[test]
2227 fn test_document_compression_config_inheritance() {
2228 let mut doc = Document::new();
2229 doc.set_title("Config Inheritance Test");
2230 doc.add_page(Page::a4());
2231
2232 doc.set_compress(false);
2234
2235 let config = crate::writer::WriterConfig {
2237 use_xref_streams: false,
2238 use_object_streams: false,
2239 pdf_version: "1.7".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_eq!(&pdf_bytes[0..5], b"%PDF-");
2250 }
2251
2252 #[test]
2253 fn test_document_metadata_all_fields() {
2254 let mut doc = Document::new();
2255
2256 doc.set_title("Test Document");
2258 doc.set_author("John Doe");
2259 doc.set_subject("Testing PDF metadata");
2260 doc.set_keywords("test, pdf, metadata");
2261 doc.set_creator("Test Suite");
2262 doc.set_producer("oxidize_pdf tests");
2263
2264 assert_eq!(doc.metadata.title.as_deref(), Some("Test Document"));
2266 assert_eq!(doc.metadata.author.as_deref(), Some("John Doe"));
2267 assert_eq!(
2268 doc.metadata.subject.as_deref(),
2269 Some("Testing PDF metadata")
2270 );
2271 assert_eq!(
2272 doc.metadata.keywords.as_deref(),
2273 Some("test, pdf, metadata")
2274 );
2275 assert_eq!(doc.metadata.creator.as_deref(), Some("Test Suite"));
2276 assert_eq!(doc.metadata.producer.as_deref(), Some("oxidize_pdf tests"));
2277 assert!(doc.metadata.creation_date.is_some());
2278 assert!(doc.metadata.modification_date.is_some());
2279 }
2280
2281 #[test]
2282 fn test_document_add_pages() {
2283 let mut doc = Document::new();
2284
2285 assert_eq!(doc.page_count(), 0);
2287
2288 let page1 = Page::a4();
2290 let page2 = Page::letter();
2291 let page3 = Page::legal();
2292
2293 doc.add_page(page1);
2294 assert_eq!(doc.page_count(), 1);
2295
2296 doc.add_page(page2);
2297 assert_eq!(doc.page_count(), 2);
2298
2299 doc.add_page(page3);
2300 assert_eq!(doc.page_count(), 3);
2301
2302 let result = doc.to_bytes();
2304 assert!(result.is_ok());
2305 }
2306
2307 #[test]
2308 fn test_document_default_font_encoding() {
2309 let mut doc = Document::new();
2310
2311 assert!(doc.default_font_encoding.is_none());
2313
2314 doc.set_default_font_encoding(Some(FontEncoding::WinAnsiEncoding));
2316 assert_eq!(
2317 doc.default_font_encoding(),
2318 Some(FontEncoding::WinAnsiEncoding)
2319 );
2320
2321 doc.set_default_font_encoding(Some(FontEncoding::MacRomanEncoding));
2323 assert_eq!(
2324 doc.default_font_encoding(),
2325 Some(FontEncoding::MacRomanEncoding)
2326 );
2327 }
2328
2329 #[test]
2330 fn test_document_compression_setting() {
2331 let mut doc = Document::new();
2332
2333 assert!(doc.compress);
2335
2336 doc.set_compress(false);
2338 assert!(!doc.compress);
2339
2340 doc.set_compress(true);
2342 assert!(doc.compress);
2343 }
2344
2345 #[test]
2346 fn test_document_with_empty_pages() {
2347 let mut doc = Document::new();
2348
2349 doc.add_page(Page::a4());
2351
2352 let result = doc.to_bytes();
2354 assert!(result.is_ok());
2355
2356 let pdf_bytes = result.unwrap();
2357 assert!(!pdf_bytes.is_empty());
2358 assert!(pdf_bytes.starts_with(b"%PDF-"));
2359 }
2360
2361 #[test]
2362 fn test_document_with_multiple_page_sizes() {
2363 let mut doc = Document::new();
2364
2365 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);
2373
2374 let result = doc.to_bytes();
2378 assert!(result.is_ok());
2379 }
2380
2381 #[test]
2382 fn test_document_metadata_dates() {
2383 use chrono::Duration;
2384
2385 let doc = Document::new();
2386
2387 assert!(doc.metadata.creation_date.is_some());
2389 assert!(doc.metadata.modification_date.is_some());
2390
2391 if let (Some(created), Some(modified)) =
2392 (doc.metadata.creation_date, doc.metadata.modification_date)
2393 {
2394 let diff = modified - created;
2396 assert!(diff < Duration::seconds(1));
2397 }
2398 }
2399
2400 #[test]
2401 fn test_document_builder_pattern() {
2402 let mut doc = Document::new();
2404 doc.set_title("Fluent");
2405 doc.set_author("Builder");
2406 doc.set_compress(true);
2407
2408 assert_eq!(doc.metadata.title.as_deref(), Some("Fluent"));
2409 assert_eq!(doc.metadata.author.as_deref(), Some("Builder"));
2410 assert!(doc.compress);
2411 }
2412
2413 #[test]
2414 fn test_xref_streams_functionality() {
2415 use crate::{Document, Font, Page};
2416
2417 let mut doc = Document::new();
2419 assert!(!doc.use_xref_streams);
2420
2421 let mut page = Page::a4();
2422 page.text()
2423 .set_font(Font::Helvetica, 12.0)
2424 .at(100.0, 700.0)
2425 .write("Testing XRef Streams")
2426 .unwrap();
2427
2428 doc.add_page(page);
2429
2430 let pdf_without_xref = doc.to_bytes().unwrap();
2432
2433 let pdf_str = String::from_utf8_lossy(&pdf_without_xref);
2435 assert!(pdf_str.contains("xref"), "Traditional xref table not found");
2436 assert!(
2437 !pdf_str.contains("/Type /XRef"),
2438 "XRef stream found when it shouldn't be"
2439 );
2440
2441 doc.enable_xref_streams(true);
2443 assert!(doc.use_xref_streams);
2444
2445 let pdf_with_xref = doc.to_bytes().unwrap();
2447
2448 let pdf_str = String::from_utf8_lossy(&pdf_with_xref);
2450 assert!(
2452 pdf_str.contains("/Type /XRef") || pdf_str.contains("stream"),
2453 "XRef stream not found when enabled"
2454 );
2455
2456 assert!(
2458 pdf_str.contains("PDF-1.5"),
2459 "PDF version not set to 1.5 for xref streams"
2460 );
2461
2462 let mut doc2 = Document::new();
2464 doc2.enable_xref_streams(true);
2465 doc2.set_title("XRef Streams Test");
2466 doc2.set_author("oxidize-pdf");
2467
2468 assert!(doc2.use_xref_streams);
2469 assert_eq!(doc2.metadata.title.as_deref(), Some("XRef Streams Test"));
2470 assert_eq!(doc2.metadata.author.as_deref(), Some("oxidize-pdf"));
2471 }
2472
2473 #[test]
2474 fn test_document_save_to_vec() {
2475 let mut doc = Document::new();
2476 doc.set_title("Test Save");
2477 doc.add_page(Page::a4());
2478
2479 let bytes_result = doc.to_bytes();
2481 assert!(bytes_result.is_ok());
2482
2483 let bytes = bytes_result.unwrap();
2484 assert!(!bytes.is_empty());
2485 assert!(bytes.starts_with(b"%PDF-"));
2486 assert!(bytes.ends_with(b"%%EOF") || bytes.ends_with(b"%%EOF\n"));
2487 }
2488
2489 #[test]
2490 fn test_document_unicode_metadata() {
2491 let mut doc = Document::new();
2492
2493 doc.set_title("日本語のタイトル");
2495 doc.set_author("作者名 😀");
2496 doc.set_subject("Тема документа");
2497 doc.set_keywords("كلمات, מפתח, 关键词");
2498
2499 assert_eq!(doc.metadata.title.as_deref(), Some("日本語のタイトル"));
2500 assert_eq!(doc.metadata.author.as_deref(), Some("作者名 😀"));
2501 assert_eq!(doc.metadata.subject.as_deref(), Some("Тема документа"));
2502 assert_eq!(
2503 doc.metadata.keywords.as_deref(),
2504 Some("كلمات, מפתח, 关键词")
2505 );
2506 }
2507
2508 #[test]
2509 fn test_document_page_iteration() {
2510 let mut doc = Document::new();
2511
2512 for i in 0..5 {
2514 let mut page = Page::a4();
2515 let gc = page.graphics();
2516 gc.begin_text();
2517 let _ = gc.show_text(&format!("Page {}", i + 1));
2518 gc.end_text();
2519 doc.add_page(page);
2520 }
2521
2522 assert_eq!(doc.page_count(), 5);
2524
2525 let result = doc.to_bytes();
2527 assert!(result.is_ok());
2528 }
2529
2530 #[test]
2531 fn test_document_with_graphics_content() {
2532 let mut doc = Document::new();
2533
2534 let mut page = Page::a4();
2535 {
2536 let gc = page.graphics();
2537
2538 gc.save_state();
2540
2541 gc.rectangle(100.0, 100.0, 200.0, 150.0);
2543 gc.stroke();
2544
2545 gc.move_to(300.0, 300.0);
2547 gc.circle(300.0, 300.0, 50.0);
2548 gc.fill();
2549
2550 gc.begin_text();
2552 gc.set_text_position(100.0, 500.0);
2553 let _ = gc.show_text("Graphics Test");
2554 gc.end_text();
2555
2556 gc.restore_state();
2557 }
2558
2559 doc.add_page(page);
2560
2561 let result = doc.to_bytes();
2563 assert!(result.is_ok());
2564 }
2565
2566 #[test]
2567 fn test_document_producer_version() {
2568 let doc = Document::new();
2569
2570 assert!(doc.metadata.producer.is_some());
2572 if let Some(producer) = &doc.metadata.producer {
2573 assert!(producer.contains("oxidize_pdf"));
2574 assert!(producer.contains(env!("CARGO_PKG_VERSION")));
2575 }
2576 }
2577
2578 #[test]
2579 fn test_document_empty_metadata_fields() {
2580 let mut doc = Document::new();
2581
2582 doc.set_title("");
2584 doc.set_author("");
2585 doc.set_subject("");
2586 doc.set_keywords("");
2587
2588 assert_eq!(doc.metadata.title.as_deref(), Some(""));
2590 assert_eq!(doc.metadata.author.as_deref(), Some(""));
2591 assert_eq!(doc.metadata.subject.as_deref(), Some(""));
2592 assert_eq!(doc.metadata.keywords.as_deref(), Some(""));
2593 }
2594
2595 #[test]
2596 fn test_document_very_long_metadata() {
2597 let mut doc = Document::new();
2598
2599 let long_title = "A".repeat(1000);
2601 let long_author = "B".repeat(500);
2602 let long_keywords = vec!["keyword"; 100].join(", ");
2603
2604 doc.set_title(&long_title);
2605 doc.set_author(&long_author);
2606 doc.set_keywords(&long_keywords);
2607
2608 assert_eq!(doc.metadata.title.as_deref(), Some(long_title.as_str()));
2609 assert_eq!(doc.metadata.author.as_deref(), Some(long_author.as_str()));
2610 assert!(doc.metadata.keywords.as_ref().unwrap().len() > 500);
2611 }
2612 }
2613
2614 #[test]
2615 fn test_add_font_from_bytes_writes_to_per_document_store_not_global() {
2616 let unique = format!("PerDocTask9_{}", std::process::id());
2618 #[allow(deprecated)]
2622 let before = crate::text::metrics::get_custom_font_metrics(&unique);
2623 assert!(before.is_none(), "precondition: name not in global");
2624
2625 let doc = Document::new();
2630 doc.font_metrics
2631 .register(unique.clone(), crate::text::metrics::FontMetrics::new(500));
2632
2633 assert!(doc.font_metrics.get(&unique).is_some());
2635
2636 #[allow(deprecated)]
2638 let after = crate::text::metrics::get_custom_font_metrics(&unique);
2639 assert!(after.is_none(), "global must remain untouched");
2640 }
2641
2642 #[test]
2643 fn test_new_page_a4_returns_page_bound_to_document_store() {
2644 let doc = Document::new();
2645 doc.font_metrics
2646 .register("Sentinel", crate::text::metrics::FontMetrics::new(400));
2647
2648 let page = doc.new_page_a4();
2649 assert!(page.font_metrics_store.is_some());
2650 let store = page.font_metrics_store.as_ref().unwrap();
2651 assert!(
2652 store.get("Sentinel").is_some(),
2653 "store must share with Document"
2654 );
2655 }
2656
2657 #[test]
2658 fn test_new_page_letter_and_new_page_carry_store() {
2659 let doc = Document::new();
2660 doc.font_metrics
2661 .register("S", crate::text::metrics::FontMetrics::new(400));
2662 assert!(doc.new_page_letter().font_metrics_store.is_some());
2663 assert!(doc.new_page(400.0, 600.0).font_metrics_store.is_some());
2664 }
2665
2666 #[test]
2667 fn test_add_page_injects_store_into_legacy_page() {
2668 let mut doc = Document::new();
2669 doc.font_metrics
2670 .register("Inj", crate::text::metrics::FontMetrics::new(400));
2671
2672 let page = Page::a4(); assert!(page.font_metrics_store.is_none());
2674
2675 doc.add_page(page);
2676
2677 let stored_page = doc.pages.last().expect("page added");
2678 assert!(
2679 stored_page.font_metrics_store.is_some(),
2680 "add_page must inject the Document store when page has none"
2681 );
2682 assert!(
2683 stored_page
2684 .font_metrics_store
2685 .as_ref()
2686 .unwrap()
2687 .get("Inj")
2688 .is_some(),
2689 "injected store must share state with the Document"
2690 );
2691 }
2692
2693 #[test]
2694 fn test_add_page_does_not_overwrite_existing_store() {
2695 let doc_a = Document::new();
2696 doc_a
2697 .font_metrics
2698 .register("FromA", crate::text::metrics::FontMetrics::new(400));
2699 let page = doc_a.new_page_a4(); let mut doc_b = Document::new();
2702 doc_b
2703 .font_metrics
2704 .register("FromB", crate::text::metrics::FontMetrics::new(500));
2705 doc_b.add_page(page);
2706
2707 let stored_page = doc_b.pages.last().expect("page added");
2708 let store = stored_page.font_metrics_store.as_ref().unwrap();
2709 assert!(store.get("FromA").is_some(), "page kept doc_a's store");
2710 assert!(store.get("FromB").is_none(), "doc_b did not overwrite");
2711 }
2712}