Skip to main content

pdf_min/
writer.rs

1use crate::*;
2
3///
4pub struct Writer {
5    ///
6    pub b: BasicPdfWriter,
7    ///
8    pub p: Page,
9    ///
10    pub fonts: Vec<Box<dyn Font>>,
11    ///
12    pub cur_font: usize, // index into fonts
13    ///
14    pub font_size: i16,
15    ///
16    pub sup: i16,
17    ///
18    pub mode: Mode,
19    ///
20    pub title: String,
21    ///
22    pub pages: Vec<Page>,
23    ///
24    pub new_page: bool,
25
26    ///
27    pub line_pad: i16,
28    ///
29    pub margin_left: i16,
30    ///
31    pub margin_right: i16,
32    ///
33    pub margin_top: i16,
34    ///
35    pub margin_bottom: i16,
36    ///
37    pub page_width: i16,
38    ///
39    pub page_height: i16,
40    ///
41    pub line_used: i16,
42    ///
43    pub line: Vec<Item>,
44    ///
45    pub max_font_size: i16,
46    ///
47    pub center: i16,
48}
49
50impl Default for Writer {
51    fn default() -> Self {
52        let mut x = Self {
53            mode: Mode::Normal,
54            title: String::new(),
55            b: BasicPdfWriter::default(),
56            fonts: Vec::new(),
57            cur_font: 0,
58            font_size: 10,
59            sup: 0,
60            p: Page::default(),
61            pages: Vec::new(),
62            new_page: true,
63
64            page_width: 400,
65            page_height: 600,
66            line_pad: 4,
67            margin_left: 20,
68            margin_right: 20,
69            margin_top: 20,
70            margin_bottom: 20,
71            line_used: 0,
72            line: Vec::new(),
73            max_font_size: 0,
74            center: 0,
75        };
76        for _ in 0..4 {
77            x.fonts.push(Box::<StandardFont>::default());
78        }
79        x
80    }
81}
82
83impl Writer {
84    ///
85    fn init_page(&mut self) {
86        self.p.width = self.page_width;
87        self.p.height = self.page_height;
88        self.p.goto(
89            self.margin_left,
90            self.p.height - self.font_size - self.margin_top,
91        );
92        if self.sup != 0 {
93            self.p.set_sup(self.sup);
94        }
95        self.new_page = false;
96    }
97
98    ///
99    pub fn save_page(&mut self) {
100        let p = std::mem::take(&mut self.p);
101        self.pages.push(p);
102        self.new_page = true;
103    }
104
105    ///
106    fn init_font(&mut self, x: usize) {
107        let f = &mut self.fonts[x];
108        f.init(&mut self.b, HELVETICA[x]);
109    }
110
111    ///
112    fn width(&self, _c: char) -> u64 {
113        // Ought to take some account of upper/lower case.
114        // This is rather preliminary.
115        if (self.cur_font & 1) == 1 {
116            550
117        } else {
118            500
119        }
120    }
121
122    ///
123    fn line_len(&self) -> i16 {
124        self.page_width - self.margin_left - self.margin_right
125    }
126
127    ///
128    fn wrap_text(&mut self, s: &str) {
129        if self.new_page {
130            self.init_page();
131        }
132
133        let mut w = 0;
134        for c in s.chars() {
135            w += self.width(c);
136        }
137        let w = (w * self.font_size as u64 / 1000) as i16;
138
139        if self.line_used + w > self.line_len() {
140            self.output_line();
141            if s == " " {
142                return;
143            }
144        }
145        self.line_used += w;
146        self.init_font(self.cur_font);
147        if self.font_size > self.max_font_size {
148            self.max_font_size = self.font_size;
149        }
150        self.line
151            .push(Item::Text(s.to_string(), self.cur_font, self.font_size));
152    }
153
154    ///
155    pub fn output_line(&mut self) {
156        if self.new_page {
157            self.init_page();
158        } else {
159            let cx = if self.center == 1 {
160                (self.line_len() - self.line_used) / 2
161            } else {
162                0
163            };
164            let h = self.max_font_size + self.line_pad;
165            if self.p.y >= h + self.margin_bottom {
166                self.p.td(self.margin_left + cx - self.p.x, -h);
167            } else {
168                self.save_page();
169                self.init_page();
170            }
171        }
172        for item in &self.line {
173            match item {
174                Item::Text(s, f, x) => {
175                    let fp = &*self.fonts[*f];
176                    self.p.text(fp, *x, s);
177                }
178                Item::Sup(x) => {
179                    self.p.set_sup(*x);
180                }
181            }
182        }
183        self.line.clear();
184        self.line_used = 0;
185        self.max_font_size = 0;
186    }
187
188    ///
189    pub fn text(&mut self, s: &str) {
190        match self.mode {
191            Mode::Normal => {
192                self.wrap_text(s);
193            }
194            Mode::Title => {
195                self.title += s;
196            }
197            Mode::Head => {}
198        }
199    }
200
201    ///
202    pub fn space(&mut self) {
203        self.text(" ");
204    }
205
206    ///
207    pub fn set_sup(&mut self, sup: i16) {
208        self.line.push(Item::Sup(sup));
209        self.sup = sup;
210    }
211
212    ///
213    pub fn finish(&mut self) {
214        self.output_line();
215        self.init_font(0);
216        self.save_page();
217        let n = self.pages.len();
218        let mut pnum = 1;
219        let font_size = 8;
220        for p in &mut self.pages {
221            p.goto(self.margin_left, self.line_pad);
222            p.text(
223                &*self.fonts[0],
224                font_size,
225                &format!("Page {} of {}", pnum, n),
226            );
227            p.finish();
228            pnum += 1;
229        }
230        self.b.finish(&self.pages, self.title.as_bytes());
231    }
232}
233
234///
235#[derive(Clone, Copy)]
236pub enum Mode {
237    ///
238    Normal,
239    ///
240    Head,
241    ///
242    Title,
243}
244
245/// Items that define a line of text.
246pub enum Item {
247    ///
248    Text(String, usize, i16),
249    ///
250    Sup(i16),
251}
252
253/// Convert byte slice into string.
254fn _tos(s: &[u8]) -> String {
255    std::str::from_utf8(s).unwrap().to_string()
256}
257
258/// Convert byte slice into string.
259fn tosl(s: &[u8]) -> &str {
260    std::str::from_utf8(s).unwrap()
261}
262
263/// Convert source html to PDF using Writer w.
264pub fn html(w: &mut Writer, source: &[u8]) {
265    let mut p = Parser::new(source);
266    p.read_token();
267    html_inner(w, &mut p, b"");
268}
269
270#[derive(Debug)]
271enum Token {
272    Text,
273    Tag,
274    WhiteSpace,
275    Eof,
276}
277
278struct Parser<'a> {
279    source: &'a [u8],
280    position: usize,
281    token_start: usize,
282    token_end: usize,
283    end_tag: bool,
284    token: Token,
285}
286
287impl<'a> Parser<'a> {
288    fn new(source: &'a [u8]) -> Self {
289        Self {
290            source,
291            position: 0,
292            token_start: 0,
293            token_end: 0,
294            end_tag: false,
295            token: Token::Eof,
296        }
297    }
298
299    fn tvalue(&self) -> &'a [u8] {
300        &self.source[self.token_start..self.token_end]
301    }
302
303    fn next(&mut self) -> u8 {
304        if self.position == self.source.len() {
305            0
306        } else {
307            let c = self.source[self.position];
308            self.position += 1;
309            c
310        }
311    }
312
313    fn read_token(&mut self) {
314        let c = self.next();
315        if c == 0 {
316            self.token = Token::Eof;
317        } else if c == b' ' || c == b'\n' {
318            self.token = Token::WhiteSpace;
319            loop {
320                let c = self.next();
321                if c != b' ' || c != b'\n' {
322                    if c != 0 {
323                        self.position -= 1;
324                    }
325                    break;
326                }
327            }
328        } else if c == b'<' {
329            // e.g. <h1 name=x> or </h1>s
330            // Find tag name, then read to end of tag.
331
332            self.token = Token::Tag;
333            self.token_start = self.position;
334            self.end_tag = false;
335            let mut c = self.next();
336            if c == b'/' {
337                self.end_tag = true;
338                self.token_start = self.position;
339                c = self.next();
340            }
341            let mut got_end = false;
342            loop {
343                if c == b' ' {
344                    if !got_end {
345                        self.token_end = self.position;
346                        got_end = true;
347                    }
348                } else if c == b'>' {
349                    if !got_end {
350                        self.token_end = self.position - 1;
351                        break;
352                    }
353                } else if c == 0 {
354                    self.token = Token::Eof;
355                    break;
356                }
357                c = self.next();
358            }
359        } else {
360            self.token = Token::Text;
361            self.token_start = self.position - 1;
362            let mut c = self.next();
363            loop {
364                if c == b'<' || c == b' ' || c == b'\n' {
365                    self.position -= 1;
366                    self.token_end = self.position;
367                    break;
368                } else if c == 0 {
369                    self.token_end = self.position;
370                    break;
371                }
372                c = self.next();
373            }
374        }
375    }
376}
377
378fn html_inner(w: &mut Writer, p: &mut Parser, endtag: &[u8]) {
379    loop {
380        match p.token {
381            Token::Eof => {
382                return;
383            }
384            Token::WhiteSpace => {
385                w.space();
386                p.read_token();
387            }
388            Token::Text => {
389                let s = tosl(p.tvalue());
390                let s = &html_escape::decode_html_entities(s);
391                w.text(s);
392                p.read_token();
393            }
394            Token::Tag => {
395                let tag = p.tvalue();
396                if p.end_tag {
397                    if tag == endtag {
398                        p.read_token();
399                    }
400                    return;
401                } else if tag == b"p" && tag == endtag {
402                    return;
403                }
404                p.read_token();
405                if tag == b"br" || tag == b"br/" {
406                    w.output_line();
407                } else {
408                    let save_mode = w.mode;
409                    let save_font = w.cur_font;
410                    let save_font_size = w.font_size;
411                    let mut save: i16 = 0;
412                    match tag {
413                        b"p" => w.output_line(),
414                        b"h1" => {
415                            w.font_size = 14;
416                            w.output_line();
417                            save = w.center;
418                            w.center = 1;
419                        }
420                        b"b" => w.cur_font |= 1,
421                        b"i" => w.cur_font |= 2,
422                        b"title" => w.mode = Mode::Title,
423                        b"html" | b"head" => w.mode = Mode::Head,
424                        b"body" => w.mode = Mode::Normal,
425                        b"sup" => {
426                            save = w.sup;
427                            w.set_sup(w.font_size / 2);
428                        }
429                        b"sub" => {
430                            save = w.sup;
431                            w.set_sup(-w.font_size / 2);
432                        }
433                        _ => {}
434                    }
435                    html_inner(w, p, tag);
436                    w.mode = save_mode;
437                    w.font_size = save_font_size;
438                    w.cur_font = save_font;
439                    match tag {
440                        b"sup" | b"sub" => w.set_sup(save),
441                        b"h1" => {
442                            w.output_line();
443                            w.center = save;
444                        }
445                        _ => {}
446                    }
447                }
448            }
449        }
450    }
451}