Skip to main content

pdf_min/
writer.rs

1use crate::*;
2use pstd::collections::BTreeMap;
3
4/// Writer - has support for wrapping text, page layout, fonts, etc.
5pub struct Writer {
6    /// Underlying Basic Writer
7    pub b: BasicPdfWriter,
8    /// Current Page
9    pub p: Page,
10    /// List of fonts
11    pub fonts: FontFamily,
12    /// Index into fonts
13    pub cur_font: usize,
14    /// Current font size, default is 10
15    pub font_size: Px,
16    /// Current sup ( raises text up off line ), use set_sup to adjust it
17    pub sup: Px,
18    /// Writing mode
19    pub mode: Mode,
20    /// PDF title
21    pub title: String,
22    /// List of Pages
23    pub pages: Vec<Page>,
24    /// Page is new ( not yet initialised )
25    pub new_page: bool,
26    /// Line padding ( space between lines ) default is 4
27    pub line_pad: Px,
28    /// Line margin ( left ), default is 20
29    pub margin_left: Px,
30    /// Line margin ( right ), default is 20
31    pub margin_right: Px,
32    /// Top margin, default is 20
33    pub margin_top: Px,
34    /// Bottom margin, default is 20
35    pub margin_bottom: Px,
36    /// Page width, default is 600
37    pub page_width: Px,
38    /// Page height, default is 800
39    pub page_height: Px,
40    /// Line used ( controls word-wrapping )
41    pub line_used: MPx,
42    /// Line items
43    pub line: Vec<Item>,
44    /// Largest font for current line
45    pub max_font_size: Px,
46    /// Default is zero, set to 1 to center output lines
47    pub center: bool,
48    /// For fetching fonts and images
49    pub fetcher: Option<Box<dyn Fetcher>>,
50    /// Cache of images
51    pub image_cache: BTreeMap<String,Image>,
52}
53
54impl Default for Writer {
55    fn default() -> Self {
56        Self {
57            mode: Mode::Normal,
58            title: String::new(),
59            b: BasicPdfWriter::default(),
60            fonts: helvetica(),
61            cur_font: 0,
62            font_size: 10,
63            sup: 0,
64            p: Page::default(),
65            pages: Vec::new(),
66            new_page: true,
67
68            page_width: 600,
69            page_height: 800,
70            line_pad: 4,
71            margin_left: 20,
72            margin_right: 20,
73            margin_top: 20,
74            margin_bottom: 20,
75            line_used: 0,
76            line: Vec::new(),
77            max_font_size: 0,
78            center: false,
79            fetcher: None,
80            image_cache: BTreeMap::new(),
81        }
82    }
83}
84
85impl Writer {
86    fn init_page(&mut self) {
87        self.p.width = self.page_width;
88        self.p.height = self.page_height;
89        self.p.goto(
90            self.margin_left,
91            self.p.height - self.font_size - self.margin_top,
92        );
93        if self.sup != 0 {
94            self.p.set_sup(self.sup);
95        }
96        self.new_page = false;
97    }
98
99    /// Completes current page.
100    pub fn save_page(&mut self) {
101        let p = std::mem::take(&mut self.p);
102        self.pages.push(p);
103        self.new_page = true;
104    }
105
106    fn init_font(&mut self, x: usize) {
107        let f = &mut self.fonts[x];
108        f.init(&mut self.b);
109    }
110
111    fn width(&self, c: char) -> MPx {
112        let f = &self.fonts[self.cur_font];
113        f.width(c) * self.font_size as MPx
114    }
115
116    fn line_len(&self) -> MPx {
117        ((self.page_width - self.margin_left - self.margin_right) as MPx) * 1000
118    }
119
120    fn wrap_init(&mut self) {
121        if self.new_page {
122            self.init_page();
123        }
124    }
125
126    fn wrap_text(&mut self, s: &str) {
127        self.wrap_init();
128
129        let mut width: MPx = 0;
130        for c in s.chars() {
131            width += self.width(c); // May depend on current font.
132        }
133
134        if self.line_used + width > self.line_len() {
135            self.output_line();
136            if s == " " {
137                return;
138            }
139        }
140        self.line_used += width;
141
142        self.init_font(self.cur_font);
143        if self.font_size > self.max_font_size {
144            self.max_font_size = self.font_size;
145        }
146
147        self.line.push(Item::Text(
148            s.to_string(),
149            self.cur_font,
150            self.font_size,
151            width,
152        ));
153    }
154
155    fn wrap_image(&mut self, im: Image, width: Px, scale: f32) {
156        self.wrap_init();
157
158        let width = (width as MPx) * 1000; // Convert width to MPx
159
160        if self.line_used + width > self.line_len() {
161            self.output_line();
162        }
163
164        self.line_used += width;
165        self.line.push(Item::Img(im, width, scale));
166    }
167
168    /// Outputs current line ( consisting of items ).
169    pub fn output_line(&mut self) {
170        if self.new_page {
171            self.init_page();
172        } else {
173            let cx = if self.center {
174                ((self.line_len() - self.line_used) / 2000) as Px
175            } else {
176                0
177            };
178            let h = self.max_font_size + self.line_pad;
179            if self.p.y >= h + self.margin_bottom {
180                self.p.td(self.margin_left + cx - self.p.x, -h);
181            } else {
182                self.save_page();
183                self.init_page();
184            }
185        }
186        let mut cx: MPx = 0;
187        for item in &self.line {
188            match item {
189                Item::Text(s, f, x, w) => {
190                    let fp = &*self.fonts[*f];
191                    self.p.text(fp, *x, s);
192                    cx += w;
193                }
194                Item::Sup(x) => {
195                    self.p.set_sup(*x);
196                }
197                Item::Img(im, width, scale) => {
198                    self.p.flush_text();
199                    let x: f32 = (self.p.x as f32) + (cx as f32 / 1000.0);
200                    let y = self.p.y as f32;
201                    im.draw(&mut self.p, x, y, *scale);
202                    cx += width;
203                    self.p.space(*width);
204                }
205            }
206        }
207        self.line.clear();
208        self.line_used = 0;
209        self.max_font_size = 0;
210    }
211
212    /// Writes word-wrapped text if mode is Normal, adds text to title if mode is Title.
213    pub fn text(&mut self, s: &str) {
214        match self.mode {
215            Mode::Normal => {
216                self.wrap_text(s);
217            }
218            Mode::Title => {
219                self.title += s;
220            }
221            Mode::Head => {}
222        }
223    }
224
225    fn fetch_image(&mut self, src: &str) -> Option<Image>
226    {
227        let mut result = None;
228        if let Some(im) = self.image_cache.get(src)
229        {
230           result = Some(im.clone());
231        }
232        else
233        {
234            let mut bf = std::mem::take(&mut self.fetcher);
235            if let Some(f) = &mut bf {
236                let im = f.image(self, src);
237                self.image_cache.insert( src.to_owned(), im.clone() );
238                result = Some(im);
239            }
240            self.fetcher = bf;
241        }
242        result
243    }   
244
245    /// Write image
246    pub fn image(&mut self, src: &str, awidth: Option<Px>, aheight: Option<Px>) {
247        if let Some(im) = self.fetch_image( src ) {
248            let mut width: Px = im.width;
249            let mut scale: f32 = 1.0;
250            if let Some(awidth) = awidth {
251                scale = awidth as f32 / width as f32;
252                width = awidth;
253            } else if let Some(aheight) = aheight {
254                scale = aheight as f32 / im.height as f32;
255                width = (width as f32 * scale) as Px;
256            }
257            self.wrap_image(im, width, scale);
258        } else {
259            self.text("error : no fetcher in pdf-min::Writer");
260        }
261    }
262
263    /// Adds a space to text.
264    pub fn space(&mut self) {
265        self.text(" ");
266    }
267
268    /// Sets sup
269    pub fn set_sup(&mut self, sup: Px) {
270        self.line.push(Item::Sup(sup));
271        self.sup = sup;
272    }
273
274    /// Flushes output line, writes page footers, saves pages, sets title, returns finished PDF as byte slice.
275    pub fn finish(&mut self) -> &[u8] {
276        self.output_line();
277        self.init_font(0);
278        self.save_page();
279        let n = self.pages.len();
280        let mut pnum = 1;
281        let font_size = 8;
282        #[allow(clippy::explicit_counter_loop)]
283        for p in &mut self.pages {
284            p.goto(self.margin_left, self.line_pad);
285            p.text(
286                &*self.fonts[0],
287                font_size,
288                &format!("Page {} of {}", pnum, n),
289            );
290            p.finish();
291            pnum += 1;
292        }
293        self.b.finish(&self.pages, self.title.as_bytes());
294        &self.b.b
295    }
296}
297
298/// Writing mode (for html)
299#[derive(Clone, Copy)]
300pub enum Mode {
301    /// Normal
302    Normal,
303    /// Text output is suppressed
304    Head,
305    /// Text is appended to title
306    Title,
307}
308
309/// Items that define a line of text.
310pub enum Item {
311    /// Text, font index, font size, width
312    Text(String, usize, Px, MPx),
313    /// Sup value ( raise text above base line )
314    Sup(Px),
315    /// Image, image, width, scale
316    Img(Image, MPx, f32),
317}
318
319/// Instances can fetch an image or font
320pub trait Fetcher {
321    /// Fetch named image
322    fn image(&mut self, _w: &mut Writer, _name: &str) -> Image {
323        todo!()
324    }
325    /// Fetch specified font
326    fn font(&mut self, _w: &mut Writer, _name: &str) -> Box<dyn Font> {
327        todo!()
328    }
329}