summaryrefslogtreecommitdiff
path: root/src/pdf.rs
blob: 7cef65c5874036b48ada9625d2993f4b651f4e5b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! Writing of documents in the _PDF_ format.

use std::io::{self, Write};
use crate::doc::Document;
use pdf::{PdfWriter, Id, Rect, Version, DocumentCatalog, PageTree,
          Page, PageData, Resource, font::Type1Font, Text, Trailer};


/// A type that is a sink for documents that can be written in the _PDF_ format.
pub trait WritePdf {
    /// Write a document into self, returning how many bytes were written.
    fn write_pdf(&mut self, doc: &Document) -> io::Result<usize>;
}

impl<W: Write> WritePdf for W {
    fn write_pdf(&mut self, doc: &Document) -> io::Result<usize> {
        let mut writer = PdfWriter::new(self);

        // Calculate unique id's for everything
        let catalog_id: Id = 1;

        let page_tree_id = catalog_id + 1;
        let pages_start = page_tree_id + 1;
        let pages_end = pages_start + doc.pages.len() as Id;

        let font_start = pages_end;
        let font_end = font_start + 1;

        let content_start = font_end;
        let content_end = content_start
            + doc.pages.iter().flat_map(|p| p.contents.iter()).count() as Id;

        writer.write_header(&Version::new(1, 7))?;

        // The document catalog
        writer.write_obj(catalog_id, &DocumentCatalog {
            page_tree: page_tree_id,
        })?;

        // Root page tree
        writer.write_obj(page_tree_id, &PageTree {
            parent: None,
            kids: (pages_start .. pages_end).collect(),
            data: PageData {
                resources: Some(vec![Resource::Font { nr: 1, id: font_start }]),
                .. PageData::none()
            },
        })?;

        // The page objects
        let mut id = pages_start;
        for page in &doc.pages {
            let width = page.size[0].to_points();
            let height = page.size[1].to_points();

            writer.write_obj(id, &Page {
                parent: page_tree_id,
                data: PageData {
                    media_box: Some(Rect::new(0.0, 0.0, width, height)),
                    contents: Some((content_start .. content_end).collect()),
                    .. PageData::none()
                },
            })?;

            id += 1;
        }

        // The resources, currently only one hardcoded font
        writer.write_obj(font_start, &Type1Font {
            base_font: "Helvetica".to_owned(),
        })?;

        // The page contents
        let mut id = content_start;
        for page in &doc.pages {
            for content in &page.contents {
                writer.write_obj(id, &Text::new()
                    .set_font(1, 13.0)
                    .move_pos(108.0, 734.0)
                    .write_text(content.0.as_bytes())
                    .to_stream()
                )?;
                id += 1;
            }
        }

        // Cross-reference table
        writer.write_xref_table()?;

        // Trailer
        writer.write_trailer(&Trailer {
            root: catalog_id,
        })?;

        Ok(writer.written())
    }
}


#[cfg(test)]
mod pdf_tests {
    use super::*;
    use crate::parsing::ParseTree;
    use crate::doc::Generate;

    /// Create a pdf with a name from the source code.
    fn test(name: &str, src: &str) {
        let mut file = std::fs::File::create(format!("../target/{}", name)).unwrap();
        let doc = src.parse_tree().unwrap().generate().unwrap();
        file.write_pdf(&doc).unwrap();
    }

    #[test]
    fn pdf_simple() {
        test("write-simple.pdf", "This is an example of a sentence.");
        test("write-break.pdf","
             Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
             diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
             voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd
             gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor
             sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut
             labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et
             justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est
             Lorem ipsum dolor sit amet.
        ");
        test("write-parens.pdf", "Text enclosed in (parenthesis), like ) or ( should work!");
    }
}