pub struct FormFonts { /* private fields */ }Expand description
The faces a form’s default resources name, loaded once for a page.
§Why the fonts are loaded rather than substituted for
A generator wants metrics, and it was tempting to hand every generator
one stock Helvetica on the reasoning that a non-embedded /DA font
substitutes to that face anyway. The metrics do not agree with that
reasoning, and the disagreement is visible: an ascent and descent taken
from the base-14 metric tables are 718 and −219, while the ones taken from
the substituted face — the size the layout engine actually stacks lines
by — are the face’s own, and for the hermetic corpus’s metric-compatible
Helvetica that is 905 and −211. On a list box the difference is the row
pitch: 11.24 units per row against 13.39, which is two extra rows in a
thirty-unit box.
So the font a widget’s /DA names is loaded from the form’s /DR /Font,
through the same loader and the same substitution options every other font
on the page goes through. A name the resources do not carry gets a stock
Helvetica, which is what the fallback is actually for.
use pdfrum_doc::ap::FormFonts;
use pdfrum_object::{Dict, NoResolve};
// A catalog with no `/AcroForm` still yields the stock fallback face.
let mut ctx = pdfrum_page::BuildContext::new();
let fonts = FormFonts::load(&Dict::default(), &NoResolve, &mut ctx);
// A name the resources do not carry falls back rather than failing.
assert!(fonts.face(b"NoSuchFace").is_some());Implementations§
Source§impl FormFonts
impl FormFonts
Sourcepub fn load<R: Resolve>(
catalog: &Dict,
r: &R,
ctx: &mut BuildContext,
) -> Arc<FormFonts> ⓘ
pub fn load<R: Resolve>( catalog: &Dict, r: &R, ctx: &mut BuildContext, ) -> Arc<FormFonts> ⓘ
Loads every font a document’s interactive form declares, plus the fallback a name outside them resolves to.
The fallback is loaded unconditionally and stored under an empty name,
so a /DA naming nothing — or naming a font the resources lack — still
has a face to measure with. That is the same substitution a viewer
performs; it is only the metric source that this fixes.
§Memoized on the context
The faces are a pure function of the form’s /DR /Font, and building
them is expensive — the /DR walk constructs every font the form
declares, encoding tables and substitution ladder included. The
annotation overlay asks for them once per page per render, so the
result is cached in the BuildContext
beside the rest of the per-document font state. A caller threading one
context through many renders of one document pays for this once.
Nothing about what is built changed when the cache was added, which is what makes the appearance streams identical: the fallback still goes through the same loader, and the second faces are still loaded here rather than where a field discovers it needs one. Only the number of times moved.
§What the key names, and why it is not the /AcroForm
Building the faces reads exactly one thing out of the document — the
/AcroForm’s /DR /Font dictionary — and takes everything else from
dictionaries written in this crate. So the faces are a function of
that dictionary alone, and keying on the /AcroForm instead threw
away every form written as a direct dictionary, which has no reference
to name it by.
A direct /AcroForm is not the rarity it reads as. An empty
<</Fields[]>> is what a producer writes when it declares a form and
then puts no fields in it, and six of this corpus’s 44 documents carry
one — none of them a form document. Every one of those was rebuilding
the fallback face and the substitute face once per page per render, for
a form with no fields, and on four of them it was the single largest
line in the render.
use pdfrum_doc::ap::FormFonts;
use pdfrum_object::{Dict, NoResolve};
// A catalog with no `/AcroForm` still yields the stock fallback face.
let mut ctx = pdfrum_page::BuildContext::new();
let fonts = FormFonts::load(&Dict::default(), &NoResolve, &mut ctx);
// Loaded once per document and shared: a second load hits the cache.
let again = FormFonts::load(&Dict::default(), &NoResolve, &mut ctx);
assert!(std::sync::Arc::ptr_eq(&fonts, &again));Sourcepub fn substitute(&self, charset: Charset) -> Option<Substitute<'_>>
pub fn substitute(&self, charset: Charset) -> Option<Substitute<'_>>
The second face a character of charset is written in, with the alias
it is filed under and the dictionary that goes into the appearance’s
own resources.
Answers nothing for a charset with no encoding table, and for one whose
face would not load — in both cases the caller leaves the character to
the /DA font, which is the behaviour that predates this.
use pdfrum_doc::ap::FormFonts;
use pdfrum_object::{Dict, NoResolve};
// A catalog with no `/AcroForm` still yields the stock fallback face.
let mut ctx = pdfrum_page::BuildContext::new();
let fonts = FormFonts::load(&Dict::default(), &NoResolve, &mut ctx);
use pdfrum_font::Charset;
// Nothing for a charset with no encoding table, or whose face will
// not load: the caller then leaves the character to the `/DA` font.
let _ = fonts.substitute(Charset::ShiftJis);Sourcepub fn face(&self, name: &[u8]) -> Option<&Font>
pub fn face(&self, name: &[u8]) -> Option<&Font>
The face filed under one resource name, or the fallback.
Answers nothing only if the fallback itself is missing, which
Self::load makes impossible — the caller then generates chrome
alone rather than being told a face exists that does not.
use pdfrum_doc::ap::FormFonts;
use pdfrum_object::{Dict, NoResolve};
// A catalog with no `/AcroForm` still yields the stock fallback face.
let mut ctx = pdfrum_page::BuildContext::new();
let fonts = FormFonts::load(&Dict::default(), &NoResolve, &mut ctx);
assert!(fonts.face(b"Helv").is_some());
// Answers the fallback rather than nothing for an unknown name.
assert!(fonts.face(b"NoSuchFace").is_some());Sourcepub fn text_font<'a>(
&'a self,
name: &[u8],
width: &'a dyn Fn(u32) -> i32,
) -> Option<TextFont<'a>>
pub fn text_font<'a>( &'a self, name: &[u8], width: &'a dyn Fn(u32) -> i32, ) -> Option<TextFont<'a>>
A TextFont over one resource name, with width borrowed for the
same lifetime.
The width closure cannot live inside the returned value — it has to be
borrowed for the font’s lifetime, which a closure over self cannot
supply before self exists — so the caller keeps it and passes it in,
the same shape TextFont::metrics_of already has.
use pdfrum_doc::ap::FormFonts;
use pdfrum_object::{Dict, NoResolve};
// A catalog with no `/AcroForm` still yields the stock fallback face.
let mut ctx = pdfrum_page::BuildContext::new();
let fonts = FormFonts::load(&Dict::default(), &NoResolve, &mut ctx);
use pdfrum_doc::ap::TextFont;
// The width closure is borrowed for the font's lifetime, so the
// caller keeps it and passes it in.
let font = fonts.face(b"Helv").expect("the fallback face");
let width = |code: u32| TextFont::char_width(font, code);
assert!(fonts.text_font(b"Helv", &width).is_some());