PDF Library for Delphi accepts the fonts a web project already ships. It imports WOFF2 containers, enumerates the design axes of a variable font as JSON, and instantiates a chosen set of axis coordinates into a static font that the ordinary embedding, subsetting and extraction paths handle without knowing anything about variations. Both TrueType outlines and CFF2 outlines are covered
The situation this addresses is common and irritating. A brand's typography is defined in a design system, the design system ships variable WOFF2 files, and the PDF generator cannot read either the container or the variation data. The usual workaround — asking the foundry for static TTF instances of every weight actually used — takes weeks and produces files nobody keeps in sync
Why is WOFF2 not just a compressed font?
Treating WOFF2 as a Brotli wrapper and decompressing it gives you bytes that are not a valid sfnt. The format also permits transformed and reordered tables: glyf, loca and hmtx can be stored in a rearranged form that must be reversed before the font is parseable. A general-purpose decompressor cannot do that
On Windows, the platform has provided container analysis and unpacking since Windows 10 version 1703, so the library uses the system implementation rather than shipping another decoder. That decision keeps the deployment free of an extra DLL and means the transform reversal is handled by the same code the operating system's own text stack uses. On other platforms, WOFF1 still travels a pure Pascal path and WOFF2 explicitly reports as unsupported, which is an honest answer rather than a silently corrupted font
The import path validates before it decodes and after: the 48-byte header, the container length, the reserved fields, the declared sfnt size and a 512 MiB ceiling are all checked first, and the decoded stream size is compared against the declaration afterward. A web font is downloaded content, and the two failure modes worth blocking are a size mismatch and a decompression bomb
uses
PDFlibrary;
var
Lib: TPDFlib;
FontID, InstanceID: Integer;
AxesJSON: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
Lib.SetPageSize('A4');
Lib.NewPage;
// WOFF2, WOFF1, TTF, OTF and TTC all enter through the same call
FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
if FontID <= 0 then
raise Exception.Create('font could not be imported');
// What can this font actually do?
if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
Log(AxesJSON); // tags, minimum, default, maximum per axis
// Bake one design location into a static font
InstanceID := Lib.CreateOpenTypeFontVariation(FontID,
'wght=620,wdth=87.5');
Lib.DrawOpenTypeText(56, 760, InstanceID, 24, 'Quarterly results', 0);
Lib.SaveToFile('report.pdf');
finally
Lib.Free;
end;
end;
What instancing has to rebuild, and what it removes
A variable font stores one default outline per glyph plus deltas indexed by position in the design space. Producing a static instance means interpolating those deltas at the requested coordinates, moving the points, and then recomputing everything derived from them
For TrueType outlines that means parsing fvar, avar, gvar, HVAR and MVAR, applying tuple deltas with shared and private point numbering, and interpolating the points that carry no explicit delta. Horizontal phantom points together with HVAR determine advance width and left side bearing. Composite glyphs need their component dependencies and point mappings resolved first, then translation, scale, matrix and attachment offsets applied — with depth limits and cycle detection, because a malicious component graph would otherwise recurse without end
Metadata moves too, and this is the part most implementations skip. MVAR updates the global metrics in hhea, OS/2 and post, so line spacing, underline position and italic angle match the instance rather than the default. The wght, wdth, ital and slnt axes additionally update the weight class, width class, selection flags, macStyle and italic angle, so a tool inspecting the static font reports the style you asked for. A font whose geometry is bold and whose metadata says regular will be mislabelled everywhere downstream
Output then flattens composite outlines and removes the tables that no longer apply: the variation tables, hints, device tables and any signature. Glyph bounds, hhea summary metrics, the maxp hint ceilings, per-table checksums and the whole-font checksum are all recomputed
CFF2 takes a different route to the same place
CFF2 cannot be embedded as a PDF font program directly: its charstrings carry no widths, and it relies on the surrounding OpenType tables for glyph order and metrics. So instancing a CFF2 font produces a CID-keyed CFF OpenType font instead, which the existing drawing, embedding and subsetting paths already handle
Loading a CFF2 font immediately produces a default-coordinate static instance for the current document, while the original CFF2 sfnt is retained on the font object so axis enumeration and later instancing still see the full design space. Each glyph is de-subroutinised individually during instancing, which removes the variation arithmetic entirely and lets the existing subsetter work unchanged. The trade is that a static instance can be slightly larger than one that kept shared subroutines — a fair price for not needing a second subsetting implementation
Verifying an instance before you trust it
Instancing is the kind of feature that can be subtly wrong in ways no exception reports. Three checks catch nearly everything
Export the instance and open it in a font tool. SaveFontToFile writes the retained font program, so an in-memory instance can be dumped without producing a PDF first. The exported file should parse cleanly, contain no variation tables, and report the style metadata you requested
Compare advances at a non-default location. If your pipeline can produce the same instance another way, compare advance widths and curve bounds glyph by glyph; the reference implementation here matched an independent instancing of a 313-glyph two-axis font with a maximum difference of zero across every glyph
And extract the text back out of the generated PDF. An instance that embeds but produces no extractable text has lost its ToUnicode mapping, which matters for search and accessibility long after the visual check passed. The wider extraction path is covered in text, image and font extraction
// Dump a generated instance for inspection or reuse
Lib.SaveFontToFile('Inter-620-87.ttf');
// Ask for coordinates outside the declared range and they are clamped,
// so read the axis JSON rather than assuming a font's limits
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}
Practical notes for a brand-font pipeline
Cache instances by coordinate set. Each distinct set of coordinates is a distinct font program, so a document mixing weight 400 and weight 620 embeds two subsets rather than one shared face. Generating the same instance repeatedly is wasted work; generating dozens of near-identical weights is wasted file size
Measure with the instance you will draw with. A condensed instance has narrower advances than the default, so any layout that measured before instancing measured the wrong widths. This is the single most common cause of text that overflows its box only in production
Keep a fallback path for the glyphs your brand font lacks. A variable brand font typically covers Latin and little else, and a report containing a Cyrillic customer name will otherwise show missing glyphs; the automatic fallback mechanism is described in automatic font fallback for missing glyphs. And when the goal is a smaller file rather than a specific weight, subsetting does more work than instancing does, as covered in PDF file size optimization and font subsetting
Web font import, variable instancing, subsetting and fallback are part of the same font pipeline for Delphi, C++Builder and Free Pascal; the complete feature list is on the PDF Library for Delphi page