Skip to main content

pdf_min/
writer.rs

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