Writing WOFF2

Write WOFF2 when the files are served to browsers: it is the smallest container, and every current browser accepts it. The package transforms the tables itself and needs Brotli only for the final stream. PHP has no Brotli built in, so you pass one of two adapters to Woff2Writer.

With the PHP Brotli extension

Use this when the PHP runtime that runs your script has ext-brotli built with font mode, which the adapter checks through BROTLI_FONT. Create output first; the destination must be new.

<?php

require __DIR__.'/vendor/autoload.php';

use Alto\Font\Compression\BrotliExtensionCompressor;
use Alto\Font\Font;
use Alto\Font\Writer\Woff2Writer;

$font = Font::fromFile(__DIR__.'/fonts/Inter-Regular.ttf');
$destination = __DIR__.'/output/inter.woff2';

new Woff2Writer(new BrotliExtensionCompressor())->write($font, $destination);

printf("Saved %s (%d bytes)\n", basename($destination), filesize($destination));

With the brotli executable

Use this when brotli is installed on the machine, from a package manager for instance, and the PHP process may call proc_open():

<?php

require __DIR__.'/vendor/autoload.php';

use Alto\Font\Compression\BrotliProcessCompressor;
use Alto\Font\Font;
use Alto\Font\Writer\Woff2Writer;

$font = Font::fromFile(__DIR__.'/fonts/Inter-Regular.ttf');
$destination = __DIR__.'/output/inter.woff2';

new Woff2Writer(new BrotliProcessCompressor())->write($font, $destination);

printf("Saved %s (%d bytes)\n", basename($destination), filesize($destination));

Pass binary: '/opt/homebrew/bin/brotli' or another absolute path to the constructor when the executable is not on the PATH of the PHP process. The adapter also needs a writable temporary directory.

Choose a quality profile

Both adapters default to BrotliCompressionProfile::Maximum, quality 11. Fast, quality 5, suits builds you rerun on every change. Measured with the brotli 1.2 executable on the complete bundled Inter Latin sample:

Profile Size Time
Maximum 23,272 bytes 0.13 s
Fast 26,156 bytes 0.02 s
<?php

use Alto\Font\Compression\BrotliCompressionProfile;
use Alto\Font\Compression\BrotliProcessCompressor;

$compressor = new BrotliProcessCompressor(profile: BrotliCompressionProfile::Fast);

Deploy the files written with Maximum.

Write a subset

Pass the font returned by Subsetting to the same writer:

<?php

$result = $font->subset(new SubsetOptions($characters, glyphIds: GlyphIdPolicy::Compact));

new Woff2Writer(new BrotliExtensionCompressor())->write($result->font, __DIR__.'/output/inter-ui.woff2');

For the translated-interface subset of the Subsetting page, this gives 18,848 bytes, against 27,100 bytes for the same subset as WOFF.

When writing fails

CompressionException is raised when the extension lacks font mode, when the executable cannot be found or started, or when the temporary directory is not writable. Check the runtime that runs your script rather than your terminal: a PHP-FPM worker or a container often has neither the extension nor the executable. Adapters lists the checks.

Reading a WOFF2 file also needs Brotli; without it, Font::fromFile() raises UnsupportedFontException. WOFF2 under Formats describes the container itself.