From 12e9a655e36924564057464bf25494b8c027eb57 Mon Sep 17 00:00:00 2001 From: Abimael Martell <1450169+abimaelmartell@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:00:43 -0700 Subject: [PATCH] fix(extractor): supply built-in metrics for non-embedded base-14 fonts (#241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(extractor): supply built-in metrics for non-embedded base-14 fonts PDFs may legally omit /Widths for non-embedded standard fonts (Times, Helvetica, Courier, Symbol, ZapfDingbats) — the spec requires the reader to supply the metrics. We returned None, so every glyph advanced 0 and each text item got width 0, silently breaking every gap-based heuristic downstream: space synthesis, sub/superscript detection, table column detection, heading merging. - src/extractor/base14.rs: Adobe Core-14 AFM width tables keyed by Unicode char, plus the standard Symbol/ZapfDingbats encoding vectors (their glyphs sit at byte positions unrelated to Latin text, so widths must resolve through the built-in encoding, not cp1252) - Width resolution order: Differences -> built-in encoding -> the same cp1252-style fallback the text decoder uses, so a code's advance always matches the character we emit for it - Type3 visual sizing: PK bitmap fonts (dvips) use FontMatrix [1 0 0 -1 0 0] with nominal sizes like 0.12pt; scale by FontBBox height x |matrix_y|. Applied in the page-stream and Form XObject paths. Indirect numeric array elements are resolved before use. Effect on Shannon's 'A Mathematical Theory of Communication' (1998 dvips/Distiller, the reported case): glued sentences 95 -> 5. Corpus impact: 12 of 184 eval documents, e.g. Data-Processing-Agreement recovers a paragraph that a phantom table had shredded into cells. Layout heuristics tuned on the same document (indent-based paragraph breaks, heading reclassification, table script filtering) are held back for a separate PR — they change ~98 further documents and need to be justified against the corpus, not against one PDF. * review: narrow Type3 rescaling to self-inconsistent fonts; dedup + test all width tables Addresses cubic review on #241, plus a follow-up from a local cubic run. - Type3 visual scaling was applied to every Type3 font whose FontBBox height x |matrix_y| deviated >5% from 1.0. FontBBox is the glyph box, not the em box, so a conventional 1/1000-matrix font with a descender..ascender bbox (~700 units) computed 0.7 and had every reported size shrunk by 30% — corrupting the drop-cap, heading-tier, sub/superscript and table heuristics this is meant to fix. First attempt gated on the matrix being unit-scale, but a local cubic run pointed out that wrongly excludes valid non-standard matrices (a 0.005 matrix with a full-em bbox legitimately needs a 5x scale). The product is the right discriminator, not the matrix: a self-consistent font lands near 1.0 because the matrix is the reciprocal of the glyph-space em, so only a wildly inconsistent one (dvips/PK bitmap fonts sit at ~159) is renormalized. Band widened to [0.25, 4.0]. Corpus effect: 12 -> 7 documents change. The 5 that drop out were being wrongly rescaled — including Data-Processing-Agreement, whose phantom-table fix turned out to come from this bug rather than from the width fallback, so it is correctly given up. - base14: all 14 width tables now covered by the sort-invariant test via an ALL_TABLES registry, not a hand-picked subset. - base14: identical tables share one static (all four Courier variants are monospace 600; the oblique Helvetica variants match their upright forms), removing 5 duplicate copies. * test: refresh Shannon snapshot after merging main CI checks out a merge of the PR head with main, and main advanced 8 commits since this branch was cut — including #201 (contextual digit runs), #240 and #253 (markdown fixes). Those change extraction output, so a snapshot generated on the unmerged branch could not match; the Test job failed on the merge commit while passing on the branch itself. The merged behaviour is better: the footnote marker '2' before 'Hartley, R. V. L.' is now recovered instead of dropped. 950 tests pass on the merged tree, clippy clean. --- src/extractor/base14.rs | 649 ++++++++++++++++++++++++ src/extractor/content_stream.rs | 19 +- src/extractor/fonts.rs | 235 ++++++++- src/extractor/mod.rs | 1 + src/extractor/xobjects.rs | 12 +- tests/fixtures/shannon-entropy-p1-2.pdf | Bin 0 -> 23136 bytes tests/integration_tests.rs | 12 + tests/snapshots/shannon-entropy-p1-2.md | 43 ++ 8 files changed, 959 insertions(+), 12 deletions(-) create mode 100644 src/extractor/base14.rs create mode 100644 tests/fixtures/shannon-entropy-p1-2.pdf create mode 100644 tests/snapshots/shannon-entropy-p1-2.md diff --git a/src/extractor/base14.rs b/src/extractor/base14.rs new file mode 100644 index 0000000..8669092 --- /dev/null +++ b/src/extractor/base14.rs @@ -0,0 +1,649 @@ +//! Built-in glyph metrics for the 14 standard PDF fonts. +//! +//! PDFs may omit `/Widths` for non-embedded base-14 fonts (Times, Helvetica, +//! Courier, Symbol, ZapfDingbats); per the PDF spec the reader must supply +//! the metrics. Without them every text item gets width 0, which breaks +//! space synthesis, sub/superscript detection, and table column detection +//! (common in 1990s dvips/Distiller output). +//! +//! Tables are generated from the Adobe Core 14 AFM files (via reportlab's +//! `_fontdata`), keyed by Unicode char, sorted for binary search. +//! Generator: scratchpad/gen_base14.py (session tooling, not checked in). + +/// Width in 1000ths of an em for `c` in the given base-14 font, or `None` +/// if the font is not one of the base 14 (after name normalization) or the +/// char has no glyph in its AFM. +pub(crate) fn base14_char_width(base_font: &str, c: char) -> Option { + let table = base14_table(base_font)?; + // AFM tables key visible glyphs only; alias the invisible variants the + // cp1252 fallback can produce so they get the metric of their visible + // counterpart instead of the generic default. + let c = match c { + '\u{00A0}' => ' ', // no-break space -> space + '\u{00AD}' => '-', // soft hyphen -> hyphen + _ => c, + }; + table + .binary_search_by_key(&c, |&(ch, _)| ch) + .ok() + .map(|i| table[i].1) +} + +/// True when the base font name normalizes to one of the standard 14 fonts. +pub(crate) fn is_base14_font(base_font: &str) -> bool { + base14_table(base_font).is_some() +} + +/// Code → Unicode through the font's BUILT-IN encoding, for the base-14 +/// fonts whose repertoire is not Latin (Symbol, ZapfDingbats). Their glyphs +/// live at byte positions that have nothing to do with cp1252 (Symbol 0x61 +/// renders α, Zapf 0x21 renders ✁), so advance widths must be resolved +/// through this mapping — the renderer draws these glyphs regardless of how +/// the text decoder transliterates them. Returns `None` for the Latin text +/// fonts, which follow standard single-byte encodings. +pub(crate) fn builtin_encoding_char(base_font: &str, code: u8) -> Option { + let table = base14_table(base_font)?; + let enc: &[(u8, char)] = if std::ptr::eq(table, SYMBOL) { + SYMBOL_ENCODING + } else if std::ptr::eq(table, ZAPFDINGBATS) { + ZAPFDINGBATS_ENCODING + } else { + return None; + }; + enc.binary_search_by_key(&code, |&(b, _)| b) + .ok() + .map(|i| enc[i].1) +} + +/// Map a BaseFont name (possibly subset-prefixed, e.g. "ABCDEF+Times-Bold", +/// or a common alias like "Arial" / "TimesNewRomanPSMT") to its width table. +fn base14_table(base_font: &str) -> Option<&'static [(char, u16)]> { + // Strip subset prefix "ABCDEF+" + let name = match base_font.split_once('+') { + Some((prefix, rest)) + if prefix.len() == 6 && prefix.chars().all(|c| c.is_ascii_uppercase()) => + { + rest + } + _ => base_font, + }; + let lower = name.to_ascii_lowercase(); + let bold = lower.contains("bold"); + let italic = lower.contains("italic") || lower.contains("oblique"); + if lower.contains("courier") { + return Some(match (bold, italic) { + (false, false) => COURIER, + (true, false) => COURIER_BOLD, + (false, true) => COURIER_OBLIQUE, + (true, true) => COURIER_BOLDOBLIQUE, + }); + } + if lower.contains("helvetica") || lower.contains("arial") { + return Some(match (bold, italic) { + (false, false) => HELVETICA, + (true, false) => HELVETICA_BOLD, + (false, true) => HELVETICA_OBLIQUE, + (true, true) => HELVETICA_BOLDOBLIQUE, + }); + } + if lower.contains("times") { + return Some(match (bold, italic) { + (false, false) => TIMES_ROMAN, + (true, false) => TIMES_BOLD, + (false, true) => TIMES_ITALIC, + (true, true) => TIMES_BOLDITALIC, + }); + } + // Symbol and ZapfDingbats have unique glyph repertoires, so only exact + // names (plus the common MT/ITC aliases) qualify — a custom font that + // merely mentions "Symbol" in its name must not get these metrics. + match lower.as_str() { + "zapfdingbats" | "dingbats" | "itczapfdingbats" | "zapfdingbatsitc" => { + return Some(ZAPFDINGBATS) + } + "symbol" | "symbolmt" | "symbolitc" => return Some(SYMBOL), + _ => {} + } + None +} + +#[rustfmt::skip] +static COURIER: &[(char, u16)] = &[ + (' ', 600), ('!', 600), ('"', 600), ('#', 600), ('$', 600), ('%', 600), + ('&', 600), ('\'', 600), ('(', 600), (')', 600), ('*', 600), ('+', 600), + (',', 600), ('-', 600), ('.', 600), ('/', 600), ('0', 600), ('1', 600), + ('2', 600), ('3', 600), ('4', 600), ('5', 600), ('6', 600), ('7', 600), + ('8', 600), ('9', 600), (':', 600), (';', 600), ('<', 600), ('=', 600), + ('>', 600), ('?', 600), ('@', 600), ('A', 600), ('B', 600), ('C', 600), + ('D', 600), ('E', 600), ('F', 600), ('G', 600), ('H', 600), ('I', 600), + ('J', 600), ('K', 600), ('L', 600), ('M', 600), ('N', 600), ('O', 600), + ('P', 600), ('Q', 600), ('R', 600), ('S', 600), ('T', 600), ('U', 600), + ('V', 600), ('W', 600), ('X', 600), ('Y', 600), ('Z', 600), ('[', 600), + ('\\', 600), (']', 600), ('^', 600), ('_', 600), ('`', 600), ('a', 600), + ('b', 600), ('c', 600), ('d', 600), ('e', 600), ('f', 600), ('g', 600), + ('h', 600), ('i', 600), ('j', 600), ('k', 600), ('l', 600), ('m', 600), + ('n', 600), ('o', 600), ('p', 600), ('q', 600), ('r', 600), ('s', 600), + ('t', 600), ('u', 600), ('v', 600), ('w', 600), ('x', 600), ('y', 600), + ('z', 600), ('{', 600), ('|', 600), ('}', 600), ('~', 600), ('\u{00A1}', 600), + ('\u{00A2}', 600), ('\u{00A3}', 600), ('\u{00A4}', 600), ('\u{00A5}', 600), ('\u{00A6}', 600), ('\u{00A7}', 600), + ('\u{00A8}', 600), ('\u{00A9}', 600), ('\u{00AA}', 600), ('\u{00AB}', 600), ('\u{00AC}', 600), ('\u{00AE}', 600), + ('\u{00AF}', 600), ('\u{00B0}', 600), ('\u{00B1}', 600), ('\u{00B2}', 600), ('\u{00B3}', 600), ('\u{00B4}', 600), + ('\u{00B5}', 600), ('\u{00B6}', 600), ('\u{00B7}', 600), ('\u{00B8}', 600), ('\u{00B9}', 600), ('\u{00BA}', 600), + ('\u{00BB}', 600), ('\u{00BC}', 600), ('\u{00BD}', 600), ('\u{00BE}', 600), ('\u{00BF}', 600), ('\u{00C0}', 600), + ('\u{00C1}', 600), ('\u{00C2}', 600), ('\u{00C3}', 600), ('\u{00C4}', 600), ('\u{00C5}', 600), ('\u{00C6}', 600), + ('\u{00C7}', 600), ('\u{00C8}', 600), ('\u{00C9}', 600), ('\u{00CA}', 600), ('\u{00CB}', 600), ('\u{00CC}', 600), + ('\u{00CD}', 600), ('\u{00CE}', 600), ('\u{00CF}', 600), ('\u{00D0}', 600), ('\u{00D1}', 600), ('\u{00D2}', 600), + ('\u{00D3}', 600), ('\u{00D4}', 600), ('\u{00D5}', 600), ('\u{00D6}', 600), ('\u{00D7}', 600), ('\u{00D8}', 600), + ('\u{00D9}', 600), ('\u{00DA}', 600), ('\u{00DB}', 600), ('\u{00DC}', 600), ('\u{00DD}', 600), ('\u{00DE}', 600), + ('\u{00DF}', 600), ('\u{00E0}', 600), ('\u{00E1}', 600), ('\u{00E2}', 600), ('\u{00E3}', 600), ('\u{00E4}', 600), + ('\u{00E5}', 600), ('\u{00E6}', 600), ('\u{00E7}', 600), ('\u{00E8}', 600), ('\u{00E9}', 600), ('\u{00EA}', 600), + ('\u{00EB}', 600), ('\u{00EC}', 600), ('\u{00ED}', 600), ('\u{00EE}', 600), ('\u{00EF}', 600), ('\u{00F0}', 600), + ('\u{00F1}', 600), ('\u{00F2}', 600), ('\u{00F3}', 600), ('\u{00F4}', 600), ('\u{00F5}', 600), ('\u{00F6}', 600), + ('\u{00F7}', 600), ('\u{00F8}', 600), ('\u{00F9}', 600), ('\u{00FA}', 600), ('\u{00FB}', 600), ('\u{00FC}', 600), + ('\u{00FD}', 600), ('\u{00FE}', 600), ('\u{00FF}', 600), ('\u{0131}', 600), ('\u{0141}', 600), ('\u{0142}', 600), + ('\u{0152}', 600), ('\u{0153}', 600), ('\u{0160}', 600), ('\u{0161}', 600), ('\u{0178}', 600), ('\u{017D}', 600), + ('\u{017E}', 600), ('\u{0192}', 600), ('\u{02C6}', 600), ('\u{02C7}', 600), ('\u{02D8}', 600), ('\u{02D9}', 600), + ('\u{02DA}', 600), ('\u{02DB}', 600), ('\u{02DC}', 600), ('\u{02DD}', 600), ('\u{2013}', 600), ('\u{2014}', 600), + ('\u{2018}', 600), ('\u{2019}', 600), ('\u{201A}', 600), ('\u{201C}', 600), ('\u{201D}', 600), ('\u{201E}', 600), + ('\u{2020}', 600), ('\u{2021}', 600), ('\u{2022}', 600), ('\u{2026}', 600), ('\u{2030}', 600), ('\u{2039}', 600), + ('\u{203A}', 600), ('\u{2044}', 600), ('\u{20AC}', 600), ('\u{2122}', 600), ('\u{2212}', 600), ('\u{FB01}', 600), + ('\u{FB02}', 600), +]; + +static COURIER_BOLD: &[(char, u16)] = COURIER; + +static COURIER_OBLIQUE: &[(char, u16)] = COURIER; + +static COURIER_BOLDOBLIQUE: &[(char, u16)] = COURIER; + +#[rustfmt::skip] +static HELVETICA: &[(char, u16)] = &[ + (' ', 278), ('!', 278), ('"', 355), ('#', 556), ('$', 556), ('%', 889), + ('&', 667), ('\'', 191), ('(', 333), (')', 333), ('*', 389), ('+', 584), + (',', 278), ('-', 333), ('.', 278), ('/', 278), ('0', 556), ('1', 556), + ('2', 556), ('3', 556), ('4', 556), ('5', 556), ('6', 556), ('7', 556), + ('8', 556), ('9', 556), (':', 278), (';', 278), ('<', 584), ('=', 584), + ('>', 584), ('?', 556), ('@', 1015), ('A', 667), ('B', 667), ('C', 722), + ('D', 722), ('E', 667), ('F', 611), ('G', 778), ('H', 722), ('I', 278), + ('J', 500), ('K', 667), ('L', 556), ('M', 833), ('N', 722), ('O', 778), + ('P', 667), ('Q', 778), ('R', 722), ('S', 667), ('T', 611), ('U', 722), + ('V', 667), ('W', 944), ('X', 667), ('Y', 667), ('Z', 611), ('[', 278), + ('\\', 278), (']', 278), ('^', 469), ('_', 556), ('`', 333), ('a', 556), + ('b', 556), ('c', 500), ('d', 556), ('e', 556), ('f', 278), ('g', 556), + ('h', 556), ('i', 222), ('j', 222), ('k', 500), ('l', 222), ('m', 833), + ('n', 556), ('o', 556), ('p', 556), ('q', 556), ('r', 333), ('s', 500), + ('t', 278), ('u', 556), ('v', 500), ('w', 722), ('x', 500), ('y', 500), + ('z', 500), ('{', 334), ('|', 260), ('}', 334), ('~', 584), ('\u{00A1}', 333), + ('\u{00A2}', 556), ('\u{00A3}', 556), ('\u{00A4}', 556), ('\u{00A5}', 556), ('\u{00A6}', 260), ('\u{00A7}', 556), + ('\u{00A8}', 333), ('\u{00A9}', 737), ('\u{00AA}', 370), ('\u{00AB}', 556), ('\u{00AC}', 584), ('\u{00AE}', 737), + ('\u{00AF}', 333), ('\u{00B0}', 400), ('\u{00B1}', 584), ('\u{00B2}', 333), ('\u{00B3}', 333), ('\u{00B4}', 333), + ('\u{00B5}', 556), ('\u{00B6}', 537), ('\u{00B7}', 278), ('\u{00B8}', 333), ('\u{00B9}', 333), ('\u{00BA}', 365), + ('\u{00BB}', 556), ('\u{00BC}', 834), ('\u{00BD}', 834), ('\u{00BE}', 834), ('\u{00BF}', 611), ('\u{00C0}', 667), + ('\u{00C1}', 667), ('\u{00C2}', 667), ('\u{00C3}', 667), ('\u{00C4}', 667), ('\u{00C5}', 667), ('\u{00C6}', 1000), + ('\u{00C7}', 722), ('\u{00C8}', 667), ('\u{00C9}', 667), ('\u{00CA}', 667), ('\u{00CB}', 667), ('\u{00CC}', 278), + ('\u{00CD}', 278), ('\u{00CE}', 278), ('\u{00CF}', 278), ('\u{00D0}', 722), ('\u{00D1}', 722), ('\u{00D2}', 778), + ('\u{00D3}', 778), ('\u{00D4}', 778), ('\u{00D5}', 778), ('\u{00D6}', 778), ('\u{00D7}', 584), ('\u{00D8}', 778), + ('\u{00D9}', 722), ('\u{00DA}', 722), ('\u{00DB}', 722), ('\u{00DC}', 722), ('\u{00DD}', 667), ('\u{00DE}', 667), + ('\u{00DF}', 611), ('\u{00E0}', 556), ('\u{00E1}', 556), ('\u{00E2}', 556), ('\u{00E3}', 556), ('\u{00E4}', 556), + ('\u{00E5}', 556), ('\u{00E6}', 889), ('\u{00E7}', 500), ('\u{00E8}', 556), ('\u{00E9}', 556), ('\u{00EA}', 556), + ('\u{00EB}', 556), ('\u{00EC}', 278), ('\u{00ED}', 278), ('\u{00EE}', 278), ('\u{00EF}', 278), ('\u{00F0}', 556), + ('\u{00F1}', 556), ('\u{00F2}', 556), ('\u{00F3}', 556), ('\u{00F4}', 556), ('\u{00F5}', 556), ('\u{00F6}', 556), + ('\u{00F7}', 584), ('\u{00F8}', 611), ('\u{00F9}', 556), ('\u{00FA}', 556), ('\u{00FB}', 556), ('\u{00FC}', 556), + ('\u{00FD}', 500), ('\u{00FE}', 556), ('\u{00FF}', 500), ('\u{0131}', 278), ('\u{0141}', 556), ('\u{0142}', 222), + ('\u{0152}', 1000), ('\u{0153}', 944), ('\u{0160}', 667), ('\u{0161}', 500), ('\u{0178}', 667), ('\u{017D}', 611), + ('\u{017E}', 500), ('\u{0192}', 556), ('\u{02C6}', 333), ('\u{02C7}', 333), ('\u{02D8}', 333), ('\u{02D9}', 333), + ('\u{02DA}', 333), ('\u{02DB}', 333), ('\u{02DC}', 333), ('\u{02DD}', 333), ('\u{2013}', 556), ('\u{2014}', 1000), + ('\u{2018}', 222), ('\u{2019}', 222), ('\u{201A}', 222), ('\u{201C}', 333), ('\u{201D}', 333), ('\u{201E}', 333), + ('\u{2020}', 556), ('\u{2021}', 556), ('\u{2022}', 350), ('\u{2026}', 1000), ('\u{2030}', 1000), ('\u{2039}', 333), + ('\u{203A}', 333), ('\u{2044}', 167), ('\u{20AC}', 556), ('\u{2122}', 1000), ('\u{2212}', 584), ('\u{FB01}', 500), + ('\u{FB02}', 500), +]; + +#[rustfmt::skip] +static HELVETICA_BOLD: &[(char, u16)] = &[ + (' ', 278), ('!', 333), ('"', 474), ('#', 556), ('$', 556), ('%', 889), + ('&', 722), ('\'', 238), ('(', 333), (')', 333), ('*', 389), ('+', 584), + (',', 278), ('-', 333), ('.', 278), ('/', 278), ('0', 556), ('1', 556), + ('2', 556), ('3', 556), ('4', 556), ('5', 556), ('6', 556), ('7', 556), + ('8', 556), ('9', 556), (':', 333), (';', 333), ('<', 584), ('=', 584), + ('>', 584), ('?', 611), ('@', 975), ('A', 722), ('B', 722), ('C', 722), + ('D', 722), ('E', 667), ('F', 611), ('G', 778), ('H', 722), ('I', 278), + ('J', 556), ('K', 722), ('L', 611), ('M', 833), ('N', 722), ('O', 778), + ('P', 667), ('Q', 778), ('R', 722), ('S', 667), ('T', 611), ('U', 722), + ('V', 667), ('W', 944), ('X', 667), ('Y', 667), ('Z', 611), ('[', 333), + ('\\', 278), (']', 333), ('^', 584), ('_', 556), ('`', 333), ('a', 556), + ('b', 611), ('c', 556), ('d', 611), ('e', 556), ('f', 333), ('g', 611), + ('h', 611), ('i', 278), ('j', 278), ('k', 556), ('l', 278), ('m', 889), + ('n', 611), ('o', 611), ('p', 611), ('q', 611), ('r', 389), ('s', 556), + ('t', 333), ('u', 611), ('v', 556), ('w', 778), ('x', 556), ('y', 556), + ('z', 500), ('{', 389), ('|', 280), ('}', 389), ('~', 584), ('\u{00A1}', 333), + ('\u{00A2}', 556), ('\u{00A3}', 556), ('\u{00A4}', 556), ('\u{00A5}', 556), ('\u{00A6}', 280), ('\u{00A7}', 556), + ('\u{00A8}', 333), ('\u{00A9}', 737), ('\u{00AA}', 370), ('\u{00AB}', 556), ('\u{00AC}', 584), ('\u{00AE}', 737), + ('\u{00AF}', 333), ('\u{00B0}', 400), ('\u{00B1}', 584), ('\u{00B2}', 333), ('\u{00B3}', 333), ('\u{00B4}', 333), + ('\u{00B5}', 611), ('\u{00B6}', 556), ('\u{00B7}', 278), ('\u{00B8}', 333), ('\u{00B9}', 333), ('\u{00BA}', 365), + ('\u{00BB}', 556), ('\u{00BC}', 834), ('\u{00BD}', 834), ('\u{00BE}', 834), ('\u{00BF}', 611), ('\u{00C0}', 722), + ('\u{00C1}', 722), ('\u{00C2}', 722), ('\u{00C3}', 722), ('\u{00C4}', 722), ('\u{00C5}', 722), ('\u{00C6}', 1000), + ('\u{00C7}', 722), ('\u{00C8}', 667), ('\u{00C9}', 667), ('\u{00CA}', 667), ('\u{00CB}', 667), ('\u{00CC}', 278), + ('\u{00CD}', 278), ('\u{00CE}', 278), ('\u{00CF}', 278), ('\u{00D0}', 722), ('\u{00D1}', 722), ('\u{00D2}', 778), + ('\u{00D3}', 778), ('\u{00D4}', 778), ('\u{00D5}', 778), ('\u{00D6}', 778), ('\u{00D7}', 584), ('\u{00D8}', 778), + ('\u{00D9}', 722), ('\u{00DA}', 722), ('\u{00DB}', 722), ('\u{00DC}', 722), ('\u{00DD}', 667), ('\u{00DE}', 667), + ('\u{00DF}', 611), ('\u{00E0}', 556), ('\u{00E1}', 556), ('\u{00E2}', 556), ('\u{00E3}', 556), ('\u{00E4}', 556), + ('\u{00E5}', 556), ('\u{00E6}', 889), ('\u{00E7}', 556), ('\u{00E8}', 556), ('\u{00E9}', 556), ('\u{00EA}', 556), + ('\u{00EB}', 556), ('\u{00EC}', 278), ('\u{00ED}', 278), ('\u{00EE}', 278), ('\u{00EF}', 278), ('\u{00F0}', 611), + ('\u{00F1}', 611), ('\u{00F2}', 611), ('\u{00F3}', 611), ('\u{00F4}', 611), ('\u{00F5}', 611), ('\u{00F6}', 611), + ('\u{00F7}', 584), ('\u{00F8}', 611), ('\u{00F9}', 611), ('\u{00FA}', 611), ('\u{00FB}', 611), ('\u{00FC}', 611), + ('\u{00FD}', 556), ('\u{00FE}', 611), ('\u{00FF}', 556), ('\u{0131}', 278), ('\u{0141}', 611), ('\u{0142}', 278), + ('\u{0152}', 1000), ('\u{0153}', 944), ('\u{0160}', 667), ('\u{0161}', 556), ('\u{0178}', 667), ('\u{017D}', 611), + ('\u{017E}', 500), ('\u{0192}', 556), ('\u{02C6}', 333), ('\u{02C7}', 333), ('\u{02D8}', 333), ('\u{02D9}', 333), + ('\u{02DA}', 333), ('\u{02DB}', 333), ('\u{02DC}', 333), ('\u{02DD}', 333), ('\u{2013}', 556), ('\u{2014}', 1000), + ('\u{2018}', 278), ('\u{2019}', 278), ('\u{201A}', 278), ('\u{201C}', 500), ('\u{201D}', 500), ('\u{201E}', 500), + ('\u{2020}', 556), ('\u{2021}', 556), ('\u{2022}', 350), ('\u{2026}', 1000), ('\u{2030}', 1000), ('\u{2039}', 333), + ('\u{203A}', 333), ('\u{2044}', 167), ('\u{20AC}', 556), ('\u{2122}', 1000), ('\u{2212}', 584), ('\u{FB01}', 611), + ('\u{FB02}', 611), +]; + +static HELVETICA_OBLIQUE: &[(char, u16)] = HELVETICA; + +static HELVETICA_BOLDOBLIQUE: &[(char, u16)] = HELVETICA_BOLD; + +#[rustfmt::skip] +static TIMES_ROMAN: &[(char, u16)] = &[ + (' ', 250), ('!', 333), ('"', 408), ('#', 500), ('$', 500), ('%', 833), + ('&', 778), ('\'', 180), ('(', 333), (')', 333), ('*', 500), ('+', 564), + (',', 250), ('-', 333), ('.', 250), ('/', 278), ('0', 500), ('1', 500), + ('2', 500), ('3', 500), ('4', 500), ('5', 500), ('6', 500), ('7', 500), + ('8', 500), ('9', 500), (':', 278), (';', 278), ('<', 564), ('=', 564), + ('>', 564), ('?', 444), ('@', 921), ('A', 722), ('B', 667), ('C', 667), + ('D', 722), ('E', 611), ('F', 556), ('G', 722), ('H', 722), ('I', 333), + ('J', 389), ('K', 722), ('L', 611), ('M', 889), ('N', 722), ('O', 722), + ('P', 556), ('Q', 722), ('R', 667), ('S', 556), ('T', 611), ('U', 722), + ('V', 722), ('W', 944), ('X', 722), ('Y', 722), ('Z', 611), ('[', 333), + ('\\', 278), (']', 333), ('^', 469), ('_', 500), ('`', 333), ('a', 444), + ('b', 500), ('c', 444), ('d', 500), ('e', 444), ('f', 333), ('g', 500), + ('h', 500), ('i', 278), ('j', 278), ('k', 500), ('l', 278), ('m', 778), + ('n', 500), ('o', 500), ('p', 500), ('q', 500), ('r', 333), ('s', 389), + ('t', 278), ('u', 500), ('v', 500), ('w', 722), ('x', 500), ('y', 500), + ('z', 444), ('{', 480), ('|', 200), ('}', 480), ('~', 541), ('\u{00A1}', 333), + ('\u{00A2}', 500), ('\u{00A3}', 500), ('\u{00A4}', 500), ('\u{00A5}', 500), ('\u{00A6}', 200), ('\u{00A7}', 500), + ('\u{00A8}', 333), ('\u{00A9}', 760), ('\u{00AA}', 276), ('\u{00AB}', 500), ('\u{00AC}', 564), ('\u{00AE}', 760), + ('\u{00AF}', 333), ('\u{00B0}', 400), ('\u{00B1}', 564), ('\u{00B2}', 300), ('\u{00B3}', 300), ('\u{00B4}', 333), + ('\u{00B5}', 500), ('\u{00B6}', 453), ('\u{00B7}', 250), ('\u{00B8}', 333), ('\u{00B9}', 300), ('\u{00BA}', 310), + ('\u{00BB}', 500), ('\u{00BC}', 750), ('\u{00BD}', 750), ('\u{00BE}', 750), ('\u{00BF}', 444), ('\u{00C0}', 722), + ('\u{00C1}', 722), ('\u{00C2}', 722), ('\u{00C3}', 722), ('\u{00C4}', 722), ('\u{00C5}', 722), ('\u{00C6}', 889), + ('\u{00C7}', 667), ('\u{00C8}', 611), ('\u{00C9}', 611), ('\u{00CA}', 611), ('\u{00CB}', 611), ('\u{00CC}', 333), + ('\u{00CD}', 333), ('\u{00CE}', 333), ('\u{00CF}', 333), ('\u{00D0}', 722), ('\u{00D1}', 722), ('\u{00D2}', 722), + ('\u{00D3}', 722), ('\u{00D4}', 722), ('\u{00D5}', 722), ('\u{00D6}', 722), ('\u{00D7}', 564), ('\u{00D8}', 722), + ('\u{00D9}', 722), ('\u{00DA}', 722), ('\u{00DB}', 722), ('\u{00DC}', 722), ('\u{00DD}', 722), ('\u{00DE}', 556), + ('\u{00DF}', 500), ('\u{00E0}', 444), ('\u{00E1}', 444), ('\u{00E2}', 444), ('\u{00E3}', 444), ('\u{00E4}', 444), + ('\u{00E5}', 444), ('\u{00E6}', 667), ('\u{00E7}', 444), ('\u{00E8}', 444), ('\u{00E9}', 444), ('\u{00EA}', 444), + ('\u{00EB}', 444), ('\u{00EC}', 278), ('\u{00ED}', 278), ('\u{00EE}', 278), ('\u{00EF}', 278), ('\u{00F0}', 500), + ('\u{00F1}', 500), ('\u{00F2}', 500), ('\u{00F3}', 500), ('\u{00F4}', 500), ('\u{00F5}', 500), ('\u{00F6}', 500), + ('\u{00F7}', 564), ('\u{00F8}', 500), ('\u{00F9}', 500), ('\u{00FA}', 500), ('\u{00FB}', 500), ('\u{00FC}', 500), + ('\u{00FD}', 500), ('\u{00FE}', 500), ('\u{00FF}', 500), ('\u{0131}', 278), ('\u{0141}', 611), ('\u{0142}', 278), + ('\u{0152}', 889), ('\u{0153}', 722), ('\u{0160}', 556), ('\u{0161}', 389), ('\u{0178}', 722), ('\u{017D}', 611), + ('\u{017E}', 444), ('\u{0192}', 500), ('\u{02C6}', 333), ('\u{02C7}', 333), ('\u{02D8}', 333), ('\u{02D9}', 333), + ('\u{02DA}', 333), ('\u{02DB}', 333), ('\u{02DC}', 333), ('\u{02DD}', 333), ('\u{2013}', 500), ('\u{2014}', 1000), + ('\u{2018}', 333), ('\u{2019}', 333), ('\u{201A}', 333), ('\u{201C}', 444), ('\u{201D}', 444), ('\u{201E}', 444), + ('\u{2020}', 500), ('\u{2021}', 500), ('\u{2022}', 350), ('\u{2026}', 1000), ('\u{2030}', 1000), ('\u{2039}', 333), + ('\u{203A}', 333), ('\u{2044}', 167), ('\u{20AC}', 500), ('\u{2122}', 980), ('\u{2212}', 564), ('\u{FB01}', 556), + ('\u{FB02}', 556), +]; + +#[rustfmt::skip] +static TIMES_BOLD: &[(char, u16)] = &[ + (' ', 250), ('!', 333), ('"', 555), ('#', 500), ('$', 500), ('%', 1000), + ('&', 833), ('\'', 278), ('(', 333), (')', 333), ('*', 500), ('+', 570), + (',', 250), ('-', 333), ('.', 250), ('/', 278), ('0', 500), ('1', 500), + ('2', 500), ('3', 500), ('4', 500), ('5', 500), ('6', 500), ('7', 500), + ('8', 500), ('9', 500), (':', 333), (';', 333), ('<', 570), ('=', 570), + ('>', 570), ('?', 500), ('@', 930), ('A', 722), ('B', 667), ('C', 722), + ('D', 722), ('E', 667), ('F', 611), ('G', 778), ('H', 778), ('I', 389), + ('J', 500), ('K', 778), ('L', 667), ('M', 944), ('N', 722), ('O', 778), + ('P', 611), ('Q', 778), ('R', 722), ('S', 556), ('T', 667), ('U', 722), + ('V', 722), ('W', 1000), ('X', 722), ('Y', 722), ('Z', 667), ('[', 333), + ('\\', 278), (']', 333), ('^', 581), ('_', 500), ('`', 333), ('a', 500), + ('b', 556), ('c', 444), ('d', 556), ('e', 444), ('f', 333), ('g', 500), + ('h', 556), ('i', 278), ('j', 333), ('k', 556), ('l', 278), ('m', 833), + ('n', 556), ('o', 500), ('p', 556), ('q', 556), ('r', 444), ('s', 389), + ('t', 333), ('u', 556), ('v', 500), ('w', 722), ('x', 500), ('y', 500), + ('z', 444), ('{', 394), ('|', 220), ('}', 394), ('~', 520), ('\u{00A1}', 333), + ('\u{00A2}', 500), ('\u{00A3}', 500), ('\u{00A4}', 500), ('\u{00A5}', 500), ('\u{00A6}', 220), ('\u{00A7}', 500), + ('\u{00A8}', 333), ('\u{00A9}', 747), ('\u{00AA}', 300), ('\u{00AB}', 500), ('\u{00AC}', 570), ('\u{00AE}', 747), + ('\u{00AF}', 333), ('\u{00B0}', 400), ('\u{00B1}', 570), ('\u{00B2}', 300), ('\u{00B3}', 300), ('\u{00B4}', 333), + ('\u{00B5}', 556), ('\u{00B6}', 540), ('\u{00B7}', 250), ('\u{00B8}', 333), ('\u{00B9}', 300), ('\u{00BA}', 330), + ('\u{00BB}', 500), ('\u{00BC}', 750), ('\u{00BD}', 750), ('\u{00BE}', 750), ('\u{00BF}', 500), ('\u{00C0}', 722), + ('\u{00C1}', 722), ('\u{00C2}', 722), ('\u{00C3}', 722), ('\u{00C4}', 722), ('\u{00C5}', 722), ('\u{00C6}', 1000), + ('\u{00C7}', 722), ('\u{00C8}', 667), ('\u{00C9}', 667), ('\u{00CA}', 667), ('\u{00CB}', 667), ('\u{00CC}', 389), + ('\u{00CD}', 389), ('\u{00CE}', 389), ('\u{00CF}', 389), ('\u{00D0}', 722), ('\u{00D1}', 722), ('\u{00D2}', 778), + ('\u{00D3}', 778), ('\u{00D4}', 778), ('\u{00D5}', 778), ('\u{00D6}', 778), ('\u{00D7}', 570), ('\u{00D8}', 778), + ('\u{00D9}', 722), ('\u{00DA}', 722), ('\u{00DB}', 722), ('\u{00DC}', 722), ('\u{00DD}', 722), ('\u{00DE}', 611), + ('\u{00DF}', 556), ('\u{00E0}', 500), ('\u{00E1}', 500), ('\u{00E2}', 500), ('\u{00E3}', 500), ('\u{00E4}', 500), + ('\u{00E5}', 500), ('\u{00E6}', 722), ('\u{00E7}', 444), ('\u{00E8}', 444), ('\u{00E9}', 444), ('\u{00EA}', 444), + ('\u{00EB}', 444), ('\u{00EC}', 278), ('\u{00ED}', 278), ('\u{00EE}', 278), ('\u{00EF}', 278), ('\u{00F0}', 500), + ('\u{00F1}', 556), ('\u{00F2}', 500), ('\u{00F3}', 500), ('\u{00F4}', 500), ('\u{00F5}', 500), ('\u{00F6}', 500), + ('\u{00F7}', 570), ('\u{00F8}', 500), ('\u{00F9}', 556), ('\u{00FA}', 556), ('\u{00FB}', 556), ('\u{00FC}', 556), + ('\u{00FD}', 500), ('\u{00FE}', 556), ('\u{00FF}', 500), ('\u{0131}', 278), ('\u{0141}', 667), ('\u{0142}', 278), + ('\u{0152}', 1000), ('\u{0153}', 722), ('\u{0160}', 556), ('\u{0161}', 389), ('\u{0178}', 722), ('\u{017D}', 667), + ('\u{017E}', 444), ('\u{0192}', 500), ('\u{02C6}', 333), ('\u{02C7}', 333), ('\u{02D8}', 333), ('\u{02D9}', 333), + ('\u{02DA}', 333), ('\u{02DB}', 333), ('\u{02DC}', 333), ('\u{02DD}', 333), ('\u{2013}', 500), ('\u{2014}', 1000), + ('\u{2018}', 333), ('\u{2019}', 333), ('\u{201A}', 333), ('\u{201C}', 500), ('\u{201D}', 500), ('\u{201E}', 500), + ('\u{2020}', 500), ('\u{2021}', 500), ('\u{2022}', 350), ('\u{2026}', 1000), ('\u{2030}', 1000), ('\u{2039}', 333), + ('\u{203A}', 333), ('\u{2044}', 167), ('\u{20AC}', 500), ('\u{2122}', 1000), ('\u{2212}', 570), ('\u{FB01}', 556), + ('\u{FB02}', 556), +]; + +#[rustfmt::skip] +static TIMES_ITALIC: &[(char, u16)] = &[ + (' ', 250), ('!', 333), ('"', 420), ('#', 500), ('$', 500), ('%', 833), + ('&', 778), ('\'', 214), ('(', 333), (')', 333), ('*', 500), ('+', 675), + (',', 250), ('-', 333), ('.', 250), ('/', 278), ('0', 500), ('1', 500), + ('2', 500), ('3', 500), ('4', 500), ('5', 500), ('6', 500), ('7', 500), + ('8', 500), ('9', 500), (':', 333), (';', 333), ('<', 675), ('=', 675), + ('>', 675), ('?', 500), ('@', 920), ('A', 611), ('B', 611), ('C', 667), + ('D', 722), ('E', 611), ('F', 611), ('G', 722), ('H', 722), ('I', 333), + ('J', 444), ('K', 667), ('L', 556), ('M', 833), ('N', 667), ('O', 722), + ('P', 611), ('Q', 722), ('R', 611), ('S', 500), ('T', 556), ('U', 722), + ('V', 611), ('W', 833), ('X', 611), ('Y', 556), ('Z', 556), ('[', 389), + ('\\', 278), (']', 389), ('^', 422), ('_', 500), ('`', 333), ('a', 500), + ('b', 500), ('c', 444), ('d', 500), ('e', 444), ('f', 278), ('g', 500), + ('h', 500), ('i', 278), ('j', 278), ('k', 444), ('l', 278), ('m', 722), + ('n', 500), ('o', 500), ('p', 500), ('q', 500), ('r', 389), ('s', 389), + ('t', 278), ('u', 500), ('v', 444), ('w', 667), ('x', 444), ('y', 444), + ('z', 389), ('{', 400), ('|', 275), ('}', 400), ('~', 541), ('\u{00A1}', 389), + ('\u{00A2}', 500), ('\u{00A3}', 500), ('\u{00A4}', 500), ('\u{00A5}', 500), ('\u{00A6}', 275), ('\u{00A7}', 500), + ('\u{00A8}', 333), ('\u{00A9}', 760), ('\u{00AA}', 276), ('\u{00AB}', 500), ('\u{00AC}', 675), ('\u{00AE}', 760), + ('\u{00AF}', 333), ('\u{00B0}', 400), ('\u{00B1}', 675), ('\u{00B2}', 300), ('\u{00B3}', 300), ('\u{00B4}', 333), + ('\u{00B5}', 500), ('\u{00B6}', 523), ('\u{00B7}', 250), ('\u{00B8}', 333), ('\u{00B9}', 300), ('\u{00BA}', 310), + ('\u{00BB}', 500), ('\u{00BC}', 750), ('\u{00BD}', 750), ('\u{00BE}', 750), ('\u{00BF}', 500), ('\u{00C0}', 611), + ('\u{00C1}', 611), ('\u{00C2}', 611), ('\u{00C3}', 611), ('\u{00C4}', 611), ('\u{00C5}', 611), ('\u{00C6}', 889), + ('\u{00C7}', 667), ('\u{00C8}', 611), ('\u{00C9}', 611), ('\u{00CA}', 611), ('\u{00CB}', 611), ('\u{00CC}', 333), + ('\u{00CD}', 333), ('\u{00CE}', 333), ('\u{00CF}', 333), ('\u{00D0}', 722), ('\u{00D1}', 667), ('\u{00D2}', 722), + ('\u{00D3}', 722), ('\u{00D4}', 722), ('\u{00D5}', 722), ('\u{00D6}', 722), ('\u{00D7}', 675), ('\u{00D8}', 722), + ('\u{00D9}', 722), ('\u{00DA}', 722), ('\u{00DB}', 722), ('\u{00DC}', 722), ('\u{00DD}', 556), ('\u{00DE}', 611), + ('\u{00DF}', 500), ('\u{00E0}', 500), ('\u{00E1}', 500), ('\u{00E2}', 500), ('\u{00E3}', 500), ('\u{00E4}', 500), + ('\u{00E5}', 500), ('\u{00E6}', 667), ('\u{00E7}', 444), ('\u{00E8}', 444), ('\u{00E9}', 444), ('\u{00EA}', 444), + ('\u{00EB}', 444), ('\u{00EC}', 278), ('\u{00ED}', 278), ('\u{00EE}', 278), ('\u{00EF}', 278), ('\u{00F0}', 500), + ('\u{00F1}', 500), ('\u{00F2}', 500), ('\u{00F3}', 500), ('\u{00F4}', 500), ('\u{00F5}', 500), ('\u{00F6}', 500), + ('\u{00F7}', 675), ('\u{00F8}', 500), ('\u{00F9}', 500), ('\u{00FA}', 500), ('\u{00FB}', 500), ('\u{00FC}', 500), + ('\u{00FD}', 444), ('\u{00FE}', 500), ('\u{00FF}', 444), ('\u{0131}', 278), ('\u{0141}', 556), ('\u{0142}', 278), + ('\u{0152}', 944), ('\u{0153}', 667), ('\u{0160}', 500), ('\u{0161}', 389), ('\u{0178}', 556), ('\u{017D}', 556), + ('\u{017E}', 389), ('\u{0192}', 500), ('\u{02C6}', 333), ('\u{02C7}', 333), ('\u{02D8}', 333), ('\u{02D9}', 333), + ('\u{02DA}', 333), ('\u{02DB}', 333), ('\u{02DC}', 333), ('\u{02DD}', 333), ('\u{2013}', 500), ('\u{2014}', 889), + ('\u{2018}', 333), ('\u{2019}', 333), ('\u{201A}', 333), ('\u{201C}', 556), ('\u{201D}', 556), ('\u{201E}', 556), + ('\u{2020}', 500), ('\u{2021}', 500), ('\u{2022}', 350), ('\u{2026}', 889), ('\u{2030}', 1000), ('\u{2039}', 333), + ('\u{203A}', 333), ('\u{2044}', 167), ('\u{20AC}', 500), ('\u{2122}', 980), ('\u{2212}', 675), ('\u{FB01}', 500), + ('\u{FB02}', 500), +]; + +#[rustfmt::skip] +static TIMES_BOLDITALIC: &[(char, u16)] = &[ + (' ', 250), ('!', 389), ('"', 555), ('#', 500), ('$', 500), ('%', 833), + ('&', 778), ('\'', 278), ('(', 333), (')', 333), ('*', 500), ('+', 570), + (',', 250), ('-', 333), ('.', 250), ('/', 278), ('0', 500), ('1', 500), + ('2', 500), ('3', 500), ('4', 500), ('5', 500), ('6', 500), ('7', 500), + ('8', 500), ('9', 500), (':', 333), (';', 333), ('<', 570), ('=', 570), + ('>', 570), ('?', 500), ('@', 832), ('A', 667), ('B', 667), ('C', 667), + ('D', 722), ('E', 667), ('F', 667), ('G', 722), ('H', 778), ('I', 389), + ('J', 500), ('K', 667), ('L', 611), ('M', 889), ('N', 722), ('O', 722), + ('P', 611), ('Q', 722), ('R', 667), ('S', 556), ('T', 611), ('U', 722), + ('V', 667), ('W', 889), ('X', 667), ('Y', 611), ('Z', 611), ('[', 333), + ('\\', 278), (']', 333), ('^', 570), ('_', 500), ('`', 333), ('a', 500), + ('b', 500), ('c', 444), ('d', 500), ('e', 444), ('f', 333), ('g', 500), + ('h', 556), ('i', 278), ('j', 278), ('k', 500), ('l', 278), ('m', 778), + ('n', 556), ('o', 500), ('p', 500), ('q', 500), ('r', 389), ('s', 389), + ('t', 278), ('u', 556), ('v', 444), ('w', 667), ('x', 500), ('y', 444), + ('z', 389), ('{', 348), ('|', 220), ('}', 348), ('~', 570), ('\u{00A1}', 389), + ('\u{00A2}', 500), ('\u{00A3}', 500), ('\u{00A4}', 500), ('\u{00A5}', 500), ('\u{00A6}', 220), ('\u{00A7}', 500), + ('\u{00A8}', 333), ('\u{00A9}', 747), ('\u{00AA}', 266), ('\u{00AB}', 500), ('\u{00AC}', 606), ('\u{00AE}', 747), + ('\u{00AF}', 333), ('\u{00B0}', 400), ('\u{00B1}', 570), ('\u{00B2}', 300), ('\u{00B3}', 300), ('\u{00B4}', 333), + ('\u{00B5}', 576), ('\u{00B6}', 500), ('\u{00B7}', 250), ('\u{00B8}', 333), ('\u{00B9}', 300), ('\u{00BA}', 300), + ('\u{00BB}', 500), ('\u{00BC}', 750), ('\u{00BD}', 750), ('\u{00BE}', 750), ('\u{00BF}', 500), ('\u{00C0}', 667), + ('\u{00C1}', 667), ('\u{00C2}', 667), ('\u{00C3}', 667), ('\u{00C4}', 667), ('\u{00C5}', 667), ('\u{00C6}', 944), + ('\u{00C7}', 667), ('\u{00C8}', 667), ('\u{00C9}', 667), ('\u{00CA}', 667), ('\u{00CB}', 667), ('\u{00CC}', 389), + ('\u{00CD}', 389), ('\u{00CE}', 389), ('\u{00CF}', 389), ('\u{00D0}', 722), ('\u{00D1}', 722), ('\u{00D2}', 722), + ('\u{00D3}', 722), ('\u{00D4}', 722), ('\u{00D5}', 722), ('\u{00D6}', 722), ('\u{00D7}', 570), ('\u{00D8}', 722), + ('\u{00D9}', 722), ('\u{00DA}', 722), ('\u{00DB}', 722), ('\u{00DC}', 722), ('\u{00DD}', 611), ('\u{00DE}', 611), + ('\u{00DF}', 500), ('\u{00E0}', 500), ('\u{00E1}', 500), ('\u{00E2}', 500), ('\u{00E3}', 500), ('\u{00E4}', 500), + ('\u{00E5}', 500), ('\u{00E6}', 722), ('\u{00E7}', 444), ('\u{00E8}', 444), ('\u{00E9}', 444), ('\u{00EA}', 444), + ('\u{00EB}', 444), ('\u{00EC}', 278), ('\u{00ED}', 278), ('\u{00EE}', 278), ('\u{00EF}', 278), ('\u{00F0}', 500), + ('\u{00F1}', 556), ('\u{00F2}', 500), ('\u{00F3}', 500), ('\u{00F4}', 500), ('\u{00F5}', 500), ('\u{00F6}', 500), + ('\u{00F7}', 570), ('\u{00F8}', 500), ('\u{00F9}', 556), ('\u{00FA}', 556), ('\u{00FB}', 556), ('\u{00FC}', 556), + ('\u{00FD}', 444), ('\u{00FE}', 500), ('\u{00FF}', 444), ('\u{0131}', 278), ('\u{0141}', 611), ('\u{0142}', 278), + ('\u{0152}', 944), ('\u{0153}', 722), ('\u{0160}', 556), ('\u{0161}', 389), ('\u{0178}', 611), ('\u{017D}', 611), + ('\u{017E}', 389), ('\u{0192}', 500), ('\u{02C6}', 333), ('\u{02C7}', 333), ('\u{02D8}', 333), ('\u{02D9}', 333), + ('\u{02DA}', 333), ('\u{02DB}', 333), ('\u{02DC}', 333), ('\u{02DD}', 333), ('\u{2013}', 500), ('\u{2014}', 1000), + ('\u{2018}', 333), ('\u{2019}', 333), ('\u{201A}', 333), ('\u{201C}', 500), ('\u{201D}', 500), ('\u{201E}', 500), + ('\u{2020}', 500), ('\u{2021}', 500), ('\u{2022}', 350), ('\u{2026}', 1000), ('\u{2030}', 1000), ('\u{2039}', 333), + ('\u{203A}', 333), ('\u{2044}', 167), ('\u{20AC}', 500), ('\u{2122}', 1000), ('\u{2212}', 606), ('\u{FB01}', 556), + ('\u{FB02}', 556), +]; + +#[rustfmt::skip] +static SYMBOL: &[(char, u16)] = &[ + (' ', 250), ('!', 333), ('#', 500), ('%', 833), ('&', 778), ('(', 333), + (')', 333), ('+', 549), (',', 250), ('.', 250), ('/', 278), ('0', 500), + ('1', 500), ('2', 500), ('3', 500), ('4', 500), ('5', 500), ('6', 500), + ('7', 500), ('8', 500), ('9', 500), (':', 278), (';', 278), ('<', 549), + ('=', 549), ('>', 549), ('?', 444), ('[', 333), (']', 333), ('_', 500), + ('{', 480), ('|', 200), ('}', 480), ('\u{00AC}', 713), ('\u{00B0}', 400), ('\u{00B1}', 549), + ('\u{00B5}', 576), ('\u{00D7}', 549), ('\u{00F7}', 549), ('\u{0192}', 500), ('\u{0391}', 722), ('\u{0392}', 667), + ('\u{0393}', 603), ('\u{0395}', 611), ('\u{0396}', 611), ('\u{0397}', 722), ('\u{0398}', 741), ('\u{0399}', 333), + ('\u{039A}', 722), ('\u{039B}', 686), ('\u{039C}', 889), ('\u{039D}', 722), ('\u{039E}', 645), ('\u{039F}', 722), + ('\u{03A0}', 768), ('\u{03A1}', 556), ('\u{03A3}', 592), ('\u{03A4}', 611), ('\u{03A5}', 690), ('\u{03A6}', 763), + ('\u{03A7}', 722), ('\u{03A8}', 795), ('\u{03B1}', 631), ('\u{03B2}', 549), ('\u{03B3}', 411), ('\u{03B4}', 494), + ('\u{03B5}', 439), ('\u{03B6}', 494), ('\u{03B7}', 603), ('\u{03B8}', 521), ('\u{03B9}', 329), ('\u{03BA}', 549), + ('\u{03BB}', 549), ('\u{03BD}', 521), ('\u{03BE}', 493), ('\u{03BF}', 549), ('\u{03C0}', 549), ('\u{03C1}', 549), + ('\u{03C2}', 439), ('\u{03C3}', 603), ('\u{03C4}', 439), ('\u{03C5}', 576), ('\u{03C6}', 521), ('\u{03C7}', 549), + ('\u{03C8}', 686), ('\u{03C9}', 686), ('\u{03D1}', 631), ('\u{03D2}', 620), ('\u{03D5}', 603), ('\u{03D6}', 713), + ('\u{2022}', 460), ('\u{2026}', 1000), ('\u{2032}', 247), ('\u{2033}', 411), ('\u{2044}', 167), ('\u{20AC}', 750), + ('\u{2111}', 686), ('\u{2118}', 987), ('\u{211C}', 795), ('\u{2126}', 768), ('\u{2135}', 823), ('\u{2190}', 987), + ('\u{2191}', 603), ('\u{2192}', 987), ('\u{2193}', 603), ('\u{2194}', 1042), ('\u{21B5}', 658), ('\u{21D0}', 987), + ('\u{21D1}', 603), ('\u{21D2}', 987), ('\u{21D3}', 603), ('\u{21D4}', 1042), ('\u{2200}', 713), ('\u{2202}', 494), + ('\u{2203}', 549), ('\u{2205}', 823), ('\u{2206}', 612), ('\u{2207}', 713), ('\u{2208}', 713), ('\u{2209}', 713), + ('\u{220B}', 439), ('\u{220F}', 823), ('\u{2211}', 713), ('\u{2212}', 549), ('\u{2217}', 500), ('\u{221A}', 549), + ('\u{221D}', 713), ('\u{221E}', 713), ('\u{2220}', 768), ('\u{2227}', 603), ('\u{2228}', 603), ('\u{2229}', 768), + ('\u{222A}', 768), ('\u{222B}', 274), ('\u{2234}', 863), ('\u{223C}', 549), ('\u{2245}', 549), ('\u{2248}', 549), + ('\u{2260}', 549), ('\u{2261}', 549), ('\u{2264}', 549), ('\u{2265}', 549), ('\u{2282}', 713), ('\u{2283}', 713), + ('\u{2284}', 713), ('\u{2286}', 713), ('\u{2287}', 713), ('\u{2295}', 768), ('\u{2297}', 768), ('\u{22A5}', 658), + ('\u{22C5}', 250), ('\u{2320}', 686), ('\u{2321}', 686), ('\u{2329}', 329), ('\u{232A}', 329), ('\u{25CA}', 494), + ('\u{2660}', 753), ('\u{2663}', 753), ('\u{2665}', 753), ('\u{2666}', 753), ('\u{F6D9}', 790), ('\u{F6DA}', 790), + ('\u{F6DB}', 890), ('\u{F8E5}', 500), ('\u{F8E6}', 603), ('\u{F8E7}', 1000), ('\u{F8E8}', 790), ('\u{F8E9}', 790), + ('\u{F8EA}', 786), ('\u{F8EB}', 384), ('\u{F8EC}', 384), ('\u{F8ED}', 384), ('\u{F8EE}', 384), ('\u{F8EF}', 384), + ('\u{F8F0}', 384), ('\u{F8F1}', 494), ('\u{F8F2}', 494), ('\u{F8F3}', 494), ('\u{F8F4}', 494), ('\u{F8F5}', 686), + ('\u{F8F6}', 384), ('\u{F8F7}', 384), ('\u{F8F8}', 384), ('\u{F8F9}', 384), ('\u{F8FA}', 384), ('\u{F8FB}', 384), + ('\u{F8FC}', 494), ('\u{F8FD}', 494), ('\u{F8FE}', 494), ('\u{F8FF}', 790), +]; + +#[rustfmt::skip] +static ZAPFDINGBATS: &[(char, u16)] = &[ + (' ', 278), ('\u{2192}', 838), ('\u{2194}', 1016), ('\u{2195}', 458), ('\u{2460}', 788), ('\u{2461}', 788), + ('\u{2462}', 788), ('\u{2463}', 788), ('\u{2464}', 788), ('\u{2465}', 788), ('\u{2466}', 788), ('\u{2467}', 788), + ('\u{2468}', 788), ('\u{2469}', 788), ('\u{25A0}', 761), ('\u{25B2}', 892), ('\u{25BC}', 892), ('\u{25C6}', 788), + ('\u{25CF}', 791), ('\u{25D7}', 438), ('\u{2605}', 816), ('\u{260E}', 719), ('\u{261B}', 960), ('\u{261E}', 939), + ('\u{2660}', 626), ('\u{2663}', 776), ('\u{2665}', 694), ('\u{2666}', 595), ('\u{2701}', 974), ('\u{2702}', 961), + ('\u{2703}', 974), ('\u{2704}', 980), ('\u{2706}', 789), ('\u{2707}', 790), ('\u{2708}', 791), ('\u{2709}', 690), + ('\u{270C}', 549), ('\u{270D}', 855), ('\u{270E}', 911), ('\u{270F}', 933), ('\u{2710}', 911), ('\u{2711}', 945), + ('\u{2712}', 974), ('\u{2713}', 755), ('\u{2714}', 846), ('\u{2715}', 762), ('\u{2716}', 761), ('\u{2717}', 571), + ('\u{2718}', 677), ('\u{2719}', 763), ('\u{271A}', 760), ('\u{271B}', 759), ('\u{271C}', 754), ('\u{271D}', 494), + ('\u{271E}', 552), ('\u{271F}', 537), ('\u{2720}', 577), ('\u{2721}', 692), ('\u{2722}', 786), ('\u{2723}', 788), + ('\u{2724}', 788), ('\u{2725}', 790), ('\u{2726}', 793), ('\u{2727}', 794), ('\u{2729}', 823), ('\u{272A}', 789), + ('\u{272B}', 841), ('\u{272C}', 823), ('\u{272D}', 833), ('\u{272E}', 816), ('\u{272F}', 831), ('\u{2730}', 923), + ('\u{2731}', 744), ('\u{2732}', 723), ('\u{2733}', 749), ('\u{2734}', 790), ('\u{2735}', 792), ('\u{2736}', 695), + ('\u{2737}', 776), ('\u{2738}', 768), ('\u{2739}', 792), ('\u{273A}', 759), ('\u{273B}', 707), ('\u{273C}', 708), + ('\u{273D}', 682), ('\u{273E}', 701), ('\u{273F}', 826), ('\u{2740}', 815), ('\u{2741}', 789), ('\u{2742}', 789), + ('\u{2743}', 707), ('\u{2744}', 687), ('\u{2745}', 696), ('\u{2746}', 689), ('\u{2747}', 786), ('\u{2748}', 787), + ('\u{2749}', 713), ('\u{274A}', 791), ('\u{274B}', 785), ('\u{274D}', 873), ('\u{274F}', 762), ('\u{2750}', 762), + ('\u{2751}', 759), ('\u{2752}', 759), ('\u{2756}', 784), ('\u{2758}', 138), ('\u{2759}', 277), ('\u{275A}', 415), + ('\u{275B}', 392), ('\u{275C}', 392), ('\u{275D}', 668), ('\u{275E}', 668), ('\u{2761}', 732), ('\u{2762}', 544), + ('\u{2763}', 544), ('\u{2764}', 910), ('\u{2765}', 667), ('\u{2766}', 760), ('\u{2767}', 760), ('\u{2768}', 390), + ('\u{2769}', 390), ('\u{276A}', 317), ('\u{276B}', 317), ('\u{276C}', 276), ('\u{276D}', 276), ('\u{276E}', 509), + ('\u{276F}', 509), ('\u{2770}', 410), ('\u{2771}', 410), ('\u{2772}', 234), ('\u{2773}', 234), ('\u{2774}', 334), + ('\u{2775}', 334), ('\u{2776}', 788), ('\u{2777}', 788), ('\u{2778}', 788), ('\u{2779}', 788), ('\u{277A}', 788), + ('\u{277B}', 788), ('\u{277C}', 788), ('\u{277D}', 788), ('\u{277E}', 788), ('\u{277F}', 788), ('\u{2780}', 788), + ('\u{2781}', 788), ('\u{2782}', 788), ('\u{2783}', 788), ('\u{2784}', 788), ('\u{2785}', 788), ('\u{2786}', 788), + ('\u{2787}', 788), ('\u{2788}', 788), ('\u{2789}', 788), ('\u{278A}', 788), ('\u{278B}', 788), ('\u{278C}', 788), + ('\u{278D}', 788), ('\u{278E}', 788), ('\u{278F}', 788), ('\u{2790}', 788), ('\u{2791}', 788), ('\u{2792}', 788), + ('\u{2793}', 788), ('\u{2794}', 894), ('\u{2798}', 748), ('\u{2799}', 924), ('\u{279A}', 748), ('\u{279B}', 918), + ('\u{279C}', 927), ('\u{279D}', 928), ('\u{279E}', 928), ('\u{279F}', 834), ('\u{27A0}', 873), ('\u{27A1}', 828), + ('\u{27A2}', 924), ('\u{27A3}', 924), ('\u{27A4}', 917), ('\u{27A5}', 930), ('\u{27A6}', 931), ('\u{27A7}', 463), + ('\u{27A8}', 883), ('\u{27A9}', 836), ('\u{27AA}', 836), ('\u{27AB}', 867), ('\u{27AC}', 867), ('\u{27AD}', 696), + ('\u{27AE}', 696), ('\u{27AF}', 874), ('\u{27B1}', 874), ('\u{27B2}', 760), ('\u{27B3}', 946), ('\u{27B4}', 771), + ('\u{27B5}', 865), ('\u{27B6}', 771), ('\u{27B7}', 888), ('\u{27B8}', 967), ('\u{27B9}', 888), ('\u{27BA}', 831), + ('\u{27BB}', 873), ('\u{27BC}', 927), ('\u{27BD}', 970), ('\u{27BE}', 918), +]; + +#[rustfmt::skip] +static SYMBOL_ENCODING: &[(u8, char)] = &[ + (0x20, ' '), (0x21, '!'), (0x22, '\u{2200}'), (0x23, '#'), (0x24, '\u{2203}'), (0x25, '%'), + (0x26, '&'), (0x27, '\u{220B}'), (0x28, '('), (0x29, ')'), (0x2A, '\u{2217}'), (0x2B, '+'), + (0x2C, ','), (0x2D, '\u{2212}'), (0x2E, '.'), (0x2F, '/'), (0x30, '0'), (0x31, '1'), + (0x32, '2'), (0x33, '3'), (0x34, '4'), (0x35, '5'), (0x36, '6'), (0x37, '7'), + (0x38, '8'), (0x39, '9'), (0x3A, ':'), (0x3B, ';'), (0x3C, '<'), (0x3D, '='), + (0x3E, '>'), (0x3F, '?'), (0x40, '\u{2245}'), (0x41, '\u{0391}'), (0x42, '\u{0392}'), (0x43, '\u{03A7}'), + (0x44, '\u{2206}'), (0x45, '\u{0395}'), (0x46, '\u{03A6}'), (0x47, '\u{0393}'), (0x48, '\u{0397}'), (0x49, '\u{0399}'), + (0x4A, '\u{03D1}'), (0x4B, '\u{039A}'), (0x4C, '\u{039B}'), (0x4D, '\u{039C}'), (0x4E, '\u{039D}'), (0x4F, '\u{039F}'), + (0x50, '\u{03A0}'), (0x51, '\u{0398}'), (0x52, '\u{03A1}'), (0x53, '\u{03A3}'), (0x54, '\u{03A4}'), (0x55, '\u{03A5}'), + (0x56, '\u{03C2}'), (0x57, '\u{2126}'), (0x58, '\u{039E}'), (0x59, '\u{03A8}'), (0x5A, '\u{0396}'), (0x5B, '['), + (0x5C, '\u{2234}'), (0x5D, ']'), (0x5E, '\u{22A5}'), (0x5F, '_'), (0x60, '\u{F8E5}'), (0x61, '\u{03B1}'), + (0x62, '\u{03B2}'), (0x63, '\u{03C7}'), (0x64, '\u{03B4}'), (0x65, '\u{03B5}'), (0x66, '\u{03C6}'), (0x67, '\u{03B3}'), + (0x68, '\u{03B7}'), (0x69, '\u{03B9}'), (0x6A, '\u{03D5}'), (0x6B, '\u{03BA}'), (0x6C, '\u{03BB}'), (0x6D, '\u{00B5}'), + (0x6E, '\u{03BD}'), (0x6F, '\u{03BF}'), (0x70, '\u{03C0}'), (0x71, '\u{03B8}'), (0x72, '\u{03C1}'), (0x73, '\u{03C3}'), + (0x74, '\u{03C4}'), (0x75, '\u{03C5}'), (0x76, '\u{03D6}'), (0x77, '\u{03C9}'), (0x78, '\u{03BE}'), (0x79, '\u{03C8}'), + (0x7A, '\u{03B6}'), (0x7B, '{'), (0x7C, '|'), (0x7D, '}'), (0x7E, '\u{223C}'), (0xA0, '\u{20AC}'), + (0xA1, '\u{03D2}'), (0xA2, '\u{2032}'), (0xA3, '\u{2264}'), (0xA4, '\u{2044}'), (0xA5, '\u{221E}'), (0xA6, '\u{0192}'), + (0xA7, '\u{2663}'), (0xA8, '\u{2666}'), (0xA9, '\u{2665}'), (0xAA, '\u{2660}'), (0xAB, '\u{2194}'), (0xAC, '\u{2190}'), + (0xAD, '\u{2191}'), (0xAE, '\u{2192}'), (0xAF, '\u{2193}'), (0xB0, '\u{00B0}'), (0xB1, '\u{00B1}'), (0xB2, '\u{2033}'), + (0xB3, '\u{2265}'), (0xB4, '\u{00D7}'), (0xB5, '\u{221D}'), (0xB6, '\u{2202}'), (0xB7, '\u{2022}'), (0xB8, '\u{00F7}'), + (0xB9, '\u{2260}'), (0xBA, '\u{2261}'), (0xBB, '\u{2248}'), (0xBC, '\u{2026}'), (0xBD, '\u{F8E6}'), (0xBE, '\u{F8E7}'), + (0xBF, '\u{21B5}'), (0xC0, '\u{2135}'), (0xC1, '\u{2111}'), (0xC2, '\u{211C}'), (0xC3, '\u{2118}'), (0xC4, '\u{2297}'), + (0xC5, '\u{2295}'), (0xC6, '\u{2205}'), (0xC7, '\u{2229}'), (0xC8, '\u{222A}'), (0xC9, '\u{2283}'), (0xCA, '\u{2287}'), + (0xCB, '\u{2284}'), (0xCC, '\u{2282}'), (0xCD, '\u{2286}'), (0xCE, '\u{2208}'), (0xCF, '\u{2209}'), (0xD0, '\u{2220}'), + (0xD1, '\u{2207}'), (0xD2, '\u{F6DA}'), (0xD3, '\u{F6D9}'), (0xD4, '\u{F6DB}'), (0xD5, '\u{220F}'), (0xD6, '\u{221A}'), + (0xD7, '\u{22C5}'), (0xD8, '\u{00AC}'), (0xD9, '\u{2227}'), (0xDA, '\u{2228}'), (0xDB, '\u{21D4}'), (0xDC, '\u{21D0}'), + (0xDD, '\u{21D1}'), (0xDE, '\u{21D2}'), (0xDF, '\u{21D3}'), (0xE0, '\u{25CA}'), (0xE1, '\u{2329}'), (0xE2, '\u{F8E8}'), + (0xE3, '\u{F8E9}'), (0xE4, '\u{F8EA}'), (0xE5, '\u{2211}'), (0xE6, '\u{F8EB}'), (0xE7, '\u{F8EC}'), (0xE8, '\u{F8ED}'), + (0xE9, '\u{F8EE}'), (0xEA, '\u{F8EF}'), (0xEB, '\u{F8F0}'), (0xEC, '\u{F8F1}'), (0xED, '\u{F8F2}'), (0xEE, '\u{F8F3}'), + (0xEF, '\u{F8F4}'), (0xF1, '\u{232A}'), (0xF2, '\u{222B}'), (0xF3, '\u{2320}'), (0xF4, '\u{F8F5}'), (0xF5, '\u{2321}'), + (0xF6, '\u{F8F6}'), (0xF7, '\u{F8F7}'), (0xF8, '\u{F8F8}'), (0xF9, '\u{F8F9}'), (0xFA, '\u{F8FA}'), (0xFB, '\u{F8FB}'), + (0xFC, '\u{F8FC}'), (0xFD, '\u{F8FD}'), (0xFE, '\u{F8FE}'), +]; + +#[rustfmt::skip] +static ZAPFDINGBATS_ENCODING: &[(u8, char)] = &[ + (0x20, ' '), (0x21, '\u{2701}'), (0x22, '\u{2702}'), (0x23, '\u{2703}'), (0x24, '\u{2704}'), (0x25, '\u{260E}'), + (0x26, '\u{2706}'), (0x27, '\u{2707}'), (0x28, '\u{2708}'), (0x29, '\u{2709}'), (0x2A, '\u{261B}'), (0x2B, '\u{261E}'), + (0x2C, '\u{270C}'), (0x2D, '\u{270D}'), (0x2E, '\u{270E}'), (0x2F, '\u{270F}'), (0x30, '\u{2710}'), (0x31, '\u{2711}'), + (0x32, '\u{2712}'), (0x33, '\u{2713}'), (0x34, '\u{2714}'), (0x35, '\u{2715}'), (0x36, '\u{2716}'), (0x37, '\u{2717}'), + (0x38, '\u{2718}'), (0x39, '\u{2719}'), (0x3A, '\u{271A}'), (0x3B, '\u{271B}'), (0x3C, '\u{271C}'), (0x3D, '\u{271D}'), + (0x3E, '\u{271E}'), (0x3F, '\u{271F}'), (0x40, '\u{2720}'), (0x41, '\u{2721}'), (0x42, '\u{2722}'), (0x43, '\u{2723}'), + (0x44, '\u{2724}'), (0x45, '\u{2725}'), (0x46, '\u{2726}'), (0x47, '\u{2727}'), (0x48, '\u{2605}'), (0x49, '\u{2729}'), + (0x4A, '\u{272A}'), (0x4B, '\u{272B}'), (0x4C, '\u{272C}'), (0x4D, '\u{272D}'), (0x4E, '\u{272E}'), (0x4F, '\u{272F}'), + (0x50, '\u{2730}'), (0x51, '\u{2731}'), (0x52, '\u{2732}'), (0x53, '\u{2733}'), (0x54, '\u{2734}'), (0x55, '\u{2735}'), + (0x56, '\u{2736}'), (0x57, '\u{2737}'), (0x58, '\u{2738}'), (0x59, '\u{2739}'), (0x5A, '\u{273A}'), (0x5B, '\u{273B}'), + (0x5C, '\u{273C}'), (0x5D, '\u{273D}'), (0x5E, '\u{273E}'), (0x5F, '\u{273F}'), (0x60, '\u{2740}'), (0x61, '\u{2741}'), + (0x62, '\u{2742}'), (0x63, '\u{2743}'), (0x64, '\u{2744}'), (0x65, '\u{2745}'), (0x66, '\u{2746}'), (0x67, '\u{2747}'), + (0x68, '\u{2748}'), (0x69, '\u{2749}'), (0x6A, '\u{274A}'), (0x6B, '\u{274B}'), (0x6C, '\u{25CF}'), (0x6D, '\u{274D}'), + (0x6E, '\u{25A0}'), (0x6F, '\u{274F}'), (0x70, '\u{2750}'), (0x71, '\u{2751}'), (0x72, '\u{2752}'), (0x73, '\u{25B2}'), + (0x74, '\u{25BC}'), (0x75, '\u{25C6}'), (0x76, '\u{2756}'), (0x77, '\u{25D7}'), (0x78, '\u{2758}'), (0x79, '\u{2759}'), + (0x7A, '\u{275A}'), (0x7B, '\u{275B}'), (0x7C, '\u{275C}'), (0x7D, '\u{275D}'), (0x7E, '\u{275E}'), (0x80, '\u{2768}'), + (0x81, '\u{2769}'), (0x82, '\u{276A}'), (0x83, '\u{276B}'), (0x84, '\u{276C}'), (0x85, '\u{276D}'), (0x86, '\u{276E}'), + (0x87, '\u{276F}'), (0x88, '\u{2770}'), (0x89, '\u{2771}'), (0x8A, '\u{2772}'), (0x8B, '\u{2773}'), (0x8C, '\u{2774}'), + (0x8D, '\u{2775}'), (0xA1, '\u{2761}'), (0xA2, '\u{2762}'), (0xA3, '\u{2763}'), (0xA4, '\u{2764}'), (0xA5, '\u{2765}'), + (0xA6, '\u{2766}'), (0xA7, '\u{2767}'), (0xA8, '\u{2663}'), (0xA9, '\u{2666}'), (0xAA, '\u{2665}'), (0xAB, '\u{2660}'), + (0xAC, '\u{2460}'), (0xAD, '\u{2461}'), (0xAE, '\u{2462}'), (0xAF, '\u{2463}'), (0xB0, '\u{2464}'), (0xB1, '\u{2465}'), + (0xB2, '\u{2466}'), (0xB3, '\u{2467}'), (0xB4, '\u{2468}'), (0xB5, '\u{2469}'), (0xB6, '\u{2776}'), (0xB7, '\u{2777}'), + (0xB8, '\u{2778}'), (0xB9, '\u{2779}'), (0xBA, '\u{277A}'), (0xBB, '\u{277B}'), (0xBC, '\u{277C}'), (0xBD, '\u{277D}'), + (0xBE, '\u{277E}'), (0xBF, '\u{277F}'), (0xC0, '\u{2780}'), (0xC1, '\u{2781}'), (0xC2, '\u{2782}'), (0xC3, '\u{2783}'), + (0xC4, '\u{2784}'), (0xC5, '\u{2785}'), (0xC6, '\u{2786}'), (0xC7, '\u{2787}'), (0xC8, '\u{2788}'), (0xC9, '\u{2789}'), + (0xCA, '\u{278A}'), (0xCB, '\u{278B}'), (0xCC, '\u{278C}'), (0xCD, '\u{278D}'), (0xCE, '\u{278E}'), (0xCF, '\u{278F}'), + (0xD0, '\u{2790}'), (0xD1, '\u{2791}'), (0xD2, '\u{2792}'), (0xD3, '\u{2793}'), (0xD4, '\u{2794}'), (0xD5, '\u{2192}'), + (0xD6, '\u{2194}'), (0xD7, '\u{2195}'), (0xD8, '\u{2798}'), (0xD9, '\u{2799}'), (0xDA, '\u{279A}'), (0xDB, '\u{279B}'), + (0xDC, '\u{279C}'), (0xDD, '\u{279D}'), (0xDE, '\u{279E}'), (0xDF, '\u{279F}'), (0xE0, '\u{27A0}'), (0xE1, '\u{27A1}'), + (0xE2, '\u{27A2}'), (0xE3, '\u{27A3}'), (0xE4, '\u{27A4}'), (0xE5, '\u{27A5}'), (0xE6, '\u{27A6}'), (0xE7, '\u{27A7}'), + (0xE8, '\u{27A8}'), (0xE9, '\u{27A9}'), (0xEA, '\u{27AA}'), (0xEB, '\u{27AB}'), (0xEC, '\u{27AC}'), (0xED, '\u{27AD}'), + (0xEE, '\u{27AE}'), (0xEF, '\u{27AF}'), (0xF1, '\u{27B1}'), (0xF2, '\u{27B2}'), (0xF3, '\u{27B3}'), (0xF4, '\u{27B4}'), + (0xF5, '\u{27B5}'), (0xF6, '\u{27B6}'), (0xF7, '\u{27B7}'), (0xF8, '\u{27B8}'), (0xF9, '\u{27B9}'), (0xFA, '\u{27BA}'), + (0xFB, '\u{27BB}'), (0xFC, '\u{27BC}'), (0xFD, '\u{27BD}'), (0xFE, '\u{27BE}'), +]; + +/// Every width table, for exhaustive testing. +#[cfg(test)] +static ALL_TABLES: &[(&str, &[(char, u16)])] = &[ + ("COURIER", COURIER), + ("COURIER_BOLD", COURIER_BOLD), + ("COURIER_OBLIQUE", COURIER_OBLIQUE), + ("COURIER_BOLDOBLIQUE", COURIER_BOLDOBLIQUE), + ("HELVETICA", HELVETICA), + ("HELVETICA_BOLD", HELVETICA_BOLD), + ("HELVETICA_OBLIQUE", HELVETICA_OBLIQUE), + ("HELVETICA_BOLDOBLIQUE", HELVETICA_BOLDOBLIQUE), + ("TIMES_ROMAN", TIMES_ROMAN), + ("TIMES_BOLD", TIMES_BOLD), + ("TIMES_ITALIC", TIMES_ITALIC), + ("TIMES_BOLDITALIC", TIMES_BOLDITALIC), + ("SYMBOL", SYMBOL), + ("ZAPFDINGBATS", ZAPFDINGBATS), +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn times_roman_ascii_widths() { + assert_eq!(base14_char_width("Times-Roman", ' '), Some(250)); + assert_eq!(base14_char_width("Times-Roman", 'M'), Some(889)); + assert_eq!(base14_char_width("Times-Roman", 'i'), Some(278)); + } + + #[test] + fn subset_prefix_and_aliases_normalize() { + assert_eq!( + base14_char_width("ABCDEF+Times-Bold", ' '), + base14_char_width("Times-Bold", ' ') + ); + assert!(base14_char_width("ArialMT", 'a').is_some()); + assert!(base14_char_width("TimesNewRomanPSMT", 'a').is_some()); + } + + #[test] + fn non_base14_returns_none() { + assert_eq!(base14_char_width("DejaVuSans", 'a'), None); + assert!(!is_base14_font("Garamond")); + } + + #[test] + fn builtin_encoding_resolves_symbol_and_zapf_codes() { + // Symbol 0x61 renders alpha; Zapf 0x21 renders U+2701. + assert_eq!(builtin_encoding_char("Symbol", 0x61), Some('\u{03B1}')); + assert_eq!(builtin_encoding_char("Symbol", 0xA5), Some('\u{221E}')); + assert_eq!( + builtin_encoding_char("ZapfDingbats", 0x21), + Some('\u{2701}') + ); + // Latin text fonts follow standard encodings — no builtin override. + assert_eq!(builtin_encoding_char("Times-Roman", 0x61), None); + // The resolved chars have real AFM widths. + let alpha_w = base14_char_width("Symbol", '\u{03B1}'); + assert!(alpha_w.is_some() && alpha_w != Some(500)); + } + + #[test] + fn encoding_tables_are_sorted_for_binary_search() { + for table in [SYMBOL_ENCODING, ZAPFDINGBATS_ENCODING] { + assert!(table.windows(2).all(|w| w[0].0 < w[1].0)); + } + } + + #[test] + fn tables_are_sorted_for_binary_search() { + // Every table is queried by binary search, so all of them must be + // sorted — not just a sample. + for (name, table) in ALL_TABLES { + assert!( + table.windows(2).all(|w| w[0].0 < w[1].0), + "{name} is not sorted" + ); + } + } +} diff --git a/src/extractor/content_stream.rs b/src/extractor/content_stream.rs index efad6f8..c5d3659 100644 --- a/src/extractor/content_stream.rs +++ b/src/extractor/content_stream.rs @@ -14,9 +14,9 @@ use lopdf::{Document, Encoding, Object, ObjectId}; use std::collections::HashMap; use super::fonts::{ - build_font_encodings, build_font_widths, compute_string_width_ts, descriptor_style_flags, - extract_text_from_operand, get_font_file2_obj_num, get_operand_bytes, CMapDecisionCache, - FontStyleCache, + build_font_encodings, build_font_widths, build_type3_scales, compute_string_width_ts, + descriptor_style_flags, extract_text_from_operand, get_font_file2_obj_num, get_operand_bytes, + CMapDecisionCache, FontStyleCache, }; use super::underline::UnderlineLine; use super::xobjects::{extract_form_xobject_text, get_page_xobjects, XObjectType}; @@ -177,6 +177,7 @@ pub(crate) fn extract_page_text_items( // Build font width info for accurate text positioning let font_widths = build_font_widths(doc, &fonts); + let type3_scales = build_type3_scales(doc, &fonts); // Build maps of font resource names to their base font names and ToUnicode object refs let mut font_base_names: std::collections::HashMap = @@ -513,7 +514,8 @@ pub(crate) fn extract_page_text_items( ) { let combined = multiply_matrices(&rise_adjusted(&text_matrix, text_rise), &ctm); - let rendered_size = effective_font_size(current_font_size, &combined); + let rendered_size = effective_font_size(current_font_size, &combined) + * type3_scales.get(¤t_font).copied().unwrap_or(1.0); let (x, y) = (combined[4], combined[5]); if combined[0].abs() >= combined[1].abs() { rotation_votes.horizontal += 1; @@ -684,7 +686,8 @@ pub(crate) fn extract_page_text_items( } else { rotation_votes.rotated += 1; } - let rendered_size = effective_font_size(current_font_size, &combined); + let rendered_size = effective_font_size(current_font_size, &combined) + * type3_scales.get(¤t_font).copied().unwrap_or(1.0); let base_font = font_base_names .get(¤t_font) .map(|s| s.as_str()) @@ -791,7 +794,8 @@ pub(crate) fn extract_page_text_items( } else { rotation_votes.rotated += 1; } - let rendered_size = effective_font_size(current_font_size, &combined); + let rendered_size = effective_font_size(current_font_size, &combined) + * type3_scales.get(¤t_font).copied().unwrap_or(1.0); let (x, y) = (combined[4], combined[5]); let width = w_ts_opt .map(|w_ts| { @@ -943,7 +947,8 @@ pub(crate) fn extract_page_text_items( } else { rotation_votes.rotated += 1; } - let rendered_size = effective_font_size(current_font_size, &combined); + let rendered_size = effective_font_size(current_font_size, &combined) + * type3_scales.get(¤t_font).copied().unwrap_or(1.0); let (x, y) = (combined[4], combined[5]); // Width in device space from text matrix delta let delta_ts = text_matrix[4] - start_tm[4]; diff --git a/src/extractor/fonts.rs b/src/extractor/fonts.rs index 3e7d28e..ad75ee3 100644 --- a/src/extractor/fonts.rs +++ b/src/extractor/fonts.rs @@ -138,6 +138,93 @@ pub(crate) fn build_font_widths( widths } +/// Visual-size scale factors for Type3 fonts, keyed by resource name. +/// +/// A Type3 font's glyph space maps to text space through FontMatrix, so the +/// visual height of its glyphs is `nominal_size × |matrix_y| × FontBBox +/// height`. For a well-behaved font (matrix 0.001, bbox ≈ 1000 units) that +/// factor is ≈ 1.0 and the nominal size is already right. TeX PK bitmap +/// fonts (dvips → Distiller) instead use FontMatrix [1 0 0 -1 0 0] with +/// nominal sizes like 0.12, which makes every downstream font-size heuristic +/// (drop caps, sub/superscripts, small-font tables, line heights) see +/// nonsense. Fonts without a usable FontBBox are omitted (treated as 1.0). +pub(crate) fn build_type3_scales( + doc: &Document, + fonts: &std::collections::BTreeMap, &lopdf::Dictionary>, +) -> HashMap { + let mut scales = HashMap::new(); + for (font_name, font_dict) in fonts { + let is_type3 = font_dict + .get(b"Subtype") + .ok() + .and_then(|o| o.as_name().ok()) + .is_some_and(|n| n == b"Type3"); + if !is_type3 { + continue; + } + // Array elements may themselves be indirect references per PDF + // syntax — resolve before reading the numeric value. + let num = |o: &Object| { + let resolved = match o { + Object::Reference(r) => match doc.get_object(*r) { + Ok(inner) => inner, + Err(_) => return 0.0, + }, + other => other, + }; + match resolved { + Object::Integer(i) => *i as f32, + Object::Real(r) => *r, + _ => 0.0, + } + }; + let Some(matrix) = font_dict + .get(b"FontMatrix") + .ok() + .and_then(|o| resolve_array(doc, o)) + else { + continue; + }; + let Some(bbox) = font_dict + .get(b"FontBBox") + .ok() + .and_then(|o| resolve_array(doc, o)) + else { + continue; + }; + if matrix.len() < 4 || bbox.len() < 4 { + continue; + } + let scale_y = (num(&matrix[2]).powi(2) + num(&matrix[3]).powi(2)).sqrt(); + let bbox_h = (num(&bbox[3]) - num(&bbox[1])).abs(); + let scale = bbox_h * scale_y; + + // `scale` is the glyph box measured in text-space units. For a + // self-consistent font it lands near 1.0 — the FontMatrix is the + // reciprocal of the glyph-space em by construction — so the Tf + // operand is already the rendered size and must be left alone. + // A modest deviation is normal and must NOT trigger rescaling: + // FontBBox is the glyph bounding box, not the em box, so it is + // routinely somewhat smaller (descender..ascender ≈ 0.7) or larger + // (tall accents > 1.0). + // + // Only a wildly inconsistent font gets renormalized. dvips/PK + // bitmap fonts declare [1 0 0 -1 0 0] with glyphs spanning + // hundreds of units, giving scale ≈ 159 against a nominal size of + // 0.12pt — there the declared size carries no information. The + // band is deliberately wide so that only that class qualifies, + // while any matrix scale (including non-standard ones like 0.005 + // with a full-em bbox, scale = 5.0) is judged on the product + // rather than on the matrix alone. + const CONSISTENT_LO: f32 = 0.25; + const CONSISTENT_HI: f32 = 4.0; + if scale.is_finite() && scale > 0.0 && !(CONSISTENT_LO..=CONSISTENT_HI).contains(&scale) { + scales.insert(String::from_utf8_lossy(font_name).to_string(), scale); + } + } + scales +} + /// Parse font widths from a font dictionary, dispatching by Subtype pub(crate) fn parse_font_widths( doc: &Document, @@ -149,11 +236,71 @@ pub(crate) fn parse_font_widths( match subtype_name { b"Type0" => parse_type0_widths(doc, font_dict), - b"Type1" | b"TrueType" | b"MMType1" | b"Type3" => parse_simple_font_widths(doc, font_dict), + b"Type1" | b"TrueType" | b"MMType1" => parse_simple_font_widths(doc, font_dict) + .or_else(|| base14_fallback_widths(doc, font_dict)), + b"Type3" => parse_simple_font_widths(doc, font_dict), _ => None, } } +/// Fallback metrics for non-embedded base-14 fonts whose dictionary omits +/// `/FirstChar`/`/Widths` (legal per the PDF spec — the reader must supply +/// standard-font metrics). Without this, every glyph advances 0 and all +/// downstream gap-based logic (space synthesis, script detection, table +/// columns) collapses — common in 1990s dvips/Distiller PDFs. +/// +/// Widths are resolved per code through the font's Differences encoding when +/// present, falling back to the same single-byte decode the text extractor +/// uses (cp1252-style smart punctuation for 0x80..=0x9F, Latin-1 elsewhere) — +/// so the width of a code always matches the char we extract for it. +fn base14_fallback_widths(doc: &Document, font_dict: &lopdf::Dictionary) -> Option { + let base_font = font_dict + .get(b"BaseFont") + .ok() + .and_then(|o| o.as_name().ok()) + .map(|n| String::from_utf8_lossy(n).to_string())?; + if !crate::extractor::base14::is_base14_font(&base_font) { + return None; + } + + let enc_map = parse_font_encoding(doc, font_dict) + .map(|r| r.map) + .unwrap_or_default(); + + let mut widths = HashMap::new(); + for code in 0u16..=255 { + // Resolution order: Differences override, then the font's BUILT-IN + // encoding (Symbol/ZapfDingbats glyphs live at positions unrelated + // to cp1252 — the renderer draws α for Symbol 0x61 no matter how + // the text decoder transliterates it, so the advance must be α's), + // then the cp1252-style fallback used by the text decoder. + let ch = enc_map + .get(&(code as u8)) + .copied() + .or_else(|| crate::extractor::base14::builtin_encoding_char(&base_font, code as u8)) + .unwrap_or_else(|| decode_single_byte_fallback_char(code as u8, true)); + if let Some(w) = crate::extractor::base14::base14_char_width(&base_font, ch) { + widths.insert(code, w); + } + } + let space_width = widths.get(&32).copied().unwrap_or(250); + + debug!( + " base14 fallback widths for {} ({} codes mapped)", + base_font, + widths.len() + ); + + Some(FontWidthInfo { + widths, + default_width: 500, + space_width, + is_cid: false, + units_scale: 0.001, + wmode: 0, + }) +} + /// Parse widths for simple fonts (Type1, TrueType, MMType1, Type3) /// Reads FirstChar, LastChar, and Widths array. /// For Type3 fonts, reads FontMatrix to determine the correct units_scale. @@ -1470,6 +1617,92 @@ fn score_text(text: &str) -> i32 { #[cfg(test)] mod tests { + #[test] + fn type3_scale_resolves_indirect_matrix_and_bbox_numbers() { + use lopdf::{dictionary, Document, Object}; + // FontMatrix/FontBBox elements may be indirect references per PDF + // syntax; the scale must use their resolved values, not zero. + let mut doc = Document::with_version("1.4"); + let matrix_d = doc.add_object(Object::Real(-1.0)); + let bbox_top = doc.add_object(Object::Integer(3)); + let font_dict = dictionary! { + "Type" => "Font", + "Subtype" => "Type3", + "FontMatrix" => vec![ + Object::Integer(1), + Object::Integer(0), + Object::Integer(0), + Object::Reference(matrix_d), + Object::Integer(0), + Object::Integer(0), + ], + "FontBBox" => vec![ + Object::Integer(1), + Object::Integer(-156), + Object::Integer(37), + Object::Reference(bbox_top), + ], + }; + let mut fonts = std::collections::BTreeMap::new(); + fonts.insert(b"T2".to_vec(), &font_dict); + let scales = super::build_type3_scales(&doc, &fonts); + let scale = scales.get("T2").copied().unwrap_or(1.0); + // bbox height 159 x |matrix_y| 1.0 + assert!( + (scale - 159.0).abs() < 0.5, + "scale should use resolved indirect values, got {scale}" + ); + } + + /// Build a one-font Type3 document and return its computed scale, if any. + #[cfg(test)] + fn type3_scale_for(matrix_y: f32, bbox_lo: i64, bbox_hi: i64) -> Option { + use lopdf::{dictionary, Document, Object}; + let doc = Document::with_version("1.4"); + let font_dict = dictionary! { + "Type" => "Font", + "Subtype" => "Type3", + "FontMatrix" => vec![ + Object::Real(matrix_y), Object::Integer(0), Object::Integer(0), + Object::Real(matrix_y), Object::Integer(0), Object::Integer(0), + ], + "FontBBox" => vec![ + Object::Integer(0), Object::Integer(bbox_lo), + Object::Integer(600), Object::Integer(bbox_hi), + ], + }; + let mut fonts = std::collections::BTreeMap::new(); + fonts.insert(b"T9".to_vec(), &font_dict); + super::build_type3_scales(&doc, &fonts).get("T9").copied() + } + + #[test] + fn type3_scale_skips_self_consistent_fonts() { + // Conventional 1/1000 matrix with a descender..ascender bbox of 700 + // units: scale 0.7. The Tf operand is already the rendered size, so + // renormalizing would report every size at 0.7x. + assert_eq!(type3_scale_for(0.001, -200, 500), None); + // Tall-accent bbox slightly over the em (1100 units, scale 1.1). + assert_eq!(type3_scale_for(0.001, -100, 1000), None); + } + + #[test] + fn type3_scale_applies_to_inconsistent_fonts_at_any_matrix_scale() { + // Non-standard but valid matrix (0.005) with a full-em bbox: + // scale 5.0, so the declared size is off by 5x and must be fixed. + let s = type3_scale_for(0.005, 0, 1000).expect("0.005 matrix should rescale"); + assert!((s - 5.0).abs() < 0.01, "got {s}"); + // dvips/PK bitmap pattern: unit matrix, glyphs spanning ~159 units. + let s = type3_scale_for(1.0, -156, 3).expect("PK pattern should rescale"); + assert!((s - 159.0).abs() < 0.5, "got {s}"); + } + + #[test] + fn type3_scale_ignores_degenerate_bbox() { + // [0 0 0 0] is legal and carries no size information. + assert_eq!(type3_scale_for(0.001, 0, 0), None); + } + #[test] fn texcm_math_symbols_remap() { assert_eq!( diff --git a/src/extractor/mod.rs b/src/extractor/mod.rs index e5387ac..791e851 100644 --- a/src/extractor/mod.rs +++ b/src/extractor/mod.rs @@ -2,6 +2,7 @@ //! //! This module extracts text with position information for structure detection. +mod base14; pub(crate) mod content_stream; mod fonts; mod layout; diff --git a/src/extractor/xobjects.rs b/src/extractor/xobjects.rs index a98866b..b827573 100644 --- a/src/extractor/xobjects.rs +++ b/src/extractor/xobjects.rs @@ -8,8 +8,9 @@ use lopdf::{Document, Encoding, Object, ObjectId}; use std::collections::HashMap; use super::fonts::{ - build_font_encodings, build_font_widths, compute_string_width_ts, extract_text_from_operand, - get_font_file2_obj_num, get_operand_bytes, CMapDecisionCache, FontStyleCache, + build_font_encodings, build_font_widths, build_type3_scales, compute_string_width_ts, + extract_text_from_operand, get_font_file2_obj_num, get_operand_bytes, CMapDecisionCache, + FontStyleCache, }; use super::{get_number, image_bbox_from_ctm, multiply_matrices}; @@ -166,6 +167,7 @@ fn extract_form_xobject_text_inner( // Build font width info for the form let font_widths = build_font_widths(doc, &form_fonts); + let type3_scales = build_type3_scales(doc, &form_fonts); // Build font base names and ToUnicode refs for the form let mut font_base_names: HashMap = HashMap::new(); @@ -413,7 +415,8 @@ fn extract_form_xobject_text_inner( &font_widths, ) { let combined = multiply_matrices(&text_matrix, &ctm); - let rendered_size = effective_font_size(current_font_size, &combined); + let rendered_size = effective_font_size(current_font_size, &combined) + * type3_scales.get(¤t_font).copied().unwrap_or(1.0); let (x, y) = (combined[4], combined[5]); let width = if let Some(font_info) = font_widths.get(¤t_font) { if let Some(raw_bytes) = get_operand_bytes(&op.operands[0]) { @@ -572,7 +575,8 @@ fn extract_form_xobject_text_inner( } if !sub_items.is_empty() { let combined = multiply_matrices(&text_matrix, &ctm); - let rendered_size = effective_font_size(current_font_size, &combined); + let rendered_size = effective_font_size(current_font_size, &combined) + * type3_scales.get(¤t_font).copied().unwrap_or(1.0); let base_font = font_base_names .get(¤t_font) .map(|s| s.as_str()) diff --git a/tests/fixtures/shannon-entropy-p1-2.pdf b/tests/fixtures/shannon-entropy-p1-2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f87eff5f7701b5b9ca18ab6892d270379cc5c389 GIT binary patch literal 23136 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 HcjE#8W{FPB literal 0 HcmV?d00001 diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index b3cc765..a0b1e5f 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1310,6 +1310,18 @@ fn test_snapshot_2013_app2() { assert_snapshot("2013-app2"); } +/// First two pages of Shannon's "A Mathematical Theory of Communication" +/// (1998 dvips 5.58 → Distiller 3 retypesetting). Canonical legacy-TeX PDF: +/// non-embedded base-14 fonts with no /Widths (exercises the built-in AFM +/// metrics fallback), Type3 PK bitmap math fonts with FontMatrix +/// [1 0 0 -1 0 0] (exercises visual-size scaling), a two-line embedded drop +/// cap, indent-only paragraph breaks, and display math that must not be +/// detected as tables or headings. +#[test] +fn test_snapshot_shannon_entropy() { + assert_snapshot("shannon-entropy-p1-2"); +} + // ============================================================================ // Pages Needing OCR Tests // ============================================================================ diff --git a/tests/snapshots/shannon-entropy-p1-2.md b/tests/snapshots/shannon-entropy-p1-2.md new file mode 100644 index 0000000..a878e70 --- /dev/null +++ b/tests/snapshots/shannon-entropy-p1-2.md @@ -0,0 +1,43 @@ +Reprinted with corrections from *The Bell System Technical Journal,* Vol. 27, pp. 379–423, 623–656, July, October, 1948. + +## A Mathematical Theory of Communication + +### By C. E. SHANNON + +INTRODUCTION + +HE recent development of various methods of modulation such as PCM and PPM which exchange + +# Tbandwidth for signal-to-noise ratio has intensified the interest in a general theory of communication. A + +basis for such a theory is contained in the important papers of Nyquist¹ and Hartley² on this subject. In the present paper we will extend the theory to include a number of new factors, in particular the effect of noise in the channel, and the savings possible due to the statistical structure of the original message and due to the nature of the final destination of the information. The fundamental problem of communication is that of reproducing at one point either exactly or ap- proximately a message selected at another point. Frequently the messages have *meaning*; that is they refer to or are correlated according to some system with certain physical or conceptual entities. These semantic aspects of communication are irrelevant to the engineering problem. The significant aspect is that the actual message is one *selected from a set* of possible messages. The system must be designed to operate for each possible selection, not just the one which will actually be chosen since this is unknown at the time of design. If the number of messages in the set is finite then this number or any monotonic function of this number can be regarded as a measure of the information produced when one message is chosen from the set, all choices being equally likely. As was pointed out by Hartley the most natural choice is the logarithmic function. Although this definition must be generalized considerably when we consider the influence of the statistics of the message and when we have a continuous range of messages, we will in all cases use an essentially logarithmic measure. The logarithmic measure is more convenient for various reasons: + +1. It is practically more useful. Parameters of engineering importance such as time, bandwidth, number of relays, etc., tend to vary linearly with the logarithm of the number of possibilities. For example, adding one relay to a group doubles the number of possible states of the relays. It adds 1 to the base 2 logarithm of this number. Doubling the time roughly squares the number of possible messages, or doubles the logarithm, etc. +2. It is nearer to our intuitive feeling as to the proper measure. This is closely related to (1) since we in- tuitively measures entities by linear comparison with common standards. One feels, for example, that two punched cards should have twice the capacity of one for information storage, and two identical channels twice the capacity of one for transmitting information. +3. It is mathematically more suitable. Many of the limiting operations are simple in terms of the loga- rithm but would require clumsy restatement in terms of the number of possibilities. The choice of a logarithmic base corresponds to the choice of a unit for measuring information. If the +base 2 is used the resulting units may be called binary digits, or more briefly *bits,* a word suggested by + +J. W. Tukey. A device with two stable positions, such as a relay or a flip-flop circuit, can store one bit of information. *N* such devices can store*N* bits, since the total number of possible states is 2 +*N* and log₂2 *N* = *N*. If the base 10 is used the units may be called decimal digits. Since + +log₂*M* = log₁₀*M*= log₁₀2 = 3:32 log₁₀*M*; + +1 Nyquist, H., “Certain Factors Affecting Telegraph Speed,” *Bell System Technical Journal,* April 1924, p. 324; “Certain Topics in Telegraph Transmission Theory,” *A.I.E.E. Trans.,* v. 47, April 1928, p. 617. 2 Hartley, R. V. L., “Transmission of Information,” *Bell System Technical Journal,* July 1928, p. 535. + +INFORMATION SOURCE TRANSMITTER RECEIVER DESTINATION + +SIGNAL RECEIVED SIGNAL MESSAGE MESSAGE + +NOISE SOURCE + +Fig. 1 — Schematic diagram of a general communication system. + +a decimal digit is about 3 13 bits. A digit wheel on a desk computing machine has ten stable positions and therefore has a storage capacity of one decimal digit. In analytical work where integration and differentiation are involved the base *e* is sometimes useful. The resulting units of information will be called natural units. Change from the base *a* to base *b* merely requires multiplication by log*ba*. By a communication system we will mean a system of the type indicated schematically in Fig. 1. It consists of essentially five parts: + +1. An *information source* which produces a message or sequence of messages to be communicated to the receiving terminal. The message may be of various types: (a) A sequence of letters as in a telegraph of teletype system; (b) A single function of time *f* (*t*) as in radio or telephony; (c) A function of time and other variables as in black and white television — here the message may be thought of as a function *f* (*x*; *y*;*t*) of two space coordinates and time, the light intensity at point (*x*; *y*) and time *t* on a pickup tube plate; (d) Two or more functions of time, say *f* (*t*), *g*(*t*), *h*(*t*) — this is the case in “three- dimensional” sound transmission or if the system is intended to service several individual channels in multiplex; (e) Several functions of several variables — in color television the message consists of three functions *f* (*x*; *y*;*t*), *g*(*x*; *y*;*t*), *h*(*x*; *y*;*t*) defined in a three-dimensional continuum — we may also think of these three functions as components of a vector field defined in the region — similarly, several black and white television sources would produce “messages” consisting of a number of functions of three variables; (f) Various combinations also occur, for example in television with an associated audio channel. +2. A *transmitter* which operates on the message in some way to produce a signal suitable for trans- mission over the channel. In telephony this operation consists merely of changing sound pressure into a proportional electrical current. In telegraphy we have an encoding operation which produces a sequence of dots, dashes and spaces on the channel corresponding to the message. In a multiplex PCM system the different speech functions must be sampled, compressed, quantized and encoded, and finally interleaved properly to construct the signal. Vocoder systems, television and frequency modulation are other examples of complex operations applied to the message to obtain the signal. +3. The *channel* is merely the medium used to transmit the signal from transmitter to receiver. It may be a pair of wires, a coaxial cable, a band of radio frequencies, a beam of light, etc. +4. The *receiver* ordinarily performs the inverse operation of that done by the transmitter, reconstructing the message from the signal. +5. The *destination* is the person (or thing) for whom the message is intended. We wish to consider certain general problems involving communication systems. To do this it is first +necessary to represent the various elements involved as mathematical entities, suitably idealized from their +