mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
F1: exact standard-14 metrics
pdfcraft_fonts::helvetica_width was a fifteen-line guess by character class, and seven crates generated appearance streams through it: annot, edit, forms, ocr, redact, sign and xfa. Times was Helvetica x 0.9, bold was x 1.05, Courier a flat 0.6. Against the published metrics a Times capital A is 722, so that estimate said 600 -- a 17% error on a letter, compounding across a line. Std14 carries the real advance widths for all fourteen faces, indexed by character code: WinAnsiEncoding for the twelve text fonts, which is what we write into the appearance streams we generate, and their own built-in encodings for Symbol and ZapfDingbats. The tables are generated by joining the published encodings with the per-glyph widths. helvetica_width now delegates, so every call site gained exact Helvetica with no change. The three places that faked another face by scaling it -- Family in edit::added, the paragraph measure in edit::text, and the redaction overlay fonts -- now select the right face instead. So does Metrics::width in fonts::pdf, which measures existing document text when a font carries no /Widths; that path feeds redaction's glyph removal, where a wrong width means cutting the wrong glyphs. Tests spot-check the table against values typed in from the specification by hand, so it is verified against the spec rather than against whatever produced it; plus Courier being fixed pitch everywhere, full printable-ASCII and Latin-1 coverage, Symbol and ZapfDingbats not being WinAnsi, /BaseFont aliases and subset prefixes resolving, and hostile text staying finite. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
681548a47f
commit
d0d1ad8192
6 files changed
+581
-70
No files matched your search
@@ -12,7 +12,7 @@ use std::collections::{HashMap, HashSet};
|
||||
|
||||
use pdfcraft_cos::{Dict, Document, ObjRef, Object, PdfString, Stream};
|
||||
use pdfcraft_fonts::{
|
||||
GlyphError, GlyphOutline, ShapedCluster, ShapedScript, helvetica_width, literal, shape_arabic, shape_thai, shaped_glyph, win_ansi,
|
||||
GlyphError, GlyphOutline, ShapedCluster, ShapedScript, Std14, literal, shape_arabic, shape_thai, shaped_glyph, win_ansi,
|
||||
};
|
||||
use unicode_bidi::{Level, ParagraphBidiInfo};
|
||||
|
||||
@@ -67,15 +67,27 @@ impl Family {
|
||||
(family, s.contains("Bold"), s.contains("Italic") || s.contains("Oblique"))
|
||||
}
|
||||
|
||||
/// Approximate advance width of `s` (standard-14 metrics are not bundled: Helvetica widths,
|
||||
/// scaled for Times; Courier is monospaced).
|
||||
/// Advance width of `s` at `size` points, from the published standard-14 metrics.
|
||||
pub fn width(self, s: &str, size: f64, bold: bool) -> f64 {
|
||||
let w = match self {
|
||||
Family::Courier => s.chars().count() as f64 * 0.6 * size,
|
||||
Family::Times => helvetica_width(s, size) * 0.9,
|
||||
Family::Helvetica => helvetica_width(s, size),
|
||||
};
|
||||
if bold { w * 1.05 } else { w }
|
||||
self.std14(bold, false).text_width(s, size)
|
||||
}
|
||||
|
||||
/// The standard-14 face this family means at the given style.
|
||||
pub fn std14(self, bold: bool, italic: bool) -> Std14 {
|
||||
match (self, bold, italic) {
|
||||
(Family::Courier, false, false) => Std14::Courier,
|
||||
(Family::Courier, true, false) => Std14::CourierBold,
|
||||
(Family::Courier, false, true) => Std14::CourierOblique,
|
||||
(Family::Courier, true, true) => Std14::CourierBoldOblique,
|
||||
(Family::Times, false, false) => Std14::TimesRoman,
|
||||
(Family::Times, true, false) => Std14::TimesBold,
|
||||
(Family::Times, false, true) => Std14::TimesItalic,
|
||||
(Family::Times, true, true) => Std14::TimesBoldItalic,
|
||||
(Family::Helvetica, false, false) => Std14::Helvetica,
|
||||
(Family::Helvetica, true, false) => Std14::HelveticaBold,
|
||||
(Family::Helvetica, false, true) => Std14::HelveticaOblique,
|
||||
(Family::Helvetica, true, true) => Std14::HelveticaBoldOblique,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1724,13 +1724,9 @@ pub(crate) fn rewrite_block_with_limit(
|
||||
// The chosen style also determines the real Japanese fallback outlines and advances.
|
||||
let (family, bold, italic) = style.family.unwrap_or((source_family(&b.base_font), b.bold, b.italic));
|
||||
let bold = style.bold.unwrap_or(bold);
|
||||
let std_width = move |s: &str, size: f64| -> f64 {
|
||||
match family {
|
||||
crate::added::Family::Courier => s.chars().count() as f64 * 0.6 * size,
|
||||
crate::added::Family::Times => pdfcraft_fonts::helvetica_width(s, size) * 0.92,
|
||||
crate::added::Family::Helvetica => pdfcraft_fonts::helvetica_width(s, size) * if bold { 1.05 } else { 1.0 },
|
||||
}
|
||||
};
|
||||
// The face is chosen after `bold` settles, so a bold request measures with the bold metrics.
|
||||
let face = family.std14(bold, italic);
|
||||
let std_width = move |s: &str, size: f64| -> f64 { face.text_width(s, size) };
|
||||
let [dx, dy] = style.offset.unwrap_or([0.0, 0.0]);
|
||||
if !(dx.is_finite() && dy.is_finite()) {
|
||||
return Err(EditError::Invalid("the paragraph can't be moved that far".into()));
|
||||
|
||||
+11
-21
@@ -1,7 +1,9 @@
|
||||
//! pdfcraft-fonts — font metrics and encodings for generated appearances (L2).
|
||||
//!
|
||||
//! See the README: the metrics are approximations by character class (no vendor metrics files
|
||||
//! are bundled). The full font subsystem lands in M2.2/M7.
|
||||
//! Standard-14 advance widths are exact (`Std14`, ISO 32000-2 Annex D). What remains approximate
|
||||
//! is everything that needs a font *program*: this crate reads font dictionaries, never embedded
|
||||
//! programs, so it has no glyph outlines for document fonts and cannot subset or re-embed. That
|
||||
//! is phase F 4.2 in docs/plan/execution-plan.md.
|
||||
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
@@ -12,6 +14,7 @@ pub mod pdf;
|
||||
mod script;
|
||||
mod shaping;
|
||||
pub mod source_shaping;
|
||||
mod std14;
|
||||
pub use craft::{
|
||||
CRAFT_FONTS, CraftFont, SHIPPORI_MINCHO, document_arabic_font, document_hebrew_font, document_japanese_font, document_japanese_font_for_style,
|
||||
document_japanese_fonts_for_style, document_thai_font, ui_arabic_fonts, ui_chinese_fonts, ui_cjk_fonts, ui_hebrew_fonts, ui_japanese_fonts,
|
||||
@@ -21,27 +24,14 @@ pub use script::{
|
||||
GlyphError, GlyphOutline, INTER_REGULAR, MAX_SIGNATURE_CHARS, ScriptOutline, japanese_glyph, japanese_glyph_from, script_missing, script_outline,
|
||||
};
|
||||
pub use shaping::{ShapedCluster, ShapedScript, arabic_glyph, arabic_has, shape_arabic, shape_thai, shaped_glyph, thai_has};
|
||||
pub use std14::Std14;
|
||||
|
||||
/// Approximate advance of `s` in Helvetica (or Arial) at `size` points.
|
||||
/// Advance of `s` in Helvetica at `size` points, using the exact standard-14 metrics.
|
||||
///
|
||||
/// Kept as a free function because generated appearances overwhelmingly use Helvetica; reach for
|
||||
/// [`Std14`] directly for any other face, and never for a scaled approximation of this one.
|
||||
pub fn helvetica_width(s: &str, size: f64) -> f64 {
|
||||
let units: f64 = s
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
' ' | 'i' | 'j' | 'l' | '\'' | '!' | '|' | '.' | ',' | ':' | ';' | 'I' => 260.0,
|
||||
'f' | 't' | 'r' | '(' | ')' | '[' | ']' | '/' | '-' | '"' => 333.0,
|
||||
'm' => 833.0,
|
||||
'w' => 722.0,
|
||||
'M' => 833.0,
|
||||
'W' => 944.0,
|
||||
'J' | 'c' | 'k' | 's' | 'v' | 'x' | 'y' | 'z' => 500.0,
|
||||
'0'..='9' | 'a'..='z' | '$' | '#' | '?' | '_' => 556.0,
|
||||
'A'..='Z' => 680.0,
|
||||
'@' => 1015.0,
|
||||
_ if c.is_whitespace() => 260.0,
|
||||
_ => 584.0,
|
||||
})
|
||||
.sum();
|
||||
units * size / 1000.0
|
||||
Std14::Helvetica.text_width(s, size)
|
||||
}
|
||||
|
||||
/// Approximate advance of `s` at `size` points in a CJK font: full-width characters take one
|
||||
|
||||
+14
-28
@@ -19,14 +19,6 @@ enum Codes {
|
||||
Ranges(Vec<(usize, Vec<u8>, Vec<u8>)>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum Std14 {
|
||||
Helvetica,
|
||||
Times,
|
||||
Courier,
|
||||
Symbolic,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Metrics {
|
||||
codes: Codes,
|
||||
@@ -39,7 +31,8 @@ pub struct Metrics {
|
||||
/// Code → CID for embedded non-identity CMaps (`cidrange` / `cidchar`).
|
||||
cid_map: Vec<(u32, u32, u32)>,
|
||||
default: f64,
|
||||
std14: Option<Std14>,
|
||||
/// The standard-14 face to measure with when the dictionary carries no `/Widths`.
|
||||
std14: Option<crate::Std14>,
|
||||
/// Glyph units → text space (0.001, or `/FontMatrix[0]` for Type 3).
|
||||
pub scale: f64,
|
||||
/// Glyph box in text space per unit of font size.
|
||||
@@ -526,7 +519,7 @@ impl Metrics {
|
||||
cid_ranges: Vec::new(),
|
||||
cid_map: Vec::new(),
|
||||
default: 500.0,
|
||||
std14: Some(Std14::Helvetica),
|
||||
std14: Some(crate::Std14::Helvetica),
|
||||
scale: 0.001,
|
||||
ascent: 0.9,
|
||||
descent: -0.25,
|
||||
@@ -655,16 +648,10 @@ impl Metrics {
|
||||
}
|
||||
}
|
||||
} else if m.widths.is_empty() {
|
||||
let base = String::from_utf8_lossy(font.name(b"BaseFont").unwrap_or(b"")).to_ascii_lowercase();
|
||||
m.std14 = Some(if base.contains("courier") {
|
||||
Std14::Courier
|
||||
} else if base.contains("times") {
|
||||
Std14::Times
|
||||
} else if base.contains("symbol") || base.contains("dingbats") {
|
||||
Std14::Symbolic
|
||||
} else {
|
||||
Std14::Helvetica
|
||||
});
|
||||
let base = String::from_utf8_lossy(font.name(b"BaseFont").unwrap_or(b"")).into_owned();
|
||||
// The real face, bold and italic included: the published metrics differ by style,
|
||||
// so Times-Bold is not Times-Roman and neither is Helvetica scaled.
|
||||
m.std14 = crate::Std14::from_base_font(&base).or(Some(crate::Std14::Helvetica));
|
||||
}
|
||||
}
|
||||
if let Some(d) = descriptor {
|
||||
@@ -726,14 +713,13 @@ impl Metrics {
|
||||
if let Some(w) = code.checked_sub(self.first).and_then(|i| self.widths.get(i as usize)) {
|
||||
return w * self.scale;
|
||||
}
|
||||
match &self.std14 {
|
||||
Some(Std14::Courier) => 0.6,
|
||||
Some(Std14::Symbolic) => 0.75,
|
||||
Some(f) => {
|
||||
let c = char::from_u32(code).filter(|c| !c.is_control()).unwrap_or('n');
|
||||
let w = crate::helvetica_width(&c.to_string(), 1.0);
|
||||
if *f == Std14::Times { w * 0.92 } else { w }
|
||||
}
|
||||
match self.std14 {
|
||||
// A font with no /Widths is one of the standard 14; use its published metrics.
|
||||
// Codes the font's encoding leaves undefined fall back to /MissingWidth.
|
||||
Some(f) => match u8::try_from(code).map(|c| f.width(c)).unwrap_or(0.0) {
|
||||
0.0 => self.default * self.scale,
|
||||
w => w / 1000.0,
|
||||
},
|
||||
None => self.default * self.scale,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,526 @@
|
||||
//! Standard-14 font metrics (ISO 32000-2 Annex D; the Adobe Core14 AFM metrics), as advance
|
||||
//! widths in glyph space — thousandths of an em.
|
||||
//!
|
||||
//! A PDF may name any of the fourteen standard fonts without embedding a program, and the viewer
|
||||
//! has to supply the metrics. These are those metrics, exactly, replacing the character-class
|
||||
//! estimate that generated appearances used before: that estimate had Times at 0.9 x Helvetica,
|
||||
//! which makes a capital A 600 where the specification says 722.
|
||||
//!
|
||||
//! **Generated.** The tables are data, not typefaces, and are the item AGENTS.md §1.1 permits as
|
||||
//! Adobe-authored non-visual technical data; `ATTRIBUTION.toml` carries the entry with
|
||||
//! `adobe_data = true`. Each row is indexed by character **code**, not by Unicode: the twelve text
|
||||
//! fonts are indexed by WinAnsiEncoding, which is what PdfCraft writes into the appearance streams
|
||||
//! it generates, and Symbol and ZapfDingbats by their own built-in encodings. A zero means the
|
||||
//! encoding leaves that code undefined.
|
||||
|
||||
/// One of the fourteen standard fonts (ISO 32000-2 §9.6.2.2).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Std14 {
|
||||
Helvetica,
|
||||
HelveticaBold,
|
||||
HelveticaOblique,
|
||||
HelveticaBoldOblique,
|
||||
TimesRoman,
|
||||
TimesBold,
|
||||
TimesItalic,
|
||||
TimesBoldItalic,
|
||||
Courier,
|
||||
CourierBold,
|
||||
CourierOblique,
|
||||
CourierBoldOblique,
|
||||
Symbol,
|
||||
ZapfDingbats,
|
||||
}
|
||||
|
||||
impl Std14 {
|
||||
/// The font a `/BaseFont` name selects, by the spec's names and the common aliases viewers
|
||||
/// accept (`Arial` for `Helvetica`, `TimesNewRoman` for `Times`, `CourierNew` for `Courier`).
|
||||
/// A subset prefix such as `ABCDEF+Helvetica` is ignored.
|
||||
pub fn from_base_font(base: &str) -> Option<Self> {
|
||||
let base = base.rsplit('+').next().unwrap_or(base);
|
||||
let flat: String = base.chars().filter(|c| c.is_ascii_alphanumeric()).map(|c| c.to_ascii_lowercase()).collect();
|
||||
let has = |w: &str| flat.contains(w);
|
||||
let (bold, italic) = (has("bold"), has("italic") || has("oblique"));
|
||||
if has("symbol") {
|
||||
return Some(Self::Symbol);
|
||||
}
|
||||
if has("zapf") || has("dingbat") {
|
||||
return Some(Self::ZapfDingbats);
|
||||
}
|
||||
if has("courier") || has("mono") {
|
||||
return Some(match (bold, italic) {
|
||||
(false, false) => Self::Courier,
|
||||
(true, false) => Self::CourierBold,
|
||||
(false, true) => Self::CourierOblique,
|
||||
(true, true) => Self::CourierBoldOblique,
|
||||
});
|
||||
}
|
||||
if has("times") || has("serif") || has("roman") {
|
||||
return Some(match (bold, italic) {
|
||||
(false, false) => Self::TimesRoman,
|
||||
(true, false) => Self::TimesBold,
|
||||
(false, true) => Self::TimesItalic,
|
||||
(true, true) => Self::TimesBoldItalic,
|
||||
});
|
||||
}
|
||||
if has("helvetica") || has("arial") {
|
||||
return Some(match (bold, italic) {
|
||||
(false, false) => Self::Helvetica,
|
||||
(true, false) => Self::HelveticaBold,
|
||||
(false, true) => Self::HelveticaOblique,
|
||||
(true, true) => Self::HelveticaBoldOblique,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// The advance of one character `code`, in thousandths of an em. Zero where the font's
|
||||
/// encoding leaves the code undefined.
|
||||
pub fn width(self, code: u8) -> f64 {
|
||||
f64::from(WIDTHS[self as usize][code as usize])
|
||||
}
|
||||
|
||||
/// The advance of `code` at `size` points.
|
||||
pub fn advance(self, code: u8, size: f64) -> f64 {
|
||||
self.width(code) * size / 1000.0
|
||||
}
|
||||
|
||||
/// The advance of `text` at `size` points, encoded as the twelve text fonts are written:
|
||||
/// WinAnsiEncoding, with characters it cannot represent replaced as `win_ansi` replaces them.
|
||||
///
|
||||
/// Symbol and ZapfDingbats have their own built-in encodings and are not WinAnsi, so this
|
||||
/// measures them as if they were; pass codes to [`Std14::width`] for those.
|
||||
pub fn text_width(self, text: &str, size: f64) -> f64 {
|
||||
crate::win_ansi(text).iter().map(|&c| self.width(c)).sum::<f64>() * size / 1000.0
|
||||
}
|
||||
|
||||
/// Courier is the only fixed-pitch family of the fourteen.
|
||||
pub fn is_fixed_pitch(self) -> bool {
|
||||
matches!(self, Self::Courier | Self::CourierBold | Self::CourierOblique | Self::CourierBoldOblique)
|
||||
}
|
||||
|
||||
/// The `/BaseFont` name for this font, as it is written into a PDF.
|
||||
pub fn base_font(self) -> &'static str {
|
||||
BASE_FONTS[self as usize]
|
||||
}
|
||||
}
|
||||
|
||||
static BASE_FONTS: [&str; 14] = [
|
||||
"Helvetica",
|
||||
"Helvetica-Bold",
|
||||
"Helvetica-Oblique",
|
||||
"Helvetica-BoldOblique",
|
||||
"Times-Roman",
|
||||
"Times-Bold",
|
||||
"Times-Italic",
|
||||
"Times-BoldItalic",
|
||||
"Courier",
|
||||
"Courier-Bold",
|
||||
"Courier-Oblique",
|
||||
"Courier-BoldOblique",
|
||||
"Symbol",
|
||||
"ZapfDingbats",
|
||||
];
|
||||
|
||||
/// Advance widths by code, one row per [`Std14`] variant in declaration order.
|
||||
#[rustfmt::skip]
|
||||
static WIDTHS: [[u16; 256]; 14] = [
|
||||
// Helvetica
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278,
|
||||
556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556,
|
||||
1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778,
|
||||
667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556,
|
||||
333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556,
|
||||
556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 0,
|
||||
556, 0, 222, 556, 333, 1000, 556, 556, 333, 1000, 667, 333, 1000, 0, 611, 0,
|
||||
0, 222, 222, 333, 333, 350, 556, 1000, 333, 1000, 500, 333, 944, 0, 500, 667,
|
||||
278, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333,
|
||||
400, 584, 333, 333, 333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611,
|
||||
667, 667, 667, 667, 667, 667, 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, 500, 556, 556, 556, 556, 278, 278, 278, 278,
|
||||
556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 500,
|
||||
],
|
||||
// HelveticaBold
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
278, 333, 474, 556, 556, 889, 722, 238, 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,
|
||||
333, 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, 0,
|
||||
556, 0, 278, 556, 500, 1000, 556, 556, 333, 1000, 667, 333, 1000, 0, 611, 0,
|
||||
0, 278, 278, 500, 500, 350, 556, 1000, 333, 1000, 556, 333, 944, 0, 500, 667,
|
||||
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,
|
||||
],
|
||||
// HelveticaOblique
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278,
|
||||
556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556,
|
||||
1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778,
|
||||
667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556,
|
||||
333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556,
|
||||
556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 0,
|
||||
556, 0, 222, 556, 333, 1000, 556, 556, 333, 1000, 667, 333, 1000, 0, 611, 0,
|
||||
0, 222, 222, 333, 333, 350, 556, 1000, 333, 1000, 500, 333, 944, 0, 500, 667,
|
||||
278, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333,
|
||||
400, 584, 333, 333, 333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611,
|
||||
667, 667, 667, 667, 667, 667, 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, 500, 556, 556, 556, 556, 278, 278, 278, 278,
|
||||
556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 500,
|
||||
],
|
||||
// HelveticaBoldOblique
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
278, 333, 474, 556, 556, 889, 722, 238, 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,
|
||||
333, 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, 0,
|
||||
556, 0, 278, 556, 500, 1000, 556, 556, 333, 1000, 667, 333, 1000, 0, 611, 0,
|
||||
0, 278, 278, 500, 500, 350, 556, 1000, 333, 1000, 556, 333, 944, 0, 500, 667,
|
||||
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,
|
||||
],
|
||||
// TimesRoman
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
250, 333, 408, 500, 500, 833, 778, 180, 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, 0,
|
||||
500, 0, 333, 500, 444, 1000, 500, 500, 333, 1000, 556, 333, 889, 0, 611, 0,
|
||||
0, 333, 333, 444, 444, 350, 500, 1000, 333, 980, 389, 333, 722, 0, 444, 722,
|
||||
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,
|
||||
],
|
||||
// TimesBold
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
250, 333, 555, 500, 500, 1000, 833, 278, 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, 0,
|
||||
500, 0, 333, 500, 500, 1000, 500, 500, 333, 1000, 556, 333, 1000, 0, 667, 0,
|
||||
0, 333, 333, 500, 500, 350, 500, 1000, 333, 1000, 389, 333, 722, 0, 444, 722,
|
||||
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, 611, 556,
|
||||
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,
|
||||
],
|
||||
// TimesItalic
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
250, 333, 420, 500, 500, 833, 778, 214, 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, 0,
|
||||
500, 0, 333, 500, 556, 889, 500, 500, 333, 1000, 500, 333, 944, 0, 556, 0,
|
||||
0, 333, 333, 556, 556, 350, 500, 889, 333, 980, 389, 333, 667, 0, 389, 556,
|
||||
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,
|
||||
],
|
||||
// TimesBoldItalic
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
250, 389, 555, 500, 500, 833, 778, 278, 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, 0,
|
||||
500, 0, 333, 500, 500, 1000, 500, 500, 333, 1000, 556, 333, 944, 0, 611, 0,
|
||||
0, 333, 333, 500, 500, 350, 500, 1000, 333, 1000, 389, 333, 722, 0, 389, 611,
|
||||
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
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0,
|
||||
600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 0,
|
||||
0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
],
|
||||
// CourierBold
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0,
|
||||
600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 0,
|
||||
0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
],
|
||||
// CourierOblique
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0,
|
||||
600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 0,
|
||||
0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
],
|
||||
// CourierBoldOblique
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0,
|
||||
600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 0,
|
||||
0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
|
||||
],
|
||||
// Symbol
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
250, 333, 713, 500, 549, 833, 778, 439, 333, 333, 500, 549, 250, 549, 250, 278,
|
||||
500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 549, 549, 549, 444,
|
||||
549, 722, 667, 722, 612, 611, 763, 603, 722, 333, 631, 722, 686, 889, 722, 722,
|
||||
768, 741, 556, 592, 611, 690, 439, 768, 645, 795, 611, 333, 863, 333, 658, 500,
|
||||
500, 631, 549, 549, 494, 439, 521, 411, 603, 329, 603, 549, 549, 576, 521, 549,
|
||||
549, 521, 549, 603, 439, 576, 713, 686, 493, 686, 494, 480, 200, 480, 549, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
750, 620, 247, 549, 167, 713, 500, 753, 753, 753, 753, 1042, 987, 603, 987, 603,
|
||||
400, 549, 411, 549, 549, 713, 494, 460, 549, 549, 549, 549, 1000, 603, 1000, 658,
|
||||
823, 686, 795, 987, 768, 768, 823, 768, 768, 713, 713, 713, 713, 713, 713, 713,
|
||||
768, 713, 790, 790, 890, 823, 549, 250, 713, 603, 603, 1042, 987, 603, 987, 603,
|
||||
494, 329, 790, 790, 786, 713, 384, 384, 384, 384, 384, 384, 494, 494, 494, 494,
|
||||
0, 329, 274, 686, 686, 686, 384, 384, 384, 384, 384, 384, 494, 494, 494, 0,
|
||||
],
|
||||
// ZapfDingbats
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
278, 974, 961, 974, 980, 719, 789, 790, 791, 690, 960, 939, 549, 855, 911, 933,
|
||||
911, 945, 974, 755, 846, 762, 761, 571, 677, 763, 760, 759, 754, 494, 552, 537,
|
||||
577, 692, 786, 788, 788, 790, 793, 794, 816, 823, 789, 841, 823, 833, 816, 831,
|
||||
923, 744, 723, 749, 790, 792, 695, 776, 768, 792, 759, 707, 708, 682, 701, 826,
|
||||
815, 789, 789, 707, 687, 696, 689, 786, 787, 713, 791, 785, 791, 873, 761, 762,
|
||||
762, 759, 759, 892, 892, 788, 784, 438, 138, 277, 415, 392, 392, 668, 668, 0,
|
||||
390, 390, 317, 317, 276, 276, 509, 509, 410, 410, 234, 234, 334, 334, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 732, 544, 544, 910, 667, 760, 760, 776, 595, 694, 626, 788, 788, 788, 788,
|
||||
788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788,
|
||||
788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788,
|
||||
788, 788, 788, 788, 894, 838, 1016, 458, 748, 924, 748, 918, 927, 928, 928, 834,
|
||||
873, 828, 924, 924, 917, 930, 931, 463, 883, 836, 836, 867, 867, 696, 696, 874,
|
||||
0, 874, 760, 946, 771, 865, 771, 888, 967, 888, 831, 873, 927, 970, 918, 0,
|
||||
],
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Widths from the published Core14 metrics, typed in here by hand so the table is checked
|
||||
/// against the specification rather than against whatever produced it.
|
||||
#[test]
|
||||
fn widths_match_the_published_metrics() {
|
||||
let cases: &[(Std14, char, u16)] = &[
|
||||
// Helvetica
|
||||
(Std14::Helvetica, ' ', 278),
|
||||
(Std14::Helvetica, 'A', 667),
|
||||
(Std14::Helvetica, 'W', 944),
|
||||
(Std14::Helvetica, 'i', 222),
|
||||
(Std14::Helvetica, 'm', 833),
|
||||
(Std14::Helvetica, '0', 556),
|
||||
(Std14::Helvetica, '@', 1015),
|
||||
(Std14::HelveticaBold, 'A', 722),
|
||||
(Std14::HelveticaBold, 'i', 278),
|
||||
// Times: not Helvetica scaled by 0.9, which is what the old estimate assumed.
|
||||
(Std14::TimesRoman, ' ', 250),
|
||||
(Std14::TimesRoman, 'A', 722),
|
||||
(Std14::TimesRoman, 'W', 944),
|
||||
(Std14::TimesRoman, 'i', 278),
|
||||
(Std14::TimesRoman, 'm', 778),
|
||||
(Std14::TimesBold, 'A', 722),
|
||||
(Std14::TimesItalic, 'A', 611),
|
||||
// Courier is fixed pitch at 600 for every glyph.
|
||||
(Std14::Courier, ' ', 600),
|
||||
(Std14::Courier, 'A', 600),
|
||||
(Std14::Courier, 'm', 600),
|
||||
(Std14::CourierBoldOblique, 'W', 600),
|
||||
];
|
||||
for &(font, ch, want) in cases {
|
||||
let code = crate::win_ansi(&ch.to_string());
|
||||
assert_eq!(code.len(), 1, "{ch:?} is one WinAnsi byte");
|
||||
assert_eq!(font.width(code[0]) as u16, want, "{font:?} {ch:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn courier_is_fixed_pitch_everywhere_it_is_defined() {
|
||||
for font in [Std14::Courier, Std14::CourierBold, Std14::CourierOblique, Std14::CourierBoldOblique] {
|
||||
assert!(font.is_fixed_pitch());
|
||||
for code in 0..=255u8 {
|
||||
let w = font.width(code);
|
||||
assert!(w == 0.0 || w == 600.0, "{font:?} code {code} has width {w}");
|
||||
}
|
||||
}
|
||||
assert!(!Std14::Helvetica.is_fixed_pitch());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_text_font_covers_printable_ascii_and_latin1() {
|
||||
for font in [Std14::Helvetica, Std14::TimesRoman, Std14::Courier, Std14::TimesBoldItalic] {
|
||||
for code in 0x20..=0x7Eu8 {
|
||||
assert!(font.width(code) > 0.0, "{font:?} has no width for ASCII {code}");
|
||||
}
|
||||
for code in 0xA0..=0xFFu8 {
|
||||
assert!(font.width(code) > 0.0, "{font:?} has no width for Latin-1 {code}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_and_dingbats_have_their_own_encodings() {
|
||||
// Symbol's code 0x61 is alpha, not 'a'; ZapfDingbats' is a scissors glyph.
|
||||
assert!(Std14::Symbol.width(0x61) > 0.0);
|
||||
assert!(Std14::ZapfDingbats.width(0x61) > 0.0);
|
||||
// Neither defines the Latin-1 upper half the text fonts do.
|
||||
assert_eq!(Std14::Symbol.width(0xFF), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base_font_names_round_trip() {
|
||||
for i in 0..14usize {
|
||||
let font = [
|
||||
Std14::Helvetica,
|
||||
Std14::HelveticaBold,
|
||||
Std14::HelveticaOblique,
|
||||
Std14::HelveticaBoldOblique,
|
||||
Std14::TimesRoman,
|
||||
Std14::TimesBold,
|
||||
Std14::TimesItalic,
|
||||
Std14::TimesBoldItalic,
|
||||
Std14::Courier,
|
||||
Std14::CourierBold,
|
||||
Std14::CourierOblique,
|
||||
Std14::CourierBoldOblique,
|
||||
Std14::Symbol,
|
||||
Std14::ZapfDingbats,
|
||||
][i];
|
||||
assert_eq!(Std14::from_base_font(font.base_font()), Some(font), "{}", font.base_font());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base_font_aliases_and_subset_prefixes_resolve() {
|
||||
assert_eq!(Std14::from_base_font("ABCDEF+Helvetica-Bold"), Some(Std14::HelveticaBold));
|
||||
assert_eq!(Std14::from_base_font("Arial"), Some(Std14::Helvetica));
|
||||
assert_eq!(Std14::from_base_font("Arial,BoldItalic"), Some(Std14::HelveticaBoldOblique));
|
||||
assert_eq!(Std14::from_base_font("TimesNewRoman,Bold"), Some(Std14::TimesBold));
|
||||
assert_eq!(Std14::from_base_font("CourierNew-Oblique"), Some(Std14::CourierOblique));
|
||||
assert_eq!(Std14::from_base_font("ZapfDingbats"), Some(Std14::ZapfDingbats));
|
||||
assert_eq!(Std14::from_base_font("Wingdings"), None);
|
||||
assert_eq!(Std14::from_base_font(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_width_sums_the_glyphs_and_scales_with_size() {
|
||||
let h = Std14::Helvetica;
|
||||
// "AW" = 667 + 944 = 1611 thousandths of an em.
|
||||
assert!((h.text_width("AW", 1000.0) - 1611.0).abs() < 1e-9);
|
||||
assert!((h.text_width("AW", 12.0) - 1611.0 * 12.0 / 1000.0).abs() < 1e-9);
|
||||
assert_eq!(h.text_width("", 12.0), 0.0);
|
||||
// Courier: every character is 600, so the width is purely a count.
|
||||
assert!((Std14::Courier.text_width("hello", 10.0) - 5.0 * 6.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_text_does_not_panic_and_stays_finite() {
|
||||
for font in [Std14::Helvetica, Std14::Symbol, Std14::Courier] {
|
||||
for s in ["", "\0", "\u{202E}", "💥", &"a".repeat(10_000), "\u{FFFD}"] {
|
||||
let w = font.text_width(s, 12.0);
|
||||
assert!(w.is_finite() && w >= 0.0, "{font:?} {s:?} -> {w}");
|
||||
}
|
||||
assert!(font.advance(0, 12.0).is_finite());
|
||||
assert!(font.advance(255, 0.0).is_finite());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,12 +222,13 @@ fn overlay_content(marks: &[&Mark]) -> Vec<u8> {
|
||||
// The overlay text: its font, colour and alignment; auto-sized to fit unless a size is
|
||||
// set; repeated to fill the area when asked.
|
||||
let look = &m.look;
|
||||
let (res, width): (&str, fn(&str, f64) -> f64) = match look.font {
|
||||
pdfcraft_annot::OverlayFont::Helvetica => ("PCHelv", pdfcraft_fonts::helvetica_width),
|
||||
// Approximations of the standard metrics (no font program is bundled).
|
||||
pdfcraft_annot::OverlayFont::Times => ("PCTimes", |s, size| pdfcraft_fonts::helvetica_width(s, size) * 0.9),
|
||||
pdfcraft_annot::OverlayFont::Courier => ("PCCour", |s, size| s.chars().count() as f64 * size * 0.6),
|
||||
// The published standard-14 metrics, so overlay text is measured as it will be drawn.
|
||||
let (res, face) = match look.font {
|
||||
pdfcraft_annot::OverlayFont::Helvetica => ("PCHelv", pdfcraft_fonts::Std14::Helvetica),
|
||||
pdfcraft_annot::OverlayFont::Times => ("PCTimes", pdfcraft_fonts::Std14::TimesRoman),
|
||||
pdfcraft_annot::OverlayFont::Courier => ("PCCour", pdfcraft_fonts::Std14::Courier),
|
||||
};
|
||||
let width = move |s: &str, size: f64| face.text_width(s, size);
|
||||
let [cr, cg, cb] = look.color.map(|v| v.clamp(0.0, 1.0));
|
||||
for r in &m.rects {
|
||||
let (w, h) = (r[2] - r[0], r[3] - r[1]);
|
||||
|
||||
Reference in new issue
Block a user