Font Subsetting
A fully embedded font program carries every glyph it ships with, which for a CJK or an emoji face can be several megabytes against a document that uses forty characters. Subsetting rewrites the embedded program so it keeps only the glyphs the document references. On text-heavy files with large embedded fonts this is usually the single largest saving available.
There are two entry points, and they operate at different scopes.
| Call | Scope |
|---|---|
optimize.Options{SubsetFonts: true} | Every embedded font in the document being written. |
Creator.EnableFontSubsetting(font) | One font, registered on the creator that is producing the document. |
SubsetFonts is the one to use when processing a file you did not create, since it works
from the object list and needs no knowledge of how the document was built.
EnableFontSubsetting is for creator output where you want a specific face subset and the
others left alone, which matters when one of the fonts is intended to be reusable.
SubsetFonts implies CleanFonts: both flags append the same pass, and any font that
cannot be subset still gets the table cleanup.
Doing it
pdfWriter, err := reader.ToWriter(nil)
if err != nil {
return err
}
pdfWriter.SetOptimizer(optimize.New(optimize.Options{
SubsetFonts: true,
}))
return pdfWriter.WriteToFile(outputPath)Subsetting has to know which glyphs are in use before it can drop the rest. The pass finds that out by running text extraction over every page, plus annotation appearance streams, and collecting the glyphs each font was asked for. Which glyphs get kept depends on the font’s encoder: identity encoders contribute glyph indices directly, simple encoders contribute the runes their charcodes map to.
Combining it with the other options is fine, and font work always runs first in the chain. See optimizer options for the rest.
Limitations
Only fonts embedded through FontFile2 are subset, which in practice means TrueType and
TrueType-flavored OpenType. A Type 1 or CFF program in FontFile or FontFile3 is
skipped, as is an OpenType font with PostScript outlines, recognizable by a program
starting with OTTO. Non-embedded fonts have nothing to subset.
If the rewritten program comes out larger than the original, the original is kept. A file already produced with subsetting will therefore report close to no saving rather than growing.
When no glyph usage is registered for a font, subsetting is skipped for that program rather than emptying it, and the generic font cleanup runs over it instead. This is what happens when text extraction cannot attribute any mark to the font.
Failures do not surface. Chain.Optimize logs an optimizer error at debug level and
continues with the objects it was given, so a font that fails to parse leaves the write
successful and the file larger than expected.
Subsetting is destructive with respect to later editing. The output font contains only the glyphs that were in use, so appending text in that font to the document afterwards can find the glyphs missing.
Run the example
pdf_font_subsetting.go reads a file, enables SubsetFonts alone, and prints the size
before and after so the saving attributable to fonts is visible on its own.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/compress
go run pdf_font_subsetting.go <input.pdf> <output.pdf>If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.
View the full source
Sample output
Against the same input as the optimizer options example, fonts alone account for more than twice the saving of the whole structural chain, and take roughly seven times as long, since every page has to be run through text extraction.
Original file: input.pdf
Original size: 266845 bytes
Optimized file: output.pdf
Optimized size: 124868 bytes
Compression ratio: 53.21%
Processing time: 366.40 ms