1use std::{borrow::Cow, io};
4
5use chrono::{DateTime, Local};
6use io::Write;
7
8use crate::{
9 common::{
10 Dict, Encoding, Matrix, NumberTree, ObjRef, PageLabel, PdfString, Point, ProcSet,
11 Rectangle, Trapped,
12 },
13 low,
14 lowering::{lower_dict, lower_outline_items, Lowerable, Lowering},
15 write::{Formatter, PdfName, Serialize},
16};
17
18pub struct Page<'a> {
20 pub media_box: Rectangle<i32>,
22 pub resources: Resources<'a>,
24 pub contents: Vec<u8>,
26}
27
28#[derive(Debug, Default)]
30pub struct Info {
31 pub title: Option<PdfString>,
33 pub author: Option<PdfString>,
35 pub subject: Option<PdfString>,
37 pub keywords: Option<PdfString>,
39 pub creator: Option<PdfString>,
41 pub producer: Option<PdfString>,
43
44 pub creation_date: Option<DateTime<Local>>,
46 pub mod_date: Option<DateTime<Local>>,
48
49 pub trapped: Option<Trapped>,
51}
52
53impl Serialize for Info {
54 fn write(&self, f: &mut Formatter) -> io::Result<()> {
55 let mut dict = f.pdf_dict();
56 if let Some(title) = &self.title {
57 dict.field("Title", title)?;
58 }
59 if let Some(author) = &self.author {
60 dict.field("Author", author)?;
61 }
62 if let Some(subject) = &self.subject {
63 dict.field("Subject", subject)?;
64 }
65 if let Some(keywords) = &self.keywords {
66 dict.field("Keywords", keywords)?;
67 }
68 if let Some(creator) = &self.creator {
69 dict.field("Creator", creator)?;
70 }
71 if let Some(producer) = &self.producer {
72 dict.field("Producer", producer)?;
73 }
74
75 if let Some(creation_date) = &self.creation_date {
76 dict.field("CreationDate", creation_date)?;
77 }
78 if let Some(mod_date) = &self.mod_date {
79 dict.field("ModDate", mod_date)?;
80 }
81 if let Some(trapped) = &self.trapped {
82 dict.field("Trapped", trapped)?;
83 }
84 dict.finish()?;
85 Ok(())
86 }
87}
88
89impl Info {
90 pub fn is_empty(&self) -> bool {
92 self.title.is_none()
93 && self.author.is_none()
94 && self.subject.is_none()
95 && self.keywords.is_none()
96 && self.creator.is_none()
97 && self.producer.is_none()
98 && self.creation_date.is_none()
99 && self.mod_date.is_none()
100 && self.trapped.is_none()
101 }
102}
103
104#[derive(Debug, Clone)]
105pub struct Outline {
107 pub children: Vec<OutlineItem>,
109}
110
111impl Default for Outline {
112 fn default() -> Self {
113 Self::new()
114 }
115}
116
117impl Outline {
118 pub fn new() -> Self {
120 Self { children: vec![] }
121 }
122}
123
124#[derive(Debug, Clone)]
126pub struct OutlineItem {
127 pub title: PdfString,
129 pub dest: Destination,
131 pub children: Vec<OutlineItem>,
133}
134
135#[derive(Debug, Copy, Clone)]
137pub enum Destination {
138 PageFitH(usize, usize),
140}
141
142#[derive(Debug)]
146pub enum Resource<T> {
147 Global {
149 index: usize,
151 },
152 Immediate(Box<T>),
154}
155
156#[derive(Debug)]
157pub struct Type3Font<'a> {
159 pub name: Option<PdfName<'a>>,
161 pub font_bbox: Rectangle<i32>,
163 pub font_matrix: Matrix<f32>,
165 pub first_char: u8,
167 pub last_char: u8,
169 pub char_procs: Dict<CharProc<'a>>,
171 pub encoding: Encoding<'a>,
173 pub widths: Vec<u32>,
175 pub to_unicode: (),
177}
178
179impl<'a> Default for Type3Font<'a> {
180 fn default() -> Self {
181 Self {
182 font_bbox: Rectangle {
183 ll: Point::default(),
184 ur: Point::default(),
185 },
186 name: None,
187 font_matrix: Matrix::default_glyph(),
188 first_char: 0,
189 last_char: 255,
190 char_procs: Dict::new(),
191 encoding: Encoding {
192 base_encoding: None,
193 differences: None,
194 },
195 widths: vec![],
196 to_unicode: (),
197 }
198 }
199}
200
201#[derive(Debug)]
202pub enum Font<'a> {
204 Type3(Type3Font<'a>),
206}
207
208#[derive(Debug)]
210pub enum XObject {
211 Image(Image),
213}
214
215#[derive(Debug)]
216pub struct Image {}
218
219pub type DictResource<T> = Dict<Resource<T>>;
221pub type ResDictRes<T> = Resource<Dict<Resource<T>>>;
223
224pub struct Resources<'a> {
226 pub fonts: ResDictRes<Font<'a>>,
228 pub x_objects: ResDictRes<XObject>,
230 pub proc_sets: Vec<ProcSet>,
232}
233
234impl<'a> Default for Resources<'a> {
235 fn default() -> Self {
236 Resources {
237 fonts: Resource::Immediate(Box::new(Dict::new())),
238 x_objects: Resource::Immediate(Box::new(Dict::new())),
239 proc_sets: vec![ProcSet::PDF, ProcSet::Text],
240 }
241 }
242}
243#[derive(Debug)]
245pub struct Res<'a> {
246 pub fonts: Vec<Font<'a>>,
248 pub font_dicts: Vec<DictResource<Font<'a>>>,
250 pub x_objects: Vec<XObject>,
252 pub x_object_dicts: Vec<DictResource<XObject>>,
254 pub char_procs: Vec<CharProc<'a>>,
256 pub encodings: Vec<Encoding<'a>>,
258}
259
260impl<'a> Default for Res<'a> {
261 fn default() -> Self {
262 Self {
263 fonts: Vec::new(),
264 font_dicts: Vec::new(),
265 x_objects: Vec::new(),
266 x_object_dicts: Vec::new(),
267 char_procs: Vec::new(),
268 encodings: Vec::new(),
269 }
270 }
271}
272
273pub struct Handle<'a> {
277 pub info: Info,
279 pub pages: Vec<Page<'a>>,
281 pub page_labels: NumberTree<PageLabel>,
283 pub outline: Outline,
285 pub res: Res<'a>,
287}
288
289impl<'a> Default for Handle<'a> {
290 fn default() -> Self {
291 Handle::new()
292 }
293}
294
295#[derive(Debug, Clone)]
296pub struct CharProc<'a>(pub Cow<'a, [u8]>);
298
299impl<'a> Handle<'a> {
300 pub fn new() -> Self {
302 Self {
303 info: Info::default(),
304 res: Res::default(),
305 page_labels: NumberTree::new(),
306 outline: Outline::new(),
307 pages: vec![],
308 }
309 }
310
311 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
313 let mut fmt = Formatter::new(w);
314
315 let gen = 0;
316 let make_ref = move |id: u64| ObjRef { id, gen };
317
318 writeln!(fmt.inner, "%PDF-1.5")?;
319 writeln!(fmt.inner)?;
320
321 let mut lowering = Lowering::new(self);
322
323 let catalog_id = lowering.id_gen.next();
324 let info_id = if self.info.is_empty() {
325 None
326 } else {
327 let info_id = lowering.id_gen.next();
328 let r = make_ref(info_id);
329 fmt.obj(r, &self.info)?;
330 Some(r)
331 };
332
333 let mut pages = low::Pages { kids: vec![] };
334 let pages_id = lowering.id_gen.next();
335 let pages_ref = make_ref(pages_id);
336
337 for page in &self.pages {
338 let page_id = lowering.id_gen.next();
339 let contents_id = lowering.id_gen.next();
340 let contents_ref = make_ref(contents_id);
341
342 let contents = low::Stream {
343 data: page.contents.clone(),
344 };
345 fmt.obj(contents_ref, &contents)?;
346
347 let page_ref = make_ref(page_id);
348 let page_low = low::Page {
349 parent: pages_ref,
350 resources: low::Resources {
351 font: lowering.font_dicts.map_dict(
352 &page.resources.fonts,
353 &mut lowering.fonts,
354 &mut lowering.font_ctx,
355 &mut lowering.id_gen,
356 ),
357 x_object: lowering.x_object_dicts.map_dict(
358 &page.resources.x_objects,
359 &mut lowering.x_objects,
360 &mut (),
361 &mut lowering.id_gen,
362 ),
363 proc_set: &page.resources.proc_sets,
364 },
365 contents: contents_ref,
366 media_box: Some(page.media_box),
367 };
368 fmt.obj(page_ref, &page_low)?;
369 pages.kids.push(page_ref);
370 }
371
372 for (font_dict_ref, font_dict) in &lowering.font_dicts.store {
373 let dict = lower_dict(
374 font_dict,
375 &mut lowering.fonts,
376 &mut lowering.font_ctx,
377 &mut lowering.id_gen,
378 );
379 fmt.obj(*font_dict_ref, &dict)?;
380 }
381
382 for (font_ref, font) in &lowering.fonts.store {
383 let font_low = font.lower(&mut lowering.font_ctx, &mut lowering.id_gen);
384 fmt.obj(*font_ref, &font_low)?;
385 }
386
387 for (cproc_ref, char_proc) in &lowering.font_ctx.0.store {
389 let cp = char_proc.lower(&mut (), &mut lowering.id_gen);
390 fmt.obj(*cproc_ref, &cp)?;
391 }
392
393 let pages_ref = make_ref(pages_id);
394 fmt.obj(pages_ref, &pages)?;
395
396 let pl_ref = if !self.page_labels.is_empty() {
397 let page_labels_id = lowering.id_gen.next();
398 let page_labels_ref = make_ref(page_labels_id);
399
400 fmt.obj(page_labels_ref, &self.page_labels)?;
401 Some(page_labels_ref)
402 } else {
403 None
404 };
405
406 let ol_ref = if !self.outline.children.is_empty() {
407 let mut ol_acc = Vec::new();
408 let outline_ref = make_ref(lowering.id_gen.next());
409 let (first, last) = lower_outline_items(
410 &mut ol_acc,
411 &pages.kids,
412 &self.outline.children,
413 outline_ref,
414 &mut lowering.id_gen,
415 )
416 .unwrap(); let outline = low::Outline {
418 first,
419 last,
420 count: self.outline.children.len(),
421 };
422
423 for (r, item) in ol_acc {
424 fmt.obj(r, &item)?;
425 }
426
427 fmt.obj(outline_ref, &outline)?;
428 Some(outline_ref)
429 } else {
430 None
431 };
432
433 let catalog = low::Catalog {
434 version: None,
435 pages: pages_ref,
436 page_labels: pl_ref,
437 outline: ol_ref,
438 };
439 let catalog_ref = make_ref(catalog_id);
440 fmt.obj(catalog_ref, &catalog)?;
441
442 let startxref = fmt.xref()?;
443
444 writeln!(fmt.inner, "trailer")?;
445
446 let trailer = low::Trailer {
447 size: fmt.xref.len(),
448 root: make_ref(catalog_id),
449 info: info_id,
450 };
451 trailer.write(&mut fmt)?;
452
453 writeln!(fmt.inner, "startxref")?;
454 writeln!(fmt.inner, "{}", startxref)?;
455 writeln!(fmt.inner, "%%EOF")?;
456
457 Ok(())
458 }
459}