// Copyright 2010 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.

// Package truetype provides a parser for the TTF and TTC file formats. // Those formats are documented at http://developer.apple.com/fonts/TTRefMan/ // and http://www.microsoft.com/typography/otspec/ // // Some of a font's methods provide lengths or co-ordinates, e.g. bounds, font // metrics and control points. All these methods take a scale parameter, which // is the number of pixels in 1 em, expressed as a 26.6 fixed point value. For // example, if 1 em is 10 pixels then scale is fixed.I(10), which is equal to // fixed.Int26_6(10 << 6). // // To measure a TrueType font in ideal FUnit space, use scale equal to // font.FUnitsPerEm().
package truetype // import "github.com/golang/freetype/truetype" import ( ) // An Index is a Font's index of a rune. type Index uint16 // A NameID identifies a name table entry. // // See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html type NameID uint16 const ( NameIDCopyright NameID = 0 NameIDFontFamily = 1 NameIDFontSubfamily = 2 NameIDUniqueSubfamilyID = 3 NameIDFontFullName = 4 NameIDNameTableVersion = 5 NameIDPostscriptName = 6 NameIDTrademarkNotice = 7 NameIDManufacturerName = 8 NameIDDesignerName = 9 NameIDFontDescription = 10 NameIDFontVendorURL = 11 NameIDFontDesignerURL = 12 NameIDFontLicense = 13 NameIDFontLicenseURL = 14 NameIDPreferredFamily = 16 NameIDPreferredSubfamily = 17 NameIDCompatibleName = 18 NameIDSampleText = 19 ) const ( // A 32-bit encoding consists of a most-significant 16-bit Platform ID and a // least-significant 16-bit Platform Specific ID. The magic numbers are // specified at https://www.microsoft.com/typography/otspec/name.htm unicodeEncodingBMPOnly = 0x00000003 // PID = 0 (Unicode), PSID = 3 (Unicode 2.0 BMP Only) unicodeEncodingFull = 0x00000004 // PID = 0 (Unicode), PSID = 4 (Unicode 2.0 Full Repertoire) microsoftSymbolEncoding = 0x00030000 // PID = 3 (Microsoft), PSID = 0 (Symbol) microsoftUCS2Encoding = 0x00030001 // PID = 3 (Microsoft), PSID = 1 (UCS-2) microsoftUCS4Encoding = 0x0003000a // PID = 3 (Microsoft), PSID = 10 (UCS-4) ) // An HMetric holds the horizontal metrics of a single glyph. type HMetric struct { AdvanceWidth, LeftSideBearing fixed.Int26_6 } // A VMetric holds the vertical metrics of a single glyph. type VMetric struct { AdvanceHeight, TopSideBearing fixed.Int26_6 } // A FormatError reports that the input is not a valid TrueType font. type FormatError string func ( FormatError) () string { return "freetype: invalid TrueType format: " + string() } // An UnsupportedError reports that the input uses a valid but unimplemented // TrueType feature. type UnsupportedError string func ( UnsupportedError) () string { return "freetype: unsupported TrueType feature: " + string() } // u32 returns the big-endian uint32 at b[i:]. func u32( []byte, int) uint32 { return uint32([])<<24 | uint32([+1])<<16 | uint32([+2])<<8 | uint32([+3]) } // u16 returns the big-endian uint16 at b[i:]. func u16( []byte, int) uint16 { return uint16([])<<8 | uint16([+1]) } // readTable returns a slice of the TTF data given by a table's directory entry. func readTable( []byte, []byte) ([]byte, error) { := int(u32(, 0)) if < 0 { return nil, FormatError(fmt.Sprintf("offset too large: %d", uint32())) } := int(u32(, 4)) if < 0 { return nil, FormatError(fmt.Sprintf("length too large: %d", uint32())) } := + if < 0 || > len() { return nil, FormatError(fmt.Sprintf("offset + length too large: %d", uint32()+uint32())) } return [:], nil } // parseSubtables returns the offset and platformID of the best subtable in // table, where best favors a Unicode cmap encoding, and failing that, a // Microsoft cmap encoding. offset is the offset of the first subtable in // table, and size is the size of each subtable. // // If pred is non-nil, then only subtables that satisfy that predicate will be // considered. func parseSubtables( []byte, string, , int, func([]byte) bool) ( int, uint32, error) { if len() < 4 { return 0, 0, FormatError( + " too short") } := int(u16(, 2)) if len() < *+ { return 0, 0, FormatError( + " too short") } := false for := 0; < ; , = +1, + { if != nil && !([:]) { continue } // We read the 16-bit Platform ID and 16-bit Platform Specific ID as a single uint32. // All values are big-endian. := u32(, ) // We prefer the Unicode cmap encoding. Failing to find that, we fall // back onto the Microsoft cmap encoding. if == unicodeEncodingBMPOnly || == unicodeEncodingFull { , , = , >>16, true break } else if == microsoftSymbolEncoding || == microsoftUCS2Encoding || == microsoftUCS4Encoding { , , = , >>16, true // We don't break out of the for loop, so that Unicode can override Microsoft. } } if ! { return 0, 0, UnsupportedError( + " encoding") } return , , nil } const ( locaOffsetFormatUnknown int = iota locaOffsetFormatShort locaOffsetFormatLong ) // A cm holds a parsed cmap entry. type cm struct { start, end, delta, offset uint32 } // A Font represents a Truetype font. type Font struct { // Tables sliced from the TTF data. The different tables are documented // at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html cmap, cvt, fpgm, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, os2, prep, vmtx []byte cmapIndexes []byte // Cached values derived from the raw ttf data. cm []cm locaOffsetFormat int nGlyph, nHMetric, nKern int fUnitsPerEm int32 ascent int32 // In FUnits. descent int32 // In FUnits; typically negative. bounds fixed.Rectangle26_6 // In FUnits. // Values from the maxp section. maxTwilightPoints, maxStorage, maxFunctionDefs, maxStackElements uint16 } func ( *Font) () error { const ( = 4 = 12 = 0 ) , , := parseSubtables(.cmap, "cmap", 4, 8, nil) if != nil { return } = int(u32(.cmap, +4)) if <= 0 || > len(.cmap) { return FormatError("bad cmap offset") } := u16(.cmap, ) switch { case : := u16(.cmap, +4) if != { return UnsupportedError(fmt.Sprintf("language: %d", )) } := int(u16(.cmap, +6)) if %2 == 1 { return FormatError(fmt.Sprintf("bad segCountX2: %d", )) } := / 2 += 14 .cm = make([]cm, ) for := 0; < ; ++ { .cm[].end = uint32(u16(.cmap, )) += 2 } += 2 for := 0; < ; ++ { .cm[].start = uint32(u16(.cmap, )) += 2 } for := 0; < ; ++ { .cm[].delta = uint32(u16(.cmap, )) += 2 } for := 0; < ; ++ { .cm[].offset = uint32(u16(.cmap, )) += 2 } .cmapIndexes = .cmap[:] return nil case : if u16(.cmap, +2) != 0 { return FormatError(fmt.Sprintf("cmap format: % x", .cmap[:+4])) } := u32(.cmap, +4) := u32(.cmap, +8) if != { return UnsupportedError(fmt.Sprintf("language: %d", )) } := u32(.cmap, +12) if != 12*+16 { return FormatError("inconsistent cmap length") } += 16 .cm = make([]cm, ) for := uint32(0); < ; ++ { .cm[].start = u32(.cmap, +0) .cm[].end = u32(.cmap, +4) .cm[].delta = u32(.cmap, +8) - .cm[].start += 12 } return nil } return UnsupportedError(fmt.Sprintf("cmap format: %d", )) } func ( *Font) () error { if len(.head) != 54 { return FormatError(fmt.Sprintf("bad head length: %d", len(.head))) } .fUnitsPerEm = int32(u16(.head, 18)) .bounds.Min.X = fixed.Int26_6(int16(u16(.head, 36))) .bounds.Min.Y = fixed.Int26_6(int16(u16(.head, 38))) .bounds.Max.X = fixed.Int26_6(int16(u16(.head, 40))) .bounds.Max.Y = fixed.Int26_6(int16(u16(.head, 42))) switch := u16(.head, 50); { case 0: .locaOffsetFormat = locaOffsetFormatShort case 1: .locaOffsetFormat = locaOffsetFormatLong default: return FormatError(fmt.Sprintf("bad indexToLocFormat: %d", )) } return nil } func ( *Font) () error { if len(.hhea) != 36 { return FormatError(fmt.Sprintf("bad hhea length: %d", len(.hhea))) } .ascent = int32(int16(u16(.hhea, 4))) .descent = int32(int16(u16(.hhea, 6))) .nHMetric = int(u16(.hhea, 34)) if 4*.nHMetric+2*(.nGlyph-.nHMetric) != len(.hmtx) { return FormatError(fmt.Sprintf("bad hmtx length: %d", len(.hmtx))) } return nil } func ( *Font) () error { // Apple's TrueType documentation (http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html) says: // "Previous versions of the 'kern' table defined both the version and nTables fields in the header // as UInt16 values and not UInt32 values. Use of the older format on the Mac OS is discouraged // (although AAT can sense an old kerning table and still make correct use of it). Microsoft // Windows still uses the older format for the 'kern' table and will not recognize the newer one. // Fonts targeted for the Mac OS only should use the new format; fonts targeted for both the Mac OS // and Windows should use the old format." // Since we expect that almost all fonts aim to be Windows-compatible, we only parse the "older" format, // just like the C Freetype implementation. if len(.kern) == 0 { if .nKern != 0 { return FormatError("bad kern table length") } return nil } if len(.kern) < 18 { return FormatError("kern data too short") } , := u16(.kern, 0), 2 if != 0 { return UnsupportedError(fmt.Sprintf("kern version: %d", )) } , := u16(.kern, ), +2 if == 0 { return UnsupportedError("kern nTables: 0") } // TODO: support multiple subtables. In practice, almost all .ttf files // have only one subtable, if they have a kern table at all. But it's not // impossible. Xolonium Regular (https://fontlibrary.org/en/font/xolonium) // has 3 subtables. Those subtables appear to be disjoint, rather than // being the same kerning pairs encoded in three different ways. // // For now, we'll use only the first subtable. += 2 // Skip the version. , := int(u16(.kern, )), +2 , := u16(.kern, ), +2 if != 0x0001 { // We only support horizontal kerning. return UnsupportedError(fmt.Sprintf("kern coverage: 0x%04x", )) } .nKern, = int(u16(.kern, )), +2 if 6*.nKern != -14 { return FormatError("bad kern table length") } return nil } func ( *Font) () error { if len(.maxp) != 32 { return FormatError(fmt.Sprintf("bad maxp length: %d", len(.maxp))) } .nGlyph = int(u16(.maxp, 4)) .maxTwilightPoints = u16(.maxp, 16) .maxStorage = u16(.maxp, 18) .maxFunctionDefs = u16(.maxp, 20) .maxStackElements = u16(.maxp, 24) return nil } // scale returns x divided by f.fUnitsPerEm, rounded to the nearest integer. func ( *Font) ( fixed.Int26_6) fixed.Int26_6 { if >= 0 { += fixed.Int26_6(.fUnitsPerEm) / 2 } else { -= fixed.Int26_6(.fUnitsPerEm) / 2 } return / fixed.Int26_6(.fUnitsPerEm) } // Bounds returns the union of a Font's glyphs' bounds. func ( *Font) ( fixed.Int26_6) fixed.Rectangle26_6 { := .bounds .Min.X = .scale( * .Min.X) .Min.Y = .scale( * .Min.Y) .Max.X = .scale( * .Max.X) .Max.Y = .scale( * .Max.Y) return } // FUnitsPerEm returns the number of FUnits in a Font's em-square's side. func ( *Font) () int32 { return .fUnitsPerEm } // Index returns a Font's index for the given rune. func ( *Font) ( rune) Index { := uint32() for , := 0, len(.cm); < ; { := + (-)/2 := &.cm[] if < .start { = } else if .end < { = + 1 } else if .offset == 0 { return Index( + .delta) } else { := int(.offset) + 2*(-len(.cm)+int(-.start)) return Index(u16(.cmapIndexes, )) } } return 0 } // Name returns the Font's name value for the given NameID. It returns "" if // there was an error, or if that name was not found. func ( *Font) ( NameID) string { , , := parseSubtables(.name, "name", 6, 12, func( []byte) bool { return NameID(u16(, 6)) == }) if != nil { return "" } , := u16(.name, 4)+u16(.name, +10), u16(.name, +8) // Return the ASCII value of the encoded string. // The string is encoded as UTF-16 on non-Apple platformIDs; Apple is platformID 1. := .name[ : +] var []byte if != 1 { // UTF-16. if len()&1 != 0 { return "" } = make([]byte, len()/2) for := range { [] = printable(u16(, 2*)) } } else { // ASCII. = make([]byte, len()) for , := range { [] = printable(uint16()) } } return string() } func printable( uint16) byte { if 0x20 <= && < 0x7f { return byte() } return '?' } // unscaledHMetric returns the unscaled horizontal metrics for the glyph with // the given index. func ( *Font) ( Index) ( HMetric) { := int() if < 0 || .nGlyph <= { return HMetric{} } if >= .nHMetric { := 4 * (.nHMetric - 1) return HMetric{ AdvanceWidth: fixed.Int26_6(u16(.hmtx, )), LeftSideBearing: fixed.Int26_6(int16(u16(.hmtx, +2*(-.nHMetric)+4))), } } return HMetric{ AdvanceWidth: fixed.Int26_6(u16(.hmtx, 4*)), LeftSideBearing: fixed.Int26_6(int16(u16(.hmtx, 4*+2))), } } // HMetric returns the horizontal metrics for the glyph with the given index. func ( *Font) ( fixed.Int26_6, Index) HMetric { := .unscaledHMetric() .AdvanceWidth = .scale( * .AdvanceWidth) .LeftSideBearing = .scale( * .LeftSideBearing) return } // unscaledVMetric returns the unscaled vertical metrics for the glyph with // the given index. yMax is the top of the glyph's bounding box. func ( *Font) ( Index, fixed.Int26_6) ( VMetric) { := int() if < 0 || .nGlyph <= { return VMetric{} } if 4*+4 <= len(.vmtx) { return VMetric{ AdvanceHeight: fixed.Int26_6(u16(.vmtx, 4*)), TopSideBearing: fixed.Int26_6(int16(u16(.vmtx, 4*+2))), } } // The OS/2 table has grown over time. // https://developer.apple.com/fonts/TTRefMan/RM06/Chap6OS2.html // says that it was originally 68 bytes. Optional fields, including // the ascender and descender, are described at // http://www.microsoft.com/typography/otspec/os2.htm if len(.os2) >= 72 { := fixed.Int26_6(int16(u16(.os2, 68))) := fixed.Int26_6(int16(u16(.os2, 70))) return VMetric{ AdvanceHeight: - , TopSideBearing: - , } } return VMetric{ AdvanceHeight: fixed.Int26_6(.fUnitsPerEm), TopSideBearing: 0, } } // VMetric returns the vertical metrics for the glyph with the given index. func ( *Font) ( fixed.Int26_6, Index) VMetric { // TODO: should 0 be bounds.YMax? := .unscaledVMetric(, 0) .AdvanceHeight = .scale( * .AdvanceHeight) .TopSideBearing = .scale( * .TopSideBearing) return } // Kern returns the horizontal adjustment for the given glyph pair. A positive // kern means to move the glyphs further apart. func ( *Font) ( fixed.Int26_6, , Index) fixed.Int26_6 { if .nKern == 0 { return 0 } := uint32()<<16 | uint32() , := 0, .nKern for < { := ( + ) / 2 := u32(.kern, 18+6*) if < { = + 1 } else if > { = } else { return .scale( * fixed.Int26_6(int16(u16(.kern, 22+6*)))) } } return 0 } // Parse returns a new Font for the given TTF or TTC data. // // For TrueType Collections, the first font in the collection is parsed. func ( []byte) ( *Font, error) { return parse(, 0) } func parse( []byte, int) ( *Font, error) { if len()- < 12 { = FormatError("TTF data is too short") return } := , := u32(, ), +4 switch { case 0x00010000: // No-op. case 0x74746366: // "ttcf" as a big-endian uint32. if != 0 { = FormatError("recursive TTC") return } , := u32(, ), +4 if != 0x00010000 && != 0x00020000 { = FormatError("bad TTC version") return } , := int(u32(, )), +4 if <= 0 { = FormatError("bad number of TTC fonts") return } if len([:])/4 < { = FormatError("TTC offset table is too short") return } // TODO: provide an API to select which font in a TrueType collection to return, // not just the first one. This may require an API to parse a TTC's name tables, // so users of this package can select the font in a TTC by name. = int(u32(, )) if <= 0 || > len() { = FormatError("bad TTC offset") return } return (, ) default: = FormatError("bad TTF version") return } , := int(u16(, )), +2 += 6 // Skip the searchRange, entrySelector and rangeShift. if len() < 16*+ { = FormatError("TTF data is too short") return } := new(Font) // Assign the table slices. for := 0; < ; ++ { := 16* + switch string([ : +4]) { case "cmap": .cmap, = readTable(, [+8:+16]) case "cvt ": .cvt, = readTable(, [+8:+16]) case "fpgm": .fpgm, = readTable(, [+8:+16]) case "glyf": .glyf, = readTable(, [+8:+16]) case "hdmx": .hdmx, = readTable(, [+8:+16]) case "head": .head, = readTable(, [+8:+16]) case "hhea": .hhea, = readTable(, [+8:+16]) case "hmtx": .hmtx, = readTable(, [+8:+16]) case "kern": .kern, = readTable(, [+8:+16]) case "loca": .loca, = readTable(, [+8:+16]) case "maxp": .maxp, = readTable(, [+8:+16]) case "name": .name, = readTable(, [+8:+16]) case "OS/2": .os2, = readTable(, [+8:+16]) case "prep": .prep, = readTable(, [+8:+16]) case "vmtx": .vmtx, = readTable(, [+8:+16]) } if != nil { return } } // Parse and sanity-check the TTF data. if = .parseHead(); != nil { return } if = .parseMaxp(); != nil { return } if = .parseCmap(); != nil { return } if = .parseKern(); != nil { return } if = .parseHhea(); != nil { return } = return }