BFO PDF Library 2.29.2, with variable fonts

BFO PDF Library 2.29.2

Going only by the number of features in the Release Notes, it doesn't look like much is new in our new 2.29.2 release, with only four changes itemized. However the primary one is added support for OpenType variable fonts, and that one-line summary and the new class that represents them in the API is the visible tip a 9000-line diff iceberg.

Variable Fonts

The first thing to say is PDF does not support OpenType variable fonts, nor some of the other OpenType innovations of the last decade such as Color Fonts. There are good reasons for this (if you're attending PDF Days Europe 2025 in Berlin this September I will be doing a talk on this, at an excruciating level of detail).

But variable fonts are becoming more common on the Web platform in particular, so we addded support in our PDF Library primarily so they can be used in BFO Publisher, which is built on top of our PDF Library. However you don't need BFO Publisher to use them - the font variation axes can be used directly from the API.

First you'll need a variable font; there are a number at fonts.google.com where you can filter to list only variable fonts. The above image uses Roboto Flex, and the PDF API can accept OpenType/TrueType, WOFF and WOFF2 formats. From there, it's easy.

   OpenTypeFont font = new OpenTypeFont(new File("fontfile.ttf"), null);
   OpenTypeFont.Axes axes = font.getAxes(); // This is the new method

   if (axes.isVariable()) {
       // Font is variable! List the axes available in the font
       List<String> axes = axes.getTags();
       System.out.println("Axes: " + axes);
       // Some fonts come with predefined "named" variations...
       if (axes.namedVariations().containsKey("Bold")) {
           axes = axes.namedVariations().get("Bold");
       } else {
           // ... but it's just as easy to create your own variation
           axes = axes.vary("wght", 700);
       }
   } else {
       System.out.println("Not a variable font");
   }

   // Create a new font from the original and the modified axes values.
   OpenTypeFont boldfont = new OpenTypeFont(font, axes);
  

The key points here are:

Because PDF does not support variable OpenType fonts, a new static instance of each font variation is created and embedded in the PDF. This sounds expensive, but font subsetting keeps the size to a minimum: in the example image above each character is in a different weight, so each one gets its own font. When compressed in the PDF each font is about 8kB, although of course that will vary depending on the number of glyphs and the details of the font. With hundreds of different variations it might be an issue, but PDF is not a dynamic format; we anticipate most PDFs will have less than that.

Font Variations work with OpenType layout, so if you want to use variable fonts with Arabic, Hindi or other languages with complex-layout, that's just fine - and note that kerning in variable fonts is only supported with the full OpenType layout model, so we recommend setting the opentype feature as shown below. Once embedded they are identical to regular OpenType fonts, so can be used with PDF/A and PDF/UA. The PDF Library only supports varying "glyf" font outlines and does not yet support varying "CFF" outlines, although so far these seem to be very rare (get in touch if you find you need this functionality).

And as with the rest of our API this is all done in 100% Java; no native libraries.

We hope you find OpenType Variable fonts simple to use - they were certainly not simple to implement! Let us know if you find them useful. The new build is available for download at https://bfo.com/download/ as always.

Below is the code we used to create the above example and here is the generated PDF.

  PDF pdf = new PDF();
  PDFPage page = pdf.newPage("A4");

  float fontSize = 60;
  OpenTypeFont font = new OpenTypeFont(new File("fontfile.pdf"), null);
  font.setFeature("opentype", true);    // Required for kerning
  OpenTypeFont.Axes axes = font.getAxes();
  float min = axes.getMinValue("wght"); // Minimum weight in the font
  float max = axes.getMaxValue("wght"); // Maximum weight in the font
  LayoutBox box = new LayoutBox(500);
  PDFStyle style = new PDFStyle();
  style.setFillColor(Color.black);

  String text = "Variable Fonts";
  int size = text.length() - 1;
  for (int i=0;i<=size;i++) {
    float v = min + (float)i / size * (max - min); // value between min..max
    style.setFont(new OpenTypeFont(font, axes.vary("wght", v)), fontSize);
    box.addText(text.substring(i, i + 1), style, null);
  }

  page.drawLayoutBox(box, 50, page.getHeight() - 50);
  pdf.render(new FileOutputStream("variable.pdf"));