pdfpdf/
lib.rs

1//! A Pretty Darn Fast library for creating PDF files. Currently, only simple vector graphics and simple text are supported.
2//!
3//!
4
5//! # Example
6//!
7//! ```
8//! use pdfpdf::{Color, Pdf};
9//!
10//! Pdf::new()
11//!     .add_page(180.0, 240.0)
12//!     .set_color(&Color::rgb(0, 0, 248))
13//!     .draw_circle(90.0, 120.0, 50.0)
14//!     .write_to("example.pdf")
15//!     .expect("Failed to write to file");
16//! ```
17//!
18//! To use this library you need to add it as a dependency in your
19//! `Cargo.toml`:
20//!
21//! ```toml
22//! [dependencies]
23//! pdfpdf = "0.2"
24//! ```
25//!
26//! More working examples can be found in [here]
27//! (https://github.com/saethlin/pdfpdf/tree/master/examples).
28#![deny(missing_docs)]
29
30#[macro_use]
31extern crate lazy_static;
32extern crate deflate;
33extern crate num;
34
35use num::NumCast;
36use std::fs::File;
37use std::io;
38
39mod graphicsstate;
40mod fonts;
41mod text;
42pub use fonts::Font;
43pub use graphicsstate::{Color, Matrix};
44pub use text::Alignment;
45
46// Represents a PDF internal object
47struct PdfObject {
48    offset: usize,
49    id: usize,
50    is_page: bool,
51}
52
53/// The top-level struct that represents a (partially) in-memory PDF file
54pub struct Pdf {
55    buffer: Vec<u8>,
56    page_buffer: Vec<u8>,
57    objects: Vec<PdfObject>,
58    width: f64,
59    height: f64,
60    fonts: Vec<fonts::Font>,
61    font_size: f64,
62    compress: bool,
63}
64
65impl Pdf {
66    /// Create a new blank PDF document
67    #[inline]
68    pub fn new() -> Self {
69        let mut buffer = Vec::new();
70        buffer.extend(b"%PDF-1.7\n%\xB5\xED\xAE\xFB\n");
71        Pdf {
72            buffer: buffer,
73            page_buffer: Vec::new(),
74            objects: vec![
75                PdfObject {
76                    offset: 0,
77                    id: 1,
78                    is_page: false,
79                },
80                PdfObject {
81                    offset: 0,
82                    id: 2,
83                    is_page: false,
84                },
85            ],
86            width: 400.0,
87            height: 400.0,
88            fonts: vec![Font::Helvetica],
89            font_size: 12.0,
90            compress: true,
91        }
92    }
93
94    /// Create a new blank PDF document without compression
95    #[inline]
96    pub fn new_uncompressed() -> Self {
97        let mut this = Pdf::new();
98        this.compress = false;
99        this
100    }
101
102    /// Move then pen, starting a new path
103    #[inline]
104    fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
105        self.page_buffer.extend(
106            format!("{:.2} {:.2} m ", x, y).bytes(),
107        );
108        self
109    }
110
111    /// Draw a line from the current location
112    #[inline]
113    fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
114        self.page_buffer.extend(
115            format!("{:.2} {:.2} l ", x, y).bytes(),
116        );
117        self
118    }
119
120    // Draw a cubic Bézier curve
121    #[inline]
122    fn curve_to(
123        &mut self,
124        (x1, y1): (f64, f64),
125        (x2, y2): (f64, f64),
126        (x3, y3): (f64, f64),
127    ) -> &mut Self {
128        self.page_buffer.extend(
129            format!(
130                "{:.2} {:.2} {:.2} {:.2} {:.2} {:.2} c\n",
131                x1,
132                y1,
133                x2,
134                y2,
135                x3,
136                y3
137            ).bytes(),
138        );
139        self
140    }
141
142    /// Set the current line width
143    #[inline]
144    pub fn set_line_width<N: NumCast>(&mut self, width: N) -> &mut Self {
145        self.page_buffer.extend(
146            format!(
147                "{:.2} w\n",
148                width.to_f64().unwrap()
149            ).bytes(),
150        );
151        self
152    }
153
154    /// Set the color for all subsequent drawing operations
155    #[inline]
156    pub fn set_color(&mut self, color: &Color) -> &mut Self {
157        let norm = |color| color as f64 / 255.0;
158        self.page_buffer.extend(
159            format!(
160                "{:.2} {:.2} {:.2} SC\n",
161                norm(color.red),
162                norm(color.green),
163                norm(color.blue),
164            ).bytes(),
165        );
166        self.page_buffer.extend(
167            format!(
168                "{:.2} {:.2} {:.2} rg\n",
169                norm(color.red),
170                norm(color.green),
171                norm(color.blue),
172            ).bytes(),
173        );
174
175        self
176    }
177
178    /// Apply a coordinate transformation to all subsequent drawing calls
179    /// Consecutive applications of this function are cumulative
180    #[inline]
181    pub fn transform(&mut self, m: Matrix) -> &mut Self {
182        self.page_buffer.extend(format!("{} cm\n", m).bytes());
183        self
184    }
185
186    /// Draw a circle with the current drawing configuration,
187    /// based on http://spencermortensen.com/articles/bezier-circle/
188    #[inline]
189    pub fn draw_circle<N1: NumCast, N2: NumCast, N3: NumCast>(
190        &mut self,
191        x: N1,
192        y: N2,
193        radius: N3,
194    ) -> &mut Self {
195        let x = x.to_f64().unwrap();
196        let y = y.to_f64().unwrap();
197        let radius = radius.to_f64().unwrap();
198        let top = y - radius;
199        let bottom = y + radius;
200        let left = x - radius;
201        let right = x + radius;
202        let c = 0.551915024494;
203        let leftp = x - (radius * c);
204        let rightp = x + (radius * c);
205        let topp = y - (radius * c);
206        let bottomp = y + (radius * c);
207        self.move_to(x, top);
208        self.curve_to((leftp, top), (left, topp), (left, y));
209        self.curve_to((left, bottomp), (leftp, bottom), (x, bottom));
210        self.curve_to((rightp, bottom), (right, bottomp), (right, y));
211        self.curve_to((right, topp), (rightp, top), (x, top));
212        self.page_buffer.extend_from_slice(b"S\n");
213        self
214    }
215
216    /// Draw a line between all these points in the order they appear
217    #[inline]
218    pub fn draw_line<I, N: NumCast>(&mut self, mut points: I) -> &mut Self
219    where
220        I: Iterator<Item = (N, N)>,
221    {
222        if let Some((x, y)) = points.next() {
223            let x = x.to_f64().unwrap();
224            let y = y.to_f64().unwrap();
225            self.move_to(x, y);
226            for (x, y) in points {
227                let x = x.to_f64().unwrap();
228                let y = y.to_f64().unwrap();
229                self.line_to(x, y);
230            }
231        }
232        self.page_buffer.extend_from_slice(b"S\n");
233        self
234    }
235
236    /// Draw a rectangle that extends from x1, y1 to x2, y2
237    #[inline]
238    pub fn draw_rectangle_filled<N1: NumCast, N2: NumCast, N3: NumCast, N4: NumCast>(
239        &mut self,
240        x: N1,
241        y: N2,
242        width: N3,
243        height: N4,
244    ) -> &mut Self {
245        self.page_buffer.extend(
246            format!(
247                "{:.2} {:.2} {:.2} {:.2} re\n",
248                x.to_f64().unwrap(),
249                y.to_f64().unwrap(),
250                width.to_f64().unwrap(),
251                height.to_f64().unwrap()
252            ).bytes(),
253        );
254        // Fill path using Nonzero Winding Number Rule
255        self.page_buffer.extend_from_slice(b"f\n");
256        self
257    }
258
259    #[inline]
260    /// Set the font for all subsequent drawing calls
261    pub fn font<N: NumCast>(&mut self, font: Font, size: N) -> &mut Self {
262        self.fonts.push(font);
263        self.font_size = size.to_f64().unwrap();
264        self
265    }
266
267    /// Draw text at a given location with the current settings
268    #[inline]
269    pub fn draw_text<N1: NumCast, N2: NumCast>(
270        &mut self,
271        x: N1,
272        y: N2,
273        alignment: Alignment,
274        text: &str,
275    ) -> &mut Self {
276
277        let x = x.to_f64().unwrap();
278        let y = y.to_f64().unwrap();
279        let widths = &fonts::GLYPH_WIDTHS[self.fonts.iter().last().unwrap()];
280        let height = self.font_size;
281
282        self.page_buffer.extend(
283            format!("BT\n/F{} {} Tf\n", self.fonts.len() - 1, self.font_size).bytes(),
284        );
285
286        let num_lines = text.split('\n').count();
287        for (l, line) in text.split('\n').enumerate() {
288            let line_width = line.chars()
289                .filter(|c| *c != '\n')
290                .map(|c| *widths.get(&c).unwrap_or(&1.0))
291                .sum::<f64>() * self.font_size;
292
293            let (line_x, line_y) = match alignment {
294                Alignment::TopLeft => (x, y - height * (l as f64 + 1.0)),
295                Alignment::TopRight => (x - line_width, y - height * (l as f64 + 1.0)),
296                Alignment::TopCenter => (x - line_width / 2.0, y - height * (l as f64 + 1.0)),
297                Alignment::CenterLeft => (
298                    x,
299                    (y - height / 3.0) -
300                        (l as f64 - (num_lines as f64 - 1.0) / 2.0) *
301                            height * 1.25,
302                ),
303                Alignment::CenterRight => (
304                    x - line_width,
305                    (y - height / 3.0) -
306                        (l as f64 - (num_lines as f64 - 1.0) / 2.0) *
307                            height * 1.25,
308                ),
309                Alignment::CenterCenter => (
310                    x - line_width / 2.0,
311                    (y - height / 3.0) -
312                        (l as f64 - (num_lines as f64 - 1.0) / 2.0) *
313                            height * 1.25,
314                ),
315                Alignment::BottomLeft => (x, y + (num_lines - l - 1) as f64 * 1.25 * height),
316                Alignment::BottomRight => (
317                    x - line_width,
318                    y + (num_lines - l - 1) as f64 * 1.25 * height,
319                ),
320                Alignment::BottomCenter => (
321                    x - line_width / 2.0,
322                    y + (num_lines - l - 1) as f64 * 1.25 * height,
323                ),
324            };
325
326            self.page_buffer.extend(
327                format!(
328                    "1 0 0 1 {} {} Tm (",
329                    line_x,
330                    line_y
331                ).bytes(),
332            );
333            for c in line.chars() {
334                let data = format!("\\{:o}", c as u32);
335                self.page_buffer.extend(data.bytes());
336            }
337            self.page_buffer.extend(b") Tj\n");
338        }
339        self.page_buffer.extend(b"ET\n");
340        self
341    }
342
343    // TODO: test with multi-page documents
344    /// Move to a new page in the PDF document
345    #[inline]
346    pub fn add_page<N1: NumCast, N2: NumCast>(&mut self, width: N1, height: N2) -> &mut Self {
347        // Compress and write out the previous page if it exists
348        if !self.page_buffer.is_empty() {
349            self.flush_page();
350        }
351
352        self.page_buffer.extend(
353            "/DeviceRGB cs /DeviceRGB CS\n".bytes(),
354        );
355        self.width = width.to_f64().unwrap();
356        self.height = height.to_f64().unwrap();
357        self
358    }
359
360    /// Dump a page out to disk
361    fn flush_page(&mut self) {
362        // Write out the data stream for this page
363        let obj_id = self.objects.iter().map(|o| o.id).max().unwrap() + 1;
364        self.objects.push(PdfObject {
365            offset: self.buffer.len(),
366            id: obj_id,
367            is_page: false,
368        });
369
370        self.buffer.extend(format!("{} 0 obj\n", obj_id).bytes());
371        if self.compress {
372            let (compressed, rounds) = compress(self.page_buffer.clone());
373            self.buffer.extend(
374                format!(
375                    "<</Length {} /Filter [{}]>>\nstream\n",
376                    compressed.len(),
377                    "/FlateDecode ".repeat(rounds)
378                ).bytes(),
379            );
380            self.buffer.extend(compressed.iter());
381            self.buffer.extend("endstream\nendobj\n".bytes())
382        } else {
383            self.buffer.extend(
384                format!("<</Length {}>>\nstream\n", self.page_buffer.len()).bytes(),
385            );
386            self.buffer.extend(self.page_buffer.iter());
387            self.buffer.extend("endstream\nendobj\n".bytes());
388        }
389        self.page_buffer.clear();
390
391        // Write out the page object
392        let obj_id = self.objects.iter().map(|o| o.id).max().unwrap() + 1;
393        self.objects.push(PdfObject {
394            offset: self.buffer.len(),
395            id: obj_id,
396            is_page: true,
397        });
398        self.buffer.extend(format!("{} 0 obj\n", obj_id).bytes());
399        self.buffer.extend_from_slice(b"<</Type /Page\n");
400        self.buffer.extend_from_slice(b"/Parent 2 0 R\n");
401        // TODO: Temporary restricted fonts
402        for (f, font) in self.fonts.iter().enumerate() {
403            self.buffer.extend(
404            format!("/Resources << /Font << /F{} << /Type /Font /Subtype /Type1 /BaseFont /{:?} /Encoding /WinAnsiEncoding>> >> >>\n", f, font).bytes(),
405            );
406        }
407
408        self.buffer.extend(
409            format!("/MediaBox [0 0 {} {}]\n", self.width, self.height).bytes(),
410        );
411        self.buffer.extend(
412            format!("/Contents {} 0 R >>\n", obj_id - 1)
413                .bytes(),
414        );
415
416        self.buffer.extend("endobj\n".bytes());
417        self.fonts.clear();
418    }
419
420    /// Write the in-memory PDF representation to disk
421    pub fn write_to(&mut self, filename: &str) -> io::Result<()> {
422        use std::io::Write;
423
424        if !self.page_buffer.is_empty() {
425            self.flush_page();
426        }
427
428        // Write out the page tree object
429        self.objects[1].offset = self.buffer.len();
430        self.buffer.extend_from_slice(b"2 0 obj\n");
431        self.buffer.extend_from_slice(b"<</Type /Pages\n");
432        self.buffer.extend(
433            format!(
434                "/Count {}\n",
435                self.objects.iter().filter(|o| o.is_page).count()
436            ).bytes(),
437        );
438        self.buffer.extend_from_slice(b"/Kids [");
439        for obj in self.objects.iter().filter(|obj| obj.is_page) {
440            self.buffer.extend(format!("{} 0 R ", obj.id).bytes());
441        }
442        self.buffer.pop();
443        self.buffer.extend_from_slice(b"]>>\nendobj\n");
444
445        // Write out the catalog dictionary object
446        self.objects[0].offset = self.buffer.len();
447        self.buffer.extend_from_slice(
448            b"1 0 obj\n<</Type /Catalog\n/Pages 2 0 R>>\nendobj\n",
449        );
450
451        // Write the cross-reference table
452        let startxref = self.buffer.len();
453        self.buffer.extend_from_slice(b"xref\n");
454        self.buffer.extend(
455            format!("0 {}\n", self.objects.len() + 1).bytes(),
456        );
457        self.buffer.extend_from_slice(b"0000000000 65535 f \n");
458        self.objects.sort_by(|a, b| a.id.cmp(&b.id));
459
460        for obj in &self.objects {
461            self.buffer.extend(
462                format!("{:010} 00000 f \n", obj.offset).bytes(),
463            );
464        }
465
466        // Write the document trailer
467        self.buffer.extend_from_slice(b"trailer\n");
468        self.buffer.extend(
469            format!("<</Size {}\n", self.objects.len())
470                .bytes(),
471        );
472        self.buffer.extend_from_slice(b"/Root 1 0 R>>\n");
473
474        // Write the offset to the xref table
475        self.buffer.extend(
476            format!("startxref\n{}\n", startxref).bytes(),
477        );
478
479        // Write the PDF EOF
480        self.buffer.extend_from_slice(b"%%EOF");
481
482        File::create(filename)?.write_all(self.buffer.as_slice())
483    }
484}
485
486fn compress(input: Vec<u8>) -> (Vec<u8>, usize) {
487    let mut compressed = input;
488    let mut rounds = 0;
489    loop {
490        let another = deflate::deflate_bytes_zlib(compressed.as_slice());
491        if another.len() < compressed.len() {
492            compressed = another;
493            rounds += 1;
494        } else {
495            break;
496        }
497    }
498    (compressed, rounds)
499}