oxidize_pdf/writer/pdf_writer/
mod.rs

1use crate::document::Document;
2use crate::error::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}
23
24impl Default for WriterConfig {
25    fn default() -> Self {
26        Self {
27            use_xref_streams: false,
28            use_object_streams: false,
29            pdf_version: "1.7".to_string(),
30            compress_streams: true,
31        }
32    }
33}
34
35impl WriterConfig {
36    /// Create a modern PDF 1.5+ configuration with all compression features enabled
37    pub fn modern() -> Self {
38        Self {
39            use_xref_streams: true,
40            use_object_streams: true,
41            pdf_version: "1.5".to_string(),
42            compress_streams: true,
43        }
44    }
45
46    /// Create a legacy PDF 1.4 configuration without modern compression
47    pub fn legacy() -> Self {
48        Self {
49            use_xref_streams: false,
50            use_object_streams: false,
51            pdf_version: "1.4".to_string(),
52            compress_streams: true,
53        }
54    }
55}
56
57pub struct PdfWriter<W: Write> {
58    writer: W,
59    xref_positions: HashMap<ObjectId, u64>,
60    current_position: u64,
61    next_object_id: u32,
62    // Maps for tracking object IDs during writing
63    catalog_id: Option<ObjectId>,
64    pages_id: Option<ObjectId>,
65    info_id: Option<ObjectId>,
66    // Maps for tracking form fields and their widgets
67    #[allow(dead_code)]
68    field_widget_map: HashMap<String, Vec<ObjectId>>, // field name -> widget IDs
69    #[allow(dead_code)]
70    field_id_map: HashMap<String, ObjectId>, // field name -> field ID
71    form_field_ids: Vec<ObjectId>, // form field IDs to add to page annotations
72    page_ids: Vec<ObjectId>,       // page IDs for form field references
73    // Configuration
74    config: WriterConfig,
75    // Characters used in document (for font subsetting)
76    document_used_chars: Option<std::collections::HashSet<char>>,
77    // Object stream buffering (when use_object_streams is enabled)
78    buffered_objects: HashMap<ObjectId, Vec<u8>>,
79    compressed_object_map: HashMap<ObjectId, (ObjectId, u32)>, // obj_id -> (stream_id, index)
80}
81
82impl<W: Write> PdfWriter<W> {
83    pub fn new_with_writer(writer: W) -> Self {
84        Self::with_config(writer, WriterConfig::default())
85    }
86
87    pub fn with_config(writer: W, config: WriterConfig) -> Self {
88        Self {
89            writer,
90            xref_positions: HashMap::new(),
91            current_position: 0,
92            next_object_id: 1, // Start at 1 for sequential numbering
93            catalog_id: None,
94            pages_id: None,
95            info_id: None,
96            field_widget_map: HashMap::new(),
97            field_id_map: HashMap::new(),
98            form_field_ids: Vec::new(),
99            page_ids: Vec::new(),
100            config,
101            document_used_chars: None,
102            buffered_objects: HashMap::new(),
103            compressed_object_map: HashMap::new(),
104        }
105    }
106
107    pub fn write_document(&mut self, document: &mut Document) -> Result<()> {
108        // Store used characters for font subsetting
109        if !document.used_characters.is_empty() {
110            self.document_used_chars = Some(document.used_characters.clone());
111        }
112
113        self.write_header()?;
114
115        // Reserve object IDs for fixed objects (written in order)
116        self.catalog_id = Some(self.allocate_object_id());
117        self.pages_id = Some(self.allocate_object_id());
118        self.info_id = Some(self.allocate_object_id());
119
120        // Write custom fonts first (so pages can reference them)
121        let font_refs = self.write_fonts(document)?;
122
123        // Write pages (they contain widget annotations and font references)
124        self.write_pages(document, &font_refs)?;
125
126        // Write form fields (must be after pages so we can track widgets)
127        self.write_form_fields(document)?;
128
129        // Write catalog (must be after forms so AcroForm has correct field references)
130        self.write_catalog(document)?;
131
132        // Write document info
133        self.write_info(document)?;
134
135        // Flush buffered objects as object streams (if enabled)
136        if self.config.use_object_streams {
137            self.flush_object_streams()?;
138        }
139
140        // Write xref table or stream
141        let xref_position = self.current_position;
142        if self.config.use_xref_streams {
143            self.write_xref_stream()?;
144        } else {
145            self.write_xref()?;
146        }
147
148        // Write trailer (only for traditional xref)
149        if !self.config.use_xref_streams {
150            self.write_trailer(xref_position)?;
151        }
152
153        if let Ok(()) = self.writer.flush() {
154            // Flush succeeded
155        }
156        Ok(())
157    }
158
159    fn write_header(&mut self) -> Result<()> {
160        let header = format!("%PDF-{}\n", self.config.pdf_version);
161        self.write_bytes(header.as_bytes())?;
162        // Binary comment to ensure file is treated as binary
163        self.write_bytes(&[b'%', 0xE2, 0xE3, 0xCF, 0xD3, b'\n'])?;
164        Ok(())
165    }
166
167    fn write_catalog(&mut self, document: &mut Document) -> Result<()> {
168        let catalog_id = self.catalog_id.expect("catalog_id must be set");
169        let pages_id = self.pages_id.expect("pages_id must be set");
170
171        let mut catalog = Dictionary::new();
172        catalog.set("Type", Object::Name("Catalog".to_string()));
173        catalog.set("Pages", Object::Reference(pages_id));
174
175        // Process FormManager if present to update AcroForm
176        // We'll write the actual fields after pages are written
177        if let Some(_form_manager) = &document.form_manager {
178            // Ensure AcroForm exists
179            if document.acro_form.is_none() {
180                document.acro_form = Some(crate::forms::AcroForm::new());
181            }
182        }
183
184        // Add AcroForm if present
185        if let Some(acro_form) = &document.acro_form {
186            // Reserve object ID for AcroForm
187            let acro_form_id = self.allocate_object_id();
188
189            // Write AcroForm object
190            self.write_object(acro_form_id, Object::Dictionary(acro_form.to_dict()))?;
191
192            // Reference it in catalog
193            catalog.set("AcroForm", Object::Reference(acro_form_id));
194        }
195
196        // Add Outlines if present
197        if let Some(outline_tree) = &document.outline {
198            if !outline_tree.items.is_empty() {
199                let outline_root_id = self.write_outline_tree(outline_tree)?;
200                catalog.set("Outlines", Object::Reference(outline_root_id));
201            }
202        }
203
204        // Add StructTreeRoot if present (Tagged PDF - ISO 32000-1 §14.8)
205        if let Some(struct_tree) = &document.struct_tree {
206            if !struct_tree.is_empty() {
207                let struct_tree_root_id = self.write_struct_tree(struct_tree)?;
208                catalog.set("StructTreeRoot", Object::Reference(struct_tree_root_id));
209                // Mark as Tagged PDF
210                catalog.set("MarkInfo", {
211                    let mut mark_info = Dictionary::new();
212                    mark_info.set("Marked", Object::Boolean(true));
213                    Object::Dictionary(mark_info)
214                });
215            }
216        }
217
218        // Add XMP Metadata stream (ISO 32000-1 §14.3.2)
219        // Generate XMP from document metadata and embed as stream
220        let xmp_metadata = document.create_xmp_metadata();
221        let xmp_packet = xmp_metadata.to_xmp_packet();
222        let metadata_id = self.allocate_object_id();
223
224        // Create metadata stream dictionary
225        let mut metadata_dict = Dictionary::new();
226        metadata_dict.set("Type", Object::Name("Metadata".to_string()));
227        metadata_dict.set("Subtype", Object::Name("XML".to_string()));
228        metadata_dict.set("Length", Object::Integer(xmp_packet.len() as i64));
229
230        // Write XMP metadata stream
231        self.write_object(
232            metadata_id,
233            Object::Stream(metadata_dict, xmp_packet.into_bytes()),
234        )?;
235
236        // Reference it in catalog
237        catalog.set("Metadata", Object::Reference(metadata_id));
238
239        self.write_object(catalog_id, Object::Dictionary(catalog))?;
240        Ok(())
241    }
242
243    fn write_page_content(&mut self, content_id: ObjectId, page: &crate::page::Page) -> Result<()> {
244        let mut page_copy = page.clone();
245        let content = page_copy.generate_content()?;
246
247        // Create stream with compression if enabled
248        #[cfg(feature = "compression")]
249        {
250            use crate::objects::Stream;
251            let mut stream = Stream::new(content);
252            // Only compress if config allows it
253            if self.config.compress_streams {
254                stream.compress_flate()?;
255            }
256
257            self.write_object(
258                content_id,
259                Object::Stream(stream.dictionary().clone(), stream.data().to_vec()),
260            )?;
261        }
262
263        #[cfg(not(feature = "compression"))]
264        {
265            let mut stream_dict = Dictionary::new();
266            stream_dict.set("Length", Object::Integer(content.len() as i64));
267
268            self.write_object(content_id, Object::Stream(stream_dict, content))?;
269        }
270
271        Ok(())
272    }
273
274    fn write_outline_tree(
275        &mut self,
276        outline_tree: &crate::structure::OutlineTree,
277    ) -> Result<ObjectId> {
278        // Create root outline dictionary
279        let outline_root_id = self.allocate_object_id();
280
281        let mut outline_root = Dictionary::new();
282        outline_root.set("Type", Object::Name("Outlines".to_string()));
283
284        if !outline_tree.items.is_empty() {
285            // Reserve IDs for all outline items
286            let mut item_ids = Vec::new();
287
288            // Count all items and assign IDs
289            fn count_items(items: &[crate::structure::OutlineItem]) -> usize {
290                let mut count = items.len();
291                for item in items {
292                    count += count_items(&item.children);
293                }
294                count
295            }
296
297            let total_items = count_items(&outline_tree.items);
298
299            // Reserve IDs for all items
300            for _ in 0..total_items {
301                item_ids.push(self.allocate_object_id());
302            }
303
304            let mut id_index = 0;
305
306            // Write root items
307            let first_id = item_ids[0];
308            let last_id = item_ids[outline_tree.items.len() - 1];
309
310            outline_root.set("First", Object::Reference(first_id));
311            outline_root.set("Last", Object::Reference(last_id));
312
313            // Visible count
314            let visible_count = outline_tree.visible_count();
315            outline_root.set("Count", Object::Integer(visible_count));
316
317            // Write all items recursively
318            let mut written_items = Vec::new();
319
320            for (i, item) in outline_tree.items.iter().enumerate() {
321                let item_id = item_ids[id_index];
322                id_index += 1;
323
324                let prev_id = if i > 0 { Some(item_ids[i - 1]) } else { None };
325                let next_id = if i < outline_tree.items.len() - 1 {
326                    Some(item_ids[i + 1])
327                } else {
328                    None
329                };
330
331                // Write this item and its children
332                let children_ids = self.write_outline_item(
333                    item,
334                    item_id,
335                    outline_root_id,
336                    prev_id,
337                    next_id,
338                    &mut item_ids,
339                    &mut id_index,
340                )?;
341
342                written_items.extend(children_ids);
343            }
344        }
345
346        self.write_object(outline_root_id, Object::Dictionary(outline_root))?;
347        Ok(outline_root_id)
348    }
349
350    #[allow(clippy::too_many_arguments)]
351    fn write_outline_item(
352        &mut self,
353        item: &crate::structure::OutlineItem,
354        item_id: ObjectId,
355        parent_id: ObjectId,
356        prev_id: Option<ObjectId>,
357        next_id: Option<ObjectId>,
358        all_ids: &mut Vec<ObjectId>,
359        id_index: &mut usize,
360    ) -> Result<Vec<ObjectId>> {
361        let mut written_ids = vec![item_id];
362
363        // Handle children if any
364        let (first_child_id, last_child_id) = if !item.children.is_empty() {
365            let first_idx = *id_index;
366            let first_id = all_ids[first_idx];
367            let last_idx = first_idx + item.children.len() - 1;
368            let last_id = all_ids[last_idx];
369
370            // Write children
371            for (i, child) in item.children.iter().enumerate() {
372                let child_id = all_ids[*id_index];
373                *id_index += 1;
374
375                let child_prev = if i > 0 {
376                    Some(all_ids[first_idx + i - 1])
377                } else {
378                    None
379                };
380                let child_next = if i < item.children.len() - 1 {
381                    Some(all_ids[first_idx + i + 1])
382                } else {
383                    None
384                };
385
386                let child_ids = self.write_outline_item(
387                    child, child_id, item_id, // This item is the parent
388                    child_prev, child_next, all_ids, id_index,
389                )?;
390
391                written_ids.extend(child_ids);
392            }
393
394            (Some(first_id), Some(last_id))
395        } else {
396            (None, None)
397        };
398
399        // Create item dictionary
400        let item_dict = crate::structure::outline_item_to_dict(
401            item,
402            parent_id,
403            first_child_id,
404            last_child_id,
405            prev_id,
406            next_id,
407        );
408
409        self.write_object(item_id, Object::Dictionary(item_dict))?;
410
411        Ok(written_ids)
412    }
413
414    /// Writes the structure tree for Tagged PDF (ISO 32000-1 §14.8)
415    fn write_struct_tree(
416        &mut self,
417        struct_tree: &crate::structure::StructTree,
418    ) -> Result<ObjectId> {
419        // Allocate IDs for StructTreeRoot and all elements
420        let struct_tree_root_id = self.allocate_object_id();
421        let mut element_ids = Vec::new();
422        for _ in 0..struct_tree.len() {
423            element_ids.push(self.allocate_object_id());
424        }
425
426        // Build parent map: element_index -> parent_id
427        let mut parent_map: std::collections::HashMap<usize, ObjectId> =
428            std::collections::HashMap::new();
429
430        // Root element's parent is StructTreeRoot
431        if let Some(root_index) = struct_tree.root_index() {
432            parent_map.insert(root_index, struct_tree_root_id);
433
434            // Recursively map all children to their parents
435            fn map_children_parents(
436                tree: &crate::structure::StructTree,
437                parent_index: usize,
438                parent_id: ObjectId,
439                element_ids: &[ObjectId],
440                parent_map: &mut std::collections::HashMap<usize, ObjectId>,
441            ) {
442                if let Some(parent_elem) = tree.get(parent_index) {
443                    for &child_index in &parent_elem.children {
444                        parent_map.insert(child_index, parent_id);
445                        map_children_parents(
446                            tree,
447                            child_index,
448                            element_ids[child_index],
449                            element_ids,
450                            parent_map,
451                        );
452                    }
453                }
454            }
455
456            map_children_parents(
457                struct_tree,
458                root_index,
459                element_ids[root_index],
460                &element_ids,
461                &mut parent_map,
462            );
463        }
464
465        // Write all structure elements with parent references
466        for (index, element) in struct_tree.iter().enumerate() {
467            let element_id = element_ids[index];
468            let mut element_dict = Dictionary::new();
469
470            element_dict.set("Type", Object::Name("StructElem".to_string()));
471            element_dict.set("S", Object::Name(element.structure_type.as_pdf_name()));
472
473            // Parent reference (ISO 32000-1 §14.7.2 - required)
474            if let Some(&parent_id) = parent_map.get(&index) {
475                element_dict.set("P", Object::Reference(parent_id));
476            }
477
478            // Element ID (optional)
479            if let Some(ref id) = element.id {
480                element_dict.set("ID", Object::String(id.clone()));
481            }
482
483            // Attributes
484            if let Some(ref lang) = element.attributes.lang {
485                element_dict.set("Lang", Object::String(lang.clone()));
486            }
487            if let Some(ref alt) = element.attributes.alt {
488                element_dict.set("Alt", Object::String(alt.clone()));
489            }
490            if let Some(ref actual_text) = element.attributes.actual_text {
491                element_dict.set("ActualText", Object::String(actual_text.clone()));
492            }
493            if let Some(ref title) = element.attributes.title {
494                element_dict.set("T", Object::String(title.clone()));
495            }
496            if let Some(bbox) = element.attributes.bbox {
497                element_dict.set(
498                    "BBox",
499                    Object::Array(vec![
500                        Object::Real(bbox[0]),
501                        Object::Real(bbox[1]),
502                        Object::Real(bbox[2]),
503                        Object::Real(bbox[3]),
504                    ]),
505                );
506            }
507
508            // Kids (children elements + marked content references)
509            let mut kids = Vec::new();
510
511            // Add child element references
512            for &child_index in &element.children {
513                kids.push(Object::Reference(element_ids[child_index]));
514            }
515
516            // Add marked content references (MCIDs)
517            for mcid_ref in &element.mcids {
518                let mut mcr = Dictionary::new();
519                mcr.set("Type", Object::Name("MCR".to_string()));
520                mcr.set("Pg", Object::Integer(mcid_ref.page_index as i64));
521                mcr.set("MCID", Object::Integer(mcid_ref.mcid as i64));
522                kids.push(Object::Dictionary(mcr));
523            }
524
525            if !kids.is_empty() {
526                element_dict.set("K", Object::Array(kids));
527            }
528
529            self.write_object(element_id, Object::Dictionary(element_dict))?;
530        }
531
532        // Create StructTreeRoot dictionary
533        let mut struct_tree_root = Dictionary::new();
534        struct_tree_root.set("Type", Object::Name("StructTreeRoot".to_string()));
535
536        // Add root element(s) as K entry
537        if let Some(root_index) = struct_tree.root_index() {
538            struct_tree_root.set("K", Object::Reference(element_ids[root_index]));
539        }
540
541        // Add RoleMap if not empty
542        if !struct_tree.role_map.mappings().is_empty() {
543            let mut role_map = Dictionary::new();
544            for (custom_type, standard_type) in struct_tree.role_map.mappings() {
545                role_map.set(
546                    custom_type.as_str(),
547                    Object::Name(standard_type.as_pdf_name().to_string()),
548                );
549            }
550            struct_tree_root.set("RoleMap", Object::Dictionary(role_map));
551        }
552
553        self.write_object(struct_tree_root_id, Object::Dictionary(struct_tree_root))?;
554        Ok(struct_tree_root_id)
555    }
556
557    fn write_form_fields(&mut self, document: &mut Document) -> Result<()> {
558        // Add collected form field IDs to AcroForm
559        if !self.form_field_ids.is_empty() {
560            if let Some(acro_form) = &mut document.acro_form {
561                // Clear any existing fields and add the ones we found
562                acro_form.fields.clear();
563                for field_id in &self.form_field_ids {
564                    acro_form.add_field(*field_id);
565                }
566
567                // Ensure AcroForm has the right properties
568                acro_form.need_appearances = true;
569                if acro_form.da.is_none() {
570                    acro_form.da = Some("/Helv 12 Tf 0 g".to_string());
571                }
572            }
573        }
574        Ok(())
575    }
576
577    fn write_info(&mut self, document: &Document) -> Result<()> {
578        let info_id = self.info_id.expect("info_id must be set");
579        let mut info_dict = Dictionary::new();
580
581        if let Some(ref title) = document.metadata.title {
582            info_dict.set("Title", Object::String(title.clone()));
583        }
584        if let Some(ref author) = document.metadata.author {
585            info_dict.set("Author", Object::String(author.clone()));
586        }
587        if let Some(ref subject) = document.metadata.subject {
588            info_dict.set("Subject", Object::String(subject.clone()));
589        }
590        if let Some(ref keywords) = document.metadata.keywords {
591            info_dict.set("Keywords", Object::String(keywords.clone()));
592        }
593        if let Some(ref creator) = document.metadata.creator {
594            info_dict.set("Creator", Object::String(creator.clone()));
595        }
596        if let Some(ref producer) = document.metadata.producer {
597            info_dict.set("Producer", Object::String(producer.clone()));
598        }
599
600        // Add creation date
601        if let Some(creation_date) = document.metadata.creation_date {
602            let date_string = format_pdf_date(creation_date);
603            info_dict.set("CreationDate", Object::String(date_string));
604        }
605
606        // Add modification date
607        if let Some(mod_date) = document.metadata.modification_date {
608            let date_string = format_pdf_date(mod_date);
609            info_dict.set("ModDate", Object::String(date_string));
610        }
611
612        // Add PDF signature (anti-spoofing and licensing)
613        // This is written AFTER user-configurable metadata so it cannot be overridden
614        let edition = if cfg!(feature = "pro") {
615            super::Edition::Pro
616        } else if cfg!(feature = "enterprise") {
617            super::Edition::Enterprise
618        } else {
619            super::Edition::Community
620        };
621
622        let signature = super::PdfSignature::new(document, edition);
623        signature.write_to_info_dict(&mut info_dict);
624
625        self.write_object(info_id, Object::Dictionary(info_dict))?;
626        Ok(())
627    }
628
629    fn write_fonts(&mut self, document: &Document) -> Result<HashMap<String, ObjectId>> {
630        let mut font_refs = HashMap::new();
631
632        // Write custom fonts from the document
633        for font_name in document.custom_font_names() {
634            if let Some(font) = document.get_custom_font(&font_name) {
635                // For now, write all custom fonts as TrueType with Identity-H for Unicode support
636                // The font from document is Arc<fonts::Font>, not text::font_manager::CustomFont
637                let font_id = self.write_font_with_unicode_support(&font_name, &font)?;
638                font_refs.insert(font_name.clone(), font_id);
639            }
640        }
641
642        Ok(font_refs)
643    }
644
645    /// Write font with automatic Unicode support detection
646    fn write_font_with_unicode_support(
647        &mut self,
648        font_name: &str,
649        font: &crate::fonts::Font,
650    ) -> Result<ObjectId> {
651        // Check if any text in the document needs Unicode
652        // For simplicity, always use Type0 for full Unicode support
653        self.write_type0_font_from_font(font_name, font)
654    }
655
656    /// Write a Type0 font with CID support from fonts::Font
657    fn write_type0_font_from_font(
658        &mut self,
659        font_name: &str,
660        font: &crate::fonts::Font,
661    ) -> Result<ObjectId> {
662        // Get used characters from document for subsetting
663        let used_chars = self.document_used_chars.clone().unwrap_or_else(|| {
664            // If no tracking, include common characters as fallback
665            let mut chars = std::collections::HashSet::new();
666            for ch in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,!?".chars()
667            {
668                chars.insert(ch);
669            }
670            chars
671        });
672        // Allocate IDs for all font objects
673        let font_id = self.allocate_object_id();
674        let descendant_font_id = self.allocate_object_id();
675        let descriptor_id = self.allocate_object_id();
676        let font_file_id = self.allocate_object_id();
677        let to_unicode_id = self.allocate_object_id();
678
679        // Write font file (embedded TTF data with subsetting for large fonts)
680        // Keep track of the glyph mapping if we subset the font
681        // IMPORTANT: We need the ORIGINAL font for width calculations, not the subset
682        let (font_data_to_embed, subset_glyph_mapping, original_font_for_widths) =
683            if font.data.len() > 100_000 && !used_chars.is_empty() {
684                // Large font - try to subset it
685                match crate::text::fonts::truetype_subsetter::subset_font(
686                    font.data.clone(),
687                    &used_chars,
688                ) {
689                    Ok(subset_result) => {
690                        // Successfully subsetted - keep both font data and mapping
691                        // Also keep reference to original font for width calculations
692                        (
693                            subset_result.font_data,
694                            Some(subset_result.glyph_mapping),
695                            font.clone(),
696                        )
697                    }
698                    Err(_) => {
699                        // Subsetting failed, use original if under 25MB
700                        if font.data.len() < 25_000_000 {
701                            (font.data.clone(), None, font.clone())
702                        } else {
703                            // Too large even for fallback
704                            (Vec::new(), None, font.clone())
705                        }
706                    }
707                }
708            } else {
709                // Small font or no character tracking - use as-is
710                (font.data.clone(), None, font.clone())
711            };
712
713        if !font_data_to_embed.is_empty() {
714            let mut font_file_dict = Dictionary::new();
715            // Add appropriate properties based on font format
716            match font.format {
717                crate::fonts::FontFormat::OpenType => {
718                    // CFF/OpenType fonts use FontFile3 with OpenType subtype
719                    font_file_dict.set("Subtype", Object::Name("OpenType".to_string()));
720                    font_file_dict.set("Length1", Object::Integer(font_data_to_embed.len() as i64));
721                }
722                crate::fonts::FontFormat::TrueType => {
723                    // TrueType fonts use FontFile2 with Length1
724                    font_file_dict.set("Length1", Object::Integer(font_data_to_embed.len() as i64));
725                }
726            }
727            let font_stream_obj = Object::Stream(font_file_dict, font_data_to_embed);
728            self.write_object(font_file_id, font_stream_obj)?;
729        } else {
730            // No font data to embed
731            let font_file_dict = Dictionary::new();
732            let font_stream_obj = Object::Stream(font_file_dict, Vec::new());
733            self.write_object(font_file_id, font_stream_obj)?;
734        }
735
736        // Write font descriptor
737        let mut descriptor = Dictionary::new();
738        descriptor.set("Type", Object::Name("FontDescriptor".to_string()));
739        descriptor.set("FontName", Object::Name(font_name.to_string()));
740        descriptor.set("Flags", Object::Integer(4)); // Symbolic font
741        descriptor.set(
742            "FontBBox",
743            Object::Array(vec![
744                Object::Integer(font.descriptor.font_bbox[0] as i64),
745                Object::Integer(font.descriptor.font_bbox[1] as i64),
746                Object::Integer(font.descriptor.font_bbox[2] as i64),
747                Object::Integer(font.descriptor.font_bbox[3] as i64),
748            ]),
749        );
750        descriptor.set(
751            "ItalicAngle",
752            Object::Real(font.descriptor.italic_angle as f64),
753        );
754        descriptor.set("Ascent", Object::Real(font.descriptor.ascent as f64));
755        descriptor.set("Descent", Object::Real(font.descriptor.descent as f64));
756        descriptor.set("CapHeight", Object::Real(font.descriptor.cap_height as f64));
757        descriptor.set("StemV", Object::Real(font.descriptor.stem_v as f64));
758        // Use appropriate FontFile type based on font format
759        let font_file_key = match font.format {
760            crate::fonts::FontFormat::OpenType => "FontFile3", // CFF/OpenType fonts
761            crate::fonts::FontFormat::TrueType => "FontFile2", // TrueType fonts
762        };
763        descriptor.set(font_file_key, Object::Reference(font_file_id));
764        self.write_object(descriptor_id, Object::Dictionary(descriptor))?;
765
766        // Write CIDFont (descendant font)
767        let mut cid_font = Dictionary::new();
768        cid_font.set("Type", Object::Name("Font".to_string()));
769        // Use appropriate CIDFont subtype based on font format
770        let cid_font_subtype =
771            if CjkFontType::should_use_cidfonttype2_for_preview_compatibility(font_name) {
772                "CIDFontType2" // Force CIDFontType2 for CJK fonts to fix Preview.app rendering
773            } else {
774                match font.format {
775                    crate::fonts::FontFormat::OpenType => "CIDFontType0", // CFF/OpenType fonts
776                    crate::fonts::FontFormat::TrueType => "CIDFontType2", // TrueType fonts
777                }
778            };
779        cid_font.set("Subtype", Object::Name(cid_font_subtype.to_string()));
780        cid_font.set("BaseFont", Object::Name(font_name.to_string()));
781
782        // CIDSystemInfo - Use appropriate values for CJK fonts
783        let mut cid_system_info = Dictionary::new();
784        let (registry, ordering, supplement) =
785            if let Some(cjk_type) = CjkFontType::detect_from_name(font_name) {
786                cjk_type.cid_system_info()
787            } else {
788                ("Adobe", "Identity", 0)
789            };
790
791        cid_system_info.set("Registry", Object::String(registry.to_string()));
792        cid_system_info.set("Ordering", Object::String(ordering.to_string()));
793        cid_system_info.set("Supplement", Object::Integer(supplement as i64));
794        cid_font.set("CIDSystemInfo", Object::Dictionary(cid_system_info));
795
796        cid_font.set("FontDescriptor", Object::Reference(descriptor_id));
797
798        // Calculate a better default width based on font metrics
799        let default_width = self.calculate_default_width(font);
800        cid_font.set("DW", Object::Integer(default_width));
801
802        // Generate proper width array from font metrics
803        // IMPORTANT: Use the ORIGINAL font for width calculations, not the subset
804        // But pass the subset mapping to know which characters we're using
805        let w_array = self.generate_width_array(
806            &original_font_for_widths,
807            default_width,
808            subset_glyph_mapping.as_ref(),
809        );
810        cid_font.set("W", Object::Array(w_array));
811
812        // CIDToGIDMap - Generate proper mapping from CID (Unicode) to GlyphID
813        // This is critical for Type0 fonts to work correctly
814        // If we subsetted the font, use the new glyph mapping
815        let cid_to_gid_map = self.generate_cid_to_gid_map(font, subset_glyph_mapping.as_ref())?;
816        if !cid_to_gid_map.is_empty() {
817            // Write the CIDToGIDMap as a stream
818            let cid_to_gid_map_id = self.allocate_object_id();
819            let mut map_dict = Dictionary::new();
820            map_dict.set("Length", Object::Integer(cid_to_gid_map.len() as i64));
821            let map_stream = Object::Stream(map_dict, cid_to_gid_map);
822            self.write_object(cid_to_gid_map_id, map_stream)?;
823            cid_font.set("CIDToGIDMap", Object::Reference(cid_to_gid_map_id));
824        } else {
825            cid_font.set("CIDToGIDMap", Object::Name("Identity".to_string()));
826        }
827
828        self.write_object(descendant_font_id, Object::Dictionary(cid_font))?;
829
830        // Write ToUnicode CMap
831        let cmap_data = self.generate_tounicode_cmap_from_font(font);
832        let cmap_dict = Dictionary::new();
833        let cmap_stream = Object::Stream(cmap_dict, cmap_data);
834        self.write_object(to_unicode_id, cmap_stream)?;
835
836        // Write Type0 font (main font)
837        let mut type0_font = Dictionary::new();
838        type0_font.set("Type", Object::Name("Font".to_string()));
839        type0_font.set("Subtype", Object::Name("Type0".to_string()));
840        type0_font.set("BaseFont", Object::Name(font_name.to_string()));
841        type0_font.set("Encoding", Object::Name("Identity-H".to_string()));
842        type0_font.set(
843            "DescendantFonts",
844            Object::Array(vec![Object::Reference(descendant_font_id)]),
845        );
846        type0_font.set("ToUnicode", Object::Reference(to_unicode_id));
847
848        self.write_object(font_id, Object::Dictionary(type0_font))?;
849
850        Ok(font_id)
851    }
852
853    /// Calculate default width based on common characters
854    fn calculate_default_width(&self, font: &crate::fonts::Font) -> i64 {
855        use crate::text::fonts::truetype::TrueTypeFont;
856
857        // Try to calculate from actual font metrics
858        if let Ok(tt_font) = TrueTypeFont::parse(font.data.clone()) {
859            if let Ok(cmap_tables) = tt_font.parse_cmap() {
860                if let Some(cmap) = cmap_tables
861                    .iter()
862                    .find(|t| t.platform_id == 3 && t.encoding_id == 1)
863                    .or_else(|| cmap_tables.iter().find(|t| t.platform_id == 0))
864                {
865                    if let Ok(widths) = tt_font.get_glyph_widths(&cmap.mappings) {
866                        // NOTE: get_glyph_widths already returns widths in PDF units (1000 per em)
867
868                        // Calculate average width of common Latin characters
869                        let common_chars =
870                            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
871                        let mut total_width = 0;
872                        let mut count = 0;
873
874                        for ch in common_chars.chars() {
875                            let unicode = ch as u32;
876                            if let Some(&pdf_width) = widths.get(&unicode) {
877                                total_width += pdf_width as i64;
878                                count += 1;
879                            }
880                        }
881
882                        if count > 0 {
883                            return total_width / count;
884                        }
885                    }
886                }
887            }
888        }
889
890        // Fallback default if we can't calculate
891        500
892    }
893
894    /// Generate width array for CID font
895    fn generate_width_array(
896        &self,
897        font: &crate::fonts::Font,
898        _default_width: i64,
899        subset_mapping: Option<&HashMap<u32, u16>>,
900    ) -> Vec<Object> {
901        use crate::text::fonts::truetype::TrueTypeFont;
902
903        let mut w_array = Vec::new();
904
905        // Try to get actual glyph widths from the font
906        if let Ok(tt_font) = TrueTypeFont::parse(font.data.clone()) {
907            // IMPORTANT: Always use ORIGINAL mappings for width calculation
908            // The subset_mapping has NEW GlyphIDs which don't correspond to the right glyphs
909            // in the original font's width table
910            let char_to_glyph = {
911                // Parse cmap to get original mappings
912                if let Ok(cmap_tables) = tt_font.parse_cmap() {
913                    if let Some(cmap) = cmap_tables
914                        .iter()
915                        .find(|t| t.platform_id == 3 && t.encoding_id == 1)
916                        .or_else(|| cmap_tables.iter().find(|t| t.platform_id == 0))
917                    {
918                        // If we have subset_mapping, filter to only include used characters
919                        if let Some(subset_map) = subset_mapping {
920                            let mut filtered = HashMap::new();
921                            for unicode in subset_map.keys() {
922                                // Get the ORIGINAL GlyphID for this Unicode
923                                if let Some(&orig_glyph) = cmap.mappings.get(unicode) {
924                                    filtered.insert(*unicode, orig_glyph);
925                                }
926                            }
927                            filtered
928                        } else {
929                            cmap.mappings.clone()
930                        }
931                    } else {
932                        HashMap::new()
933                    }
934                } else {
935                    HashMap::new()
936                }
937            };
938
939            if !char_to_glyph.is_empty() {
940                // Get actual widths from the font
941                if let Ok(widths) = tt_font.get_glyph_widths(&char_to_glyph) {
942                    // NOTE: get_glyph_widths already returns widths scaled to PDF units (1000 per em)
943                    // So we DON'T need to scale them again here
944
945                    // Group consecutive characters with same width for efficiency
946                    let mut sorted_chars: Vec<_> = widths.iter().collect();
947                    sorted_chars.sort_by_key(|(unicode, _)| *unicode);
948
949                    let mut i = 0;
950                    while i < sorted_chars.len() {
951                        let start_unicode = *sorted_chars[i].0;
952                        // Width is already in PDF units from get_glyph_widths
953                        let pdf_width = *sorted_chars[i].1 as i64;
954
955                        // Find consecutive characters with same width
956                        let mut end_unicode = start_unicode;
957                        let mut j = i + 1;
958                        while j < sorted_chars.len() && *sorted_chars[j].0 == end_unicode + 1 {
959                            let next_pdf_width = *sorted_chars[j].1 as i64;
960                            if next_pdf_width == pdf_width {
961                                end_unicode = *sorted_chars[j].0;
962                                j += 1;
963                            } else {
964                                break;
965                            }
966                        }
967
968                        // Add to W array
969                        if start_unicode == end_unicode {
970                            // Single character
971                            w_array.push(Object::Integer(start_unicode as i64));
972                            w_array.push(Object::Array(vec![Object::Integer(pdf_width)]));
973                        } else {
974                            // Range of characters
975                            w_array.push(Object::Integer(start_unicode as i64));
976                            w_array.push(Object::Integer(end_unicode as i64));
977                            w_array.push(Object::Integer(pdf_width));
978                        }
979
980                        i = j;
981                    }
982
983                    return w_array;
984                }
985            }
986        }
987
988        // Fallback to reasonable default widths if we can't parse the font
989        let ranges = vec![
990            // Space character should be narrower
991            (0x20, 0x20, 250), // Space
992            (0x21, 0x2F, 333), // Punctuation
993            (0x30, 0x39, 500), // Numbers (0-9)
994            (0x3A, 0x40, 333), // More punctuation
995            (0x41, 0x5A, 667), // Uppercase letters (A-Z)
996            (0x5B, 0x60, 333), // Brackets
997            (0x61, 0x7A, 500), // Lowercase letters (a-z)
998            (0x7B, 0x7E, 333), // More brackets
999            // Extended Latin
1000            (0xA0, 0xA0, 250), // Non-breaking space
1001            (0xA1, 0xBF, 333), // Latin-1 punctuation
1002            (0xC0, 0xD6, 667), // Latin-1 uppercase
1003            (0xD7, 0xD7, 564), // Multiplication sign
1004            (0xD8, 0xDE, 667), // More Latin-1 uppercase
1005            (0xDF, 0xF6, 500), // Latin-1 lowercase
1006            (0xF7, 0xF7, 564), // Division sign
1007            (0xF8, 0xFF, 500), // More Latin-1 lowercase
1008            // Latin Extended-A
1009            (0x100, 0x17F, 500), // Latin Extended-A
1010            // Symbols and special characters
1011            (0x2000, 0x200F, 250), // Various spaces
1012            (0x2010, 0x2027, 333), // Hyphens and dashes
1013            (0x2028, 0x202F, 250), // More spaces
1014            (0x2030, 0x206F, 500), // General Punctuation
1015            (0x2070, 0x209F, 400), // Superscripts
1016            (0x20A0, 0x20CF, 600), // Currency symbols
1017            (0x2100, 0x214F, 700), // Letterlike symbols
1018            (0x2190, 0x21FF, 600), // Arrows
1019            (0x2200, 0x22FF, 600), // Mathematical operators
1020            (0x2300, 0x23FF, 600), // Miscellaneous technical
1021            (0x2500, 0x257F, 500), // Box drawing
1022            (0x2580, 0x259F, 500), // Block elements
1023            (0x25A0, 0x25FF, 600), // Geometric shapes
1024            (0x2600, 0x26FF, 600), // Miscellaneous symbols
1025            (0x2700, 0x27BF, 600), // Dingbats
1026        ];
1027
1028        // Convert ranges to W array format
1029        for (start, end, width) in ranges {
1030            if start == end {
1031                // Single character
1032                w_array.push(Object::Integer(start));
1033                w_array.push(Object::Array(vec![Object::Integer(width)]));
1034            } else {
1035                // Range of characters
1036                w_array.push(Object::Integer(start));
1037                w_array.push(Object::Integer(end));
1038                w_array.push(Object::Integer(width));
1039            }
1040        }
1041
1042        w_array
1043    }
1044
1045    /// Generate CIDToGIDMap for Type0 font
1046    fn generate_cid_to_gid_map(
1047        &mut self,
1048        font: &crate::fonts::Font,
1049        subset_mapping: Option<&HashMap<u32, u16>>,
1050    ) -> Result<Vec<u8>> {
1051        use crate::text::fonts::truetype::TrueTypeFont;
1052
1053        // If we have a subset mapping, use it directly
1054        // Otherwise, parse the font to get the original cmap table
1055        let cmap_mappings = if let Some(subset_map) = subset_mapping {
1056            // Use the subset mapping directly
1057            subset_map.clone()
1058        } else {
1059            // Parse the font to get the original cmap table
1060            let tt_font = TrueTypeFont::parse(font.data.clone())?;
1061            let cmap_tables = tt_font.parse_cmap()?;
1062
1063            // Find the best cmap table (Unicode)
1064            let cmap = cmap_tables
1065                .iter()
1066                .find(|t| t.platform_id == 3 && t.encoding_id == 1) // Windows Unicode
1067                .or_else(|| cmap_tables.iter().find(|t| t.platform_id == 0)) // Unicode
1068                .ok_or_else(|| {
1069                    crate::error::PdfError::FontError("No Unicode cmap table found".to_string())
1070                })?;
1071
1072            cmap.mappings.clone()
1073        };
1074
1075        // Build the CIDToGIDMap
1076        // Since we use Unicode code points as CIDs, we need to map Unicode → GlyphID
1077        // The map is a binary array where index = CID (Unicode) * 2, value = GlyphID (big-endian)
1078
1079        // OPTIMIZATION: Only create map for characters actually used in the document
1080        // Get used characters from document tracking
1081        let used_chars = self.document_used_chars.clone().unwrap_or_default();
1082
1083        // Find the maximum Unicode value from used characters or full font
1084        let max_unicode = if !used_chars.is_empty() {
1085            // If we have used chars tracking, only map up to the highest used character
1086            used_chars
1087                .iter()
1088                .map(|ch| *ch as u32)
1089                .max()
1090                .unwrap_or(0x00FF) // At least Basic Latin
1091                .min(0xFFFF) as usize
1092        } else {
1093            // Fallback to original behavior if no tracking
1094            cmap_mappings
1095                .keys()
1096                .max()
1097                .copied()
1098                .unwrap_or(0xFFFF)
1099                .min(0xFFFF) as usize
1100        };
1101
1102        // Create the map: 2 bytes per entry
1103        let mut map = vec![0u8; (max_unicode + 1) * 2];
1104
1105        // Fill in the mappings
1106        let mut sample_mappings = Vec::new();
1107        for (&unicode, &glyph_id) in &cmap_mappings {
1108            if unicode <= max_unicode as u32 {
1109                let idx = (unicode as usize) * 2;
1110                // Write glyph_id in big-endian format
1111                map[idx] = (glyph_id >> 8) as u8;
1112                map[idx + 1] = (glyph_id & 0xFF) as u8;
1113
1114                // Collect some sample mappings for debugging
1115                if unicode == 0x0041 || unicode == 0x0061 || unicode == 0x00E1 || unicode == 0x00F1
1116                {
1117                    sample_mappings.push((unicode, glyph_id));
1118                }
1119            }
1120        }
1121
1122        Ok(map)
1123    }
1124
1125    /// Generate ToUnicode CMap for Type0 font from fonts::Font
1126    fn generate_tounicode_cmap_from_font(&self, font: &crate::fonts::Font) -> Vec<u8> {
1127        use crate::text::fonts::truetype::TrueTypeFont;
1128
1129        let mut cmap = String::new();
1130
1131        // CMap header
1132        cmap.push_str("/CIDInit /ProcSet findresource begin\n");
1133        cmap.push_str("12 dict begin\n");
1134        cmap.push_str("begincmap\n");
1135        cmap.push_str("/CIDSystemInfo\n");
1136        cmap.push_str("<< /Registry (Adobe)\n");
1137        cmap.push_str("   /Ordering (UCS)\n");
1138        cmap.push_str("   /Supplement 0\n");
1139        cmap.push_str(">> def\n");
1140        cmap.push_str("/CMapName /Adobe-Identity-UCS def\n");
1141        cmap.push_str("/CMapType 2 def\n");
1142        cmap.push_str("1 begincodespacerange\n");
1143        cmap.push_str("<0000> <FFFF>\n");
1144        cmap.push_str("endcodespacerange\n");
1145
1146        // Try to get actual mappings from the font
1147        let mut mappings = Vec::new();
1148        let mut has_font_mappings = false;
1149
1150        if let Ok(tt_font) = TrueTypeFont::parse(font.data.clone()) {
1151            if let Ok(cmap_tables) = tt_font.parse_cmap() {
1152                // Find the best cmap table (Unicode)
1153                if let Some(cmap_table) = cmap_tables
1154                    .iter()
1155                    .find(|t| t.platform_id == 3 && t.encoding_id == 1) // Windows Unicode
1156                    .or_else(|| cmap_tables.iter().find(|t| t.platform_id == 0))
1157                // Unicode
1158                {
1159                    // For Identity-H encoding, we use Unicode code points as CIDs
1160                    // So the ToUnicode CMap should map CID (=Unicode) → Unicode
1161                    for (&unicode, &glyph_id) in &cmap_table.mappings {
1162                        if glyph_id > 0 && unicode <= 0xFFFF {
1163                            // Only non-.notdef glyphs
1164                            // Map CID (which is Unicode value) to Unicode
1165                            mappings.push((unicode, unicode));
1166                        }
1167                    }
1168                    has_font_mappings = true;
1169                }
1170            }
1171        }
1172
1173        // If we couldn't get font mappings, use identity mapping for common ranges
1174        if !has_font_mappings {
1175            // Basic Latin and Latin-1 Supplement (0x0020-0x00FF)
1176            for i in 0x0020..=0x00FF {
1177                mappings.push((i, i));
1178            }
1179
1180            // Latin Extended-A (0x0100-0x017F)
1181            for i in 0x0100..=0x017F {
1182                mappings.push((i, i));
1183            }
1184
1185            // CJK Unicode ranges - CRITICAL for CJK font support
1186            // Hiragana (Japanese)
1187            for i in 0x3040..=0x309F {
1188                mappings.push((i, i));
1189            }
1190
1191            // Katakana (Japanese)
1192            for i in 0x30A0..=0x30FF {
1193                mappings.push((i, i));
1194            }
1195
1196            // CJK Unified Ideographs (Chinese, Japanese, Korean)
1197            for i in 0x4E00..=0x9FFF {
1198                mappings.push((i, i));
1199            }
1200
1201            // Hangul Syllables (Korean)
1202            for i in 0xAC00..=0xD7AF {
1203                mappings.push((i, i));
1204            }
1205
1206            // Common symbols and punctuation
1207            for i in 0x2000..=0x206F {
1208                mappings.push((i, i));
1209            }
1210
1211            // Mathematical symbols
1212            for i in 0x2200..=0x22FF {
1213                mappings.push((i, i));
1214            }
1215
1216            // Arrows
1217            for i in 0x2190..=0x21FF {
1218                mappings.push((i, i));
1219            }
1220
1221            // Box drawing
1222            for i in 0x2500..=0x259F {
1223                mappings.push((i, i));
1224            }
1225
1226            // Geometric shapes
1227            for i in 0x25A0..=0x25FF {
1228                mappings.push((i, i));
1229            }
1230
1231            // Miscellaneous symbols
1232            for i in 0x2600..=0x26FF {
1233                mappings.push((i, i));
1234            }
1235        }
1236
1237        // Sort mappings by CID for better organization
1238        mappings.sort_by_key(|&(cid, _)| cid);
1239
1240        // Use more efficient bfrange where possible
1241        let mut i = 0;
1242        while i < mappings.len() {
1243            // Check if we can use a range
1244            let start_cid = mappings[i].0;
1245            let start_unicode = mappings[i].1;
1246            let mut end_idx = i;
1247
1248            // Find consecutive mappings
1249            while end_idx + 1 < mappings.len()
1250                && mappings[end_idx + 1].0 == mappings[end_idx].0 + 1
1251                && mappings[end_idx + 1].1 == mappings[end_idx].1 + 1
1252                && end_idx - i < 99
1253            // Max 100 per block
1254            {
1255                end_idx += 1;
1256            }
1257
1258            if end_idx > i {
1259                // Use bfrange for consecutive mappings
1260                cmap.push_str("1 beginbfrange\n");
1261                cmap.push_str(&format!(
1262                    "<{:04X}> <{:04X}> <{:04X}>\n",
1263                    start_cid, mappings[end_idx].0, start_unicode
1264                ));
1265                cmap.push_str("endbfrange\n");
1266                i = end_idx + 1;
1267            } else {
1268                // Use bfchar for individual mappings
1269                let mut chars = Vec::new();
1270                let chunk_end = (i + 100).min(mappings.len());
1271
1272                for item in &mappings[i..chunk_end] {
1273                    chars.push(*item);
1274                }
1275
1276                if !chars.is_empty() {
1277                    cmap.push_str(&format!("{} beginbfchar\n", chars.len()));
1278                    for (cid, unicode) in chars {
1279                        cmap.push_str(&format!("<{:04X}> <{:04X}>\n", cid, unicode));
1280                    }
1281                    cmap.push_str("endbfchar\n");
1282                }
1283
1284                i = chunk_end;
1285            }
1286        }
1287
1288        // CMap footer
1289        cmap.push_str("endcmap\n");
1290        cmap.push_str("CMapName currentdict /CMap defineresource pop\n");
1291        cmap.push_str("end\n");
1292        cmap.push_str("end\n");
1293
1294        cmap.into_bytes()
1295    }
1296
1297    /// Write a regular TrueType font
1298    #[allow(dead_code)]
1299    fn write_truetype_font(
1300        &mut self,
1301        font_name: &str,
1302        font: &crate::text::font_manager::CustomFont,
1303    ) -> Result<ObjectId> {
1304        // Allocate IDs for font objects
1305        let font_id = self.allocate_object_id();
1306        let descriptor_id = self.allocate_object_id();
1307        let font_file_id = self.allocate_object_id();
1308
1309        // Write font file (embedded TTF data)
1310        if let Some(ref data) = font.font_data {
1311            let mut font_file_dict = Dictionary::new();
1312            font_file_dict.set("Length1", Object::Integer(data.len() as i64));
1313            let font_stream_obj = Object::Stream(font_file_dict, data.clone());
1314            self.write_object(font_file_id, font_stream_obj)?;
1315        }
1316
1317        // Write font descriptor
1318        let mut descriptor = Dictionary::new();
1319        descriptor.set("Type", Object::Name("FontDescriptor".to_string()));
1320        descriptor.set("FontName", Object::Name(font_name.to_string()));
1321        descriptor.set("Flags", Object::Integer(32)); // Non-symbolic font
1322        descriptor.set(
1323            "FontBBox",
1324            Object::Array(vec![
1325                Object::Integer(-1000),
1326                Object::Integer(-1000),
1327                Object::Integer(2000),
1328                Object::Integer(2000),
1329            ]),
1330        );
1331        descriptor.set("ItalicAngle", Object::Integer(0));
1332        descriptor.set("Ascent", Object::Integer(font.descriptor.ascent as i64));
1333        descriptor.set("Descent", Object::Integer(font.descriptor.descent as i64));
1334        descriptor.set(
1335            "CapHeight",
1336            Object::Integer(font.descriptor.cap_height as i64),
1337        );
1338        descriptor.set("StemV", Object::Integer(font.descriptor.stem_v as i64));
1339        descriptor.set("FontFile2", Object::Reference(font_file_id));
1340        self.write_object(descriptor_id, Object::Dictionary(descriptor))?;
1341
1342        // Write font dictionary
1343        let mut font_dict = Dictionary::new();
1344        font_dict.set("Type", Object::Name("Font".to_string()));
1345        font_dict.set("Subtype", Object::Name("TrueType".to_string()));
1346        font_dict.set("BaseFont", Object::Name(font_name.to_string()));
1347        font_dict.set("FirstChar", Object::Integer(0));
1348        font_dict.set("LastChar", Object::Integer(255));
1349
1350        // Create widths array (simplified - all 600)
1351        let widths: Vec<Object> = (0..256).map(|_| Object::Integer(600)).collect();
1352        font_dict.set("Widths", Object::Array(widths));
1353        font_dict.set("FontDescriptor", Object::Reference(descriptor_id));
1354
1355        // Use WinAnsiEncoding for regular TrueType
1356        font_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1357
1358        self.write_object(font_id, Object::Dictionary(font_dict))?;
1359
1360        Ok(font_id)
1361    }
1362
1363    fn write_pages(
1364        &mut self,
1365        document: &Document,
1366        font_refs: &HashMap<String, ObjectId>,
1367    ) -> Result<()> {
1368        let pages_id = self.pages_id.expect("pages_id must be set");
1369        let mut pages_dict = Dictionary::new();
1370        pages_dict.set("Type", Object::Name("Pages".to_string()));
1371        pages_dict.set("Count", Object::Integer(document.pages.len() as i64));
1372
1373        let mut kids = Vec::new();
1374
1375        // Allocate page object IDs sequentially
1376        let mut page_ids = Vec::new();
1377        let mut content_ids = Vec::new();
1378        for _ in 0..document.pages.len() {
1379            page_ids.push(self.allocate_object_id());
1380            content_ids.push(self.allocate_object_id());
1381        }
1382
1383        for page_id in &page_ids {
1384            kids.push(Object::Reference(*page_id));
1385        }
1386
1387        pages_dict.set("Kids", Object::Array(kids));
1388
1389        self.write_object(pages_id, Object::Dictionary(pages_dict))?;
1390
1391        // Store page IDs for form field references
1392        self.page_ids = page_ids.clone();
1393
1394        // Write individual pages with font references
1395        for (i, page) in document.pages.iter().enumerate() {
1396            let page_id = page_ids[i];
1397            let content_id = content_ids[i];
1398
1399            self.write_page_with_fonts(page_id, pages_id, content_id, page, document, font_refs)?;
1400            self.write_page_content(content_id, page)?;
1401        }
1402
1403        Ok(())
1404    }
1405
1406    /// Compatibility alias for `write_pages` to maintain backwards compatibility
1407    #[allow(dead_code)]
1408    fn write_pages_with_fonts(
1409        &mut self,
1410        document: &Document,
1411        font_refs: &HashMap<String, ObjectId>,
1412    ) -> Result<()> {
1413        self.write_pages(document, font_refs)
1414    }
1415
1416    fn write_page_with_fonts(
1417        &mut self,
1418        page_id: ObjectId,
1419        parent_id: ObjectId,
1420        content_id: ObjectId,
1421        page: &crate::page::Page,
1422        _document: &Document,
1423        font_refs: &HashMap<String, ObjectId>,
1424    ) -> Result<()> {
1425        // Start with the page's dictionary which includes annotations
1426        let mut page_dict = page.to_dict();
1427
1428        page_dict.set("Type", Object::Name("Page".to_string()));
1429        page_dict.set("Parent", Object::Reference(parent_id));
1430        page_dict.set("Contents", Object::Reference(content_id));
1431
1432        // Get resources dictionary or create new one
1433        let mut resources = if let Some(Object::Dictionary(res)) = page_dict.get("Resources") {
1434            res.clone()
1435        } else {
1436            Dictionary::new()
1437        };
1438
1439        // Add font resources
1440        let mut font_dict = Dictionary::new();
1441
1442        // Add ALL standard PDF fonts (Type1) with WinAnsiEncoding
1443        // This fixes the text rendering issue in dashboards where HelveticaBold was missing
1444
1445        // Helvetica family
1446        let mut helvetica_dict = Dictionary::new();
1447        helvetica_dict.set("Type", Object::Name("Font".to_string()));
1448        helvetica_dict.set("Subtype", Object::Name("Type1".to_string()));
1449        helvetica_dict.set("BaseFont", Object::Name("Helvetica".to_string()));
1450        helvetica_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1451        font_dict.set("Helvetica", Object::Dictionary(helvetica_dict));
1452
1453        let mut helvetica_bold_dict = Dictionary::new();
1454        helvetica_bold_dict.set("Type", Object::Name("Font".to_string()));
1455        helvetica_bold_dict.set("Subtype", Object::Name("Type1".to_string()));
1456        helvetica_bold_dict.set("BaseFont", Object::Name("Helvetica-Bold".to_string()));
1457        helvetica_bold_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1458        font_dict.set("Helvetica-Bold", Object::Dictionary(helvetica_bold_dict));
1459
1460        let mut helvetica_oblique_dict = Dictionary::new();
1461        helvetica_oblique_dict.set("Type", Object::Name("Font".to_string()));
1462        helvetica_oblique_dict.set("Subtype", Object::Name("Type1".to_string()));
1463        helvetica_oblique_dict.set("BaseFont", Object::Name("Helvetica-Oblique".to_string()));
1464        helvetica_oblique_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1465        font_dict.set(
1466            "Helvetica-Oblique",
1467            Object::Dictionary(helvetica_oblique_dict),
1468        );
1469
1470        let mut helvetica_bold_oblique_dict = Dictionary::new();
1471        helvetica_bold_oblique_dict.set("Type", Object::Name("Font".to_string()));
1472        helvetica_bold_oblique_dict.set("Subtype", Object::Name("Type1".to_string()));
1473        helvetica_bold_oblique_dict.set(
1474            "BaseFont",
1475            Object::Name("Helvetica-BoldOblique".to_string()),
1476        );
1477        helvetica_bold_oblique_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1478        font_dict.set(
1479            "Helvetica-BoldOblique",
1480            Object::Dictionary(helvetica_bold_oblique_dict),
1481        );
1482
1483        // Times family
1484        let mut times_dict = Dictionary::new();
1485        times_dict.set("Type", Object::Name("Font".to_string()));
1486        times_dict.set("Subtype", Object::Name("Type1".to_string()));
1487        times_dict.set("BaseFont", Object::Name("Times-Roman".to_string()));
1488        times_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1489        font_dict.set("Times-Roman", Object::Dictionary(times_dict));
1490
1491        let mut times_bold_dict = Dictionary::new();
1492        times_bold_dict.set("Type", Object::Name("Font".to_string()));
1493        times_bold_dict.set("Subtype", Object::Name("Type1".to_string()));
1494        times_bold_dict.set("BaseFont", Object::Name("Times-Bold".to_string()));
1495        times_bold_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1496        font_dict.set("Times-Bold", Object::Dictionary(times_bold_dict));
1497
1498        let mut times_italic_dict = Dictionary::new();
1499        times_italic_dict.set("Type", Object::Name("Font".to_string()));
1500        times_italic_dict.set("Subtype", Object::Name("Type1".to_string()));
1501        times_italic_dict.set("BaseFont", Object::Name("Times-Italic".to_string()));
1502        times_italic_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1503        font_dict.set("Times-Italic", Object::Dictionary(times_italic_dict));
1504
1505        let mut times_bold_italic_dict = Dictionary::new();
1506        times_bold_italic_dict.set("Type", Object::Name("Font".to_string()));
1507        times_bold_italic_dict.set("Subtype", Object::Name("Type1".to_string()));
1508        times_bold_italic_dict.set("BaseFont", Object::Name("Times-BoldItalic".to_string()));
1509        times_bold_italic_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1510        font_dict.set(
1511            "Times-BoldItalic",
1512            Object::Dictionary(times_bold_italic_dict),
1513        );
1514
1515        // Courier family
1516        let mut courier_dict = Dictionary::new();
1517        courier_dict.set("Type", Object::Name("Font".to_string()));
1518        courier_dict.set("Subtype", Object::Name("Type1".to_string()));
1519        courier_dict.set("BaseFont", Object::Name("Courier".to_string()));
1520        courier_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1521        font_dict.set("Courier", Object::Dictionary(courier_dict));
1522
1523        let mut courier_bold_dict = Dictionary::new();
1524        courier_bold_dict.set("Type", Object::Name("Font".to_string()));
1525        courier_bold_dict.set("Subtype", Object::Name("Type1".to_string()));
1526        courier_bold_dict.set("BaseFont", Object::Name("Courier-Bold".to_string()));
1527        courier_bold_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1528        font_dict.set("Courier-Bold", Object::Dictionary(courier_bold_dict));
1529
1530        let mut courier_oblique_dict = Dictionary::new();
1531        courier_oblique_dict.set("Type", Object::Name("Font".to_string()));
1532        courier_oblique_dict.set("Subtype", Object::Name("Type1".to_string()));
1533        courier_oblique_dict.set("BaseFont", Object::Name("Courier-Oblique".to_string()));
1534        courier_oblique_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1535        font_dict.set("Courier-Oblique", Object::Dictionary(courier_oblique_dict));
1536
1537        let mut courier_bold_oblique_dict = Dictionary::new();
1538        courier_bold_oblique_dict.set("Type", Object::Name("Font".to_string()));
1539        courier_bold_oblique_dict.set("Subtype", Object::Name("Type1".to_string()));
1540        courier_bold_oblique_dict.set("BaseFont", Object::Name("Courier-BoldOblique".to_string()));
1541        courier_bold_oblique_dict.set("Encoding", Object::Name("WinAnsiEncoding".to_string()));
1542        font_dict.set(
1543            "Courier-BoldOblique",
1544            Object::Dictionary(courier_bold_oblique_dict),
1545        );
1546
1547        // Add custom fonts (Type0 fonts for Unicode support)
1548        for (font_name, font_id) in font_refs {
1549            font_dict.set(font_name, Object::Reference(*font_id));
1550        }
1551
1552        resources.set("Font", Object::Dictionary(font_dict));
1553
1554        // Add images as XObjects
1555        if !page.images().is_empty() {
1556            let mut xobject_dict = Dictionary::new();
1557
1558            for (name, image) in page.images() {
1559                // Use sequential ObjectId allocation to avoid conflicts
1560                let image_id = self.allocate_object_id();
1561
1562                // Check if image has transparency (alpha channel)
1563                if image.has_transparency() {
1564                    // Handle transparent images with SMask
1565                    let (mut main_obj, smask_obj) = image.to_pdf_object_with_transparency();
1566
1567                    // If we have a soft mask, write it as a separate object and reference it
1568                    if let Some(smask_stream) = smask_obj {
1569                        let smask_id = self.allocate_object_id();
1570                        self.write_object(smask_id, smask_stream)?;
1571
1572                        // Add SMask reference to the main image dictionary
1573                        if let Object::Stream(ref mut dict, _) = main_obj {
1574                            dict.set("SMask", Object::Reference(smask_id));
1575                        }
1576                    }
1577
1578                    // Write the main image XObject (now with SMask reference if applicable)
1579                    self.write_object(image_id, main_obj)?;
1580                } else {
1581                    // Write the image XObject without transparency
1582                    self.write_object(image_id, image.to_pdf_object())?;
1583                }
1584
1585                // Add reference to XObject dictionary
1586                xobject_dict.set(name, Object::Reference(image_id));
1587            }
1588
1589            resources.set("XObject", Object::Dictionary(xobject_dict));
1590        }
1591
1592        // Add ExtGState resources for transparency
1593        if let Some(extgstate_states) = page.get_extgstate_resources() {
1594            let mut extgstate_dict = Dictionary::new();
1595            for (name, state) in extgstate_states {
1596                let mut state_dict = Dictionary::new();
1597                state_dict.set("Type", Object::Name("ExtGState".to_string()));
1598
1599                // Add transparency parameters
1600                if let Some(alpha_stroke) = state.alpha_stroke {
1601                    state_dict.set("CA", Object::Real(alpha_stroke));
1602                }
1603                if let Some(alpha_fill) = state.alpha_fill {
1604                    state_dict.set("ca", Object::Real(alpha_fill));
1605                }
1606
1607                // Add other parameters as needed
1608                if let Some(line_width) = state.line_width {
1609                    state_dict.set("LW", Object::Real(line_width));
1610                }
1611                if let Some(line_cap) = state.line_cap {
1612                    state_dict.set("LC", Object::Integer(line_cap as i64));
1613                }
1614                if let Some(line_join) = state.line_join {
1615                    state_dict.set("LJ", Object::Integer(line_join as i64));
1616                }
1617                if let Some(dash_pattern) = &state.dash_pattern {
1618                    let dash_objects: Vec<Object> = dash_pattern
1619                        .array
1620                        .iter()
1621                        .map(|&d| Object::Real(d))
1622                        .collect();
1623                    state_dict.set(
1624                        "D",
1625                        Object::Array(vec![
1626                            Object::Array(dash_objects),
1627                            Object::Real(dash_pattern.phase),
1628                        ]),
1629                    );
1630                }
1631
1632                extgstate_dict.set(name, Object::Dictionary(state_dict));
1633            }
1634            if !extgstate_dict.is_empty() {
1635                resources.set("ExtGState", Object::Dictionary(extgstate_dict));
1636            }
1637        }
1638
1639        page_dict.set("Resources", Object::Dictionary(resources));
1640
1641        // Handle form widget annotations
1642        if let Some(Object::Array(annots)) = page_dict.get("Annots") {
1643            let mut new_annots = Vec::new();
1644
1645            for annot in annots {
1646                if let Object::Dictionary(ref annot_dict) = annot {
1647                    if let Some(Object::Name(subtype)) = annot_dict.get("Subtype") {
1648                        if subtype == "Widget" {
1649                            // Process widget annotation
1650                            let widget_id = self.allocate_object_id();
1651                            self.write_object(widget_id, annot.clone())?;
1652                            new_annots.push(Object::Reference(widget_id));
1653
1654                            // Track widget for form fields
1655                            if let Some(Object::Name(_ft)) = annot_dict.get("FT") {
1656                                if let Some(Object::String(field_name)) = annot_dict.get("T") {
1657                                    self.field_widget_map
1658                                        .entry(field_name.clone())
1659                                        .or_default()
1660                                        .push(widget_id);
1661                                    self.field_id_map.insert(field_name.clone(), widget_id);
1662                                    self.form_field_ids.push(widget_id);
1663                                }
1664                            }
1665                            continue;
1666                        }
1667                    }
1668                }
1669                new_annots.push(annot.clone());
1670            }
1671
1672            if !new_annots.is_empty() {
1673                page_dict.set("Annots", Object::Array(new_annots));
1674            }
1675        }
1676
1677        self.write_object(page_id, Object::Dictionary(page_dict))?;
1678        Ok(())
1679    }
1680}
1681
1682impl PdfWriter<BufWriter<std::fs::File>> {
1683    pub fn new(path: impl AsRef<Path>) -> Result<Self> {
1684        let file = std::fs::File::create(path)?;
1685        let writer = BufWriter::new(file);
1686
1687        Ok(Self {
1688            writer,
1689            xref_positions: HashMap::new(),
1690            current_position: 0,
1691            next_object_id: 1,
1692            catalog_id: None,
1693            pages_id: None,
1694            info_id: None,
1695            field_widget_map: HashMap::new(),
1696            field_id_map: HashMap::new(),
1697            form_field_ids: Vec::new(),
1698            page_ids: Vec::new(),
1699            config: WriterConfig::default(),
1700            document_used_chars: None,
1701            buffered_objects: HashMap::new(),
1702            compressed_object_map: HashMap::new(),
1703        })
1704    }
1705}
1706
1707impl<W: Write> PdfWriter<W> {
1708    fn allocate_object_id(&mut self) -> ObjectId {
1709        let id = ObjectId::new(self.next_object_id, 0);
1710        self.next_object_id += 1;
1711        id
1712    }
1713
1714    fn write_object(&mut self, id: ObjectId, object: Object) -> Result<()> {
1715        use crate::writer::ObjectStreamWriter;
1716
1717        // If object streams enabled and object is compressible, buffer it
1718        if self.config.use_object_streams && ObjectStreamWriter::can_compress(&object) {
1719            let mut buffer = Vec::new();
1720            self.write_object_value_to_buffer(&object, &mut buffer)?;
1721            self.buffered_objects.insert(id, buffer);
1722            return Ok(());
1723        }
1724
1725        // Otherwise write immediately (streams, encryption dicts, etc.)
1726        self.xref_positions.insert(id, self.current_position);
1727
1728        // Pre-format header to count exact bytes once
1729        let header = format!("{} {} obj\n", id.number(), id.generation());
1730        self.write_bytes(header.as_bytes())?;
1731
1732        self.write_object_value(&object)?;
1733
1734        self.write_bytes(b"\nendobj\n")?;
1735        Ok(())
1736    }
1737
1738    fn write_object_value(&mut self, object: &Object) -> Result<()> {
1739        match object {
1740            Object::Null => self.write_bytes(b"null")?,
1741            Object::Boolean(b) => self.write_bytes(if *b { b"true" } else { b"false" })?,
1742            Object::Integer(i) => self.write_bytes(i.to_string().as_bytes())?,
1743            Object::Real(f) => self.write_bytes(
1744                format!("{f:.6}")
1745                    .trim_end_matches('0')
1746                    .trim_end_matches('.')
1747                    .as_bytes(),
1748            )?,
1749            Object::String(s) => {
1750                self.write_bytes(b"(")?;
1751                self.write_bytes(s.as_bytes())?;
1752                self.write_bytes(b")")?;
1753            }
1754            Object::Name(n) => {
1755                self.write_bytes(b"/")?;
1756                self.write_bytes(n.as_bytes())?;
1757            }
1758            Object::Array(arr) => {
1759                self.write_bytes(b"[")?;
1760                for (i, obj) in arr.iter().enumerate() {
1761                    if i > 0 {
1762                        self.write_bytes(b" ")?;
1763                    }
1764                    self.write_object_value(obj)?;
1765                }
1766                self.write_bytes(b"]")?;
1767            }
1768            Object::Dictionary(dict) => {
1769                self.write_bytes(b"<<")?;
1770                for (key, value) in dict.entries() {
1771                    self.write_bytes(b"\n/")?;
1772                    self.write_bytes(key.as_bytes())?;
1773                    self.write_bytes(b" ")?;
1774                    self.write_object_value(value)?;
1775                }
1776                self.write_bytes(b"\n>>")?;
1777            }
1778            Object::Stream(dict, data) => {
1779                self.write_object_value(&Object::Dictionary(dict.clone()))?;
1780                self.write_bytes(b"\nstream\n")?;
1781                self.write_bytes(data)?;
1782                self.write_bytes(b"\nendstream")?;
1783            }
1784            Object::Reference(id) => {
1785                let ref_str = format!("{} {} R", id.number(), id.generation());
1786                self.write_bytes(ref_str.as_bytes())?;
1787            }
1788        }
1789        Ok(())
1790    }
1791
1792    /// Write object value to a buffer (for object streams)
1793    fn write_object_value_to_buffer(&self, object: &Object, buffer: &mut Vec<u8>) -> Result<()> {
1794        match object {
1795            Object::Null => buffer.extend_from_slice(b"null"),
1796            Object::Boolean(b) => buffer.extend_from_slice(if *b { b"true" } else { b"false" }),
1797            Object::Integer(i) => buffer.extend_from_slice(i.to_string().as_bytes()),
1798            Object::Real(f) => buffer.extend_from_slice(
1799                format!("{f:.6}")
1800                    .trim_end_matches('0')
1801                    .trim_end_matches('.')
1802                    .as_bytes(),
1803            ),
1804            Object::String(s) => {
1805                buffer.push(b'(');
1806                buffer.extend_from_slice(s.as_bytes());
1807                buffer.push(b')');
1808            }
1809            Object::Name(n) => {
1810                buffer.push(b'/');
1811                buffer.extend_from_slice(n.as_bytes());
1812            }
1813            Object::Array(arr) => {
1814                buffer.push(b'[');
1815                for (i, obj) in arr.iter().enumerate() {
1816                    if i > 0 {
1817                        buffer.push(b' ');
1818                    }
1819                    self.write_object_value_to_buffer(obj, buffer)?;
1820                }
1821                buffer.push(b']');
1822            }
1823            Object::Dictionary(dict) => {
1824                buffer.extend_from_slice(b"<<");
1825                for (key, value) in dict.entries() {
1826                    buffer.extend_from_slice(b"\n/");
1827                    buffer.extend_from_slice(key.as_bytes());
1828                    buffer.push(b' ');
1829                    self.write_object_value_to_buffer(value, buffer)?;
1830                }
1831                buffer.extend_from_slice(b"\n>>");
1832            }
1833            Object::Stream(_, _) => {
1834                // Streams should never be compressed in object streams
1835                return Err(crate::error::PdfError::ObjectStreamError(
1836                    "Cannot compress stream objects in object streams".to_string(),
1837                ));
1838            }
1839            Object::Reference(id) => {
1840                let ref_str = format!("{} {} R", id.number(), id.generation());
1841                buffer.extend_from_slice(ref_str.as_bytes());
1842            }
1843        }
1844        Ok(())
1845    }
1846
1847    /// Flush buffered objects as compressed object streams
1848    fn flush_object_streams(&mut self) -> Result<()> {
1849        if self.buffered_objects.is_empty() {
1850            return Ok(());
1851        }
1852
1853        // Create object stream writer
1854        let config = ObjectStreamConfig {
1855            max_objects_per_stream: 100,
1856            compression_level: 6,
1857            enabled: true,
1858        };
1859        let mut os_writer = ObjectStreamWriter::new(config);
1860
1861        // Sort buffered objects by ID for deterministic output
1862        let mut buffered: Vec<_> = self.buffered_objects.iter().collect();
1863        buffered.sort_by_key(|(id, _)| id.number());
1864
1865        // Add all buffered objects to the stream writer
1866        for (id, data) in buffered {
1867            os_writer.add_object(*id, data.clone())?;
1868        }
1869
1870        // Finalize and get completed streams
1871        let streams = os_writer.finalize()?;
1872
1873        // Write each object stream to the PDF
1874        for mut stream in streams {
1875            let stream_id = stream.stream_id;
1876
1877            // Generate compressed stream data
1878            let compressed_data = stream.generate_stream_data(6)?;
1879
1880            // Generate stream dictionary
1881            let dict = stream.generate_dictionary(&compressed_data);
1882
1883            // Track compressed object mapping for xref
1884            for (index, (obj_id, _)) in stream.objects.iter().enumerate() {
1885                self.compressed_object_map
1886                    .insert(*obj_id, (stream_id, index as u32));
1887            }
1888
1889            // Write the object stream itself
1890            self.xref_positions.insert(stream_id, self.current_position);
1891
1892            let header = format!("{} {} obj\n", stream_id.number(), stream_id.generation());
1893            self.write_bytes(header.as_bytes())?;
1894
1895            self.write_object_value(&Object::Dictionary(dict))?;
1896
1897            self.write_bytes(b"\nstream\n")?;
1898            self.write_bytes(&compressed_data)?;
1899            self.write_bytes(b"\nendstream\nendobj\n")?;
1900        }
1901
1902        Ok(())
1903    }
1904
1905    fn write_xref(&mut self) -> Result<()> {
1906        self.write_bytes(b"xref\n")?;
1907
1908        // Sort by object number and write entries
1909        let mut entries: Vec<_> = self
1910            .xref_positions
1911            .iter()
1912            .map(|(id, pos)| (*id, *pos))
1913            .collect();
1914        entries.sort_by_key(|(id, _)| id.number());
1915
1916        // Find the highest object number to determine size
1917        let max_obj_num = entries.iter().map(|(id, _)| id.number()).max().unwrap_or(0);
1918
1919        // Write subsection header - PDF 1.7 spec allows multiple subsections
1920        // For simplicity, write one subsection from 0 to max
1921        self.write_bytes(b"0 ")?;
1922        self.write_bytes((max_obj_num + 1).to_string().as_bytes())?;
1923        self.write_bytes(b"\n")?;
1924
1925        // Write free object entry
1926        self.write_bytes(b"0000000000 65535 f \n")?;
1927
1928        // Write entries for all object numbers from 1 to max
1929        // Fill in gaps with free entries
1930        for obj_num in 1..=max_obj_num {
1931            let _obj_id = ObjectId::new(obj_num, 0);
1932            if let Some((_, position)) = entries.iter().find(|(id, _)| id.number() == obj_num) {
1933                let entry = format!("{:010} {:05} n \n", position, 0);
1934                self.write_bytes(entry.as_bytes())?;
1935            } else {
1936                // Free entry for gap
1937                self.write_bytes(b"0000000000 00000 f \n")?;
1938            }
1939        }
1940
1941        Ok(())
1942    }
1943
1944    fn write_xref_stream(&mut self) -> Result<()> {
1945        let catalog_id = self.catalog_id.expect("catalog_id must be set");
1946        let info_id = self.info_id.expect("info_id must be set");
1947
1948        // Allocate object ID for the xref stream
1949        let xref_stream_id = self.allocate_object_id();
1950        let xref_position = self.current_position;
1951
1952        // Create XRef stream writer with trailer information
1953        let mut xref_writer = XRefStreamWriter::new(xref_stream_id);
1954        xref_writer.set_trailer_info(catalog_id, info_id);
1955
1956        // Add free entry for object 0
1957        xref_writer.add_free_entry(0, 65535);
1958
1959        // Sort entries by object number
1960        let mut entries: Vec<_> = self
1961            .xref_positions
1962            .iter()
1963            .map(|(id, pos)| (*id, *pos))
1964            .collect();
1965        entries.sort_by_key(|(id, _)| id.number());
1966
1967        // Find the highest object number (including the xref stream itself)
1968        let max_obj_num = entries
1969            .iter()
1970            .map(|(id, _)| id.number())
1971            .max()
1972            .unwrap_or(0)
1973            .max(xref_stream_id.number());
1974
1975        // Add entries for all objects (including compressed objects)
1976        for obj_num in 1..=max_obj_num {
1977            let obj_id = ObjectId::new(obj_num, 0);
1978
1979            if obj_num == xref_stream_id.number() {
1980                // The xref stream entry will be added with the correct position
1981                xref_writer.add_in_use_entry(xref_position, 0);
1982            } else if let Some((stream_id, index)) = self.compressed_object_map.get(&obj_id) {
1983                // Type 2: Object is compressed in an object stream
1984                xref_writer.add_compressed_entry(stream_id.number(), *index);
1985            } else if let Some((id, position)) =
1986                entries.iter().find(|(id, _)| id.number() == obj_num)
1987            {
1988                // Type 1: Regular in-use entry
1989                xref_writer.add_in_use_entry(*position, id.generation());
1990            } else {
1991                // Type 0: Free entry for gap
1992                xref_writer.add_free_entry(0, 0);
1993            }
1994        }
1995
1996        // Mark position for xref stream object
1997        self.xref_positions.insert(xref_stream_id, xref_position);
1998
1999        // Write object header
2000        self.write_bytes(
2001            format!(
2002                "{} {} obj\n",
2003                xref_stream_id.number(),
2004                xref_stream_id.generation()
2005            )
2006            .as_bytes(),
2007        )?;
2008
2009        // Get the encoded data
2010        let uncompressed_data = xref_writer.encode_entries();
2011        let final_data = if self.config.compress_streams {
2012            crate::compression::compress(&uncompressed_data)?
2013        } else {
2014            uncompressed_data
2015        };
2016
2017        // Create and write dictionary
2018        let mut dict = xref_writer.create_dictionary(None);
2019        dict.set("Length", Object::Integer(final_data.len() as i64));
2020
2021        // Add filter if compression is enabled
2022        if self.config.compress_streams {
2023            dict.set("Filter", Object::Name("FlateDecode".to_string()));
2024        }
2025        self.write_bytes(b"<<")?;
2026        for (key, value) in dict.iter() {
2027            self.write_bytes(b"\n/")?;
2028            self.write_bytes(key.as_bytes())?;
2029            self.write_bytes(b" ")?;
2030            self.write_object_value(value)?;
2031        }
2032        self.write_bytes(b"\n>>\n")?;
2033
2034        // Write stream
2035        self.write_bytes(b"stream\n")?;
2036        self.write_bytes(&final_data)?;
2037        self.write_bytes(b"\nendstream\n")?;
2038        self.write_bytes(b"endobj\n")?;
2039
2040        // Write startxref and EOF
2041        self.write_bytes(b"\nstartxref\n")?;
2042        self.write_bytes(xref_position.to_string().as_bytes())?;
2043        self.write_bytes(b"\n%%EOF\n")?;
2044
2045        Ok(())
2046    }
2047
2048    fn write_trailer(&mut self, xref_position: u64) -> Result<()> {
2049        let catalog_id = self.catalog_id.expect("catalog_id must be set");
2050        let info_id = self.info_id.expect("info_id must be set");
2051        // Find the highest object number to determine size
2052        let max_obj_num = self
2053            .xref_positions
2054            .keys()
2055            .map(|id| id.number())
2056            .max()
2057            .unwrap_or(0);
2058
2059        let mut trailer = Dictionary::new();
2060        trailer.set("Size", Object::Integer((max_obj_num + 1) as i64));
2061        trailer.set("Root", Object::Reference(catalog_id));
2062        trailer.set("Info", Object::Reference(info_id));
2063
2064        self.write_bytes(b"trailer\n")?;
2065        self.write_object_value(&Object::Dictionary(trailer))?;
2066        self.write_bytes(b"\nstartxref\n")?;
2067        self.write_bytes(xref_position.to_string().as_bytes())?;
2068        self.write_bytes(b"\n%%EOF\n")?;
2069
2070        Ok(())
2071    }
2072
2073    fn write_bytes(&mut self, data: &[u8]) -> Result<()> {
2074        self.writer.write_all(data)?;
2075        self.current_position += data.len() as u64;
2076        Ok(())
2077    }
2078
2079    #[allow(dead_code)]
2080    fn create_widget_appearance_stream(&mut self, widget_dict: &Dictionary) -> Result<ObjectId> {
2081        // Get widget rectangle
2082        let rect = if let Some(Object::Array(rect_array)) = widget_dict.get("Rect") {
2083            if rect_array.len() >= 4 {
2084                if let (
2085                    Some(Object::Real(x1)),
2086                    Some(Object::Real(y1)),
2087                    Some(Object::Real(x2)),
2088                    Some(Object::Real(y2)),
2089                ) = (
2090                    rect_array.first(),
2091                    rect_array.get(1),
2092                    rect_array.get(2),
2093                    rect_array.get(3),
2094                ) {
2095                    (*x1, *y1, *x2, *y2)
2096                } else {
2097                    (0.0, 0.0, 100.0, 20.0) // Default
2098                }
2099            } else {
2100                (0.0, 0.0, 100.0, 20.0) // Default
2101            }
2102        } else {
2103            (0.0, 0.0, 100.0, 20.0) // Default
2104        };
2105
2106        let width = rect.2 - rect.0;
2107        let height = rect.3 - rect.1;
2108
2109        // Create appearance stream content
2110        let mut content = String::new();
2111
2112        // Set graphics state
2113        content.push_str("q\n");
2114
2115        // Draw border (black)
2116        content.push_str("0 0 0 RG\n"); // Black stroke color
2117        content.push_str("1 w\n"); // 1pt line width
2118
2119        // Draw rectangle border
2120        content.push_str(&format!("0 0 {width} {height} re\n"));
2121        content.push_str("S\n"); // Stroke
2122
2123        // Fill with white background
2124        content.push_str("1 1 1 rg\n"); // White fill color
2125        content.push_str(&format!("0.5 0.5 {} {} re\n", width - 1.0, height - 1.0));
2126        content.push_str("f\n"); // Fill
2127
2128        // Restore graphics state
2129        content.push_str("Q\n");
2130
2131        // Create stream dictionary
2132        let mut stream_dict = Dictionary::new();
2133        stream_dict.set("Type", Object::Name("XObject".to_string()));
2134        stream_dict.set("Subtype", Object::Name("Form".to_string()));
2135        stream_dict.set(
2136            "BBox",
2137            Object::Array(vec![
2138                Object::Real(0.0),
2139                Object::Real(0.0),
2140                Object::Real(width),
2141                Object::Real(height),
2142            ]),
2143        );
2144        stream_dict.set("Resources", Object::Dictionary(Dictionary::new()));
2145        stream_dict.set("Length", Object::Integer(content.len() as i64));
2146
2147        // Write the appearance stream
2148        let stream_id = self.allocate_object_id();
2149        self.write_object(stream_id, Object::Stream(stream_dict, content.into_bytes()))?;
2150
2151        Ok(stream_id)
2152    }
2153
2154    #[allow(dead_code)]
2155    fn create_field_appearance_stream(
2156        &mut self,
2157        field_dict: &Dictionary,
2158        widget: &crate::forms::Widget,
2159    ) -> Result<ObjectId> {
2160        let width = widget.rect.upper_right.x - widget.rect.lower_left.x;
2161        let height = widget.rect.upper_right.y - widget.rect.lower_left.y;
2162
2163        // Create appearance stream content
2164        let mut content = String::new();
2165
2166        // Set graphics state
2167        content.push_str("q\n");
2168
2169        // Draw background if specified
2170        if let Some(bg_color) = &widget.appearance.background_color {
2171            match bg_color {
2172                crate::graphics::Color::Gray(g) => {
2173                    content.push_str(&format!("{g} g\n"));
2174                }
2175                crate::graphics::Color::Rgb(r, g, b) => {
2176                    content.push_str(&format!("{r} {g} {b} rg\n"));
2177                }
2178                crate::graphics::Color::Cmyk(c, m, y, k) => {
2179                    content.push_str(&format!("{c} {m} {y} {k} k\n"));
2180                }
2181            }
2182            content.push_str(&format!("0 0 {width} {height} re\n"));
2183            content.push_str("f\n");
2184        }
2185
2186        // Draw border
2187        if let Some(border_color) = &widget.appearance.border_color {
2188            match border_color {
2189                crate::graphics::Color::Gray(g) => {
2190                    content.push_str(&format!("{g} G\n"));
2191                }
2192                crate::graphics::Color::Rgb(r, g, b) => {
2193                    content.push_str(&format!("{r} {g} {b} RG\n"));
2194                }
2195                crate::graphics::Color::Cmyk(c, m, y, k) => {
2196                    content.push_str(&format!("{c} {m} {y} {k} K\n"));
2197                }
2198            }
2199            content.push_str(&format!("{} w\n", widget.appearance.border_width));
2200            content.push_str(&format!("0 0 {width} {height} re\n"));
2201            content.push_str("S\n");
2202        }
2203
2204        // For checkboxes, add a checkmark if checked
2205        if let Some(Object::Name(ft)) = field_dict.get("FT") {
2206            if ft == "Btn" {
2207                if let Some(Object::Name(v)) = field_dict.get("V") {
2208                    if v == "Yes" {
2209                        // Draw checkmark
2210                        content.push_str("0 0 0 RG\n"); // Black
2211                        content.push_str("2 w\n");
2212                        let margin = width * 0.2;
2213                        content.push_str(&format!("{} {} m\n", margin, height / 2.0));
2214                        content.push_str(&format!("{} {} l\n", width / 2.0, margin));
2215                        content.push_str(&format!("{} {} l\n", width - margin, height - margin));
2216                        content.push_str("S\n");
2217                    }
2218                }
2219            }
2220        }
2221
2222        // Restore graphics state
2223        content.push_str("Q\n");
2224
2225        // Create stream dictionary
2226        let mut stream_dict = Dictionary::new();
2227        stream_dict.set("Type", Object::Name("XObject".to_string()));
2228        stream_dict.set("Subtype", Object::Name("Form".to_string()));
2229        stream_dict.set(
2230            "BBox",
2231            Object::Array(vec![
2232                Object::Real(0.0),
2233                Object::Real(0.0),
2234                Object::Real(width),
2235                Object::Real(height),
2236            ]),
2237        );
2238        stream_dict.set("Resources", Object::Dictionary(Dictionary::new()));
2239        stream_dict.set("Length", Object::Integer(content.len() as i64));
2240
2241        // Write the appearance stream
2242        let stream_id = self.allocate_object_id();
2243        self.write_object(stream_id, Object::Stream(stream_dict, content.into_bytes()))?;
2244
2245        Ok(stream_id)
2246    }
2247}
2248
2249/// Format a DateTime as a PDF date string (D:YYYYMMDDHHmmSSOHH'mm)
2250fn format_pdf_date(date: DateTime<Utc>) -> String {
2251    // Format the UTC date according to PDF specification
2252    // D:YYYYMMDDHHmmSSOHH'mm where O is the relationship of local time to UTC (+ or -)
2253    let formatted = date.format("D:%Y%m%d%H%M%S");
2254
2255    // For UTC, the offset is always +00'00
2256    format!("{formatted}+00'00")
2257}
2258
2259#[cfg(test)]
2260mod tests;
2261
2262#[cfg(test)]
2263mod rigorous_tests;