Skip to main content

oxidize_pdf/
document.rs

1use crate::error::Result;
2use crate::fonts::{Font as CustomFont, FontCache};
3use crate::forms::{AcroForm, FormManager};
4use crate::page::Page;
5use crate::page_labels::PageLabelTree;
6use crate::semantic::{BoundingBox, EntityType, RelationType, SemanticEntity};
7use crate::structure::{NamedDestinations, OutlineTree, StructTree};
8use crate::text::FontEncoding;
9use crate::writer::PdfWriter;
10use chrono::{DateTime, Local, Utc};
11use std::collections::HashSet;
12use std::sync::Arc;
13
14mod encryption;
15pub use encryption::{DocumentEncryption, EncryptionStrength};
16
17/// A PDF document that can contain multiple pages and metadata.
18///
19/// # Example
20///
21/// ```rust
22/// use oxidize_pdf::{Document, Page};
23///
24/// let mut doc = Document::new();
25/// doc.set_title("My Document");
26/// doc.set_author("John Doe");
27///
28/// let page = Page::a4();
29/// doc.add_page(page);
30///
31/// doc.save("output.pdf").unwrap();
32/// ```
33pub struct Document {
34    pub(crate) pages: Vec<Page>,
35    pub(crate) metadata: DocumentMetadata,
36    pub(crate) encryption: Option<DocumentEncryption>,
37    pub(crate) outline: Option<OutlineTree>,
38    pub(crate) named_destinations: Option<NamedDestinations>,
39    pub(crate) page_labels: Option<PageLabelTree>,
40    /// Default font encoding to use for fonts when no encoding is specified
41    pub(crate) default_font_encoding: Option<FontEncoding>,
42    /// Interactive form data (AcroForm)
43    pub(crate) acro_form: Option<AcroForm>,
44    /// Form manager for handling interactive forms
45    pub(crate) form_manager: Option<FormManager>,
46    /// Whether to compress streams when writing the PDF
47    pub(crate) compress: bool,
48    /// Whether to use compressed cross-reference streams (PDF 1.5+)
49    pub(crate) use_xref_streams: bool,
50    /// Cache for custom fonts
51    pub(crate) custom_fonts: FontCache,
52    /// Characters used in the document (for font subsetting)
53    pub(crate) used_characters: HashSet<char>,
54    /// Action to execute when the document is opened
55    pub(crate) open_action: Option<crate::actions::Action>,
56    /// Viewer preferences for controlling document display
57    pub(crate) viewer_preferences: Option<crate::viewer_preferences::ViewerPreferences>,
58    /// Semantic entities marked in the document for AI processing
59    pub(crate) semantic_entities: Vec<SemanticEntity>,
60    /// Document structure tree for Tagged PDF (accessibility)
61    pub(crate) struct_tree: Option<StructTree>,
62}
63
64/// Metadata for a PDF document.
65#[derive(Debug, Clone)]
66pub struct DocumentMetadata {
67    /// Document title
68    pub title: Option<String>,
69    /// Document author
70    pub author: Option<String>,
71    /// Document subject
72    pub subject: Option<String>,
73    /// Document keywords
74    pub keywords: Option<String>,
75    /// Software that created the original document
76    pub creator: Option<String>,
77    /// Software that produced the PDF
78    pub producer: Option<String>,
79    /// Date and time the document was created
80    pub creation_date: Option<DateTime<Utc>>,
81    /// Date and time the document was last modified
82    pub modification_date: Option<DateTime<Utc>>,
83}
84
85impl Default for DocumentMetadata {
86    fn default() -> Self {
87        let now = Utc::now();
88
89        // Determine edition string based on features
90        let edition = if cfg!(feature = "pro") {
91            "PRO Edition"
92        } else if cfg!(feature = "enterprise") {
93            "Enterprise Edition"
94        } else {
95            "Community Edition"
96        };
97
98        Self {
99            title: None,
100            author: None,
101            subject: None,
102            keywords: None,
103            creator: Some("oxidize_pdf".to_string()),
104            producer: Some(format!(
105                "oxidize_pdf v{} ({})",
106                env!("CARGO_PKG_VERSION"),
107                edition
108            )),
109            creation_date: Some(now),
110            modification_date: Some(now),
111        }
112    }
113}
114
115impl Document {
116    /// Creates a new empty PDF document.
117    pub fn new() -> Self {
118        Self {
119            pages: Vec::new(),
120            metadata: DocumentMetadata::default(),
121            encryption: None,
122            outline: None,
123            named_destinations: None,
124            page_labels: None,
125            default_font_encoding: None,
126            acro_form: None,
127            form_manager: None,
128            compress: true,          // Enable compression by default
129            use_xref_streams: false, // Disabled by default for compatibility
130            custom_fonts: FontCache::new(),
131            used_characters: HashSet::new(),
132            open_action: None,
133            viewer_preferences: None,
134            semantic_entities: Vec::new(),
135            struct_tree: None,
136        }
137    }
138
139    /// Adds a page to the document.
140    pub fn add_page(&mut self, page: Page) {
141        // Collect used characters from the page
142        if let Some(used_chars) = page.get_used_characters() {
143            self.used_characters.extend(used_chars);
144        }
145        self.pages.push(page);
146    }
147
148    /// Sets the document title.
149    pub fn set_title(&mut self, title: impl Into<String>) {
150        self.metadata.title = Some(title.into());
151    }
152
153    /// Sets the document author.
154    pub fn set_author(&mut self, author: impl Into<String>) {
155        self.metadata.author = Some(author.into());
156    }
157
158    /// Sets the form manager for the document.
159    pub fn set_form_manager(&mut self, form_manager: FormManager) {
160        self.form_manager = Some(form_manager);
161    }
162
163    /// Sets the document subject.
164    pub fn set_subject(&mut self, subject: impl Into<String>) {
165        self.metadata.subject = Some(subject.into());
166    }
167
168    /// Sets the document keywords.
169    pub fn set_keywords(&mut self, keywords: impl Into<String>) {
170        self.metadata.keywords = Some(keywords.into());
171    }
172
173    /// Set document encryption
174    pub fn set_encryption(&mut self, encryption: DocumentEncryption) {
175        self.encryption = Some(encryption);
176    }
177
178    /// Set simple encryption with passwords
179    pub fn encrypt_with_passwords(
180        &mut self,
181        user_password: impl Into<String>,
182        owner_password: impl Into<String>,
183    ) {
184        self.encryption = Some(DocumentEncryption::with_passwords(
185            user_password,
186            owner_password,
187        ));
188    }
189
190    /// Check if document is encrypted
191    pub fn is_encrypted(&self) -> bool {
192        self.encryption.is_some()
193    }
194
195    /// Set the action to execute when the document is opened
196    pub fn set_open_action(&mut self, action: crate::actions::Action) {
197        self.open_action = Some(action);
198    }
199
200    /// Get the document open action
201    pub fn open_action(&self) -> Option<&crate::actions::Action> {
202        self.open_action.as_ref()
203    }
204
205    /// Set viewer preferences for controlling document display
206    pub fn set_viewer_preferences(
207        &mut self,
208        preferences: crate::viewer_preferences::ViewerPreferences,
209    ) {
210        self.viewer_preferences = Some(preferences);
211    }
212
213    /// Get viewer preferences
214    pub fn viewer_preferences(&self) -> Option<&crate::viewer_preferences::ViewerPreferences> {
215        self.viewer_preferences.as_ref()
216    }
217
218    /// Set the document structure tree for Tagged PDF (accessibility)
219    ///
220    /// Tagged PDF provides semantic information about document content,
221    /// making PDFs accessible to screen readers and assistive technologies.
222    ///
223    /// # Example
224    ///
225    /// ```rust,no_run
226    /// use oxidize_pdf::{Document, structure::{StructTree, StructureElement, StandardStructureType}};
227    ///
228    /// let mut doc = Document::new();
229    /// let mut tree = StructTree::new();
230    ///
231    /// // Create document root
232    /// let doc_elem = StructureElement::new(StandardStructureType::Document);
233    /// let doc_idx = tree.set_root(doc_elem);
234    ///
235    /// // Add heading
236    /// let h1 = StructureElement::new(StandardStructureType::H1)
237    ///     .with_language("en-US")
238    ///     .with_actual_text("Welcome");
239    /// tree.add_child(doc_idx, h1).unwrap();
240    ///
241    /// doc.set_struct_tree(tree);
242    /// ```
243    pub fn set_struct_tree(&mut self, tree: StructTree) {
244        self.struct_tree = Some(tree);
245    }
246
247    /// Get a reference to the document structure tree
248    pub fn struct_tree(&self) -> Option<&StructTree> {
249        self.struct_tree.as_ref()
250    }
251
252    /// Get a mutable reference to the document structure tree
253    pub fn struct_tree_mut(&mut self) -> Option<&mut StructTree> {
254        self.struct_tree.as_mut()
255    }
256
257    /// Initialize a new structure tree if one doesn't exist and return a mutable reference
258    ///
259    /// This is a convenience method for adding Tagged PDF support.
260    ///
261    /// # Example
262    ///
263    /// ```rust,no_run
264    /// use oxidize_pdf::{Document, structure::{StructureElement, StandardStructureType}};
265    ///
266    /// let mut doc = Document::new();
267    /// let tree = doc.get_or_create_struct_tree();
268    ///
269    /// // Create document root
270    /// let doc_elem = StructureElement::new(StandardStructureType::Document);
271    /// tree.set_root(doc_elem);
272    /// ```
273    pub fn get_or_create_struct_tree(&mut self) -> &mut StructTree {
274        self.struct_tree.get_or_insert_with(StructTree::new)
275    }
276
277    /// Set document outline (bookmarks)
278    pub fn set_outline(&mut self, outline: OutlineTree) {
279        self.outline = Some(outline);
280    }
281
282    /// Get document outline
283    pub fn outline(&self) -> Option<&OutlineTree> {
284        self.outline.as_ref()
285    }
286
287    /// Get mutable document outline
288    pub fn outline_mut(&mut self) -> Option<&mut OutlineTree> {
289        self.outline.as_mut()
290    }
291
292    /// Set named destinations
293    pub fn set_named_destinations(&mut self, destinations: NamedDestinations) {
294        self.named_destinations = Some(destinations);
295    }
296
297    /// Get named destinations
298    pub fn named_destinations(&self) -> Option<&NamedDestinations> {
299        self.named_destinations.as_ref()
300    }
301
302    /// Get mutable named destinations
303    pub fn named_destinations_mut(&mut self) -> Option<&mut NamedDestinations> {
304        self.named_destinations.as_mut()
305    }
306
307    /// Set page labels
308    pub fn set_page_labels(&mut self, labels: PageLabelTree) {
309        self.page_labels = Some(labels);
310    }
311
312    /// Get page labels
313    pub fn page_labels(&self) -> Option<&PageLabelTree> {
314        self.page_labels.as_ref()
315    }
316
317    /// Get mutable page labels
318    pub fn page_labels_mut(&mut self) -> Option<&mut PageLabelTree> {
319        self.page_labels.as_mut()
320    }
321
322    /// Get page label for a specific page
323    pub fn get_page_label(&self, page_index: u32) -> String {
324        self.page_labels
325            .as_ref()
326            .and_then(|labels| labels.get_label(page_index))
327            .unwrap_or_else(|| (page_index + 1).to_string())
328    }
329
330    /// Get all page labels
331    pub fn get_all_page_labels(&self) -> Vec<String> {
332        let page_count = self.pages.len() as u32;
333        if let Some(labels) = &self.page_labels {
334            labels.get_all_labels(page_count)
335        } else {
336            (1..=page_count).map(|i| i.to_string()).collect()
337        }
338    }
339
340    /// Sets the document creator (software that created the original document).
341    pub fn set_creator(&mut self, creator: impl Into<String>) {
342        self.metadata.creator = Some(creator.into());
343    }
344
345    /// Sets the document producer (software that produced the PDF).
346    pub fn set_producer(&mut self, producer: impl Into<String>) {
347        self.metadata.producer = Some(producer.into());
348    }
349
350    /// Sets the document creation date.
351    pub fn set_creation_date(&mut self, date: DateTime<Utc>) {
352        self.metadata.creation_date = Some(date);
353    }
354
355    /// Sets the document creation date using local time.
356    pub fn set_creation_date_local(&mut self, date: DateTime<Local>) {
357        self.metadata.creation_date = Some(date.with_timezone(&Utc));
358    }
359
360    /// Sets the document modification date.
361    pub fn set_modification_date(&mut self, date: DateTime<Utc>) {
362        self.metadata.modification_date = Some(date);
363    }
364
365    /// Sets the document modification date using local time.
366    pub fn set_modification_date_local(&mut self, date: DateTime<Local>) {
367        self.metadata.modification_date = Some(date.with_timezone(&Utc));
368    }
369
370    /// Sets the modification date to the current time.
371    pub fn update_modification_date(&mut self) {
372        self.metadata.modification_date = Some(Utc::now());
373    }
374
375    /// Sets the default font encoding for fonts that don't specify an encoding.
376    ///
377    /// This encoding will be applied to fonts in the PDF font dictionary when
378    /// no explicit encoding is specified. Setting this to `None` (the default)
379    /// means no encoding metadata will be added to fonts unless explicitly specified.
380    ///
381    /// # Example
382    ///
383    /// ```rust
384    /// use oxidize_pdf::{Document, text::FontEncoding};
385    ///
386    /// let mut doc = Document::new();
387    /// doc.set_default_font_encoding(Some(FontEncoding::WinAnsiEncoding));
388    /// ```
389    pub fn set_default_font_encoding(&mut self, encoding: Option<FontEncoding>) {
390        self.default_font_encoding = encoding;
391    }
392
393    /// Gets the current default font encoding.
394    pub fn default_font_encoding(&self) -> Option<FontEncoding> {
395        self.default_font_encoding
396    }
397
398    /// Add a custom font from a file path
399    ///
400    /// # Example
401    ///
402    /// ```rust,no_run
403    /// use oxidize_pdf::Document;
404    ///
405    /// let mut doc = Document::new();
406    /// doc.add_font("MyFont", "path/to/font.ttf").unwrap();
407    /// ```
408    pub fn add_font(
409        &mut self,
410        name: impl Into<String>,
411        path: impl AsRef<std::path::Path>,
412    ) -> Result<()> {
413        let name = name.into();
414        let font = CustomFont::from_file(&name, path)?;
415        self.custom_fonts.add_font(name, font)?;
416        Ok(())
417    }
418
419    /// Add a custom font from byte data
420    ///
421    /// # Example
422    ///
423    /// ```rust,no_run
424    /// use oxidize_pdf::Document;
425    ///
426    /// let mut doc = Document::new();
427    /// let font_data = vec![0; 1000]; // Your font data
428    /// doc.add_font_from_bytes("MyFont", font_data).unwrap();
429    /// ```
430    pub fn add_font_from_bytes(&mut self, name: impl Into<String>, data: Vec<u8>) -> Result<()> {
431        let name = name.into();
432        let font = CustomFont::from_bytes(&name, data)?;
433
434        // TODO: Implement automatic font metrics registration
435        // This needs to be properly integrated with the font metrics system
436
437        self.custom_fonts.add_font(name, font)?;
438        Ok(())
439    }
440
441    /// Get a custom font by name
442    pub(crate) fn get_custom_font(&self, name: &str) -> Option<Arc<CustomFont>> {
443        self.custom_fonts.get_font(name)
444    }
445
446    /// Check if a custom font is loaded
447    pub fn has_custom_font(&self, name: &str) -> bool {
448        self.custom_fonts.has_font(name)
449    }
450
451    /// Get all loaded custom font names
452    pub fn custom_font_names(&self) -> Vec<String> {
453        self.custom_fonts.font_names()
454    }
455
456    /// Gets the number of pages in the document.
457    pub fn page_count(&self) -> usize {
458        self.pages.len()
459    }
460
461    /// Gets a reference to the AcroForm (interactive form) if present.
462    pub fn acro_form(&self) -> Option<&AcroForm> {
463        self.acro_form.as_ref()
464    }
465
466    /// Gets a mutable reference to the AcroForm (interactive form) if present.
467    pub fn acro_form_mut(&mut self) -> Option<&mut AcroForm> {
468        self.acro_form.as_mut()
469    }
470
471    /// Enables interactive forms by creating a FormManager if not already present.
472    /// The FormManager handles both the AcroForm and the connection with page widgets.
473    pub fn enable_forms(&mut self) -> &mut FormManager {
474        if self.acro_form.is_none() {
475            self.acro_form = Some(AcroForm::new());
476        }
477        self.form_manager.get_or_insert_with(FormManager::new)
478    }
479
480    /// Disables interactive forms by removing both the AcroForm and FormManager.
481    pub fn disable_forms(&mut self) {
482        self.acro_form = None;
483        self.form_manager = None;
484    }
485
486    /// Saves the document to a file.
487    ///
488    /// # Errors
489    ///
490    /// Returns an error if the file cannot be created or written.
491    pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
492        // Update modification date before saving
493        self.update_modification_date();
494
495        // Create writer config with document's compression setting
496        let config = crate::writer::WriterConfig {
497            use_xref_streams: self.use_xref_streams,
498            use_object_streams: false, // For now, keep object streams disabled by default
499            pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
500            compress_streams: self.compress,
501            incremental_update: false,
502        };
503
504        use std::io::BufWriter;
505        let file = std::fs::File::create(path)?;
506        // Use 512KB buffer for better I/O performance (vs default 8KB)
507        // Reduces syscalls by ~98% for typical PDFs
508        let writer = BufWriter::with_capacity(512 * 1024, file);
509        let mut pdf_writer = PdfWriter::with_config(writer, config);
510
511        pdf_writer.write_document(self)?;
512        Ok(())
513    }
514
515    /// Saves the document to a file with custom writer configuration.
516    ///
517    /// # Errors
518    ///
519    /// Returns an error if the file cannot be created or written.
520    pub fn save_with_config(
521        &mut self,
522        path: impl AsRef<std::path::Path>,
523        config: crate::writer::WriterConfig,
524    ) -> Result<()> {
525        use std::io::BufWriter;
526
527        // Update modification date before saving
528        self.update_modification_date();
529
530        // Use the config as provided (don't override compress_streams)
531
532        let file = std::fs::File::create(path)?;
533        // Use 512KB buffer for better I/O performance (vs default 8KB)
534        let writer = BufWriter::with_capacity(512 * 1024, file);
535        let mut pdf_writer = PdfWriter::with_config(writer, config);
536        pdf_writer.write_document(self)?;
537        Ok(())
538    }
539
540    /// Saves the document to a file with custom values for headers/footers.
541    ///
542    /// This method processes all pages to replace custom placeholders in headers
543    /// and footers before saving the document.
544    ///
545    /// # Arguments
546    ///
547    /// * `path` - The path where the document should be saved
548    /// * `custom_values` - A map of placeholder names to their replacement values
549    ///
550    /// # Errors
551    ///
552    /// Returns an error if the file cannot be created or written.
553    pub fn save_with_custom_values(
554        &mut self,
555        path: impl AsRef<std::path::Path>,
556        custom_values: &std::collections::HashMap<String, String>,
557    ) -> Result<()> {
558        // Process all pages with custom values
559        let total_pages = self.pages.len();
560        for (index, page) in self.pages.iter_mut().enumerate() {
561            // Generate content with page info and custom values
562            let page_content = page.generate_content_with_page_info(
563                Some(index + 1),
564                Some(total_pages),
565                Some(custom_values),
566            )?;
567            // Update the page content
568            page.set_content(page_content);
569        }
570
571        // Save the document normally
572        self.save(path)
573    }
574
575    /// Writes the document to a buffer.
576    ///
577    /// # Errors
578    ///
579    /// Returns an error if the PDF cannot be generated.
580    pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
581        // Update modification date before writing
582        self.update_modification_date();
583
584        let mut writer = PdfWriter::new_with_writer(buffer);
585        writer.write_document(self)?;
586        Ok(())
587    }
588
589    /// Enables or disables compression for PDF streams.
590    ///
591    /// When compression is enabled (default), content streams and XRef streams are compressed
592    /// using Flate/Zlib compression to reduce file size. When disabled, streams are written
593    /// uncompressed, making the PDF larger but easier to debug.
594    ///
595    /// # Arguments
596    ///
597    /// * `compress` - Whether to enable compression
598    ///
599    /// # Example
600    ///
601    /// ```rust
602    /// use oxidize_pdf::{Document, Page};
603    ///
604    /// let mut doc = Document::new();
605    ///
606    /// // Disable compression for debugging
607    /// doc.set_compress(false);
608    ///
609    /// doc.set_title("My Document");
610    /// doc.add_page(Page::a4());
611    ///
612    /// let pdf_bytes = doc.to_bytes().unwrap();
613    /// println!("Uncompressed PDF size: {} bytes", pdf_bytes.len());
614    /// ```
615    pub fn set_compress(&mut self, compress: bool) {
616        self.compress = compress;
617    }
618
619    /// Enable or disable compressed cross-reference streams (PDF 1.5+).
620    ///
621    /// Cross-reference streams provide more compact representation of the cross-reference
622    /// table and support additional features like compressed object streams.
623    ///
624    /// # Arguments
625    ///
626    /// * `enable` - Whether to enable compressed cross-reference streams
627    ///
628    /// # Example
629    ///
630    /// ```rust
631    /// use oxidize_pdf::Document;
632    ///
633    /// let mut doc = Document::new();
634    /// doc.enable_xref_streams(true);
635    /// ```
636    pub fn enable_xref_streams(&mut self, enable: bool) -> &mut Self {
637        self.use_xref_streams = enable;
638        self
639    }
640
641    /// Gets the current compression setting.
642    ///
643    /// # Returns
644    ///
645    /// Returns `true` if compression is enabled, `false` otherwise.
646    pub fn get_compress(&self) -> bool {
647        self.compress
648    }
649
650    /// Generates the PDF document as bytes in memory.
651    ///
652    /// This method provides in-memory PDF generation without requiring file I/O.
653    /// The document is serialized to bytes and returned as a `Vec<u8>`.
654    ///
655    /// # Returns
656    ///
657    /// Returns the PDF document as bytes on success.
658    ///
659    /// # Errors
660    ///
661    /// Returns an error if the document cannot be serialized.
662    ///
663    /// # Example
664    ///
665    /// ```rust
666    /// use oxidize_pdf::{Document, Page};
667    ///
668    /// let mut doc = Document::new();
669    /// doc.set_title("My Document");
670    ///
671    /// let page = Page::a4();
672    /// doc.add_page(page);
673    ///
674    /// let pdf_bytes = doc.to_bytes().unwrap();
675    /// println!("Generated PDF size: {} bytes", pdf_bytes.len());
676    /// ```
677    pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
678        // Update modification date before serialization
679        self.update_modification_date();
680
681        // Create a buffer to write the PDF data to
682        let mut buffer = Vec::new();
683
684        // Create writer config with document's compression setting
685        let config = crate::writer::WriterConfig {
686            use_xref_streams: self.use_xref_streams,
687            use_object_streams: false, // For now, keep object streams disabled by default
688            pdf_version: if self.use_xref_streams { "1.5" } else { "1.7" }.to_string(),
689            compress_streams: self.compress,
690            incremental_update: false,
691        };
692
693        // Use PdfWriter with the buffer as output and config
694        let mut writer = PdfWriter::with_config(&mut buffer, config);
695        writer.write_document(self)?;
696
697        Ok(buffer)
698    }
699
700    /// Generates the PDF document as bytes with custom writer configuration.
701    ///
702    /// This method allows customizing the PDF output (e.g., using XRef streams)
703    /// while still generating the document in memory.
704    ///
705    /// # Arguments
706    ///
707    /// * `config` - Writer configuration options
708    ///
709    /// # Returns
710    ///
711    /// Returns the PDF document as bytes on success.
712    ///
713    /// # Errors
714    ///
715    /// Returns an error if the document cannot be serialized.
716    ///
717    /// # Example
718    ///
719    /// ```rust
720    /// use oxidize_pdf::{Document, Page};
721    /// use oxidize_pdf::writer::WriterConfig;
722    ///
723    /// let mut doc = Document::new();
724    /// doc.set_title("My Document");
725    ///
726    /// let page = Page::a4();
727    /// doc.add_page(page);
728    ///
729    /// let config = WriterConfig {
730    ///     use_xref_streams: true,
731    ///     use_object_streams: false,
732    ///     pdf_version: "1.5".to_string(),
733    ///     compress_streams: true,
734    ///     incremental_update: false,
735    /// };
736    ///
737    /// let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
738    /// println!("Generated PDF size: {} bytes", pdf_bytes.len());
739    /// ```
740    pub fn to_bytes_with_config(&mut self, config: crate::writer::WriterConfig) -> Result<Vec<u8>> {
741        // Update modification date before serialization
742        self.update_modification_date();
743
744        // Use the config as provided (don't override compress_streams)
745
746        // Create a buffer to write the PDF data to
747        let mut buffer = Vec::new();
748
749        // Use PdfWriter with the buffer as output and custom config
750        let mut writer = PdfWriter::with_config(&mut buffer, config);
751        writer.write_document(self)?;
752
753        Ok(buffer)
754    }
755
756    // ==================== Semantic Entity Methods ====================
757
758    /// Mark a region of the PDF with semantic meaning for AI processing.
759    ///
760    /// This creates an AI-Ready PDF that contains machine-readable metadata
761    /// alongside the visual content, enabling automated document processing.
762    ///
763    /// # Example
764    ///
765    /// ```rust
766    /// use oxidize_pdf::{Document, semantic::{EntityType, BoundingBox}};
767    ///
768    /// let mut doc = Document::new();
769    ///
770    /// // Mark an invoice number region
771    /// let entity_id = doc.mark_entity(
772    ///     "invoice_001".to_string(),
773    ///     EntityType::InvoiceNumber,
774    ///     BoundingBox::new(100.0, 700.0, 150.0, 20.0, 1)
775    /// );
776    ///
777    /// // Add content and metadata
778    /// doc.set_entity_content(&entity_id, "INV-2024-001");
779    /// doc.add_entity_metadata(&entity_id, "confidence", "0.98");
780    /// ```
781    pub fn mark_entity(
782        &mut self,
783        id: impl Into<String>,
784        entity_type: EntityType,
785        bounds: BoundingBox,
786    ) -> String {
787        let entity_id = id.into();
788        let entity = SemanticEntity::new(entity_id.clone(), entity_type, bounds);
789        self.semantic_entities.push(entity);
790        entity_id
791    }
792
793    /// Set the content text for an entity
794    pub fn set_entity_content(&mut self, entity_id: &str, content: impl Into<String>) -> bool {
795        if let Some(entity) = self
796            .semantic_entities
797            .iter_mut()
798            .find(|e| e.id == entity_id)
799        {
800            entity.content = content.into();
801            true
802        } else {
803            false
804        }
805    }
806
807    /// Add metadata to an entity
808    pub fn add_entity_metadata(
809        &mut self,
810        entity_id: &str,
811        key: impl Into<String>,
812        value: impl Into<String>,
813    ) -> bool {
814        if let Some(entity) = self
815            .semantic_entities
816            .iter_mut()
817            .find(|e| e.id == entity_id)
818        {
819            entity.metadata.properties.insert(key.into(), value.into());
820            true
821        } else {
822            false
823        }
824    }
825
826    /// Set confidence score for an entity
827    pub fn set_entity_confidence(&mut self, entity_id: &str, confidence: f32) -> bool {
828        if let Some(entity) = self
829            .semantic_entities
830            .iter_mut()
831            .find(|e| e.id == entity_id)
832        {
833            entity.metadata.confidence = Some(confidence.clamp(0.0, 1.0));
834            true
835        } else {
836            false
837        }
838    }
839
840    /// Add a relationship between two entities
841    pub fn relate_entities(
842        &mut self,
843        from_id: &str,
844        to_id: &str,
845        relation_type: RelationType,
846    ) -> bool {
847        // First check if target entity exists
848        let target_exists = self.semantic_entities.iter().any(|e| e.id == to_id);
849        if !target_exists {
850            return false;
851        }
852
853        // Then add the relationship
854        if let Some(entity) = self.semantic_entities.iter_mut().find(|e| e.id == from_id) {
855            entity.relationships.push(crate::semantic::EntityRelation {
856                target_id: to_id.to_string(),
857                relation_type,
858            });
859            true
860        } else {
861            false
862        }
863    }
864
865    /// Get all semantic entities in the document
866    pub fn get_semantic_entities(&self) -> &[SemanticEntity] {
867        &self.semantic_entities
868    }
869
870    /// Get entities by type
871    pub fn get_entities_by_type(&self, entity_type: EntityType) -> Vec<&SemanticEntity> {
872        self.semantic_entities
873            .iter()
874            .filter(|e| e.entity_type == entity_type)
875            .collect()
876    }
877
878    /// Export semantic entities as JSON
879    #[cfg(feature = "semantic")]
880    pub fn export_semantic_entities_json(&self) -> Result<String> {
881        serde_json::to_string_pretty(&self.semantic_entities)
882            .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
883    }
884
885    /// Export semantic entities as JSON-LD with Schema.org context
886    ///
887    /// This creates a machine-readable export compatible with Schema.org vocabularies,
888    /// making the PDF data accessible to AI/ML processing pipelines.
889    ///
890    /// # Example
891    ///
892    /// ```rust
893    /// use oxidize_pdf::{Document, semantic::{EntityType, BoundingBox}};
894    ///
895    /// let mut doc = Document::new();
896    ///
897    /// // Mark an invoice
898    /// let inv_id = doc.mark_entity(
899    ///     "invoice_1".to_string(),
900    ///     EntityType::Invoice,
901    ///     BoundingBox::new(50.0, 50.0, 500.0, 700.0, 1)
902    /// );
903    /// doc.set_entity_content(&inv_id, "Invoice #INV-001");
904    /// doc.add_entity_metadata(&inv_id, "totalPrice", "1234.56");
905    ///
906    /// // Export as JSON-LD
907    /// let json_ld = doc.export_semantic_entities_json_ld().unwrap();
908    /// println!("{}", json_ld);
909    /// ```
910    #[cfg(feature = "semantic")]
911    pub fn export_semantic_entities_json_ld(&self) -> Result<String> {
912        use crate::semantic::{Entity, EntityMap};
913
914        let mut entity_map = EntityMap::new();
915
916        // Convert SemanticEntity to Entity (backward compatibility)
917        for sem_entity in &self.semantic_entities {
918            let entity = Entity {
919                id: sem_entity.id.clone(),
920                entity_type: sem_entity.entity_type.clone(),
921                bounds: (
922                    sem_entity.bounds.x as f64,
923                    sem_entity.bounds.y as f64,
924                    sem_entity.bounds.width as f64,
925                    sem_entity.bounds.height as f64,
926                ),
927                page: (sem_entity.bounds.page - 1) as usize, // Convert 1-indexed to 0-indexed
928                metadata: sem_entity.metadata.clone(),
929            };
930            entity_map.add_entity(entity);
931        }
932
933        // Add document metadata
934        if let Some(title) = &self.metadata.title {
935            entity_map
936                .document_metadata
937                .insert("name".to_string(), title.clone());
938        }
939        if let Some(author) = &self.metadata.author {
940            entity_map
941                .document_metadata
942                .insert("author".to_string(), author.clone());
943        }
944
945        entity_map
946            .to_json_ld()
947            .map_err(|e| crate::error::PdfError::SerializationError(e.to_string()))
948    }
949
950    /// Find an entity by ID
951    pub fn find_entity(&self, entity_id: &str) -> Option<&SemanticEntity> {
952        self.semantic_entities.iter().find(|e| e.id == entity_id)
953    }
954
955    /// Remove an entity by ID
956    pub fn remove_entity(&mut self, entity_id: &str) -> bool {
957        if let Some(pos) = self
958            .semantic_entities
959            .iter()
960            .position(|e| e.id == entity_id)
961        {
962            self.semantic_entities.remove(pos);
963            // Also remove any relationships pointing to this entity
964            for entity in &mut self.semantic_entities {
965                entity.relationships.retain(|r| r.target_id != entity_id);
966            }
967            true
968        } else {
969            false
970        }
971    }
972
973    /// Get the count of semantic entities
974    pub fn semantic_entity_count(&self) -> usize {
975        self.semantic_entities.len()
976    }
977
978    /// Create XMP metadata from document metadata
979    ///
980    /// Generates an XMP metadata object from the document's metadata.
981    /// The XMP metadata can be serialized and embedded in the PDF.
982    ///
983    /// # Returns
984    /// XMP metadata object populated with document information
985    pub fn create_xmp_metadata(&self) -> crate::metadata::XmpMetadata {
986        let mut xmp = crate::metadata::XmpMetadata::new();
987
988        // Add Dublin Core metadata
989        if let Some(title) = &self.metadata.title {
990            xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "title", title);
991        }
992        if let Some(author) = &self.metadata.author {
993            xmp.set_text(crate::metadata::XmpNamespace::DublinCore, "creator", author);
994        }
995        if let Some(subject) = &self.metadata.subject {
996            xmp.set_text(
997                crate::metadata::XmpNamespace::DublinCore,
998                "description",
999                subject,
1000            );
1001        }
1002
1003        // Add XMP Basic metadata
1004        if let Some(creator) = &self.metadata.creator {
1005            xmp.set_text(
1006                crate::metadata::XmpNamespace::XmpBasic,
1007                "CreatorTool",
1008                creator,
1009            );
1010        }
1011        if let Some(creation_date) = &self.metadata.creation_date {
1012            xmp.set_date(
1013                crate::metadata::XmpNamespace::XmpBasic,
1014                "CreateDate",
1015                creation_date.to_rfc3339(),
1016            );
1017        }
1018        if let Some(mod_date) = &self.metadata.modification_date {
1019            xmp.set_date(
1020                crate::metadata::XmpNamespace::XmpBasic,
1021                "ModifyDate",
1022                mod_date.to_rfc3339(),
1023            );
1024        }
1025
1026        // Add PDF specific metadata
1027        if let Some(producer) = &self.metadata.producer {
1028            xmp.set_text(crate::metadata::XmpNamespace::Pdf, "Producer", producer);
1029        }
1030
1031        xmp
1032    }
1033
1034    /// Get XMP packet as string
1035    ///
1036    /// Returns the XMP metadata packet that can be embedded in the PDF.
1037    /// This is a convenience method that creates XMP from document metadata
1038    /// and serializes it to XML.
1039    ///
1040    /// # Returns
1041    /// XMP packet as XML string
1042    pub fn get_xmp_packet(&self) -> String {
1043        self.create_xmp_metadata().to_xmp_packet()
1044    }
1045
1046    /// Extract text content from all pages (placeholder implementation)
1047    pub fn extract_text(&self) -> Result<String> {
1048        // Placeholder implementation - in a real PDF reader this would
1049        // parse content streams and extract text operators
1050        let mut text = String::new();
1051        for (i, _page) in self.pages.iter().enumerate() {
1052            text.push_str(&format!("Text from page {} (placeholder)\n", i + 1));
1053        }
1054        Ok(text)
1055    }
1056
1057    /// Extract text content from a specific page (placeholder implementation)
1058    pub fn extract_page_text(&self, page_index: usize) -> Result<String> {
1059        if page_index < self.pages.len() {
1060            Ok(format!("Text from page {} (placeholder)", page_index + 1))
1061        } else {
1062            Err(crate::error::PdfError::InvalidReference(format!(
1063                "Page index {} out of bounds",
1064                page_index
1065            )))
1066        }
1067    }
1068}
1069
1070impl Default for Document {
1071    fn default() -> Self {
1072        Self::new()
1073    }
1074}
1075
1076#[cfg(test)]
1077mod tests {
1078    use super::*;
1079
1080    #[test]
1081    fn test_document_new() {
1082        let doc = Document::new();
1083        assert!(doc.pages.is_empty());
1084        assert!(doc.metadata.title.is_none());
1085        assert!(doc.metadata.author.is_none());
1086        assert!(doc.metadata.subject.is_none());
1087        assert!(doc.metadata.keywords.is_none());
1088        assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1089        assert!(doc
1090            .metadata
1091            .producer
1092            .as_ref()
1093            .unwrap()
1094            .starts_with("oxidize_pdf"));
1095    }
1096
1097    #[test]
1098    fn test_document_default() {
1099        let doc = Document::default();
1100        assert!(doc.pages.is_empty());
1101    }
1102
1103    #[test]
1104    fn test_add_page() {
1105        let mut doc = Document::new();
1106        let page1 = Page::a4();
1107        let page2 = Page::letter();
1108
1109        doc.add_page(page1);
1110        assert_eq!(doc.pages.len(), 1);
1111
1112        doc.add_page(page2);
1113        assert_eq!(doc.pages.len(), 2);
1114    }
1115
1116    #[test]
1117    fn test_set_title() {
1118        let mut doc = Document::new();
1119        assert!(doc.metadata.title.is_none());
1120
1121        doc.set_title("Test Document");
1122        assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
1123
1124        doc.set_title(String::from("Another Title"));
1125        assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
1126    }
1127
1128    #[test]
1129    fn test_set_author() {
1130        let mut doc = Document::new();
1131        assert!(doc.metadata.author.is_none());
1132
1133        doc.set_author("John Doe");
1134        assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
1135    }
1136
1137    #[test]
1138    fn test_set_subject() {
1139        let mut doc = Document::new();
1140        assert!(doc.metadata.subject.is_none());
1141
1142        doc.set_subject("Test Subject");
1143        assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
1144    }
1145
1146    #[test]
1147    fn test_set_keywords() {
1148        let mut doc = Document::new();
1149        assert!(doc.metadata.keywords.is_none());
1150
1151        doc.set_keywords("test, pdf, rust");
1152        assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
1153    }
1154
1155    #[test]
1156    fn test_metadata_default() {
1157        let metadata = DocumentMetadata::default();
1158        assert!(metadata.title.is_none());
1159        assert!(metadata.author.is_none());
1160        assert!(metadata.subject.is_none());
1161        assert!(metadata.keywords.is_none());
1162        assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
1163        assert!(metadata
1164            .producer
1165            .as_ref()
1166            .unwrap()
1167            .starts_with("oxidize_pdf"));
1168    }
1169
1170    #[test]
1171    fn test_write_to_buffer() {
1172        let mut doc = Document::new();
1173        doc.set_title("Buffer Test");
1174        doc.add_page(Page::a4());
1175
1176        let mut buffer = Vec::new();
1177        let result = doc.write(&mut buffer);
1178
1179        assert!(result.is_ok());
1180        assert!(!buffer.is_empty());
1181        assert!(buffer.starts_with(b"%PDF-1.7"));
1182    }
1183
1184    #[test]
1185    fn test_document_with_multiple_pages() {
1186        let mut doc = Document::new();
1187        doc.set_title("Multi-page Document");
1188        doc.set_author("Test Author");
1189        doc.set_subject("Testing multiple pages");
1190        doc.set_keywords("test, multiple, pages");
1191
1192        for _ in 0..5 {
1193            doc.add_page(Page::a4());
1194        }
1195
1196        assert_eq!(doc.pages.len(), 5);
1197        assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
1198        assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1199    }
1200
1201    #[test]
1202    fn test_empty_document_write() {
1203        let mut doc = Document::new();
1204        let mut buffer = Vec::new();
1205
1206        // Empty document should still produce valid PDF
1207        let result = doc.write(&mut buffer);
1208        assert!(result.is_ok());
1209        assert!(!buffer.is_empty());
1210        assert!(buffer.starts_with(b"%PDF-1.7"));
1211    }
1212
1213    // Integration tests for Document ↔ Writer ↔ Parser interactions
1214    mod integration_tests {
1215        use super::*;
1216        use crate::graphics::Color;
1217        use crate::text::Font;
1218        use std::fs;
1219        use tempfile::TempDir;
1220
1221        #[test]
1222        fn test_document_writer_roundtrip() {
1223            let temp_dir = TempDir::new().unwrap();
1224            let file_path = temp_dir.path().join("test.pdf");
1225
1226            // Create document with content
1227            let mut doc = Document::new();
1228            doc.set_title("Integration Test");
1229            doc.set_author("Test Author");
1230            doc.set_subject("Writer Integration");
1231            doc.set_keywords("test, writer, integration");
1232
1233            let mut page = Page::a4();
1234            page.text()
1235                .set_font(Font::Helvetica, 12.0)
1236                .at(100.0, 700.0)
1237                .write("Integration Test Content")
1238                .unwrap();
1239
1240            doc.add_page(page);
1241
1242            // Write to file
1243            let result = doc.save(&file_path);
1244            assert!(result.is_ok());
1245
1246            // Verify file exists and has content
1247            assert!(file_path.exists());
1248            let metadata = fs::metadata(&file_path).unwrap();
1249            assert!(metadata.len() > 0);
1250
1251            // Read file back to verify PDF format
1252            let content = fs::read(&file_path).unwrap();
1253            assert!(content.starts_with(b"%PDF-1.7"));
1254            // Check for %%EOF with or without newline
1255            assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
1256        }
1257
1258        #[test]
1259        fn test_document_with_complex_content() {
1260            let temp_dir = TempDir::new().unwrap();
1261            let file_path = temp_dir.path().join("complex.pdf");
1262
1263            let mut doc = Document::new();
1264            doc.set_title("Complex Content Test");
1265
1266            // Create page with mixed content
1267            let mut page = Page::a4();
1268
1269            // Add text
1270            page.text()
1271                .set_font(Font::Helvetica, 14.0)
1272                .at(50.0, 750.0)
1273                .write("Complex Content Test")
1274                .unwrap();
1275
1276            // Add graphics
1277            page.graphics()
1278                .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
1279                .rectangle(50.0, 500.0, 200.0, 100.0)
1280                .fill();
1281
1282            page.graphics()
1283                .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
1284                .set_line_width(2.0)
1285                .move_to(50.0, 400.0)
1286                .line_to(250.0, 400.0)
1287                .stroke();
1288
1289            doc.add_page(page);
1290
1291            // Write and verify
1292            let result = doc.save(&file_path);
1293            assert!(result.is_ok());
1294            assert!(file_path.exists());
1295        }
1296
1297        #[test]
1298        fn test_document_multiple_pages_integration() {
1299            let temp_dir = TempDir::new().unwrap();
1300            let file_path = temp_dir.path().join("multipage.pdf");
1301
1302            let mut doc = Document::new();
1303            doc.set_title("Multi-page Integration Test");
1304
1305            // Create multiple pages with different content
1306            for i in 1..=5 {
1307                let mut page = Page::a4();
1308
1309                page.text()
1310                    .set_font(Font::Helvetica, 16.0)
1311                    .at(50.0, 750.0)
1312                    .write(&format!("Page {i}"))
1313                    .unwrap();
1314
1315                page.text()
1316                    .set_font(Font::Helvetica, 12.0)
1317                    .at(50.0, 700.0)
1318                    .write(&format!("This is the content for page {i}"))
1319                    .unwrap();
1320
1321                // Add unique graphics for each page
1322                let color = match i % 3 {
1323                    0 => Color::rgb(1.0, 0.0, 0.0),
1324                    1 => Color::rgb(0.0, 1.0, 0.0),
1325                    _ => Color::rgb(0.0, 0.0, 1.0),
1326                };
1327
1328                page.graphics()
1329                    .set_fill_color(color)
1330                    .rectangle(50.0, 600.0, 100.0, 50.0)
1331                    .fill();
1332
1333                doc.add_page(page);
1334            }
1335
1336            // Write and verify
1337            let result = doc.save(&file_path);
1338            assert!(result.is_ok());
1339            assert!(file_path.exists());
1340
1341            // Verify file size is reasonable for 5 pages
1342            let metadata = fs::metadata(&file_path).unwrap();
1343            assert!(metadata.len() > 1000); // Should be substantial
1344        }
1345
1346        #[test]
1347        fn test_document_metadata_persistence() {
1348            let temp_dir = TempDir::new().unwrap();
1349            let file_path = temp_dir.path().join("metadata.pdf");
1350
1351            let mut doc = Document::new();
1352            doc.set_title("Metadata Persistence Test");
1353            doc.set_author("Test Author");
1354            doc.set_subject("Testing metadata preservation");
1355            doc.set_keywords("metadata, persistence, test");
1356
1357            doc.add_page(Page::a4());
1358
1359            // Write to file
1360            let result = doc.save(&file_path);
1361            assert!(result.is_ok());
1362
1363            // Read file content to verify metadata is present
1364            let content = fs::read(&file_path).unwrap();
1365            let content_str = String::from_utf8_lossy(&content);
1366
1367            // Check that metadata appears in the PDF
1368            assert!(content_str.contains("Metadata Persistence Test"));
1369            assert!(content_str.contains("Test Author"));
1370        }
1371
1372        #[test]
1373        fn test_document_writer_error_handling() {
1374            let mut doc = Document::new();
1375            doc.add_page(Page::a4());
1376
1377            // Test writing to invalid path
1378            let result = doc.save("/invalid/path/test.pdf");
1379            assert!(result.is_err());
1380        }
1381
1382        #[test]
1383        fn test_document_page_integration() {
1384            let mut doc = Document::new();
1385
1386            // Test different page configurations
1387            let page1 = Page::a4();
1388            let page2 = Page::letter();
1389            let mut page3 = Page::new(500.0, 400.0);
1390
1391            // Add content to custom page
1392            page3
1393                .text()
1394                .set_font(Font::Helvetica, 10.0)
1395                .at(25.0, 350.0)
1396                .write("Custom size page")
1397                .unwrap();
1398
1399            doc.add_page(page1);
1400            doc.add_page(page2);
1401            doc.add_page(page3);
1402
1403            assert_eq!(doc.pages.len(), 3);
1404
1405            // Verify pages maintain their properties (actual dimensions may vary)
1406            assert!(doc.pages[0].width() > 500.0); // A4 width is reasonable
1407            assert!(doc.pages[0].height() > 700.0); // A4 height is reasonable
1408            assert!(doc.pages[1].width() > 500.0); // Letter width is reasonable
1409            assert!(doc.pages[1].height() > 700.0); // Letter height is reasonable
1410            assert_eq!(doc.pages[2].width(), 500.0); // Custom width
1411            assert_eq!(doc.pages[2].height(), 400.0); // Custom height
1412        }
1413
1414        #[test]
1415        fn test_document_content_generation() {
1416            let temp_dir = TempDir::new().unwrap();
1417            let file_path = temp_dir.path().join("content.pdf");
1418
1419            let mut doc = Document::new();
1420            doc.set_title("Content Generation Test");
1421
1422            let mut page = Page::a4();
1423
1424            // Generate content programmatically
1425            for i in 0..10 {
1426                let y_pos = 700.0 - (i as f64 * 30.0);
1427                page.text()
1428                    .set_font(Font::Helvetica, 12.0)
1429                    .at(50.0, y_pos)
1430                    .write(&format!("Generated line {}", i + 1))
1431                    .unwrap();
1432            }
1433
1434            doc.add_page(page);
1435
1436            // Write and verify
1437            let result = doc.save(&file_path);
1438            assert!(result.is_ok());
1439            assert!(file_path.exists());
1440
1441            // Verify content was generated
1442            let metadata = fs::metadata(&file_path).unwrap();
1443            assert!(metadata.len() > 500); // Should contain substantial content
1444        }
1445
1446        #[test]
1447        fn test_document_buffer_vs_file_write() {
1448            let temp_dir = TempDir::new().unwrap();
1449            let file_path = temp_dir.path().join("buffer_vs_file.pdf");
1450
1451            let mut doc = Document::new();
1452            doc.set_title("Buffer vs File Test");
1453            doc.add_page(Page::a4());
1454
1455            // Write to buffer
1456            let mut buffer = Vec::new();
1457            let buffer_result = doc.write(&mut buffer);
1458            assert!(buffer_result.is_ok());
1459
1460            // Write to file
1461            let file_result = doc.save(&file_path);
1462            assert!(file_result.is_ok());
1463
1464            // Read file back
1465            let file_content = fs::read(&file_path).unwrap();
1466
1467            // Both should be valid PDFs with same structure (timestamps may differ)
1468            assert!(buffer.starts_with(b"%PDF-1.7"));
1469            assert!(file_content.starts_with(b"%PDF-1.7"));
1470            assert!(buffer.ends_with(b"%%EOF\n"));
1471            assert!(file_content.ends_with(b"%%EOF\n"));
1472
1473            // Both should contain the same title
1474            let buffer_str = String::from_utf8_lossy(&buffer);
1475            let file_str = String::from_utf8_lossy(&file_content);
1476            assert!(buffer_str.contains("Buffer vs File Test"));
1477            assert!(file_str.contains("Buffer vs File Test"));
1478        }
1479
1480        #[test]
1481        fn test_document_large_content_handling() {
1482            let temp_dir = TempDir::new().unwrap();
1483            let file_path = temp_dir.path().join("large_content.pdf");
1484
1485            let mut doc = Document::new();
1486            doc.set_title("Large Content Test");
1487
1488            let mut page = Page::a4();
1489
1490            // Add large amount of text content - make it much larger
1491            let large_text =
1492                "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(200);
1493            page.text()
1494                .set_font(Font::Helvetica, 10.0)
1495                .at(50.0, 750.0)
1496                .write(&large_text)
1497                .unwrap();
1498
1499            doc.add_page(page);
1500
1501            // Write and verify
1502            let result = doc.save(&file_path);
1503            assert!(result.is_ok());
1504            assert!(file_path.exists());
1505
1506            // Verify large content was handled properly - reduce expectation
1507            let metadata = fs::metadata(&file_path).unwrap();
1508            assert!(metadata.len() > 500); // Should be substantial but realistic
1509        }
1510
1511        #[test]
1512        fn test_document_incremental_building() {
1513            let temp_dir = TempDir::new().unwrap();
1514            let file_path = temp_dir.path().join("incremental.pdf");
1515
1516            let mut doc = Document::new();
1517
1518            // Build document incrementally
1519            doc.set_title("Incremental Building Test");
1520
1521            // Add first page
1522            let mut page1 = Page::a4();
1523            page1
1524                .text()
1525                .set_font(Font::Helvetica, 12.0)
1526                .at(50.0, 750.0)
1527                .write("First page content")
1528                .unwrap();
1529            doc.add_page(page1);
1530
1531            // Add metadata
1532            doc.set_author("Incremental Author");
1533            doc.set_subject("Incremental Subject");
1534
1535            // Add second page
1536            let mut page2 = Page::a4();
1537            page2
1538                .text()
1539                .set_font(Font::Helvetica, 12.0)
1540                .at(50.0, 750.0)
1541                .write("Second page content")
1542                .unwrap();
1543            doc.add_page(page2);
1544
1545            // Add more metadata
1546            doc.set_keywords("incremental, building, test");
1547
1548            // Final write
1549            let result = doc.save(&file_path);
1550            assert!(result.is_ok());
1551            assert!(file_path.exists());
1552
1553            // Verify final state
1554            assert_eq!(doc.pages.len(), 2);
1555            assert_eq!(
1556                doc.metadata.title,
1557                Some("Incremental Building Test".to_string())
1558            );
1559            assert_eq!(doc.metadata.author, Some("Incremental Author".to_string()));
1560            assert_eq!(
1561                doc.metadata.subject,
1562                Some("Incremental Subject".to_string())
1563            );
1564            assert_eq!(
1565                doc.metadata.keywords,
1566                Some("incremental, building, test".to_string())
1567            );
1568        }
1569
1570        #[test]
1571        fn test_document_concurrent_page_operations() {
1572            let mut doc = Document::new();
1573            doc.set_title("Concurrent Operations Test");
1574
1575            // Simulate concurrent-like operations
1576            let mut pages = Vec::new();
1577
1578            // Create multiple pages
1579            for i in 0..5 {
1580                let mut page = Page::a4();
1581                page.text()
1582                    .set_font(Font::Helvetica, 12.0)
1583                    .at(50.0, 750.0)
1584                    .write(&format!("Concurrent page {i}"))
1585                    .unwrap();
1586                pages.push(page);
1587            }
1588
1589            // Add all pages
1590            for page in pages {
1591                doc.add_page(page);
1592            }
1593
1594            assert_eq!(doc.pages.len(), 5);
1595
1596            // Verify each page maintains its content
1597            let temp_dir = TempDir::new().unwrap();
1598            let file_path = temp_dir.path().join("concurrent.pdf");
1599            let result = doc.save(&file_path);
1600            assert!(result.is_ok());
1601        }
1602
1603        #[test]
1604        fn test_document_memory_efficiency() {
1605            let mut doc = Document::new();
1606            doc.set_title("Memory Efficiency Test");
1607
1608            // Add multiple pages with content
1609            for i in 0..10 {
1610                let mut page = Page::a4();
1611                page.text()
1612                    .set_font(Font::Helvetica, 12.0)
1613                    .at(50.0, 700.0)
1614                    .write(&format!("Memory test page {i}"))
1615                    .unwrap();
1616                doc.add_page(page);
1617            }
1618
1619            // Write to buffer to test memory usage
1620            let mut buffer = Vec::new();
1621            let result = doc.write(&mut buffer);
1622            assert!(result.is_ok());
1623            assert!(!buffer.is_empty());
1624
1625            // Buffer should be reasonable size
1626            assert!(buffer.len() < 1_000_000); // Should be less than 1MB for simple content
1627        }
1628
1629        #[test]
1630        fn test_document_creator_producer() {
1631            let mut doc = Document::new();
1632
1633            // Default values
1634            assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1635            assert!(doc
1636                .metadata
1637                .producer
1638                .as_ref()
1639                .unwrap()
1640                .contains("oxidize_pdf"));
1641
1642            // Set custom values
1643            doc.set_creator("My Application");
1644            doc.set_producer("My PDF Library v1.0");
1645
1646            assert_eq!(doc.metadata.creator, Some("My Application".to_string()));
1647            assert_eq!(
1648                doc.metadata.producer,
1649                Some("My PDF Library v1.0".to_string())
1650            );
1651        }
1652
1653        #[test]
1654        fn test_document_dates() {
1655            use chrono::{TimeZone, Utc};
1656
1657            let mut doc = Document::new();
1658
1659            // Check default dates are set
1660            assert!(doc.metadata.creation_date.is_some());
1661            assert!(doc.metadata.modification_date.is_some());
1662
1663            // Set specific dates
1664            let creation_date = Utc.with_ymd_and_hms(2023, 1, 1, 12, 0, 0).unwrap();
1665            let mod_date = Utc.with_ymd_and_hms(2023, 6, 15, 18, 30, 0).unwrap();
1666
1667            doc.set_creation_date(creation_date);
1668            doc.set_modification_date(mod_date);
1669
1670            assert_eq!(doc.metadata.creation_date, Some(creation_date));
1671            assert_eq!(doc.metadata.modification_date, Some(mod_date));
1672        }
1673
1674        #[test]
1675        fn test_document_dates_local() {
1676            use chrono::{Local, TimeZone};
1677
1678            let mut doc = Document::new();
1679
1680            // Test setting dates with local time
1681            let local_date = Local.with_ymd_and_hms(2023, 12, 25, 10, 30, 0).unwrap();
1682            doc.set_creation_date_local(local_date);
1683
1684            // Verify it was converted to UTC
1685            assert!(doc.metadata.creation_date.is_some());
1686            // Just verify the date was set, don't compare exact values due to timezone complexities
1687            assert!(doc.metadata.creation_date.is_some());
1688        }
1689
1690        #[test]
1691        fn test_update_modification_date() {
1692            let mut doc = Document::new();
1693
1694            let initial_mod_date = doc.metadata.modification_date;
1695            assert!(initial_mod_date.is_some());
1696
1697            // Sleep briefly to ensure time difference
1698            std::thread::sleep(std::time::Duration::from_millis(10));
1699
1700            doc.update_modification_date();
1701
1702            let new_mod_date = doc.metadata.modification_date;
1703            assert!(new_mod_date.is_some());
1704            assert!(new_mod_date.unwrap() > initial_mod_date.unwrap());
1705        }
1706
1707        #[test]
1708        fn test_document_save_updates_modification_date() {
1709            let temp_dir = TempDir::new().unwrap();
1710            let file_path = temp_dir.path().join("mod_date_test.pdf");
1711
1712            let mut doc = Document::new();
1713            doc.add_page(Page::a4());
1714
1715            let initial_mod_date = doc.metadata.modification_date;
1716
1717            // Sleep briefly to ensure time difference
1718            std::thread::sleep(std::time::Duration::from_millis(10));
1719
1720            doc.save(&file_path).unwrap();
1721
1722            // Modification date should be updated
1723            assert!(doc.metadata.modification_date.unwrap() > initial_mod_date.unwrap());
1724        }
1725
1726        #[test]
1727        fn test_document_metadata_complete() {
1728            let mut doc = Document::new();
1729
1730            // Set all metadata fields
1731            doc.set_title("Complete Metadata Test");
1732            doc.set_author("Test Author");
1733            doc.set_subject("Testing all metadata fields");
1734            doc.set_keywords("test, metadata, complete");
1735            doc.set_creator("Test Application v1.0");
1736            doc.set_producer("oxidize_pdf Test Suite");
1737
1738            // Verify all fields
1739            assert_eq!(
1740                doc.metadata.title,
1741                Some("Complete Metadata Test".to_string())
1742            );
1743            assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1744            assert_eq!(
1745                doc.metadata.subject,
1746                Some("Testing all metadata fields".to_string())
1747            );
1748            assert_eq!(
1749                doc.metadata.keywords,
1750                Some("test, metadata, complete".to_string())
1751            );
1752            assert_eq!(
1753                doc.metadata.creator,
1754                Some("Test Application v1.0".to_string())
1755            );
1756            assert_eq!(
1757                doc.metadata.producer,
1758                Some("oxidize_pdf Test Suite".to_string())
1759            );
1760            assert!(doc.metadata.creation_date.is_some());
1761            assert!(doc.metadata.modification_date.is_some());
1762        }
1763
1764        #[test]
1765        fn test_document_to_bytes() {
1766            let mut doc = Document::new();
1767            doc.set_title("Test Document");
1768            doc.set_author("Test Author");
1769
1770            let page = Page::a4();
1771            doc.add_page(page);
1772
1773            // Generate PDF as bytes
1774            let pdf_bytes = doc.to_bytes().unwrap();
1775
1776            // Basic validation
1777            assert!(!pdf_bytes.is_empty());
1778            assert!(pdf_bytes.len() > 100); // Should be reasonable size
1779
1780            // Check PDF header
1781            let header = &pdf_bytes[0..5];
1782            assert_eq!(header, b"%PDF-");
1783
1784            // Check for some basic PDF structure
1785            let pdf_str = String::from_utf8_lossy(&pdf_bytes);
1786            assert!(pdf_str.contains("Test Document"));
1787            assert!(pdf_str.contains("Test Author"));
1788        }
1789
1790        #[test]
1791        fn test_document_to_bytes_with_config() {
1792            let mut doc = Document::new();
1793            doc.set_title("Test Document XRef");
1794
1795            let page = Page::a4();
1796            doc.add_page(page);
1797
1798            let config = crate::writer::WriterConfig {
1799                use_xref_streams: true,
1800                use_object_streams: false,
1801                pdf_version: "1.5".to_string(),
1802                compress_streams: true,
1803                incremental_update: false,
1804            };
1805
1806            // Generate PDF with custom config
1807            let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1808
1809            // Basic validation
1810            assert!(!pdf_bytes.is_empty());
1811            assert!(pdf_bytes.len() > 100);
1812
1813            // Check PDF header with correct version
1814            let header = String::from_utf8_lossy(&pdf_bytes[0..8]);
1815            assert!(header.contains("PDF-1.5"));
1816        }
1817
1818        #[test]
1819        fn test_to_bytes_vs_save_equivalence() {
1820            use std::fs;
1821            use tempfile::NamedTempFile;
1822
1823            // Create two identical documents
1824            let mut doc1 = Document::new();
1825            doc1.set_title("Equivalence Test");
1826            doc1.add_page(Page::a4());
1827
1828            let mut doc2 = Document::new();
1829            doc2.set_title("Equivalence Test");
1830            doc2.add_page(Page::a4());
1831
1832            // Generate bytes
1833            let pdf_bytes = doc1.to_bytes().unwrap();
1834
1835            // Save to file
1836            let temp_file = NamedTempFile::new().unwrap();
1837            doc2.save(temp_file.path()).unwrap();
1838            let file_bytes = fs::read(temp_file.path()).unwrap();
1839
1840            // Both should generate similar structure (lengths may vary due to timestamps)
1841            assert!(!pdf_bytes.is_empty());
1842            assert!(!file_bytes.is_empty());
1843            assert_eq!(&pdf_bytes[0..5], &file_bytes[0..5]); // PDF headers should match
1844        }
1845
1846        #[test]
1847        fn test_document_set_compress() {
1848            let mut doc = Document::new();
1849            doc.set_title("Compression Test");
1850            doc.add_page(Page::a4());
1851
1852            // Default should be compressed
1853            assert!(doc.get_compress());
1854
1855            // Test with compression enabled
1856            doc.set_compress(true);
1857            let compressed_bytes = doc.to_bytes().unwrap();
1858
1859            // Test with compression disabled
1860            doc.set_compress(false);
1861            let uncompressed_bytes = doc.to_bytes().unwrap();
1862
1863            // Uncompressed should generally be larger (though not always guaranteed)
1864            assert!(!compressed_bytes.is_empty());
1865            assert!(!uncompressed_bytes.is_empty());
1866
1867            // Both should be valid PDFs
1868            assert_eq!(&compressed_bytes[0..5], b"%PDF-");
1869            assert_eq!(&uncompressed_bytes[0..5], b"%PDF-");
1870        }
1871
1872        #[test]
1873        fn test_document_compression_config_inheritance() {
1874            let mut doc = Document::new();
1875            doc.set_title("Config Inheritance Test");
1876            doc.add_page(Page::a4());
1877
1878            // Set document compression to false
1879            doc.set_compress(false);
1880
1881            // Create config with compression true (should be overridden)
1882            let config = crate::writer::WriterConfig {
1883                use_xref_streams: false,
1884                use_object_streams: false,
1885                pdf_version: "1.7".to_string(),
1886                compress_streams: true,
1887                incremental_update: false,
1888            };
1889
1890            // Document setting should take precedence
1891            let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1892
1893            // Should be valid PDF
1894            assert!(!pdf_bytes.is_empty());
1895            assert_eq!(&pdf_bytes[0..5], b"%PDF-");
1896        }
1897
1898        #[test]
1899        fn test_document_metadata_all_fields() {
1900            let mut doc = Document::new();
1901
1902            // Set all metadata fields
1903            doc.set_title("Test Document");
1904            doc.set_author("John Doe");
1905            doc.set_subject("Testing PDF metadata");
1906            doc.set_keywords("test, pdf, metadata");
1907            doc.set_creator("Test Suite");
1908            doc.set_producer("oxidize_pdf tests");
1909
1910            // Verify all fields are set
1911            assert_eq!(doc.metadata.title.as_deref(), Some("Test Document"));
1912            assert_eq!(doc.metadata.author.as_deref(), Some("John Doe"));
1913            assert_eq!(
1914                doc.metadata.subject.as_deref(),
1915                Some("Testing PDF metadata")
1916            );
1917            assert_eq!(
1918                doc.metadata.keywords.as_deref(),
1919                Some("test, pdf, metadata")
1920            );
1921            assert_eq!(doc.metadata.creator.as_deref(), Some("Test Suite"));
1922            assert_eq!(doc.metadata.producer.as_deref(), Some("oxidize_pdf tests"));
1923            assert!(doc.metadata.creation_date.is_some());
1924            assert!(doc.metadata.modification_date.is_some());
1925        }
1926
1927        #[test]
1928        fn test_document_add_pages() {
1929            let mut doc = Document::new();
1930
1931            // Initially empty
1932            assert_eq!(doc.page_count(), 0);
1933
1934            // Add pages
1935            let page1 = Page::a4();
1936            let page2 = Page::letter();
1937            let page3 = Page::legal();
1938
1939            doc.add_page(page1);
1940            assert_eq!(doc.page_count(), 1);
1941
1942            doc.add_page(page2);
1943            assert_eq!(doc.page_count(), 2);
1944
1945            doc.add_page(page3);
1946            assert_eq!(doc.page_count(), 3);
1947
1948            // Verify we can convert to PDF with multiple pages
1949            let result = doc.to_bytes();
1950            assert!(result.is_ok());
1951        }
1952
1953        #[test]
1954        fn test_document_default_font_encoding() {
1955            let mut doc = Document::new();
1956
1957            // Initially no default encoding
1958            assert!(doc.default_font_encoding.is_none());
1959
1960            // Set default encoding
1961            doc.set_default_font_encoding(Some(FontEncoding::WinAnsiEncoding));
1962            assert_eq!(
1963                doc.default_font_encoding(),
1964                Some(FontEncoding::WinAnsiEncoding)
1965            );
1966
1967            // Change encoding
1968            doc.set_default_font_encoding(Some(FontEncoding::MacRomanEncoding));
1969            assert_eq!(
1970                doc.default_font_encoding(),
1971                Some(FontEncoding::MacRomanEncoding)
1972            );
1973        }
1974
1975        #[test]
1976        fn test_document_compression_setting() {
1977            let mut doc = Document::new();
1978
1979            // Default should compress
1980            assert!(doc.compress);
1981
1982            // Disable compression
1983            doc.set_compress(false);
1984            assert!(!doc.compress);
1985
1986            // Re-enable compression
1987            doc.set_compress(true);
1988            assert!(doc.compress);
1989        }
1990
1991        #[test]
1992        fn test_document_with_empty_pages() {
1993            let mut doc = Document::new();
1994
1995            // Add empty page
1996            doc.add_page(Page::a4());
1997
1998            // Should be able to convert to bytes
1999            let result = doc.to_bytes();
2000            assert!(result.is_ok());
2001
2002            let pdf_bytes = result.unwrap();
2003            assert!(!pdf_bytes.is_empty());
2004            assert!(pdf_bytes.starts_with(b"%PDF-"));
2005        }
2006
2007        #[test]
2008        fn test_document_with_multiple_page_sizes() {
2009            let mut doc = Document::new();
2010
2011            // Add pages with different sizes
2012            doc.add_page(Page::a4()); // 595 x 842
2013            doc.add_page(Page::letter()); // 612 x 792
2014            doc.add_page(Page::legal()); // 612 x 1008
2015            doc.add_page(Page::a4()); // Another A4
2016            doc.add_page(Page::new(200.0, 300.0)); // Custom size
2017
2018            assert_eq!(doc.page_count(), 5);
2019
2020            // Verify we have 5 pages
2021            // Note: Direct page access is not available in public API
2022            // We verify by successful PDF generation
2023            let result = doc.to_bytes();
2024            assert!(result.is_ok());
2025        }
2026
2027        #[test]
2028        fn test_document_metadata_dates() {
2029            use chrono::Duration;
2030
2031            let doc = Document::new();
2032
2033            // Should have creation and modification dates
2034            assert!(doc.metadata.creation_date.is_some());
2035            assert!(doc.metadata.modification_date.is_some());
2036
2037            if let (Some(created), Some(modified)) =
2038                (doc.metadata.creation_date, doc.metadata.modification_date)
2039            {
2040                // Dates should be very close (created during construction)
2041                let diff = modified - created;
2042                assert!(diff < Duration::seconds(1));
2043            }
2044        }
2045
2046        #[test]
2047        fn test_document_builder_pattern() {
2048            // Test fluent API style
2049            let mut doc = Document::new();
2050            doc.set_title("Fluent");
2051            doc.set_author("Builder");
2052            doc.set_compress(true);
2053
2054            assert_eq!(doc.metadata.title.as_deref(), Some("Fluent"));
2055            assert_eq!(doc.metadata.author.as_deref(), Some("Builder"));
2056            assert!(doc.compress);
2057        }
2058
2059        #[test]
2060        fn test_xref_streams_functionality() {
2061            use crate::{Document, Font, Page};
2062
2063            // Test with xref streams disabled (default)
2064            let mut doc = Document::new();
2065            assert!(!doc.use_xref_streams);
2066
2067            let mut page = Page::a4();
2068            page.text()
2069                .set_font(Font::Helvetica, 12.0)
2070                .at(100.0, 700.0)
2071                .write("Testing XRef Streams")
2072                .unwrap();
2073
2074            doc.add_page(page);
2075
2076            // Generate PDF without xref streams
2077            let pdf_without_xref = doc.to_bytes().unwrap();
2078
2079            // Verify traditional xref is used
2080            let pdf_str = String::from_utf8_lossy(&pdf_without_xref);
2081            assert!(pdf_str.contains("xref"), "Traditional xref table not found");
2082            assert!(
2083                !pdf_str.contains("/Type /XRef"),
2084                "XRef stream found when it shouldn't be"
2085            );
2086
2087            // Test with xref streams enabled
2088            doc.enable_xref_streams(true);
2089            assert!(doc.use_xref_streams);
2090
2091            // Generate PDF with xref streams
2092            let pdf_with_xref = doc.to_bytes().unwrap();
2093
2094            // Verify xref streams are used
2095            let pdf_str = String::from_utf8_lossy(&pdf_with_xref);
2096            // XRef streams replace traditional xref tables in PDF 1.5+
2097            assert!(
2098                pdf_str.contains("/Type /XRef") || pdf_str.contains("stream"),
2099                "XRef stream not found when enabled"
2100            );
2101
2102            // Verify PDF version is set correctly
2103            assert!(
2104                pdf_str.contains("PDF-1.5"),
2105                "PDF version not set to 1.5 for xref streams"
2106            );
2107
2108            // Test fluent interface
2109            let mut doc2 = Document::new();
2110            doc2.enable_xref_streams(true);
2111            doc2.set_title("XRef Streams Test");
2112            doc2.set_author("oxidize-pdf");
2113
2114            assert!(doc2.use_xref_streams);
2115            assert_eq!(doc2.metadata.title.as_deref(), Some("XRef Streams Test"));
2116            assert_eq!(doc2.metadata.author.as_deref(), Some("oxidize-pdf"));
2117        }
2118
2119        #[test]
2120        fn test_document_save_to_vec() {
2121            let mut doc = Document::new();
2122            doc.set_title("Test Save");
2123            doc.add_page(Page::a4());
2124
2125            // Test to_bytes
2126            let bytes_result = doc.to_bytes();
2127            assert!(bytes_result.is_ok());
2128
2129            let bytes = bytes_result.unwrap();
2130            assert!(!bytes.is_empty());
2131            assert!(bytes.starts_with(b"%PDF-"));
2132            assert!(bytes.ends_with(b"%%EOF") || bytes.ends_with(b"%%EOF\n"));
2133        }
2134
2135        #[test]
2136        fn test_document_unicode_metadata() {
2137            let mut doc = Document::new();
2138
2139            // Set metadata with Unicode characters
2140            doc.set_title("日本語のタイトル");
2141            doc.set_author("作者名 😀");
2142            doc.set_subject("Тема документа");
2143            doc.set_keywords("كلمات, מפתח, 关键词");
2144
2145            assert_eq!(doc.metadata.title.as_deref(), Some("日本語のタイトル"));
2146            assert_eq!(doc.metadata.author.as_deref(), Some("作者名 😀"));
2147            assert_eq!(doc.metadata.subject.as_deref(), Some("Тема документа"));
2148            assert_eq!(
2149                doc.metadata.keywords.as_deref(),
2150                Some("كلمات, מפתח, 关键词")
2151            );
2152        }
2153
2154        #[test]
2155        fn test_document_page_iteration() {
2156            let mut doc = Document::new();
2157
2158            // Add multiple pages
2159            for i in 0..5 {
2160                let mut page = Page::a4();
2161                let gc = page.graphics();
2162                gc.begin_text();
2163                let _ = gc.show_text(&format!("Page {}", i + 1));
2164                gc.end_text();
2165                doc.add_page(page);
2166            }
2167
2168            // Verify page count
2169            assert_eq!(doc.page_count(), 5);
2170
2171            // Verify we can generate PDF with all pages
2172            let result = doc.to_bytes();
2173            assert!(result.is_ok());
2174        }
2175
2176        #[test]
2177        fn test_document_with_graphics_content() {
2178            let mut doc = Document::new();
2179
2180            let mut page = Page::a4();
2181            {
2182                let gc = page.graphics();
2183
2184                // Add various graphics operations
2185                gc.save_state();
2186
2187                // Draw rectangle
2188                gc.rectangle(100.0, 100.0, 200.0, 150.0);
2189                gc.stroke();
2190
2191                // Draw circle (approximated)
2192                gc.move_to(300.0, 300.0);
2193                gc.circle(300.0, 300.0, 50.0);
2194                gc.fill();
2195
2196                // Add text
2197                gc.begin_text();
2198                gc.set_text_position(100.0, 500.0);
2199                let _ = gc.show_text("Graphics Test");
2200                gc.end_text();
2201
2202                gc.restore_state();
2203            }
2204
2205            doc.add_page(page);
2206
2207            // Should produce valid PDF
2208            let result = doc.to_bytes();
2209            assert!(result.is_ok());
2210        }
2211
2212        #[test]
2213        fn test_document_producer_version() {
2214            let doc = Document::new();
2215
2216            // Producer should contain version
2217            assert!(doc.metadata.producer.is_some());
2218            if let Some(producer) = &doc.metadata.producer {
2219                assert!(producer.contains("oxidize_pdf"));
2220                assert!(producer.contains(env!("CARGO_PKG_VERSION")));
2221            }
2222        }
2223
2224        #[test]
2225        fn test_document_empty_metadata_fields() {
2226            let mut doc = Document::new();
2227
2228            // Set empty strings
2229            doc.set_title("");
2230            doc.set_author("");
2231            doc.set_subject("");
2232            doc.set_keywords("");
2233
2234            // Empty strings should be stored as Some("")
2235            assert_eq!(doc.metadata.title.as_deref(), Some(""));
2236            assert_eq!(doc.metadata.author.as_deref(), Some(""));
2237            assert_eq!(doc.metadata.subject.as_deref(), Some(""));
2238            assert_eq!(doc.metadata.keywords.as_deref(), Some(""));
2239        }
2240
2241        #[test]
2242        fn test_document_very_long_metadata() {
2243            let mut doc = Document::new();
2244
2245            // Create very long strings
2246            let long_title = "A".repeat(1000);
2247            let long_author = "B".repeat(500);
2248            let long_keywords = vec!["keyword"; 100].join(", ");
2249
2250            doc.set_title(&long_title);
2251            doc.set_author(&long_author);
2252            doc.set_keywords(&long_keywords);
2253
2254            assert_eq!(doc.metadata.title.as_deref(), Some(long_title.as_str()));
2255            assert_eq!(doc.metadata.author.as_deref(), Some(long_author.as_str()));
2256            assert!(doc.metadata.keywords.as_ref().unwrap().len() > 500);
2257        }
2258    }
2259}