1use crate::ast::{AstNode, NodeId, NodeType, PdfAstGraph};
2use crate::types::{PdfArray, PdfDictionary, PdfName, PdfString, PdfValue};
3
4pub struct DocumentBuilder {
6 graph: PdfAstGraph,
7 catalog_id: Option<NodeId>,
8 pages_root_id: Option<NodeId>,
9 current_page: Option<NodeId>,
10 info_id: Option<NodeId>,
11}
12
13impl DocumentBuilder {
14 pub fn new() -> Self {
15 Self {
16 graph: PdfAstGraph::new(),
17 catalog_id: None,
18 pages_root_id: None,
19 current_page: None,
20 info_id: None,
21 }
22 }
23
24 pub fn with_catalog(&mut self) -> &mut Self {
26 let mut catalog_dict = PdfDictionary::new();
27 catalog_dict.insert("Type", PdfValue::Name(PdfName::new("Catalog")));
28
29 let catalog_node = AstNode::new(
30 NodeId(1),
31 NodeType::Catalog,
32 PdfValue::Dictionary(catalog_dict),
33 );
34
35 let catalog_id = self
36 .graph
37 .create_node(NodeType::Catalog, catalog_node.value);
38 self.graph.set_root(catalog_id);
39 self.catalog_id = Some(catalog_id);
40
41 self
42 }
43
44 pub fn with_pages_tree(&mut self) -> &mut Self {
46 if self.catalog_id.is_none() {
47 self.with_catalog();
48 }
49
50 let mut pages_dict = PdfDictionary::new();
51 pages_dict.insert("Type", PdfValue::Name(PdfName::new("Pages")));
52 pages_dict.insert("Count", PdfValue::Integer(0));
53 pages_dict.insert("Kids", PdfValue::Array(PdfArray::new()));
54
55 let pages_id = self
56 .graph
57 .create_node(NodeType::Pages, PdfValue::Dictionary(pages_dict));
58
59 if let Some(catalog_id) = self.catalog_id {
60 self.graph
61 .add_edge(catalog_id, pages_id, crate::ast::EdgeType::Child);
62 }
63
64 self.pages_root_id = Some(pages_id);
65 self
66 }
67
68 pub fn add_page(&mut self, width: f32, height: f32) -> &mut Self {
70 if self.pages_root_id.is_none() {
71 self.with_pages_tree();
72 }
73
74 let mut page_dict = PdfDictionary::new();
75 page_dict.insert("Type", PdfValue::Name(PdfName::new("Page")));
76
77 let mut media_box = PdfArray::new();
79 media_box.push(PdfValue::Integer(0));
80 media_box.push(PdfValue::Integer(0));
81 media_box.push(PdfValue::Real(width as f64));
82 media_box.push(PdfValue::Real(height as f64));
83 page_dict.insert("MediaBox", PdfValue::Array(media_box));
84
85 let resources_dict = PdfDictionary::new();
87 page_dict.insert("Resources", PdfValue::Dictionary(resources_dict));
88
89 let page_id = self
90 .graph
91 .create_node(NodeType::Page, PdfValue::Dictionary(page_dict));
92
93 if let Some(pages_id) = self.pages_root_id {
94 self.graph
95 .add_edge(pages_id, page_id, crate::ast::EdgeType::Child);
96
97 if let Some(pages_node) = self.graph.get_node_mut(pages_id) {
99 if let PdfValue::Dictionary(dict) = &mut pages_node.value {
100 if let Some(PdfValue::Integer(count)) = dict.get_mut("Count") {
101 *count += 1;
102 }
103
104 if let Some(PdfValue::Array(kids)) = dict.get_mut("Kids") {
106 kids.push(PdfValue::Reference(crate::types::PdfReference::new(
107 page_id.0 as u32,
108 0,
109 )));
110 }
111 }
112 }
113 }
114
115 self.current_page = Some(page_id);
116 self
117 }
118
119 pub fn add_content_stream(&mut self, content: &str) -> &mut Self {
121 if let Some(page_id) = self.current_page {
122 let content_data = content.as_bytes().to_vec();
123 let stream = crate::types::PdfStream {
124 dict: {
125 let mut dict = PdfDictionary::new();
126 dict.insert("Length", PdfValue::Integer(content_data.len() as i64));
127 dict
128 },
129 data: crate::types::StreamData::Raw(content_data),
130 };
131
132 let stream_id = self
133 .graph
134 .create_node(NodeType::ContentStream, PdfValue::Stream(stream));
135
136 self.graph
137 .add_edge(page_id, stream_id, crate::ast::EdgeType::Child);
138
139 if let Some(page_node) = self.graph.get_node_mut(page_id) {
141 if let PdfValue::Dictionary(dict) = &mut page_node.value {
142 dict.insert(
143 "Contents",
144 PdfValue::Reference(crate::types::PdfReference::new(stream_id.0 as u32, 0)),
145 );
146 }
147 }
148 }
149
150 self
151 }
152
153 pub fn add_font(&mut self, name: &str, font_type: FontType, base_font: &str) -> &mut Self {
155 if let Some(page_id) = self.current_page {
156 let mut font_dict = PdfDictionary::new();
157 font_dict.insert("Type", PdfValue::Name(PdfName::new("Font")));
158 font_dict.insert("Subtype", PdfValue::Name(PdfName::new(font_type.as_str())));
159 font_dict.insert("BaseFont", PdfValue::Name(PdfName::new(base_font)));
160
161 let font_id = self.graph.create_node(
162 match font_type {
163 FontType::Type1 => NodeType::Type1Font,
164 FontType::TrueType => NodeType::TrueTypeFont,
165 FontType::Type3 => NodeType::Type3Font,
166 },
167 PdfValue::Dictionary(font_dict),
168 );
169
170 self.graph
171 .add_edge(page_id, font_id, crate::ast::EdgeType::Child);
172
173 if let Some(page_node) = self.graph.get_node_mut(page_id) {
175 if let PdfValue::Dictionary(page_dict) = &mut page_node.value {
176 if let Some(PdfValue::Dictionary(resources)) = page_dict.get_mut("Resources") {
177 let font_dict = resources
179 .entry("Font")
180 .or_insert_with(|| PdfValue::Dictionary(PdfDictionary::new()));
181
182 if let PdfValue::Dictionary(fonts) = font_dict {
183 fonts.insert(
184 name,
185 PdfValue::Reference(crate::types::PdfReference::new(
186 font_id.0 as u32,
187 0,
188 )),
189 );
190 }
191 }
192 }
193 }
194 }
195
196 self
197 }
198
199 pub fn with_info(
201 &mut self,
202 title: Option<&str>,
203 author: Option<&str>,
204 creator: Option<&str>,
205 ) -> &mut Self {
206 let mut info_dict = PdfDictionary::new();
207
208 if let Some(title) = title {
209 info_dict.insert(
210 "Title",
211 PdfValue::String(PdfString::new_literal(title.as_bytes())),
212 );
213 }
214
215 if let Some(author) = author {
216 info_dict.insert(
217 "Author",
218 PdfValue::String(PdfString::new_literal(author.as_bytes())),
219 );
220 }
221
222 if let Some(creator) = creator {
223 info_dict.insert(
224 "Creator",
225 PdfValue::String(PdfString::new_literal(creator.as_bytes())),
226 );
227 }
228
229 let now = chrono::Utc::now().format("D:%Y%m%d%H%M%S%z").to_string();
231 info_dict.insert(
232 "CreationDate",
233 PdfValue::String(PdfString::new_literal(now.as_bytes())),
234 );
235
236 let info_id = self
237 .graph
238 .create_node(NodeType::Metadata, PdfValue::Dictionary(info_dict));
239
240 if let Some(catalog_id) = self.catalog_id {
241 self.graph
242 .add_edge(catalog_id, info_id, crate::ast::EdgeType::Child);
243 }
244
245 self.info_id = Some(info_id);
246 self
247 }
248
249 pub fn add_annotation(&mut self, annotation_type: AnnotationType, rect: [f32; 4]) -> &mut Self {
251 if let Some(page_id) = self.current_page {
252 let mut annot_dict = PdfDictionary::new();
253 annot_dict.insert("Type", PdfValue::Name(PdfName::new("Annot")));
254 annot_dict.insert(
255 "Subtype",
256 PdfValue::Name(PdfName::new(annotation_type.as_str())),
257 );
258
259 let mut rect_array = PdfArray::new();
261 for &coord in &rect {
262 rect_array.push(PdfValue::Real(coord as f64));
263 }
264 annot_dict.insert("Rect", PdfValue::Array(rect_array));
265
266 let annot_id = self
267 .graph
268 .create_node(NodeType::Annotation, PdfValue::Dictionary(annot_dict));
269
270 self.graph
271 .add_edge(page_id, annot_id, crate::ast::EdgeType::Child);
272 }
273
274 self
275 }
276
277 pub fn build(self) -> PdfAstGraph {
279 self.graph
280 }
281
282 pub fn graph(&self) -> &PdfAstGraph {
284 &self.graph
285 }
286
287 pub fn graph_mut(&mut self) -> &mut PdfAstGraph {
289 &mut self.graph
290 }
291}
292
293impl Default for DocumentBuilder {
294 fn default() -> Self {
295 Self::new()
296 }
297}
298
299#[derive(Debug, Clone, Copy)]
300pub enum FontType {
301 Type1,
302 TrueType,
303 Type3,
304}
305
306impl FontType {
307 fn as_str(&self) -> &'static str {
308 match self {
309 FontType::Type1 => "Type1",
310 FontType::TrueType => "TrueType",
311 FontType::Type3 => "Type3",
312 }
313 }
314}
315
316#[derive(Debug, Clone, Copy)]
317pub enum AnnotationType {
318 Text,
319 Link,
320 FreeText,
321 Line,
322 Square,
323 Circle,
324 Highlight,
325 Underline,
326 Squiggly,
327 StrikeOut,
328 Stamp,
329 Caret,
330 Ink,
331 Popup,
332 FileAttachment,
333 Sound,
334 Movie,
335 Widget,
336 Screen,
337 PrinterMark,
338 TrapNet,
339 Watermark,
340 ThreeD,
341 Redact,
342}
343
344impl AnnotationType {
345 fn as_str(&self) -> &'static str {
346 match self {
347 AnnotationType::Text => "Text",
348 AnnotationType::Link => "Link",
349 AnnotationType::FreeText => "FreeText",
350 AnnotationType::Line => "Line",
351 AnnotationType::Square => "Square",
352 AnnotationType::Circle => "Circle",
353 AnnotationType::Highlight => "Highlight",
354 AnnotationType::Underline => "Underline",
355 AnnotationType::Squiggly => "Squiggly",
356 AnnotationType::StrikeOut => "StrikeOut",
357 AnnotationType::Stamp => "Stamp",
358 AnnotationType::Caret => "Caret",
359 AnnotationType::Ink => "Ink",
360 AnnotationType::Popup => "Popup",
361 AnnotationType::FileAttachment => "FileAttachment",
362 AnnotationType::Sound => "Sound",
363 AnnotationType::Movie => "Movie",
364 AnnotationType::Widget => "Widget",
365 AnnotationType::Screen => "Screen",
366 AnnotationType::PrinterMark => "PrinterMark",
367 AnnotationType::TrapNet => "TrapNet",
368 AnnotationType::Watermark => "Watermark",
369 AnnotationType::ThreeD => "3D",
370 AnnotationType::Redact => "Redact",
371 }
372 }
373}