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::structure::{NamedDestinations, OutlineTree, PageTree};
8use crate::text::{FontEncoding, FontWithEncoding};
9use crate::writer::PdfWriter;
10use chrono::{DateTime, Local, Utc};
11use std::collections::{HashMap, 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    #[allow(dead_code)]
36    pub(crate) objects: HashMap<ObjectId, Object>,
37    #[allow(dead_code)]
38    pub(crate) next_object_id: u32,
39    pub(crate) metadata: DocumentMetadata,
40    pub(crate) encryption: Option<DocumentEncryption>,
41    pub(crate) outline: Option<OutlineTree>,
42    pub(crate) named_destinations: Option<NamedDestinations>,
43    #[allow(dead_code)]
44    pub(crate) page_tree: Option<PageTree>,
45    pub(crate) page_labels: Option<PageLabelTree>,
46    /// Default font encoding to use for fonts when no encoding is specified
47    pub(crate) default_font_encoding: Option<FontEncoding>,
48    /// Interactive form data (AcroForm)
49    pub(crate) acro_form: Option<AcroForm>,
50    /// Form manager for handling interactive forms
51    pub(crate) form_manager: Option<FormManager>,
52    /// Whether to compress streams when writing the PDF
53    pub(crate) compress: bool,
54    /// Cache for custom fonts
55    pub(crate) custom_fonts: FontCache,
56    /// Map from font name to embedded font object ID
57    #[allow(dead_code)]
58    pub(crate) embedded_fonts: HashMap<String, ObjectId>,
59}
60
61/// Metadata for a PDF document.
62#[derive(Debug, Clone)]
63pub struct DocumentMetadata {
64    /// Document title
65    pub title: Option<String>,
66    /// Document author
67    pub author: Option<String>,
68    /// Document subject
69    pub subject: Option<String>,
70    /// Document keywords
71    pub keywords: Option<String>,
72    /// Software that created the original document
73    pub creator: Option<String>,
74    /// Software that produced the PDF
75    pub producer: Option<String>,
76    /// Date and time the document was created
77    pub creation_date: Option<DateTime<Utc>>,
78    /// Date and time the document was last modified
79    pub modification_date: Option<DateTime<Utc>>,
80}
81
82impl Default for DocumentMetadata {
83    fn default() -> Self {
84        let now = Utc::now();
85        Self {
86            title: None,
87            author: None,
88            subject: None,
89            keywords: None,
90            creator: Some("oxidize_pdf".to_string()),
91            producer: Some(format!("oxidize_pdf v{}", env!("CARGO_PKG_VERSION"))),
92            creation_date: Some(now),
93            modification_date: Some(now),
94        }
95    }
96}
97
98impl Document {
99    /// Creates a new empty PDF document.
100    pub fn new() -> Self {
101        Self {
102            pages: Vec::new(),
103            objects: HashMap::new(),
104            next_object_id: 1,
105            metadata: DocumentMetadata::default(),
106            encryption: None,
107            outline: None,
108            named_destinations: None,
109            page_tree: None,
110            page_labels: None,
111            default_font_encoding: None,
112            acro_form: None,
113            form_manager: None,
114            compress: true, // Enable compression by default
115            custom_fonts: FontCache::new(),
116            embedded_fonts: HashMap::new(),
117        }
118    }
119
120    /// Adds a page to the document.
121    pub fn add_page(&mut self, page: Page) {
122        self.pages.push(page);
123    }
124
125    /// Sets the document title.
126    pub fn set_title(&mut self, title: impl Into<String>) {
127        self.metadata.title = Some(title.into());
128    }
129
130    /// Sets the document author.
131    pub fn set_author(&mut self, author: impl Into<String>) {
132        self.metadata.author = Some(author.into());
133    }
134
135    /// Sets the document subject.
136    pub fn set_subject(&mut self, subject: impl Into<String>) {
137        self.metadata.subject = Some(subject.into());
138    }
139
140    /// Sets the document keywords.
141    pub fn set_keywords(&mut self, keywords: impl Into<String>) {
142        self.metadata.keywords = Some(keywords.into());
143    }
144
145    /// Set document encryption
146    pub fn set_encryption(&mut self, encryption: DocumentEncryption) {
147        self.encryption = Some(encryption);
148    }
149
150    /// Set simple encryption with passwords
151    pub fn encrypt_with_passwords(
152        &mut self,
153        user_password: impl Into<String>,
154        owner_password: impl Into<String>,
155    ) {
156        self.encryption = Some(DocumentEncryption::with_passwords(
157            user_password,
158            owner_password,
159        ));
160    }
161
162    /// Check if document is encrypted
163    pub fn is_encrypted(&self) -> bool {
164        self.encryption.is_some()
165    }
166
167    /// Set document outline (bookmarks)
168    pub fn set_outline(&mut self, outline: OutlineTree) {
169        self.outline = Some(outline);
170    }
171
172    /// Get document outline
173    pub fn outline(&self) -> Option<&OutlineTree> {
174        self.outline.as_ref()
175    }
176
177    /// Get mutable document outline
178    pub fn outline_mut(&mut self) -> Option<&mut OutlineTree> {
179        self.outline.as_mut()
180    }
181
182    /// Set named destinations
183    pub fn set_named_destinations(&mut self, destinations: NamedDestinations) {
184        self.named_destinations = Some(destinations);
185    }
186
187    /// Get named destinations
188    pub fn named_destinations(&self) -> Option<&NamedDestinations> {
189        self.named_destinations.as_ref()
190    }
191
192    /// Get mutable named destinations
193    pub fn named_destinations_mut(&mut self) -> Option<&mut NamedDestinations> {
194        self.named_destinations.as_mut()
195    }
196
197    /// Set page labels
198    pub fn set_page_labels(&mut self, labels: PageLabelTree) {
199        self.page_labels = Some(labels);
200    }
201
202    /// Get page labels
203    pub fn page_labels(&self) -> Option<&PageLabelTree> {
204        self.page_labels.as_ref()
205    }
206
207    /// Get mutable page labels
208    pub fn page_labels_mut(&mut self) -> Option<&mut PageLabelTree> {
209        self.page_labels.as_mut()
210    }
211
212    /// Get page label for a specific page
213    pub fn get_page_label(&self, page_index: u32) -> String {
214        self.page_labels
215            .as_ref()
216            .and_then(|labels| labels.get_label(page_index))
217            .unwrap_or_else(|| (page_index + 1).to_string())
218    }
219
220    /// Get all page labels
221    pub fn get_all_page_labels(&self) -> Vec<String> {
222        let page_count = self.pages.len() as u32;
223        if let Some(labels) = &self.page_labels {
224            labels.get_all_labels(page_count)
225        } else {
226            (1..=page_count).map(|i| i.to_string()).collect()
227        }
228    }
229
230    /// Sets the document creator (software that created the original document).
231    pub fn set_creator(&mut self, creator: impl Into<String>) {
232        self.metadata.creator = Some(creator.into());
233    }
234
235    /// Sets the document producer (software that produced the PDF).
236    pub fn set_producer(&mut self, producer: impl Into<String>) {
237        self.metadata.producer = Some(producer.into());
238    }
239
240    /// Sets the document creation date.
241    pub fn set_creation_date(&mut self, date: DateTime<Utc>) {
242        self.metadata.creation_date = Some(date);
243    }
244
245    /// Sets the document creation date using local time.
246    pub fn set_creation_date_local(&mut self, date: DateTime<Local>) {
247        self.metadata.creation_date = Some(date.with_timezone(&Utc));
248    }
249
250    /// Sets the document modification date.
251    pub fn set_modification_date(&mut self, date: DateTime<Utc>) {
252        self.metadata.modification_date = Some(date);
253    }
254
255    /// Sets the document modification date using local time.
256    pub fn set_modification_date_local(&mut self, date: DateTime<Local>) {
257        self.metadata.modification_date = Some(date.with_timezone(&Utc));
258    }
259
260    /// Sets the modification date to the current time.
261    pub fn update_modification_date(&mut self) {
262        self.metadata.modification_date = Some(Utc::now());
263    }
264
265    /// Sets the default font encoding for fonts that don't specify an encoding.
266    ///
267    /// This encoding will be applied to fonts in the PDF font dictionary when
268    /// no explicit encoding is specified. Setting this to `None` (the default)
269    /// means no encoding metadata will be added to fonts unless explicitly specified.
270    ///
271    /// # Example
272    ///
273    /// ```rust
274    /// use oxidize_pdf::{Document, text::FontEncoding};
275    ///
276    /// let mut doc = Document::new();
277    /// doc.set_default_font_encoding(Some(FontEncoding::WinAnsiEncoding));
278    /// ```
279    pub fn set_default_font_encoding(&mut self, encoding: Option<FontEncoding>) {
280        self.default_font_encoding = encoding;
281    }
282
283    /// Gets the current default font encoding.
284    pub fn default_font_encoding(&self) -> Option<FontEncoding> {
285        self.default_font_encoding
286    }
287
288    /// Gets all fonts used in the document with their encodings.
289    ///
290    /// This scans all pages and collects the unique fonts used, applying
291    /// the default encoding where no explicit encoding is specified.
292    pub(crate) fn get_fonts_with_encodings(&self) -> Vec<FontWithEncoding> {
293        let mut fonts_used = HashSet::new();
294
295        // Collect fonts from all pages
296        for page in &self.pages {
297            // Get fonts from text content
298            for font in page.get_used_fonts() {
299                let font_with_encoding = match self.default_font_encoding {
300                    Some(default_encoding) => FontWithEncoding::new(font, Some(default_encoding)),
301                    None => FontWithEncoding::without_encoding(font),
302                };
303                fonts_used.insert(font_with_encoding);
304            }
305        }
306
307        fonts_used.into_iter().collect()
308    }
309
310    /// Add a custom font from a file path
311    ///
312    /// # Example
313    ///
314    /// ```rust,no_run
315    /// use oxidize_pdf::Document;
316    ///
317    /// let mut doc = Document::new();
318    /// doc.add_font("MyFont", "path/to/font.ttf").unwrap();
319    /// ```
320    pub fn add_font(
321        &mut self,
322        name: impl Into<String>,
323        path: impl AsRef<std::path::Path>,
324    ) -> Result<()> {
325        let name = name.into();
326        let font = CustomFont::from_file(&name, path)?;
327        self.custom_fonts.add_font(name, font)?;
328        Ok(())
329    }
330
331    /// Add a custom font from byte data
332    ///
333    /// # Example
334    ///
335    /// ```rust,no_run
336    /// use oxidize_pdf::Document;
337    ///
338    /// let mut doc = Document::new();
339    /// let font_data = vec![0; 1000]; // Your font data
340    /// doc.add_font_from_bytes("MyFont", font_data).unwrap();
341    /// ```
342    pub fn add_font_from_bytes(&mut self, name: impl Into<String>, data: Vec<u8>) -> Result<()> {
343        let name = name.into();
344        let font = CustomFont::from_bytes(&name, data)?;
345        self.custom_fonts.add_font(name, font)?;
346        Ok(())
347    }
348
349    /// Get a custom font by name
350    #[allow(dead_code)]
351    pub(crate) fn get_custom_font(&self, name: &str) -> Option<Arc<CustomFont>> {
352        self.custom_fonts.get_font(name)
353    }
354
355    /// Check if a custom font is loaded
356    pub fn has_custom_font(&self, name: &str) -> bool {
357        self.custom_fonts.has_font(name)
358    }
359
360    /// Get all loaded custom font names
361    pub fn custom_font_names(&self) -> Vec<String> {
362        self.custom_fonts.font_names()
363    }
364
365    /// Gets the number of pages in the document.
366    pub fn page_count(&self) -> usize {
367        self.pages.len()
368    }
369
370    /// Gets a reference to the AcroForm (interactive form) if present.
371    pub fn acro_form(&self) -> Option<&AcroForm> {
372        self.acro_form.as_ref()
373    }
374
375    /// Gets a mutable reference to the AcroForm (interactive form) if present.
376    pub fn acro_form_mut(&mut self) -> Option<&mut AcroForm> {
377        self.acro_form.as_mut()
378    }
379
380    /// Enables interactive forms by creating a FormManager if not already present.
381    /// The FormManager handles both the AcroForm and the connection with page widgets.
382    pub fn enable_forms(&mut self) -> &mut FormManager {
383        if self.form_manager.is_none() {
384            self.form_manager = Some(FormManager::new());
385        }
386        if self.acro_form.is_none() {
387            self.acro_form = Some(AcroForm::new());
388        }
389        self.form_manager.as_mut().unwrap()
390    }
391
392    /// Disables interactive forms by removing both the AcroForm and FormManager.
393    pub fn disable_forms(&mut self) {
394        self.acro_form = None;
395        self.form_manager = None;
396    }
397
398    /// Saves the document to a file.
399    ///
400    /// # Errors
401    ///
402    /// Returns an error if the file cannot be created or written.
403    pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
404        // Update modification date before saving
405        self.update_modification_date();
406
407        // Create writer config with document's compression setting
408        let config = crate::writer::WriterConfig {
409            use_xref_streams: false,
410            pdf_version: "1.7".to_string(),
411            compress_streams: self.compress,
412        };
413
414        use std::io::BufWriter;
415        let file = std::fs::File::create(path)?;
416        let writer = BufWriter::new(file);
417        let mut pdf_writer = PdfWriter::with_config(writer, config);
418
419        pdf_writer.write_document(self)?;
420        Ok(())
421    }
422
423    /// Saves the document to a file with custom writer configuration.
424    ///
425    /// # Errors
426    ///
427    /// Returns an error if the file cannot be created or written.
428    pub fn save_with_config(
429        &mut self,
430        path: impl AsRef<std::path::Path>,
431        config: crate::writer::WriterConfig,
432    ) -> Result<()> {
433        use std::io::BufWriter;
434
435        // Update modification date before saving
436        self.update_modification_date();
437
438        // Use the config as provided (don't override compress_streams)
439
440        let file = std::fs::File::create(path)?;
441        let writer = BufWriter::new(file);
442        let mut pdf_writer = PdfWriter::with_config(writer, config);
443        pdf_writer.write_document(self)?;
444        Ok(())
445    }
446
447    /// Writes the document to a buffer.
448    ///
449    /// # Errors
450    ///
451    /// Returns an error if the PDF cannot be generated.
452    pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
453        // Update modification date before writing
454        self.update_modification_date();
455
456        let mut writer = PdfWriter::new_with_writer(buffer);
457        writer.write_document(self)?;
458        Ok(())
459    }
460
461    #[allow(dead_code)]
462    pub(crate) fn allocate_object_id(&mut self) -> ObjectId {
463        let id = ObjectId::new(self.next_object_id, 0);
464        self.next_object_id += 1;
465        id
466    }
467
468    #[allow(dead_code)]
469    pub(crate) fn add_object(&mut self, obj: Object) -> ObjectId {
470        let id = self.allocate_object_id();
471        self.objects.insert(id, obj);
472        id
473    }
474
475    /// Enables or disables compression for PDF streams.
476    ///
477    /// When compression is enabled (default), content streams and XRef streams are compressed
478    /// using Flate/Zlib compression to reduce file size. When disabled, streams are written
479    /// uncompressed, making the PDF larger but easier to debug.
480    ///
481    /// # Arguments
482    ///
483    /// * `compress` - Whether to enable compression
484    ///
485    /// # Example
486    ///
487    /// ```rust
488    /// use oxidize_pdf::{Document, Page};
489    ///
490    /// let mut doc = Document::new();
491    ///
492    /// // Disable compression for debugging
493    /// doc.set_compress(false);
494    ///
495    /// doc.set_title("My Document");
496    /// doc.add_page(Page::a4());
497    ///
498    /// let pdf_bytes = doc.to_bytes().unwrap();
499    /// println!("Uncompressed PDF size: {} bytes", pdf_bytes.len());
500    /// ```
501    pub fn set_compress(&mut self, compress: bool) {
502        self.compress = compress;
503    }
504
505    /// Gets the current compression setting.
506    ///
507    /// # Returns
508    ///
509    /// Returns `true` if compression is enabled, `false` otherwise.
510    pub fn get_compress(&self) -> bool {
511        self.compress
512    }
513
514    /// Generates the PDF document as bytes in memory.
515    ///
516    /// This method provides in-memory PDF generation without requiring file I/O.
517    /// The document is serialized to bytes and returned as a `Vec<u8>`.
518    ///
519    /// # Returns
520    ///
521    /// Returns the PDF document as bytes on success.
522    ///
523    /// # Errors
524    ///
525    /// Returns an error if the document cannot be serialized.
526    ///
527    /// # Example
528    ///
529    /// ```rust
530    /// use oxidize_pdf::{Document, Page};
531    ///
532    /// let mut doc = Document::new();
533    /// doc.set_title("My Document");
534    ///
535    /// let page = Page::a4();
536    /// doc.add_page(page);
537    ///
538    /// let pdf_bytes = doc.to_bytes().unwrap();
539    /// println!("Generated PDF size: {} bytes", pdf_bytes.len());
540    /// ```
541    pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
542        // Update modification date before serialization
543        self.update_modification_date();
544
545        // Create a buffer to write the PDF data to
546        let mut buffer = Vec::new();
547
548        // Create writer config with document's compression setting
549        let config = crate::writer::WriterConfig {
550            use_xref_streams: false,
551            pdf_version: "1.7".to_string(),
552            compress_streams: self.compress,
553        };
554
555        // Use PdfWriter with the buffer as output and config
556        let mut writer = PdfWriter::with_config(&mut buffer, config);
557        writer.write_document(self)?;
558
559        Ok(buffer)
560    }
561
562    /// Generates the PDF document as bytes with custom writer configuration.
563    ///
564    /// This method allows customizing the PDF output (e.g., using XRef streams)
565    /// while still generating the document in memory.
566    ///
567    /// # Arguments
568    ///
569    /// * `config` - Writer configuration options
570    ///
571    /// # Returns
572    ///
573    /// Returns the PDF document as bytes on success.
574    ///
575    /// # Errors
576    ///
577    /// Returns an error if the document cannot be serialized.
578    ///
579    /// # Example
580    ///
581    /// ```rust
582    /// use oxidize_pdf::{Document, Page};
583    /// use oxidize_pdf::writer::WriterConfig;
584    ///
585    /// let mut doc = Document::new();
586    /// doc.set_title("My Document");
587    ///
588    /// let page = Page::a4();
589    /// doc.add_page(page);
590    ///
591    /// let config = WriterConfig {
592    ///     use_xref_streams: true,
593    ///     pdf_version: "1.5".to_string(),
594    ///     compress_streams: true,
595    /// };
596    ///
597    /// let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
598    /// println!("Generated PDF size: {} bytes", pdf_bytes.len());
599    /// ```
600    pub fn to_bytes_with_config(&mut self, config: crate::writer::WriterConfig) -> Result<Vec<u8>> {
601        // Update modification date before serialization
602        self.update_modification_date();
603
604        // Use the config as provided (don't override compress_streams)
605
606        // Create a buffer to write the PDF data to
607        let mut buffer = Vec::new();
608
609        // Use PdfWriter with the buffer as output and custom config
610        let mut writer = PdfWriter::with_config(&mut buffer, config);
611        writer.write_document(self)?;
612
613        Ok(buffer)
614    }
615}
616
617impl Default for Document {
618    fn default() -> Self {
619        Self::new()
620    }
621}
622
623#[cfg(test)]
624mod tests {
625    use super::*;
626
627    #[test]
628    fn test_document_new() {
629        let doc = Document::new();
630        assert!(doc.pages.is_empty());
631        assert!(doc.objects.is_empty());
632        assert_eq!(doc.next_object_id, 1);
633        assert!(doc.metadata.title.is_none());
634        assert!(doc.metadata.author.is_none());
635        assert!(doc.metadata.subject.is_none());
636        assert!(doc.metadata.keywords.is_none());
637        assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
638        assert!(doc
639            .metadata
640            .producer
641            .as_ref()
642            .unwrap()
643            .starts_with("oxidize_pdf"));
644    }
645
646    #[test]
647    fn test_document_default() {
648        let doc = Document::default();
649        assert!(doc.pages.is_empty());
650        assert_eq!(doc.next_object_id, 1);
651    }
652
653    #[test]
654    fn test_add_page() {
655        let mut doc = Document::new();
656        let page1 = Page::a4();
657        let page2 = Page::letter();
658
659        doc.add_page(page1);
660        assert_eq!(doc.pages.len(), 1);
661
662        doc.add_page(page2);
663        assert_eq!(doc.pages.len(), 2);
664    }
665
666    #[test]
667    fn test_set_title() {
668        let mut doc = Document::new();
669        assert!(doc.metadata.title.is_none());
670
671        doc.set_title("Test Document");
672        assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
673
674        doc.set_title(String::from("Another Title"));
675        assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
676    }
677
678    #[test]
679    fn test_set_author() {
680        let mut doc = Document::new();
681        assert!(doc.metadata.author.is_none());
682
683        doc.set_author("John Doe");
684        assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
685    }
686
687    #[test]
688    fn test_set_subject() {
689        let mut doc = Document::new();
690        assert!(doc.metadata.subject.is_none());
691
692        doc.set_subject("Test Subject");
693        assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
694    }
695
696    #[test]
697    fn test_set_keywords() {
698        let mut doc = Document::new();
699        assert!(doc.metadata.keywords.is_none());
700
701        doc.set_keywords("test, pdf, rust");
702        assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
703    }
704
705    #[test]
706    fn test_metadata_default() {
707        let metadata = DocumentMetadata::default();
708        assert!(metadata.title.is_none());
709        assert!(metadata.author.is_none());
710        assert!(metadata.subject.is_none());
711        assert!(metadata.keywords.is_none());
712        assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
713        assert!(metadata
714            .producer
715            .as_ref()
716            .unwrap()
717            .starts_with("oxidize_pdf"));
718    }
719
720    #[test]
721    fn test_allocate_object_id() {
722        let mut doc = Document::new();
723
724        let id1 = doc.allocate_object_id();
725        assert_eq!(id1.number(), 1);
726        assert_eq!(id1.generation(), 0);
727        assert_eq!(doc.next_object_id, 2);
728
729        let id2 = doc.allocate_object_id();
730        assert_eq!(id2.number(), 2);
731        assert_eq!(id2.generation(), 0);
732        assert_eq!(doc.next_object_id, 3);
733    }
734
735    #[test]
736    fn test_add_object() {
737        let mut doc = Document::new();
738        assert!(doc.objects.is_empty());
739
740        let obj = Object::Boolean(true);
741        let id = doc.add_object(obj.clone());
742
743        assert_eq!(id.number(), 1);
744        assert_eq!(doc.objects.len(), 1);
745        assert!(doc.objects.contains_key(&id));
746    }
747
748    #[test]
749    fn test_write_to_buffer() {
750        let mut doc = Document::new();
751        doc.set_title("Buffer Test");
752        doc.add_page(Page::a4());
753
754        let mut buffer = Vec::new();
755        let result = doc.write(&mut buffer);
756
757        assert!(result.is_ok());
758        assert!(!buffer.is_empty());
759        assert!(buffer.starts_with(b"%PDF-1.7"));
760    }
761
762    #[test]
763    fn test_document_with_multiple_pages() {
764        let mut doc = Document::new();
765        doc.set_title("Multi-page Document");
766        doc.set_author("Test Author");
767        doc.set_subject("Testing multiple pages");
768        doc.set_keywords("test, multiple, pages");
769
770        for _ in 0..5 {
771            doc.add_page(Page::a4());
772        }
773
774        assert_eq!(doc.pages.len(), 5);
775        assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
776        assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
777    }
778
779    #[test]
780    fn test_empty_document_write() {
781        let mut doc = Document::new();
782        let mut buffer = Vec::new();
783
784        // Empty document should still produce valid PDF
785        let result = doc.write(&mut buffer);
786        assert!(result.is_ok());
787        assert!(!buffer.is_empty());
788        assert!(buffer.starts_with(b"%PDF-1.7"));
789    }
790
791    // Integration tests for Document ↔ Writer ↔ Parser interactions
792    mod integration_tests {
793        use super::*;
794        use crate::graphics::Color;
795        use crate::text::Font;
796        use std::fs;
797        use tempfile::TempDir;
798
799        #[test]
800        fn test_document_writer_roundtrip() {
801            let temp_dir = TempDir::new().unwrap();
802            let file_path = temp_dir.path().join("test.pdf");
803
804            // Create document with content
805            let mut doc = Document::new();
806            doc.set_title("Integration Test");
807            doc.set_author("Test Author");
808            doc.set_subject("Writer Integration");
809            doc.set_keywords("test, writer, integration");
810
811            let mut page = Page::a4();
812            page.text()
813                .set_font(Font::Helvetica, 12.0)
814                .at(100.0, 700.0)
815                .write("Integration Test Content")
816                .unwrap();
817
818            doc.add_page(page);
819
820            // Write to file
821            let result = doc.save(&file_path);
822            assert!(result.is_ok());
823
824            // Verify file exists and has content
825            assert!(file_path.exists());
826            let metadata = fs::metadata(&file_path).unwrap();
827            assert!(metadata.len() > 0);
828
829            // Read file back to verify PDF format
830            let content = fs::read(&file_path).unwrap();
831            assert!(content.starts_with(b"%PDF-1.7"));
832            // Check for %%EOF with or without newline
833            assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
834        }
835
836        #[test]
837        fn test_document_with_complex_content() {
838            let temp_dir = TempDir::new().unwrap();
839            let file_path = temp_dir.path().join("complex.pdf");
840
841            let mut doc = Document::new();
842            doc.set_title("Complex Content Test");
843
844            // Create page with mixed content
845            let mut page = Page::a4();
846
847            // Add text
848            page.text()
849                .set_font(Font::Helvetica, 14.0)
850                .at(50.0, 750.0)
851                .write("Complex Content Test")
852                .unwrap();
853
854            // Add graphics
855            page.graphics()
856                .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
857                .rectangle(50.0, 500.0, 200.0, 100.0)
858                .fill();
859
860            page.graphics()
861                .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
862                .set_line_width(2.0)
863                .move_to(50.0, 400.0)
864                .line_to(250.0, 400.0)
865                .stroke();
866
867            doc.add_page(page);
868
869            // Write and verify
870            let result = doc.save(&file_path);
871            assert!(result.is_ok());
872            assert!(file_path.exists());
873        }
874
875        #[test]
876        fn test_document_multiple_pages_integration() {
877            let temp_dir = TempDir::new().unwrap();
878            let file_path = temp_dir.path().join("multipage.pdf");
879
880            let mut doc = Document::new();
881            doc.set_title("Multi-page Integration Test");
882
883            // Create multiple pages with different content
884            for i in 1..=5 {
885                let mut page = Page::a4();
886
887                page.text()
888                    .set_font(Font::Helvetica, 16.0)
889                    .at(50.0, 750.0)
890                    .write(&format!("Page {}", i))
891                    .unwrap();
892
893                page.text()
894                    .set_font(Font::Helvetica, 12.0)
895                    .at(50.0, 700.0)
896                    .write(&format!("This is the content for page {}", i))
897                    .unwrap();
898
899                // Add unique graphics for each page
900                let color = match i % 3 {
901                    0 => Color::rgb(1.0, 0.0, 0.0),
902                    1 => Color::rgb(0.0, 1.0, 0.0),
903                    _ => Color::rgb(0.0, 0.0, 1.0),
904                };
905
906                page.graphics()
907                    .set_fill_color(color)
908                    .rectangle(50.0, 600.0, 100.0, 50.0)
909                    .fill();
910
911                doc.add_page(page);
912            }
913
914            // Write and verify
915            let result = doc.save(&file_path);
916            assert!(result.is_ok());
917            assert!(file_path.exists());
918
919            // Verify file size is reasonable for 5 pages
920            let metadata = fs::metadata(&file_path).unwrap();
921            assert!(metadata.len() > 1000); // Should be substantial
922        }
923
924        #[test]
925        fn test_document_metadata_persistence() {
926            let temp_dir = TempDir::new().unwrap();
927            let file_path = temp_dir.path().join("metadata.pdf");
928
929            let mut doc = Document::new();
930            doc.set_title("Metadata Persistence Test");
931            doc.set_author("Test Author");
932            doc.set_subject("Testing metadata preservation");
933            doc.set_keywords("metadata, persistence, test");
934
935            doc.add_page(Page::a4());
936
937            // Write to file
938            let result = doc.save(&file_path);
939            assert!(result.is_ok());
940
941            // Read file content to verify metadata is present
942            let content = fs::read(&file_path).unwrap();
943            let content_str = String::from_utf8_lossy(&content);
944
945            // Check that metadata appears in the PDF
946            assert!(content_str.contains("Metadata Persistence Test"));
947            assert!(content_str.contains("Test Author"));
948        }
949
950        #[test]
951        fn test_document_writer_error_handling() {
952            let mut doc = Document::new();
953            doc.add_page(Page::a4());
954
955            // Test writing to invalid path
956            let result = doc.save("/invalid/path/test.pdf");
957            assert!(result.is_err());
958        }
959
960        #[test]
961        fn test_document_object_management() {
962            let mut doc = Document::new();
963
964            // Add objects and verify they're managed properly
965            let obj1 = Object::Boolean(true);
966            let obj2 = Object::Integer(42);
967            let obj3 = Object::Real(std::f64::consts::PI);
968
969            let id1 = doc.add_object(obj1.clone());
970            let id2 = doc.add_object(obj2.clone());
971            let id3 = doc.add_object(obj3.clone());
972
973            assert_eq!(id1.number(), 1);
974            assert_eq!(id2.number(), 2);
975            assert_eq!(id3.number(), 3);
976
977            assert_eq!(doc.objects.len(), 3);
978            assert!(doc.objects.contains_key(&id1));
979            assert!(doc.objects.contains_key(&id2));
980            assert!(doc.objects.contains_key(&id3));
981
982            // Verify objects are correct
983            assert_eq!(doc.objects.get(&id1), Some(&obj1));
984            assert_eq!(doc.objects.get(&id2), Some(&obj2));
985            assert_eq!(doc.objects.get(&id3), Some(&obj3));
986        }
987
988        #[test]
989        fn test_document_page_integration() {
990            let mut doc = Document::new();
991
992            // Test different page configurations
993            let page1 = Page::a4();
994            let page2 = Page::letter();
995            let mut page3 = Page::new(500.0, 400.0);
996
997            // Add content to custom page
998            page3
999                .text()
1000                .set_font(Font::Helvetica, 10.0)
1001                .at(25.0, 350.0)
1002                .write("Custom size page")
1003                .unwrap();
1004
1005            doc.add_page(page1);
1006            doc.add_page(page2);
1007            doc.add_page(page3);
1008
1009            assert_eq!(doc.pages.len(), 3);
1010
1011            // Verify pages maintain their properties (actual dimensions may vary)
1012            assert!(doc.pages[0].width() > 500.0); // A4 width is reasonable
1013            assert!(doc.pages[0].height() > 700.0); // A4 height is reasonable
1014            assert!(doc.pages[1].width() > 500.0); // Letter width is reasonable
1015            assert!(doc.pages[1].height() > 700.0); // Letter height is reasonable
1016            assert_eq!(doc.pages[2].width(), 500.0); // Custom width
1017            assert_eq!(doc.pages[2].height(), 400.0); // Custom height
1018        }
1019
1020        #[test]
1021        fn test_document_content_generation() {
1022            let temp_dir = TempDir::new().unwrap();
1023            let file_path = temp_dir.path().join("content.pdf");
1024
1025            let mut doc = Document::new();
1026            doc.set_title("Content Generation Test");
1027
1028            let mut page = Page::a4();
1029
1030            // Generate content programmatically
1031            for i in 0..10 {
1032                let y_pos = 700.0 - (i as f64 * 30.0);
1033                page.text()
1034                    .set_font(Font::Helvetica, 12.0)
1035                    .at(50.0, y_pos)
1036                    .write(&format!("Generated line {}", i + 1))
1037                    .unwrap();
1038            }
1039
1040            doc.add_page(page);
1041
1042            // Write and verify
1043            let result = doc.save(&file_path);
1044            assert!(result.is_ok());
1045            assert!(file_path.exists());
1046
1047            // Verify content was generated
1048            let metadata = fs::metadata(&file_path).unwrap();
1049            assert!(metadata.len() > 500); // Should contain substantial content
1050        }
1051
1052        #[test]
1053        fn test_document_buffer_vs_file_write() {
1054            let temp_dir = TempDir::new().unwrap();
1055            let file_path = temp_dir.path().join("buffer_vs_file.pdf");
1056
1057            let mut doc = Document::new();
1058            doc.set_title("Buffer vs File Test");
1059            doc.add_page(Page::a4());
1060
1061            // Write to buffer
1062            let mut buffer = Vec::new();
1063            let buffer_result = doc.write(&mut buffer);
1064            assert!(buffer_result.is_ok());
1065
1066            // Write to file
1067            let file_result = doc.save(&file_path);
1068            assert!(file_result.is_ok());
1069
1070            // Read file back
1071            let file_content = fs::read(&file_path).unwrap();
1072
1073            // Both should be valid PDFs with same structure (timestamps may differ)
1074            assert!(buffer.starts_with(b"%PDF-1.7"));
1075            assert!(file_content.starts_with(b"%PDF-1.7"));
1076            assert!(buffer.ends_with(b"%%EOF\n"));
1077            assert!(file_content.ends_with(b"%%EOF\n"));
1078
1079            // Both should contain the same title
1080            let buffer_str = String::from_utf8_lossy(&buffer);
1081            let file_str = String::from_utf8_lossy(&file_content);
1082            assert!(buffer_str.contains("Buffer vs File Test"));
1083            assert!(file_str.contains("Buffer vs File Test"));
1084        }
1085
1086        #[test]
1087        fn test_document_large_content_handling() {
1088            let temp_dir = TempDir::new().unwrap();
1089            let file_path = temp_dir.path().join("large_content.pdf");
1090
1091            let mut doc = Document::new();
1092            doc.set_title("Large Content Test");
1093
1094            let mut page = Page::a4();
1095
1096            // Add large amount of text content - make it much larger
1097            let large_text =
1098                "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(200);
1099            page.text()
1100                .set_font(Font::Helvetica, 10.0)
1101                .at(50.0, 750.0)
1102                .write(&large_text)
1103                .unwrap();
1104
1105            doc.add_page(page);
1106
1107            // Write and verify
1108            let result = doc.save(&file_path);
1109            assert!(result.is_ok());
1110            assert!(file_path.exists());
1111
1112            // Verify large content was handled properly - reduce expectation
1113            let metadata = fs::metadata(&file_path).unwrap();
1114            assert!(metadata.len() > 500); // Should be substantial but realistic
1115        }
1116
1117        #[test]
1118        fn test_document_incremental_building() {
1119            let temp_dir = TempDir::new().unwrap();
1120            let file_path = temp_dir.path().join("incremental.pdf");
1121
1122            let mut doc = Document::new();
1123
1124            // Build document incrementally
1125            doc.set_title("Incremental Building Test");
1126
1127            // Add first page
1128            let mut page1 = Page::a4();
1129            page1
1130                .text()
1131                .set_font(Font::Helvetica, 12.0)
1132                .at(50.0, 750.0)
1133                .write("First page content")
1134                .unwrap();
1135            doc.add_page(page1);
1136
1137            // Add metadata
1138            doc.set_author("Incremental Author");
1139            doc.set_subject("Incremental Subject");
1140
1141            // Add second page
1142            let mut page2 = Page::a4();
1143            page2
1144                .text()
1145                .set_font(Font::Helvetica, 12.0)
1146                .at(50.0, 750.0)
1147                .write("Second page content")
1148                .unwrap();
1149            doc.add_page(page2);
1150
1151            // Add more metadata
1152            doc.set_keywords("incremental, building, test");
1153
1154            // Final write
1155            let result = doc.save(&file_path);
1156            assert!(result.is_ok());
1157            assert!(file_path.exists());
1158
1159            // Verify final state
1160            assert_eq!(doc.pages.len(), 2);
1161            assert_eq!(
1162                doc.metadata.title,
1163                Some("Incremental Building Test".to_string())
1164            );
1165            assert_eq!(doc.metadata.author, Some("Incremental Author".to_string()));
1166            assert_eq!(
1167                doc.metadata.subject,
1168                Some("Incremental Subject".to_string())
1169            );
1170            assert_eq!(
1171                doc.metadata.keywords,
1172                Some("incremental, building, test".to_string())
1173            );
1174        }
1175
1176        #[test]
1177        fn test_document_concurrent_page_operations() {
1178            let mut doc = Document::new();
1179            doc.set_title("Concurrent Operations Test");
1180
1181            // Simulate concurrent-like operations
1182            let mut pages = Vec::new();
1183
1184            // Create multiple pages
1185            for i in 0..5 {
1186                let mut page = Page::a4();
1187                page.text()
1188                    .set_font(Font::Helvetica, 12.0)
1189                    .at(50.0, 750.0)
1190                    .write(&format!("Concurrent page {}", i))
1191                    .unwrap();
1192                pages.push(page);
1193            }
1194
1195            // Add all pages
1196            for page in pages {
1197                doc.add_page(page);
1198            }
1199
1200            assert_eq!(doc.pages.len(), 5);
1201
1202            // Verify each page maintains its content
1203            let temp_dir = TempDir::new().unwrap();
1204            let file_path = temp_dir.path().join("concurrent.pdf");
1205            let result = doc.save(&file_path);
1206            assert!(result.is_ok());
1207        }
1208
1209        #[test]
1210        fn test_document_memory_efficiency() {
1211            let mut doc = Document::new();
1212            doc.set_title("Memory Efficiency Test");
1213
1214            // Add multiple pages with content
1215            for i in 0..10 {
1216                let mut page = Page::a4();
1217                page.text()
1218                    .set_font(Font::Helvetica, 12.0)
1219                    .at(50.0, 700.0)
1220                    .write(&format!("Memory test page {}", i))
1221                    .unwrap();
1222                doc.add_page(page);
1223            }
1224
1225            // Write to buffer to test memory usage
1226            let mut buffer = Vec::new();
1227            let result = doc.write(&mut buffer);
1228            assert!(result.is_ok());
1229            assert!(!buffer.is_empty());
1230
1231            // Buffer should be reasonable size
1232            assert!(buffer.len() < 1_000_000); // Should be less than 1MB for simple content
1233        }
1234
1235        #[test]
1236        fn test_document_creator_producer() {
1237            let mut doc = Document::new();
1238
1239            // Default values
1240            assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
1241            assert!(doc
1242                .metadata
1243                .producer
1244                .as_ref()
1245                .unwrap()
1246                .contains("oxidize_pdf"));
1247
1248            // Set custom values
1249            doc.set_creator("My Application");
1250            doc.set_producer("My PDF Library v1.0");
1251
1252            assert_eq!(doc.metadata.creator, Some("My Application".to_string()));
1253            assert_eq!(
1254                doc.metadata.producer,
1255                Some("My PDF Library v1.0".to_string())
1256            );
1257        }
1258
1259        #[test]
1260        fn test_document_dates() {
1261            use chrono::{TimeZone, Utc};
1262
1263            let mut doc = Document::new();
1264
1265            // Check default dates are set
1266            assert!(doc.metadata.creation_date.is_some());
1267            assert!(doc.metadata.modification_date.is_some());
1268
1269            // Set specific dates
1270            let creation_date = Utc.with_ymd_and_hms(2023, 1, 1, 12, 0, 0).unwrap();
1271            let mod_date = Utc.with_ymd_and_hms(2023, 6, 15, 18, 30, 0).unwrap();
1272
1273            doc.set_creation_date(creation_date);
1274            doc.set_modification_date(mod_date);
1275
1276            assert_eq!(doc.metadata.creation_date, Some(creation_date));
1277            assert_eq!(doc.metadata.modification_date, Some(mod_date));
1278        }
1279
1280        #[test]
1281        fn test_document_dates_local() {
1282            use chrono::{Local, TimeZone};
1283
1284            let mut doc = Document::new();
1285
1286            // Test setting dates with local time
1287            let local_date = Local.with_ymd_and_hms(2023, 12, 25, 10, 30, 0).unwrap();
1288            doc.set_creation_date_local(local_date);
1289
1290            // Verify it was converted to UTC
1291            assert!(doc.metadata.creation_date.is_some());
1292            // Just verify the date was set, don't compare exact values due to timezone complexities
1293            assert!(doc.metadata.creation_date.is_some());
1294        }
1295
1296        #[test]
1297        fn test_update_modification_date() {
1298            let mut doc = Document::new();
1299
1300            let initial_mod_date = doc.metadata.modification_date;
1301            assert!(initial_mod_date.is_some());
1302
1303            // Sleep briefly to ensure time difference
1304            std::thread::sleep(std::time::Duration::from_millis(10));
1305
1306            doc.update_modification_date();
1307
1308            let new_mod_date = doc.metadata.modification_date;
1309            assert!(new_mod_date.is_some());
1310            assert!(new_mod_date.unwrap() > initial_mod_date.unwrap());
1311        }
1312
1313        #[test]
1314        fn test_document_save_updates_modification_date() {
1315            let temp_dir = TempDir::new().unwrap();
1316            let file_path = temp_dir.path().join("mod_date_test.pdf");
1317
1318            let mut doc = Document::new();
1319            doc.add_page(Page::a4());
1320
1321            let initial_mod_date = doc.metadata.modification_date;
1322
1323            // Sleep briefly to ensure time difference
1324            std::thread::sleep(std::time::Duration::from_millis(10));
1325
1326            doc.save(&file_path).unwrap();
1327
1328            // Modification date should be updated
1329            assert!(doc.metadata.modification_date.unwrap() > initial_mod_date.unwrap());
1330        }
1331
1332        #[test]
1333        fn test_document_metadata_complete() {
1334            let mut doc = Document::new();
1335
1336            // Set all metadata fields
1337            doc.set_title("Complete Metadata Test");
1338            doc.set_author("Test Author");
1339            doc.set_subject("Testing all metadata fields");
1340            doc.set_keywords("test, metadata, complete");
1341            doc.set_creator("Test Application v1.0");
1342            doc.set_producer("oxidize_pdf Test Suite");
1343
1344            // Verify all fields
1345            assert_eq!(
1346                doc.metadata.title,
1347                Some("Complete Metadata Test".to_string())
1348            );
1349            assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
1350            assert_eq!(
1351                doc.metadata.subject,
1352                Some("Testing all metadata fields".to_string())
1353            );
1354            assert_eq!(
1355                doc.metadata.keywords,
1356                Some("test, metadata, complete".to_string())
1357            );
1358            assert_eq!(
1359                doc.metadata.creator,
1360                Some("Test Application v1.0".to_string())
1361            );
1362            assert_eq!(
1363                doc.metadata.producer,
1364                Some("oxidize_pdf Test Suite".to_string())
1365            );
1366            assert!(doc.metadata.creation_date.is_some());
1367            assert!(doc.metadata.modification_date.is_some());
1368        }
1369
1370        #[test]
1371        fn test_document_to_bytes() {
1372            let mut doc = Document::new();
1373            doc.set_title("Test Document");
1374            doc.set_author("Test Author");
1375
1376            let page = Page::a4();
1377            doc.add_page(page);
1378
1379            // Generate PDF as bytes
1380            let pdf_bytes = doc.to_bytes().unwrap();
1381
1382            // Basic validation
1383            assert!(!pdf_bytes.is_empty());
1384            assert!(pdf_bytes.len() > 100); // Should be reasonable size
1385
1386            // Check PDF header
1387            let header = &pdf_bytes[0..5];
1388            assert_eq!(header, b"%PDF-");
1389
1390            // Check for some basic PDF structure
1391            let pdf_str = String::from_utf8_lossy(&pdf_bytes);
1392            assert!(pdf_str.contains("Test Document"));
1393            assert!(pdf_str.contains("Test Author"));
1394        }
1395
1396        #[test]
1397        fn test_document_to_bytes_with_config() {
1398            let mut doc = Document::new();
1399            doc.set_title("Test Document XRef");
1400
1401            let page = Page::a4();
1402            doc.add_page(page);
1403
1404            let config = crate::writer::WriterConfig {
1405                use_xref_streams: true,
1406                pdf_version: "1.5".to_string(),
1407                compress_streams: true,
1408            };
1409
1410            // Generate PDF with custom config
1411            let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1412
1413            // Basic validation
1414            assert!(!pdf_bytes.is_empty());
1415            assert!(pdf_bytes.len() > 100);
1416
1417            // Check PDF header with correct version
1418            let header = String::from_utf8_lossy(&pdf_bytes[0..8]);
1419            assert!(header.contains("PDF-1.5"));
1420        }
1421
1422        #[test]
1423        fn test_to_bytes_vs_save_equivalence() {
1424            use std::fs;
1425            use tempfile::NamedTempFile;
1426
1427            // Create two identical documents
1428            let mut doc1 = Document::new();
1429            doc1.set_title("Equivalence Test");
1430            doc1.add_page(Page::a4());
1431
1432            let mut doc2 = Document::new();
1433            doc2.set_title("Equivalence Test");
1434            doc2.add_page(Page::a4());
1435
1436            // Generate bytes
1437            let pdf_bytes = doc1.to_bytes().unwrap();
1438
1439            // Save to file
1440            let temp_file = NamedTempFile::new().unwrap();
1441            doc2.save(temp_file.path()).unwrap();
1442            let file_bytes = fs::read(temp_file.path()).unwrap();
1443
1444            // Both should generate similar structure (lengths may vary due to timestamps)
1445            assert!(!pdf_bytes.is_empty());
1446            assert!(!file_bytes.is_empty());
1447            assert_eq!(&pdf_bytes[0..5], &file_bytes[0..5]); // PDF headers should match
1448        }
1449
1450        #[test]
1451        fn test_document_set_compress() {
1452            let mut doc = Document::new();
1453            doc.set_title("Compression Test");
1454            doc.add_page(Page::a4());
1455
1456            // Default should be compressed
1457            assert!(doc.get_compress());
1458
1459            // Test with compression enabled
1460            doc.set_compress(true);
1461            let compressed_bytes = doc.to_bytes().unwrap();
1462
1463            // Test with compression disabled
1464            doc.set_compress(false);
1465            let uncompressed_bytes = doc.to_bytes().unwrap();
1466
1467            // Uncompressed should generally be larger (though not always guaranteed)
1468            assert!(!compressed_bytes.is_empty());
1469            assert!(!uncompressed_bytes.is_empty());
1470
1471            // Both should be valid PDFs
1472            assert_eq!(&compressed_bytes[0..5], b"%PDF-");
1473            assert_eq!(&uncompressed_bytes[0..5], b"%PDF-");
1474        }
1475
1476        #[test]
1477        fn test_document_compression_config_inheritance() {
1478            let mut doc = Document::new();
1479            doc.set_title("Config Inheritance Test");
1480            doc.add_page(Page::a4());
1481
1482            // Set document compression to false
1483            doc.set_compress(false);
1484
1485            // Create config with compression true (should be overridden)
1486            let config = crate::writer::WriterConfig {
1487                use_xref_streams: false,
1488                pdf_version: "1.7".to_string(),
1489                compress_streams: true,
1490            };
1491
1492            // Document setting should take precedence
1493            let pdf_bytes = doc.to_bytes_with_config(config).unwrap();
1494
1495            // Should be valid PDF
1496            assert!(!pdf_bytes.is_empty());
1497            assert_eq!(&pdf_bytes[0..5], b"%PDF-");
1498        }
1499    }
1500}