2010-05-03 23:54:43 +00:00
|
|
|
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by your choice of either the
|
2010-08-03 01:07:23 +00:00
|
|
|
// FreeType License or the GNU General Public License version 2 (or
|
|
|
|
// any later version), both of which can be found in the LICENSE file.
|
2010-05-03 23:54:43 +00:00
|
|
|
|
2012-07-03 10:46:16 +00:00
|
|
|
// Package truetype provides a parser for the TTF and TTC file formats.
|
2012-05-07 01:42:35 +00:00
|
|
|
// Those formats are documented at http://developer.apple.com/fonts/TTRefMan/
|
|
|
|
// and http://www.microsoft.com/typography/otspec/
|
2010-05-03 23:54:43 +00:00
|
|
|
//
|
2012-07-25 12:10:25 +00:00
|
|
|
// 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
|
2015-08-18 06:30:37 +00:00
|
|
|
// 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).
|
2012-07-25 12:10:25 +00:00
|
|
|
//
|
|
|
|
// To measure a TrueType font in ideal FUnit space, use scale equal to
|
|
|
|
// font.FUnitsPerEm().
|
2015-08-12 04:34:19 +00:00
|
|
|
package truetype // import "github.com/golang/freetype/truetype"
|
2010-05-03 23:54:43 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2015-08-18 06:30:37 +00:00
|
|
|
|
|
|
|
"golang.org/x/image/math/fixed"
|
2010-05-03 23:54:43 +00:00
|
|
|
)
|
|
|
|
|
2011-12-22 23:55:36 +00:00
|
|
|
// An Index is a Font's index of a rune.
|
2010-05-03 23:54:43 +00:00
|
|
|
type Index uint16
|
|
|
|
|
2015-09-03 10:44:40 +00:00
|
|
|
// A NameID represents the Name Identifiers in the name table.
|
|
|
|
type NameID uint16
|
|
|
|
|
|
|
|
const (
|
|
|
|
NameIDCopyright NameID = iota
|
|
|
|
NameIDFontFamily
|
|
|
|
NameIDFontSubfamily
|
|
|
|
NameIDUniqueSubfamilyID
|
|
|
|
NameIDFontFullName
|
|
|
|
NameIDNameTableVersion
|
|
|
|
NameIDPostscriptName
|
|
|
|
NameIDTrademarkNotice
|
|
|
|
NameIDManufacturerName
|
|
|
|
NameIDDesignerName
|
|
|
|
NameIDFontDescription
|
|
|
|
NameIDFontVendorURL
|
|
|
|
NameIDFontDesignerURL
|
|
|
|
NameIDFontLicense
|
|
|
|
NameIDFontLicenseURL
|
|
|
|
NameIDReserved
|
|
|
|
NameIDPreferredFamily
|
|
|
|
NameIDPreferredSubfamily
|
|
|
|
NameIDCompatibleName
|
|
|
|
NameIDSampleText
|
|
|
|
)
|
|
|
|
|
|
|
|
// A Bounds holds the co-ordinate range of one or more glyphs.
|
|
|
|
// The endpoints are inclusive.
|
|
|
|
type Bounds struct {
|
|
|
|
XMin, YMin, XMax, YMax fixed.Int26_6
|
|
|
|
}
|
|
|
|
|
2010-05-03 23:54:43 +00:00
|
|
|
// An HMetric holds the horizontal metrics of a single glyph.
|
|
|
|
type HMetric struct {
|
2015-08-18 06:30:37 +00:00
|
|
|
AdvanceWidth, LeftSideBearing fixed.Int26_6
|
2013-11-04 22:58:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// A VMetric holds the vertical metrics of a single glyph.
|
|
|
|
type VMetric struct {
|
2015-08-18 06:30:37 +00:00
|
|
|
AdvanceHeight, TopSideBearing fixed.Int26_6
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// A FormatError reports that the input is not a valid TrueType font.
|
|
|
|
type FormatError string
|
|
|
|
|
2011-11-07 23:38:49 +00:00
|
|
|
func (e FormatError) Error() string {
|
2010-05-03 23:54:43 +00:00
|
|
|
return "freetype: invalid TrueType format: " + string(e)
|
|
|
|
}
|
|
|
|
|
|
|
|
// An UnsupportedError reports that the input uses a valid but unimplemented
|
|
|
|
// TrueType feature.
|
|
|
|
type UnsupportedError string
|
|
|
|
|
2011-11-07 23:38:49 +00:00
|
|
|
func (e UnsupportedError) Error() string {
|
2010-05-03 23:54:43 +00:00
|
|
|
return "freetype: unsupported TrueType feature: " + string(e)
|
|
|
|
}
|
|
|
|
|
2012-06-16 02:19:07 +00:00
|
|
|
// u32 returns the big-endian uint32 at b[i:].
|
|
|
|
func u32(b []byte, i int) uint32 {
|
|
|
|
return uint32(b[i])<<24 | uint32(b[i+1])<<16 | uint32(b[i+2])<<8 | uint32(b[i+3])
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
|
2012-06-16 02:19:07 +00:00
|
|
|
// u16 returns the big-endian uint16 at b[i:].
|
|
|
|
func u16(b []byte, i int) uint16 {
|
|
|
|
return uint16(b[i])<<8 | uint16(b[i+1])
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// readTable returns a slice of the TTF data given by a table's directory entry.
|
2011-11-07 23:38:49 +00:00
|
|
|
func readTable(ttf []byte, offsetLength []byte) ([]byte, error) {
|
2012-06-16 02:19:07 +00:00
|
|
|
offset := int(u32(offsetLength, 0))
|
2012-05-06 23:37:34 +00:00
|
|
|
if offset < 0 {
|
|
|
|
return nil, FormatError(fmt.Sprintf("offset too large: %d", uint32(offset)))
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
length := int(u32(offsetLength, 4))
|
2012-05-06 23:37:34 +00:00
|
|
|
if length < 0 {
|
|
|
|
return nil, FormatError(fmt.Sprintf("length too large: %d", uint32(length)))
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
2012-05-06 23:37:34 +00:00
|
|
|
end := offset + length
|
|
|
|
if end < 0 || end > len(ttf) {
|
|
|
|
return nil, FormatError(fmt.Sprintf("offset + length too large: %d", uint32(offset)+uint32(length)))
|
|
|
|
}
|
|
|
|
return ttf[offset:end], nil
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
locaOffsetFormatUnknown int = iota
|
|
|
|
locaOffsetFormatShort
|
|
|
|
locaOffsetFormatLong
|
|
|
|
)
|
|
|
|
|
|
|
|
// A cm holds a parsed cmap entry.
|
|
|
|
type cm struct {
|
2013-10-10 20:29:40 +00:00
|
|
|
start, end, delta, offset uint32
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
|
2015-09-03 00:44:39 +00:00
|
|
|
// nnameInfo holds a font's name and style.
|
|
|
|
type nameInfo struct {
|
2015-09-03 10:44:40 +00:00
|
|
|
values [20]string
|
2015-09-01 19:49:33 +00:00
|
|
|
}
|
|
|
|
|
2010-05-03 23:54:43 +00:00
|
|
|
// 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
|
2015-09-02 15:00:02 +00:00
|
|
|
cmap, cvt, fpgm, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, os2, prep, vmtx []byte
|
2013-07-30 09:20:31 +00:00
|
|
|
|
|
|
|
cmapIndexes []byte
|
2010-05-03 23:54:43 +00:00
|
|
|
|
|
|
|
// Cached values derived from the raw ttf data.
|
|
|
|
cm []cm
|
|
|
|
locaOffsetFormat int
|
|
|
|
nGlyph, nHMetric, nKern int
|
2012-07-25 12:10:25 +00:00
|
|
|
fUnitsPerEm int32
|
Use fixed.Rectangle26_6 instead of truetype.Bounds.
The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.
Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
2015-08-30 12:06:37 +00:00
|
|
|
bounds fixed.Rectangle26_6
|
2012-07-11 11:49:03 +00:00
|
|
|
// Values from the maxp section.
|
|
|
|
maxTwilightPoints, maxStorage, maxFunctionDefs, maxStackElements uint16
|
2015-09-03 00:44:39 +00:00
|
|
|
// First value from head table.
|
2015-09-03 10:44:40 +00:00
|
|
|
nameRecord *nameInfo
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
|
2011-11-07 23:38:49 +00:00
|
|
|
func (f *Font) parseCmap() error {
|
2010-05-03 23:54:43 +00:00
|
|
|
const (
|
|
|
|
cmapFormat4 = 4
|
2013-10-10 20:29:40 +00:00
|
|
|
cmapFormat12 = 12
|
2010-05-03 23:54:43 +00:00
|
|
|
languageIndependent = 0
|
|
|
|
|
|
|
|
// A 32-bit encoding consists of a most-significant 16-bit Platform ID and a
|
2014-02-05 23:38:58 +00:00
|
|
|
// least-significant 16-bit Platform Specific ID. The magic numbers are
|
|
|
|
// specified at https://www.microsoft.com/typography/otspec/name.htm
|
|
|
|
unicodeEncoding = 0x00000003 // PID = 0 (Unicode), PSID = 3 (Unicode 2.0)
|
|
|
|
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)
|
2010-05-03 23:54:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if len(f.cmap) < 4 {
|
|
|
|
return FormatError("cmap too short")
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
nsubtab := int(u16(f.cmap, 2))
|
2010-05-03 23:54:43 +00:00
|
|
|
if len(f.cmap) < 8*nsubtab+4 {
|
|
|
|
return FormatError("cmap too short")
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
offset, found, x := 0, false, 4
|
2010-05-03 23:54:43 +00:00
|
|
|
for i := 0; i < nsubtab; i++ {
|
|
|
|
// We read the 16-bit Platform ID and 16-bit Platform Specific ID as a single uint32.
|
|
|
|
// All values are big-endian.
|
2012-06-16 02:19:07 +00:00
|
|
|
pidPsid, o := u32(f.cmap, x), u32(f.cmap, x+4)
|
|
|
|
x += 8
|
2010-05-03 23:54:43 +00:00
|
|
|
// We prefer the Unicode cmap encoding. Failing to find that, we fall
|
|
|
|
// back onto the Microsoft cmap encoding.
|
|
|
|
if pidPsid == unicodeEncoding {
|
|
|
|
offset, found = int(o), true
|
|
|
|
break
|
2014-02-05 23:38:58 +00:00
|
|
|
|
|
|
|
} else if pidPsid == microsoftSymbolEncoding ||
|
|
|
|
pidPsid == microsoftUCS2Encoding ||
|
|
|
|
pidPsid == microsoftUCS4Encoding {
|
|
|
|
|
2010-05-03 23:54:43 +00:00
|
|
|
offset, found = int(o), true
|
|
|
|
// We don't break out of the for loop, so that Unicode can override Microsoft.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
return UnsupportedError("cmap encoding")
|
|
|
|
}
|
|
|
|
if offset <= 0 || offset > len(f.cmap) {
|
|
|
|
return FormatError("bad cmap offset")
|
|
|
|
}
|
|
|
|
|
2012-06-16 02:19:07 +00:00
|
|
|
cmapFormat := u16(f.cmap, offset)
|
2013-10-10 20:29:40 +00:00
|
|
|
switch cmapFormat {
|
|
|
|
case cmapFormat4:
|
|
|
|
language := u16(f.cmap, offset+4)
|
|
|
|
if language != languageIndependent {
|
|
|
|
return UnsupportedError(fmt.Sprintf("language: %d", language))
|
|
|
|
}
|
|
|
|
segCountX2 := int(u16(f.cmap, offset+6))
|
|
|
|
if segCountX2%2 == 1 {
|
|
|
|
return FormatError(fmt.Sprintf("bad segCountX2: %d", segCountX2))
|
|
|
|
}
|
|
|
|
segCount := segCountX2 / 2
|
|
|
|
offset += 14
|
|
|
|
f.cm = make([]cm, segCount)
|
|
|
|
for i := 0; i < segCount; i++ {
|
|
|
|
f.cm[i].end = uint32(u16(f.cmap, offset))
|
|
|
|
offset += 2
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
offset += 2
|
2013-10-10 20:29:40 +00:00
|
|
|
for i := 0; i < segCount; i++ {
|
|
|
|
f.cm[i].start = uint32(u16(f.cmap, offset))
|
|
|
|
offset += 2
|
|
|
|
}
|
|
|
|
for i := 0; i < segCount; i++ {
|
|
|
|
f.cm[i].delta = uint32(u16(f.cmap, offset))
|
|
|
|
offset += 2
|
|
|
|
}
|
|
|
|
for i := 0; i < segCount; i++ {
|
|
|
|
f.cm[i].offset = uint32(u16(f.cmap, offset))
|
|
|
|
offset += 2
|
|
|
|
}
|
|
|
|
f.cmapIndexes = f.cmap[offset:]
|
|
|
|
return nil
|
|
|
|
|
|
|
|
case cmapFormat12:
|
|
|
|
if u16(f.cmap, offset+2) != 0 {
|
|
|
|
return FormatError(fmt.Sprintf("cmap format: % x", f.cmap[offset:offset+4]))
|
|
|
|
}
|
|
|
|
length := u32(f.cmap, offset+4)
|
|
|
|
language := u32(f.cmap, offset+8)
|
|
|
|
if language != languageIndependent {
|
|
|
|
return UnsupportedError(fmt.Sprintf("language: %d", language))
|
|
|
|
}
|
|
|
|
nGroups := u32(f.cmap, offset+12)
|
|
|
|
if length != 12*nGroups+16 {
|
|
|
|
return FormatError("inconsistent cmap length")
|
|
|
|
}
|
|
|
|
offset += 16
|
|
|
|
f.cm = make([]cm, nGroups)
|
|
|
|
for i := uint32(0); i < nGroups; i++ {
|
|
|
|
f.cm[i].start = u32(f.cmap, offset+0)
|
|
|
|
f.cm[i].end = u32(f.cmap, offset+4)
|
|
|
|
f.cm[i].delta = u32(f.cmap, offset+8) - f.cm[i].start
|
|
|
|
offset += 12
|
|
|
|
}
|
|
|
|
return nil
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
2013-10-10 20:29:40 +00:00
|
|
|
return UnsupportedError(fmt.Sprintf("cmap format: %d", cmapFormat))
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
|
2011-11-07 23:38:49 +00:00
|
|
|
func (f *Font) parseHead() error {
|
2010-05-03 23:54:43 +00:00
|
|
|
if len(f.head) != 54 {
|
|
|
|
return FormatError(fmt.Sprintf("bad head length: %d", len(f.head)))
|
|
|
|
}
|
2012-07-25 12:10:25 +00:00
|
|
|
f.fUnitsPerEm = int32(u16(f.head, 18))
|
Use fixed.Rectangle26_6 instead of truetype.Bounds.
The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.
Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
2015-08-30 12:06:37 +00:00
|
|
|
f.bounds.Min.X = fixed.Int26_6(int16(u16(f.head, 36)))
|
|
|
|
f.bounds.Min.Y = fixed.Int26_6(int16(u16(f.head, 38)))
|
|
|
|
f.bounds.Max.X = fixed.Int26_6(int16(u16(f.head, 40)))
|
|
|
|
f.bounds.Max.Y = fixed.Int26_6(int16(u16(f.head, 42)))
|
2012-06-16 02:19:07 +00:00
|
|
|
switch i := u16(f.head, 50); i {
|
2010-05-03 23:54:43 +00:00
|
|
|
case 0:
|
|
|
|
f.locaOffsetFormat = locaOffsetFormatShort
|
|
|
|
case 1:
|
|
|
|
f.locaOffsetFormat = locaOffsetFormatLong
|
|
|
|
default:
|
|
|
|
return FormatError(fmt.Sprintf("bad indexToLocFormat: %d", i))
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-11-07 23:38:49 +00:00
|
|
|
func (f *Font) parseHhea() error {
|
2010-05-03 23:54:43 +00:00
|
|
|
if len(f.hhea) != 36 {
|
|
|
|
return FormatError(fmt.Sprintf("bad hhea length: %d", len(f.hhea)))
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
f.nHMetric = int(u16(f.hhea, 34))
|
2010-05-03 23:54:43 +00:00
|
|
|
if 4*f.nHMetric+2*(f.nGlyph-f.nHMetric) != len(f.hmtx) {
|
|
|
|
return FormatError(fmt.Sprintf("bad hmtx length: %d", len(f.hmtx)))
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-11-07 23:38:49 +00:00
|
|
|
func (f *Font) parseKern() error {
|
2010-05-03 23:54:43 +00:00
|
|
|
// 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(f.kern) == 0 {
|
|
|
|
if f.nKern != 0 {
|
|
|
|
return FormatError("bad kern table length")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if len(f.kern) < 18 {
|
|
|
|
return FormatError("kern data too short")
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
version, offset := u16(f.kern, 0), 2
|
2010-05-03 23:54:43 +00:00
|
|
|
if version != 0 {
|
|
|
|
return UnsupportedError(fmt.Sprintf("kern version: %d", version))
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
n, offset := u16(f.kern, offset), offset+2
|
2010-05-03 23:54:43 +00:00
|
|
|
if n != 1 {
|
|
|
|
return UnsupportedError(fmt.Sprintf("kern nTables: %d", n))
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
offset += 2
|
|
|
|
length, offset := int(u16(f.kern, offset)), offset+2
|
|
|
|
coverage, offset := u16(f.kern, offset), offset+2
|
2010-05-03 23:54:43 +00:00
|
|
|
if coverage != 0x0001 {
|
|
|
|
// We only support horizontal kerning.
|
|
|
|
return UnsupportedError(fmt.Sprintf("kern coverage: 0x%04x", coverage))
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
f.nKern, offset = int(u16(f.kern, offset)), offset+2
|
2010-05-03 23:54:43 +00:00
|
|
|
if 6*f.nKern != length-14 {
|
|
|
|
return FormatError("bad kern table length")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-11-07 23:38:49 +00:00
|
|
|
func (f *Font) parseMaxp() error {
|
2010-05-03 23:54:43 +00:00
|
|
|
if len(f.maxp) != 32 {
|
|
|
|
return FormatError(fmt.Sprintf("bad maxp length: %d", len(f.maxp)))
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
f.nGlyph = int(u16(f.maxp, 4))
|
2012-07-11 11:49:03 +00:00
|
|
|
f.maxTwilightPoints = u16(f.maxp, 16)
|
|
|
|
f.maxStorage = u16(f.maxp, 18)
|
|
|
|
f.maxFunctionDefs = u16(f.maxp, 20)
|
|
|
|
f.maxStackElements = u16(f.maxp, 24)
|
2010-05-03 23:54:43 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-09-01 19:49:33 +00:00
|
|
|
func (f *Font) parseName() error {
|
2015-09-03 00:44:39 +00:00
|
|
|
if len(f.name) < 18 { // The name table is three uint16s + at least one Name Record. Name Records are six uint16s.
|
|
|
|
return FormatError("name table too short")
|
2015-09-01 19:49:33 +00:00
|
|
|
}
|
2015-09-03 00:44:39 +00:00
|
|
|
if u16(f.name, 0) != 0 { // The first uint16 in the name table should be set to 0, per the spec.
|
2015-09-01 19:49:33 +00:00
|
|
|
return FormatError(fmt.Sprintf("invalid format specifier: %d", u16(f.name, 0)))
|
|
|
|
}
|
2015-09-03 00:44:39 +00:00
|
|
|
|
|
|
|
numNames := int(u16(f.name, 2)) // The number of Name Records is the second uint16 in the name table.
|
|
|
|
stringOffset := u16(f.name, 4) // And the offset marking the beginning of the character data is the third int16.
|
|
|
|
|
|
|
|
if len(f.name) < int(stringOffset) {
|
|
|
|
return FormatError("string offset is larger than the name table")
|
|
|
|
}
|
|
|
|
|
|
|
|
const ( // The platform IDs in the name table.
|
|
|
|
PLATFORM_UNICODE = 0
|
|
|
|
PLATFORM_MACINTOSH = 1
|
|
|
|
PLATFORM_MICROSOFT = 3
|
|
|
|
)
|
|
|
|
|
|
|
|
const ( // Encoding IDs for the Microsoft platform.
|
|
|
|
MICROSOFT_SYMBOL_CS = 1
|
|
|
|
MICROSOFT_UNICODE_CS = 2
|
|
|
|
MICROSOFT_UCS_4 = 10
|
|
|
|
)
|
|
|
|
|
|
|
|
// Keep track of which records contain the name and style information.
|
|
|
|
var foundUnicode, foundAppleEnglish, foundAppleRoman, foundMicrosoft, foundApple [2]int
|
|
|
|
foundMicrosoftEnglish := false
|
2015-09-01 19:49:33 +00:00
|
|
|
|
2015-09-03 10:44:40 +00:00
|
|
|
// Name Records begin at the fourth uint16 and are six uint16s long.
|
|
|
|
// Loop through them and keep track of records of interest.
|
2015-09-01 19:49:33 +00:00
|
|
|
for offset, i := 6, 0; i < numNames; i, offset = i+1, offset+12 {
|
2015-09-03 10:44:40 +00:00
|
|
|
platformID, encodingID, languageID, nameID := u16(f.name, offset), u16(f.name, offset+2), u16(f.name, offset+4), NameID(u16(f.name, offset+6))
|
2015-09-03 00:44:39 +00:00
|
|
|
|
2015-09-01 19:49:33 +00:00
|
|
|
switch nameID {
|
2015-09-03 10:44:40 +00:00
|
|
|
case NameIDFontFamily, NameIDFontSubfamily:
|
2015-09-03 00:44:39 +00:00
|
|
|
if u16(f.name, offset+8) == 0 { // Offset + 8 is the location of the string length. If zero, skip it.
|
|
|
|
break
|
|
|
|
}
|
|
|
|
switch platformID {
|
|
|
|
case PLATFORM_UNICODE:
|
|
|
|
foundUnicode[nameID-1] = offset
|
|
|
|
case PLATFORM_MACINTOSH:
|
|
|
|
if languageID == 0 { // The language ID for English is zero.
|
|
|
|
foundAppleEnglish[nameID-1] = offset
|
|
|
|
} else if encodingID == 0 { // A zero encoding ID means Roman.
|
|
|
|
foundAppleRoman[nameID-1] = offset
|
|
|
|
}
|
|
|
|
case PLATFORM_MICROSOFT:
|
2015-09-03 09:11:16 +00:00
|
|
|
if foundMicrosoft[nameID-1] == 0 || languageID&0x3FF == 0x009 { // Prefer Microsoft fonts in English.
|
2015-09-03 00:44:39 +00:00
|
|
|
if encodingID == MICROSOFT_SYMBOL_CS || encodingID == MICROSOFT_UNICODE_CS || encodingID == MICROSOFT_UCS_4 {
|
2015-09-03 09:11:16 +00:00
|
|
|
foundMicrosoftEnglish = languageID&0x3FF == 9
|
2015-09-03 00:44:39 +00:00
|
|
|
foundMicrosoft[nameID-1] = offset
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-09-01 19:49:33 +00:00
|
|
|
|
2015-09-03 00:44:39 +00:00
|
|
|
foundApple = foundAppleRoman
|
|
|
|
if foundAppleEnglish[0] > 0 {
|
|
|
|
foundApple = foundAppleEnglish
|
|
|
|
}
|
2015-09-01 19:49:33 +00:00
|
|
|
|
2015-09-03 10:44:40 +00:00
|
|
|
var nameOffset, nameLength, styleOffset, styleLength uint16
|
|
|
|
nameRecord := &nameInfo{}
|
|
|
|
// Retrieve ASCII version of the values.
|
2015-09-03 00:44:39 +00:00
|
|
|
switch {
|
|
|
|
case foundMicrosoft[0] > 0 && !(foundApple[0] > 0 && !foundMicrosoftEnglish):
|
2015-09-03 10:44:40 +00:00
|
|
|
nameOffset, styleOffset = u16(f.name, foundMicrosoft[0]+10)+stringOffset, u16(f.name, foundMicrosoft[1]+10)+stringOffset
|
|
|
|
nameLength, styleLength = u16(f.name, foundMicrosoft[0]+8), u16(f.name, foundMicrosoft[1]+8)
|
2015-09-03 00:44:39 +00:00
|
|
|
case foundApple[0] > 0:
|
2015-09-03 10:44:40 +00:00
|
|
|
nameOffset, styleOffset = u16(f.name, foundApple[0]+10)+stringOffset, u16(f.name, foundApple[1]+10)+stringOffset
|
|
|
|
nameLength, styleLength = u16(f.name, foundApple[0]+8), u16(f.name, foundApple[1]+8)
|
2015-09-03 00:44:39 +00:00
|
|
|
case foundUnicode[0] > 0:
|
2015-09-03 10:44:40 +00:00
|
|
|
nameOffset, styleOffset = u16(f.name, foundUnicode[0]+10)+stringOffset, u16(f.name, foundUnicode[1]+10)+stringOffset
|
|
|
|
nameLength, styleLength = u16(f.name, foundUnicode[0]+8), u16(f.name, foundUnicode[1]+8)
|
2015-09-03 00:44:39 +00:00
|
|
|
}
|
2015-09-01 19:49:33 +00:00
|
|
|
|
2015-09-03 10:44:40 +00:00
|
|
|
nameRecord.values[NameIDFontFamily] = f.nameEntryInASCII(f.name[nameOffset:nameOffset+nameLength], true)
|
|
|
|
nameRecord.values[NameIDFontSubfamily] = f.nameEntryInASCII(f.name[styleOffset:styleOffset+styleLength], true)
|
|
|
|
|
2015-09-03 00:44:39 +00:00
|
|
|
f.nameRecord = nameRecord
|
2015-09-01 19:57:34 +00:00
|
|
|
|
2015-09-03 00:44:39 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *Font) nameEntryInASCII(b []byte, utf16 bool) string {
|
2015-09-03 09:10:10 +00:00
|
|
|
var buf []byte
|
2015-09-03 00:44:39 +00:00
|
|
|
if utf16 { // Equivalent to tt_name_ascii_from_utf16.
|
|
|
|
j := len(b)
|
|
|
|
if j&1 == 1 {
|
|
|
|
return ""
|
2015-09-01 19:49:33 +00:00
|
|
|
}
|
|
|
|
|
2015-09-03 00:44:39 +00:00
|
|
|
for i := 0; i < j; i += 2 {
|
|
|
|
el := u16(b, i)
|
|
|
|
if el == 0 {
|
|
|
|
continue
|
|
|
|
} else if el < 32 || el > 127 {
|
2015-09-03 09:10:10 +00:00
|
|
|
buf = append(buf, '?')
|
2015-09-03 00:44:39 +00:00
|
|
|
} else {
|
2015-09-03 09:10:10 +00:00
|
|
|
buf = append(buf, byte(el))
|
2015-09-03 00:44:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else { // Equivalent to tt_name_ascii_from_other.
|
|
|
|
for _, el := range b {
|
|
|
|
if el == 0 {
|
|
|
|
continue
|
|
|
|
} else if el < 32 || el > 127 {
|
2015-09-03 09:10:10 +00:00
|
|
|
buf = append(buf, '?')
|
2015-09-03 00:44:39 +00:00
|
|
|
} else {
|
2015-09-03 09:10:10 +00:00
|
|
|
buf = append(buf, el)
|
2015-09-03 00:44:39 +00:00
|
|
|
}
|
2015-09-01 19:49:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-03 09:10:10 +00:00
|
|
|
return string(buf)
|
2015-09-01 19:49:33 +00:00
|
|
|
}
|
|
|
|
|
2015-09-03 00:44:39 +00:00
|
|
|
// Name returns the font's name.
|
2015-09-03 10:44:40 +00:00
|
|
|
func (f *Font) Name(id NameID) string {
|
|
|
|
if f.nameRecord == nil {
|
|
|
|
f.parseName()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return empty string if caller requests something larger than we support.
|
|
|
|
if id > NameIDSampleText {
|
|
|
|
return ""
|
|
|
|
}
|
2015-09-01 19:49:33 +00:00
|
|
|
|
2015-09-03 10:44:40 +00:00
|
|
|
return f.nameRecord.values[id]
|
2015-09-01 19:49:33 +00:00
|
|
|
}
|
|
|
|
|
2012-07-25 12:10:25 +00:00
|
|
|
// scale returns x divided by f.fUnitsPerEm, rounded to the nearest integer.
|
2015-08-18 06:30:37 +00:00
|
|
|
func (f *Font) scale(x fixed.Int26_6) fixed.Int26_6 {
|
2012-07-25 12:10:25 +00:00
|
|
|
if x >= 0 {
|
2015-08-18 06:30:37 +00:00
|
|
|
x += fixed.Int26_6(f.fUnitsPerEm) / 2
|
2012-07-25 12:10:25 +00:00
|
|
|
} else {
|
2015-08-18 06:30:37 +00:00
|
|
|
x -= fixed.Int26_6(f.fUnitsPerEm) / 2
|
2012-07-25 12:10:25 +00:00
|
|
|
}
|
2015-08-18 06:30:37 +00:00
|
|
|
return x / fixed.Int26_6(f.fUnitsPerEm)
|
2012-07-25 12:10:25 +00:00
|
|
|
}
|
|
|
|
|
2010-05-03 23:54:43 +00:00
|
|
|
// Bounds returns the union of a Font's glyphs' bounds.
|
Use fixed.Rectangle26_6 instead of truetype.Bounds.
The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.
Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
2015-08-30 12:06:37 +00:00
|
|
|
func (f *Font) Bounds(scale fixed.Int26_6) fixed.Rectangle26_6 {
|
2012-07-25 12:10:25 +00:00
|
|
|
b := f.bounds
|
Use fixed.Rectangle26_6 instead of truetype.Bounds.
The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.
Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
2015-08-30 12:06:37 +00:00
|
|
|
b.Min.X = f.scale(scale * b.Min.X)
|
|
|
|
b.Min.Y = f.scale(scale * b.Min.Y)
|
|
|
|
b.Max.X = f.scale(scale * b.Max.X)
|
|
|
|
b.Max.Y = f.scale(scale * b.Max.Y)
|
2012-07-25 12:10:25 +00:00
|
|
|
return b
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
|
2012-07-25 12:10:25 +00:00
|
|
|
// FUnitsPerEm returns the number of FUnits in a Font's em-square's side.
|
|
|
|
func (f *Font) FUnitsPerEm() int32 {
|
|
|
|
return f.fUnitsPerEm
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
|
2011-12-22 23:55:36 +00:00
|
|
|
// Index returns a Font's index for the given rune.
|
|
|
|
func (f *Font) Index(x rune) Index {
|
2013-10-10 20:29:40 +00:00
|
|
|
c := uint32(x)
|
2013-10-11 22:29:59 +00:00
|
|
|
for i, j := 0, len(f.cm); i < j; {
|
|
|
|
h := i + (j-i)/2
|
|
|
|
cm := &f.cm[h]
|
|
|
|
if c < cm.start {
|
|
|
|
j = h
|
|
|
|
} else if cm.end < c {
|
|
|
|
i = h + 1
|
|
|
|
} else if cm.offset == 0 {
|
|
|
|
return Index(c + cm.delta)
|
|
|
|
} else {
|
|
|
|
offset := int(cm.offset) + 2*(h-len(f.cm)+int(c-cm.start))
|
2012-06-16 02:19:07 +00:00
|
|
|
return Index(u16(f.cmapIndexes, offset))
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
return 0
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
|
2013-10-09 20:43:32 +00:00
|
|
|
// unscaledHMetric returns the unscaled horizontal metrics for the glyph with
|
|
|
|
// the given index.
|
|
|
|
func (f *Font) unscaledHMetric(i Index) (h HMetric) {
|
2010-05-03 23:54:43 +00:00
|
|
|
j := int(i)
|
2013-11-04 22:58:40 +00:00
|
|
|
if j < 0 || f.nGlyph <= j {
|
2010-05-03 23:54:43 +00:00
|
|
|
return HMetric{}
|
|
|
|
}
|
|
|
|
if j >= f.nHMetric {
|
|
|
|
p := 4 * (f.nHMetric - 1)
|
2013-10-09 20:43:32 +00:00
|
|
|
return HMetric{
|
2015-08-18 06:30:37 +00:00
|
|
|
AdvanceWidth: fixed.Int26_6(u16(f.hmtx, p)),
|
|
|
|
LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, p+2*(j-f.nHMetric)+4))),
|
2013-10-09 20:43:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return HMetric{
|
2015-08-18 06:30:37 +00:00
|
|
|
AdvanceWidth: fixed.Int26_6(u16(f.hmtx, 4*j)),
|
|
|
|
LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, 4*j+2))),
|
2012-07-25 12:10:25 +00:00
|
|
|
}
|
2013-10-09 20:43:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// HMetric returns the horizontal metrics for the glyph with the given index.
|
2015-08-18 06:30:37 +00:00
|
|
|
func (f *Font) HMetric(scale fixed.Int26_6, i Index) HMetric {
|
2013-10-09 20:43:32 +00:00
|
|
|
h := f.unscaledHMetric(i)
|
2012-07-25 12:10:25 +00:00
|
|
|
h.AdvanceWidth = f.scale(scale * h.AdvanceWidth)
|
|
|
|
h.LeftSideBearing = f.scale(scale * h.LeftSideBearing)
|
|
|
|
return h
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
|
2013-11-04 22:58:40 +00:00
|
|
|
// unscaledVMetric returns the unscaled vertical metrics for the glyph with
|
2013-11-28 23:33:11 +00:00
|
|
|
// the given index. yMax is the top of the glyph's bounding box.
|
2015-08-18 06:30:37 +00:00
|
|
|
func (f *Font) unscaledVMetric(i Index, yMax fixed.Int26_6) (v VMetric) {
|
2013-11-04 22:58:40 +00:00
|
|
|
j := int(i)
|
|
|
|
if j < 0 || f.nGlyph <= j {
|
|
|
|
return VMetric{}
|
|
|
|
}
|
|
|
|
if 4*j+4 <= len(f.vmtx) {
|
|
|
|
return VMetric{
|
2015-08-18 06:30:37 +00:00
|
|
|
AdvanceHeight: fixed.Int26_6(u16(f.vmtx, 4*j)),
|
|
|
|
TopSideBearing: fixed.Int26_6(int16(u16(f.vmtx, 4*j+2))),
|
2013-11-04 22:58:40 +00:00
|
|
|
}
|
|
|
|
}
|
2013-11-28 23:33:11 +00:00
|
|
|
// 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(f.os2) >= 72 {
|
2015-08-18 06:30:37 +00:00
|
|
|
sTypoAscender := fixed.Int26_6(int16(u16(f.os2, 68)))
|
|
|
|
sTypoDescender := fixed.Int26_6(int16(u16(f.os2, 70)))
|
2013-11-28 23:33:11 +00:00
|
|
|
return VMetric{
|
|
|
|
AdvanceHeight: sTypoAscender - sTypoDescender,
|
|
|
|
TopSideBearing: sTypoAscender - yMax,
|
|
|
|
}
|
|
|
|
}
|
2013-11-04 22:58:40 +00:00
|
|
|
return VMetric{
|
2015-08-18 06:30:37 +00:00
|
|
|
AdvanceHeight: fixed.Int26_6(f.fUnitsPerEm),
|
2013-11-04 22:58:40 +00:00
|
|
|
TopSideBearing: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// VMetric returns the vertical metrics for the glyph with the given index.
|
2015-08-18 06:30:37 +00:00
|
|
|
func (f *Font) VMetric(scale fixed.Int26_6, i Index) VMetric {
|
2013-11-28 23:33:11 +00:00
|
|
|
// TODO: should 0 be bounds.YMax?
|
|
|
|
v := f.unscaledVMetric(i, 0)
|
2013-11-04 22:58:40 +00:00
|
|
|
v.AdvanceHeight = f.scale(scale * v.AdvanceHeight)
|
|
|
|
v.TopSideBearing = f.scale(scale * v.TopSideBearing)
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2015-08-24 06:17:16 +00:00
|
|
|
// Kern returns the horizontal adjustment for the given glyph pair. A positive
|
|
|
|
// kern means to move the glyphs further apart.
|
|
|
|
func (f *Font) Kern(scale fixed.Int26_6, i0, i1 Index) fixed.Int26_6 {
|
2010-05-03 23:54:43 +00:00
|
|
|
if f.nKern == 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
g := uint32(i0)<<16 | uint32(i1)
|
|
|
|
lo, hi := 0, f.nKern
|
|
|
|
for lo < hi {
|
|
|
|
i := (lo + hi) / 2
|
2012-06-16 02:19:07 +00:00
|
|
|
ig := u32(f.kern, 18+6*i)
|
2010-05-03 23:54:43 +00:00
|
|
|
if ig < g {
|
|
|
|
lo = i + 1
|
|
|
|
} else if ig > g {
|
|
|
|
hi = i
|
|
|
|
} else {
|
2015-08-18 06:30:37 +00:00
|
|
|
return f.scale(scale * fixed.Int26_6(int16(u16(f.kern, 22+6*i))))
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2012-05-07 01:42:35 +00:00
|
|
|
// Parse returns a new Font for the given TTF or TTC data.
|
|
|
|
//
|
|
|
|
// For TrueType Collections, the first font in the collection is parsed.
|
2011-11-07 23:38:49 +00:00
|
|
|
func Parse(ttf []byte) (font *Font, err error) {
|
2012-05-07 01:42:35 +00:00
|
|
|
return parse(ttf, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func parse(ttf []byte, offset int) (font *Font, err error) {
|
|
|
|
if len(ttf)-offset < 12 {
|
2010-05-03 23:54:43 +00:00
|
|
|
err = FormatError("TTF data is too short")
|
|
|
|
return
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
originalOffset := offset
|
|
|
|
magic, offset := u32(ttf, offset), offset+4
|
|
|
|
switch magic {
|
2012-05-07 01:42:35 +00:00
|
|
|
case 0x00010000:
|
|
|
|
// No-op.
|
|
|
|
case 0x74746366: // "ttcf" as a big-endian uint32.
|
2012-06-16 02:19:07 +00:00
|
|
|
if originalOffset != 0 {
|
2012-05-07 01:42:35 +00:00
|
|
|
err = FormatError("recursive TTC")
|
|
|
|
return
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
ttcVersion, offset := u32(ttf, offset), offset+4
|
|
|
|
if ttcVersion != 0x00010000 {
|
2012-05-07 01:42:35 +00:00
|
|
|
// TODO: support TTC version 2.0, once I have such a .ttc file to test with.
|
|
|
|
err = FormatError("bad TTC version")
|
|
|
|
return
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
numFonts, offset := int(u32(ttf, offset)), offset+4
|
2012-05-07 01:42:35 +00:00
|
|
|
if numFonts <= 0 {
|
|
|
|
err = FormatError("bad number of TTC fonts")
|
|
|
|
return
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
if len(ttf[offset:])/4 < numFonts {
|
2012-05-07 01:42:35 +00:00
|
|
|
err = 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.
|
2012-06-16 02:19:07 +00:00
|
|
|
offset = int(u32(ttf, offset))
|
2012-05-07 01:42:35 +00:00
|
|
|
if offset <= 0 || offset > len(ttf) {
|
|
|
|
err = FormatError("bad TTC offset")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return parse(ttf, offset)
|
|
|
|
default:
|
|
|
|
err = FormatError("bad TTF version")
|
2010-05-03 23:54:43 +00:00
|
|
|
return
|
|
|
|
}
|
2012-06-16 02:19:07 +00:00
|
|
|
n, offset := int(u16(ttf, offset)), offset+2
|
2010-05-03 23:54:43 +00:00
|
|
|
if len(ttf) < 16*n+12 {
|
|
|
|
err = FormatError("TTF data is too short")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
f := new(Font)
|
|
|
|
// Assign the table slices.
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
x := 16*i + 12
|
|
|
|
switch string(ttf[x : x+4]) {
|
|
|
|
case "cmap":
|
|
|
|
f.cmap, err = readTable(ttf, ttf[x+8:x+16])
|
2013-07-30 09:20:31 +00:00
|
|
|
case "cvt ":
|
|
|
|
f.cvt, err = readTable(ttf, ttf[x+8:x+16])
|
|
|
|
case "fpgm":
|
|
|
|
f.fpgm, err = readTable(ttf, ttf[x+8:x+16])
|
2010-05-03 23:54:43 +00:00
|
|
|
case "glyf":
|
|
|
|
f.glyf, err = readTable(ttf, ttf[x+8:x+16])
|
2014-01-13 22:58:07 +00:00
|
|
|
case "hdmx":
|
|
|
|
f.hdmx, err = readTable(ttf, ttf[x+8:x+16])
|
2010-05-03 23:54:43 +00:00
|
|
|
case "head":
|
|
|
|
f.head, err = readTable(ttf, ttf[x+8:x+16])
|
|
|
|
case "hhea":
|
|
|
|
f.hhea, err = readTable(ttf, ttf[x+8:x+16])
|
|
|
|
case "hmtx":
|
|
|
|
f.hmtx, err = readTable(ttf, ttf[x+8:x+16])
|
|
|
|
case "kern":
|
|
|
|
f.kern, err = readTable(ttf, ttf[x+8:x+16])
|
|
|
|
case "loca":
|
|
|
|
f.loca, err = readTable(ttf, ttf[x+8:x+16])
|
|
|
|
case "maxp":
|
|
|
|
f.maxp, err = readTable(ttf, ttf[x+8:x+16])
|
2015-09-02 15:00:02 +00:00
|
|
|
case "name":
|
|
|
|
f.name, err = readTable(ttf, ttf[x+8:x+16])
|
2013-11-28 23:33:11 +00:00
|
|
|
case "OS/2":
|
|
|
|
f.os2, err = readTable(ttf, ttf[x+8:x+16])
|
2013-07-30 09:20:31 +00:00
|
|
|
case "prep":
|
|
|
|
f.prep, err = readTable(ttf, ttf[x+8:x+16])
|
2013-11-04 22:58:40 +00:00
|
|
|
case "vmtx":
|
|
|
|
f.vmtx, err = readTable(ttf, ttf[x+8:x+16])
|
2010-05-03 23:54:43 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Parse and sanity-check the TTF data.
|
|
|
|
if err = f.parseHead(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err = f.parseMaxp(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err = f.parseCmap(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err = f.parseKern(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err = f.parseHhea(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2015-09-01 19:49:33 +00:00
|
|
|
if err = f.parseName(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2010-05-03 23:54:43 +00:00
|
|
|
font = f
|
|
|
|
return
|
|
|
|
}
|