From c281ee3b61e8ab53ff118866d72618ae8cce582b Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Mon, 13 Mar 2017 12:54:22 -0400 Subject: Updating server dependancies. Also adding github.com/jaytaylor/html2text and gopkg.in/gomail.v2 (#5748) --- vendor/golang.org/x/image/font/sfnt/sfnt.go | 561 +++++++++++++++++++++++++--- 1 file changed, 499 insertions(+), 62 deletions(-) (limited to 'vendor/golang.org/x/image/font/sfnt/sfnt.go') diff --git a/vendor/golang.org/x/image/font/sfnt/sfnt.go b/vendor/golang.org/x/image/font/sfnt/sfnt.go index d4929a838..02efd5f3a 100644 --- a/vendor/golang.org/x/image/font/sfnt/sfnt.go +++ b/vendor/golang.org/x/image/font/sfnt/sfnt.go @@ -2,6 +2,8 @@ // 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" @@ -13,11 +15,15 @@ package sfnt // import "golang.org/x/image/font/sfnt" // // 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" ) @@ -25,6 +31,20 @@ import ( // 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 + maxGlyphDataLength = 64 * 1024 maxHintBits = 256 maxNumTables = 256 @@ -41,27 +61,34 @@ var ( errInvalidBounds = errors.New("sfnt: invalid bounds") errInvalidCFFTable = errors.New("sfnt: invalid CFF table") + errInvalidCmapTable = errors.New("sfnt: invalid cmap table") errInvalidGlyphData = errors.New("sfnt: invalid glyph data") errInvalidHeadTable = errors.New("sfnt: invalid head 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") 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") errInvalidVersion = errors.New("sfnt: invalid version") - errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version") - errUnsupportedCompoundGlyph = errors.New("sfnt: unsupported compound glyph") - errUnsupportedGlyphDataLength = errors.New("sfnt: unsupported glyph data length") - errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding") - errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints") - errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables") - errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding") - errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length") - errUnsupportedType2Charstring = errors.New("sfnt: unsupported Type 2 Charstring") + 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") + errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints") + 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. @@ -107,16 +134,16 @@ const ( // display resolution (DPI) and font size (e.g. a 12 point font). type Units int32 -// Platform IDs and Platform Specific IDs as per -// https://www.microsoft.com/typography/otspec/name.htm -const ( - pidMacintosh = 1 - pidWindows = 3 - - psidMacintoshRoman = 0 - - psidWindowsUCS2 = 1 -) +// 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. @@ -208,6 +235,20 @@ func (s *source) u16(buf []byte, t table, i int) (uint16, error) { 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 @@ -247,6 +288,18 @@ func ParseReaderAt(src io.ReaderAt) (*Font, error) { // // 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 @@ -283,11 +336,16 @@ type Font struct { // https://www.microsoft.com/typography/otspec/otff.htm#otttables // "Other OpenType Tables". // - // TODO: hdmx, kern, vmtx? Others? + // TODO: hdmx, vmtx? Others? + kern table cached struct { + glyphIndex glyphIndexFunc indexToLocFormat bool // false means short, true means long. isPostScript bool + kernNumPairs int32 + kernOffset int32 + postTableVersion uint32 unitsPerEm Units // The glyph data for the glyph index i is in @@ -306,51 +364,96 @@ func (f *Font) initialize() error { if !f.src.valid() { return errInvalidSourceData } - var buf []byte + buf, isPostScript, err := f.initializeTables(nil) + 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, indexToLocFormat, unitsPerEm, err := f.parseHead(buf) + if err != nil { + return err + } + buf, numGlyphs, locations, err := f.parseMaxp(buf, 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, postTableVersion, err := f.parsePost(buf, numGlyphs) + if err != nil { + return err + } + + f.cached.glyphIndex = glyphIndex + f.cached.indexToLocFormat = indexToLocFormat + f.cached.isPostScript = isPostScript + f.cached.kernNumPairs = kernNumPairs + f.cached.kernOffset = kernOffset + f.cached.postTableVersion = postTableVersion + f.cached.unitsPerEm = unitsPerEm + f.cached.locations = locations + + return nil +} + +func (f *Font) initializeTables(buf []byte) (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(buf, 0, 12) + buf, err = f.src.view(buf, 0, 12) if err != nil { - return err + return nil, false, err } switch u32(buf) { default: - return errInvalidVersion + return nil, false, errInvalidVersion case 0x00010000: // No-op. case 0x4f54544f: // "OTTO". - f.cached.isPostScript = true + isPostScript = true } numTables := int(u16(buf[4:])) if numTables > maxNumTables { - return errUnsupportedNumberOfTables + 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, 12, 16*numTables) if err != nil { - return err + 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 errInvalidTableTagOrder + return nil, false, errInvalidTableTagOrder } prevTag = tag o, n := u32(b[8:12]), u32(b[12:16]) if o > maxTableOffset || n > maxTableLength { - return errUnsupportedTableOffsetLength + return nil, false, errUnsupportedTableOffsetLength } // We ignore the checksums, but "all tables must begin on four byte // boundries [sic]". if o&3 != 0 { - return errInvalidTableOffset + return nil, false, errInvalidTableOffset } // Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32. @@ -369,6 +472,8 @@ func (f *Font) initialize() error { 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: @@ -379,69 +484,258 @@ func (f *Font) initialize() error { f.post = table{o, n} } } + return buf, isPostScript, nil +} - var u uint16 +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, indexToLocFormat bool, unitsPerEm Units, err error) { // https://www.microsoft.com/typography/otspec/head.htm + if f.head.length != 54 { - return errInvalidHeadTable + return nil, false, 0, errInvalidHeadTable } - u, err = f.src.u16(buf, f.head, 18) + u, err := f.src.u16(buf, f.head, 18) if err != nil { - return err + return nil, false, 0, err } if u == 0 { - return errInvalidHeadTable + return nil, false, 0, errInvalidHeadTable } - f.cached.unitsPerEm = Units(u) + unitsPerEm = Units(u) u, err = f.src.u16(buf, f.head, 50) if err != nil { - return err + return nil, false, 0, err + } + indexToLocFormat = u != 0 + return buf, indexToLocFormat, unitsPerEm, 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: + // TODO: support numTables != 1. Testing that requires finding such a font. + if numTables := int(u16(buf[2:])); numTables != 1 { + return nil, 0, 0, errUnsupportedKernTable + } + return f.parseKernVersion0(buf, offset, length) + case 1: + // TODO: find such a (proprietary?) font, and support it. Both of + // https://www.microsoft.com/typography/otspec/kern.htm + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html + // say that such fonts work on Mac OS but not on Windows. + } + 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 } - f.cached.indexToLocFormat = u != 0 + 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: + // TODO: find such a (proprietary?) font, and support it. + } + 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, indexToLocFormat, isPostScript bool) (buf1 []byte, numGlyphs int, locations []uint32, err error) { // https://www.microsoft.com/typography/otspec/maxp.htm - if f.cached.isPostScript { + + if isPostScript { if f.maxp.length != 6 { - return errInvalidMaxpTable + return nil, 0, nil, errInvalidMaxpTable } } else { if f.maxp.length != 32 { - return errInvalidMaxpTable + return nil, 0, nil, errInvalidMaxpTable } } - u, err = f.src.u16(buf, f.maxp, 4) + u, err := f.src.u16(buf, f.maxp, 4) if err != nil { - return err + return nil, 0, nil, err } - numGlyphs := int(u) + numGlyphs = int(u) - if f.cached.isPostScript { + 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), } - f.cached.locations, err = p.parse() + locations, err = p.parse() if err != nil { - return err + return nil, 0, nil, err } } else { - f.cached.locations, err = parseLoca( - &f.src, f.loca, f.glyf.offset, f.cached.indexToLocFormat, numGlyphs) + locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs) if err != nil { - return err + return nil, 0, nil, err } } - if len(f.cached.locations) != numGlyphs+1 { - return errInvalidLocationData + if len(locations) != numGlyphs+1 { + return nil, 0, nil, errInvalidLocationData } - return nil + + return buf, numGlyphs, locations, nil } -// TODO: func (f *Font) GlyphIndex(r rune) (x GlyphIndex, ok bool) -// This will require parsing the cmap table. +func (f *Font) parsePost(buf []byte, numGlyphs int) (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 +} + +// 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) ([]byte, error) { xx := int(x) @@ -458,15 +752,16 @@ func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) ([]byte, error) { // LoadGlyphOptions are the options to the Font.LoadGlyph method. type LoadGlyphOptions struct { - // TODO: scale / transform / hinting. + // TODO: transform / hinting. } -// LoadGlyph returns the vector segments for the x'th glyph. +// 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. // // It returns ErrNotFound if the glyph index is out of range. -func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, opts *LoadGlyphOptions) ([]Segment, error) { +func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) ([]Segment, error) { if b == nil { b = &Buffer{} } @@ -491,11 +786,153 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, opts *LoadGlyphOptions) ([]Seg b.segments = segments } - // TODO: look at opts to scale / transform / hint the Buffer.segments. + // 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. + for i := range b.segments { + s := &b.segments[i] + for j := range s.Args { + s.Args[j] = scale(s.Args[j]*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-- + } +} + +// 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, there's no need to + // allocate a Buffer. + if f.kern.length == 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 +} + // Name returns the name value keyed by the given NameID. // // It returns ErrNotFound if there is no value for that key. @@ -512,14 +949,14 @@ func (f *Font) Name(b *Buffer, id NameID) (string, error) { if err != nil { return "", err } - nSubtables := u16(buf[2:]) - if f.name.length < headerSize+entrySize*uint32(nSubtables) { + 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(nSubtables); i < n; i++ { + 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 -- cgit v1.2.3-1-g7c22