golang-image/font/sfnt/sfnt.go

1729 lines
53 KiB
Go
Raw Permalink Normal View History

// 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 "git.fireandbrimst.one/aw/golang-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"
"image"
"io"
"git.fireandbrimst.one/aw/golang-image/font"
"git.fireandbrimst.one/aw/golang-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")
errInvalidOS2Table = errors.New("sfnt: invalid OS/2 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, 0x74727565: // 0x10000, "OTTO", "true"
// 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
capHeight 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
post *PostTable
slope [2]int32
unitsPerEm Units
xHeight int32
}
}
// 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, run, rise, numHMetrics, err := f.parseHhea(buf, numGlyphs)
if err != nil {
return err
}
buf, err = f.parseHmtx(buf, numGlyphs, numHMetrics)
if err != nil {
return err
}
buf, hasXHeightCapHeight, xHeight, capHeight, err := f.parseOS2(buf)
if err != nil {
return err
}
buf, post, err := f.parsePost(buf, numGlyphs)
if err != nil {
return err
}
f.cached.ascent = ascent
f.cached.capHeight = capHeight
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.post = post
f.cached.slope = [2]int32{run, rise}
f.cached.unitsPerEm = unitsPerEm
f.cached.xHeight = xHeight
if !hasXHeightCapHeight {
xh, ch, err := f.initOS2Version1()
if err != nil {
return err
}
f.cached.xHeight = xh
f.cached.capHeight = ch
}
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 0x74727565: // "true"
// No-op.
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, run, rise, numHMetrics int32, err error) {
// https://www.microsoft.com/typography/OTSPEC/hhea.htm
if f.hhea.length != 36 {
return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable
}
u, err := f.src.u16(buf, f.hhea, 34)
if err != nil {
return nil, 0, 0, 0, 0, 0, 0, err
}
if int32(u) > numGlyphs || u == 0 {
return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable
}
a, err := f.src.u16(buf, f.hhea, 4)
if err != nil {
return nil, 0, 0, 0, 0, 0, 0, err
}
d, err := f.src.u16(buf, f.hhea, 6)
if err != nil {
return nil, 0, 0, 0, 0, 0, 0, err
}
l, err := f.src.u16(buf, f.hhea, 8)
if err != nil {
return nil, 0, 0, 0, 0, 0, 0, err
}
ru, err := f.src.u16(buf, f.hhea, 20)
if err != nil {
return nil, 0, 0, 0, 0, 0, 0, err
}
ri, err := f.src.u16(buf, f.hhea, 18)
if err != nil {
return nil, 0, 0, 0, 0, 0, 0, err
}
return buf, int32(int16(a)), int32(int16(d)), int32(int16(l)), int32(int16(ru)), int32(int16(ri)), int32(u), nil
}
func (f *Font) parseHmtx(buf []byte, numGlyphs, numHMetrics int32) (buf1 []byte, err error) {
// https://www.microsoft.com/typography/OTSPEC/hmtx.htm
// The spec says that the hmtx table's length should be
// "4*numHMetrics+2*(numGlyphs-numHMetrics)". However, some fonts seen in the
// wild omit the "2*(nG-nHM)". See https://github.com/golang/go/issues/28379
if f.hmtx.length != uint32(4*numHMetrics) && f.hmtx.length != uint32(4*numHMetrics+2*(numGlyphs-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) glyphTopOS2(b *Buffer, ppem fixed.Int26_6, r rune) (int32, error) {
ind, err := f.GlyphIndex(b, r)
if err != nil && err != ErrNotFound {
return 0, err
} else if ind == 0 {
return 0, nil
}
// Y axis points down
var min fixed.Int26_6
seg, err := f.LoadGlyph(b, ind, ppem, nil)
if err != nil {
return 0, err
}
for _, s := range seg {
for _, p := range s.Args {
if p.Y < min {
min = p.Y
}
}
}
return int32(min), nil
}
func (f *Font) initOS2Version1() (xHeight, capHeight int32, err error) {
ppem := fixed.Int26_6(f.UnitsPerEm())
var b Buffer
// sxHeight equal to the top of the unscaled and unhinted glyph bounding box
// of the glyph encoded at U+0078 (LATIN SMALL LETTER X).
xh, err := f.glyphTopOS2(&b, ppem, 'x')
if err != nil {
return 0, 0, err
}
// sCapHeight may be set equal to the top of the unscaled and unhinted glyph
// bounding box of the glyph encoded at U+0048 (LATIN CAPITAL LETTER H).
ch, err := f.glyphTopOS2(&b, ppem, 'H')
if err != nil {
return 0, 0, err
}
return int32(xh), int32(ch), nil
}
func (f *Font) parseOS2(buf []byte) (buf1 []byte, hasXHeightCapHeight bool, xHeight, capHeight int32, err error) {
// https://docs.microsoft.com/da-dk/typography/opentype/spec/os2
if f.os2.length == 0 {
// Apple TrueType fonts might omit the OS/2 table.
return buf, false, 0, 0, nil
} else if f.os2.length < 2 {
return nil, false, 0, 0, errInvalidOS2Table
}
vers, err := f.src.u16(buf, f.os2, 0)
if err != nil {
return nil, false, 0, 0, err
}
if vers <= 1 {
const headerSize = 86
if f.os2.length < headerSize {
return nil, false, 0, 0, errInvalidOS2Table
}
// Will resolve xHeight and capHeight later, see initOS2Version1.
return buf, false, 0, 0, nil
}
const headerSize = 96
if f.os2.length < headerSize {
return nil, false, 0, 0, errInvalidOS2Table
}
xh, err := f.src.u16(buf, f.os2, 86)
if err != nil {
return nil, false, 0, 0, err
}
ch, err := f.src.u16(buf, f.os2, 88)
if err != nil {
return nil, false, 0, 0, err
}
return buf, true, int32(int16(xh)), int32(int16(ch)), nil
}
// PostTable represents an information stored in the PostScript font section.
type PostTable struct {
// Version of the version tag of the "post" table.
Version uint32
// ItalicAngle in counter-clockwise degrees from the vertical. Zero for
// upright text, negative for text that leans to the right (forward).
ItalicAngle float64
// UnderlinePosition is the suggested distance of the top of the
// underline from the baseline (negative values indicate below baseline).
UnderlinePosition int16
// Suggested values for the underline thickness.
UnderlineThickness int16
// IsFixedPitch indicates that the font is not proportionally spaced
// (i.e. monospaced).
IsFixedPitch bool
}
// PostTable returns the information from the font's "post" table. It can
// return nil, if the font doesn't have such a table.
//
// See https://docs.microsoft.com/en-us/typography/opentype/spec/post
func (f *Font) PostTable() *PostTable {
return f.cached.post
}
func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, post *PostTable, err error) {
// https://www.microsoft.com/typography/otspec/post.htm
const headerSize = 32
if f.post.length < headerSize {
return nil, nil, errInvalidPostTable
}
u, err := f.src.u32(buf, f.post, 0)
if err != nil {
return nil, nil, err
}
switch u {
case 0x10000:
// No-op.
case 0x20000:
if f.post.length < headerSize+2+2*uint32(numGlyphs) {
return nil, nil, errInvalidPostTable
}
case 0x30000:
// No-op.
default:
return nil, nil, errUnsupportedPostTable
}
ang, err := f.src.u32(buf, f.post, 4)
if err != nil {
return nil, nil, err
}
up, err := f.src.u16(buf, f.post, 8)
if err != nil {
return nil, nil, err
}
ut, err := f.src.u16(buf, f.post, 10)
if err != nil {
return nil, nil, err
}
fp, err := f.src.u32(buf, f.post, 12)
if err != nil {
return nil, nil, err
}
post = &PostTable{
Version: u,
ItalicAngle: float64(int32(ang)) / 0x10000,
UnderlinePosition: int16(up),
UnderlineThickness: int16(ut),
IsFixedPitch: fp != 0,
}
return buf, post, 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
}
func (f *Font) glyphNameFormat10(x GlyphIndex) (string, error) {
if x >= numBuiltInPostNames {
return "", ErrNotFound
}
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
i := builtInPostNamesOffsets[x+0]
j := builtInPostNamesOffsets[x+1]
return builtInPostNamesData[i:j], nil
}
func (f *Font) glyphNameFormat20(b *Buffer, x GlyphIndex) (string, error) {
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--
}
}
// 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.post == nil {
return "", nil
}
switch f.cached.post.Version {
case 0x10000:
return f.glyphNameFormat10(x)
case 0x20000:
return f.glyphNameFormat20(b, x)
default:
return "", nil
}
}
// 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{
Height: scale(fixed.Int26_6(f.cached.ascent-f.cached.descent+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),
XHeight: scale(fixed.Int26_6(f.cached.xHeight)*ppem, f.cached.unitsPerEm),
CapHeight: scale(fixed.Int26_6(f.cached.capHeight)*ppem, f.cached.unitsPerEm),
CaretSlope: image.Point{X: int(f.cached.slope[0]), Y: int(f.cached.slope[1])},
}
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
m.XHeight = (m.XHeight + 63) &^ 63
m.CapHeight = (m.CapHeight + 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),
}
}