pdf_create/
high.rs

1//! High-Level API
2
3use 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
18/// A single page
19pub struct Page<'a> {
20    /// The dimensions of the page
21    pub media_box: Rectangle<i32>,
22    /// The resource used within the page
23    pub resources: Resources<'a>,
24    /// The content stream of the page
25    pub contents: Vec<u8>,
26}
27
28/// The Metadata/Info
29#[derive(Debug, Default)]
30pub struct Info {
31    /// The title
32    pub title: Option<PdfString>,
33    /// The author
34    pub author: Option<PdfString>,
35    /// The subject
36    pub subject: Option<PdfString>,
37    /// A list of keywords
38    pub keywords: Option<PdfString>,
39    /// The program used to create the source
40    pub creator: Option<PdfString>,
41    /// The program that produced the file (this library)
42    pub producer: Option<PdfString>,
43
44    /// The date of creation
45    pub creation_date: Option<DateTime<Local>>,
46    /// The date of the last modification
47    pub mod_date: Option<DateTime<Local>>,
48
49    /// Whether the PDF is *trapped*
50    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    /// Check whether the info contains any meaningful data
91    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)]
105/// Information for the Outline of the document
106pub struct Outline {
107    /// Immediate children of this item
108    pub children: Vec<OutlineItem>,
109}
110
111impl Default for Outline {
112    fn default() -> Self {
113        Self::new()
114    }
115}
116
117impl Outline {
118    /// Creates a new outline struct
119    pub fn new() -> Self {
120        Self { children: vec![] }
121    }
122}
123
124/// One item in the outline
125#[derive(Debug, Clone)]
126pub struct OutlineItem {
127    /// The title of the outline item
128    pub title: PdfString,
129    /// The destination to navigate to
130    pub dest: Destination,
131    /// Immediate children of this item
132    pub children: Vec<OutlineItem>,
133}
134
135/// A destination of a GoTo Action
136#[derive(Debug, Copy, Clone)]
137pub enum Destination {
138    /// Scroll to page {0} at height {1} while fitting the page to the viewer
139    PageFitH(usize, usize),
140}
141
142/// This enum represents a resource of type T for use in a dictionary.
143///
144/// It does not implement serialize, because it's possible that an index needs to be resolved
145#[derive(Debug)]
146pub enum Resource<T> {
147    /// Use the resource at {index} from the global list
148    Global {
149        /// The index into the global list
150        index: usize,
151    },
152    /// Use the value in the box
153    Immediate(Box<T>),
154}
155
156#[derive(Debug)]
157/// A type 3 font
158pub struct Type3Font<'a> {
159    /// The name of the font
160    pub name: Option<PdfName<'a>>,
161    /// The largest boundig box that fits all glyphs
162    pub font_bbox: Rectangle<i32>,
163    /// The matrix to map glyph space into text space
164    pub font_matrix: Matrix<f32>,
165    /// The first used char key
166    pub first_char: u8,
167    /// The last used char key
168    pub last_char: u8,
169    /// Dict of char names to drawing procedures
170    pub char_procs: Dict<CharProc<'a>>,
171    /// Dict of encoding value to char names
172    pub encoding: Encoding<'a>,
173    /// Width of every char between first and last
174    pub widths: Vec<u32>,
175    /// TODO
176    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)]
202/// A Font resource
203pub enum Font<'a> {
204    /// A type 3 font i.e. arbitrary glyph drawings
205    Type3(Type3Font<'a>),
206}
207
208/// An embedded object resource
209#[derive(Debug)]
210pub enum XObject {
211    /// An image
212    Image(Image),
213}
214
215#[derive(Debug)]
216/// An Image resource
217pub struct Image {}
218
219/// A dict of resources
220pub type DictResource<T> = Dict<Resource<T>>;
221/// A referenced or immediate dict of resources
222pub type ResDictRes<T> = Resource<Dict<Resource<T>>>;
223
224/// The resources of a page
225pub struct Resources<'a> {
226    /// A dict of font resources
227    pub fonts: ResDictRes<Font<'a>>,
228    /// A dict of embedded object resources
229    pub x_objects: ResDictRes<XObject>,
230    /// A set of valid procedures
231    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/// The global context for lowering
244#[derive(Debug)]
245pub struct Res<'a> {
246    /// Font resources
247    pub fonts: Vec<Font<'a>>,
248    /// Font dict resources
249    pub font_dicts: Vec<DictResource<Font<'a>>>,
250    /// Embedded object resources
251    pub x_objects: Vec<XObject>,
252    /// Embedded object dict resources
253    pub x_object_dicts: Vec<DictResource<XObject>>,
254    /// Char Procedure resources
255    pub char_procs: Vec<CharProc<'a>>,
256    /// Encoding resources
257    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
273/// Entrypoint to the high-level API
274///
275/// Create a new handle to start creating a PDF document
276pub struct Handle<'a> {
277    /// The info/metadata
278    pub info: Info,
279    /// The pages
280    pub pages: Vec<Page<'a>>,
281    /// The settings for page numbering for a PDF viewer
282    pub page_labels: NumberTree<PageLabel>,
283    /// The outline for a PDF viewer
284    pub outline: Outline,
285    /// The global resource struct
286    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)]
296/// A character drawing procedure
297pub struct CharProc<'a>(pub Cow<'a, [u8]>);
298
299impl<'a> Handle<'a> {
300    /// Creates a new handle
301    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    /// Write the whole PDF to the given writer
312    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        // FIXME: this only works AFTER all fonts are lowered
388        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(); // safe because this will always return Some for non empty children
417            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}