pdfox 0.1.0

A pure-Rust PDF library — create, parse, and render PDF documents with zero C dependencies
Documentation
/// Built-in PDF standard 14 fonts.
/// These are guaranteed to be available in every PDF viewer without embedding.
/// Character widths are in 1/1000 of a text unit (per PDF spec, Appendix D).

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BuiltinFont {
    Helvetica,
    HelveticaBold,
    HelveticaOblique,
    HelveticaBoldOblique,
    TimesRoman,
    TimesBold,
    TimesItalic,
    TimesBoldItalic,
    Courier,
    CourierBold,
    CourierOblique,
    CourierBoldOblique,
    Symbol,
    ZapfDingbats,
}

impl BuiltinFont {
    pub fn pdf_name(&self) -> &'static str {
        match self {
            BuiltinFont::Helvetica => "Helvetica",
            BuiltinFont::HelveticaBold => "Helvetica-Bold",
            BuiltinFont::HelveticaOblique => "Helvetica-Oblique",
            BuiltinFont::HelveticaBoldOblique => "Helvetica-BoldOblique",
            BuiltinFont::TimesRoman => "Times-Roman",
            BuiltinFont::TimesBold => "Times-Bold",
            BuiltinFont::TimesItalic => "Times-Italic",
            BuiltinFont::TimesBoldItalic => "Times-BoldItalic",
            BuiltinFont::Courier => "Courier",
            BuiltinFont::CourierBold => "Courier-Bold",
            BuiltinFont::CourierOblique => "Courier-Oblique",
            BuiltinFont::CourierBoldOblique => "Courier-BoldOblique",
            BuiltinFont::Symbol => "Symbol",
            BuiltinFont::ZapfDingbats => "ZapfDingbats",
        }
    }

    /// Returns the width of a character in 1/1000 text units.
    /// Used for text width calculation and layout.
    pub fn char_width(&self, c: char) -> f64 {
        let code = c as u32;
        // Characters outside WinAnsiEncoding (> U+00FF, excluding Latin-1 supplement)
        // are replaced during PDF string encoding. Map common ones to their fallback width.
        let idx = if code > 0xFF {
            let replacement = match c {
                '\u{2014}' => '-',  // em dash -> "--" but measure as one '-'
                '\u{2013}' => '-',  // en dash
                '\u{2018}' | '\u{2019}' => '\'',
                '\u{201C}' | '\u{201D}' => '"',
                '\u{2026}' => '.',  // ellipsis -> "..." but rough measure as '.'
                _ => '?',
            } as usize;
            replacement
        } else {
            code as usize
        };
        let widths = self.widths();
        widths.get(idx).copied().unwrap_or(500) as f64
    }

    /// Measure the width of a string at a given font size (in points).
    /// Accounts for multi-char substitutions (e.g. em-dash -> "--") to match
    /// what pdf_string_escape will actually emit.
    pub fn string_width(&self, text: &str, size: f64) -> f64 {
        let total: f64 = text.chars().flat_map(|c| {
            // Mirror the substitution logic in pdf_string_escape
            if c as u32 > 0xFF {
                let s: &[char] = match c {
                    '\u{2014}' => &['-', '-'],  // em dash -> "--"
                    '\u{2026}' => &['.', '.', '.'], // ellipsis -> "..."
                    _ => &['?'],
                };
                s.iter().map(|&ch| self.char_width(ch)).collect::<Vec<_>>()
            } else {
                vec![self.char_width(c)]
            }
        }).sum();
        total * size / 1000.0
    }

    /// Returns per-glyph widths array (indices 0-255)
    fn widths(&self) -> &'static [u16] {
        match self {
            BuiltinFont::Helvetica | BuiltinFont::HelveticaOblique => HELVETICA_WIDTHS,
            BuiltinFont::HelveticaBold | BuiltinFont::HelveticaBoldOblique => HELVETICA_BOLD_WIDTHS,
            BuiltinFont::TimesRoman => TIMES_ROMAN_WIDTHS,
            BuiltinFont::TimesBold => TIMES_BOLD_WIDTHS,
            BuiltinFont::TimesItalic => TIMES_ITALIC_WIDTHS,
            BuiltinFont::TimesBoldItalic => TIMES_BOLD_ITALIC_WIDTHS,
            // Courier is monospaced: all glyphs are 600 units wide
            BuiltinFont::Courier
            | BuiltinFont::CourierBold
            | BuiltinFont::CourierOblique
            | BuiltinFont::CourierBoldOblique => COURIER_WIDTHS,
            _ => HELVETICA_WIDTHS,
        }
    }

    pub fn is_monospace(&self) -> bool {
        matches!(
            self,
            BuiltinFont::Courier
                | BuiltinFont::CourierBold
                | BuiltinFont::CourierOblique
                | BuiltinFont::CourierBoldOblique
        )
    }
}

// Width tables per glyph 0-255 (1/1000 units).
// Source: Adobe Glyph List + PDF spec Appendix D

static HELVETICA_WIDTHS: &[u16] = &[
    278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, // 0-15
    278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, // 16-31
    278,278,355,556,556,889,667,222,333,333,389,584,278,333,278,278, // 32-47
    556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556, // 48-63
    1015,667,667,722,722,667,611,778,722,278,500,667,556,833,722,778, // 64-79
    667,778,722,667,611,722,667,944,667,667,611,278,278,278,469,556, // 80-95
    222,556,556,500,556,556,278,556,556,222,222,500,222,833,556,556, // 96-111
    556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,278, // 112-127
    278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, // 128-143
    278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, // 144-159
    278,333,556,556,556,556,260,556,333,737,370,556,584,333,737,333, // 160-175
    400,584,333,333,333,556,537,278,333,333,365,556,834,834,834,611, // 176-191
    667,667,667,667,667,667,1000,722,667,667,667,667,278,278,278,278,// 192-207
    722,722,778,778,778,778,778,584,778,722,722,722,722,667,667,611, // 208-223
    556,556,556,556,556,556,889,500,556,556,556,556,278,278,278,278, // 224-239
    556,556,556,556,556,556,556,584,611,556,556,556,556,500,556,500, // 240-255
];

