aboutsummaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/image/font/sfnt/sfnt.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/image/font/sfnt/sfnt.go')
-rw-r--r--vendor/golang.org/x/image/font/sfnt/sfnt.go1538
1 files changed, 1538 insertions, 0 deletions
diff --git a/vendor/golang.org/x/image/font/sfnt/sfnt.go b/vendor/golang.org/x/image/font/sfnt/sfnt.go
new file mode 100644
index 0000000..cc4ceac
--- /dev/null
+++ b/vendor/golang.org/x/image/font/sfnt/sfnt.go
@@ -0,0 +1,1538 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:generate go run gen.go
+
+// Package sfnt implements a decoder for SFNT font file formats, including
+// TrueType and OpenType.
+package sfnt // import "golang.org/x/image/font/sfnt"
+
+// This implementation was written primarily to the
+// https://www.microsoft.com/en-us/Typography/OpenTypeSpecification.aspx
+// specification. Additional documentation is at
+// http://developer.apple.com/fonts/TTRefMan/
+//
+// The pyftinspect tool from https://github.com/fonttools/fonttools is useful
+// for inspecting SFNT fonts.
+//
+// The ttfdump tool is also useful. For example:
+// ttfdump -t cmap ../testdata/CFFTest.otf dump.txt
+
+import (
+ "errors"
+ "io"
+
+ "golang.org/x/image/font"
+ "golang.org/x/image/math/fixed"
+ "golang.org/x/text/encoding/charmap"
+)
+
+// These constants are not part of the specifications, but are limitations used
+// by this implementation.
+const (
+ // This value is arbitrary, but defends against parsing malicious font
+ // files causing excessive memory allocations. For reference, Adobe's
+ // SourceHanSansSC-Regular.otf has 65535 glyphs and:
+ // - its format-4 cmap table has 1581 segments.
+ // - its format-12 cmap table has 16498 segments.
+ //
+ // TODO: eliminate this constraint? If the cmap table is very large, load
+ // some or all of it lazily (at the time Font.GlyphIndex is called) instead
+ // of all of it eagerly (at the time Font.initialize is called), while
+ // keeping an upper bound on the memory used? This will make the code in
+ // cmap.go more complicated, considering that all of the Font methods are
+ // safe to call concurrently, as long as each call has a different *Buffer.
+ maxCmapSegments = 20000
+
+ // TODO: similarly, load subroutine locations lazily. Adobe's
+ // SourceHanSansSC-Regular.otf has up to 30000 subroutines.
+ maxNumSubroutines = 40000
+
+ maxCompoundRecursionDepth = 8
+ maxCompoundStackSize = 64
+ maxGlyphDataLength = 64 * 1024
+ maxHintBits = 256
+ maxNumFontDicts = 256
+ maxNumFonts = 256
+ maxNumTables = 256
+ maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
+
+ // (maxTableOffset + maxTableLength) will not overflow an int32.
+ maxTableLength = 1 << 29
+ maxTableOffset = 1 << 29
+)
+
+var (
+ // ErrColoredGlyph indicates that the requested glyph is not a monochrome
+ // vector glyph, such as a colored (bitmap or vector) emoji glyph.
+ ErrColoredGlyph = errors.New("sfnt: colored glyph")
+ // ErrNotFound indicates that the requested value was not found.
+ ErrNotFound = errors.New("sfnt: not found")
+
+ errInvalidBounds = errors.New("sfnt: invalid bounds")
+ errInvalidCFFTable = errors.New("sfnt: invalid CFF table")
+ errInvalidCmapTable = errors.New("sfnt: invalid cmap table")
+ errInvalidDfont = errors.New("sfnt: invalid dfont")
+ errInvalidFont = errors.New("sfnt: invalid font")
+ errInvalidFontCollection = errors.New("sfnt: invalid font collection")
+ errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
+ errInvalidGlyphDataLength = errors.New("sfnt: invalid glyph data length")
+ errInvalidHeadTable = errors.New("sfnt: invalid head table")
+ errInvalidHheaTable = errors.New("sfnt: invalid hhea table")
+ errInvalidHmtxTable = errors.New("sfnt: invalid hmtx table")
+ errInvalidKernTable = errors.New("sfnt: invalid kern table")
+ errInvalidLocaTable = errors.New("sfnt: invalid loca table")
+ errInvalidLocationData = errors.New("sfnt: invalid location data")
+ errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
+ errInvalidNameTable = errors.New("sfnt: invalid name table")
+ errInvalidPostTable = errors.New("sfnt: invalid post table")
+ errInvalidSingleFont = errors.New("sfnt: invalid single font (data is a font collection)")
+ errInvalidSourceData = errors.New("sfnt: invalid source data")
+ errInvalidTableOffset = errors.New("sfnt: invalid table offset")
+ errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
+ errInvalidUCS2String = errors.New("sfnt: invalid UCS-2 string")
+
+ errUnsupportedCFFFDSelectTable = errors.New("sfnt: unsupported CFF FDSelect table")
+ errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
+ errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings")
+ errUnsupportedCompoundGlyph = errors.New("sfnt: unsupported compound glyph")
+ errUnsupportedGlyphDataLength = errors.New("sfnt: unsupported glyph data length")
+ errUnsupportedKernTable = errors.New("sfnt: unsupported kern table")
+ errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
+ errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
+ errUnsupportedNumberOfFontDicts = errors.New("sfnt: unsupported number of font dicts")
+ errUnsupportedNumberOfFonts = errors.New("sfnt: unsupported number of fonts")
+ errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
+ errUnsupportedNumberOfSubroutines = errors.New("sfnt: unsupported number of subroutines")
+ errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
+ errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding")
+ errUnsupportedPostTable = errors.New("sfnt: unsupported post table")
+ errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length")
+ errUnsupportedType2Charstring = errors.New("sfnt: unsupported Type 2 Charstring")
+)
+
+// GlyphIndex is a glyph index in a Font.
+type GlyphIndex uint16
+
+// NameID identifies a name table entry.
+//
+// See the "Name IDs" section of
+// https://www.microsoft.com/typography/otspec/name.htm
+type NameID uint16
+
+const (
+ NameIDCopyright NameID = 0
+ NameIDFamily = 1
+ NameIDSubfamily = 2
+ NameIDUniqueIdentifier = 3
+ NameIDFull = 4
+ NameIDVersion = 5
+ NameIDPostScript = 6
+ NameIDTrademark = 7
+ NameIDManufacturer = 8
+ NameIDDesigner = 9
+ NameIDDescription = 10
+ NameIDVendorURL = 11
+ NameIDDesignerURL = 12
+ NameIDLicense = 13
+ NameIDLicenseURL = 14
+ NameIDTypographicFamily = 16
+ NameIDTypographicSubfamily = 17
+ NameIDCompatibleFull = 18
+ NameIDSampleText = 19
+ NameIDPostScriptCID = 20
+ NameIDWWSFamily = 21
+ NameIDWWSSubfamily = 22
+ NameIDLightBackgroundPalette = 23
+ NameIDDarkBackgroundPalette = 24
+ NameIDVariationsPostScriptPrefix = 25
+)
+
+// Units are an integral number of abstract, scalable "font units". The em
+// square is typically 1000 or 2048 "font units". This would map to a certain
+// number (e.g. 30 pixels) of physical pixels, depending on things like the
+// display resolution (DPI) and font size (e.g. a 12 point font).
+type Units int32
+
+// scale returns x divided by unitsPerEm, rounded to the nearest fixed.Int26_6
+// value (1/64th of a pixel).
+func scale(x fixed.Int26_6, unitsPerEm Units) fixed.Int26_6 {
+ if x >= 0 {
+ x += fixed.Int26_6(unitsPerEm) / 2
+ } else {
+ x -= fixed.Int26_6(unitsPerEm) / 2
+ }
+ return x / fixed.Int26_6(unitsPerEm)
+}
+
+func u16(b []byte) uint16 {
+ _ = b[1] // Bounds check hint to compiler.
+ return uint16(b[0])<<8 | uint16(b[1])<<0
+}
+
+func u32(b []byte) uint32 {
+ _ = b[3] // Bounds check hint to compiler.
+ return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])<<0
+}
+
+// source is a source of byte data. Conceptually, it is like an io.ReaderAt,
+// except that a common source of SFNT font data is in-memory instead of
+// on-disk: a []byte containing the entire data, either as a global variable
+// (e.g. "goregular.TTF") or the result of an ioutil.ReadFile call. In such
+// cases, as an optimization, we skip the io.Reader / io.ReaderAt model of
+// copying from the source to a caller-supplied buffer, and instead provide
+// direct access to the underlying []byte data.
+type source struct {
+ b []byte
+ r io.ReaderAt
+
+ // TODO: add a caching layer, if we're using the io.ReaderAt? Note that
+ // this might make a source no longer safe to use concurrently.
+}
+
+// valid returns whether exactly one of s.b and s.r is nil.
+func (s *source) valid() bool {
+ return (s.b == nil) != (s.r == nil)
+}
+
+// viewBufferWritable returns whether the []byte returned by source.view can be
+// written to by the caller, including by passing it to the same method
+// (source.view) on other receivers (i.e. different sources).
+//
+// In other words, it returns whether the source's underlying data is an
+// io.ReaderAt, not a []byte.
+func (s *source) viewBufferWritable() bool {
+ return s.b == nil
+}
+
+// view returns the length bytes at the given offset. buf is an optional
+// scratch buffer to reduce allocations when calling view multiple times. A nil
+// buf is valid. The []byte returned may be a sub-slice of buf[:cap(buf)], or
+// it may be an unrelated slice. In any case, the caller should not modify the
+// contents of the returned []byte, other than passing that []byte back to this
+// method on the same source s.
+func (s *source) view(buf []byte, offset, length int) ([]byte, error) {
+ if 0 > offset || offset > offset+length {
+ return nil, errInvalidBounds
+ }
+
+ // Try reading from the []byte.
+ if s.b != nil {
+ if offset+length > len(s.b) {
+ return nil, errInvalidBounds
+ }
+ return s.b[offset : offset+length], nil
+ }
+
+ // Read from the io.ReaderAt.
+ if length <= cap(buf) {
+ buf = buf[:length]
+ } else {
+ // Round length up to the nearest KiB. The slack can lead to fewer
+ // allocations if the buffer is re-used for multiple source.view calls.
+ n := length
+ n += 1023
+ n &^= 1023
+ buf = make([]byte, length, n)
+ }
+ if n, err := s.r.ReadAt(buf, int64(offset)); n != length {
+ return nil, err
+ }
+ return buf, nil
+}
+
+// u16 returns the uint16 in the table t at the relative offset i.
+//
+// buf is an optional scratch buffer as per the source.view method.
+func (s *source) u16(buf []byte, t table, i int) (uint16, error) {
+ if i < 0 || uint(t.length) < uint(i+2) {
+ return 0, errInvalidBounds
+ }
+ buf, err := s.view(buf, int(t.offset)+i, 2)
+ if err != nil {
+ return 0, err
+ }
+ return u16(buf), nil
+}
+
+// u32 returns the uint32 in the table t at the relative offset i.
+//
+// buf is an optional scratch buffer as per the source.view method.
+func (s *source) u32(buf []byte, t table, i int) (uint32, error) {
+ if i < 0 || uint(t.length) < uint(i+4) {
+ return 0, errInvalidBounds
+ }
+ buf, err := s.view(buf, int(t.offset)+i, 4)
+ if err != nil {
+ return 0, err
+ }
+ return u32(buf), nil
+}
+
+// table is a section of the font data.
+type table struct {
+ offset, length uint32
+}
+
+// ParseCollection parses an SFNT font collection, such as TTC or OTC data,
+// from a []byte data source.
+//
+// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
+// will return a collection containing 1 font.
+func ParseCollection(src []byte) (*Collection, error) {
+ c := &Collection{src: source{b: src}}
+ if err := c.initialize(); err != nil {
+ return nil, err
+ }
+ return c, nil
+}
+
+// ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data,
+// from an io.ReaderAt data source.
+//
+// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
+// will return a collection containing 1 font.
+func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
+ c := &Collection{src: source{r: src}}
+ if err := c.initialize(); err != nil {
+ return nil, err
+ }
+ return c, nil
+}
+
+// Collection is a collection of one or more fonts.
+//
+// All of the Collection methods are safe to call concurrently.
+type Collection struct {
+ src source
+ offsets []uint32
+ isDfont bool
+}
+
+// NumFonts returns the number of fonts in the collection.
+func (c *Collection) NumFonts() int { return len(c.offsets) }
+
+func (c *Collection) initialize() error {
+ // The https://www.microsoft.com/typography/otspec/otff.htm "Font
+ // Collections" section describes the TTC header.
+ //
+ // https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
+ // describes the dfont header.
+ //
+ // 16 is the maximum of sizeof(TTCHeader) and sizeof(DfontHeader).
+ buf, err := c.src.view(nil, 0, 16)
+ if err != nil {
+ return err
+ }
+ // These cases match the switch statement in Font.initializeTables.
+ switch u32(buf) {
+ default:
+ return errInvalidFontCollection
+ case dfontResourceDataOffset:
+ return c.parseDfont(buf, u32(buf[4:]), u32(buf[12:]))
+ case 0x00010000, 0x4f54544f:
+ // Try parsing it as a single font instead of a collection.
+ c.offsets = []uint32{0}
+ case 0x74746366: // "ttcf".
+ numFonts := u32(buf[8:])
+ if numFonts == 0 || numFonts > maxNumFonts {
+ return errUnsupportedNumberOfFonts
+ }
+ buf, err = c.src.view(nil, 12, int(4*numFonts))
+ if err != nil {
+ return err
+ }
+ c.offsets = make([]uint32, numFonts)
+ for i := range c.offsets {
+ o := u32(buf[4*i:])
+ if o > maxTableOffset {
+ return errUnsupportedTableOffsetLength
+ }
+ c.offsets[i] = o
+ }
+ }
+ return nil
+}
+
+// dfontResourceDataOffset is the assumed value of a dfont file's resource data
+// offset.
+//
+// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
+// says that "A Mac OS resource file... [starts with an] offset from start of
+// file to start of resource data section... [usually] 0x0100". In theory,
+// 0x00000100 isn't always a magic number for identifying dfont files. In
+// practice, it seems to work.
+const dfontResourceDataOffset = 0x00000100
+
+// parseDfont parses a dfont resource map, as per
+// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
+//
+// That unofficial wiki page lists all of its fields as *signed* integers,
+// which looks unusual. The actual file format might use *unsigned* integers in
+// various places, but until we have either an official specification or an
+// actual dfont file where this matters, we'll use signed integers and treat
+// negative values as invalid.
+func (c *Collection) parseDfont(buf []byte, resourceMapOffset, resourceMapLength uint32) error {
+ if resourceMapOffset > maxTableOffset || resourceMapLength > maxTableLength {
+ return errUnsupportedTableOffsetLength
+ }
+
+ const headerSize = 28
+ if resourceMapLength < headerSize {
+ return errInvalidDfont
+ }
+ buf, err := c.src.view(buf, int(resourceMapOffset+24), 2)
+ if err != nil {
+ return err
+ }
+ typeListOffset := int(int16(u16(buf)))
+
+ if typeListOffset < headerSize || resourceMapLength < uint32(typeListOffset)+2 {
+ return errInvalidDfont
+ }
+ buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset, 2)
+ if err != nil {
+ return err
+ }
+ typeCount := int(int16(u16(buf)))
+
+ const tSize = 8
+ if typeCount < 0 || tSize*uint32(typeCount) > resourceMapLength-uint32(typeListOffset)-2 {
+ return errInvalidDfont
+ }
+ buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset+2, tSize*typeCount)
+ if err != nil {
+ return err
+ }
+ resourceCount, resourceListOffset := 0, 0
+ for i := 0; i < typeCount; i++ {
+ if u32(buf[tSize*i:]) != 0x73666e74 { // "sfnt".
+ continue
+ }
+
+ resourceCount = int(int16(u16(buf[tSize*i+4:])))
+ if resourceCount < 0 {
+ return errInvalidDfont
+ }
+ // https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
+ // says that the value in the wire format is "the number of
+ // resources of this type, minus one."
+ resourceCount++
+
+ resourceListOffset = int(int16(u16(buf[tSize*i+6:])))
+ if resourceListOffset < 0 {
+ return errInvalidDfont
+ }
+ break
+ }
+ if resourceCount == 0 {
+ return errInvalidDfont
+ }
+ if resourceCount > maxNumFonts {
+ return errUnsupportedNumberOfFonts
+ }
+
+ const rSize = 12
+ if o, n := uint32(typeListOffset+resourceListOffset), rSize*uint32(resourceCount); o > resourceMapLength || n > resourceMapLength-o {
+ return errInvalidDfont
+ } else {
+ buf, err = c.src.view(buf, int(resourceMapOffset+o), int(n))
+ if err != nil {
+ return err
+ }
+ }
+ c.offsets = make([]uint32, resourceCount)
+ for i := range c.offsets {
+ o := 0xffffff & u32(buf[rSize*i+4:])
+ // Offsets are relative to the resource data start, not the file start.
+ // A particular resource's data also starts with a 4-byte length, which
+ // we skip.
+ o += dfontResourceDataOffset + 4
+ if o > maxTableOffset {
+ return errUnsupportedTableOffsetLength
+ }
+ c.offsets[i] = o
+ }
+ c.isDfont = true
+ return nil
+}
+
+// Font returns the i'th font in the collection.
+func (c *Collection) Font(i int) (*Font, error) {
+ if i < 0 || len(c.offsets) <= i {
+ return nil, ErrNotFound
+ }
+ f := &Font{src: c.src}
+ if err := f.initialize(int(c.offsets[i]), c.isDfont); err != nil {
+ return nil, err
+ }
+ return f, nil
+}
+
+// Parse parses an SFNT font, such as TTF or OTF data, from a []byte data
+// source.
+func Parse(src []byte) (*Font, error) {
+ f := &Font{src: source{b: src}}
+ if err := f.initialize(0, false); err != nil {
+ return nil, err
+ }
+ return f, nil
+}
+
+// ParseReaderAt parses an SFNT font, such as TTF or OTF data, from an
+// io.ReaderAt data source.
+func ParseReaderAt(src io.ReaderAt) (*Font, error) {
+ f := &Font{src: source{r: src}}
+ if err := f.initialize(0, false); err != nil {
+ return nil, err
+ }
+ return f, nil
+}
+
+// Font is an SFNT font.
+//
+// Many of its methods take a *Buffer argument, as re-using buffers can reduce
+// the total memory allocation of repeated Font method calls, such as measuring
+// and rasterizing every unique glyph in a string of text. If efficiency is not
+// a concern, passing a nil *Buffer is valid, and implies using a temporary
+// buffer for a single call.
+//
+// It is valid to re-use a *Buffer with multiple Font method calls, even with
+// different *Font receivers, as long as they are not concurrent calls.
+//
+// All of the Font methods are safe to call concurrently, as long as each call
+// has a different *Buffer (or nil).
+//
+// The Font methods that don't take a *Buffer argument are always safe to call
+// concurrently.
+//
+// Some methods provide lengths or coordinates, e.g. bounds, font metrics and
+// control points. All of these methods take a ppem 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 ppem is fixed.I(10), which equals
+// fixed.Int26_6(10 << 6).
+//
+// To get those lengths or coordinates in terms of font units instead of
+// pixels, use ppem = fixed.Int26_6(f.UnitsPerEm()) and if those methods take a
+// font.Hinting parameter, use font.HintingNone. The return values will have
+// type fixed.Int26_6, but those numbers can be converted back to Units with no
+// further scaling necessary.
+type Font struct {
+ src source
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Required Tables".
+ cmap table
+ head table
+ hhea table
+ hmtx table
+ maxp table
+ name table
+ os2 table
+ post table
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Tables Related to TrueType Outlines".
+ //
+ // This implementation does not support hinting, so it does not read the
+ // cvt, fpgm gasp or prep tables.
+ glyf table
+ loca table
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Tables Related to PostScript Outlines".
+ //
+ // TODO: cff2, vorg?
+ cff table
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Tables Related to Bitmap Glyphs".
+ //
+ // TODO: Others?
+ cblc table
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Advanced Typographic Tables".
+ //
+ // TODO: base, gdef, gpos, gsub, jstf, math?
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Other OpenType Tables".
+ //
+ // TODO: hdmx, vmtx? Others?
+ kern table
+
+ cached struct {
+ ascent int32
+ glyphData glyphData
+ glyphIndex glyphIndexFunc
+ bounds [4]int16
+ descent int32
+ indexToLocFormat bool // false means short, true means long.
+ isColorBitmap bool
+ isPostScript bool
+ kernNumPairs int32
+ kernOffset int32
+ lineGap int32
+ numHMetrics int32
+ postTableVersion uint32
+ unitsPerEm Units
+ }
+}
+
+// NumGlyphs returns the number of glyphs in f.
+func (f *Font) NumGlyphs() int { return len(f.cached.glyphData.locations) - 1 }
+
+// UnitsPerEm returns the number of units per em for f.
+func (f *Font) UnitsPerEm() Units { return f.cached.unitsPerEm }
+
+func (f *Font) initialize(offset int, isDfont bool) error {
+ if !f.src.valid() {
+ return errInvalidSourceData
+ }
+ buf, isPostScript, err := f.initializeTables(offset, isDfont)
+ if err != nil {
+ return err
+ }
+
+ // The order of these parseXxx calls matters. Later calls may depend on
+ // information parsed by earlier calls, such as the maxp table's numGlyphs.
+ // To enforce these dependencies, such information is passed and returned
+ // explicitly, and the f.cached fields are only set afterwards.
+ //
+ // When implementing new parseXxx methods, take care not to call methods
+ // such as Font.NumGlyphs that implicitly depend on f.cached fields.
+
+ buf, bounds, indexToLocFormat, unitsPerEm, err := f.parseHead(buf)
+ if err != nil {
+ return err
+ }
+ buf, numGlyphs, err := f.parseMaxp(buf, isPostScript)
+ if err != nil {
+ return err
+ }
+ buf, glyphData, isColorBitmap, err := f.parseGlyphData(buf, numGlyphs, indexToLocFormat, isPostScript)
+ if err != nil {
+ return err
+ }
+ buf, glyphIndex, err := f.parseCmap(buf)
+ if err != nil {
+ return err
+ }
+ buf, kernNumPairs, kernOffset, err := f.parseKern(buf)
+ if err != nil {
+ return err
+ }
+ buf, ascent, descent, lineGap, numHMetrics, err := f.parseHhea(buf, numGlyphs)
+ if err != nil {
+ return err
+ }
+ buf, err = f.parseHmtx(buf, numGlyphs, numHMetrics)
+ if err != nil {
+ return err
+ }
+ buf, postTableVersion, err := f.parsePost(buf, numGlyphs)
+ if err != nil {
+ return err
+ }
+
+ f.cached.ascent = ascent
+ f.cached.glyphData = glyphData
+ f.cached.glyphIndex = glyphIndex
+ f.cached.bounds = bounds
+ f.cached.descent = descent
+ f.cached.indexToLocFormat = indexToLocFormat
+ f.cached.isColorBitmap = isColorBitmap
+ f.cached.isPostScript = isPostScript
+ f.cached.kernNumPairs = kernNumPairs
+ f.cached.kernOffset = kernOffset
+ f.cached.lineGap = lineGap
+ f.cached.numHMetrics = numHMetrics
+ f.cached.postTableVersion = postTableVersion
+ f.cached.unitsPerEm = unitsPerEm
+
+ return nil
+}
+
+func (f *Font) initializeTables(offset int, isDfont bool) (buf1 []byte, isPostScript bool, err error) {
+ // https://www.microsoft.com/typography/otspec/otff.htm "Organization of an
+ // OpenType Font" says that "The OpenType font starts with the Offset
+ // Table", which is 12 bytes.
+ buf, err := f.src.view(nil, offset, 12)
+ if err != nil {
+ return nil, false, err
+ }
+ // When updating the cases in this switch statement, also update the
+ // Collection.initialize method.
+ switch u32(buf) {
+ default:
+ return nil, false, errInvalidFont
+ case dfontResourceDataOffset:
+ return nil, false, errInvalidSingleFont
+ case 0x00010000:
+ // No-op.
+ case 0x4f54544f: // "OTTO".
+ isPostScript = true
+ case 0x74746366: // "ttcf".
+ return nil, false, errInvalidSingleFont
+ }
+ numTables := int(u16(buf[4:]))
+ if numTables > maxNumTables {
+ return nil, false, errUnsupportedNumberOfTables
+ }
+
+ // "The Offset Table is followed immediately by the Table Record entries...
+ // sorted in ascending order by tag", 16 bytes each.
+ buf, err = f.src.view(buf, offset+12, 16*numTables)
+ if err != nil {
+ return nil, false, err
+ }
+ for b, first, prevTag := buf, true, uint32(0); len(b) > 0; b = b[16:] {
+ tag := u32(b)
+ if first {
+ first = false
+ } else if tag <= prevTag {
+ return nil, false, errInvalidTableTagOrder
+ }
+ prevTag = tag
+
+ o, n := u32(b[8:12]), u32(b[12:16])
+ // For dfont files, the offset is relative to the resource, not the
+ // file.
+ if isDfont {
+ origO := o
+ o += uint32(offset)
+ if o < origO {
+ return nil, false, errUnsupportedTableOffsetLength
+ }
+ }
+ if o > maxTableOffset || n > maxTableLength {
+ return nil, false, errUnsupportedTableOffsetLength
+ }
+ // We ignore the checksums, but "all tables must begin on four byte
+ // boundries [sic]".
+ if o&3 != 0 {
+ return nil, false, errInvalidTableOffset
+ }
+
+ // Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32.
+ switch tag {
+ case 0x43424c43:
+ f.cblc = table{o, n}
+ case 0x43464620:
+ f.cff = table{o, n}
+ case 0x4f532f32:
+ f.os2 = table{o, n}
+ case 0x636d6170:
+ f.cmap = table{o, n}
+ case 0x676c7966:
+ f.glyf = table{o, n}
+ case 0x68656164:
+ f.head = table{o, n}
+ case 0x68686561:
+ f.hhea = table{o, n}
+ case 0x686d7478:
+ f.hmtx = table{o, n}
+ case 0x6b65726e:
+ f.kern = table{o, n}
+ case 0x6c6f6361:
+ f.loca = table{o, n}
+ case 0x6d617870:
+ f.maxp = table{o, n}
+ case 0x6e616d65:
+ f.name = table{o, n}
+ case 0x706f7374:
+ f.post = table{o, n}
+ }
+ }
+ return buf, isPostScript, nil
+}
+
+func (f *Font) parseCmap(buf []byte) (buf1 []byte, glyphIndex glyphIndexFunc, err error) {
+ // https://www.microsoft.com/typography/OTSPEC/cmap.htm
+
+ const headerSize, entrySize = 4, 8
+ if f.cmap.length < headerSize {
+ return nil, nil, errInvalidCmapTable
+ }
+ u, err := f.src.u16(buf, f.cmap, 2)
+ if err != nil {
+ return nil, nil, err
+ }
+ numSubtables := int(u)
+ if f.cmap.length < headerSize+entrySize*uint32(numSubtables) {
+ return nil, nil, errInvalidCmapTable
+ }
+
+ var (
+ bestWidth int
+ bestOffset uint32
+ bestLength uint32
+ bestFormat uint16
+ )
+
+ // Scan all of the subtables, picking the widest supported one. See the
+ // platformEncodingWidth comment for more discussion of width.
+ for i := 0; i < numSubtables; i++ {
+ buf, err = f.src.view(buf, int(f.cmap.offset)+headerSize+entrySize*i, entrySize)
+ if err != nil {
+ return nil, nil, err
+ }
+ pid := u16(buf)
+ psid := u16(buf[2:])
+ width := platformEncodingWidth(pid, psid)
+ if width <= bestWidth {
+ continue
+ }
+ offset := u32(buf[4:])
+
+ if offset > f.cmap.length-4 {
+ return nil, nil, errInvalidCmapTable
+ }
+ buf, err = f.src.view(buf, int(f.cmap.offset+offset), 4)
+ if err != nil {
+ return nil, nil, err
+ }
+ format := u16(buf)
+ if !supportedCmapFormat(format, pid, psid) {
+ continue
+ }
+ length := uint32(u16(buf[2:]))
+
+ bestWidth = width
+ bestOffset = offset
+ bestLength = length
+ bestFormat = format
+ }
+
+ if bestWidth == 0 {
+ return nil, nil, errUnsupportedCmapEncodings
+ }
+ return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat)
+}
+
+func (f *Font) parseHead(buf []byte) (buf1 []byte, bounds [4]int16, indexToLocFormat bool, unitsPerEm Units, err error) {
+ // https://www.microsoft.com/typography/otspec/head.htm
+
+ if f.head.length != 54 {
+ return nil, [4]int16{}, false, 0, errInvalidHeadTable
+ }
+
+ u, err := f.src.u16(buf, f.head, 18)
+ if err != nil {
+ return nil, [4]int16{}, false, 0, err
+ }
+ if u == 0 {
+ return nil, [4]int16{}, false, 0, errInvalidHeadTable
+ }
+ unitsPerEm = Units(u)
+
+ for i := range bounds {
+ u, err := f.src.u16(buf, f.head, 36+2*i)
+ if err != nil {
+ return nil, [4]int16{}, false, 0, err
+ }
+ bounds[i] = int16(u)
+ }
+
+ u, err = f.src.u16(buf, f.head, 50)
+ if err != nil {
+ return nil, [4]int16{}, false, 0, err
+ }
+ indexToLocFormat = u != 0
+ return buf, bounds, indexToLocFormat, unitsPerEm, nil
+}
+
+func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, ascent, descent, lineGap, numHMetrics int32, err error) {
+ // https://www.microsoft.com/typography/OTSPEC/hhea.htm
+
+ if f.hhea.length != 36 {
+ return nil, 0, 0, 0, 0, errInvalidHheaTable
+ }
+ u, err := f.src.u16(buf, f.hhea, 34)
+ if err != nil {
+ return nil, 0, 0, 0, 0, err
+ }
+ if int32(u) > numGlyphs || u == 0 {
+ return nil, 0, 0, 0, 0, errInvalidHheaTable
+ }
+ a, err := f.src.u16(buf, f.hhea, 4)
+ if err != nil {
+ return nil, 0, 0, 0, 0, err
+ }
+ d, err := f.src.u16(buf, f.hhea, 6)
+ if err != nil {
+ return nil, 0, 0, 0, 0, err
+ }
+ l, err := f.src.u16(buf, f.hhea, 8)
+ if err != nil {
+ return nil, 0, 0, 0, 0, err
+ }
+ return buf, int32(int16(a)), int32(int16(d)), int32(int16(l)), int32(u), nil
+}
+
+func (f *Font) parseHmtx(buf []byte, numGlyphs, numHMetrics int32) (buf1 []byte, err error) {
+ // https://www.microsoft.com/typography/OTSPEC/hmtx.htm
+
+ if f.hmtx.length != uint32(2*numGlyphs+2*numHMetrics) {
+ return nil, errInvalidHmtxTable
+ }
+ return buf, nil
+}
+
+func (f *Font) parseKern(buf []byte) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
+ // https://www.microsoft.com/typography/otspec/kern.htm
+
+ if f.kern.length == 0 {
+ return buf, 0, 0, nil
+ }
+ const headerSize = 4
+ if f.kern.length < headerSize {
+ return nil, 0, 0, errInvalidKernTable
+ }
+ buf, err = f.src.view(buf, int(f.kern.offset), headerSize)
+ if err != nil {
+ return nil, 0, 0, err
+ }
+ offset := int(f.kern.offset) + headerSize
+ length := int(f.kern.length) - headerSize
+
+ switch version := u16(buf); version {
+ case 0:
+ if numTables := int(u16(buf[2:])); numTables == 0 {
+ return buf, 0, 0, nil
+ } else if numTables > 1 {
+ // TODO: support multiple subtables. For now, fall through and use
+ // only the first one.
+ }
+ return f.parseKernVersion0(buf, offset, length)
+ case 1:
+ if buf[2] != 0 || buf[3] != 0 {
+ return nil, 0, 0, errUnsupportedKernTable
+ }
+ // Microsoft's https://www.microsoft.com/typography/otspec/kern.htm
+ // says that "Apple has extended the definition of the 'kern' table to
+ // provide additional functionality. The Apple extensions are not
+ // supported on Windows."
+ //
+ // The format is relatively complicated, including encoding a state
+ // machine, but rarely seen. We follow Microsoft's and FreeType's
+ // behavior and simply ignore it. Theoretically, we could follow
+ // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html
+ // but it doesn't seem worth the effort.
+ return buf, 0, 0, nil
+ }
+ return nil, 0, 0, errUnsupportedKernTable
+}
+
+func (f *Font) parseKernVersion0(buf []byte, offset, length int) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
+ const headerSize = 6
+ if length < headerSize {
+ return nil, 0, 0, errInvalidKernTable
+ }
+ buf, err = f.src.view(buf, offset, headerSize)
+ if err != nil {
+ return nil, 0, 0, err
+ }
+ if version := u16(buf); version != 0 {
+ return nil, 0, 0, errUnsupportedKernTable
+ }
+ subtableLength := int(u16(buf[2:]))
+ if subtableLength < headerSize || length < subtableLength {
+ return nil, 0, 0, errInvalidKernTable
+ }
+ if coverageBits := buf[5]; coverageBits != 0x01 {
+ // We only support horizontal kerning.
+ return nil, 0, 0, errUnsupportedKernTable
+ }
+ offset += headerSize
+ length -= headerSize
+ subtableLength -= headerSize
+
+ switch format := buf[4]; format {
+ case 0:
+ return f.parseKernFormat0(buf, offset, subtableLength)
+ case 2:
+ // If we could find such a font, we could write code to support it, but
+ // a comment in the equivalent FreeType code (sfnt/ttkern.c) says that
+ // they've never seen such a font.
+ }
+ return nil, 0, 0, errUnsupportedKernTable
+}
+
+func (f *Font) parseKernFormat0(buf []byte, offset, length int) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
+ const headerSize, entrySize = 8, 6
+ if length < headerSize {
+ return nil, 0, 0, errInvalidKernTable
+ }
+ buf, err = f.src.view(buf, offset, headerSize)
+ if err != nil {
+ return nil, 0, 0, err
+ }
+ kernNumPairs = int32(u16(buf))
+ if length != headerSize+entrySize*int(kernNumPairs) {
+ return nil, 0, 0, errInvalidKernTable
+ }
+ return buf, kernNumPairs, int32(offset) + headerSize, nil
+}
+
+func (f *Font) parseMaxp(buf []byte, isPostScript bool) (buf1 []byte, numGlyphs int32, err error) {
+ // https://www.microsoft.com/typography/otspec/maxp.htm
+
+ if isPostScript {
+ if f.maxp.length != 6 {
+ return nil, 0, errInvalidMaxpTable
+ }
+ } else {
+ if f.maxp.length != 32 {
+ return nil, 0, errInvalidMaxpTable
+ }
+ }
+ u, err := f.src.u16(buf, f.maxp, 4)
+ if err != nil {
+ return nil, 0, err
+ }
+ return buf, int32(u), nil
+}
+
+type glyphData struct {
+ // The glyph data for the i'th glyph index is in
+ // src[locations[i+0]:locations[i+1]].
+ //
+ // The slice length equals 1 plus the number of glyphs.
+ locations []uint32
+
+ // For PostScript fonts, the bytecode for the i'th global or local
+ // subroutine is in src[x[i+0]:x[i+1]].
+ //
+ // The []uint32 slice length equals 1 plus the number of subroutines
+ gsubrs []uint32
+ singleSubrs []uint32
+ multiSubrs [][]uint32
+
+ fdSelect fdSelect
+}
+
+func (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, isColorBitmap bool, err error) {
+ if isPostScript {
+ p := cffParser{
+ src: &f.src,
+ base: int(f.cff.offset),
+ offset: int(f.cff.offset),
+ end: int(f.cff.offset + f.cff.length),
+ }
+ ret, err = p.parse(numGlyphs)
+ if err != nil {
+ return nil, glyphData{}, false, err
+ }
+ } else if f.loca.length != 0 {
+ ret.locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
+ if err != nil {
+ return nil, glyphData{}, false, err
+ }
+ } else if f.cblc.length != 0 {
+ isColorBitmap = true
+ // TODO: parse the CBLC (and CBDT) tables. For now, we return a font
+ // with empty glyphs.
+ ret.locations = make([]uint32, numGlyphs+1)
+ }
+
+ if len(ret.locations) != int(numGlyphs+1) {
+ return nil, glyphData{}, false, errInvalidLocationData
+ }
+
+ return buf, ret, isColorBitmap, nil
+}
+
+func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, postTableVersion uint32, err error) {
+ // https://www.microsoft.com/typography/otspec/post.htm
+
+ const headerSize = 32
+ if f.post.length < headerSize {
+ return nil, 0, errInvalidPostTable
+ }
+ u, err := f.src.u32(buf, f.post, 0)
+ if err != nil {
+ return nil, 0, err
+ }
+ switch u {
+ case 0x20000:
+ if f.post.length < headerSize+2+2*uint32(numGlyphs) {
+ return nil, 0, errInvalidPostTable
+ }
+ case 0x30000:
+ // No-op.
+ default:
+ return nil, 0, errUnsupportedPostTable
+ }
+ return buf, u, nil
+}
+
+// Bounds returns the union of a Font's glyphs' bounds.
+//
+// In the returned Rectangle26_6's (x, y) coordinates, the Y axis increases
+// down.
+func (f *Font) Bounds(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (fixed.Rectangle26_6, error) {
+ // The 0, 3, 2, 1 indices are to flip the Y coordinates. OpenType's Y axis
+ // increases up. Go's standard graphics libraries' Y axis increases down.
+ r := fixed.Rectangle26_6{
+ Min: fixed.Point26_6{
+ X: +scale(fixed.Int26_6(f.cached.bounds[0])*ppem, f.cached.unitsPerEm),
+ Y: -scale(fixed.Int26_6(f.cached.bounds[3])*ppem, f.cached.unitsPerEm),
+ },
+ Max: fixed.Point26_6{
+ X: +scale(fixed.Int26_6(f.cached.bounds[2])*ppem, f.cached.unitsPerEm),
+ Y: -scale(fixed.Int26_6(f.cached.bounds[1])*ppem, f.cached.unitsPerEm),
+ },
+ }
+ if h == font.HintingFull {
+ // Quantize the Min down and Max up to a whole pixel.
+ r.Min.X = (r.Min.X + 0) &^ 63
+ r.Min.Y = (r.Min.Y + 0) &^ 63
+ r.Max.X = (r.Max.X + 63) &^ 63
+ r.Max.Y = (r.Max.Y + 63) &^ 63
+ }
+ return r, nil
+}
+
+// TODO: API for looking up glyph variants?? For example, some fonts may
+// provide both slashed and dotted zero glyphs ('0'), or regular and 'old
+// style' numerals, and users can direct software to choose a variant.
+
+type glyphIndexFunc func(f *Font, b *Buffer, r rune) (GlyphIndex, error)
+
+// GlyphIndex returns the glyph index for the given rune.
+//
+// It returns (0, nil) if there is no glyph for r.
+// https://www.microsoft.com/typography/OTSPEC/cmap.htm says that "Character
+// codes that do not correspond to any glyph in the font should be mapped to
+// glyph index 0. The glyph at this location must be a special glyph
+// representing a missing character, commonly known as .notdef."
+func (f *Font) GlyphIndex(b *Buffer, r rune) (GlyphIndex, error) {
+ return f.cached.glyphIndex(f, b, r)
+}
+
+func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, length uint32, err error) {
+ xx := int(x)
+ if f.NumGlyphs() <= xx {
+ return nil, 0, 0, ErrNotFound
+ }
+ i := f.cached.glyphData.locations[xx+0]
+ j := f.cached.glyphData.locations[xx+1]
+ if j < i {
+ return nil, 0, 0, errInvalidGlyphDataLength
+ }
+ if j-i > maxGlyphDataLength {
+ return nil, 0, 0, errUnsupportedGlyphDataLength
+ }
+ buf, err = b.view(&f.src, int(i), int(j-i))
+ return buf, i, j - i, err
+}
+
+// LoadGlyphOptions are the options to the Font.LoadGlyph method.
+type LoadGlyphOptions struct {
+ // TODO: transform / hinting.
+}
+
+// LoadGlyph returns the vector segments for the x'th glyph. ppem is the number
+// of pixels in 1 em.
+//
+// If b is non-nil, the segments become invalid to use once b is re-used.
+//
+// In the returned Segments' (x, y) coordinates, the Y axis increases down.
+//
+// It returns ErrNotFound if the glyph index is out of range. It returns
+// ErrColoredGlyph if the glyph is not a monochrome vector glyph, such as a
+// colored (bitmap or vector) emoji glyph.
+func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) ([]Segment, error) {
+ if b == nil {
+ b = &Buffer{}
+ }
+
+ b.segments = b.segments[:0]
+ if f.cached.isColorBitmap {
+ return nil, ErrColoredGlyph
+ }
+ if f.cached.isPostScript {
+ buf, offset, length, err := f.viewGlyphData(b, x)
+ if err != nil {
+ return nil, err
+ }
+ b.psi.type2Charstrings.initialize(f, b, x)
+ if err := b.psi.run(psContextType2Charstring, buf, offset, length); err != nil {
+ return nil, err
+ }
+ if !b.psi.type2Charstrings.ended {
+ return nil, errInvalidCFFTable
+ }
+ } else if err := loadGlyf(f, b, x, 0, 0); err != nil {
+ return nil, err
+ }
+
+ // Scale the segments. If we want to support hinting, we'll have to push
+ // the scaling computation into the PostScript / TrueType specific glyph
+ // loading code, such as the appendGlyfSegments body, since TrueType
+ // hinting bytecode works on the scaled glyph vectors. For now, though,
+ // it's simpler to scale as a post-processing step.
+ //
+ // We also flip the Y coordinates. OpenType's Y axis increases up. Go's
+ // standard graphics libraries' Y axis increases down.
+ for i := range b.segments {
+ a := &b.segments[i].Args
+ for j := range a {
+ a[j].X = +scale(a[j].X*ppem, f.cached.unitsPerEm)
+ a[j].Y = -scale(a[j].Y*ppem, f.cached.unitsPerEm)
+ }
+ }
+
+ // TODO: look at opts to transform / hint the Buffer.segments.
+
+ return b.segments, nil
+}
+
+// GlyphName returns the name of the x'th glyph.
+//
+// Not every font contains glyph names. If not present, GlyphName will return
+// ("", nil).
+//
+// If present, the glyph name, provided by the font, is assumed to follow the
+// Adobe Glyph List Specification:
+// https://github.com/adobe-type-tools/agl-specification/blob/master/README.md
+//
+// This is also known as the "Adobe Glyph Naming convention", the "Adobe
+// document [for] Unicode and Glyph Names" or "PostScript glyph names".
+//
+// It returns ErrNotFound if the glyph index is out of range.
+func (f *Font) GlyphName(b *Buffer, x GlyphIndex) (string, error) {
+ if int(x) >= f.NumGlyphs() {
+ return "", ErrNotFound
+ }
+ if f.cached.postTableVersion != 0x20000 {
+ return "", nil
+ }
+ if b == nil {
+ b = &Buffer{}
+ }
+
+ // The wire format for a Version 2 post table is documented at:
+ // https://www.microsoft.com/typography/otspec/post.htm
+ const glyphNameIndexOffset = 34
+
+ buf, err := b.view(&f.src, int(f.post.offset)+glyphNameIndexOffset+2*int(x), 2)
+ if err != nil {
+ return "", err
+ }
+ u := u16(buf)
+ if u < numBuiltInPostNames {
+ i := builtInPostNamesOffsets[u+0]
+ j := builtInPostNamesOffsets[u+1]
+ return builtInPostNamesData[i:j], nil
+ }
+ // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
+ // says that "32768 through 65535 are reserved for future use".
+ if u > 32767 {
+ return "", errUnsupportedPostTable
+ }
+ u -= numBuiltInPostNames
+
+ // Iterate through the list of Pascal-formatted strings. A linear scan is
+ // clearly O(u), which isn't great (as the obvious loop, calling
+ // Font.GlyphName, to get all of the glyph names in a font has quadratic
+ // complexity), but the wire format doesn't suggest a better alternative.
+
+ offset := glyphNameIndexOffset + 2*f.NumGlyphs()
+ buf, err = b.view(&f.src, int(f.post.offset)+offset, int(f.post.length)-offset)
+ if err != nil {
+ return "", err
+ }
+
+ for {
+ if len(buf) == 0 {
+ return "", errInvalidPostTable
+ }
+ n := 1 + int(buf[0])
+ if len(buf) < n {
+ return "", errInvalidPostTable
+ }
+ if u == 0 {
+ return string(buf[1:n]), nil
+ }
+ buf = buf[n:]
+ u--
+ }
+}
+
+// GlyphAdvance returns the advance width for the x'th glyph. ppem is the
+// number of pixels in 1 em.
+//
+// It returns ErrNotFound if the glyph index is out of range.
+func (f *Font) GlyphAdvance(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) {
+ if int(x) >= f.NumGlyphs() {
+ return 0, ErrNotFound
+ }
+ if b == nil {
+ b = &Buffer{}
+ }
+
+ // https://www.microsoft.com/typography/OTSPEC/hmtx.htm says that "As an
+ // optimization, the number of records can be less than the number of
+ // glyphs, in which case the advance width value of the last record applies
+ // to all remaining glyph IDs."
+ if n := GlyphIndex(f.cached.numHMetrics - 1); x > n {
+ x = n
+ }
+
+ buf, err := b.view(&f.src, int(f.hmtx.offset)+int(4*x), 2)
+ if err != nil {
+ return 0, err
+ }
+ adv := fixed.Int26_6(u16(buf))
+ adv = scale(adv*ppem, f.cached.unitsPerEm)
+ if h == font.HintingFull {
+ // Quantize the fixed.Int26_6 value to the nearest pixel.
+ adv = (adv + 32) &^ 63
+ }
+ return adv, nil
+}
+
+// Kern returns the horizontal adjustment for the kerning pair (x0, x1). A
+// positive kern means to move the glyphs further apart. ppem is the number of
+// pixels in 1 em.
+//
+// It returns ErrNotFound if either glyph index is out of range.
+func (f *Font) Kern(b *Buffer, x0, x1 GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) {
+ // TODO: how should this work with the GPOS table and CFF fonts?
+ // https://www.microsoft.com/typography/otspec/kern.htm says that
+ // "OpenType™ fonts containing CFF outlines are not supported by the 'kern'
+ // table and must use the 'GPOS' OpenType Layout table."
+
+ if n := f.NumGlyphs(); int(x0) >= n || int(x1) >= n {
+ return 0, ErrNotFound
+ }
+ // Not every font has a kern table. If it doesn't, or if that table is
+ // ignored, there's no need to allocate a Buffer.
+ if f.cached.kernNumPairs == 0 {
+ return 0, nil
+ }
+ if b == nil {
+ b = &Buffer{}
+ }
+
+ key := uint32(x0)<<16 | uint32(x1)
+ lo, hi := int32(0), f.cached.kernNumPairs
+ for lo < hi {
+ i := (lo + hi) / 2
+
+ // TODO: this view call inside the inner loop can lead to many small
+ // reads instead of fewer larger reads, which can be expensive. We
+ // should be able to do better, although we don't want to make (one)
+ // arbitrarily large read. Perhaps we should round up reads to 4K or 8K
+ // chunks. For reference, Arial.ttf's kern table is 5472 bytes.
+ // Times_New_Roman.ttf's kern table is 5220 bytes.
+ const entrySize = 6
+ buf, err := b.view(&f.src, int(f.cached.kernOffset+i*entrySize), entrySize)
+ if err != nil {
+ return 0, err
+ }
+
+ k := u32(buf)
+ if k < key {
+ lo = i + 1
+ } else if k > key {
+ hi = i
+ } else {
+ kern := fixed.Int26_6(int16(u16(buf[4:])))
+ kern = scale(kern*ppem, f.cached.unitsPerEm)
+ if h == font.HintingFull {
+ // Quantize the fixed.Int26_6 value to the nearest pixel.
+ kern = (kern + 32) &^ 63
+ }
+ return kern, nil
+ }
+ }
+ return 0, nil
+}
+
+// Metrics returns the metrics of this font.
+func (f *Font) Metrics(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (font.Metrics, error) {
+ m := font.Metrics{
+ // TODO: is adding lineGap correct?
+ Height: ppem + scale(fixed.Int26_6(f.cached.lineGap)*ppem, f.cached.unitsPerEm),
+ Ascent: +scale(fixed.Int26_6(f.cached.ascent)*ppem, f.cached.unitsPerEm),
+ Descent: -scale(fixed.Int26_6(f.cached.descent)*ppem, f.cached.unitsPerEm),
+ }
+ if h == font.HintingFull {
+ // Quantize up to a whole pixel.
+ m.Height = (m.Height + 63) &^ 63
+ m.Ascent = (m.Ascent + 63) &^ 63
+ m.Descent = (m.Descent + 63) &^ 63
+ }
+ return m, nil
+}
+
+// Name returns the name value keyed by the given NameID.
+//
+// It returns ErrNotFound if there is no value for that key.
+func (f *Font) Name(b *Buffer, id NameID) (string, error) {
+ if b == nil {
+ b = &Buffer{}
+ }
+
+ const headerSize, entrySize = 6, 12
+ if f.name.length < headerSize {
+ return "", errInvalidNameTable
+ }
+ buf, err := b.view(&f.src, int(f.name.offset), headerSize)
+ if err != nil {
+ return "", err
+ }
+ numSubtables := u16(buf[2:])
+ if f.name.length < headerSize+entrySize*uint32(numSubtables) {
+ return "", errInvalidNameTable
+ }
+ stringOffset := u16(buf[4:])
+
+ seen := false
+ for i, n := 0, int(numSubtables); i < n; i++ {
+ buf, err := b.view(&f.src, int(f.name.offset)+headerSize+entrySize*i, entrySize)
+ if err != nil {
+ return "", err
+ }
+ if u16(buf[6:]) != uint16(id) {
+ continue
+ }
+ seen = true
+
+ var stringify func([]byte) (string, error)
+ switch u32(buf) {
+ default:
+ continue
+ case pidMacintosh<<16 | psidMacintoshRoman:
+ stringify = stringifyMacintosh
+ case pidWindows<<16 | psidWindowsUCS2:
+ stringify = stringifyUCS2
+ }
+
+ nameLength := u16(buf[8:])
+ nameOffset := u16(buf[10:])
+ buf, err = b.view(&f.src, int(f.name.offset)+int(nameOffset)+int(stringOffset), int(nameLength))
+ if err != nil {
+ return "", err
+ }
+ return stringify(buf)
+ }
+
+ if seen {
+ return "", errUnsupportedPlatformEncoding
+ }
+ return "", ErrNotFound
+}
+
+func stringifyMacintosh(b []byte) (string, error) {
+ for _, c := range b {
+ if c >= 0x80 {
+ // b contains some non-ASCII bytes.
+ s, _ := charmap.Macintosh.NewDecoder().Bytes(b)
+ return string(s), nil
+ }
+ }
+ // b contains only ASCII bytes.
+ return string(b), nil
+}
+
+func stringifyUCS2(b []byte) (string, error) {
+ if len(b)&1 != 0 {
+ return "", errInvalidUCS2String
+ }
+ r := make([]rune, len(b)/2)
+ for i := range r {
+ r[i] = rune(u16(b))
+ b = b[2:]
+ }
+ return string(r), nil
+}
+
+// Buffer holds re-usable buffers that can reduce the total memory allocation
+// of repeated Font method calls.
+//
+// See the Font type's documentation comment for more details.
+type Buffer struct {
+ // buf is a byte buffer for when a Font's source is an io.ReaderAt.
+ buf []byte
+ // segments holds glyph vector path segments.
+ segments []Segment
+ // compoundStack holds the components of a TrueType compound glyph.
+ compoundStack [maxCompoundStackSize]struct {
+ glyphIndex GlyphIndex
+ dx, dy int16
+ hasTransform bool
+ transformXX int16
+ transformXY int16
+ transformYX int16
+ transformYY int16
+ }
+ // psi is a PostScript interpreter for when the Font is an OpenType/CFF
+ // font.
+ psi psInterpreter
+}
+
+func (b *Buffer) view(src *source, offset, length int) ([]byte, error) {
+ buf, err := src.view(b.buf, offset, length)
+ if err != nil {
+ return nil, err
+ }
+ // Only update b.buf if it is safe to re-use buf.
+ if src.viewBufferWritable() {
+ b.buf = buf
+ }
+ return buf, nil
+}
+
+// Segment is a segment of a vector path.
+type Segment struct {
+ // Op is the operator.
+ Op SegmentOp
+ // Args is up to three (x, y) coordinates. The Y axis increases down.
+ Args [3]fixed.Point26_6
+}
+
+// SegmentOp is a vector path segment's operator.
+type SegmentOp uint32
+
+const (
+ SegmentOpMoveTo SegmentOp = iota
+ SegmentOpLineTo
+ SegmentOpQuadTo
+ SegmentOpCubeTo
+)
+
+// translateArgs applies a translation to args.
+func translateArgs(args *[3]fixed.Point26_6, dx, dy fixed.Int26_6) {
+ args[0].X += dx
+ args[0].Y += dy
+ args[1].X += dx
+ args[1].Y += dy
+ args[2].X += dx
+ args[2].Y += dy
+}
+
+// transformArgs applies an affine transformation to args. The t?? arguments
+// are 2.14 fixed point values.
+func transformArgs(args *[3]fixed.Point26_6, txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6) {
+ args[0] = tform(txx, txy, tyx, tyy, dx, dy, args[0])
+ args[1] = tform(txx, txy, tyx, tyy, dx, dy, args[1])
+ args[2] = tform(txx, txy, tyx, tyy, dx, dy, args[2])
+}
+
+func tform(txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6, p fixed.Point26_6) fixed.Point26_6 {
+ const half = 1 << 13
+ return fixed.Point26_6{
+ X: dx +
+ fixed.Int26_6((int64(p.X)*int64(txx)+half)>>14) +
+ fixed.Int26_6((int64(p.Y)*int64(tyx)+half)>>14),
+ Y: dy +
+ fixed.Int26_6((int64(p.X)*int64(txy)+half)>>14) +
+ fixed.Int26_6((int64(p.Y)*int64(tyy)+half)>>14),
+ }
+}