Technical Article

Variable Font Axes in PDF: Keep the Outline and Metadata in Sync

A heading rendered at variable-font weight 650 looks right in a PDF, but the embedded font reports a different weight or width. HotPDF resolves the selected axes when creating a static instance; the coordinates used for interpolation and the metadata describing that instance have different jobs

A variable font holds a range of possible outlines and metrics in one font file. Embedding a static instance means selecting a point in that range, producing the corresponding glyphs and widths, and writing metadata that describes the same choice. The visual result is only one part of that work. An application reading the embedded font can also look at weight, width class, italic angle and naming information, so a heading can appear plausible while its font metadata tells a different story

One font file, many positions on an axis

Start with fvar. It lists axes such as wght, wdth and slnt with minimum, default and maximum values in the font's user coordinate system; a named instance supplies a preset combination of those values. A request for weight 650 is one such user coordinate. The font engine converts it to normalized coordinates for interpolation, and an avar table can change that mapping nonlinearly. Think of a dimmer: the setting printed by the knob identifies what the user chose, while the mechanism inside controls how the light responds. Copy the mechanism's internal position onto the label and the light may look fine, but the label no longer describes the selection. Font metadata needs the selected user value, while the outline calculation uses the mapped coordinate appropriate to interpolation

Why inspect the axes before registration?

HotPDF exposes the workflow through InspectVariableFont, SetVariableFontAxis and RegisterUnicodeTTF. Before showing a weight control, read the actual font's axis range: the presence of a wght axis does not mean every font accepts the same minimum, default or maximum

Pdf.SetVariableFontAxis('wght', 650);
Pdf.RegisterUnicodeTTF(FontPath);
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont(FamilyName, [], 12, DEFAULT_CHARSET);
Pdf.CurrentPage.UnicodeTextOut(72, 100, 0, 'Annual report');
Pdf.EndDoc;

Choose the axis value before you register the font. A registered instance retains its selection; changing the axis afterward affects a later registration rather than rewriting the instance already in use. In the following code, Pdf is a configured THotPDF, FontPath names the variable font file, and FamilyName is a name accepted by registration. Keep the numeric selection with your generation job even if the user interface calls it “Semibold.” The label helps a person choose, but the coordinate is needed to repeat the selection. If users can supply arbitrary fonts, show their actual ranges instead of presenting a fixed list that happens to work for the font used during development

uses
  SysUtils, HPDFDoc, HPDFOpenTypeAdvanced;

procedure ShowFontAxes(Pdf: THotPDF; const FontPath: string);
var
  Axes: THPDFVariableFontAxisArray;
  Instances: THPDFVariableFontNamedInstanceArray;
  I: Integer;
begin
  if not Pdf.InspectVariableFont(FontPath, Axes, Instances) then
    raise Exception.Create('Cannot inspect variable font');
  for I := 0 to High(Axes) do
    Writeln(string(Axes[I].Tag), ': ', Axes[I].MinimumValue:0:2,
      ' / ', Axes[I].DefaultValue:0:2,
      ' / ', Axes[I].MaximumValue:0:2);
end;

Run the axis reader against the same file you intend to register. Its minimum, default and maximum values let the application check a proposed weight before generating the PDF; if 650 is outside that font's range, choose a value the font actually declares

Why width is not a number from 1 to 9

The width axis and the static font's width class use different units. wdth is a percentage of normal width; OS/2.usWidthClass is an integer category from 1 through 9. Writing 75 directly into the class field would be meaningless even if 75 is the requested width percentage. HotPDF maps the resolved percentage between defined percentage anchors, rounds the resulting category and keeps it within the 1–9 range. The conversion uses floor(value + 0.5). Weight metadata is likewise derived from the resolved user-space value with explicit rounding, rather than depending on the host language's default behavior for half values. Those details matter when a selected coordinate falls between category anchors: the outline and the classification should describe the same chosen instance, even though their numbers need not match

A nonlinear avar mapping makes it especially easy to confuse the number used inside interpolation with the value selected by the user. Take a font whose width range includes 75 and 100. Selecting 75 chooses a condensed position on that font's width scale; a tool reporting width class 3 is displaying an OS/2 category, while an axis reader reporting 75 is displaying the requested percentage. Neither number can simply be copied into the other's field. The mapping used for interpolation may change the normalized position of that request, but it should not turn an internal interpolation coordinate into the value advertised as the selected width. We would keep both representations visible during a font investigation: the requested coordinate explains the job setting, and the mapped coordinate explains the glyph interpolation

Unspecified axes still need values. If the application sets only wght, HotPDF resolves the other declared axes to their defaults when preparing the instance. Two jobs can therefore share the same weight while producing different results when one job selects a different width or another axis. Record the complete resolved coordinate set when you diagnose a mismatch; the weight alone does not describe the whole instance

Slant and italic are different choices

For slnt, the value is an angle, and the font's post.italicAngle uses the same sign convention. A rightward slant is normally negative. Reversing that sign can produce a font whose outline leans one way while its metadata describes the opposite angle. The ital axis has a different role as a classification; its value is not an angle in degrees. Naming also deserves its own check. Family and style names, legacy style-link flags and the actual outlines can all influence how a consumer presents a regular/italic pair, so setting one axis does not settle every naming decision

What should you inspect in the output?

Use the default instance and a clearly nondefault instance of the same font, then render the same text at the same size. The glyph shape and width should reflect the selected axes. When you open the embedded font, read the recorded weight, width class and italic angle too: a visual result cannot reveal every metadata error. A font with nonlinear avar data is particularly valuable for this exercise because a mistaken use of interpolation coordinates may be less obvious when the mapping is linear. Include a width between class anchors and a negative slant, in addition to the default selection, to exercise both rounding and sign handling

A PDF does more than paint a heading. Open it in the viewers the application targets and check text extraction and selection along with the visible glyphs. For a mismatch, retain the font file, selected axis values and generation settings with the result. A family name by itself cannot identify the axis ranges or mappings of a particular font file; even different versions carrying the same family name can expose different defaults. Use the same text and size for the default and selected instances when measuring the change, and check whether any difference comes from the glyph shape, its advance width or the metadata recorded for the static font

Register each instance you intend to use deliberately when a document mixes regular and selected variations. A change in advance width can move a line break even if individual glyph outlines look correct, so measure a line and look at a paragraph boundary as well as a single character. For the wider embedding workflow, see static instances of variable fonts and CFF2 font embedding. The axis-selection APIs are part of the HotPDF Delphi Component