pdf_canvas/
lib.rs

1//! A library for creating pdf files.
2//!
3//! Currently, simple vector graphics and text set in the 14 built-in
4//! fonts are supported.
5//! The main entry point of the crate is the [struct Pdf](struct.Pdf.html),
6//! representing a PDF file being written.
7
8//! # Example
9//!
10//! ```
11//! use pdf_canvas::{Pdf, BuiltinFont, FontSource};
12//! use pdf_canvas::graphicsstate::Color;
13//!
14//! let mut document = Pdf::create("example.pdf")
15//!     .expect("Create pdf file");
16//! // The 14 builtin fonts are available
17//! let font = BuiltinFont::Times_Roman;
18//!
19//! // Add a page to the document.  This page will be 180 by 240 pt large.
20//! document.render_page(180.0, 240.0, |canvas| {
21//!     // This closure defines the content of the page
22//!     let hello = "Hello World!";
23//!     let w = font.get_width(24.0, hello) + 8.0;
24//!
25//!     // Some simple graphics
26//!     canvas.set_stroke_color(Color::rgb(0, 0, 248))?;
27//!     canvas.rectangle(90.0 - w / 2.0, 194.0, w, 26.0)?;
28//!     canvas.stroke()?;
29//!
30//!     // Some text
31//!     canvas.center_text(90.0, 200.0, font, 24.0, hello)
32//! }).expect("Write page");
33//! // Write all pending content, including the trailer and index
34//! document.finish().expect("Finish pdf document");
35//! ```
36//!
37//! To use this library you need to add it as a dependency in your
38//! `Cargo.toml`:
39//!
40//! ```toml
41//! [dependencies]
42//! pdf-canvas = "*"
43//! ```
44//!
45//! Some more working usage examples exists in [the examples directory]
46//! (https://github.com/kaj/rust-pdf/tree/master/examples).
47#![deny(missing_docs)]
48
49#[macro_use]
50extern crate lazy_static;
51
52use chrono::offset::Local;
53use std::collections::{BTreeMap, HashMap};
54use std::fmt;
55use std::fs::File;
56use std::io::{self, Seek, SeekFrom, Write};
57
58mod fontsource;
59pub use crate::fontsource::{BuiltinFont, FontSource};
60
61mod fontref;
62pub use crate::fontref::FontRef;
63
64mod fontmetrics;
65pub use crate::fontmetrics::FontMetrics;
66
67mod encoding;
68pub use crate::encoding::Encoding;
69
70pub mod graphicsstate;
71
72mod outline;
73use crate::outline::OutlineItem;
74
75mod canvas;
76pub use crate::canvas::Canvas;
77
78mod textobject;
79pub use crate::textobject::TextObject;
80
81/// The top-level object for writing a PDF.
82///
83/// A PDF file is created with the `create` or `new` methods.
84/// Some metadata can be stored with `set_foo` methods, and pages
85/// are appended with the `render_page` method.
86/// Don't forget to call `finish` when done, to write the document
87/// trailer, without it the written file won't be a proper PDF.
88pub struct Pdf {
89    output: File,
90    object_offsets: Vec<i64>,
91    page_objects_ids: Vec<usize>,
92    all_font_object_ids: HashMap<BuiltinFont, usize>,
93    outline_items: Vec<OutlineItem>,
94    document_info: BTreeMap<String, String>,
95}
96
97const ROOT_OBJECT_ID: usize = 1;
98const PAGES_OBJECT_ID: usize = 2;
99
100impl Pdf {
101    /// Create a new PDF document as a new file with given filename.
102    pub fn create(filename: &str) -> io::Result<Pdf> {
103        let file = File::create(filename)?;
104        Pdf::new(file)
105    }
106
107    /// Create a new PDF document, writing to `output`.
108    pub fn new(mut output: File) -> io::Result<Pdf> {
109        // TODO Maybe use a lower version?  Possibly decide by features used?
110        output.write_all(b"%PDF-1.7\n%\xB5\xED\xAE\xFB\n")?;
111        Ok(Pdf {
112            output,
113            // Object ID 0 is special in PDF.
114            // We reserve IDs 1 and 2 for the catalog and page tree.
115            object_offsets: vec![-1, -1, -1],
116            page_objects_ids: Vec::new(),
117            all_font_object_ids: HashMap::new(),
118            outline_items: Vec::new(),
119            document_info: BTreeMap::new(),
120        })
121    }
122    /// Set metadata: the document's title.
123    pub fn set_title(&mut self, title: &str) {
124        self.document_info
125            .insert("Title".to_string(), title.to_string());
126    }
127    /// Set metadata: the name of the person who created the document.
128    pub fn set_author(&mut self, author: &str) {
129        self.document_info
130            .insert("Author".to_string(), author.to_string());
131    }
132    /// Set metadata: the subject of the document.
133    pub fn set_subject(&mut self, subject: &str) {
134        self.document_info
135            .insert("Subject".to_string(), subject.to_string());
136    }
137    /// Set metadata: keywords associated with the document.
138    pub fn set_keywords(&mut self, keywords: &str) {
139        self.document_info
140            .insert("Keywords".to_string(), keywords.to_string());
141    }
142    /// Set metadata: If the document was converted to PDF from another
143    /// format, the name of the conforming product that created the original
144    /// document from which it was converted.
145    pub fn set_creator(&mut self, creator: &str) {
146        self.document_info
147            .insert("Creator".to_string(), creator.to_string());
148    }
149    /// Set metadata: If the document was converted to PDF from another
150    /// format, the name of the conforming product that converted it to PDF.
151    pub fn set_producer(&mut self, producer: &str) {
152        self.document_info
153            .insert("Producer".to_string(), producer.to_string());
154    }
155
156    /// Return the current read/write position in the output file.
157    fn tell(&mut self) -> io::Result<u64> {
158        self.output.seek(SeekFrom::Current(0))
159    }
160
161    /// Create a new page in the PDF document.
162    ///
163    /// The page will be `width` x `height` points large, and the
164    /// actual content of the page will be created by the function
165    /// `render_contents` by applying drawing methods on the Canvas.
166    pub fn render_page<F>(
167        &mut self,
168        width: f32,
169        height: f32,
170        render_contents: F,
171    ) -> io::Result<()>
172    where
173        F: FnOnce(&mut Canvas) -> io::Result<()>,
174    {
175        let (contents_object_id, content_length, fonts, outline_items) = self
176            .write_new_object(move |contents_object_id, pdf| {
177                // Guess the ID of the next object. (We’ll assert it below.)
178                writeln!(
179                    pdf.output,
180                    "<< /Length {} 0 R >>\n\
181                     stream",
182                    contents_object_id + 1,
183                )?;
184
185                let start = pdf.tell()?;
186                writeln!(pdf.output, "/DeviceRGB cs /DeviceRGB CS")?;
187                let mut fonts = HashMap::new();
188                let mut outline_items = Vec::new();
189                render_contents(&mut Canvas::new(
190                    &mut pdf.output,
191                    &mut fonts,
192                    &mut outline_items,
193                ))?;
194                let end = pdf.tell()?;
195
196                writeln!(pdf.output, "endstream")?;
197                Ok((contents_object_id, end - start, fonts, outline_items))
198            })?;
199        self.write_new_object(|length_object_id, pdf| {
200            assert!(length_object_id == contents_object_id + 1);
201            writeln!(pdf.output, "{}", content_length)
202        })?;
203
204        let mut font_oids = NamedRefs::new(fonts.len());
205        for (src, r) in fonts {
206            if let Some(&object_id) = self.all_font_object_ids.get(&src) {
207                font_oids.insert(r, object_id);
208            } else {
209                let object_id = src.write_object(self)?;
210                font_oids.insert(r, object_id);
211                self.all_font_object_ids.insert(src, object_id);
212            }
213        }
214        let page_oid = self.write_page_dict(
215            contents_object_id,
216            width,
217            height,
218            font_oids,
219        )?;
220        // Take the outline_items from this page, mark them with the page ref,
221        // and save them for the document outline.
222        for mut item in outline_items {
223            item.set_page(page_oid);
224            self.outline_items.push(item);
225        }
226        self.page_objects_ids.push(page_oid);
227        Ok(())
228    }
229
230    fn write_page_dict(
231        &mut self,
232        content_oid: usize,
233        width: f32,
234        height: f32,
235        font_oids: NamedRefs,
236    ) -> io::Result<usize> {
237        self.write_new_object(|page_oid, pdf| {
238            writeln!(
239                pdf.output,
240                "<< /Type /Page\n   \
241                 /Parent {parent} 0 R\n   \
242                 /Resources << /Font << {fonts}>> >>\n   \
243                 /MediaBox [ 0 0 {width} {height} ]\n   \
244                 /Contents {c_oid} 0 R\n\
245                 >>",
246                parent = PAGES_OBJECT_ID,
247                fonts = font_oids,
248                width = width,
249                height = height,
250                c_oid = content_oid,
251            )
252            .map(|_| page_oid)
253        })
254    }
255
256    fn write_new_object<F, T>(&mut self, write_content: F) -> io::Result<T>
257    where
258        F: FnOnce(usize, &mut Pdf) -> io::Result<T>,
259    {
260        let id = self.object_offsets.len();
261        let (result, offset) =
262            self.write_object(id, |pdf| write_content(id, pdf))?;
263        self.object_offsets.push(offset);
264        Ok(result)
265    }
266
267    fn write_object_with_id<F, T>(
268        &mut self,
269        id: usize,
270        write_content: F,
271    ) -> io::Result<T>
272    where
273        F: FnOnce(&mut Pdf) -> io::Result<T>,
274    {
275        assert!(self.object_offsets[id] == -1);
276        let (result, offset) = self.write_object(id, write_content)?;
277        self.object_offsets[id] = offset;
278        Ok(result)
279    }
280
281    fn write_object<F, T>(
282        &mut self,
283        id: usize,
284        write_content: F,
285    ) -> io::Result<(T, i64)>
286    where
287        F: FnOnce(&mut Pdf) -> io::Result<T>,
288    {
289        // `as i64` here would overflow for PDF files bigger than 2**63 bytes
290        let offset = self.tell()? as i64;
291        writeln!(self.output, "{} 0 obj", id)?;
292        let result = write_content(self)?;
293        writeln!(self.output, "endobj")?;
294        Ok((result, offset))
295    }
296
297    /// Write out the document trailer.
298    /// The trailer consists of the pages object, the root object,
299    /// the xref list, the trailer object and the startxref position.
300    pub fn finish(mut self) -> io::Result<()> {
301        self.write_object_with_id(PAGES_OBJECT_ID, |pdf| {
302            write!(
303                pdf.output,
304                "<< /Type /Pages\n   \
305                 /Count {}\n   ",
306                pdf.page_objects_ids.len()
307            )?;
308            write!(pdf.output, "/Kids [ ")?;
309            for id in &pdf.page_objects_ids {
310                write!(pdf.output, "{} 0 R ", id)?;
311            }
312            writeln!(pdf.output, "]\n>>")
313        })?;
314        let document_info_id = if !self.document_info.is_empty() {
315            let info = self.document_info.clone();
316            self.write_new_object(|page_object_id, pdf| {
317                write!(pdf.output, "<<")?;
318                for (key, value) in info {
319                    writeln!(pdf.output, " /{} ({})", key, value)?;
320                }
321                let now = Local::now().format("%Y%m%d%H%M%S%z").to_string();
322                write!(
323                    pdf.output,
324                    " /CreationDate (D:{now})\n \
325                     /ModDate (D:{now})",
326                    now = now,
327                )?;
328                writeln!(pdf.output, ">>")?;
329                Ok(Some(page_object_id))
330            })?
331        } else {
332            None
333        };
334
335        let outlines_id = self.write_outlines()?;
336
337        self.write_object_with_id(ROOT_OBJECT_ID, |pdf| {
338            writeln!(
339                pdf.output,
340                "<< /Type /Catalog\n   \
341                 /Pages {} 0 R",
342                PAGES_OBJECT_ID,
343            )?;
344            if let Some(outlines_id) = outlines_id {
345                writeln!(pdf.output, "/Outlines {} 0 R", outlines_id)?;
346            }
347            writeln!(pdf.output, ">>")
348        })?;
349        let startxref = self.tell()?;
350        writeln!(
351            self.output,
352            "xref\n\
353             0 {}\n\
354             0000000000 65535 f ",
355            self.object_offsets.len(),
356        )?;
357        // Object 0 (above) is special
358        // Use [1..] to skip object 0 in self.object_offsets.
359        for &offset in &self.object_offsets[1..] {
360            assert!(offset >= 0);
361            writeln!(self.output, "{:010} 00000 n ", offset)?;
362        }
363        writeln!(
364            self.output,
365            "trailer\n\
366             << /Size {size}\n   \
367             /Root {root} 0 R",
368            size = self.object_offsets.len(),
369            root = ROOT_OBJECT_ID,
370        )?;
371        if let Some(id) = document_info_id {
372            writeln!(self.output, "   /Info {} 0 R", id)?;
373        }
374        writeln!(
375            self.output,
376            ">>\n\
377             startxref\n\
378             {}\n\
379             %%EOF",
380            startxref,
381        )
382    }
383
384    fn write_outlines(&mut self) -> io::Result<Option<usize>> {
385        if self.outline_items.is_empty() {
386            return Ok(None);
387        }
388
389        let parent_id = self.object_offsets.len();
390        self.object_offsets.push(-1);
391        let count = self.outline_items.len();
392        let mut first_id = 0;
393        let mut last_id = 0;
394        let items = self.outline_items.clone();
395        for (i, item) in items.iter().enumerate() {
396            let (is_first, is_last) = (i == 0, i == count - 1);
397            let id = self.write_new_object(|object_id, pdf| {
398                item.write_dictionary(
399                    &mut pdf.output,
400                    parent_id,
401                    if is_first { None } else { Some(object_id - 1) },
402                    if is_last { None } else { Some(object_id + 1) },
403                )
404                .and(Ok(object_id))
405            })?;
406            if is_first {
407                first_id = id;
408            }
409            if is_last {
410                last_id = id;
411            }
412        }
413        self.write_object_with_id(parent_id, |pdf| {
414            writeln!(
415                pdf.output,
416                "<< /Type /Outlines\n   \
417                 /First {first} 0 R\n   \
418                 /Last {last} 0 R\n   \
419                 /Count {count}\n\
420                 >>",
421                last = last_id,
422                first = first_id,
423                count = count,
424            )
425        })?;
426        Ok(Some(parent_id))
427    }
428}
429
430struct NamedRefs {
431    oids: HashMap<FontRef, usize>,
432}
433
434impl NamedRefs {
435    fn new(capacity: usize) -> Self {
436        NamedRefs {
437            oids: HashMap::with_capacity(capacity),
438        }
439    }
440    fn insert(&mut self, name: FontRef, oid: usize) -> Option<usize> {
441        self.oids.insert(name, oid)
442    }
443}
444
445impl fmt::Display for NamedRefs {
446    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
447        for (name, id) in &self.oids {
448            write!(f, "{} {} 0 R ", name, id)?;
449        }
450        Ok(())
451    }
452}