Skip to main content

graphitepdf_kit/
writer.rs

1use crate::backend_font::{Font, FontRegistry};
2use crate::compress::flate_encode;
3use crate::error::Result;
4use crate::metadata::Metadata;
5use crate::objects::Object;
6use crate::page::PageSize;
7use std::io::Write;
8
9#[cfg(feature = "tracing")]
10use tracing::instrument;
11
12#[derive(Clone, Debug)]
13struct PageInfo {
14    size: PageSize,
15    content: Vec<u8>,
16}
17
18pub struct PdfWriter {
19    buffer: Vec<u8>,
20    pages: Vec<PageInfo>,
21    metadata: Metadata,
22    fonts: FontRegistry,
23}
24
25impl PdfWriter {
26    pub fn new() -> Self {
27        Self {
28            buffer: Vec::new(),
29            pages: Vec::new(),
30            metadata: Metadata::new(),
31            fonts: FontRegistry::with_default_font(),
32        }
33    }
34
35    pub fn with_metadata(metadata: Metadata) -> Self {
36        Self::with_metadata_and_fonts(metadata, FontRegistry::with_default_font())
37    }
38
39    pub(crate) fn with_metadata_and_fonts(metadata: Metadata, fonts: FontRegistry) -> Self {
40        Self {
41            buffer: Vec::new(),
42            pages: Vec::new(),
43            metadata,
44            fonts,
45        }
46    }
47
48    pub fn add_page(&mut self, size: PageSize, content: Vec<u8>) {
49        self.pages.push(PageInfo { size, content });
50    }
51
52    pub fn add_font(&mut self, font: impl Into<Font>) -> String {
53        self.fonts.register(font)
54    }
55
56    #[cfg_attr(feature = "tracing", instrument(skip(self)))]
57    pub fn write_all(mut self) -> Result<Vec<u8>> {
58        self.buffer.extend(b"%PDF-1.7\n");
59        self.buffer.extend(b"%\xc3\xa4\xc3\xb1\xc3\xb6\xc3\x9f\n");
60
61        let mut objects = Vec::new();
62
63        // 1. Add all fonts
64        let mut font_ids: std::collections::HashMap<String, usize> =
65            std::collections::HashMap::new();
66        for (name, (font, _)) in &self.fonts.fonts {
67            let font_obj = Object::dict([
68                ("Type", Object::name("Font")),
69                ("Subtype", Object::name("Type1")),
70                ("BaseFont", Object::name(font.base_font_name())),
71                ("Encoding", Object::name("WinAnsiEncoding")),
72            ]);
73            font_ids.insert(name.clone(), objects.len() + 1);
74            objects.push(font_obj);
75        }
76
77        // 2. Add all page content streams
78        let mut content_ids = Vec::new();
79        for page in &self.pages {
80            let compressed = flate_encode(&page.content)?;
81            let content_obj = Object::Stream {
82                dict: vec![("Filter".to_string(), Object::name("FlateDecode"))],
83                data: compressed,
84            };
85            content_ids.push(objects.len() + 1);
86            objects.push(content_obj);
87        }
88
89        // 3. Add all page objects
90        let mut page_ids = Vec::new();
91        for (i, page) in self.pages.iter().enumerate() {
92            let mut font_dict: Vec<(String, Object)> = Vec::new();
93            for (name, &id) in &font_ids {
94                font_dict.push((name.clone(), Object::Ref(id as u64)));
95            }
96
97            let page_obj = Object::dict([
98                ("Type", Object::name("Page")),
99                (
100                    "MediaBox",
101                    Object::array([0.0, 0.0, page.size.width, page.size.height]),
102                ),
103                ("Contents", Object::Ref(content_ids[i] as u64)),
104                (
105                    "Resources",
106                    Object::dict([("Font", Object::Dict(font_dict))]),
107                ),
108            ]);
109            page_ids.push(objects.len() + 1);
110            objects.push(page_obj);
111        }
112
113        // 4. Add pages tree
114        let pages_tree_id = objects.len() + 1;
115        objects.push(Object::dict([
116            ("Type", Object::name("Pages")),
117            ("Count", Object::Integer(page_ids.len() as i64)),
118            (
119                "Kids",
120                Object::Array(
121                    page_ids
122                        .into_iter()
123                        .map(|id| Object::Ref(id as u64))
124                        .collect(),
125                ),
126            ),
127        ]));
128
129        // 5. Add catalog
130        let catalog_id = objects.len() + 1;
131        objects.push(Object::dict([
132            ("Type", Object::name("Catalog")),
133            ("Pages", Object::Ref(pages_tree_id as u64)),
134        ]));
135
136        // 6. Add info dictionary
137        let mut info_dict_entries =
138            vec![("Producer".to_string(), Object::string("GraphitePDF Kit"))];
139        if let Some(title) = &self.metadata.title {
140            info_dict_entries.push(("Title".to_string(), Object::string(title)));
141        }
142        if let Some(author) = &self.metadata.author {
143            info_dict_entries.push(("Author".to_string(), Object::string(author)));
144        }
145        if let Some(subject) = &self.metadata.subject {
146            info_dict_entries.push(("Subject".to_string(), Object::string(subject)));
147        }
148        if !self.metadata.keywords.is_empty() {
149            info_dict_entries.push((
150                "Keywords".to_string(),
151                Object::string(self.metadata.keywords.join(", ")),
152            ));
153        }
154        if let Some(creator) = &self.metadata.creator {
155            info_dict_entries.push(("Creator".to_string(), Object::string(creator)));
156        }
157        let info_id = objects.len() + 1;
158        objects.push(Object::Dict(info_dict_entries));
159
160        let mut offsets = Vec::new();
161        for obj in objects {
162            offsets.push(self.buffer.len() as u64);
163            writeln!(&mut self.buffer, "{} 0 obj", offsets.len())?;
164            obj.write(&mut self.buffer)?;
165            self.buffer.push(b'\n');
166            writeln!(&mut self.buffer, "endobj")?;
167        }
168
169        let xref_offset = self.buffer.len() as u64;
170        writeln!(&mut self.buffer, "xref")?;
171        writeln!(&mut self.buffer, "0 {}", offsets.len() + 1)?;
172        writeln!(&mut self.buffer, "0000000000 65535 f ")?;
173        for offset in &offsets {
174            writeln!(&mut self.buffer, "{:010} 00000 n ", offset)?;
175        }
176
177        writeln!(&mut self.buffer, "trailer")?;
178        Object::dict([
179            ("Size", Object::Integer((offsets.len() + 1) as i64)),
180            ("Root", Object::Ref(catalog_id as u64)),
181            ("Info", Object::Ref(info_id as u64)),
182        ])
183        .write(&mut self.buffer)?;
184        writeln!(&mut self.buffer)?;
185
186        writeln!(&mut self.buffer, "startxref")?;
187        writeln!(&mut self.buffer, "{}", xref_offset)?;
188        writeln!(&mut self.buffer, "%%EOF")?;
189
190        Ok(self.buffer)
191    }
192}
193
194impl Default for PdfWriter {
195    fn default() -> Self {
196        Self::new()
197    }
198}