pdf-min 0.1.15

Very minimal crate for writing PDFs
Documentation
use crate::*;

/// Writer - has support for wrapping text, page layout, fonts, etc.
pub struct Writer {
    /// Underlying Basic Writer
    pub b: BasicPdfWriter,
    /// Current Page
    pub p: Page,
    /// List of fonts
    pub fonts: Vec<Box<dyn Font>>,
    /// Index into fonts
    pub cur_font: usize,
    /// Current font size, default is 10
    pub font_size: Px,
    /// Current sup ( raises text up off line ), use set_sup to adjust it
    pub sup: Px,
    /// Writing mode
    pub mode: Mode,
    /// PDF title
    pub title: String,
    /// List of Pages
    pub pages: Vec<Page>,
    /// Page is new ( not yet initialised )
    pub new_page: bool,
    /// Line padding ( space between lines ) default is 4
    pub line_pad: Px,
    /// Line margin ( left ), default is 20
    pub margin_left: Px,
    /// Line margin ( right ), default is 20
    pub margin_right: Px,
    /// Top margin, default is 20
    pub margin_top: Px,
    /// Bottom margin, default is 20
    pub margin_bottom: Px,
    /// Page width, default is 600
    pub page_width: Px,
    /// Page height, default is 800
    pub page_height: Px,
    /// Line used ( controls word-wrapping )
    pub line_used: MPx,
    /// Line items
    pub line: Vec<Item>,
    /// Largest font for current line
    pub max_font_size: Px,
    /// Default is zero, set to 1 to center output lines
    pub center: bool,
    /// For fetching fonts and images
    pub fetcher: Option<Box<dyn Fetcher>>,
}

impl Default for Writer {
    fn default() -> Self {
        let mut x = Self {
            mode: Mode::Normal,
            title: String::new(),
            b: BasicPdfWriter::default(),
            fonts: Vec::new(),
            cur_font: 0,
            font_size: 10,
            sup: 0,
            p: Page::default(),
            pages: Vec::new(),
            new_page: true,

            page_width: 600,
            page_height: 800,
            line_pad: 4,
            margin_left: 20,
            margin_right: 20,
            margin_top: 20,
            margin_bottom: 20,
            line_used: 0,
            line: Vec::new(),
            max_font_size: 0,
            center: false,
            fetcher: None,
        };
        for _ in 0..4 {
            x.fonts.push(Box::<StandardFont>::default());
        }
        x
    }
}

impl Writer {
    fn init_page(&mut self) {
        self.p.width = self.page_width;
        self.p.height = self.page_height;
        self.p.goto(
            self.margin_left,
            self.p.height - self.font_size - self.margin_top,
        );
        if self.sup != 0 {
            self.p.set_sup(self.sup);
        }
        self.new_page = false;
    }

    /// Completes current page.
    pub fn save_page(&mut self) {
        let p = std::mem::take(&mut self.p);
        self.pages.push(p);
        self.new_page = true;
    }

    fn init_font(&mut self, x: usize) {
        let f = &mut self.fonts[x];
        f.init(&mut self.b, HELVETICA[x]);
    }

    fn width(&self, _c: char) -> MPx {
        // This is rather preliminary.
        // Ought to get exact char width from self.cur_font.
        // In mean time, could allow more for upper case and wide chars like 'W'
        let w : MPx = if (self.cur_font & 1) == 1 { 550 } else { 500 }; // Allow more for bold fonts.
        w * (self.font_size as MPx)
    }

    fn line_len(&self) -> MPx {
        ( ( self.page_width - self.margin_left - self.margin_right ) as MPx ) * 1000
    }

    fn wrap_text(&mut self, s: &str) {
        if self.new_page {
            self.init_page();
        }

        let mut w = 0;
        for c in s.chars() {
            w += self.width(c);
        }

        if self.line_used + w > self.line_len() {
            self.output_line();
            if s == " " {
                return;
            }
        }
        self.line_used += w;
        self.init_font(self.cur_font);
        if self.font_size > self.max_font_size {
            self.max_font_size = self.font_size;
        }
        self.line
            .push(Item::Text(s.to_string(), self.cur_font, self.font_size, w));
    }