static HELVETICA_BOLD_WIDTHS: &[u16] = &[
    278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,
    278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,
    278,333,474,556,556,889,722,278,333,333,389,584,278,333,278,278,
    556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,
    975,722,722,722,722,667,611,778,722,278,556,722,611,833,722,778,
    667,778,722,667,611,722,667,944,667,667,611,333,278,333,584,556,
    278,556,611,556,611,556,333,611,611,278,278,556,278,889,611,611,
    611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,278,
    278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,
    278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,
    278,333,556,556,556,556,280,556,333,737,370,556,584,333,737,333,
    400,584,333,333,333,611,556,278,333,333,365,556,834,834,834,611,
    722,722,722,722,722,722,1000,722,667,667,667,667,278,278,278,278,
    722,722,778,778,778,778,778,584,778,722,722,722,722,667,667,611,
    556,556,556,556,556,556,889,556,556,556,556,556,278,278,278,278,
    611,611,611,611,611,611,611,584,611,611,611,611,611,556,611,556,
];

static TIMES_ROMAN_WIDTHS: &[u16] = &[
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,333,408,500,500,833,778,333,333,333,500,564,250,333,250,278,
    500,500,500,500,500,500,500,500,500,500,278,278,564,564,564,444,
    921,722,667,667,722,611,556,722,722,333,389,722,611,889,722,722,
    556,722,667,556,611,722,722,944,722,722,611,333,278,333,469,500,
    333,444,500,444,500,444,333,500,500,278,278,500,278,778,500,500,
    500,500,333,389,278,500,500,722,500,500,444,480,200,480,541,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,333,500,500,500,500,200,500,333,760,276,500,564,333,760,333,
    400,564,300,300,333,500,453,250,333,300,310,500,750,750,750,444,
    722,722,722,722,722,722,889,667,611,611,611,611,333,333,333,333,
    722,722,722,722,722,722,722,564,722,722,722,722,722,722,556,500,
    444,444,444,444,444,444,667,444,444,444,444,444,278,278,278,278,
    500,500,500,500,500,500,500,564,500,500,500,500,500,500,500,500,
];

static TIMES_BOLD_WIDTHS: &[u16] = &[
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,333,555,500,500,1000,833,333,333,333,500,570,250,333,250,278,
    500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,
    930,722,667,722,722,667,611,778,778,389,500,778,667,944,722,778,
    611,778,722,556,667,722,722,1000,722,722,667,333,278,333,581,500,
    333,500,556,444,556,444,333,500,556,278,333,556,278,833,556,500,
    556,556,444,389,333,556,500,722,500,500,444,394,220,394,520,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,333,500,500,500,500,220,500,333,747,300,500,570,333,747,333,
    400,570,300,300,333,556,540,250,333,300,330,500,750,750,750,500,
    722,722,722,722,722,722,1000,722,667,667,667,667,389,389,389,389,
    722,722,778,778,778,778,778,570,778,722,722,722,722,722,556,500,
    500,500,500,500,500,500,722,444,444,444,444,444,278,278,278,278,
    500,556,500,500,500,500,500,570,500,556,556,556,556,500,556,500,
];

static TIMES_ITALIC_WIDTHS: &[u16] = &[
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,333,420,500,500,833,778,333,333,333,500,675,250,333,250,278,
    500,500,500,500,500,500,500,500,500,500,333,333,675,675,675,500,
    920,611,611,667,722,611,611,722,722,333,444,667,556,833,667,722,
    611,722,611,500,556,722,611,833,611,556,556,389,278,389,422,500,
    333,500,500,444,500,444,278,500,500,278,278,444,278,722,500,500,
    500,500,389,389,278,500,444,667,444,444,389,400,275,400,541,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,389,500,500,500,500,275,500,333,760,276,500,675,333,760,333,
    400,675,300,300,333,500,523,250,333,300,310,500,750,750,750,500,
    611,611,611,611,611,611,889,667,611,611,611,611,333,333,333,333,
    722,667,722,722,722,722,722,675,722,722,722,722,722,556,611,500,
    500,500,500,500,500,500,667,444,444,444,444,444,278,278,278,278,
    500,500,500,500,500,500,500,675,500,500,500,500,500,444,500,444,
];

static TIMES_BOLD_ITALIC_WIDTHS: &[u16] = &[
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,389,555,500,500,833,778,333,333,333,500,570,250,333,250,278,
    500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,
    832,667,667,667,722,667,667,722,778,389,500,667,611,889,722,722,
    611,722,667,556,611,722,667,889,667,611,611,333,278,333,570,500,
    333,500,500,444,500,444,333,500,556,278,278,500,278,778,556,500,
    500,500,389,389,278,556,444,667,500,444,389,348,220,348,570,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
    250,389,500,500,500,500,220,500,333,747,266,500,606,333,747,333,
    400,570,300,300,333,576,500,250,333,300,300,500,750,750,750,500,
    667,667,667,667,667,667,944,667,667,667,667,667,389,389,389,389,
    722,722,722,722,722,722,722,570,722,722,722,722,722,611,611,500,
    500,500,500,500,500,500,722,444,444,444,444,444,278,278,278,278,
    500,556,500,500,500,500,500,570,500,556,556,556,556,444,500,444,
];

// Courier is a fixed-width font: all characters are 600 units wide
static COURIER_WIDTHS: &[u16] = &[600; 256];