oxidize_pdf/
document.rs

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