Pro edition
Font Tools — Deep Reference
At a glance
Section titled “At a glance”This page is the contract-level reference for NextPDF Pro Font Tools. The surface is one scanner, NextPDF\Pro\FontTools\FontDesubsetter, and two immutable value objects, SubsetInfo and DesubsetPlan. The scanner reads raw PDF bytes, reports every distinct /BaseFont entry, and flags entries that follow the subset naming convention of ISO 32000-2:2020 §9.9.2. A plan aggregates the flagged subsets and estimates the byte cost of restoring full font programs. The module analyzes and estimates only; it never rewrites an embedded font program. This page states the public API, the observable behavior contract, and the failure modes.
Availability & licensing
Section titled “Availability & licensing”This capability ships in NextPDF Pro (nextpdf/pro) and activates with a Pro-tier license envelope. A deployment without that entitlement does not load the capability’s classes. Compare editions and get a license.
No per-feature license flag gates this module. The Font Tools classes are available whenever nextpdf/pro is installed.
Public API surface
Section titled “Public API surface”| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
FontDesubsetter | none | Stateless scanner over raw PDF bytes | — | — | final; safe to reuse across documents |
FontDesubsetter::analyzeSubsets() | string $pdfData | Reports every distinct /BaseFont entry, subset or not, flagged by isSubset | list<SubsetInfo> | InvalidArgumentException when a width-derived subset estimate exceeds the name-derived full-count estimate | Byte-level scan; compressed object streams are not decoded |
FontDesubsetter::isSubsetFont() | string $baseFontName | Matches the six-uppercase-letter-plus-+ prefix convention | bool | — | Anchored at the start of the name |
FontDesubsetter::extractSubsetPrefix() | string $baseFontName | Returns the six-letter subset tag | string | — | Empty string for non-subset names |
FontDesubsetter::generateDesubsetPlan() | list<SubsetInfo> $subsets | Collects entries whose isSubset is true and sums the size estimate | DesubsetPlan | Does not throw | Non-subset entries are skipped silently |
SubsetInfo::__construct | $fontName, $baseFont, $subsetGlyphCount, $fullGlyphCount, $isSubset, $encoding | Immutable description of one /BaseFont entry | — | InvalidArgumentException on a negative glyph count, or a subset count above the full count | final readonly; all properties public |
SubsetInfo::subsetPrefix() | none | Extracts the six-letter tag from fontName | string | — | Empty string when not a subset or the + sits away from position six |
SubsetInfo::coveragePercent() | none | Subset share of the full glyph set | float in [0.0, 100.0] | — | Returns 0.0 when fullGlyphCount is 0 |
DesubsetPlan::__construct | list<SubsetInfo> $targets, int $estimatedSizeIncrease | Immutable de-subsetting plan | — | — | final readonly; all properties public |
DesubsetPlan::count() | none | Number of targeted fonts | int | — | Equals the length of targets |
DesubsetPlan::totalGlyphsNeeded() | none | Missing glyphs summed across all targets | int | — | Sum of fullGlyphCount - subsetGlyphCount per target |
Entry-point signatures
Section titled “Entry-point signatures”public function analyzeSubsets(string $pdfData): array
public function isSubsetFont(string $baseFontName): bool
public function extractSubsetPrefix(string $baseFontName): string
public function generateDesubsetPlan(array $subsets): DesubsetPlanpublic function __construct( public string $fontName, public string $baseFont, public int $subsetGlyphCount, public int $fullGlyphCount, public bool $isSubset, public string $encoding,)
public function subsetPrefix(): string
public function coveragePercent(): floatpublic function __construct( public array $targets, public int $estimatedSizeIncrease,) {}
public function count(): int
public function totalGlyphsNeeded(): intBehavior contract
Section titled “Behavior contract”Scan and subset detection
Section titled “Scan and subset detection”analyzeSubsets() extracts /BaseFont name tokens from the raw bytes with a byte-level pattern match. Duplicate names collapse to one entry; order follows first appearance. Every distinct name yields a SubsetInfo, whether or not it is a subset. A name is a subset when it begins with exactly six uppercase ASCII letters followed by +, the §9.9.2 convention. For subset names, baseFont is the name with the seven-character prefix removed. For ordinary names, baseFont equals fontName. Each distinct subset name is reported as its own entry, matching the §9.9.2 guidance to treat subsets as independent entities.
Encoding detection
Section titled “Encoding detection”For each font, the scanner searches a bounded byte window after the /BaseFont occurrence. An /Encoding name entry in the window wins. Failing that, an Identity-H or Identity-V substring in the window is reported. Failing both, the entry reports Unknown. Encoding values held in dictionaries or reached through indirect references report Unknown.
Glyph accounting
Section titled “Glyph accounting”Both glyph counts are estimates. subsetGlyphCount derives from width arrays visible near the font entry: a CIDFont /W array yields roughly one glyph per width triple, and a simple-font /Widths array yields one glyph per numeric entry. When neither array is visible in the window, a small fixed default applies. When the /BaseFont occurrence cannot be relocated for the window search, the count is 0. fullGlyphCount derives from family-name heuristics: a table of well-known Latin families, a set of CJK family-name indicators, and a generic floor otherwise. The embedded font program is never parsed. The specific tables, window sizes, and constants are implementation detail, are not published, and may change between releases.
Plan generation
Section titled “Plan generation”generateDesubsetPlan() filters the input to entries whose isSubset is true. Each target contributes its missing-glyph count, multiplied by a fixed average-bytes-per-glyph constant, to estimatedSizeIncrease. The plan is a projection for capacity decisions, not a measured delta. Executing a plan — rewriting font programs — is outside this module.
Determinism
Section titled “Determinism”The whole surface is a pure function of its input. Identical bytes produce identical results. There is no randomness, no network call, and no filesystem access.
Edge cases & failure modes
Section titled “Edge cases & failure modes”SubsetInfoconstruction rejects invalid states: a negative glyph count, or a subset count above the full count, throwsInvalidArgumentException.analyzeSubsets()can propagate that exception in one corner: a font whose name matches a known family but whose visible width array yields a larger subset estimate than the family’s full-count figure.- Detection operates on the byte representation.
/BaseFontentries serialized inside compressed object streams are invisible; decompress those streams before scanning. - Entries whose
/BaseFontkey and value are separated by whitespace other than a single space are still detected, but the per-font window search fails to relocate them. Such entries report encodingUnknownand a subset glyph count of0. - PDF names using
#-escaped bytes are reported in raw escaped form; escapes are not decoded. - Duplicate
/BaseFontnames collapse into a single entry. Two distinct font objects sharing one name are indistinguishable to this scanner. generateDesubsetPlan()never fails on non-subset input; entries withisSubsetset tofalseare simply excluded fromtargets.- All counts and
estimatedSizeIncreaseare heuristics. Do not treat them as measured values; use them for triage and capacity planning only. - No cryptographic operation occurs in this module, so there is no FIPS-mode-specific behavior.
Conformance
Section titled “Conformance”| Claim | Standard | Clause |
|---|---|---|
Subset detection matches the subset naming convention: a tag of six uppercase letters followed by + prefixed to the BaseFont value. | ISO 32000-2:2020 | §9.9.2 |
| Each distinct subset name is reported independently, following the recommendation to treat multiple subsets as separate entities. | ISO 32000-2:2020 | §9.9.2 |
All clauses are paraphrased; NextPDF does not reproduce normative text. The module asserts detection of the naming convention and deterministic reporting; it does not assert the accuracy of glyph-count or size estimates.
Development notes
Section titled “Development notes”- Install with
composer require nextpdf/pro:^3. Available sincenextpdf/pro1.9.0; current innextpdf/pro3.1.0. FontDesubsetteris stateless. Construct once and reuse it across documents and threads of work.- Feed
analyzeSubsets()decompressed bytes when subset coverage matters; object-stream-packed font dictionaries are otherwise missed. - Branch on
SubsetInfo::isSubsetbefore acting; the result list intentionally includes non-subset fonts for inventory purposes. - Use
DesubsetPlan::totalGlyphsNeeded()andestimatedSizeIncreaseto decide whether de-subsetting is worth the file-size cost before sourcing full font programs. - Scanning is linear in input length with bounded per-font window searches. The module stores nothing and emits no telemetry.
Publication boundary
Section titled “Publication boundary”This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.
See also
Section titled “See also”- Font Tools (capability) — install, quick start, and planning workflow samples.
- Optimizer — Deep Reference — the sibling size-reduction surface, including font-related optimization.
- Core font module — font embedding and subsetting during document creation in NextPDF Core.