Skip to content
getnextpdf.com

Pro edition

Font Tools — Deep Reference

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.

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.

SymbolParametersDefault behaviorReturnsThrows or fails withNotes
FontDesubsetternoneStateless scanner over raw PDF bytesfinal; safe to reuse across documents
FontDesubsetter::analyzeSubsets()string $pdfDataReports every distinct /BaseFont entry, subset or not, flagged by isSubsetlist<SubsetInfo>InvalidArgumentException when a width-derived subset estimate exceeds the name-derived full-count estimateByte-level scan; compressed object streams are not decoded
FontDesubsetter::isSubsetFont()string $baseFontNameMatches the six-uppercase-letter-plus-+ prefix conventionboolAnchored at the start of the name
FontDesubsetter::extractSubsetPrefix()string $baseFontNameReturns the six-letter subset tagstringEmpty string for non-subset names
FontDesubsetter::generateDesubsetPlan()list<SubsetInfo> $subsetsCollects entries whose isSubset is true and sums the size estimateDesubsetPlanDoes not throwNon-subset entries are skipped silently
SubsetInfo::__construct$fontName, $baseFont, $subsetGlyphCount, $fullGlyphCount, $isSubset, $encodingImmutable description of one /BaseFont entryInvalidArgumentException on a negative glyph count, or a subset count above the full countfinal readonly; all properties public
SubsetInfo::subsetPrefix()noneExtracts the six-letter tag from fontNamestringEmpty string when not a subset or the + sits away from position six
SubsetInfo::coveragePercent()noneSubset share of the full glyph setfloat in [0.0, 100.0]Returns 0.0 when fullGlyphCount is 0
DesubsetPlan::__constructlist<SubsetInfo> $targets, int $estimatedSizeIncreaseImmutable de-subsetting planfinal readonly; all properties public
DesubsetPlan::count()noneNumber of targeted fontsintEquals the length of targets
DesubsetPlan::totalGlyphsNeeded()noneMissing glyphs summed across all targetsintSum of fullGlyphCount - subsetGlyphCount per target
public function analyzeSubsets(string $pdfData): array
public function isSubsetFont(string $baseFontName): bool
public function extractSubsetPrefix(string $baseFontName): string
public function generateDesubsetPlan(array $subsets): DesubsetPlan
public 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(): float
public function __construct(
public array $targets,
public int $estimatedSizeIncrease,
) {}
public function count(): int
public function totalGlyphsNeeded(): int

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.

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.

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.

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.

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.

  • SubsetInfo construction rejects invalid states: a negative glyph count, or a subset count above the full count, throws InvalidArgumentException.
  • 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. /BaseFont entries serialized inside compressed object streams are invisible; decompress those streams before scanning.
  • Entries whose /BaseFont key 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 encoding Unknown and a subset glyph count of 0.
  • PDF names using #-escaped bytes are reported in raw escaped form; escapes are not decoded.
  • Duplicate /BaseFont names 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 with isSubset set to false are simply excluded from targets.
  • All counts and estimatedSizeIncrease are 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.
ClaimStandardClause
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.

  • Install with composer require nextpdf/pro:^3. Available since nextpdf/pro 1.9.0; current in nextpdf/pro 3.1.0.
  • FontDesubsetter is 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::isSubset before acting; the result list intentionally includes non-subset fonts for inventory purposes.
  • Use DesubsetPlan::totalGlyphsNeeded() and estimatedSizeIncrease to 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.

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.