UniDoc's fork of tdewolff/font by Taco de Wolff (MIT, license unchanged), maintained for our own use, mainly WOFF/WOFF2 decoding. Differences from upstream:
- WOFF2 fonts with bit 11 of
head.flagsclear are accepted instead of rejected.- Requires Go 1.25; the
examples/glyphsexample is removed.- Upstream's AI-agent instruction files are removed.
See NOTICE for details.
This fork is maintained for UniDoc's own needs, with no support or stability guarantees, and may be changed, archived or removed at any time. For general use, we recommend upstream.
Parsers for SFNT-based fonts (TTF, OTF, WOFF, WOFF2, EOT) that can extract glyph paths, advancement and kerning data, Unicode mapping and glyph lookups. It also supports merging two fonts as well as subsetting fonts using a list of glyphs. It can write out TTF/OTF as well as WOFF2 fonts. The WOFF and WOFF2 converters have been testing using the validation tests from the W3C (https://github.com/w3c/woff/tree/master/woff1/tests and https://github.com/w3c/woff2-tests).
fontcmd: font toolkit that can select a subset of glyphs from a font, merge fonts, or show font information and display glyphs in the command line or as a raster image.
import "github.com/unidoc/font"
// Can be any of TTF, OTF, TTC, WOFF, WOFF2, or EOT
fontBytes, err := os.ReadFile("DejaVuSerif.otf")
if err != nil {
panic(err)
}
sfnt, err := font.ParseSFNT(fontBytes, 0) // 0 is the first index for TTC fonts
if err != nil {
panic(err)
}
// font information
sfnt.NumGlyphs() uint16
sfnt.UnitsPerEm() uint16
sfnt.VerticalMetrics() (uint16, uint16, uint16)
// glyph mappings
sfnt.GlyphIndex(r rune) uint16
sfnt.GlyphToUnicode(glyphID uint16) []rune
sfnt.GlyphName(glyphID uint16) string
sfnt.FindGlyphName(name string) uint16
// glyph shapes
sfnt.GlyphPath(p Pather, glyphID, ppem uint16, x, y, scale float64, hinting Hinting) error
sfnt.GlyphBounds(glyphID uint16) (int16, int16, int16, int16)
sfnt.GlyphAdvance(glyphID uint16) uint16
sfnt.GlyphVerticalAdvance(glyphID uint16) uint16
sfnt.Kerning(left, right uint16) int16
// editting
sfnt.SetGlyphNames(names []string) error
sfnt.Merge(sfnt *SFNT, options MergeOptions) error
sfnt.Subset(glyphIDs []uint16, options SubsetOptions) (*SFNT, error)
sfnt.Write() []byte
sfnt.WriteWOFF2() ([]byte, error)import "github.com/unidoc/font"
// Load two fonts
fontBytes, err := os.ReadFile("DejaVuSerif.otf")
if err != nil {
panic(err)
}
font2Bytes, err := os.ReadFile("DejaVuSerif-Extended.otf")
if err != nil {
panic(err)
}
sfnt, err := font.ParseSFNT(fontBytes, 0)
if err != nil {
panic(err)
}
sfnt2, err := font.ParseSFNT(font2Bytes, 0)
if err != nil {
panic(err)
}
// Add all glyphs of the second font to the first
options := font.MergeOptions{
RearrangeCmap: false, // don't rewrite unicode mapping to sequential order
}
if err := sfnt.Merge(sfnt2, options); err != nil {
panic(err)
}
if err := os.WriteFile("DejaVuSerif-Merged.otf", sfnt.Write(), 0644); err != nil{
panic(err)
}import "github.com/unidoc/font"
fontBytes, err := os.ReadFile("DejaVuSerif.otf")
if err != nil {
panic(err)
}
sfnt, err := font.ParseSFNT(fontBytes, 0)
if err != nil {
panic(err)
}
var glyphIDs []uint16
for c := 'A'; c <= 'Z'; c++ {
if glyphID := sfnt.GlyphIndex(c); glyphID != 0 {
glyphIDs = append(glyphIDs, glyphID)
}
}
// Create subset
options := font.SubsetOptions{
Tables: font.KeepMinTables, // keep only required tables
}
sfntSubset, err := sfnt.Subset(glyphIDs, options)
if err != nil {
panic(err)
}
if err := os.WriteFile("DejaVuSerif-Subset.otf", sfntSubset.Write(), 0644); err != nil{
panic(err)
}380132 bytes => 6700 bytes (1.7%)
import "github.com/unidoc/font"
// Can be any of TTF, OTF, TTC, WOFF, WOFF2, or EOT
fontBytes, err := os.ReadFile("DejaVuSerif.woff")
if err != nil {
panic(err)
}
// []byte to []byte
sfnt, err := font.ToSFNT(fontBytes)
if err != nil {
panic(err)
}
ext := font.Extension(sfnt) // .ttf or .otf
if err := os.WriteFile("DejaVuSerif"+ext, sfnt, 0644); err != nil{
panic(err)
}Released under the MIT license.