Skip to main content

oxidize_pdf/writer/pdf_writer/
mod.rs

1use crate::document::Document;
2use crate::error::{PdfError, Result};
3use crate::objects::{Dictionary, Object, ObjectId};
4use crate::text::fonts::embedding::CjkFontType;
5use crate::writer::{ObjectStreamConfig, ObjectStreamWriter, XRefStreamWriter};
6use chrono::{DateTime, Utc};
7use std::collections::HashMap;
8use std::io::{BufWriter, Write};
9use std::path::Path;
10
11/// Configuration for PDF writer
12#[derive(Debug, Clone)]
13pub struct WriterConfig {
14    /// Use XRef streams instead of traditional XRef tables (PDF 1.5+)
15    pub use_xref_streams: bool,
16    /// Use Object Streams for compressing multiple objects together (PDF 1.5+)
17    pub use_object_streams: bool,
18    /// PDF version to write (default: 1.7)
19    pub pdf_version: String,
20    /// Enable compression for streams (default: true)
21    pub compress_streams: bool,
22    /// Enable incremental updates mode (ISO 32000-1 §7.5.6)
23    pub incremental_update: bool,
24}
25
26impl Default for WriterConfig {
27    fn default() -> Self {
28        Self {
29            use_xref_streams: false,
30            use_object_streams: false,
31            pdf_version: "1.7".to_string(),
32            compress_streams: true,
33            incremental_update: false,
34        }
35    }
36}
37
38impl WriterConfig {
39    /// Create a modern PDF 1.5+ configuration with all compression features enabled
40    pub fn modern() -> Self {
41        Self {
42            use_xref_streams: true,
43            use_object_streams: true,
44            pdf_version: "1.5".to_string(),
45            compress_streams: true,
46            incremental_update: false,
47        }
48    }
49
50    /// Create a legacy PDF 1.4 configuration without modern compression
51    pub fn legacy() -> Self {
52        Self {
53            use_xref_streams: false,
54            use_object_streams: false,
55            pdf_version: "1.4".to_string(),
56            compress_streams: true,
57            incremental_update: false,
58        }
59    }
60
61    /// Create configuration for incremental updates (ISO 32000-1 §7.5.6)
62    pub fn incremental() -> Self {
63        Self {
64            use_xref_streams: false,
65            use_object_streams: false,
66            pdf_version: "1.4".to_string(),
67            compress_streams: true,
68            incremental_update: true,
69        }
70    }
71}
72
73pub struct PdfWriter<W: Write> {
74    writer: W,
75    xref_positions: HashMap<ObjectId, u64>,
76    current_position: u64,
77    next_object_id: u32,
78    // Maps for tracking object IDs during writing
79    catalog_id: Option<ObjectId>,
80    pages_id: Option<ObjectId>,
81    info_id: Option<ObjectId>,
82    // Maps for tracking form fields and their widgets
83    #[allow(dead_code)]
84    field_widget_map: HashMap<String, Vec<ObjectId>>, // field name -> widget IDs
85    #[allow(dead_code)]
86    field_id_map: HashMap<String, ObjectId>, // field name -> field ID
87    form_field_ids: Vec<ObjectId>, // form field IDs to add to page annotations
88    page_ids: Vec<ObjectId>,       // page IDs for form field references
89    // Configuration
90    config: WriterConfig,
91    // Characters used in document (for font subsetting)
92    document_used_chars: Option<std::collections::HashSet<char>>,
93    // Object stream buffering (when use_object_streams is enabled)
94    buffered_objects: HashMap<ObjectId, Vec<u8>>,
95    compressed_object_map: HashMap<ObjectId, (ObjectId, u32)>, // obj_id -> (stream_id, index)
96    // Incremental update support (ISO 32000-1 §7.5.6)
97    prev_xref_offset: Option<u64>,
98    base_pdf_size: Option<u64>,
99    // Encryption support
100    encrypt_obj_id: Option<ObjectId>,
101    file_id: Option<Vec<u8>>,
102    encryption_state: Option<WriterEncryptionState>,
103    pending_encrypt_dict: Option<Dictionary>,
104}
105
106/// Holds the encryption key and encryptor for encrypting objects during write
107struct WriterEncryptionState {
108    encryptor: crate::encryption::ObjectEncryptor,
109}
110
111impl<W: Write> PdfWriter<W> {
112    pub fn new_with_writer(writer: W) -> Self {
113        Self::with_config(writer, WriterConfig::default())
114    }
115
116    pub fn with_config(writer: W, config: WriterConfig) -> Self {
117        Self {
118            writer,
119            xref_positions: HashMap::new(),
120            current_position: 0,
121            next_object_id: 1, // Start at 1 for sequential numbering
122            catalog_id: None,
123            pages_id: None,
124            info_id: None,
125            field_widget_map: HashMap::new(),
126            field_id_map: HashMap::new(),
127            form_field_ids: Vec::new(),
128            page_ids: Vec::new(),
129            config,
130            document_used_chars: None,
131            buffered_objects: HashMap::new(),
132            compressed_object_map: HashMap::new(),
133            prev_xref_offset: None,
134            base_pdf_size: None,
135            encrypt_obj_id: None,
136            file_id: None,
137            encryption_state: None,
138            pending_encrypt_dict: None,
139        }
140    }
141
142    pub fn write_document(&mut self, document: &mut Document) -> Result<()> {
143        // Store used characters for font subsetting
144        if !document.used_characters.is_empty() {
145            self.document_used_chars = Some(document.used_characters.clone());
146        }
147
148        self.write_header()?;
149
150        // Reserve object IDs for fixed objects (written in order)
151        self.catalog_id = Some(self.allocate_object_id());
152        self.pages_id = Some(self.allocate_object_id());
153        self.info_id = Some(self.allocate_object_id());
154
155        // Initialize encryption state BEFORE writing objects
156        // (objects need to be encrypted as they are written)
157        if let Some(ref encryption) = document.encryption {
158            self.init_encryption(encryption)?;
159        }
160
161        // Write custom fonts first (so pages can reference them)
162        let font_refs = self.write_fonts(document)?;
163
164        // Write pages (they contain widget annotations and font references)
165        self.write_pages(document, &font_refs)?;
166
167        // Write form fields (must be after pages so we can track widgets)
168        self.write_form_fields(document)?;
169
170        // Write catalog (must be after forms so AcroForm has correct field references)
171        self.write_catalog(document)?;
172
173        // Write document info
174        self.write_info(document)?;
175
176        // Write /Encrypt dict AFTER all objects (it must NOT be encrypted itself)
177        self.write_encryption_dict()?;
178
179        // Flush buffered objects as object streams (if enabled)
180        if self.config.use_object_streams {
181            self.flush_object_streams()?;
182        }
183
184        // Write xref table or stream
185        let xref_position = self.current_position;
186        if self.config.use_xref_streams {
187            self.write_xref_stream()?;
188        } else {
189            self.write_xref()?;
190        }
191
192        // Write trailer (only for traditional xref)
193        if !self.config.use_xref_streams {
194            self.write_trailer(xref_position)?;
195        }
196
197        if let Ok(()) = self.writer.flush() {
198            // Flush succeeded
199        }
200        Ok(())
201    }
202
203    /// Write an incremental update to an existing PDF (ISO 32000-1 §7.5.6)
204    ///
205    /// This appends new/modified objects to the end of an existing PDF file
206    /// without modifying the original content. The base PDF is copied first,
207    /// then new pages are ADDED to the end of the document.
208    ///
209    /// For REPLACING specific pages (e.g., form filling), use `write_incremental_with_page_replacement`.
210    ///
211    /// # Arguments
212    ///
213    /// * `base_pdf_path` - Path to the existing PDF file
214    /// * `document` - Document containing NEW pages to add
215    ///
216    /// # Returns
217    ///
218    /// Returns Ok(()) if the incremental update was written successfully
219    ///
220    /// # Example - Adding Pages
221    ///
222    /// ```no_run
223    /// use oxidize_pdf::{Document, Page, writer::{PdfWriter, WriterConfig}};
224    /// use std::fs::File;
225    /// use std::io::BufWriter;
226    ///
227    /// let mut doc = Document::new();
228    /// doc.add_page(Page::a4()); // This will be added as a NEW page
229    ///
230    /// let file = File::create("output.pdf").unwrap();
231    /// let writer = BufWriter::new(file);
232    /// let config = WriterConfig::incremental();
233    /// let mut pdf_writer = PdfWriter::with_config(writer, config);
234    /// pdf_writer.write_incremental_update("base.pdf", &mut doc).unwrap();
235    /// ```
236    pub fn write_incremental_update(
237        &mut self,
238        base_pdf_path: impl AsRef<std::path::Path>,
239        document: &mut Document,
240    ) -> Result<()> {
241        use std::io::{BufReader, Read, Seek, SeekFrom};
242
243        // Step 1: Parse the base PDF to get catalog and page information
244        let base_pdf_file = std::fs::File::open(base_pdf_path.as_ref())?;
245        let mut pdf_reader = crate::parser::PdfReader::new(BufReader::new(base_pdf_file))?;
246
247        // Get catalog from base PDF
248        let base_catalog = pdf_reader.catalog()?;
249
250        // Extract Pages reference from base catalog
251        let (base_pages_id, base_pages_gen) = base_catalog
252            .get("Pages")
253            .and_then(|obj| {
254                if let crate::parser::objects::PdfObject::Reference(id, gen) = obj {
255                    Some((*id, *gen))
256                } else {
257                    None
258                }
259            })
260            .ok_or_else(|| {
261                crate::error::PdfError::InvalidStructure(
262                    "Base PDF catalog missing /Pages reference".to_string(),
263                )
264            })?;
265
266        // Get the pages dictionary from the base PDF using the reference
267        let base_pages_obj = pdf_reader.get_object(base_pages_id, base_pages_gen)?;
268        let base_pages_kids = if let crate::parser::objects::PdfObject::Dictionary(dict) =
269            base_pages_obj
270        {
271            dict.get("Kids")
272                .and_then(|obj| {
273                    if let crate::parser::objects::PdfObject::Array(arr) = obj {
274                        // Convert PdfObject::Reference to writer::Object::Reference
275                        // PdfArray.0 gives access to the internal Vec<PdfObject>
276                        Some(
277                            arr.0
278                                .iter()
279                                .filter_map(|item| {
280                                    if let crate::parser::objects::PdfObject::Reference(id, gen) =
281                                        item
282                                    {
283                                        Some(crate::objects::Object::Reference(
284                                            crate::objects::ObjectId::new(*id, *gen),
285                                        ))
286                                    } else {
287                                        None
288                                    }
289                                })
290                                .collect::<Vec<_>>(),
291                        )
292                    } else {
293                        None
294                    }
295                })
296                .unwrap_or_default()
297        } else {
298            Vec::new()
299        };
300
301        // Count existing pages
302        let base_page_count = base_pages_kids.len();
303
304        // Step 2: Copy the base PDF content
305        let base_pdf = std::fs::File::open(base_pdf_path.as_ref())?;
306        let mut base_reader = BufReader::new(base_pdf);
307
308        // Find the startxref offset in the base PDF
309        base_reader.seek(SeekFrom::End(-100))?;
310        let mut end_buffer = vec![0u8; 100];
311        let bytes_read = base_reader.read(&mut end_buffer)?;
312        end_buffer.truncate(bytes_read);
313
314        let end_str = String::from_utf8_lossy(&end_buffer);
315        let prev_xref = if let Some(startxref_pos) = end_str.find("startxref") {
316            let after_startxref = &end_str[startxref_pos + 9..];
317
318            let number_str: String = after_startxref
319                .chars()
320                .skip_while(|c| c.is_whitespace())
321                .take_while(|c| c.is_ascii_digit())
322                .collect();
323
324            number_str.parse::<u64>().map_err(|_| {
325                crate::error::PdfError::InvalidStructure(
326                    "Could not parse startxref offset".to_string(),
327                )
328            })?
329        } else {
330            return Err(crate::error::PdfError::InvalidStructure(
331                "startxref not found in base PDF".to_string(),
332            ));
333        };
334
335        // Copy entire base PDF
336        base_reader.seek(SeekFrom::Start(0))?;
337        let base_size = std::io::copy(&mut base_reader, &mut self.writer)? as u64;
338
339        // Store base PDF info for trailer
340        self.prev_xref_offset = Some(prev_xref);
341        self.base_pdf_size = Some(base_size);
342        self.current_position = base_size;
343
344        // Step 3: Write new/modified objects only
345        if !document.used_characters.is_empty() {
346            self.document_used_chars = Some(document.used_characters.clone());
347        }
348
349        // Allocate IDs for new objects
350        self.catalog_id = Some(self.allocate_object_id());
351        self.pages_id = Some(self.allocate_object_id());
352        self.info_id = Some(self.allocate_object_id());
353
354        // Write custom fonts first
355        let font_refs = self.write_fonts(document)?;
356
357        // Write NEW pages only (not rewriting all pages)
358        self.write_pages(document, &font_refs)?;
359
360        // Write form fields
361        self.write_form_fields(document)?;
362
363        // Step 4: Write modified catalog that references BOTH old and new pages
364        let catalog_id = self.get_catalog_id()?;
365        let new_pages_id = self.get_pages_id()?;
366
367        let mut catalog = crate::objects::Dictionary::new();
368        catalog.set("Type", crate::objects::Object::Name("Catalog".to_string()));
369        catalog.set("Pages", crate::objects::Object::Reference(new_pages_id));
370
371        // Note: For now, we only preserve the Pages reference.
372        // Full catalog preservation (Outlines, AcroForm, etc.) would require
373        // converting parser::PdfObject to writer::Object, which is a future enhancement.
374
375        self.write_object(catalog_id, crate::objects::Object::Dictionary(catalog))?;
376
377        // Step 5: Write new Pages tree that includes BOTH base pages and new pages
378        let mut all_pages_kids = base_pages_kids;
379
380        // Add references to new pages
381        for page_id in &self.page_ids {
382            all_pages_kids.push(crate::objects::Object::Reference(*page_id));
383        }
384
385        let mut pages_dict = crate::objects::Dictionary::new();
386        pages_dict.set("Type", crate::objects::Object::Name("Pages".to_string()));
387        pages_dict.set("Kids", crate::objects::Object::Array(all_pages_kids));
388        pages_dict.set(
389            "Count",
390            crate::objects::Object::Integer((base_page_count + self.page_ids.len()) as i64),
391        );
392
393        self.write_object(new_pages_id, crate::objects::Object::Dictionary(pages_dict))?;
394
395        // Write document info
396        self.write_info(document)?;
397
398        // Step 6: Write new XRef table with /Prev pointer
399        let xref_position = self.current_position;
400        self.write_xref()?;
401
402        // Step 7: Write trailer with /Prev
403        self.write_trailer(xref_position)?;
404
405        self.writer.flush()?;
406        Ok(())
407    }
408
409    /// Replaces pages in an existing PDF using incremental update structure (ISO 32000-1 §7.5.6).
410    ///
411    /// # Use Cases
412    /// This API is ideal for:
413    /// - **Dynamic page generation**: You have logic to generate complete pages from data
414    /// - **Template variants**: Switching between multiple pre-generated page versions
415    /// - **Page repair**: Regenerating corrupted or problematic pages from scratch
416    ///
417    /// # Manual Content Recreation Required
418    /// **IMPORTANT**: This API requires you to **manually recreate** the entire page content.
419    /// The replaced page will contain ONLY what you provide in `document.pages`.
420    ///
421    /// If you need to modify existing content (e.g., fill form fields on an existing page),
422    /// you must recreate the base content AND add your modifications.
423    ///
424    /// # Example: Form Filling with Manual Recreation
425    /// ```rust,no_run
426    /// use oxidize_pdf::{Document, Page, text::Font, writer::{PdfWriter, WriterConfig}};
427    /// use std::fs::File;
428    /// use std::io::BufWriter;
429    ///
430    /// let mut filled_doc = Document::new();
431    /// let mut page = Page::a4();
432    ///
433    /// // Step 1: Recreate the template content (REQUIRED - you must know this)
434    /// page.text()
435    ///     .set_font(Font::Helvetica, 12.0)
436    ///     .at(50.0, 700.0)
437    ///     .write("Name: _______________________________")?;
438    ///
439    /// // Step 2: Add your filled data at the appropriate position
440    /// page.text()
441    ///     .set_font(Font::Helvetica, 12.0)
442    ///     .at(110.0, 700.0)
443    ///     .write("John Smith")?;
444    ///
445    /// filled_doc.add_page(page);
446    ///
447    /// let file = File::create("filled.pdf")?;
448    /// let writer = BufWriter::new(file);
449    /// let mut pdf_writer = PdfWriter::with_config(writer, WriterConfig::incremental());
450    ///
451    /// pdf_writer.write_incremental_with_page_replacement("template.pdf", &mut filled_doc)?;
452    /// # Ok::<(), Box<dyn std::error::Error>>(())
453    /// ```
454    ///
455    /// # ISO Compliance
456    /// This function implements ISO 32000-1 §7.5.6 incremental updates:
457    /// - Preserves original PDF bytes (append-only)
458    /// - Uses /Prev pointer in trailer
459    /// - Maintains cross-reference chain
460    /// - Compatible with digital signatures on base PDF
461    ///
462    /// # Future: Automatic Overlay API
463    /// For automatic form filling (load + modify + save) without manual recreation,
464    /// a future `write_incremental_with_overlay()` API is planned. This will require
465    /// implementation of `Document::load()` and content overlay system.
466    ///
467    /// # Parameters
468    /// - `base_pdf_path`: Path to the existing PDF to modify
469    /// - `document`: Document containing replacement pages (first N pages will replace base pages 0..N-1)
470    ///
471    /// # Returns
472    /// - `Ok(())` if incremental update was written successfully
473    /// - `Err(PdfError)` if base PDF cannot be read, parsed, or structure is invalid
474    pub fn write_incremental_with_page_replacement(
475        &mut self,
476        base_pdf_path: impl AsRef<std::path::Path>,
477        document: &mut Document,
478    ) -> Result<()> {
479        use std::io::Cursor;
480
481        // Step 1: Read the entire base PDF into memory (avoids double file open)
482        let base_pdf_bytes = std::fs::read(base_pdf_path.as_ref())?;
483        let base_size = base_pdf_bytes.len() as u64;
484
485        // Step 2: Parse from memory to get page information
486        let mut pdf_reader = crate::parser::PdfReader::new(Cursor::new(&base_pdf_bytes))?;
487
488        let base_catalog = pdf_reader.catalog()?;
489
490        let (base_pages_id, base_pages_gen) = base_catalog
491            .get("Pages")
492            .and_then(|obj| {
493                if let crate::parser::objects::PdfObject::Reference(id, gen) = obj {
494                    Some((*id, *gen))
495                } else {
496                    None
497                }
498            })
499            .ok_or_else(|| {
500                crate::error::PdfError::InvalidStructure(
501                    "Base PDF catalog missing /Pages reference".to_string(),
502                )
503            })?;
504
505        let base_pages_obj = pdf_reader.get_object(base_pages_id, base_pages_gen)?;
506        let base_pages_kids = if let crate::parser::objects::PdfObject::Dictionary(dict) =
507            base_pages_obj
508        {
509            dict.get("Kids")
510                .and_then(|obj| {
511                    if let crate::parser::objects::PdfObject::Array(arr) = obj {
512                        Some(
513                            arr.0
514                                .iter()
515                                .filter_map(|item| {
516                                    if let crate::parser::objects::PdfObject::Reference(id, gen) =
517                                        item
518                                    {
519                                        Some(crate::objects::Object::Reference(
520                                            crate::objects::ObjectId::new(*id, *gen),
521                                        ))
522                                    } else {
523                                        None
524                                    }
525                                })
526                                .collect::<Vec<_>>(),
527                        )
528                    } else {
529                        None
530                    }
531                })
532                .unwrap_or_default()
533        } else {
534            Vec::new()
535        };
536
537        let base_page_count = base_pages_kids.len();
538
539        // Step 3: Find startxref offset from the bytes
540        let start_search = if base_size > 100 { base_size - 100 } else { 0 } as usize;
541        let end_bytes = &base_pdf_bytes[start_search..];
542        let end_str = String::from_utf8_lossy(end_bytes);
543
544        let prev_xref = if let Some(startxref_pos) = end_str.find("startxref") {
545            let after_startxref = &end_str[startxref_pos + 9..];
546            let number_str: String = after_startxref
547                .chars()
548                .skip_while(|c| c.is_whitespace())
549                .take_while(|c| c.is_ascii_digit())
550                .collect();
551
552            number_str.parse::<u64>().map_err(|_| {
553                crate::error::PdfError::InvalidStructure(
554                    "Could not parse startxref offset".to_string(),
555                )
556            })?
557        } else {
558            return Err(crate::error::PdfError::InvalidStructure(
559                "startxref not found in base PDF".to_string(),
560            ));
561        };
562
563        // Step 4: Copy base PDF bytes to output
564        self.writer.write_all(&base_pdf_bytes)?;
565
566        self.prev_xref_offset = Some(prev_xref);
567        self.base_pdf_size = Some(base_size);
568        self.current_position = base_size;
569
570        // Step 3: Write replacement pages
571        if !document.used_characters.is_empty() {
572            self.document_used_chars = Some(document.used_characters.clone());
573        }
574
575        self.catalog_id = Some(self.allocate_object_id());
576        self.pages_id = Some(self.allocate_object_id());
577        self.info_id = Some(self.allocate_object_id());
578
579        let font_refs = self.write_fonts(document)?;
580        self.write_pages(document, &font_refs)?;
581        self.write_form_fields(document)?;
582
583        // Step 4: Create Pages tree with REPLACEMENTS
584        let catalog_id = self.get_catalog_id()?;
585        let new_pages_id = self.get_pages_id()?;
586
587        let mut catalog = crate::objects::Dictionary::new();
588        catalog.set("Type", crate::objects::Object::Name("Catalog".to_string()));
589        catalog.set("Pages", crate::objects::Object::Reference(new_pages_id));
590        self.write_object(catalog_id, crate::objects::Object::Dictionary(catalog))?;
591
592        // Build new Kids array: replace first N pages, keep rest from base
593        let mut all_pages_kids = Vec::new();
594        let replacement_count = document.pages.len();
595
596        // Add replacement pages (these override base pages at same indices)
597        for page_id in &self.page_ids {
598            all_pages_kids.push(crate::objects::Object::Reference(*page_id));
599        }
600
601        // Add remaining base pages that weren't replaced
602        if replacement_count < base_page_count {
603            for i in replacement_count..base_page_count {
604                if let Some(page_ref) = base_pages_kids.get(i) {
605                    all_pages_kids.push(page_ref.clone());
606                }
607            }
608        }
609
610        let mut pages_dict = crate::objects::Dictionary::new();
611        pages_dict.set("Type", crate::objects::Object::Name("Pages".to_string()));
612        pages_dict.set(
613            "Kids",
614            crate::objects::Object::Array(all_pages_kids.clone()),
615        );
616        pages_dict.set(
617            "Count",
618            crate::objects::Object::Integer(all_pages_kids.len() as i64),
619        );
620
621        self.write_object(new_pages_id, crate::objects::Object::Dictionary(pages_dict))?;
622        self.write_info(document)?;
623
624        let xref_position = self.current_position;
625        self.write_xref()?;
626        self.write_trailer(xref_position)?;
627
628        self.writer.flush()?;
629        Ok(())
630    }
631
632    /// Overlays content onto existing PDF pages using incremental updates (PLANNED).
633    ///
634    /// **STATUS**: Not yet implemented. This API is planned for a future release.
635    ///
636    /// # What This Will Do
637    /// When implemented, this function will allow you to:
638    /// - Load an existing PDF
639    /// - Modify specific elements (fill form fields, add annotations, watermarks)
640    /// - Save incrementally without recreating entire pages
641    ///
642    /// # Difference from Page Replacement
643    /// - **Page Replacement** (`write_incremental_with_page_replacement`): Replaces entire pages with manually recreated content
644    /// - **Overlay** (this function): Modifies existing pages by adding/changing specific elements
645    ///
646    /// # Planned Usage (Future)
647    /// ```rust,ignore
648    /// // This code will work in a future release
649    /// let mut pdf_writer = PdfWriter::with_config(writer, WriterConfig::incremental());
650    ///
651    /// let overlays = vec![
652    ///     PageOverlay::new(0)
653    ///         .add_text(110.0, 700.0, "John Smith")
654    ///         .add_annotation(Annotation::text(200.0, 500.0, "Review this")),
655    /// ];
656    ///
657    /// pdf_writer.write_incremental_with_overlay("form.pdf", overlays)?;
658    /// ```
659    ///
660    /// # Implementation Requirements
661    /// This function requires:
662    /// 1. `Document::load()` - Load existing PDF into Document structure
663    /// 2. `Page::from_parsed()` - Convert parsed pages to writable format
664    /// 3. Content stream overlay system - Append to existing content streams
665    /// 4. Resource merging - Combine new resources with existing ones
666    ///
667    /// Estimated implementation effort: 6-7 days
668    ///
669    /// # Current Workaround
670    /// Until this is implemented, use `write_incremental_with_page_replacement()` with manual
671    /// page recreation. See that function's documentation for examples.
672    ///
673    /// # Parameters
674    /// - `base_pdf_path`: Path to the existing PDF to modify (future)
675    /// - `overlays`: Content to overlay on existing pages (future)
676    ///
677    /// # Returns
678    /// Currently always returns `PdfError::NotImplemented`
679    pub fn write_incremental_with_overlay<P: AsRef<std::path::Path>>(
680        &mut self,
681        base_pdf_path: P,
682        mut overlay_fn: impl FnMut(&mut crate::Page) -> Result<()>,
683    ) -> Result<()> {
684        use std::io::Cursor;
685
686        // Step 1: Read the entire base PDF into memory
687        let base_pdf_bytes = std::fs::read(base_pdf_path.as_ref())?;
688        let base_size = base_pdf_bytes.len() as u64;
689
690        // Step 2: Parse from memory to get page information
691        let pdf_reader = crate::parser::PdfReader::new(Cursor::new(&base_pdf_bytes))?;
692        let parsed_doc = crate::parser::PdfDocument::new(pdf_reader);
693
694        // Get all pages from base PDF
695        let page_count = parsed_doc.page_count()?;
696
697        // Step 3: Find startxref offset from the bytes
698        let start_search = if base_size > 100 { base_size - 100 } else { 0 } as usize;
699        let end_bytes = &base_pdf_bytes[start_search..];
700        let end_str = String::from_utf8_lossy(end_bytes);
701
702        let prev_xref = if let Some(startxref_pos) = end_str.find("startxref") {
703            let after_startxref = &end_str[startxref_pos + 9..];
704            let number_str: String = after_startxref
705                .chars()
706                .skip_while(|c| c.is_whitespace())
707                .take_while(|c| c.is_ascii_digit())
708                .collect();
709
710            number_str.parse::<u64>().map_err(|_| {
711                crate::error::PdfError::InvalidStructure(
712                    "Could not parse startxref offset".to_string(),
713                )
714            })?
715        } else {
716            return Err(crate::error::PdfError::InvalidStructure(
717                "startxref not found in base PDF".to_string(),
718            ));
719        };
720
721        // Step 5: Copy base PDF bytes to output
722        self.writer.write_all(&base_pdf_bytes)?;
723
724        self.prev_xref_offset = Some(prev_xref);
725        self.base_pdf_size = Some(base_size);
726        self.current_position = base_size;
727
728        // Step 6: Build temporary document with overlaid pages
729        let mut temp_doc = crate::Document::new();
730
731        for page_idx in 0..page_count {
732            // Convert parsed page to writable with content preservation
733            let parsed_page = parsed_doc.get_page(page_idx)?;
734            let mut writable_page =
735                crate::Page::from_parsed_with_content(&parsed_page, &parsed_doc)?;
736
737            // Apply overlay function
738            overlay_fn(&mut writable_page)?;
739
740            // Add to temporary document
741            temp_doc.add_page(writable_page);
742        }
743
744        // Step 7: Write document with standard writer methods
745        // This ensures consistent object numbering
746        if !temp_doc.used_characters.is_empty() {
747            self.document_used_chars = Some(temp_doc.used_characters.clone());
748        }
749
750        self.catalog_id = Some(self.allocate_object_id());
751        self.pages_id = Some(self.allocate_object_id());
752        self.info_id = Some(self.allocate_object_id());
753
754        let font_refs = self.write_fonts(&temp_doc)?;
755        self.write_pages(&temp_doc, &font_refs)?;
756        self.write_form_fields(&mut temp_doc)?;
757
758        // Step 8: Create new catalog and pages tree
759        let catalog_id = self.get_catalog_id()?;
760        let new_pages_id = self.get_pages_id()?;
761
762        let mut catalog = crate::objects::Dictionary::new();
763        catalog.set("Type", crate::objects::Object::Name("Catalog".to_string()));
764        catalog.set("Pages", crate::objects::Object::Reference(new_pages_id));
765        self.write_object(catalog_id, crate::objects::Object::Dictionary(catalog))?;
766
767        // Build new Kids array with ALL overlaid pages
768        let mut all_pages_kids = Vec::new();
769        for page_id in &self.page_ids {
770            all_pages_kids.push(crate::objects::Object::Reference(*page_id));
771        }
772
773        let mut pages_dict = crate::objects::Dictionary::new();
774        pages_dict.set("Type", crate::objects::Object::Name("Pages".to_string()));
775        pages_dict.set(
776            "Kids",
777            crate::objects::Object::Array(all_pages_kids.clone()),
778        );
779        pages_dict.set(
780            "Count",
781            crate::objects::Object::Integer(all_pages_kids.len() as i64),
782        );
783
784        self.write_object(new_pages_id, crate::objects::Object::Dictionary(pages_dict))?;
785        self.write_info(&temp_doc)?;
786
787        let xref_position = self.current_position;
788        self.write_xref()?;
789        self.write_trailer(xref_position)?;
790
791        self.writer.flush()?;
792        Ok(())
793    }
794
795    fn write_header(&mut self) -> Result<()> {
796        let header = format!("%PDF-{}\n", self.config.pdf_version);
797        self.write_bytes(header.as_bytes())?;
798        // Binary comment to ensure file is treated as binary
799        self.write_bytes(&[b'%', 0xE2, 0xE3, 0xCF, 0xD3, b'\n'])?;
800        Ok(())
801    }
802
803    /// Convert pdf_objects types to writer objects types
804    /// This is a temporary bridge until type unification is complete
805    fn convert_pdf_objects_dict_to_writer(
806        &self,
807        pdf_dict: &crate::pdf_objects::Dictionary,
808    ) -> crate::objects::Dictionary {
809        let mut writer_dict = crate::objects::Dictionary::new();
810
811        for (key, value) in pdf_dict.iter() {
812            let writer_obj = self.convert_pdf_object_to_writer(value);
813            writer_dict.set(key.as_str(), writer_obj);
814        }
815
816        writer_dict
817    }
818
819    fn convert_pdf_object_to_writer(
820        &self,
821        obj: &crate::pdf_objects::Object,
822    ) -> crate::objects::Object {
823        use crate::objects::Object as WriterObj;
824        use crate::pdf_objects::Object as PdfObj;
825
826        match obj {
827            PdfObj::Null => WriterObj::Null,
828            PdfObj::Boolean(b) => WriterObj::Boolean(*b),
829            PdfObj::Integer(i) => WriterObj::Integer(*i),
830            PdfObj::Real(f) => WriterObj::Real(*f),
831            PdfObj::String(s) => {
832                WriterObj::String(String::from_utf8_lossy(s.as_bytes()).to_string())
833            }
834            PdfObj::Name(n) => WriterObj::Name(n.as_str().to_string()),
835            PdfObj::Array(arr) => {
836                let items: Vec<WriterObj> = arr
837                    .iter()
838                    .map(|item| self.convert_pdf_object_to_writer(item))
839                    .collect();
840                WriterObj::Array(items)
841            }
842            PdfObj::Dictionary(dict) => {
843                WriterObj::Dictionary(self.convert_pdf_objects_dict_to_writer(dict))
844            }
845            PdfObj::Stream(stream) => {
846                let dict = self.convert_pdf_objects_dict_to_writer(&stream.dict);
847                WriterObj::Stream(dict, stream.data.clone())
848            }
849            PdfObj::Reference(id) => {
850                WriterObj::Reference(crate::objects::ObjectId::new(id.number(), id.generation()))
851            }
852        }
853    }
854
855    fn write_catalog(&mut self, document: &mut Document) -> Result<()> {
856        let catalog_id = self.get_catalog_id()?;
857        let pages_id = self.get_pages_id()?;
858
859        let mut catalog = Dictionary::new();
860        catalog.set("Type", Object::Name("Catalog".to_string()));
861        catalog.set("Pages", Object::Reference(pages_id));
862
863        // Process FormManager if present to update AcroForm
864        // We'll write the actual fields after pages are written
865        if let Some(_form_manager) = &document.form_manager {
866            // Ensure AcroForm exists
867            if document.acro_form.is_none() {
868                document.acro_form = Some(crate::forms::AcroForm::new());
869            }
870        }
871
872        // Add AcroForm if present
873        if let Some(acro_form) = &document.acro_form {
874            // Reserve object ID for AcroForm
875            let acro_form_id = self.allocate_object_id();
876
877            // Write AcroForm object
878            self.write_object(acro_form_id, Object::Dictionary(acro_form.to_dict()))?;
879
880            // Reference it in catalog
881            catalog.set("AcroForm", Object::Reference(acro_form_id));
882        }
883
884        // Add Outlines if present
885        if let Some(outline_tree) = &document.outline {
886            if !outline_tree.items.is_empty() {
887                let outline_root_id = self.write_outline_tree(outline_tree)?;
888                catalog.set("Outlines", Object::Reference(outline_root_id));
889            }
890        }
891
892        // Add StructTreeRoot if present (Tagged PDF - ISO 32000-1 §14.8)
893        if let Some(struct_tree) = &document.struct_tree {
894            if !struct_tree.is_empty() {
895                let struct_tree_root_id = self.write_struct_tree(struct_tree)?;
896                catalog.set("StructTreeRoot", Object::Reference(struct_tree_root_id));
897                // Mark as Tagged PDF
898                catalog.set("MarkInfo", {
899                    let mut mark_info = Dictionary::new();
900                    mark_info.set("Marked", Object::Boolean(true));
901                    Object::Dictionary(mark_info)
902                });
903            }
904        }
905
906        // Add XMP Metadata stream (ISO 32000-1 §14.3.2)
907        // Generate XMP from document metadata and embed as stream
908        let xmp_metadata = document.create_xmp_metadata();
909        let xmp_packet = xmp_metadata.to_xmp_packet();
910        let metadata_id = self.allocate_object_id();
911
912        // Create metadata stream dictionary
913        let mut metadata_dict = Dictionary::new();
914        metadata_dict.set("Type", Object::Name("Metadata".to_string()));
915        metadata_dict.set("Subtype", Object::Name("XML".to_string()));
916        metadata_dict.set("Length", Object::Integer(xmp_packet.len() as i64));
917
918        // Write XMP metadata stream
919        self.write_object(
920            metadata_id,
921            Object::Stream(metadata_dict, xmp_packet.into_bytes()),
922        )?;
923
924        // Reference it in catalog
925        catalog.set("Metadata", Object::Reference(metadata_id));
926
927        self.write_object(catalog_id, Object::Dictionary(catalog))?;
928        Ok(())
929    }
930
931    fn write_page_content(&mut self, content_id: ObjectId, page: &crate::page::Page) -> Result<()> {
932        let mut page_copy = page.clone();
933        let content = page_copy.generate_content()?;
934
935        // Create stream with compression if enabled
936        #[cfg(feature = "compression")]
937        {
938            use crate::objects::Stream;
939            let mut stream = Stream::new(content);
940            // Only compress if config allows it
941            if self.config.compress_streams {
942                stream.compress_flate()?;
943            }
944
945            self.write_object(
946                content_id,
947                Object::Stream(stream.dictionary().clone(), stream.data().to_vec()),
948            )?;
949        }
950
951        #[cfg(not(feature = "compression"))]
952        {
953            let mut stream_dict = Dictionary::new();
954            stream_dict.set("Length", Object::Integer(content.len() as i64));
955
956            self.write_object(content_id, Object::Stream(stream_dict, content))?;
957        }
958
959        Ok(())
960    }
961
962    fn write_outline_tree(
963        &mut self,
964        outline_tree: &crate::structure::OutlineTree,
965    ) -> Result<ObjectId> {
966        // Create root outline dictionary
967        let outline_root_id = self.allocate_object_id();
968
969        let mut outline_root = Dictionary::new();
970        outline_root.set("Type", Object::Name("Outlines".to_string()));
971
972        if !outline_tree.items.is_empty() {
973            // Reserve IDs for all outline items
974            let mut item_ids = Vec::new();
975
976            // Count all items and assign IDs
977            fn count_items(items: &[crate::structure::OutlineItem]) -> usize {
978                let mut count = items.len();
979                for item in items {
980                    count += count_items(&item.children);
981                }
982                count
983            }
984
985            let total_items = count_items(&outline_tree.items);
986
987            // Reserve IDs for all items
988            for _ in 0..total_items {
989                item_ids.push(self.allocate_object_id());
990            }
991
992            let mut id_index = 0;
993
994            // Write root items
995            let first_id = item_ids[0];
996            let last_id = item_ids[outline_tree.items.len() - 1];
997
998            outline_root.set("First", Object::Reference(first_id));
999            outline_root.set("Last", Object::Reference(last_id));
1000
1001            // Visible count
1002            let visible_count = outline_tree.visible_count();
1003            outline_root.set("Count", Object::Integer(visible_count));
1004
1005            // Write all items recursively
1006            let mut written_items = Vec::new();
1007
1008            for (i, item) in outline_tree.items.iter().enumerate() {
1009                let item_id = item_ids[id_index];
1010                id_index += 1;
1011
1012                let prev_id = if i > 0 { Some(item_ids[i - 1]) } else { None };
1013                let next_id = if i < outline_tree.items.len() - 1 {
1014                    Some(item_ids[i + 1])
1015                } else {
1016                    None
1017                };
1018
1019                // Write this item and its children
1020                let children_ids = self.write_outline_item(
1021                    item,
1022                    item_id,
1023                    outline_root_id,
1024                    prev_id,
1025                    next_id,
1026                    &mut item_ids,
1027                    &mut id_index,
1028                )?;
1029
1030                written_items.extend(children_ids);
1031            }
1032        }
1033
1034        self.write_object(outline_root_id, Object::Dictionary(outline_root))?;
1035        Ok(outline_root_id)
1036    }
1037
1038    #[allow(clippy::too_many_arguments)]
1039    fn write_outline_item(
1040        &mut self,
1041        item: &crate::structure::OutlineItem,
1042        item_id: ObjectId,
1043        parent_id: ObjectId,
1044        prev_id: Option<ObjectId>,
1045        next_id: Option<ObjectId>,
1046        all_ids: &mut Vec<ObjectId>,
1047        id_index: &mut usize,
1048    ) -> Result<Vec<ObjectId>> {
1049        let mut written_ids = vec![item_id];
1050
1051        // Handle children if any
1052        let (first_child_id, last_child_id) = if !item.children.is_empty() {
1053            let first_idx = *id_index;
1054            let first_id = all_ids[first_idx];
1055            let last_idx = first_idx + item.children.len() - 1;
1056            let last_id = all_ids[last_idx];
1057
1058            // Write children
1059            for (i, child) in item.children.iter().enumerate() {
1060                let child_id = all_ids[*id_index];
1061                *id_index += 1;
1062
1063                let child_prev = if i > 0 {
1064                    Some(all_ids[first_idx + i - 1])
1065                } else {
1066                    None
1067                };
1068                let child_next = if i < item.children.len() - 1 {
1069                    Some(all_ids[first_idx + i + 1])
1070                } else {
1071                    None
1072                };
1073
1074                let child_ids = self.write_outline_item(
1075                    child, child_id, item_id, // This item is the parent
1076                    child_prev, child_next, all_ids, id_index,
1077                )?;
1078
1079                written_ids.extend(child_ids);
1080            }
1081
1082            (Some(first_id), Some(last_id))
1083        } else {
1084            (None, None)
1085        };
1086
1087        // Create item dictionary
1088        let item_dict = crate::structure::outline_item_to_dict(
1089            item,
1090            parent_id,
1091            first_child_id,
1092            last_child_id,
1093            prev_id,
1094            next_id,
1095        );
1096
1097        self.write_object(item_id, Object::Dictionary(item_dict))?;
1098
1099        Ok(written_ids)
1100    }
1101
1102    /// Writes the structure tree for Tagged PDF (ISO 32000-1 §14.8)
1103    fn write_struct_tree(
1104        &mut self,
1105        struct_tree: &crate::structure::StructTree,
1106    ) -> Result<ObjectId> {
1107        // Allocate IDs for StructTreeRoot and all elements
1108        let struct_tree_root_id = self.allocate_object_id();
1109        let mut element_ids = Vec::new();
1110        for _ in 0..struct_tree.len() {
1111            element_ids.push(self.allocate_object_id());
1112        }
1113
1114        // Build parent map: element_index -> parent_id
1115        let mut parent_map: std::collections::HashMap<usize, ObjectId> =
1116            std::collections::HashMap::new();
1117
1118        // Root element's parent is StructTreeRoot
1119        if let Some(root_index) = struct_tree.root_index() {
1120            parent_map.insert(root_index, struct_tree_root_id);
1121
1122            // Recursively map all children to their parents
1123            fn map_children_parents(
1124                tree: &crate::structure::StructTree,
1125                parent_index: usize,
1126                parent_id: ObjectId,
1127                element_ids: &[ObjectId],
1128                parent_map: &mut std::collections::HashMap<usize, ObjectId>,
1129            ) {
1130                if let Some(parent_elem) = tree.get(parent_index) {
1131                    for &child_index in &parent_elem.children {
1132                        parent_map.insert(child_index, parent_id);
1133                        map_children_parents(
1134                            tree,
1135                            child_index,
1136                            element_ids[child_index],
1137                            element_ids,
1138                            parent_map,
1139                        );
1140                    }
1141                }
1142            }
1143
1144            map_children_parents(
1145                struct_tree,
1146                root_index,
1147                element_ids[root_index],
1148                &element_ids,
1149                &mut parent_map,
1150            );
1151        }
1152
1153        // Write all structure elements with parent references
1154        for (index, element) in struct_tree.iter().enumerate() {
1155            let element_id = element_ids[index];
1156            let mut element_dict = Dictionary::new();
1157
1158            element_dict.set("Type", Object::Name("StructElem".to_string()));
1159            element_dict.set("S", Object::Name(element.structure_type.as_pdf_name()));
1160
1161            // Parent reference (ISO 32000-1 §14.7.2 - required)
1162            if let Some(&parent_id) = parent_map.get(&index) {
1163                element_dict.set("P", Object::Reference(parent_id));
1164            }
1165
1166            // Element ID (optional)
1167            if let Some(ref id) = element.id {
1168                element_dict.set("ID", Object::String(id.clone()));
1169            }
1170
1171            // Attributes
1172            if let Some(ref lang) = element.attributes.lang {
1173                element_dict.set("Lang", Object::String(lang.clone()));
1174            }
1175            if let Some(ref alt) = element.attributes.alt {
1176                element_dict.set("Alt", Object::String(alt.clone()));
1177            }
1178            if let Some(ref actual_text) = element.attributes.actual_text {
1179                element_dict.set("ActualText", Object::String(actual_text.clone()));
1180            }
1181            if let Some(ref title) = element.attributes.title {
1182                element_dict.set("T", Object::String(title.clone()));
1183            }
1184            if let Some(bbox) = element.attributes.bbox {
1185                element_dict.set(
1186                    "BBox",
1187                    Object::Array(vec![
1188                        Object::Real(bbox[0]),
1189                        Object::Real(bbox[1]),
1190                        Object::Real(bbox[2]),
1191                        Object::Real(bbox[3]),
1192                    ]),
1193                );
1194            }
1195
1196            // Kids (children elements + marked content references)
1197            let mut kids = Vec::new();
1198
1199            // Add child element references
1200            for &child_index in &element.children {
1201                kids.push(Object::Reference(element_ids[child_index]));
1202            }
1203
1204            // Add marked content references (MCIDs)
1205            for mcid_ref in &element.mcids {
1206                let mut mcr = Dictionary::new();
1207                mcr.set("Type", Object::Name("MCR".to_string()));
1208                mcr.set("Pg", Object::Integer(mcid_ref.page_index as i64));
1209                mcr.set("MCID", Object::Integer(mcid_ref.mcid as i64));
1210                kids.push(Object::Dictionary(mcr));
1211            }
1212
1213            if !kids.is_empty() {
1214                element_dict.set("K", Object::Array(kids));
1215            }
1216
1217            self.write_object(element_id, Object::Dictionary(element_dict))?;
1218        }
1219
1220        // Create StructTreeRoot dictionary
1221        let mut struct_tree_root = Dictionary::new();
1222        struct_tree_root.set("Type", Object::Name("StructTreeRoot".to_string()));
1223
1224        // Add root element(s) as K entry
1225        if let Some(root_index) = struct_tree.root_index() {
1226            struct_tree_root.set("K", Object::Reference(element_ids[root_index]));
1227        }
1228
1229        // Add RoleMap if not empty
1230        if !struct_tree.role_map.mappings().is_empty() {
1231            let mut role_map = Dictionary::new();
1232            for (custom_type, standard_type) in struct_tree.role_map.mappings() {
1233                role_map.set(
1234                    custom_type.as_str(),
1235                    Object::Name(standard_type.as_pdf_name().to_string()),
1236                );
1237            }
1238            struct_tree_root.set("RoleMap", Object::Dictionary(role_map));
1239        }
1240
1241        self.write_object(struct_tree_root_id, Object::Dictionary(struct_tree_root))?;
1242        Ok(struct_tree_root_id)
1243    }
1244
1245    fn write_form_fields(&mut self, document: &mut Document) -> Result<()> {
1246        // Add collected form field IDs to AcroForm
1247        if !self.form_field_ids.is_empty() {
1248            if let Some(acro_form) = &mut document.acro_form {
1249                // Clear any existing fields and add the ones we found
1250                acro_form.fields.clear();
1251                for field_id in &self.form_field_ids {
1252                    acro_form.add_field(*field_id);
1253                }
1254
1255                // Ensure AcroForm has the right properties
1256                acro_form.need_appearances = true;
1257                if acro_form.da.is_none() {
1258                    acro_form.da = Some("/Helv 12 Tf 0 g".to_string());
1259                }
1260            }
1261        }
1262        Ok(())
1263    }
1264
1265    fn write_info(&mut self, document: &Document) -> Result<()> {
1266        let info_id = self.get_info_id()?;
1267        let mut info_dict = Dictionary::new();
1268
1269        if let Some(ref title) = document.metadata.title {
1270            info_dict.set("Title", Object::String(title.clone()));
1271        }
1272        if let Some(ref author) = document.metadata.author {
1273            info_dict.set("Author", Object::String(author.clone()));
1274        }
1275        if let Some(ref subject) = document.metadata.subject {
1276            info_dict.set("Subject", Object::String(subject.clone()));
1277        }
1278        if let Some(ref keywords) = document.metadata.keywords {
1279            info_dict.set("Keywords", Object::String(keywords.clone()));
1280        }
1281        if let Some(ref creator) = document.metadata.creator {
1282            info_dict.set("Creator", Object::String(creator.clone()));
1283        }
1284        if let Some(ref producer) = document.metadata.producer {
1285            info_dict.set("Producer", Object::String(producer.clone()));
1286        }
1287
1288        // Add creation date
1289        if let Some(creation_date) = document.metadata.creation_date {
1290            let date_string = format_pdf_date(creation_date);
1291            info_dict.set("CreationDate", Object::String(date_string));
1292        }
1293
1294        // Add modification date
1295        if let Some(mod_date) = document.metadata.modification_date {
1296            let date_string = format_pdf_date(mod_date);
1297            info_dict.set("ModDate", Object::String(date_string));
1298        }
1299
1300        // Add PDF signature (anti-spoofing and licensing)
1301        // This is written AFTER user-configurable metadata so it cannot be overridden
1302        let edition = super::Edition::OpenSource;
1303
1304        let signature = super::PdfSignature::new(document, edition);
1305        signature.write_to_info_dict(&mut info_dict);
1306
1307        self.write_object(info_id, Object::Dictionary(info_dict))?;
1308        Ok(())
1309    }
1310
1311    fn write_fonts(&mut self, document: &Document) -> Result<HashMap<String, ObjectId>> {
1312        let mut font_refs = HashMap::new();
1313
1314        // Write custom fonts from the document
1315        for font_name in document.custom_font_names() {
1316            if let Some(font) = document.get_custom_font(&font_name) {
1317                // For now, write all custom fonts as TrueType with Identity-H for Unicode support
1318                // The font from document is Arc<fonts::Font>, not text::font_manager::CustomFont
1319                let font_id = self.write_font_with_unicode_support(&font_name, &font)?;
1320                font_refs.insert(font_name.clone(), font_id);
1321            }
1322        }
1323
1324        Ok(font_refs)
1325    }
1326
1327    /// Write font with automatic Unicode support detection
1328    fn write_font_with_unicode_support(
1329        &mut self,
1330        font_name: &str,
1331        font: &crate::fonts::Font,
1332    ) -> Result<ObjectId> {
1333        // Check if any text in the document needs Unicode
1334        // For simplicity, always use Type0 for full Unicode support
1335        self.write_type0_font_from_font(font_name, font)
1336    }
1337
1338    /// Write a Type0 font with CID support from fonts::Font
1339    fn write_type0_font_from_font(
1340        &mut self,
1341        font_name: &str,
1342        font: &crate::fonts::Font,
1343    ) -> Result<ObjectId> {
1344        // Get used characters from document for subsetting
1345        let used_chars = self.document_used_chars.clone().unwrap_or_else(|| {
1346            // If no tracking, include common characters as fallback
1347            let mut chars = std::collections::HashSet::new();
1348            for ch in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,!?".chars()
1349            {
1350                chars.insert(ch);
1351            }
1352            chars
1353        });
1354        // Allocate IDs for all font objects
1355        let font_id = self.allocate_object_id();
1356        let descendant_font_id = self.allocate_object_id();
1357        let descriptor_id = self.allocate_object_id();
1358        let font_file_id = self.allocate_object_id();
1359        let to_unicode_id = self.allocate_object_id();
1360
1361        // Write font file (embedded TTF data with subsetting for large fonts)
1362        // Keep track of the glyph mapping if we subset the font
1363        // IMPORTANT: We need the ORIGINAL font for width calculations, not the subset
1364        let (font_data_to_embed, subset_glyph_mapping, original_font_for_widths) =
1365            if font.data.len() > 100_000 && !used_chars.is_empty() {
1366                // Large font - try to subset it
1367                match crate::text::fonts::truetype_subsetter::subset_font(
1368                    font.data.clone(),
1369                    &used_chars,
1370                ) {
1371                    Ok(subset_result) => {
1372                        // Successfully subsetted - keep both font data and mapping
1373                        // Also keep reference to original font for width calculations
1374                        (
1375                            subset_result.font_data,
1376                            Some(subset_result.glyph_mapping),
1377                            font.clone(),
1378                        )
1379                    }
1380                    Err(_) => {
1381                        // Subsetting failed, use original if under 25MB
1382                        if font.data.len() < 25_000_000 {
1383                            (font.data.clone(), None, font.clone())
1384                        } else {
1385                            // Too large even for fallback
1386                            (Vec::new(), None, font.clone())
1387                        }
1388                    }
1389                }
1390            } else {
1391                // Small font or no character tracking - use as-is
1392                (font.data.clone(), None, font.clone())
1393            };
1394
1395        if !font_data_to_embed.is_empty() {
1396            let mut font_file_dict = Dictionary::new();
1397            // Add appropriate properties based on font format
1398            match font.format {
1399                crate::fonts::FontFormat::OpenType => {
1400                    // CFF/OpenType fonts use FontFile3 with OpenType subtype
1401                    font_file_dict.set("Subtype", Object::Name("OpenType".to_string()));
1402                    font_file_dict.set("Length1", Object::Integer(font_data_to_embed.len() as i64));
1403                }
1404                crate::fonts::FontFormat::TrueType => {
1405                    // TrueType fonts use FontFile2 with Length1
1406                    font_file_dict.set("Length1", Object::Integer(font_data_to_embed.len() as i64));
1407                }
1408            }
1409            let font_stream_obj = Object::Stream(font_file_dict, font_data_to_embed);
1410            self.write_object(font_file_id, font_stream_obj)?;
1411        } else {
1412            // No font data to embed
1413            let font_file_dict = Dictionary::new();
1414            let font_stream_obj = Object::Stream(font_file_dict, Vec::new());
1415            self.write_object(font_file_id, font_stream_obj)?;
1416        }
1417
1418        // Write font descriptor
1419        let mut descriptor = Dictionary::new();
1420        descriptor.set("Type", Object::Name("FontDescriptor".to_string()));
1421        descriptor.set("FontName", Object::Name(font_name.to_string()));
1422        descriptor.set("Flags", Object::Integer(4)); // Symbolic font
1423        descriptor.set(
1424            "FontBBox",
1425            Object::Array(vec![
1426                Object::Integer(font.descriptor.font_bbox[0] as i64),
1427                Object::Integer(font.descriptor.font_bbox[1] as i64),
1428                Object::Integer(font.descriptor.font_bbox[2] as i64),
1429                Object::Integer(font.descriptor.font_bbox[3] as i64),
1430            ]),
1431        );
1432        descriptor.set(
1433            "ItalicAngle",
1434            Object::Real(font.descriptor.italic_angle as f64),
1435        );
1436        descriptor.set("Ascent", Object::Real(font.descriptor.ascent as f64));
1437        descriptor.set("Descent", Object::Real(font.descriptor.descent as f64));
1438        descriptor.set("CapHeight", Object::Real(font.descriptor.cap_height as f64));
1439        descriptor.set("StemV", Object::Real(font.descriptor.stem_v as f64));
1440        // Use appropriate FontFile type based on font format
1441        let font_file_key = match font.format {
1442            crate::fonts::FontFormat::OpenType => "FontFile3", // CFF/OpenType fonts
1443            crate::fonts::FontFormat::TrueType => "FontFile2", // TrueType fonts
1444        };
1445        descriptor.set(font_file_key, Object::Reference(font_file_id));
1446        self.write_object(descriptor_id, Object::Dictionary(descriptor))?;
1447
1448        // Write CIDFont (descendant font)
1449        let mut cid_font = Dictionary::new();
1450        cid_font.set("Type", Object::Name("Font".to_string()));
1451        // Use appropriate CIDFont subtype based on font format
1452        let cid_font_subtype =
1453            if CjkFontType::should_use_cidfonttype2_for_preview_compatibility(font_name) {
1454                "CIDFontType2" // Force CIDFontType2 for CJK fonts to fix Preview.app rendering
1455            } else {
1456                match font.format {
1457                    crate::fonts::FontFormat::OpenType => "CIDFontType0", // CFF/OpenType fonts
1458                    crate::fonts::FontFormat::TrueType => "CIDFontType2", // TrueType fonts
1459                }
1460            };
1461        cid_font.set("Subtype", Object::Name(cid_font_subtype.to_string()));
1462        cid_font.set("BaseFont", Object::Name(font_name.to_string()));
1463
1464        // CIDSystemInfo - Use appropriate values for CJK fonts
1465        let mut cid_system_info = Dictionary::new();
1466        let (registry, ordering, supplement) =
1467            if let Some(cjk_type) = CjkFontType::detect_from_name(font_name) {
1468                cjk_type.cid_system_info()
1469            } else {
1470                ("Adobe", "Identity", 0)
1471            };
1472
1473        cid_system_info.set("Registry", Object::String(registry.to_string()));
1474        cid_system_info.set("Ordering", Object::String(ordering.to_string()));
1475        cid_system_info.set("Supplement", Object::Integer(supplement as i64));
1476        cid_font.set("CIDSystemInfo", Object::Dictionary(cid_system_info));
1477
1478        cid_font.set("FontDescriptor", Object::Reference(descriptor_id));
1479
1480        // Calculate a better default width based on font metrics
1481        let default_width = self.calculate_default_width(font);
1482        cid_font.set("DW", Object::Integer(default_width));
1483
1484        // Generate proper width array from font metrics
1485        // IMPORTANT: Use the ORIGINAL font for width calculations, not the subset
1486        // But pass the subset mapping to know which characters we're using
1487        let w_array = self.generate_width_array(
1488            &original_font_for_widths,
1489            default_width,
1490            subset_glyph_mapping.as_ref(),
1491        );
1492        cid_font.set("W", Object::Array(w_array));
1493
1494        // CIDToGIDMap - Only required for CIDFontType2 (TrueType)
1495        // For CIDFontType0 (CFF/OpenType), CIDToGIDMap should NOT be present per ISO 32000-1:2008 §9.7.4.2
1496        // CFF fonts use CIDs directly as glyph identifiers, so no mapping is needed
1497        if cid_font_subtype == "CIDFontType2" {
1498            // TrueType fonts need CIDToGIDMap to map CIDs (Unicode code points) to Glyph IDs
1499            let cid_to_gid_map =
1500                self.generate_cid_to_gid_map(font, subset_glyph_mapping.as_ref())?;
1501            if !cid_to_gid_map.is_empty() {
1502                // Write the CIDToGIDMap as a stream
1503                let cid_to_gid_map_id = self.allocate_object_id();
1504                let mut map_dict = Dictionary::new();
1505                map_dict.set("Length", Object::Integer(cid_to_gid_map.len() as i64));
1506                let map_stream = Object::Stream(map_dict, cid_to_gid_map);
1507                self.write_object(cid_to_gid_map_id, map_stream)?;
1508                cid_font.set("CIDToGIDMap", Object::Reference(cid_to_gid_map_id));
1509            } else {
1510                cid_font.set("CIDToGIDMap", Object::Name("Identity".to_string()));
1511            }
1512        }
1513        // Note: For CIDFontType0 (CFF), we intentionally omit CIDToGIDMap
1514
1515        self.write_object(descendant_font_id, Object::Dictionary(cid_font))?;
1516
1517        // Write ToUnicode CMap
1518        let cmap_data = self.generate_tounicode_cmap_from_font(font);
1519        let cmap_dict = Dictionary::new();
1520        let cmap_stream = Object::Stream(cmap_dict, cmap_data);
1521        self.write_object(to_unicode_id, cmap_stream)?;
1522
1523        // Write Type0 font (main font)
1524        let mut type0_font = Dictionary::new();
1525        type0_font.set("Type", Object::Name("Font".to_string()));
1526        type0_font.set("Subtype", Object::Name("Type0".to_string()));
1527        type0_font.set("BaseFont", Object::Name(font_name.to_string()));
1528        type0_font.set("Encoding", Object::Name("Identity-H".to_string()));
1529        type0_font.set(
1530            "DescendantFonts",
1531            Object::Array(vec![Object::Reference(descendant_font_id)]),
1532        );
1533        type0_font.set("ToUnicode", Object::Reference(to_unicode_id));
1534
1535        self.write_object(font_id, Object::Dictionary(type0_font))?;
1536
1537        Ok(font_id)
1538    }
1539
1540    /// Calculate default width based on common characters
1541    fn calculate_default_width(&self, font: &crate::fonts::Font) -> i64 {
1542        use crate::text::fonts::truetype::TrueTypeFont;
1543
1544        // Try to calculate from actual font metrics
1545        if let Ok(tt_font) = TrueTypeFont::parse(font.data.clone()) {
1546            if let Ok(cmap_tables) = tt_font.parse_cmap() {
1547                if let Some(cmap) = cmap_tables
1548                    .iter()
1549                    .find(|t| t.platform_id == 3 && t.encoding_id == 1)
1550                    .or_else(|| cmap_tables.iter().find(|t| t.platform_id == 0))
1551                {
1552                    if let Ok(widths) = tt_font.get_glyph_widths(&cmap.mappings) {
1553                        // NOTE: get_glyph_widths already returns widths in PDF units (1000 per em)
1554
1555                        // Calculate average width of common Latin characters
1556                        let common_chars =
1557                            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
1558                        let mut total_width = 0;
1559                        let mut count = 0;
1560
1561                        for ch in common_chars.chars() {
1562                            let unicode = ch as u32;
1563                            if let Some(&pdf_width) = widths.get(&unicode) {
1564                                total_width += pdf_width as i64;
1565                                count += 1;
1566                            }
1567                        }
1568
1569                        if count > 0 {
1570                            return total_width / count;
1571                        }
1572                    }
1573                }
1574            }
1575        }
1576
1577        // Fallback default if we can't calculate
1578        500
1579    }
1580
1581    /// Generate width array for CID font
1582    fn generate_width_array(
1583        &self,
1584        font: &crate::fonts::Font,
1585        _default_width: i64,
1586        subset_mapping: Option<&HashMap<u32, u16>>,
1587    ) -> Vec<Object> {
1588        use crate::text::fonts::truetype::TrueTypeFont;
1589
1590        let mut w_array = Vec::new();
1591
1592        // Try to get actual glyph widths from the font
1593        if let Ok(tt_font) = TrueTypeFont::parse(font.data.clone()) {
1594            // IMPORTANT: Always use ORIGINAL mappings for width calculation
1595            // The subset_mapping has NEW GlyphIDs which don't correspond to the right glyphs
1596            // in the original font's width table
1597            let char_to_glyph = {
1598                // Parse cmap to get original mappings
1599                if let Ok(cmap_tables) = tt_font.parse_cmap() {
1600                    if let Some(cmap) = cmap_tables
1601                        .iter()
1602                        .find(|t| t.platform_id == 3 && t.encoding_id == 1)
1603                        .or_else(|| cmap_tables.iter().find(|t| t.platform_id == 0))
1604                    {
1605                        // If we have subset_mapping, filter to only include used characters
1606                        if let Some(subset_map) = subset_mapping {
1607                            let mut filtered = HashMap::new();
1608                            for unicode in subset_map.keys() {
1609                                // Get the ORIGINAL GlyphID for this Unicode
1610                                if let Some(&orig_glyph) = cmap.mappings.get(unicode) {
1611                                    filtered.insert(*unicode, orig_glyph);
1612                                }
1613                            }
1614                            filtered
1615                        } else {
1616                            cmap.mappings.clone()
1617                        }
1618                    } else {
1619                        HashMap::new()
1620                    }
1621                } else {
1622                    HashMap::new()
1623                }
1624            };
1625
1626            if !char_to_glyph.is_empty() {
1627                // Get actual widths from the font
1628                if let Ok(widths) = tt_font.get_glyph_widths(&char_to_glyph) {
1629                    // NOTE: get_glyph_widths already returns widths scaled to PDF units (1000 per em)
1630                    // So we DON'T need to scale them again here
1631
1632                    // Group consecutive characters with same width for efficiency
1633                    let mut sorted_chars: Vec<_> = widths.iter().collect();
1634                    sorted_chars.sort_by_key(|(unicode, _)| *unicode);
1635
1636                    let mut i = 0;
1637                    while i < sorted_chars.len() {
1638                        let start_unicode = *sorted_chars[i].0;
1639                        // Width is already in PDF units from get_glyph_widths
1640                        let pdf_width = *sorted_chars[i].1 as i64;
1641
1642                        // Find consecutive characters with same width
1643                        let mut end_unicode = start_unicode;
1644                        let mut j = i + 1;
1645                        while j < sorted_chars.len() && *sorted_chars[j].0 == end_unicode + 1 {
1646                            let next_pdf_width = *sorted_chars[j].1 as i64;
1647                            if next_pdf_width == pdf_width {
1648                                end_unicode = *sorted_chars[j].0;
1649                                j += 1;
1650                            } else {
1651                                break;
1652                            }
1653                        }
1654
1655                        // Add to W array
1656                        if start_unicode == end_unicode {
1657                            // Single character
1658                            w_array.push(Object::Integer(start_unicode as i64));
1659                            w_array.push(Object::Array(vec![Object::Integer(pdf_width)]));
1660                        } else {
1661                            // Range of characters
1662                            w_array.push(Object::Integer(start_unicode as i64));
1663                            w_array.push(Object::Integer(end_unicode as i64));
1664                            w_array.push(Object::Integer(pdf_width));
1665                        }
1666
1667                        i = j;
1668                    }
1669
1670                    return w_array;
1671                }
1672            }
1673        }
1674
1675        // Fallback to reasonable default widths if we can't parse the font
1676        let ranges = vec![
1677            // Space character should be narrower
1678            (0x20, 0x20, 250), // Space
1679            (0x21, 0x2F, 333), // Punctuation
1680            (0x30, 0x39, 500), // Numbers (0-9)
1681            (0x3A, 0x40, 333), // More punctuation
1682            (0x41, 0x5A, 667), // Uppercase letters (A-Z)
1683            (0x5B, 0x60, 333), // Brackets
1684            (0x61, 0x7A, 500), // Lowercase letters (a-z)
1685            (0x7B, 0x7E, 333), // More brackets
1686            // Extended Latin
1687            (0xA0, 0xA0, 250), // Non-breaking space
1688            (0xA1, 0xBF, 333), // Latin-1 punctuation
1689            (0xC0, 0xD6, 667), // Latin-1 uppercase
1690            (0xD7, 0xD7, 564), // Multiplication sign
1691            (0xD8, 0xDE, 667), // More Latin-1 uppercase
1692            (0xDF, 0xF6, 500), // Latin-1 lowercase
1693            (0xF7, 0xF7, 564), // Division sign
1694            (0xF8, 0xFF, 500), // More Latin-1 lowercase
1695            // Latin Extended-A
1696            (0x100, 0x17F, 500), // Latin Extended-A
1697            // Symbols and special characters
1698            (0x2000, 0x200F, 250), // Various spaces
1699            (0x2010, 0x2027, 333), // Hyphens and dashes
1700            (0x2028, 0x202F, 250), // More spaces
1701            (0x2030, 0x206F, 500), // General Punctuation
1702            (0x2070, 0x209F, 400), // Superscripts
1703            (0x20A0, 0x20CF, 600), // Currency symbols
1704            (0x2100, 0x214F, 700), // Letterlike symbols
1705            (0x2190, 0x21FF, 600), // Arrows
1706            (0x2200, 0x22FF, 600), // Mathematical operators
1707            (0x2300, 0x23FF, 600), // Miscellaneous technical
1708            (0x2500, 0x257F, 500), // Box drawing
1709            (0x2580, 0x259F, 500), // Block elements
1710            (0x25A0, 0x25FF, 600), // Geometric shapes
1711            (0x2600, 0x26FF, 600), // Miscellaneous symbols
1712            (0x2700, 0x27BF, 600), // Dingbats
1713        ];
1714
1715        // Convert ranges to W array format
1716        for (start, end, width) in ranges {
1717            if start == end {
1718                // Single character
1719                w_array.push(Object::Integer(start));
1720                w_array.push(Object::Array(vec![Object::Integer(width)]));
1721            } else {
1722                // Range of characters
1723                w_array.push(Object::Integer(start));
1724                w_array.push(Object::Integer(end));
1725                w_array.push(Object::Integer(width));
1726            }
1727        }
1728
1729        w_array
1730    }
1731
1732    /// Generate CIDToGIDMap for Type0 font
1733    fn generate_cid_to_gid_map(
1734        &mut self,
1735        font: &crate::fonts::Font,
1736        subset_mapping: Option<&HashMap<u32, u16>>,
1737    ) -> Result<Vec<u8>> {
1738        use crate::text::fonts::truetype::TrueTypeFont;
1739
1740        // If we have a subset mapping, use it directly
1741        // Otherwise, parse the font to get the original cmap table
1742        let cmap_mappings = if let Some(subset_map) = subset_mapping {
1743            // Use the subset mapping directly
1744            subset_map.clone()
1745        } else {
1746            // Parse the font to get the original cmap table
1747            let tt_font = TrueTypeFont::parse(font.data.clone())?;
1748            let cmap_tables = tt_font.parse_cmap()?;
1749
1750            // Find the best cmap table (Unicode)
1751            let cmap = cmap_tables
1752                .iter()
1753                .find(|t| t.platform_id == 3 && t.encoding_id == 1) // Windows Unicode
1754                .or_else(|| cmap_tables.iter().find(|t| t.platform_id == 0)) // Unicode
1755                .ok_or_else(|| {
1756                    crate::error::PdfError::FontError("No Unicode cmap table found".to_string())
1757                })?;
1758
1759            cmap.mappings.clone()
1760        };
1761
1762        // Build the CIDToGIDMap
1763        // Since we use Unicode code points as CIDs, we need to map Unicode → GlyphID
1764        // The map is a binary array where index = CID (Unicode) * 2, value = GlyphID (big-endian)
1765
1766        // OPTIMIZATION: Only create map for characters actually used in the document
1767        // Get used characters from document tracking
1768        let used_chars = self.document_used_chars.clone().unwrap_or_default();
1769
1770        // Find the maximum Unicode value from used characters or full font
1771        let max_unicode = if !used_chars.is_empty() {
1772            // If we have used chars tracking, only map up to the highest used character
1773            used_chars
1774                .iter()
1775                .map(|ch| *ch as u32)
1776                .max()
1777                .unwrap_or(0x00FF) // At least Basic Latin
1778                .min(0xFFFF) as usize
1779        } else {
1780            // Fallback to original behavior if no tracking
1781            cmap_mappings
1782                .keys()
1783                .max()
1784                .copied()
1785                .unwrap_or(0xFFFF)
1786                .min(0xFFFF) as usize
1787        };
1788
1789        // Create the map: 2 bytes per entry
1790        let mut map = vec![0u8; (max_unicode + 1) * 2];
1791
1792        // Fill in the mappings
1793        let mut sample_mappings = Vec::new();
1794        for (&unicode, &glyph_id) in &cmap_mappings {
1795            if unicode <= max_unicode as u32 {
1796                let idx = (unicode as usize) * 2;
1797                // Write glyph_id in big-endian format
1798                map[idx] = (glyph_id >> 8) as u8;
1799                map[idx + 1] = (glyph_id & 0xFF) as u8;
1800
1801                // Collect some sample mappings for debugging
1802                if unicode == 0x0041 || unicode == 0x0061 || unicode == 0x00E1 || unicode == 0x00F1
1803                {
1804                    sample_mappings.push((unicode, glyph_id));
1805                }
1806            }
1807        }
1808
1809        Ok(map)
1810    }
1811
1812    /// Generate ToUnicode CMap for Type0 font from fonts::Font
1813    fn generate_tounicode_cmap_from_font(&self, font: &crate::fonts::Font) -> Vec<u8> {
1814        use crate::text::fonts::truetype::TrueTypeFont;
1815
1816        let mut cmap = String::new();
1817
1818        // CMap header
1819        cmap.push_str("/CIDInit /ProcSet findresource begin\n");
1820        cmap.push_str("12 dict begin\n");
1821        cmap.push_str("begincmap\n");
1822        cmap.push_str("/CIDSystemInfo\n");
1823        cmap.push_str("<< /Registry (Adobe)\n");
1824        cmap.push_str("   /Ordering (UCS)\n");
1825        cmap.push_str("   /Supplement 0\n");
1826        cmap.push_str(">> def\n");
1827        cmap.push_str("/CMapName /Adobe-Identity-UCS def\n");
1828        cmap.push_str("/CMapType 2 def\n");
1829        cmap.push_str("1 begincodespacerange\n");
1830        cmap.push_str("<0000> <FFFF>\n");
1831        cmap.push_str("endcodespacerange\n");
1832
1833        // Try to get actual mappings from the font
1834        let mut mappings = Vec::new();
1835        let mut has_font_mappings = false;
1836
1837        if let Ok(tt_font) = TrueTypeFont::parse(font.data.clone()) {
1838            if let Ok(cmap_tables) = tt_font.parse_cmap() {
1839                // Find the best cmap table (Unicode)
1840                if let Some(cmap_table) = cmap_tables
1841                    .iter()
1842                    .find(|t| t.platform_id == 3 && t.encoding_id == 1) // Windows Unicode
1843                    .or_else(|| cmap_tables.iter().find(|t| t.platform_id == 0))
1844                // Unicode
1845                {
1846                    // For Identity-H encoding, we use Unicode code points as CIDs
1847                    // So the ToUnicode CMap should map CID (=Unicode) → Unicode
1848                    for (&unicode, &glyph_id) in &cmap_table.mappings {
1849                        if glyph_id > 0 && unicode <= 0xFFFF {
1850                            // Only non-.notdef glyphs
1851                            // Map CID (which is Unicode value) to Unicode
1852                            mappings.push((unicode, unicode));
1853                        }
1854                    }
1855                    has_font_mappings = true;
1856                }
1857            }
1858        }
1859
1860        // If we couldn't get font mappings, use identity mapping for common ranges
1861        if !has_font_mappings {
1862            // Basic Latin and Latin-1 Supplement (0x0020-0x00FF)
1863            for i in 0x0020..=0x00FF {
1864                mappings.push((i, i));
1865            }
1866
1867            // Latin Extended-A (0x0100-0x017F)
1868            for i in 0x0100..=0x017F {
1869                mappings.push((i, i));
1870            }
1871
1872            // CJK Unicode ranges - CRITICAL for CJK font support
1873            // Hiragana (Japanese)
1874            for i in 0x3040..=0x309F {
1875                mappings.push((i, i));
1876            }
1877
1878            // Katakana (Japanese)
1879            for i in 0x30A0..=0x30FF {
1880                mappings.push((i, i));
1881            }
1882
1883            // CJK Unified Ideographs (Chinese, Japanese, Korean)
1884            for i in 0x4E00..=0x9FFF {
1885                mappings.push((i, i));
1886            }
1887
1888            // Hangul Syllables (Korean)
1889            for i in 0xAC00..=0xD7AF {
1890                mappings.push((i, i));
1891            }
1892
1893            // Common symbols and punctuation
1894            for i in 0x2000..=0x206F {
1895                mappings.push((i, i));
1896            }
1897
1898            // Mathematical symbols
1899            for i in 0x2200..=0x22FF {
1900                mappings.push((i, i));
1901            }
1902
1903            // Arrows
1904            for i in 0x2190..=0x21FF {
1905                mappings.push((i, i));
1906            }
1907
1908            // Box drawing
1909            for i in 0x2500..=0x259F {
1910                mappings.push((i, i));
1911            }
1912
1913            // Geometric shapes
1914            for i in 0x25A0..=0x25FF {
1915                mappings.push((i, i));
1916            }
1917
1918            // Miscellaneous symbols
1919            for i in 0x2600..=0x26FF {
1920                mappings.push((i, i));
1921            }
1922        }
1923
1924        // Sort mappings by CID for better organization
1925        mappings.sort_by_key(|&(cid, _)| cid);
1926
1927        // Use more efficient bfrange where possible
1928        let mut i = 0;
1929        while i < mappings.len() {
1930            // Check if we can use a range
1931            let start_cid = mappings[i].0;
1932            let start_unicode = mappings[i].1;
1933            let mut end_idx = i;
1934
1935            // Find consecutive mappings
1936            while end_idx + 1 < mappings.len()
1937                && mappings[end_idx + 1].0 == mappings[end_idx].0 + 1
1938                && mappings[end_idx + 1].1 == mappings[end_idx].1 + 1
1939                && end_idx - i < 99
1940            // Max 100 per block
1941            {
1942                end_idx += 1;
1943            }
1944
1945            if end_idx > i {
1946                // Use bfrange for consecutive mappings
1947                cmap.push_str("1 beginbfrange\n");
1948                cmap.push_str(&format!(
1949                    "<{:04X}> <{:04X}> <{:04X}>\n",
1950                    start_cid, mappings[end_idx].0, start_unicode
1951                ));
1952                cmap.push_str("endbfrange\n");
1953                i = end_idx + 1;
1954            } else {
1955                // Use bfchar for individual mappings
1956                let mut chars = Vec::new();
1957                let chunk_end = (i + 100).min(mappings.len());
1958
1959                for item in &mappings[i..chunk_end] {
1960                    chars.push(*item);
1961                }
1962
1963                if !chars.is_empty() {
1964                    cmap.push_str(&format!("{} beginbfchar\n", chars.len()));
1965                    for (cid, unicode) in chars {
1966                        cmap.push_str(&format!("<{:04X}> <{:04X}>\n", cid, unicode));
1967                    }
1968                    cmap.push_str("endbfchar\n");
1969                }
1970
1971                i = chunk_end;
1972            }
1973        }
1974
1975        // CMap footer
1976        cmap.push_str("endcmap\n");
1977        cmap.push_str("CMapName currentdict /CMap defineresource pop\n");
1978        cmap.push_str("end\n");
1979        cmap.push_str("end\n");
1980
1981        cmap.into_bytes()
1982    }
1983
1984    /// Write a regular TrueType font
1985    #[allow(dead_code)]
1986    fn write_truetype_font(
1987        &mut self,
1988        font_name: &str,
1989        font: &crate::text::font_manager::CustomFont,
1990    ) -> Result<ObjectId> {
1991        // Allocate IDs for font objects
1992        let font_id = self.allocate_object_id();
1993        let descriptor_id = self.allocate_object_id();
1994        let font_file_id = self.allocate_object_id();
1995
1996        // Write font file (embedded TTF data)
1997        if let Some(ref data) = font.font_data {
1998            let mut font_file_dict = Dictionary::new();
1999            font_file_dict.set("Length1", Object::Integer(data.len() as i64));
2000            let font_stream_obj = Object::Stream(font_file_dict, data.clone());
2001            self.write_object(font_file_id, font_stream_obj)?;
2002        }
2003
2004        // Write font descriptor
2005        let mut descriptor = Dictionary::new();
2006        descriptor.set("Type", Object::Name("FontDescriptor".to_string()));
2007        descriptor.set("FontName", Object::Name(font_name.to_string()));
2008        descriptor.set("Flags", Object::Integer(32)); // Non-symbolic font
2009        descriptor.set(
2010            "FontBBox",
2011            Object::Array(vec![
2012                Object::Integer(-1000),
2013                Object::Integer(-1000),
2014                Object::Integer(2000),
2015                Object::Integer(2000),
2016            ]),
2017        );
2018        descriptor.set("ItalicAngle", Object::Integer(0));
2019        descriptor.set("Ascent", Object::Integer(font.descriptor.ascent as i64));
2020        descriptor.set("Descent", Object::Integer(font.descriptor.descent as i64));
2021        descriptor.set(
2022            "CapHeight",
2023            Object::Integer(font.descriptor.cap_height as i64),
2024        );
2025        descriptor.set("StemV", Object::Integer(font.descriptor.stem_v as i64));
2026        descriptor.set("FontFile2", Object::Reference(font_file_id));
2027        self.write_object(descriptor_id, Object::Dictionary(descriptor))?;
2028
2029        // Write font dictionary
2030        let mut font_dict = Dictionary::new();
2031        font_dict.set("Type", Object::Name("Font".to_string()));
2032        font_dict.set("Subtype", Object::Name("TrueType".to_string()));
2033        font_dict.set("BaseFont", Object::Name(font_name.to_string()));
2034        font_dict.set("FirstChar", Object::Integer(0));
2035        font_dict.set("LastChar", Object::Integer(255));
2036
2037        // Create widths array (simplified - all 600)
2038        let widths: Vec<Object> = (0..256).map(|_| Object::Integer(600)).collect();
2039        font_dict.set("Widths", Object::Array(widths));
2040        font_dict.set("FontDescriptor", Object::Reference(descriptor_id));
2041
2042        // Use WinAnsiEncoding for regular TrueType
2043        font_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2044
2045        self.write_object(font_id, Object::Dictionary(font_dict))?;
2046
2047        Ok(font_id)
2048    }
2049
2050    fn write_pages(
2051        &mut self,
2052        document: &Document,
2053        font_refs: &HashMap<String, ObjectId>,
2054    ) -> Result<()> {
2055        let pages_id = self.get_pages_id()?;
2056        let mut pages_dict = Dictionary::new();
2057        pages_dict.set("Type", Object::Name("Pages".to_string()));
2058        pages_dict.set("Count", Object::Integer(document.pages.len() as i64));
2059
2060        let mut kids = Vec::new();
2061
2062        // Allocate page object IDs sequentially
2063        let mut page_ids = Vec::new();
2064        let mut content_ids = Vec::new();
2065        for _ in 0..document.pages.len() {
2066            page_ids.push(self.allocate_object_id());
2067            content_ids.push(self.allocate_object_id());
2068        }
2069
2070        for page_id in &page_ids {
2071            kids.push(Object::Reference(*page_id));
2072        }
2073
2074        pages_dict.set("Kids", Object::Array(kids));
2075
2076        self.write_object(pages_id, Object::Dictionary(pages_dict))?;
2077
2078        // Store page IDs for form field references
2079        self.page_ids = page_ids.clone();
2080
2081        // Write individual pages with font references
2082        for (i, page) in document.pages.iter().enumerate() {
2083            let page_id = page_ids[i];
2084            let content_id = content_ids[i];
2085
2086            self.write_page_with_fonts(page_id, pages_id, content_id, page, document, font_refs)?;
2087            self.write_page_content(content_id, page)?;
2088        }
2089
2090        Ok(())
2091    }
2092
2093    /// Compatibility alias for `write_pages` to maintain backwards compatibility
2094    #[allow(dead_code)]
2095    fn write_pages_with_fonts(
2096        &mut self,
2097        document: &Document,
2098        font_refs: &HashMap<String, ObjectId>,
2099    ) -> Result<()> {
2100        self.write_pages(document, font_refs)
2101    }
2102
2103    fn write_page_with_fonts(
2104        &mut self,
2105        page_id: ObjectId,
2106        parent_id: ObjectId,
2107        content_id: ObjectId,
2108        page: &crate::page::Page,
2109        _document: &Document,
2110        font_refs: &HashMap<String, ObjectId>,
2111    ) -> Result<()> {
2112        // Start with the page's dictionary which includes annotations
2113        let mut page_dict = page.to_dict();
2114
2115        page_dict.set("Type", Object::Name("Page".to_string()));
2116        page_dict.set("Parent", Object::Reference(parent_id));
2117        page_dict.set("Contents", Object::Reference(content_id));
2118
2119        // Get resources dictionary or create new one
2120        let mut resources = if let Some(Object::Dictionary(res)) = page_dict.get("Resources") {
2121            res.clone()
2122        } else {
2123            Dictionary::new()
2124        };
2125
2126        // Add font resources
2127        let mut font_dict = Dictionary::new();
2128
2129        // Add ALL standard PDF fonts (Type1) with WinAnsiEncoding
2130        // This fixes the text rendering issue in dashboards where HelveticaBold was missing
2131
2132        // Helvetica family
2133        let mut helvetica_dict = Dictionary::new();
2134        helvetica_dict.set("Type", Object::Name("Font".to_string()));
2135        helvetica_dict.set("Subtype", Object::Name("Type1".to_string()));
2136        helvetica_dict.set("BaseFont", Object::Name("Helvetica".to_string()));
2137        helvetica_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2138        font_dict.set("Helvetica", Object::Dictionary(helvetica_dict));
2139
2140        let mut helvetica_bold_dict = Dictionary::new();
2141        helvetica_bold_dict.set("Type", Object::Name("Font".to_string()));
2142        helvetica_bold_dict.set("Subtype", Object::Name("Type1".to_string()));
2143        helvetica_bold_dict.set("BaseFont", Object::Name("Helvetica-Bold".to_string()));
2144        helvetica_bold_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2145        font_dict.set("Helvetica-Bold", Object::Dictionary(helvetica_bold_dict));
2146
2147        let mut helvetica_oblique_dict = Dictionary::new();
2148        helvetica_oblique_dict.set("Type", Object::Name("Font".to_string()));
2149        helvetica_oblique_dict.set("Subtype", Object::Name("Type1".to_string()));
2150        helvetica_oblique_dict.set("BaseFont", Object::Name("Helvetica-Oblique".to_string()));
2151        helvetica_oblique_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2152        font_dict.set(
2153            "Helvetica-Oblique",
2154            Object::Dictionary(helvetica_oblique_dict),
2155        );
2156
2157        let mut helvetica_bold_oblique_dict = Dictionary::new();
2158        helvetica_bold_oblique_dict.set("Type", Object::Name("Font".to_string()));
2159        helvetica_bold_oblique_dict.set("Subtype", Object::Name("Type1".to_string()));
2160        helvetica_bold_oblique_dict.set(
2161            "BaseFont",
2162            Object::Name("Helvetica-BoldOblique".to_string()),
2163        );
2164        helvetica_bold_oblique_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2165        font_dict.set(
2166            "Helvetica-BoldOblique",
2167            Object::Dictionary(helvetica_bold_oblique_dict),
2168        );
2169
2170        // Times family
2171        let mut times_dict = Dictionary::new();
2172        times_dict.set("Type", Object::Name("Font".to_string()));
2173        times_dict.set("Subtype", Object::Name("Type1".to_string()));
2174        times_dict.set("BaseFont", Object::Name("Times-Roman".to_string()));
2175        times_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2176        font_dict.set("Times-Roman", Object::Dictionary(times_dict));
2177
2178        let mut times_bold_dict = Dictionary::new();
2179        times_bold_dict.set("Type", Object::Name("Font".to_string()));
2180        times_bold_dict.set("Subtype", Object::Name("Type1".to_string()));
2181        times_bold_dict.set("BaseFont", Object::Name("Times-Bold".to_string()));
2182        times_bold_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2183        font_dict.set("Times-Bold", Object::Dictionary(times_bold_dict));
2184
2185        let mut times_italic_dict = Dictionary::new();
2186        times_italic_dict.set("Type", Object::Name("Font".to_string()));
2187        times_italic_dict.set("Subtype", Object::Name("Type1".to_string()));
2188        times_italic_dict.set("BaseFont", Object::Name("Times-Italic".to_string()));
2189        times_italic_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2190        font_dict.set("Times-Italic", Object::Dictionary(times_italic_dict));
2191
2192        let mut times_bold_italic_dict = Dictionary::new();
2193        times_bold_italic_dict.set("Type", Object::Name("Font".to_string()));
2194        times_bold_italic_dict.set("Subtype", Object::Name("Type1".to_string()));
2195        times_bold_italic_dict.set("BaseFont", Object::Name("Times-BoldItalic".to_string()));
2196        times_bold_italic_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2197        font_dict.set(
2198            "Times-BoldItalic",
2199            Object::Dictionary(times_bold_italic_dict),
2200        );
2201
2202        // Courier family
2203        let mut courier_dict = Dictionary::new();
2204        courier_dict.set("Type", Object::Name("Font".to_string()));
2205        courier_dict.set("Subtype", Object::Name("Type1".to_string()));
2206        courier_dict.set("BaseFont", Object::Name("Courier".to_string()));
2207        courier_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2208        font_dict.set("Courier", Object::Dictionary(courier_dict));
2209
2210        let mut courier_bold_dict = Dictionary::new();
2211        courier_bold_dict.set("Type", Object::Name("Font".to_string()));
2212        courier_bold_dict.set("Subtype", Object::Name("Type1".to_string()));
2213        courier_bold_dict.set("BaseFont", Object::Name("Courier-Bold".to_string()));
2214        courier_bold_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2215        font_dict.set("Courier-Bold", Object::Dictionary(courier_bold_dict));
2216
2217        let mut courier_oblique_dict = Dictionary::new();
2218        courier_oblique_dict.set("Type", Object::Name("Font".to_string()));
2219        courier_oblique_dict.set("Subtype", Object::Name("Type1".to_string()));
2220        courier_oblique_dict.set("BaseFont", Object::Name("Courier-Oblique".to_string()));
2221        courier_oblique_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2222        font_dict.set("Courier-Oblique", Object::Dictionary(courier_oblique_dict));
2223
2224        let mut courier_bold_oblique_dict = Dictionary::new();
2225        courier_bold_oblique_dict.set("Type", Object::Name("Font".to_string()));
2226        courier_bold_oblique_dict.set("Subtype", Object::Name("Type1".to_string()));
2227        courier_bold_oblique_dict.set("BaseFont", Object::Name("Courier-BoldOblique".to_string()));
2228        courier_bold_oblique_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
2229        font_dict.set(
2230            "Courier-BoldOblique",
2231            Object::Dictionary(courier_bold_oblique_dict),
2232        );
2233
2234        // Add custom fonts (Type0 fonts for Unicode support)
2235        for (font_name, font_id) in font_refs {
2236            font_dict.set(font_name, Object::Reference(*font_id));
2237        }
2238
2239        resources.set("Font", Object::Dictionary(font_dict));
2240
2241        // Add images as XObjects
2242        if !page.images().is_empty() {
2243            let mut xobject_dict = Dictionary::new();
2244
2245            for (name, image) in page.images() {
2246                // Use sequential ObjectId allocation to avoid conflicts
2247                let image_id = self.allocate_object_id();
2248
2249                // Check if image has transparency (alpha channel)
2250                if image.has_transparency() {
2251                    // Handle transparent images with SMask
2252                    let (mut main_obj, smask_obj) = image.to_pdf_object_with_transparency()?;
2253
2254                    // If we have a soft mask, write it as a separate object and reference it
2255                    if let Some(smask_stream) = smask_obj {
2256                        let smask_id = self.allocate_object_id();
2257                        self.write_object(smask_id, smask_stream)?;
2258
2259                        // Add SMask reference to the main image dictionary
2260                        if let Object::Stream(ref mut dict, _) = main_obj {
2261                            dict.set("SMask", Object::Reference(smask_id));
2262                        }
2263                    }
2264
2265                    // Write the main image XObject (now with SMask reference if applicable)
2266                    self.write_object(image_id, main_obj)?;
2267                } else {
2268                    // Write the image XObject without transparency
2269                    self.write_object(image_id, image.to_pdf_object())?;
2270                }
2271
2272                // Add reference to XObject dictionary
2273                xobject_dict.set(name, Object::Reference(image_id));
2274            }
2275
2276            resources.set("XObject", Object::Dictionary(xobject_dict));
2277        }
2278
2279        // Add ExtGState resources for transparency
2280        if let Some(extgstate_states) = page.get_extgstate_resources() {
2281            let mut extgstate_dict = Dictionary::new();
2282            for (name, state) in extgstate_states {
2283                let mut state_dict = Dictionary::new();
2284                state_dict.set("Type", Object::Name("ExtGState".to_string()));
2285
2286                // Add transparency parameters
2287                if let Some(alpha_stroke) = state.alpha_stroke {
2288                    state_dict.set("CA", Object::Real(alpha_stroke));
2289                }
2290                if let Some(alpha_fill) = state.alpha_fill {
2291                    state_dict.set("ca", Object::Real(alpha_fill));
2292                }
2293
2294                // Add other parameters as needed
2295                if let Some(line_width) = state.line_width {
2296                    state_dict.set("LW", Object::Real(line_width));
2297                }
2298                if let Some(line_cap) = state.line_cap {
2299                    state_dict.set("LC", Object::Integer(line_cap as i64));
2300                }
2301                if let Some(line_join) = state.line_join {
2302                    state_dict.set("LJ", Object::Integer(line_join as i64));
2303                }
2304                if let Some(dash_pattern) = &state.dash_pattern {
2305                    let dash_objects: Vec<Object> = dash_pattern
2306                        .array
2307                        .iter()
2308                        .map(|&d| Object::Real(d))
2309                        .collect();
2310                    state_dict.set(
2311                        "D",
2312                        Object::Array(vec![
2313                            Object::Array(dash_objects),
2314                            Object::Real(dash_pattern.phase),
2315                        ]),
2316                    );
2317                }
2318
2319                extgstate_dict.set(name, Object::Dictionary(state_dict));
2320            }
2321            if !extgstate_dict.is_empty() {
2322                resources.set("ExtGState", Object::Dictionary(extgstate_dict));
2323            }
2324        }
2325
2326        // Merge preserved resources from original PDF (if any)
2327        // Phase 2.3: Rename preserved fonts to avoid conflicts with overlay fonts
2328        if let Some(preserved_res) = page.get_preserved_resources() {
2329            // Convert pdf_objects::Dictionary to writer Dictionary FIRST
2330            let mut preserved_writer_dict = self.convert_pdf_objects_dict_to_writer(preserved_res);
2331
2332            // Step 1: Rename preserved fonts (F1 → OrigF1)
2333            if let Some(Object::Dictionary(fonts)) = preserved_writer_dict.get("Font") {
2334                // Rename font dictionary keys using our utility function
2335                let renamed_fonts = crate::writer::rename_preserved_fonts(fonts);
2336
2337                // Replace Font dictionary with renamed version
2338                preserved_writer_dict.set("Font", Object::Dictionary(renamed_fonts));
2339            }
2340
2341            // Phase 3.3: Write embedded font streams as indirect objects
2342            // Fonts that were resolved in Phase 3.2 have embedded Stream objects
2343            // We need to write these streams as separate PDF objects and replace with References
2344            if let Some(Object::Dictionary(fonts)) = preserved_writer_dict.get("Font") {
2345                let mut fonts_with_refs = crate::objects::Dictionary::new();
2346
2347                for (font_name, font_obj) in fonts.iter() {
2348                    if let Object::Dictionary(font_dict) = font_obj {
2349                        // Try to extract and write embedded font streams
2350                        let updated_font = self.write_embedded_font_streams(font_dict)?;
2351                        fonts_with_refs.set(font_name, Object::Dictionary(updated_font));
2352                    } else {
2353                        // Not a dictionary, keep as-is
2354                        fonts_with_refs.set(font_name, font_obj.clone());
2355                    }
2356                }
2357
2358                // Replace Font dictionary with version that has References instead of Streams
2359                preserved_writer_dict.set("Font", Object::Dictionary(fonts_with_refs));
2360            }
2361
2362            // Merge each resource category (Font, XObject, ColorSpace, etc.)
2363            for (key, value) in preserved_writer_dict.iter() {
2364                // If the resource category already exists, merge dictionaries
2365                if let Some(Object::Dictionary(existing)) = resources.get(key) {
2366                    if let Object::Dictionary(preserved_dict) = value {
2367                        let mut merged = existing.clone();
2368                        // Add all preserved resources, giving priority to existing (overlay wins)
2369                        for (res_name, res_obj) in preserved_dict.iter() {
2370                            if !merged.contains_key(res_name) {
2371                                merged.set(res_name, res_obj.clone());
2372                            }
2373                        }
2374                        resources.set(key, Object::Dictionary(merged));
2375                    }
2376                } else {
2377                    // Resource category doesn't exist yet, add it directly
2378                    resources.set(key, value.clone());
2379                }
2380            }
2381        }
2382
2383        page_dict.set("Resources", Object::Dictionary(resources));
2384
2385        // Handle form widget annotations
2386        if let Some(Object::Array(annots)) = page_dict.get("Annots") {
2387            let mut new_annots = Vec::new();
2388
2389            for annot in annots {
2390                if let Object::Dictionary(ref annot_dict) = annot {
2391                    if let Some(Object::Name(subtype)) = annot_dict.get("Subtype") {
2392                        if subtype == "Widget" {
2393                            // Process widget annotation
2394                            let widget_id = self.allocate_object_id();
2395                            self.write_object(widget_id, annot.clone())?;
2396                            new_annots.push(Object::Reference(widget_id));
2397
2398                            // Track widget for form fields
2399                            if let Some(Object::Name(_ft)) = annot_dict.get("FT") {
2400                                if let Some(Object::String(field_name)) = annot_dict.get("T") {
2401                                    self.field_widget_map
2402                                        .entry(field_name.clone())
2403                                        .or_default()
2404                                        .push(widget_id);
2405                                    self.field_id_map.insert(field_name.clone(), widget_id);
2406                                    self.form_field_ids.push(widget_id);
2407                                }
2408                            }
2409                            continue;
2410                        }
2411                    }
2412                }
2413                new_annots.push(annot.clone());
2414            }
2415
2416            if !new_annots.is_empty() {
2417                page_dict.set("Annots", Object::Array(new_annots));
2418            }
2419        }
2420
2421        self.write_object(page_id, Object::Dictionary(page_dict))?;
2422        Ok(())
2423    }
2424}
2425
2426impl PdfWriter<BufWriter<std::fs::File>> {
2427    pub fn new(path: impl AsRef<Path>) -> Result<Self> {
2428        let file = std::fs::File::create(path)?;
2429        let writer = BufWriter::new(file);
2430
2431        Ok(Self {
2432            writer,
2433            xref_positions: HashMap::new(),
2434            current_position: 0,
2435            next_object_id: 1,
2436            catalog_id: None,
2437            pages_id: None,
2438            info_id: None,
2439            field_widget_map: HashMap::new(),
2440            field_id_map: HashMap::new(),
2441            form_field_ids: Vec::new(),
2442            page_ids: Vec::new(),
2443            config: WriterConfig::default(),
2444            document_used_chars: None,
2445            buffered_objects: HashMap::new(),
2446            compressed_object_map: HashMap::new(),
2447            prev_xref_offset: None,
2448            base_pdf_size: None,
2449            encrypt_obj_id: None,
2450            file_id: None,
2451            encryption_state: None,
2452            pending_encrypt_dict: None,
2453        })
2454    }
2455}
2456
2457impl<W: Write> PdfWriter<W> {
2458    /// Write embedded font streams as indirect objects (Phase 3.3 + Phase 3.4)
2459    ///
2460    /// Takes a font dictionary that may contain embedded Stream objects
2461    /// in its FontDescriptor, writes those streams as separate PDF objects,
2462    /// and returns an updated font dictionary with References instead of Streams.
2463    ///
2464    /// For Type0 (composite) fonts, also handles:
2465    /// - DescendantFonts array with embedded CIDFont dictionaries
2466    /// - ToUnicode stream embedded directly in Type0 font
2467    /// - CIDFont → FontDescriptor → FontFile2/FontFile3 chain
2468    ///
2469    /// # Example
2470    /// FontDescriptor:
2471    ///   FontFile2: Stream(dict, font_data)  → Write stream as obj 50
2472    ///   FontFile2: Reference(50, 0)          → Updated reference
2473    fn write_embedded_font_streams(
2474        &mut self,
2475        font_dict: &crate::objects::Dictionary,
2476    ) -> Result<crate::objects::Dictionary> {
2477        let mut updated_font = font_dict.clone();
2478
2479        // Phase 3.4: Check for Type0 fonts with embedded DescendantFonts
2480        if let Some(Object::Name(subtype)) = font_dict.get("Subtype") {
2481            if subtype == "Type0" {
2482                // Process DescendantFonts array
2483                if let Some(Object::Array(descendants)) = font_dict.get("DescendantFonts") {
2484                    let mut updated_descendants = Vec::new();
2485
2486                    for descendant in descendants {
2487                        match descendant {
2488                            Object::Dictionary(cidfont) => {
2489                                // CIDFont is embedded as Dictionary, process its FontDescriptor
2490                                let updated_cidfont =
2491                                    self.write_cidfont_embedded_streams(cidfont)?;
2492                                // Write CIDFont as a separate object
2493                                let cidfont_id = self.allocate_object_id();
2494                                self.write_object(cidfont_id, Object::Dictionary(updated_cidfont))?;
2495                                // Replace with reference
2496                                updated_descendants.push(Object::Reference(cidfont_id));
2497                            }
2498                            Object::Reference(_) => {
2499                                // Already a reference, keep as-is
2500                                updated_descendants.push(descendant.clone());
2501                            }
2502                            _ => {
2503                                updated_descendants.push(descendant.clone());
2504                            }
2505                        }
2506                    }
2507
2508                    updated_font.set("DescendantFonts", Object::Array(updated_descendants));
2509                }
2510
2511                // Process ToUnicode stream if embedded
2512                if let Some(Object::Stream(stream_dict, stream_data)) = font_dict.get("ToUnicode") {
2513                    let tounicode_id = self.allocate_object_id();
2514                    self.write_object(
2515                        tounicode_id,
2516                        Object::Stream(stream_dict.clone(), stream_data.clone()),
2517                    )?;
2518                    updated_font.set("ToUnicode", Object::Reference(tounicode_id));
2519                }
2520
2521                return Ok(updated_font);
2522            }
2523        }
2524
2525        // Original Phase 3.3 logic for simple fonts (Type1, TrueType, etc.)
2526        // Check if font has a FontDescriptor
2527        if let Some(Object::Dictionary(descriptor)) = font_dict.get("FontDescriptor") {
2528            let mut updated_descriptor = descriptor.clone();
2529            let font_file_keys = ["FontFile", "FontFile2", "FontFile3"];
2530
2531            // Check each font file key for embedded streams
2532            for key in &font_file_keys {
2533                if let Some(Object::Stream(stream_dict, stream_data)) = descriptor.get(*key) {
2534                    // Found embedded stream! Write it as a separate object
2535                    let stream_id = self.allocate_object_id();
2536                    let stream_obj = Object::Stream(stream_dict.clone(), stream_data.clone());
2537                    self.write_object(stream_id, stream_obj)?;
2538
2539                    // Replace Stream with Reference to the newly written object
2540                    updated_descriptor.set(*key, Object::Reference(stream_id));
2541                }
2542                // If it's already a Reference, leave it as-is
2543            }
2544
2545            // Update FontDescriptor in font dictionary
2546            updated_font.set("FontDescriptor", Object::Dictionary(updated_descriptor));
2547        }
2548
2549        Ok(updated_font)
2550    }
2551
2552    /// Helper function to process CIDFont embedded streams (Phase 3.4)
2553    fn write_cidfont_embedded_streams(
2554        &mut self,
2555        cidfont: &crate::objects::Dictionary,
2556    ) -> Result<crate::objects::Dictionary> {
2557        let mut updated_cidfont = cidfont.clone();
2558
2559        // Process FontDescriptor
2560        if let Some(Object::Dictionary(descriptor)) = cidfont.get("FontDescriptor") {
2561            let mut updated_descriptor = descriptor.clone();
2562            let font_file_keys = ["FontFile", "FontFile2", "FontFile3"];
2563
2564            // Write embedded font streams
2565            for key in &font_file_keys {
2566                if let Some(Object::Stream(stream_dict, stream_data)) = descriptor.get(*key) {
2567                    let stream_id = self.allocate_object_id();
2568                    self.write_object(
2569                        stream_id,
2570                        Object::Stream(stream_dict.clone(), stream_data.clone()),
2571                    )?;
2572                    updated_descriptor.set(*key, Object::Reference(stream_id));
2573                }
2574            }
2575
2576            // Write FontDescriptor as a separate object
2577            let descriptor_id = self.allocate_object_id();
2578            self.write_object(descriptor_id, Object::Dictionary(updated_descriptor))?;
2579
2580            // Update CIDFont to reference the FontDescriptor
2581            updated_cidfont.set("FontDescriptor", Object::Reference(descriptor_id));
2582        }
2583
2584        // Process CIDToGIDMap if present and embedded as stream
2585        if let Some(Object::Stream(map_dict, map_data)) = cidfont.get("CIDToGIDMap") {
2586            let map_id = self.allocate_object_id();
2587            self.write_object(map_id, Object::Stream(map_dict.clone(), map_data.clone()))?;
2588            updated_cidfont.set("CIDToGIDMap", Object::Reference(map_id));
2589        }
2590
2591        Ok(updated_cidfont)
2592    }
2593
2594    fn allocate_object_id(&mut self) -> ObjectId {
2595        let id = ObjectId::new(self.next_object_id, 0);
2596        self.next_object_id += 1;
2597        id
2598    }
2599
2600    /// Get catalog_id, returning error if not initialized
2601    fn get_catalog_id(&self) -> Result<ObjectId> {
2602        self.catalog_id.ok_or_else(|| {
2603            PdfError::InvalidOperation(
2604                "catalog_id not initialized - write_document() must be called first".to_string(),
2605            )
2606        })
2607    }
2608
2609    /// Get pages_id, returning error if not initialized
2610    fn get_pages_id(&self) -> Result<ObjectId> {
2611        self.pages_id.ok_or_else(|| {
2612            PdfError::InvalidOperation(
2613                "pages_id not initialized - write_document() must be called first".to_string(),
2614            )
2615        })
2616    }
2617
2618    /// Get info_id, returning error if not initialized
2619    fn get_info_id(&self) -> Result<ObjectId> {
2620        self.info_id.ok_or_else(|| {
2621            PdfError::InvalidOperation(
2622                "info_id not initialized - write_document() must be called first".to_string(),
2623            )
2624        })
2625    }
2626
2627    fn write_object(&mut self, id: ObjectId, object: Object) -> Result<()> {
2628        use crate::writer::ObjectStreamWriter;
2629
2630        // Encrypt the object if encryption is active
2631        let object = if let Some(ref enc_state) = self.encryption_state {
2632            let mut obj = object;
2633            enc_state.encryptor.encrypt_object(&mut obj, &id)?;
2634            obj
2635        } else {
2636            object
2637        };
2638
2639        // If object streams enabled and object is compressible, buffer it
2640        if self.config.use_object_streams && ObjectStreamWriter::can_compress(&object) {
2641            let mut buffer = Vec::new();
2642            self.write_object_value_to_buffer(&object, &mut buffer)?;
2643            self.buffered_objects.insert(id, buffer);
2644            return Ok(());
2645        }
2646
2647        // Otherwise write immediately (streams, encryption dicts, etc.)
2648        self.xref_positions.insert(id, self.current_position);
2649
2650        // Pre-format header to count exact bytes once
2651        let header = format!("{} {} obj\n", id.number(), id.generation());
2652        self.write_bytes(header.as_bytes())?;
2653
2654        self.write_object_value(&object)?;
2655
2656        self.write_bytes(b"\nendobj\n")?;
2657        Ok(())
2658    }
2659
2660    fn write_object_value(&mut self, object: &Object) -> Result<()> {
2661        match object {
2662            Object::Null => self.write_bytes(b"null")?,
2663            Object::Boolean(b) => self.write_bytes(if *b { b"true" } else { b"false" })?,
2664            Object::Integer(i) => self.write_bytes(i.to_string().as_bytes())?,
2665            Object::Real(f) => self.write_bytes(
2666                format!("{f:.6}")
2667                    .trim_end_matches('0')
2668                    .trim_end_matches('.')
2669                    .as_bytes(),
2670            )?,
2671            Object::String(s) => {
2672                self.write_bytes(b"(")?;
2673                self.write_bytes(s.as_bytes())?;
2674                self.write_bytes(b")")?;
2675            }
2676            Object::ByteString(bytes) => {
2677                // Write as PDF hex string <AABB...> for byte-perfect binary data
2678                self.write_bytes(b"<")?;
2679                for byte in bytes {
2680                    self.write_bytes(format!("{byte:02X}").as_bytes())?;
2681                }
2682                self.write_bytes(b">")?;
2683            }
2684            Object::Name(n) => {
2685                self.write_bytes(b"/")?;
2686                self.write_bytes(n.as_bytes())?;
2687            }
2688            Object::Array(arr) => {
2689                self.write_bytes(b"[")?;
2690                for (i, obj) in arr.iter().enumerate() {
2691                    if i > 0 {
2692                        self.write_bytes(b" ")?;
2693                    }
2694                    self.write_object_value(obj)?;
2695                }
2696                self.write_bytes(b"]")?;
2697            }
2698            Object::Dictionary(dict) => {
2699                self.write_bytes(b"<<")?;
2700                for (key, value) in dict.entries() {
2701                    self.write_bytes(b"\n/")?;
2702                    self.write_bytes(key.as_bytes())?;
2703                    self.write_bytes(b" ")?;
2704                    self.write_object_value(value)?;
2705                }
2706                self.write_bytes(b"\n>>")?;
2707            }
2708            Object::Stream(dict, data) => {
2709                // CRITICAL: Ensure Length in dictionary matches actual data length
2710                // This prevents "Bad Length" PDF syntax errors
2711                let mut corrected_dict = dict.clone();
2712                corrected_dict.set("Length", Object::Integer(data.len() as i64));
2713
2714                self.write_object_value(&Object::Dictionary(corrected_dict))?;
2715                self.write_bytes(b"\nstream\n")?;
2716                self.write_bytes(data)?;
2717                self.write_bytes(b"\nendstream")?;
2718            }
2719            Object::Reference(id) => {
2720                let ref_str = format!("{} {} R", id.number(), id.generation());
2721                self.write_bytes(ref_str.as_bytes())?;
2722            }
2723        }
2724        Ok(())
2725    }
2726
2727    /// Write object value to a buffer (for object streams)
2728    fn write_object_value_to_buffer(&self, object: &Object, buffer: &mut Vec<u8>) -> Result<()> {
2729        match object {
2730            Object::Null => buffer.extend_from_slice(b"null"),
2731            Object::Boolean(b) => buffer.extend_from_slice(if *b { b"true" } else { b"false" }),
2732            Object::Integer(i) => buffer.extend_from_slice(i.to_string().as_bytes()),
2733            Object::Real(f) => buffer.extend_from_slice(
2734                format!("{f:.6}")
2735                    .trim_end_matches('0')
2736                    .trim_end_matches('.')
2737                    .as_bytes(),
2738            ),
2739            Object::String(s) => {
2740                buffer.push(b'(');
2741                buffer.extend_from_slice(s.as_bytes());
2742                buffer.push(b')');
2743            }
2744            Object::ByteString(bytes) => {
2745                buffer.push(b'<');
2746                for byte in bytes {
2747                    buffer.extend_from_slice(format!("{byte:02X}").as_bytes());
2748                }
2749                buffer.push(b'>');
2750            }
2751            Object::Name(n) => {
2752                buffer.push(b'/');
2753                buffer.extend_from_slice(n.as_bytes());
2754            }
2755            Object::Array(arr) => {
2756                buffer.push(b'[');
2757                for (i, obj) in arr.iter().enumerate() {
2758                    if i > 0 {
2759                        buffer.push(b' ');
2760                    }
2761                    self.write_object_value_to_buffer(obj, buffer)?;
2762                }
2763                buffer.push(b']');
2764            }
2765            Object::Dictionary(dict) => {
2766                buffer.extend_from_slice(b"<<");
2767                for (key, value) in dict.entries() {
2768                    buffer.extend_from_slice(b"\n/");
2769                    buffer.extend_from_slice(key.as_bytes());
2770                    buffer.push(b' ');
2771                    self.write_object_value_to_buffer(value, buffer)?;
2772                }
2773                buffer.extend_from_slice(b"\n>>");
2774            }
2775            Object::Stream(_, _) => {
2776                // Streams should never be compressed in object streams
2777                return Err(crate::error::PdfError::ObjectStreamError(
2778                    "Cannot compress stream objects in object streams".to_string(),
2779                ));
2780            }
2781            Object::Reference(id) => {
2782                let ref_str = format!("{} {} R", id.number(), id.generation());
2783                buffer.extend_from_slice(ref_str.as_bytes());
2784            }
2785        }
2786        Ok(())
2787    }
2788
2789    /// Flush buffered objects as compressed object streams
2790    fn flush_object_streams(&mut self) -> Result<()> {
2791        if self.buffered_objects.is_empty() {
2792            return Ok(());
2793        }
2794
2795        // Create object stream writer
2796        let config = ObjectStreamConfig {
2797            max_objects_per_stream: 100,
2798            compression_level: 6,
2799            enabled: true,
2800        };
2801        let mut os_writer = ObjectStreamWriter::new(config);
2802
2803        // Sort buffered objects by ID for deterministic output
2804        let mut buffered: Vec<_> = self.buffered_objects.iter().collect();
2805        buffered.sort_by_key(|(id, _)| id.number());
2806
2807        // Add all buffered objects to the stream writer
2808        for (id, data) in buffered {
2809            os_writer.add_object(*id, data.clone())?;
2810        }
2811
2812        // Finalize and get completed streams
2813        let streams = os_writer.finalize()?;
2814
2815        // Write each object stream to the PDF
2816        for mut stream in streams {
2817            let stream_id = stream.stream_id;
2818
2819            // Generate compressed stream data
2820            let compressed_data = stream.generate_stream_data(6)?;
2821
2822            // Generate stream dictionary
2823            let dict = stream.generate_dictionary(&compressed_data);
2824
2825            // Track compressed object mapping for xref
2826            for (index, (obj_id, _)) in stream.objects.iter().enumerate() {
2827                self.compressed_object_map
2828                    .insert(*obj_id, (stream_id, index as u32));
2829            }
2830
2831            // Write the object stream itself
2832            self.xref_positions.insert(stream_id, self.current_position);
2833
2834            let header = format!("{} {} obj\n", stream_id.number(), stream_id.generation());
2835            self.write_bytes(header.as_bytes())?;
2836
2837            self.write_object_value(&Object::Dictionary(dict))?;
2838
2839            self.write_bytes(b"\nstream\n")?;
2840            self.write_bytes(&compressed_data)?;
2841            self.write_bytes(b"\nendstream\nendobj\n")?;
2842        }
2843
2844        Ok(())
2845    }
2846
2847    fn write_xref(&mut self) -> Result<()> {
2848        self.write_bytes(b"xref\n")?;
2849
2850        // Sort by object number and write entries
2851        let mut entries: Vec<_> = self
2852            .xref_positions
2853            .iter()
2854            .map(|(id, pos)| (*id, *pos))
2855            .collect();
2856        entries.sort_by_key(|(id, _)| id.number());
2857
2858        // Find the highest object number to determine size
2859        let max_obj_num = entries.iter().map(|(id, _)| id.number()).max().unwrap_or(0);
2860
2861        // Write subsection header - PDF 1.7 spec allows multiple subsections
2862        // For simplicity, write one subsection from 0 to max
2863        self.write_bytes(b"0 ")?;
2864        self.write_bytes((max_obj_num + 1).to_string().as_bytes())?;
2865        self.write_bytes(b"\n")?;
2866
2867        // Write free object entry
2868        self.write_bytes(b"0000000000 65535 f \n")?;
2869
2870        // Write entries for all object numbers from 1 to max
2871        // Fill in gaps with free entries
2872        for obj_num in 1..=max_obj_num {
2873            let _obj_id = ObjectId::new(obj_num, 0);
2874            if let Some((_, position)) = entries.iter().find(|(id, _)| id.number() == obj_num) {
2875                let entry = format!("{:010} {:05} n \n", position, 0);
2876                self.write_bytes(entry.as_bytes())?;
2877            } else {
2878                // Free entry for gap
2879                self.write_bytes(b"0000000000 00000 f \n")?;
2880            }
2881        }
2882
2883        Ok(())
2884    }
2885
2886    fn write_xref_stream(&mut self) -> Result<()> {
2887        let catalog_id = self.get_catalog_id()?;
2888        let info_id = self.get_info_id()?;
2889
2890        // Allocate object ID for the xref stream
2891        let xref_stream_id = self.allocate_object_id();
2892        let xref_position = self.current_position;
2893
2894        // Create XRef stream writer with trailer information
2895        let mut xref_writer = XRefStreamWriter::new(xref_stream_id);
2896        xref_writer.set_trailer_info(catalog_id, info_id);
2897
2898        // Add free entry for object 0
2899        xref_writer.add_free_entry(0, 65535);
2900
2901        // Sort entries by object number
2902        let mut entries: Vec<_> = self
2903            .xref_positions
2904            .iter()
2905            .map(|(id, pos)| (*id, *pos))
2906            .collect();
2907        entries.sort_by_key(|(id, _)| id.number());
2908
2909        // Find the highest object number (including the xref stream itself)
2910        let max_obj_num = entries
2911            .iter()
2912            .map(|(id, _)| id.number())
2913            .max()
2914            .unwrap_or(0)
2915            .max(xref_stream_id.number());
2916
2917        // Add entries for all objects (including compressed objects)
2918        for obj_num in 1..=max_obj_num {
2919            let obj_id = ObjectId::new(obj_num, 0);
2920
2921            if obj_num == xref_stream_id.number() {
2922                // The xref stream entry will be added with the correct position
2923                xref_writer.add_in_use_entry(xref_position, 0);
2924            } else if let Some((stream_id, index)) = self.compressed_object_map.get(&obj_id) {
2925                // Type 2: Object is compressed in an object stream
2926                xref_writer.add_compressed_entry(stream_id.number(), *index);
2927            } else if let Some((id, position)) =
2928                entries.iter().find(|(id, _)| id.number() == obj_num)
2929            {
2930                // Type 1: Regular in-use entry
2931                xref_writer.add_in_use_entry(*position, id.generation());
2932            } else {
2933                // Type 0: Free entry for gap
2934                xref_writer.add_free_entry(0, 0);
2935            }
2936        }
2937
2938        // Mark position for xref stream object
2939        self.xref_positions.insert(xref_stream_id, xref_position);
2940
2941        // Write object header
2942        self.write_bytes(
2943            format!(
2944                "{} {} obj\n",
2945                xref_stream_id.number(),
2946                xref_stream_id.generation()
2947            )
2948            .as_bytes(),
2949        )?;
2950
2951        // Get the encoded data
2952        let uncompressed_data = xref_writer.encode_entries();
2953        let final_data = if self.config.compress_streams {
2954            crate::compression::compress(&uncompressed_data)?
2955        } else {
2956            uncompressed_data
2957        };
2958
2959        // Create and write dictionary
2960        let mut dict = xref_writer.create_dictionary(None);
2961        dict.set("Length", Object::Integer(final_data.len() as i64));
2962
2963        // Add filter if compression is enabled
2964        if self.config.compress_streams {
2965            dict.set("Filter", Object::Name("FlateDecode".to_string()));
2966        }
2967        self.write_bytes(b"<<")?;
2968        for (key, value) in dict.iter() {
2969            self.write_bytes(b"\n/")?;
2970            self.write_bytes(key.as_bytes())?;
2971            self.write_bytes(b" ")?;
2972            self.write_object_value(value)?;
2973        }
2974        self.write_bytes(b"\n>>\n")?;
2975
2976        // Write stream
2977        self.write_bytes(b"stream\n")?;
2978        self.write_bytes(&final_data)?;
2979        self.write_bytes(b"\nendstream\n")?;
2980        self.write_bytes(b"endobj\n")?;
2981
2982        // Write startxref and EOF
2983        self.write_bytes(b"\nstartxref\n")?;
2984        self.write_bytes(xref_position.to_string().as_bytes())?;
2985        self.write_bytes(b"\n%%EOF\n")?;
2986
2987        Ok(())
2988    }
2989
2990    /// Write the encryption dictionary as an indirect object and store
2991    /// the object ID and file ID for the trailer.
2992    /// Initialize encryption state: generates file ID, creates encryption dict,
2993    /// computes encryption key, and builds the ObjectEncryptor.
2994    /// The /Encrypt dict object is written later (after all other objects) since it
2995    /// must NOT be encrypted itself (ISO 32000-1 §7.6.1).
2996    fn init_encryption(&mut self, encryption: &crate::document::DocumentEncryption) -> Result<()> {
2997        use crate::encryption::{
2998            CryptFilterManager, CryptFilterMethod, FunctionalCryptFilter, ObjectEncryptor,
2999        };
3000        use std::sync::Arc;
3001
3002        // Generate file ID (16 random bytes, required by ISO 32000-1 §7.5.5)
3003        let mut fid = vec![0u8; 16];
3004        use rand::Rng;
3005        rand::rng().fill_bytes(&mut fid);
3006
3007        let enc_dict = encryption
3008            .create_encryption_dict(Some(&fid))
3009            .map_err(|e| PdfError::EncryptionError(format!("encryption dict: {}", e)))?;
3010
3011        // Compute encryption key
3012        let enc_key = encryption
3013            .get_encryption_key(&enc_dict, Some(&fid))
3014            .map_err(|e| PdfError::EncryptionError(format!("encryption key: {}", e)))?;
3015
3016        // Build CryptFilterManager based on encryption strength
3017        let handler = encryption.handler();
3018        let (method, key_len) = match encryption.strength {
3019            crate::document::EncryptionStrength::Rc4_40bit => (CryptFilterMethod::V2, Some(5)),
3020            crate::document::EncryptionStrength::Rc4_128bit => (CryptFilterMethod::V2, Some(16)),
3021            crate::document::EncryptionStrength::Aes128 => (CryptFilterMethod::AESV2, Some(16)),
3022            crate::document::EncryptionStrength::Aes256 => (CryptFilterMethod::AESV3, Some(32)),
3023        };
3024
3025        let std_filter = FunctionalCryptFilter {
3026            name: "StdCF".to_string(),
3027            method,
3028            length: key_len,
3029            auth_event: crate::encryption::AuthEvent::DocOpen,
3030            recipients: None,
3031        };
3032
3033        let mut filter_manager =
3034            CryptFilterManager::new(Box::new(handler), "StdCF".to_string(), "StdCF".to_string());
3035        filter_manager.add_filter(std_filter);
3036
3037        let encryptor =
3038            ObjectEncryptor::new(Arc::new(filter_manager), enc_key, enc_dict.encrypt_metadata);
3039
3040        // Reserve ID for /Encrypt dict (will be written at the end)
3041        let encrypt_id = self.allocate_object_id();
3042        self.encrypt_obj_id = Some(encrypt_id);
3043        self.file_id = Some(fid);
3044        self.encryption_state = Some(WriterEncryptionState { encryptor });
3045
3046        // Store the dict to write later
3047        self.pending_encrypt_dict = Some(enc_dict.to_dict());
3048
3049        Ok(())
3050    }
3051
3052    /// Write the /Encrypt dictionary object (must NOT be encrypted per ISO 32000-1 §7.6.1)
3053    fn write_encryption_dict(&mut self) -> Result<()> {
3054        if let (Some(encrypt_id), Some(dict)) =
3055            (self.encrypt_obj_id, self.pending_encrypt_dict.take())
3056        {
3057            // Temporarily disable encryption so the /Encrypt dict is not encrypted
3058            let enc_state = self.encryption_state.take();
3059            self.write_object(encrypt_id, Object::Dictionary(dict))?;
3060            self.encryption_state = enc_state;
3061        }
3062        Ok(())
3063    }
3064
3065    fn write_trailer(&mut self, xref_position: u64) -> Result<()> {
3066        let catalog_id = self.get_catalog_id()?;
3067        let info_id = self.get_info_id()?;
3068        // Find the highest object number to determine size
3069        let max_obj_num = self
3070            .xref_positions
3071            .keys()
3072            .map(|id| id.number())
3073            .max()
3074            .unwrap_or(0);
3075
3076        let mut trailer = Dictionary::new();
3077        trailer.set("Size", Object::Integer((max_obj_num + 1) as i64));
3078        trailer.set("Root", Object::Reference(catalog_id));
3079        trailer.set("Info", Object::Reference(info_id));
3080
3081        // Add /Prev pointer for incremental updates (ISO 32000-1 §7.5.6)
3082        if let Some(prev_xref) = self.prev_xref_offset {
3083            trailer.set("Prev", Object::Integer(prev_xref as i64));
3084        }
3085
3086        // Add /Encrypt reference and /ID array for encrypted documents
3087        if let Some(encrypt_id) = self.encrypt_obj_id {
3088            trailer.set("Encrypt", Object::Reference(encrypt_id));
3089        }
3090        if let Some(ref fid) = self.file_id {
3091            trailer.set(
3092                "ID",
3093                Object::Array(vec![
3094                    Object::ByteString(fid.clone()),
3095                    Object::ByteString(fid.clone()),
3096                ]),
3097            );
3098        }
3099
3100        self.write_bytes(b"trailer\n")?;
3101        self.write_object_value(&Object::Dictionary(trailer))?;
3102        self.write_bytes(b"\nstartxref\n")?;
3103        self.write_bytes(xref_position.to_string().as_bytes())?;
3104        self.write_bytes(b"\n%%EOF\n")?;
3105
3106        Ok(())
3107    }
3108
3109    fn write_bytes(&mut self, data: &[u8]) -> Result<()> {
3110        self.writer.write_all(data)?;
3111        self.current_position += data.len() as u64;
3112        Ok(())
3113    }
3114
3115    #[allow(dead_code)]
3116    fn create_widget_appearance_stream(&mut self, widget_dict: &Dictionary) -> Result<ObjectId> {
3117        // Get widget rectangle
3118        let rect = if let Some(Object::Array(rect_array)) = widget_dict.get("Rect") {
3119            if rect_array.len() >= 4 {
3120                if let (
3121                    Some(Object::Real(x1)),
3122                    Some(Object::Real(y1)),
3123                    Some(Object::Real(x2)),
3124                    Some(Object::Real(y2)),
3125                ) = (
3126                    rect_array.first(),
3127                    rect_array.get(1),
3128                    rect_array.get(2),
3129                    rect_array.get(3),
3130                ) {
3131                    (*x1, *y1, *x2, *y2)
3132                } else {
3133                    (0.0, 0.0, 100.0, 20.0) // Default
3134                }
3135            } else {
3136                (0.0, 0.0, 100.0, 20.0) // Default
3137            }
3138        } else {
3139            (0.0, 0.0, 100.0, 20.0) // Default
3140        };
3141
3142        let width = rect.2 - rect.0;
3143        let height = rect.3 - rect.1;
3144
3145        // Create appearance stream content
3146        let mut content = String::new();
3147
3148        // Set graphics state
3149        content.push_str("q\n");
3150
3151        // Draw border (black)
3152        content.push_str("0 0 0 RG\n"); // Black stroke color
3153        content.push_str("1 w\n"); // 1pt line width
3154
3155        // Draw rectangle border
3156        content.push_str(&format!("0 0 {width} {height} re\n"));
3157        content.push_str("S\n"); // Stroke
3158
3159        // Fill with white background
3160        content.push_str("1 1 1 rg\n"); // White fill color
3161        content.push_str(&format!("0.5 0.5 {} {} re\n", width - 1.0, height - 1.0));
3162        content.push_str("f\n"); // Fill
3163
3164        // Restore graphics state
3165        content.push_str("Q\n");
3166
3167        // Create stream dictionary
3168        let mut stream_dict = Dictionary::new();
3169        stream_dict.set("Type", Object::Name("XObject".to_string()));
3170        stream_dict.set("Subtype", Object::Name("Form".to_string()));
3171        stream_dict.set(
3172            "BBox",
3173            Object::Array(vec![
3174                Object::Real(0.0),
3175                Object::Real(0.0),
3176                Object::Real(width),
3177                Object::Real(height),
3178            ]),
3179        );
3180        stream_dict.set("Resources", Object::Dictionary(Dictionary::new()));
3181        stream_dict.set("Length", Object::Integer(content.len() as i64));
3182
3183        // Write the appearance stream
3184        let stream_id = self.allocate_object_id();
3185        self.write_object(stream_id, Object::Stream(stream_dict, content.into_bytes()))?;
3186
3187        Ok(stream_id)
3188    }
3189
3190    #[allow(dead_code)]
3191    fn create_field_appearance_stream(
3192        &mut self,
3193        field_dict: &Dictionary,
3194        widget: &crate::forms::Widget,
3195    ) -> Result<ObjectId> {
3196        let width = widget.rect.upper_right.x - widget.rect.lower_left.x;
3197        let height = widget.rect.upper_right.y - widget.rect.lower_left.y;
3198
3199        // Create appearance stream content
3200        let mut content = String::new();
3201
3202        // Set graphics state
3203        content.push_str("q\n");
3204
3205        // Draw background if specified
3206        if let Some(bg_color) = &widget.appearance.background_color {
3207            match bg_color {
3208                crate::graphics::Color::Gray(g) => {
3209                    content.push_str(&format!("{g} g\n"));
3210                }
3211                crate::graphics::Color::Rgb(r, g, b) => {
3212                    content.push_str(&format!("{r} {g} {b} rg\n"));
3213                }
3214                crate::graphics::Color::Cmyk(c, m, y, k) => {
3215                    content.push_str(&format!("{c} {m} {y} {k} k\n"));
3216                }
3217            }
3218            content.push_str(&format!("0 0 {width} {height} re\n"));
3219            content.push_str("f\n");
3220        }
3221
3222        // Draw border
3223        if let Some(border_color) = &widget.appearance.border_color {
3224            match border_color {
3225                crate::graphics::Color::Gray(g) => {
3226                    content.push_str(&format!("{g} G\n"));
3227                }
3228                crate::graphics::Color::Rgb(r, g, b) => {
3229                    content.push_str(&format!("{r} {g} {b} RG\n"));
3230                }
3231                crate::graphics::Color::Cmyk(c, m, y, k) => {
3232                    content.push_str(&format!("{c} {m} {y} {k} K\n"));
3233                }
3234            }
3235            content.push_str(&format!("{} w\n", widget.appearance.border_width));
3236            content.push_str(&format!("0 0 {width} {height} re\n"));
3237            content.push_str("S\n");
3238        }
3239
3240        // For checkboxes, add a checkmark if checked
3241        if let Some(Object::Name(ft)) = field_dict.get("FT") {
3242            if ft == "Btn" {
3243                if let Some(Object::Name(v)) = field_dict.get("V") {
3244                    if v == "Yes" {
3245                        // Draw checkmark
3246                        content.push_str("0 0 0 RG\n"); // Black
3247                        content.push_str("2 w\n");
3248                        let margin = width * 0.2;
3249                        content.push_str(&format!("{} {} m\n", margin, height / 2.0));
3250                        content.push_str(&format!("{} {} l\n", width / 2.0, margin));
3251                        content.push_str(&format!("{} {} l\n", width - margin, height - margin));
3252                        content.push_str("S\n");
3253                    }
3254                }
3255            }
3256        }
3257
3258        // Restore graphics state
3259        content.push_str("Q\n");
3260
3261        // Create stream dictionary
3262        let mut stream_dict = Dictionary::new();
3263        stream_dict.set("Type", Object::Name("XObject".to_string()));
3264        stream_dict.set("Subtype", Object::Name("Form".to_string()));
3265        stream_dict.set(
3266            "BBox",
3267            Object::Array(vec![
3268                Object::Real(0.0),
3269                Object::Real(0.0),
3270                Object::Real(width),
3271                Object::Real(height),
3272            ]),
3273        );
3274        stream_dict.set("Resources", Object::Dictionary(Dictionary::new()));
3275        stream_dict.set("Length", Object::Integer(content.len() as i64));
3276
3277        // Write the appearance stream
3278        let stream_id = self.allocate_object_id();
3279        self.write_object(stream_id, Object::Stream(stream_dict, content.into_bytes()))?;
3280
3281        Ok(stream_id)
3282    }
3283}
3284
3285/// Format a DateTime as a PDF date string (D:YYYYMMDDHHmmSSOHH'mm)
3286fn format_pdf_date(date: DateTime<Utc>) -> String {
3287    // Format the UTC date according to PDF specification
3288    // D:YYYYMMDDHHmmSSOHH'mm where O is the relationship of local time to UTC (+ or -)
3289    let formatted = date.format("D:%Y%m%d%H%M%S");
3290
3291    // For UTC, the offset is always +00'00
3292    format!("{formatted}+00'00")
3293}
3294
3295#[cfg(test)]
3296mod tests;
3297
3298#[cfg(test)]
3299mod rigorous_tests;