    /// Outputs current line ( consisting of items ).
    pub fn output_line(&mut self) {
        if self.new_page {
            self.init_page();
        } else {
            let cx = if self.center {
                ( (self.line_len() - self.line_used) / 2000 ) as Px
            } else {
                0
            };
            let h = self.max_font_size + self.line_pad;
            if self.p.y >= h + self.margin_bottom {
                self.p.td(self.margin_left + cx  - self.p.x, -h);
            } else {
                self.save_page();
                self.init_page();
            }
        }
        let mut cx : MPx = 0; // Unit is 1/1000 pixel
        for item in &self.line {
            match item {
                Item::Text(s, f, x, w) => {
                    let fp = &*self.fonts[*f];
                    self.p.text(fp, *x, s);
                    cx += w;
                }
                Item::Sup(x) => {
                    self.p.set_sup(*x);
                }
                Item::Img(im, width, scale) => {
                    self.p.flush_text();
                    cx += 5000; // 5 pixels left padding
                    let x : f32 = (self.p.x as f32) + ( ( cx / 1000 ) as f32 );
                    let y = self.p.y as f32;
                    im.draw( &mut self.p, x, y, *scale );
                    cx += 5000; // 5 pixels right padding

                    let width = (*width as MPx) * 1000;
                    cx += width;
                    self.p.space(width + 10000);
                }
            }
        }
        self.line.clear();
        self.line_used = 0;
        self.max_font_size = 0;
    }

    /// Writes word-wrapped text if mode is Normal, adds text to title if mode is Title.
    pub fn text(&mut self, s: &str) {
        match self.mode {
            Mode::Normal => {
                self.wrap_text(s);
            }
            Mode::Title => {
                self.title += s;
            }
            Mode::Head => {}
        }
    }

    /// Write image
    pub fn image(&mut self, src: &str, _width:Option<Px>, _height: Option<Px>)
    {
       let mut bf = std::mem::take(&mut self.fetcher);
       if let Some(f) = &mut bf
       {
           let im = f.image( self, src );
           let width = im.width;
           self.line.push(Item::Img(im, width, 1.0));
           // Maybe need to adjust self.line_used here.
       } else {
           self.text("error : no fetcher in pdf-min::Writer");
       }
       self.fetcher = bf;
    }

    /// Adds a space to text.
    pub fn space(&mut self) {
        self.text(" ");
    }

    /// Sets sup
    pub fn set_sup(&mut self, sup: Px) {
        self.line.push(Item::Sup(sup));
        self.sup = sup;
    }

    /// Flushes output line, writes page footers, saves pages, sets title, returns finished PDF as byte slice.
    pub fn finish(&mut self) -> &[u8] {
        self.output_line();
        self.init_font(0);
        self.save_page();
        let n = self.pages.len();
        let mut pnum = 1;
        let font_size = 8;
        #[allow(clippy::explicit_counter_loop)]
        for p in &mut self.pages {
            p.goto(self.margin_left, self.line_pad);
            p.text(
                &*self.fonts[0],
                font_size,
                &format!("Page {} of {}", pnum, n),
            );
            p.finish();
            pnum += 1;
        }
        self.b.finish(&self.pages, self.title.as_bytes());
        &self.b.b
    }
}

/// Writing mode (for html)
#[derive(Clone, Copy)]
pub enum Mode {
    /// Normal
    Normal,
    /// Text output is suppressed
    Head,
    /// Text is appended to title
    Title,
}

/// Items that define a line of text.
pub enum Item {
    /// Text, font index, font size, width
    Text(String, usize, Px, MPx),
    /// Sup value ( raise text above base line )
    Sup(Px),
    /// Image, image, width, scale
    Img(Image, Px, f32)
}

/// Instances can fetch an image or font
pub trait Fetcher {
    /// Fetch named image
    fn image(&mut self, _w: &mut Writer, _name: &str) -> Image { todo!() }
    /// Fetch specified font
    fn font(&mut self, _w: &mut Writer, _name: &str) -> Box<dyn Font>{ todo!() }
}