Documentation page: Configuring PDF Fonts. This page is also available as Markdown at configuring-pdf-fonts.md.

Configuring PDF Fonts

The PD4ML PRO, DMS, and UA license tiers can embed any TrueType or OpenType font's full Unicode glyph range into the generated PDF, rather than relying only on PD4ML's own bundled font substitution. The mechanism behind this looks more involved than it needs to at first glance, and for a specific reason: in ordinary Java code, instantiating a java.awt.Font for a given face name and asking it for metrics is trivial, but PDF generation needs more than that. To embed a font (and to subset it down to only the glyphs actually used, keeping file size reasonable), PD4ML needs direct access to the underlying .ttf/.otf file itself -- and Java's own Font API offers no way to recover that file's location from a Font object. The practical workaround is a small mapping file, associating each font face name with the actual file that provides it. PD4ML's default name for this file is pd4fonts.properties.

As of v4.0.16, an interactive alternative to hand-building this file is also available: the PD4ML Fonts Tool in PD4Browser walks through the same configuration visually rather than via the command line.

Indexing a specific set of fonts

The common case: collect the TTF/OTF files a project actually needs into their own directory, then index that directory once, ahead of time, with the bundled command-line tool:

java -Xmx512m -jar pd4ml.jar -configure.fonts /path/to/my/fonts/
pd4ml.useTTF("/path/to/my/fonts/");
// or identically
pd4ml.useTTF("/path/to/my/fonts/pd4fonts.properties");

Indexing system fonts (read-only directories)

A system font directory (c:/windows/fonts, /usr/share/fonts, ...) usually can't be written to by an application process. Passing a second, writable path to -configure.fonts generates the mapping file there instead, while its entries still point back at the original, read-only font directory:

java -Xmx512m -jar pd4ml.jar -configure.fonts c:/windows/fonts/ c:/path/to/my/config
pd4ml.useTTF("c:/path/to/my/config");
// or identically
pd4ml.useTTF("c:/path/to/my/config/pd4fonts.properties");

Runnable example: N002TtfFonts.java -- indexes c:/windows/fonts into a separate pd4fonts.properties, then passes it to useTTF(...).

Font coverage index

From v4.2.0. A generated pd4fonts.properties also lists which characters each font file has. When a font lacks a character, PD4ML uses this list to skip the font files that cannot display it, instead of loading each of them to find out. This matters most for a large font directory: looking up a rare script among the Windows fonts loaded 175 font files without the index and 3 with it. The font PD4ML picks stays the same.

font.coverage=2/64
font.coverage.arial.ttf=1045956;0-1b,1d,22-23,74-86

font.coverage is the index format and the number of characters each listed unit stands for. A font.coverage.<file> entry holds the file length, then the hex units that have at least one character the font can display.

  • The index is generated with the mapping file by the command-line tool (-configure.fonts), PD4Util and PD4Browser's font configuration, not by useTTF(dir, true) or useTTF(dir, filter) during a conversion (see below).
  • A mapping file generated by an earlier version has no index and works as before. Regenerate it to get the speedup.
  • A local font file whose length no longer matches its entry is loaded and checked as usual, as is a symbol font, which has no entry.
  • Indexing takes longer than before, about 44 ms per font file, and the mapping file is larger: about 59 KB instead of 13 KB for 369 font files. A mapping built in memory through the pd4ml.autoindex.fonts runtime property has no index.

Generating the mapping on the fly

Rather than running the CLI tool as a separate step, passing true as a second argument to useTTF(...) builds (or refreshes) pd4fonts.properties automatically the first time it's needed:

pd4ml.useTTF("/path/to/my/fonts/", true);

This is convenient for a quick setup, but for a large font directory the indexing cost is paid on that request rather than ahead of time -- for anything beyond occasional/development use, pre-generating the file with the CLI tool (above) avoids repeating that cost on every cold start. A mapping file written this way has no font coverage index, so that the conversion that writes it does not wait for every font file to be indexed; generate the file with the CLI tool to get the index.

Runnable example: N001TtfFonts.java

Filtering which fonts get indexed

When only a handful of fonts within a large system directory actually matter, a comma-delimited list of name patterns limits indexing to just those, keeping an in-memory, on-the-fly mapping fast even against a directory with hundreds of installed fonts:

pd4ml.useTTF("c:/windows/fonts/", "arial,times,courier");

Packaging fonts into a JAR

For a web application deployment where arbitrary filesystem paths aren't available or desirable, fonts (and their generated pd4fonts.properties) can be packaged into an ordinary JAR alongside the application's other resources, then addressed with PD4ML's java: resource-URL scheme:

java -Xmx512m -jar pd4ml.jar -configure.fonts /path/to/my/fonts/

jar cvf fonts.jar /path/to/my/fonts/
pd4ml.useTTF("java:fonts/");

Web fonts via @font-face

Fonts can also be pulled in the same way a browser would, via an ordinary CSS @font-face rule -- either from a bundled resource, or fetched directly from a remote server -- without any dedicated useTTF(...) call at all. Both local (java:) and remote (https://) sources work, and either TTF or WOFF format:

@font-face {
  font-family: "Consolas";
  src: url("java:/html/rc/FiraMono-Regular.ttf") format("ttf");
}
@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/s/lato/v11/qIIYRU-oROkIk8vfvxw6QvesZW2xOQ-xsNqO47m55DA.woff) format('woff');
}

Runnable examples: H001ConvertHtml.java (a bundled java: TTF resource), Z001ConvertHtml.java (a font fetched over https://).

Font kerning

Kerning nudges the spacing between specific pairs of characters in a proportional font closer together or further apart than their default advance widths would otherwise place them -- classic examples being the tight overlap of "AV" or "Wa". Applied correctly, it reads as more visually polished; left off, tightly-spaced letter pairs can look loose or unevenly spaced compared to the rest of the line:

Side-by-side comparison of the letter pairs "AV" and "Wa" rendered with and without kerning applied

pd4ml.applyKerning(true);

Running PD4ML as the standalone command-line tool achieves the same with the -kerning flag.

See also