Skip to main content

FormFonts

Struct FormFonts 

Source
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

Source

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));
Source

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);
Source

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());
Source

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());

Trait Implementations§

Source§

impl Debug for FormFonts

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.