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 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 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 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 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 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 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}