mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 11:17:10 -04:00
F3: one documented substitution chain, and report what it chose and why
A font the document does not embed was replaced by whatever the caller happened to pick, and nothing told the user. `doc_info` said only `embedded: false`, which is why "my Chinese PDF shows nothing" has been a mystery rather than a sentence. `crates/fonts/src/fallback.rs` applies one chain, in a documented order, and returns what it chose with a reason: 1. embedded -> nothing substituted; 2. /BaseFont names a standard-14 face or a known alias (exact metrics); 3. craft-fonts holds that exact family; 4. /CIDSystemInfo /Ordering names a script we have a face for; 5. the ordering names a script we have no licensed face for -- report none, so nothing is drawn rather than the wrong glyphs; 6. otherwise the nearest of the fourteen by descriptor flags. It delegates to the existing per-script selectors instead of restating their order, so each keeps one source of truth. It answers the dictionary question only; "which face has a glyph for this character?" depends on the string being drawn and stays where the drawing happens. `doc_info` gains `substitute` and `substitution` per font. Both null means the document's own glyphs are shown; a null `substitute` with a reason means no face was available at all. That is Acrobat's Actual Font, minus the interface, which `parity/` now records as still missing. Two findings on the way: `Std14::from_base_font` matches loosely, so "SomeHouseSerif" resolved to Times by its name containing "serif". The face is a fair guess but reporting it as "names one of the standard 14" would have been untrue of a font nobody has heard of. `Std14::recognise` now says whether a name matched an alias or only a family hint, and the two report differently. `from_base_font` is unchanged. The generated table moved to `crates/fonts/src/std14_metrics.rs`, data alone, and the ATTRIBUTION entry and AGENTS.md 1.1 line follow it. F1 put the hash on a file holding both the table and the code reading it, so this commit's ordinary edits tripped the gate -- and a hash that churns on unrelated changes gets re-attested reflexively, which is the protection gone. Verified both directions after the split: editing std14.rs passes the gate, changing one width in std14_metrics.rs fails it. Every new test was watched failing first: removing the script step breaks the two unit tests and the doc_info integration test. Co-Authored-By: Claude <[email protected]>
This commit is contained in:
1 parent
3f044b6263
commit
d9607ec3be
12 files changed
+877
-333
No files matched your search
@@ -25,7 +25,7 @@ An **asset** is any non-code material that the project includes, ships, embeds,
|
||||
- Screenshots of Adobe software exist only as private, local observation notes under `plan/acrobat/`, which is gitignored. They must **never** be committed, published, linked, shown in docs, used as a design source to trace, or placed anywhere else in the repository or its outputs.
|
||||
- The only Adobe-authored material allowed is **non-visual technical data** that is openly licensed and required for PDF compatibility. Each item needs its own entry in `ATTRIBUTION.toml` with `adobe_data = true`, and the list below is closed:
|
||||
- The CMap resources (BSD-3-Clause), bundled inside the `hayro-cmap` dependency.
|
||||
- The standard-14 font metrics and encoding tables in the PDF specification. They are data, not typefaces. They are embedded as code in hayro, and as our own generated table in `crates/fonts/src/std14.rs` (approved by the owner, 2026-10-08), which is what generated appearances measure with and what survives hayro being retired.
|
||||
- The standard-14 font metrics and encoding tables in the PDF specification. They are data, not typefaces. They are embedded as code in hayro, and as our own generated table in `crates/fonts/src/std14_metrics.rs` (approved by the owner, 2026-10-08; that file holds the data alone, so the code reading it can change without re-attesting the table), which is what generated appearances measure with and what survives hayro being retired.
|
||||
|
||||
Adding anything to this list needs the user's explicit approval.
|
||||
|
||||
|
||||
+1
-1
@@ -222,7 +222,7 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
|
||||
| `vendor/hayro-interpret/assets/FoxitSerifItalic.pfb` | FoxitSerifItalic (standard-14 substitute font) | Foxit Software Inc. / PDFium Authors | BSD-3-Clause | https://pdfium.googlesource.com/pdfium (via hayro-interpret 0.7.0 assets/) | Renders non-embedded standard-14 fonts (vendored hayro-interpret) |
|
||||
| `vendor/hayro-interpret/assets/FoxitSymbol.pfb` | FoxitSymbol (standard-14 substitute font) | Foxit Software Inc. / PDFium Authors | BSD-3-Clause | https://pdfium.googlesource.com/pdfium (via hayro-interpret 0.7.0 assets/) | Renders non-embedded standard-14 fonts (vendored hayro-interpret) |
|
||||
| `vendor/hayro-interpret/assets/CGATS001Compat-v2-micro.icc` | CGATS001Compat v2 micro ICC profile | Clinton Ingram (saucecontrol/Compact-ICC-Profiles) | CC0-1.0 | https://github.com/saucecontrol/Compact-ICC-Profiles | Default CMYK colour conversion (vendored hayro-interpret) |
|
||||
| `crates/fonts/src/std14.rs` | Standard-14 font advance widths and encodings (as Rust tables) | Adobe (Core14 AFM metrics, PDF specification encodings); tables generated by the PdfCraft contributors | MIT OR Apache-2.0 | ISO 32000-2 Annex D and the Adobe Core14 AFM metrics. Generated by joining the published WinAnsi, Symbol and ZapfDingbats encodings (code to glyph name) with the per-glyph advance widths, then spot-checked against the specification by hand in the tests of crates/fonts/src/std14.rs. | Advance widths for the fourteen standard fonts, which a PDF may name without embedding. Non-visual data, not a typeface: the item AGENTS.md 1.1 permits, held first-party so generated appearance streams and the renderer agree. |
|
||||
| `crates/fonts/src/std14_metrics.rs` | Standard-14 font advance widths and encodings (as Rust tables) | Adobe (Core14 AFM metrics, PDF specification encodings); tables generated by the PdfCraft contributors | MIT OR Apache-2.0 | ISO 32000-2 Annex D and the Adobe Core14 AFM metrics. Generated by joining the published WinAnsi, Symbol and ZapfDingbats encodings (code to glyph name) with the per-glyph advance widths, then spot-checked against the specification by hand in the tests of crates/fonts/src/std14.rs. Data only: the code that reads these tables lives in std14.rs, so that ordinary edits do not churn this file's hash. | Advance widths for the fourteen standard fonts, which a PDF may name without embedding. Non-visual data, not a typeface: the item AGENTS.md 1.1 permits, held first-party so generated appearance streams and the renderer agree. |
|
||||
| `docs/brand/artcraft-logo-white.png` | ArtCraft brand: artcraft-logo-white | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. |
|
||||
| `docs/brand/artcraft-logo-white.svg` | ArtCraft brand: artcraft-logo-white | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. |
|
||||
| `docs/brand/artcraft-logo.png` | ArtCraft brand: artcraft-logo | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. |
|
||||
|
||||
+3
-3
@@ -2365,16 +2365,16 @@ usage = "Default CMYK colour conversion (vendored hayro-interpret)"
|
||||
sha256 = "73e1ba37d2bad5bab2a964f40a9eed96209666efc067c3322626214bbef234a0"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/fonts/src/std14.rs"
|
||||
path = "crates/fonts/src/std14_metrics.rs"
|
||||
title = "Standard-14 font advance widths and encodings (as Rust tables)"
|
||||
author = "Adobe (Core14 AFM metrics, PDF specification encodings); tables generated by the PdfCraft contributors"
|
||||
source = "ISO 32000-2 Annex D and the Adobe Core14 AFM metrics. Generated by joining the published WinAnsi, Symbol and ZapfDingbats encodings (code to glyph name) with the per-glyph advance widths, then spot-checked against the specification by hand in the tests of crates/fonts/src/std14.rs."
|
||||
source = "ISO 32000-2 Annex D and the Adobe Core14 AFM metrics. Generated by joining the published WinAnsi, Symbol and ZapfDingbats encodings (code to glyph name) with the per-glyph advance widths, then spot-checked against the specification by hand in the tests of crates/fonts/src/std14.rs. Data only: the code that reads these tables lives in std14.rs, so that ordinary edits do not churn this file's hash."
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "data"
|
||||
adobe_data = true
|
||||
usage = "Advance widths for the fourteen standard fonts, which a PDF may name without embedding. Non-visual data, not a typeface: the item AGENTS.md 1.1 permits, held first-party so generated appearance streams and the renderer agree."
|
||||
sha256 = "4ce47db7e65467d4ea1d1642ff76dfcaeb9669c1811a93819d7e00e63fa9fcf8"
|
||||
sha256 = "f225e0b044875b8fa6375b6b5ad935f0d99fcc1ad03ecf9f0baae2cedbfca4d2"
|
||||
|
||||
[[bundled]]
|
||||
crate = "epaint_default_fonts"
|
||||
|
||||
@@ -2205,8 +2205,13 @@ fn info(d: &Document) -> Value {
|
||||
})).collect::<Vec<_>>(),
|
||||
"layers": i.layers.iter().map(|l| json!({ "name": l.name, "visible": l.visible })).collect::<Vec<_>>(),
|
||||
"attachments": i.attachments.iter().map(|a| json!({ "name": a.name, "description": a.description, "size": a.size })).collect::<Vec<_>>(),
|
||||
// `substitute` names the face standing in for a program the document does not embed, and
|
||||
// `substitution` says why. Both are null for an embedded font: nothing stands in. A null
|
||||
// `substitute` with a non-null `substitution` means no face was available at all, so
|
||||
// nothing is drawn for that font -- which is the answer to "why is my text missing?".
|
||||
"fonts": i.fonts.iter().map(|f| json!({
|
||||
"name": f.name, "kind": f.kind, "embedded": f.embedded, "subset": f.subset, "encoding": f.encoding,
|
||||
"substitute": f.substitute, "substitution": f.substitution,
|
||||
})).collect::<Vec<_>>(),
|
||||
"warnings": i.warnings,
|
||||
"repairs": d.repair_log(),
|
||||
|
||||
@@ -409,6 +409,13 @@ fn open_inspect_render_and_find() {
|
||||
assert_eq!(info["pages"][0]["width"], 200.0);
|
||||
assert_eq!(info["pages"][1]["label"], "2");
|
||||
|
||||
// The fixture names /Helvetica without embedding it, so an agent can see both that a face is
|
||||
// standing in and why that face. `substitution` is null only when nothing stands in.
|
||||
let font = &info["fonts"][0];
|
||||
assert_eq!((&font["name"], &font["embedded"]), (&json!("Helvetica"), &json!(false)));
|
||||
assert_eq!(font["substitute"], json!("Helvetica"));
|
||||
assert!(font["substitution"].as_str().is_some_and(|w| w.contains("standard 14")), "{font}");
|
||||
|
||||
assert_eq!(page_text(&mut a, doc), ["Page 1", "Page 2", "Page 3"]);
|
||||
|
||||
let found = ok(&mut a, "text_find", json!({ "doc": doc, "query": "page 2" }));
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
//! The one chain PdfCraft follows when a document does not embed a font program, and a record of
|
||||
//! what it chose and why.
|
||||
//!
|
||||
//! Substitution used to be decided per caller and never written down: `edit` picked a craft face
|
||||
//! by the script of the text it was drawing, `Std14::from_base_font` matched Latin names, and the
|
||||
//! renderer did its own thing. Nothing told the *user* that the glyphs they are looking at are not
|
||||
//! the ones the document asked for, which is the difference between "PdfCraft shows nothing for my
|
||||
//! Chinese PDF" being a mystery and being a sentence.
|
||||
//!
|
||||
//! This module answers the *dictionary* question — "the document does not carry this program; what
|
||||
//! will stand in for it, and why?" — which is what inspection and reporting need. It deliberately
|
||||
//! does not answer the *text* question ("which face has a glyph for こ?"), because that depends on
|
||||
//! the string being drawn and is already handled where the drawing happens. It delegates to the
|
||||
//! existing per-script selectors rather than restating their order, so there is one source of
|
||||
//! truth for each.
|
||||
//!
|
||||
//! The chain is tried in order, and the first step that yields a face wins:
|
||||
//!
|
||||
//! 1. The document embeds the program. Nothing is substituted.
|
||||
//! 2. `/BaseFont` names one of the standard 14 (or a well-known alias such as Arial). Exact
|
||||
//! published metrics apply, so this is as close to right as a substitution gets.
|
||||
//! 3. craft-fonts holds that exact family. Never another family — see [`crate::embedding`].
|
||||
//! 4. The descendant font's `/CIDSystemInfo /Ordering` names a script, and craft-fonts has a face
|
||||
//! for it. Today that means Adobe-Japan1.
|
||||
//! 5. The ordering names a script we have **no** licensed face for (GB1, CNS1, Korea1). There is
|
||||
//! no honest substitute, so the chain reports none: the caller draws nothing and can say why.
|
||||
//! 6. Otherwise, the nearest of the standard 14 by `/FontDescriptor /Flags` — serif, fixed pitch,
|
||||
//! bold and italic. This step always succeeds, so a simple font is never left without a face.
|
||||
|
||||
use crate::{CraftFont, Std14};
|
||||
|
||||
/// What a font dictionary asks for, as far as the dictionary alone can say.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Request<'a> {
|
||||
/// `/BaseFont` with any `ABCDEF+` subset tag already stripped.
|
||||
pub base_font: &'a str,
|
||||
/// Whether the descriptor carries `/FontFile`, `/FontFile2` or `/FontFile3` (or it is Type 3).
|
||||
pub embedded: bool,
|
||||
/// `/FontDescriptor /Flags`, or 0 when there is no descriptor.
|
||||
pub flags: u32,
|
||||
/// The descendant font's `/CIDSystemInfo /Ordering`, for a composite font.
|
||||
pub ordering: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// The face the chain picked.
|
||||
///
|
||||
/// `Debug`, `PartialEq` and `Eq` are written by hand rather than derived, because [`CraftFont`]
|
||||
/// carries a whole font program: deriving would print megabytes and compare font files.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Face {
|
||||
/// The document's own program; nothing was substituted.
|
||||
Embedded,
|
||||
/// One of the standard 14, whose metrics are exact.
|
||||
Standard14(Std14),
|
||||
/// A face from the optional craft-fonts build input.
|
||||
Craft(&'static CraftFont),
|
||||
/// No face at all. The caller must draw nothing rather than draw the wrong glyphs.
|
||||
None,
|
||||
}
|
||||
|
||||
impl PartialEq for Face {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Embedded, Self::Embedded) | (Self::None, Self::None) => true,
|
||||
(Self::Standard14(a), Self::Standard14(b)) => a == b,
|
||||
// craft-fonts entries are unique statics, so identity is the comparison that means
|
||||
// what we want: two styles of one family are different faces and must not be equal.
|
||||
(Self::Craft(a), Self::Craft(b)) => std::ptr::eq(*a, *b),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Face {}
|
||||
|
||||
impl std::fmt::Debug for Face {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Embedded => f.write_str("Embedded"),
|
||||
Self::Standard14(s) => write!(f, "Standard14({})", s.base_font()),
|
||||
// The name, never the bytes.
|
||||
Self::Craft(c) => write!(f, "Craft({})", c.name()),
|
||||
Self::None => f.write_str("None"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Why the chain picked that face. The variants are in the order the chain tries them.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Why {
|
||||
/// The document carries the font program.
|
||||
Embedded,
|
||||
/// `/BaseFont` names a standard-14 face or a well-known alias of one.
|
||||
NamedStandard14,
|
||||
/// `/BaseFont` is otherwise unknown, but its name hints at a family ("Serif", "Roman",
|
||||
/// "Mono"). A guess from the name, and reported as one.
|
||||
NameHintedStandard14,
|
||||
/// craft-fonts holds this exact family.
|
||||
ExactCraftFamily,
|
||||
/// A craft-fonts face for the script named by `/CIDSystemInfo /Ordering`.
|
||||
ScriptFace,
|
||||
/// The ordering names a script with no licensed face available.
|
||||
NoFaceForScript,
|
||||
/// The nearest standard-14 face by descriptor flags.
|
||||
StyleMatched,
|
||||
}
|
||||
|
||||
impl Why {
|
||||
/// A stable sentence for a report, written for whoever has to act on it.
|
||||
pub fn note(self) -> &'static str {
|
||||
match self {
|
||||
Self::Embedded => "embedded in the document; not substituted",
|
||||
Self::NamedStandard14 => "names one of the standard 14 fonts, or a well-known alias of one, whose metrics are exact",
|
||||
Self::NameHintedStandard14 => {
|
||||
"not embedded and not a known family, but its name hints at one (Serif, Roman or Mono), so the nearest standard-14 face was chosen from that hint"
|
||||
}
|
||||
Self::ExactCraftFamily => "this exact family is available, so no other family was chosen",
|
||||
Self::ScriptFace => "substituted by a face for the script the font declares",
|
||||
Self::NoFaceForScript => {
|
||||
"no licensed face is available for the script this font declares, so nothing is drawn for it \
|
||||
rather than the wrong glyphs"
|
||||
}
|
||||
Self::StyleMatched => "not embedded and not a known family, so the nearest standard-14 face was chosen by its descriptor flags",
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the document's own glyphs are being shown. Only [`Why::Embedded`] is faithful.
|
||||
pub fn is_faithful(self) -> bool {
|
||||
matches!(self, Self::Embedded)
|
||||
}
|
||||
}
|
||||
|
||||
/// What the chain chose, and why.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Substitution {
|
||||
pub face: Face,
|
||||
pub why: Why,
|
||||
}
|
||||
|
||||
impl Substitution {
|
||||
/// The chosen face's name, for a report. `None` when nothing was chosen.
|
||||
pub fn face_name(&self) -> Option<String> {
|
||||
match self.face {
|
||||
Face::Embedded => None,
|
||||
Face::Standard14(f) => Some(f.base_font().to_owned()),
|
||||
Face::Craft(f) => Some(f.name()),
|
||||
Face::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CID orderings we recognise, and whether a craft-fonts face may exist for them.
|
||||
///
|
||||
/// `Identity` is not a script: it says the codes are glyph ids, so it tells us nothing about
|
||||
/// which face would suit and the chain falls through to the flags.
|
||||
fn script_for(ordering: &str) -> Option<&'static str> {
|
||||
match ordering {
|
||||
"Japan1" => Some("Jpan"),
|
||||
// Registered orderings we have no openly licensed face for. AGENTS.md 1.1 rules out
|
||||
// Source Han and Noto CJK (Source Han rebranded), which is why these have none.
|
||||
"GB1" | "CNS1" | "Korea1" | "KR" => Some(""),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply the chain to `req`. Total: every request gets a [`Substitution`].
|
||||
///
|
||||
/// See the module documentation for the order and the reasoning behind each step.
|
||||
pub fn substitute(req: &Request<'_>) -> Substitution {
|
||||
let at = |face, why| Substitution { face, why };
|
||||
if req.embedded {
|
||||
return at(Face::Embedded, Why::Embedded);
|
||||
}
|
||||
if let Some((f, how)) = Std14::recognise(req.base_font) {
|
||||
return match how {
|
||||
crate::std14::Match::Alias => at(Face::Standard14(f), Why::NamedStandard14),
|
||||
crate::std14::Match::FamilyHint => at(Face::Standard14(f), Why::NameHintedStandard14),
|
||||
};
|
||||
}
|
||||
if let Some(f) = crate::embedding::matching_faces(req.base_font).first() {
|
||||
return at(Face::Craft(f), Why::ExactCraftFamily);
|
||||
}
|
||||
if let Some(script) = req.ordering.and_then(script_for) {
|
||||
// A script we hold a face for; otherwise there is no honest substitute to offer.
|
||||
return match crate::CRAFT_FONTS.iter().find(|f| f.covers(script)) {
|
||||
Some(f) => at(Face::Craft(f), Why::ScriptFace),
|
||||
None => at(Face::None, Why::NoFaceForScript),
|
||||
};
|
||||
}
|
||||
at(Face::Standard14(style_matched(req.flags)), Why::StyleMatched)
|
||||
}
|
||||
|
||||
/// The nearest standard-14 face for `/FontDescriptor /Flags` (ISO 32000-2 table 121): fixed pitch
|
||||
/// is bit 1, serif bit 2, italic bit 7 and ForceBold bit 19.
|
||||
fn style_matched(flags: u32) -> Std14 {
|
||||
let (bold, italic) = (flags & 0x4_0000 != 0, flags & 0x40 != 0);
|
||||
match (flags & 1 != 0, flags & 2 != 0, bold, italic) {
|
||||
(true, _, false, false) => Std14::Courier,
|
||||
(true, _, true, false) => Std14::CourierBold,
|
||||
(true, _, false, true) => Std14::CourierOblique,
|
||||
(true, _, true, true) => Std14::CourierBoldOblique,
|
||||
(false, true, false, false) => Std14::TimesRoman,
|
||||
(false, true, true, false) => Std14::TimesBold,
|
||||
(false, true, false, true) => Std14::TimesItalic,
|
||||
(false, true, true, true) => Std14::TimesBoldItalic,
|
||||
(false, false, false, false) => Std14::Helvetica,
|
||||
(false, false, true, false) => Std14::HelveticaBold,
|
||||
(false, false, false, true) => Std14::HelveticaOblique,
|
||||
(false, false, true, true) => Std14::HelveticaBoldOblique,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn req<'a>(base: &'a str, embedded: bool, flags: u32, ordering: Option<&'a str>) -> Request<'a> {
|
||||
Request { base_font: base, embedded, flags, ordering }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_embedded_font_is_never_substituted() {
|
||||
// True even when the name and flags would otherwise pick something: embedding wins first.
|
||||
for (base, ordering) in [("Helvetica", None), ("Whatever", Some("GB1")), ("", None)] {
|
||||
let s = substitute(&req(base, true, 0, ordering));
|
||||
assert_eq!(s, Substitution { face: Face::Embedded, why: Why::Embedded });
|
||||
assert!(s.why.is_faithful());
|
||||
assert_eq!(s.face_name(), None, "nothing stands in, so there is no substitute to name");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_standard_14_name_wins_over_the_flags() {
|
||||
// Flags claiming a fixed-pitch serif must not override a name we have exact metrics for.
|
||||
let s = substitute(&req("Times-BoldItalic", false, 0b11, None));
|
||||
assert_eq!(s.face, Face::Standard14(Std14::TimesBoldItalic));
|
||||
assert_eq!(s.why, Why::NamedStandard14);
|
||||
assert_eq!(s.face_name().as_deref(), Some("Times-BoldItalic"));
|
||||
// Aliases and subset tags resolve the same way.
|
||||
for name in ["Arial", "ArialMT", "Arial-Bold", "Symbol", "ZapfDingbats", "CourierNew"] {
|
||||
assert_eq!(substitute(&req(name, false, 0, None)).why, Why::NamedStandard14, "{name}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_name_hint_is_reported_as_a_guess_not_as_a_standard_14_name() {
|
||||
// A house font whose name merely contains "Serif" gets Times, which is a sensible guess --
|
||||
// but calling that "names one of the standard 14" would tell the reader something untrue.
|
||||
let hinted = substitute(&req("SomeHouseSerif", false, 0, None));
|
||||
assert_eq!(hinted.face, Face::Standard14(Std14::TimesRoman));
|
||||
assert_eq!(hinted.why, Why::NameHintedStandard14);
|
||||
assert!(hinted.why.note().contains("hints at one"), "{}", hinted.why.note());
|
||||
|
||||
// Style words still apply to the guess.
|
||||
assert_eq!(substitute(&req("SomeHouseSerif-BoldItalic", false, 0, None)).face, Face::Standard14(Std14::TimesBoldItalic));
|
||||
// "Mono" hints at Courier the same way.
|
||||
assert_eq!(substitute(&req("HouseMono", false, 0, None)).why, Why::NameHintedStandard14);
|
||||
|
||||
// A real alias is still reported as the request it is.
|
||||
for alias in ["Arial", "CourierNew", "Times-Roman", "Symbol"] {
|
||||
assert_eq!(substitute(&req(alias, false, 0, None)).why, Why::NamedStandard14, "{alias}");
|
||||
}
|
||||
// A name with no hint at all falls through to the flags instead.
|
||||
assert_eq!(substitute(&req("HouseGrotesk", false, 0b10, None)).why, Why::StyleMatched);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flags_pick_the_nearest_of_the_fourteen() {
|
||||
// Bit 1 fixed pitch, bit 2 serif, bit 7 italic, bit 19 ForceBold.
|
||||
let cases = [
|
||||
(0, Std14::Helvetica),
|
||||
(0b10, Std14::TimesRoman),
|
||||
(0b1, Std14::Courier),
|
||||
(0x40, Std14::HelveticaOblique),
|
||||
(0x4_0000, Std14::HelveticaBold),
|
||||
(0x4_0040, Std14::HelveticaBoldOblique),
|
||||
(0b10 | 0x40, Std14::TimesItalic),
|
||||
(0b10 | 0x4_0000, Std14::TimesBold),
|
||||
(0b10 | 0x4_0040, Std14::TimesBoldItalic),
|
||||
(0b1 | 0x40, Std14::CourierOblique),
|
||||
(0b1 | 0x4_0000, Std14::CourierBold),
|
||||
(0b1 | 0x4_0040, Std14::CourierBoldOblique),
|
||||
// Fixed pitch and serif together: monospace is the stronger signal for layout.
|
||||
(0b11, Std14::Courier),
|
||||
];
|
||||
for (flags, want) in cases {
|
||||
let s = substitute(&req("SomethingUnknown", false, flags, None));
|
||||
assert_eq!(s.face, Face::Standard14(want), "flags {flags:#x}");
|
||||
assert_eq!(s.why, Why::StyleMatched);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_script_with_no_licensed_face_reports_none_rather_than_latin() {
|
||||
// The user's Chinese PDF draws nothing. The point of this is that we can now say so,
|
||||
// instead of quietly standing Helvetica in and showing the wrong glyphs.
|
||||
for ordering in ["GB1", "CNS1", "Korea1", "KR"] {
|
||||
let s = substitute(&req("SomeCJKFont", false, 0, Some(ordering)));
|
||||
assert_eq!(s.face, Face::None, "{ordering}");
|
||||
assert_eq!(s.why, Why::NoFaceForScript, "{ordering}");
|
||||
assert_eq!(s.face_name(), None);
|
||||
assert!(!s.why.is_faithful());
|
||||
assert!(s.why.note().contains("no licensed face"), "{ordering}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_ordering_is_not_a_script_and_falls_through_to_the_flags() {
|
||||
// /Ordering (Identity) says the codes are glyph ids. It implies nothing about the face,
|
||||
// so treating it as a script would invent a substitution the dictionary never asked for.
|
||||
let s = substitute(&req("SomeSubsetFont", false, 0b10, Some("Identity")));
|
||||
assert_eq!(s.face, Face::Standard14(Std14::TimesRoman));
|
||||
assert_eq!(s.why, Why::StyleMatched);
|
||||
// An unregistered ordering is treated the same way, not guessed at.
|
||||
assert_eq!(substitute(&req("X", false, 0, Some("MadeUp1"))).why, Why::StyleMatched);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn japanese_depends_on_craft_fonts_and_says_which_it_got() {
|
||||
// craft-fonts is an optional build input, so both outcomes are correct -- but they must be
|
||||
// distinguishable, and neither may be a silent Latin stand-in.
|
||||
let s = substitute(&req("SomeJapaneseFont", false, 0, Some("Japan1")));
|
||||
if crate::CRAFT_FONTS.iter().any(|f| f.covers("Jpan")) {
|
||||
assert_eq!(s.why, Why::ScriptFace);
|
||||
assert!(matches!(s.face, Face::Craft(_)));
|
||||
assert!(s.face_name().is_some_and(|n| !n.is_empty()));
|
||||
} else {
|
||||
assert_eq!(s.why, Why::NoFaceForScript);
|
||||
assert_eq!(s.face, Face::None);
|
||||
}
|
||||
assert!(!s.why.is_faithful(), "a substitution is never faithful");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_why_has_a_distinct_usable_note() {
|
||||
let all = [
|
||||
Why::Embedded,
|
||||
Why::NamedStandard14,
|
||||
Why::NameHintedStandard14,
|
||||
Why::ExactCraftFamily,
|
||||
Why::ScriptFace,
|
||||
Why::NoFaceForScript,
|
||||
Why::StyleMatched,
|
||||
];
|
||||
let mut notes: Vec<&str> = all.iter().map(|w| w.note()).collect();
|
||||
notes.sort_unstable();
|
||||
let before = notes.len();
|
||||
notes.dedup();
|
||||
assert_eq!(notes.len(), before, "each reason reads differently");
|
||||
assert!(all.iter().all(|w| !w.note().is_empty() && w.note().len() > 20), "notes are sentences, not labels");
|
||||
assert_eq!(all.iter().filter(|w| w.is_faithful()).count(), 1, "only an embedded program is faithful");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_input_is_total_and_never_panics() {
|
||||
// Every field is attacker-controlled: a name is any bytes the document chose.
|
||||
let long = "a".repeat(10_000);
|
||||
let names = ["", " ", "+", "ABCDEF+", "\u{0}\u{1}", long.as_str(), "../../etc/passwd", "Ĥéļvéťïçà"];
|
||||
for name in names {
|
||||
for flags in [0, u32::MAX, 0x4_0040, 1] {
|
||||
for ordering in [None, Some(""), Some("Japan1"), Some("\u{0}")] {
|
||||
let s = substitute(&req(name, false, flags, ordering));
|
||||
// Always a decision, and the reason always reads.
|
||||
assert!(!s.why.note().is_empty());
|
||||
assert_eq!(s.face == Face::None, s.why == Why::NoFaceForScript, "only one step declines: {name:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,13 @@
|
||||
mod craft;
|
||||
pub mod embedding;
|
||||
mod encodings;
|
||||
pub mod fallback;
|
||||
pub mod pdf;
|
||||
mod script;
|
||||
mod shaping;
|
||||
pub mod source_shaping;
|
||||
mod std14;
|
||||
mod std14_metrics;
|
||||
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,
|
||||
|
||||
+40
-312
@@ -32,44 +32,60 @@ pub enum Std14 {
|
||||
ZapfDingbats,
|
||||
}
|
||||
|
||||
/// How [`Std14::recognise`] matched a `/BaseFont` name.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Match {
|
||||
/// A standard-14 face or a well-known alias of one (Arial for Helvetica, CourierNew for
|
||||
/// Courier). The document is asking for this face.
|
||||
Alias,
|
||||
/// Only a family hint matched -- "Serif", "Roman" or "Mono" in an otherwise unknown name.
|
||||
/// The face is a guess from the name, not the one the document asked for.
|
||||
FamilyHint,
|
||||
}
|
||||
|
||||
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> {
|
||||
Self::recognise(base).map(|(face, _)| face)
|
||||
}
|
||||
|
||||
/// [`Std14::from_base_font`], also saying *how* the name matched.
|
||||
///
|
||||
/// Reporting needs the difference. "Arial" really is a request for Helvetica, but
|
||||
/// "SomeHouseSerif" is a house font whose name merely contains "Serif"; choosing Times for it
|
||||
/// is a reasonable guess from the name, not the face the document asked for, and a substitution
|
||||
/// report that called it a standard-14 name would be telling the reader something untrue.
|
||||
pub fn recognise(base: &str) -> Option<(Self, Match)> {
|
||||
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"));
|
||||
// Two bools index a four-element array, so the subscript is provably 0..=3.
|
||||
let styled = |faces: [Self; 4]| faces[usize::from(bold) | usize::from(italic) << 1];
|
||||
let courier = || styled([Self::Courier, Self::CourierBold, Self::CourierOblique, Self::CourierBoldOblique]);
|
||||
let times = || styled([Self::TimesRoman, Self::TimesBold, Self::TimesItalic, Self::TimesBoldItalic]);
|
||||
if has("symbol") {
|
||||
return Some(Self::Symbol);
|
||||
return Some((Self::Symbol, Match::Alias));
|
||||
}
|
||||
if has("zapf") || has("dingbat") {
|
||||
return Some(Self::ZapfDingbats);
|
||||
return Some((Self::ZapfDingbats, Match::Alias));
|
||||
}
|
||||
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("courier") {
|
||||
return Some((courier(), Match::Alias));
|
||||
}
|
||||
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("mono") {
|
||||
return Some((courier(), Match::FamilyHint));
|
||||
}
|
||||
if has("times") {
|
||||
return Some((times(), Match::Alias));
|
||||
}
|
||||
if has("serif") || has("roman") {
|
||||
return Some((times(), Match::FamilyHint));
|
||||
}
|
||||
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,
|
||||
});
|
||||
return Some((styled([Self::Helvetica, Self::HelveticaBold, Self::HelveticaOblique, Self::HelveticaBoldOblique]), Match::Alias));
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -77,7 +93,7 @@ impl Std14 {
|
||||
/// 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])
|
||||
f64::from(crate::std14_metrics::WIDTHS[self as usize][code as usize])
|
||||
}
|
||||
|
||||
/// The advance of `code` at `size` points.
|
||||
@@ -101,298 +117,10 @@ impl Std14 {
|
||||
|
||||
/// 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]
|
||||
crate::std14_metrics::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::*;
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
//! The generated standard-14 tables: the fourteen base font names and their per-code advance
|
||||
//! widths. **Data only, no logic** -- see `std14.rs` for everything that interprets it.
|
||||
//!
|
||||
//! This file is the closed-list item in AGENTS.md 1.1 and carries a SHA-256 in `ATTRIBUTION.toml`,
|
||||
//! so `cargo xtask assets` fails until a change to it is re-attested. It is kept separate from the
|
||||
//! code that reads it for exactly that reason: if ordinary edits churned the hash, re-attesting
|
||||
//! would become reflexive and the check would stop meaning anything.
|
||||
//!
|
||||
//! Widths are in thousandths of an em, indexed by character code in each face's own encoding --
|
||||
//! WinAnsi for the twelve text faces, and their built-in encodings for Symbol and ZapfDingbats.
|
||||
//! A zero means the encoding leaves that code undefined. Verified against the published
|
||||
//! specification by the tests in `std14.rs`, which type the expected values in by hand.
|
||||
|
||||
pub(crate) 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]
|
||||
pub(crate) 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,
|
||||
],
|
||||
];
|
||||
+143
-11
@@ -365,6 +365,13 @@ pub struct FontInfo {
|
||||
pub embedded: bool,
|
||||
pub subset: bool,
|
||||
pub encoding: Option<String>,
|
||||
/// The face standing in for a program the document does not embed. `None` when the font is
|
||||
/// embedded (nothing stands in) or when no face was available at all -- `substitution` then
|
||||
/// says which of the two it is.
|
||||
pub substitute: Option<String>,
|
||||
/// Why a face was substituted, as a sentence for whoever has to act on it. `None` only when
|
||||
/// the document's own glyphs are being shown.
|
||||
pub substitution: Option<String>,
|
||||
}
|
||||
|
||||
/// Check that `bytes` still open as the same document: the parser reads them and the page
|
||||
@@ -1290,20 +1297,38 @@ impl<'a> Inspector<'a> {
|
||||
_ => None,
|
||||
};
|
||||
// Embedded: FontFile/2/3 in the descriptor (for Type0, in the descendant font).
|
||||
let descriptor_of = |d: &Dictionary| d.get(b"FontDescriptor").ok().and_then(|o| self.dict(o)).cloned();
|
||||
let desc = descriptor_of(f).or_else(|| {
|
||||
f.get(b"DescendantFonts")
|
||||
.ok()
|
||||
.and_then(|o| self.resolve(o).as_array().ok())
|
||||
.and_then(|a| a.first())
|
||||
.and_then(|o| self.dict(o))
|
||||
.and_then(descriptor_of)
|
||||
});
|
||||
let descriptor_of = |d: &'a Dictionary| d.get(b"FontDescriptor").ok().and_then(|o| self.dict(o));
|
||||
let descendant =
|
||||
f.get(b"DescendantFonts").ok().and_then(|o| self.resolve(o).as_array().ok()).and_then(|a| a.first()).and_then(|o| self.dict(o));
|
||||
let desc = descriptor_of(f).or_else(|| descendant.and_then(descriptor_of));
|
||||
let embedded =
|
||||
kind == "Type3" || desc.is_some_and(|d| d.get(b"FontFile").is_ok() || d.get(b"FontFile2").is_ok() || d.get(b"FontFile3").is_ok());
|
||||
// What the substitution chain needs beyond the name: the descriptor flags, and for
|
||||
// a composite font the script its descendant declares (ISO 32000-2 9.7.3).
|
||||
let flags = desc
|
||||
.and_then(|d| d.get(b"Flags").ok())
|
||||
.and_then(|o| self.resolve(o).as_i64().ok())
|
||||
.and_then(|n| u32::try_from(n).ok())
|
||||
.unwrap_or(0);
|
||||
let ordering =
|
||||
descendant.and_then(|d| d.get(b"CIDSystemInfo").ok()).and_then(|o| self.dict(o)).and_then(|c| self.text(c, b"Ordering"));
|
||||
let subset = base.len() > 7 && base.as_bytes()[6] == b'+' && base[..6].bytes().all(|b| b.is_ascii_uppercase());
|
||||
let name = if subset { base[7..].to_string() } else { base };
|
||||
let info = FontInfo { name, kind, embedded, subset, encoding };
|
||||
let chosen = pdfcraft_fonts::fallback::substitute(&pdfcraft_fonts::fallback::Request {
|
||||
base_font: &name,
|
||||
embedded,
|
||||
flags,
|
||||
ordering: ordering.as_deref(),
|
||||
});
|
||||
let info = FontInfo {
|
||||
name,
|
||||
kind,
|
||||
embedded,
|
||||
subset,
|
||||
encoding,
|
||||
substitute: chosen.face_name(),
|
||||
substitution: (!chosen.why.is_faithful()).then(|| chosen.why.note().to_owned()),
|
||||
};
|
||||
if seen.insert((info.name.clone(), info.kind.clone(), info.embedded)) {
|
||||
out.push(info);
|
||||
}
|
||||
@@ -1981,7 +2006,20 @@ trailer << /Root 1 0 R >>
|
||||
let expected: &[u8] = if a.name == "notes.txt" { b"hello notes" } else { b"a,b,c" };
|
||||
assert_eq!(data, expected, "{}", a.name);
|
||||
}
|
||||
assert_eq!(info.fonts, vec![FontInfo { name: "Helvetica".into(), kind: "Type1".into(), embedded: false, subset: true, encoding: None }]);
|
||||
// A non-embedded /Helvetica is reported as standing in for itself: the standard-14
|
||||
// metrics are exact, so the substitution is named rather than left implicit.
|
||||
assert_eq!(
|
||||
info.fonts,
|
||||
vec![FontInfo {
|
||||
name: "Helvetica".into(),
|
||||
kind: "Type1".into(),
|
||||
embedded: false,
|
||||
subset: true,
|
||||
encoding: None,
|
||||
substitute: Some("Helvetica".into()),
|
||||
substitution: Some(pdfcraft_fonts::fallback::Why::NamedStandard14.note().into()),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
const LAYERS: &[u8] = b"%PDF-1.7
|
||||
@@ -2520,3 +2558,97 @@ mod version_tests {
|
||||
assert_eq!(super::version_label("Unknown"), "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod substitution_tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{FontInfo, inspect};
|
||||
|
||||
/// One page naming four fonts the document does not embed: a Chinese CID font (Adobe-GB1), a
|
||||
/// Japanese one (Adobe-Japan1), an unknown serif-flagged simple font, and an aliased
|
||||
/// standard-14 name.
|
||||
const NOT_EMBEDDED: &[u8] = b"%PDF-1.7
|
||||
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
|
||||
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
|
||||
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Resources << /Font << /F1 4 0 R /F2 6 0 R /F3 8 0 R /F4 9 0 R /F5 11 0 R >> >> >> endobj
|
||||
4 0 obj << /Type /Font /Subtype /Type0 /BaseFont /STSong-Light /Encoding /UniGB-UCS2-H /DescendantFonts [5 0 R] >> endobj
|
||||
5 0 obj << /Type /Font /Subtype /CIDFontType0 /BaseFont /STSong-Light /CIDSystemInfo << /Registry (Adobe) /Ordering (GB1) /Supplement 2 >> >> endobj
|
||||
6 0 obj << /Type /Font /Subtype /Type0 /BaseFont /KozMinPro-Regular /Encoding /UniJIS-UCS2-H /DescendantFonts [7 0 R] >> endobj
|
||||
7 0 obj << /Type /Font /Subtype /CIDFontType0 /BaseFont /KozMinPro-Regular /CIDSystemInfo << /Registry (Adobe) /Ordering (Japan1) /Supplement 4 >> >> endobj
|
||||
8 0 obj << /Type /Font /Subtype /TrueType /BaseFont /SomeHouseSerif /FontDescriptor 10 0 R >> endobj
|
||||
9 0 obj << /Type /Font /Subtype /TrueType /BaseFont /ArialMT >> endobj
|
||||
10 0 obj << /Type /FontDescriptor /FontName /SomeHouseSerif /Flags 34 >> endobj
|
||||
11 0 obj << /Type /Font /Subtype /TrueType /BaseFont /HouseGrotesk /FontDescriptor 12 0 R >> endobj
|
||||
12 0 obj << /Type /FontDescriptor /FontName /HouseGrotesk /Flags 33 >> endobj
|
||||
trailer << /Size 13 /Root 1 0 R >>
|
||||
%%EOF";
|
||||
|
||||
fn font<'a>(fonts: &'a [FontInfo], name: &str) -> &'a FontInfo {
|
||||
fonts.iter().find(|f| f.name == name).unwrap_or_else(|| panic!("{name} listed; got {:?}", fonts.iter().map(|f| &f.name).collect::<Vec<_>>()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doc_info_says_which_face_stands_in_and_why() {
|
||||
let info = inspect(Arc::new(NOT_EMBEDDED.to_vec()), None).expect("opens");
|
||||
|
||||
// The one that matters most to a user: a Chinese PDF that draws nothing. Before this,
|
||||
// doc_info said only `embedded: false` and left them to guess.
|
||||
let cjk = font(&info.fonts, "STSong-Light");
|
||||
assert!(!cjk.embedded);
|
||||
assert_eq!(cjk.substitute, None, "no licensed Chinese face, so nothing stands in");
|
||||
let why = cjk.substitution.as_deref().unwrap_or_default();
|
||||
assert!(why.contains("no licensed face") && why.contains("rather than the wrong glyphs"), "{why}");
|
||||
|
||||
// An aliased standard-14 name resolves to the real face, whose metrics are exact.
|
||||
let arial = font(&info.fonts, "ArialMT");
|
||||
assert_eq!(arial.substitute.as_deref(), Some("Helvetica"));
|
||||
assert!(arial.substitution.as_deref().unwrap_or_default().contains("standard 14"));
|
||||
|
||||
// A house font whose name merely contains "Serif" gets Times -- reported as the guess it
|
||||
// is, not as a standard-14 name, which would be untrue of a font nobody has heard of.
|
||||
let serif = font(&info.fonts, "SomeHouseSerif");
|
||||
assert_eq!(serif.substitute.as_deref(), Some("Times-Roman"));
|
||||
assert!(serif.substitution.as_deref().unwrap_or_default().contains("hints at one"), "{serif:?}");
|
||||
|
||||
// No hint in the name at all, so the descriptor flags decide: 33 = fixed pitch + nonsymbolic.
|
||||
let grotesk = font(&info.fonts, "HouseGrotesk");
|
||||
assert_eq!(grotesk.substitute.as_deref(), Some("Courier"));
|
||||
assert!(grotesk.substitution.as_deref().unwrap_or_default().contains("descriptor flags"), "{grotesk:?}");
|
||||
|
||||
// Japanese depends on the optional craft-fonts build input; either way it is reported,
|
||||
// and never as a Latin face.
|
||||
let jpan = font(&info.fonts, "KozMinPro-Regular");
|
||||
assert!(jpan.substitution.is_some(), "a substitution is always explained");
|
||||
match &jpan.substitute {
|
||||
Some(face) => {
|
||||
assert!(!face.starts_with("Helvetica") && !face.starts_with("Times"), "a Latin face is never a Japanese substitute: {face}")
|
||||
}
|
||||
None => assert!(jpan.substitution.as_deref().unwrap_or_default().contains("no licensed face")),
|
||||
}
|
||||
|
||||
// Nothing claims to be embedded, so every font here carries a reason.
|
||||
assert!(info.fonts.iter().all(|f| !f.embedded && f.substitution.is_some()), "{:?}", info.fonts);
|
||||
}
|
||||
|
||||
/// An embedded font is reported as faithful: nothing stands in, and there is nothing to explain.
|
||||
const EMBEDDED: &[u8] = b"%PDF-1.7
|
||||
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
|
||||
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
|
||||
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Resources << /Font << /F1 4 0 R >> >> >> endobj
|
||||
4 0 obj << /Type /Font /Subtype /TrueType /BaseFont /ABCDEF+SomeHouseSans /FontDescriptor 5 0 R >> endobj
|
||||
5 0 obj << /Type /FontDescriptor /FontName /ABCDEF+SomeHouseSans /Flags 32 /FontFile2 6 0 R >> endobj
|
||||
6 0 obj << /Length 4 >> stream
|
||||
test
|
||||
endstream endobj
|
||||
trailer << /Size 7 /Root 1 0 R >>
|
||||
%%EOF";
|
||||
|
||||
#[test]
|
||||
fn an_embedded_font_reports_no_substitution_at_all() {
|
||||
let info = inspect(Arc::new(EMBEDDED.to_vec()), None).expect("opens");
|
||||
let f = font(&info.fonts, "SomeHouseSans");
|
||||
assert!(f.embedded && f.subset, "{f:?}");
|
||||
assert_eq!((f.substitute.as_deref(), f.substitution.as_deref()), (None, None), "the document's own glyphs are shown");
|
||||
}
|
||||
}
|
||||
@@ -3436,8 +3436,8 @@ milestone = "M3"
|
||||
status = "shipped"
|
||||
commands = ["file.properties"]
|
||||
tools = ["doc_info"]
|
||||
evidence = ["crates/ui-egui/tests/view.rs::fonts_tab_lists_fonts_and_embedding"]
|
||||
notes = ""
|
||||
evidence = ["crates/ui-egui/tests/view.rs::fonts_tab_lists_fonts_and_embedding", "crates/render/src/inspect.rs::doc_info_says_which_face_stands_in_and_why", "crates/render/src/inspect.rs::an_embedded_font_reports_no_substitution_at_all"]
|
||||
notes = "name, type, embedding, subset and encoding; doc_info also reports the face standing in for a program the document does not embed and why it was chosen (Acrobat's Actual Font), including the case where no licensed face exists so nothing is drawn. Still missing: the Fonts tab in the interface does not show the substituted face yet, only the data layer does"
|
||||
|
||||
[[feature]]
|
||||
id = "organize.initial-view"
|
||||
|
||||
+3
-3
@@ -47,7 +47,7 @@ pub const ADOBE_DATA_ALLOWED: &[(&str, &str)] = &[("hayro-cmap", "assets/cmaps.b
|
||||
/// technical data we hold ourselves rather than through a dependency. Each still needs an
|
||||
/// `[[asset]]` entry with `adobe_data = true`, so its SHA-256 is checked and any change to the
|
||||
/// table has to be re-attested deliberately.
|
||||
pub const ADOBE_DATA_FILES: &[&str] = &["crates/fonts/src/std14.rs"];
|
||||
pub const ADOBE_DATA_FILES: &[&str] = &["crates/fonts/src/std14_metrics.rs"];
|
||||
|
||||
/// File extensions that count as assets wherever they appear in the repository.
|
||||
const ASSET_EXTENSIONS: &[&str] = &[
|
||||
@@ -580,7 +580,7 @@ mod tests {
|
||||
fn first_party_adobe_data_only_on_the_closed_list() {
|
||||
let l = lock(&[]);
|
||||
// The generated standard-14 table is on the list, as `kind = "data"`.
|
||||
let mut ok = asset("crates/fonts/src/std14.rs", "Adobe (Core14 AFM metrics); tables by the PdfCraft contributors", "data", "MIT");
|
||||
let mut ok = asset("crates/fonts/src/std14_metrics.rs", "Adobe (Core14 AFM metrics); tables by the PdfCraft contributors", "data", "MIT");
|
||||
ok.adobe_data = true;
|
||||
let m = Manifest { asset: vec![ok], ..Default::default() };
|
||||
assert!(check(&root(), &m, &[], &l).is_empty(), "{:?}", check(&root(), &m, &[], &l));
|
||||
@@ -592,7 +592,7 @@ mod tests {
|
||||
assert!(check(&root(), &m, &[], &l).iter().any(|p| p.contains("closed list")));
|
||||
|
||||
// Even on the list, it may only be data — never a typeface.
|
||||
let mut wrong_kind = asset("crates/fonts/src/std14.rs", "Adobe", "font", "MIT");
|
||||
let mut wrong_kind = asset("crates/fonts/src/std14_metrics.rs", "Adobe", "font", "MIT");
|
||||
wrong_kind.adobe_data = true;
|
||||
let m = Manifest { asset: vec![wrong_kind], ..Default::default() };
|
||||
assert!(!check(&root(), &m, &[], &l).is_empty());
|
||||
|
||||
Reference in new issue
Block a user