font/sfnt: make parseXxx dependencies explicit.

Change-Id: Ib0b76c48cd0b4288700458407077aae4e5911233
Reviewed-on: https://go-review.googlesource.com/37553
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Nigel Tao 2017-03-02 14:53:40 +11:00
parent e6cbe778da
commit 069db1da13
2 changed files with 134 additions and 127 deletions

View File

@ -88,7 +88,7 @@ var supportedCmapFormat = func(format, pid, psid uint16) bool {
return false return false
} }
func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format uint16) ([]byte, error) { func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format uint16) ([]byte, glyphIndexFunc, error) {
switch format { switch format {
case 0: case 0:
return f.makeCachedGlyphIndexFormat0(buf, offset, length) return f.makeCachedGlyphIndexFormat0(buf, offset, length)
@ -100,18 +100,18 @@ func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format ui
panic("unreachable") panic("unreachable")
} }
func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([]byte, error) { func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) {
if length != 6+256 || offset+length > f.cmap.length { if length != 6+256 || offset+length > f.cmap.length {
return nil, errInvalidCmapTable return nil, nil, errInvalidCmapTable
} }
var err error var err error
buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(length)) buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(length))
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
var table [256]byte var table [256]byte
copy(table[:], buf[6:]) copy(table[:], buf[6:])
f.cached.glyphIndex = func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
// TODO: for this closure to be goroutine-safe, the // TODO: for this closure to be goroutine-safe, the
// golang.org/x/text/encoding/charmap API needs to allocate a new // golang.org/x/text/encoding/charmap API needs to allocate a new
// Encoder and new []byte buffers, for every call to this closure, even // Encoder and new []byte buffers, for every call to this closure, even
@ -126,38 +126,37 @@ func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([
return 0, nil return 0, nil
} }
return GlyphIndex(table[dst[0]]), nil return GlyphIndex(table[dst[0]]), nil
} }, nil
return buf, nil
} }
func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([]byte, error) { func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) {
const headerSize = 14 const headerSize = 14
if offset+headerSize > f.cmap.length { if offset+headerSize > f.cmap.length {
return nil, errInvalidCmapTable return nil, nil, errInvalidCmapTable
} }
var err error var err error
buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize) buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
offset += headerSize offset += headerSize
segCount := u16(buf[6:]) segCount := u16(buf[6:])
if segCount&1 != 0 { if segCount&1 != 0 {
return nil, errInvalidCmapTable return nil, nil, errInvalidCmapTable
} }
segCount /= 2 segCount /= 2
if segCount > maxCmapSegments { if segCount > maxCmapSegments {
return nil, errUnsupportedNumberOfCmapSegments return nil, nil, errUnsupportedNumberOfCmapSegments
} }
eLength := 8*uint32(segCount) + 2 eLength := 8*uint32(segCount) + 2
if offset+eLength > f.cmap.length { if offset+eLength > f.cmap.length {
return nil, errInvalidCmapTable return nil, nil, errInvalidCmapTable
} }
buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength)) buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
offset += eLength offset += eLength
@ -173,7 +172,7 @@ func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([
indexesBase := f.cmap.offset + offset indexesBase := f.cmap.offset + offset
indexesLength := f.cmap.length - offset indexesLength := f.cmap.length - offset
f.cached.glyphIndex = func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
if uint32(r) > 0xffff { if uint32(r) > 0xffff {
return 0, nil return 0, nil
} }
@ -201,38 +200,37 @@ func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([
} }
} }
return 0, nil return 0, nil
} }, nil
return buf, nil
} }
func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byte, error) { func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byte, glyphIndexFunc, error) {
const headerSize = 16 const headerSize = 16
if offset+headerSize > f.cmap.length { if offset+headerSize > f.cmap.length {
return nil, errInvalidCmapTable return nil, nil, errInvalidCmapTable
} }
var err error var err error
buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize) buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
length := u32(buf[4:]) length := u32(buf[4:])
if f.cmap.length < offset || length > f.cmap.length-offset { if f.cmap.length < offset || length > f.cmap.length-offset {
return nil, errInvalidCmapTable return nil, nil, errInvalidCmapTable
} }
offset += headerSize offset += headerSize
numGroups := u32(buf[12:]) numGroups := u32(buf[12:])
if numGroups > maxCmapSegments { if numGroups > maxCmapSegments {
return nil, errUnsupportedNumberOfCmapSegments return nil, nil, errUnsupportedNumberOfCmapSegments
} }
eLength := 12 * numGroups eLength := 12 * numGroups
if headerSize+eLength != length { if headerSize+eLength != length {
return nil, errInvalidCmapTable return nil, nil, errInvalidCmapTable
} }
buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength)) buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
offset += eLength offset += eLength
@ -245,7 +243,7 @@ func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byt
} }
} }
f.cached.glyphIndex = func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
c := uint32(r) c := uint32(r)
for i, j := 0, len(entries); i < j; { for i, j := 0, len(entries); i < j; {
h := i + (j-i)/2 h := i + (j-i)/2
@ -259,8 +257,7 @@ func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byt
} }
} }
return 0, nil return 0, nil
} }, nil
return buf, nil
} }
type cmapEntry16 struct { type cmapEntry16 struct {

View File

@ -340,7 +340,7 @@ type Font struct {
kern table kern table
cached struct { cached struct {
glyphIndex func(f *Font, b *Buffer, r rune) (GlyphIndex, error) glyphIndex glyphIndexFunc
indexToLocFormat bool // false means short, true means long. indexToLocFormat bool // false means short, true means long.
isPostScript bool isPostScript bool
kernNumPairs int32 kernNumPairs int32
@ -364,84 +364,96 @@ func (f *Font) initialize() error {
if !f.src.valid() { if !f.src.valid() {
return errInvalidSourceData return errInvalidSourceData
} }
buf, err := f.initializeTables(nil) buf, isPostScript, err := f.initializeTables(nil)
if err != nil { if err != nil {
return err return err
} }
// The order of these parseXxx calls matters. Later calls may depend on // The order of these parseXxx calls matters. Later calls may depend on
// f.cached state set up by earlier calls, such as the number of glyphs in // information parsed by earlier calls, such as the maxp table's numGlyphs.
// the font being parsed by parseMaxp. // 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.
// TODO: make state dependencies explicit instead of implicit. 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
buf, err = f.parseHead(buf)
if err != nil {
return err
}
buf, err = f.parseMaxp(buf)
if err != nil {
return err
}
buf, err = f.parseCmap(buf)
if err != nil {
return err
}
buf, err = f.parseKern(buf)
if err != nil {
return err
}
buf, err = f.parsePost(buf)
if err != nil {
return err
}
return nil return nil
} }
func (f *Font) initializeTables(buf []byte) ([]byte, error) { func (f *Font) initializeTables(buf []byte) (buf1 []byte, isPostScript bool, err error) {
// https://www.microsoft.com/typography/otspec/otff.htm "Organization of an // https://www.microsoft.com/typography/otspec/otff.htm "Organization of an
// OpenType Font" says that "The OpenType font starts with the Offset // OpenType Font" says that "The OpenType font starts with the Offset
// Table", which is 12 bytes. // Table", which is 12 bytes.
buf, err := f.src.view(buf, 0, 12) buf, err = f.src.view(buf, 0, 12)
if err != nil { if err != nil {
return nil, err return nil, false, err
} }
switch u32(buf) { switch u32(buf) {
default: default:
return nil, errInvalidVersion return nil, false, errInvalidVersion
case 0x00010000: case 0x00010000:
// No-op. // No-op.
case 0x4f54544f: // "OTTO". case 0x4f54544f: // "OTTO".
f.cached.isPostScript = true isPostScript = true
} }
numTables := int(u16(buf[4:])) numTables := int(u16(buf[4:]))
if numTables > maxNumTables { if numTables > maxNumTables {
return nil, errUnsupportedNumberOfTables return nil, false, errUnsupportedNumberOfTables
} }
// "The Offset Table is followed immediately by the Table Record entries... // "The Offset Table is followed immediately by the Table Record entries...
// sorted in ascending order by tag", 16 bytes each. // sorted in ascending order by tag", 16 bytes each.
buf, err = f.src.view(buf, 12, 16*numTables) buf, err = f.src.view(buf, 12, 16*numTables)
if err != nil { if err != nil {
return nil, err return nil, false, err
} }
for b, first, prevTag := buf, true, uint32(0); len(b) > 0; b = b[16:] { for b, first, prevTag := buf, true, uint32(0); len(b) > 0; b = b[16:] {
tag := u32(b) tag := u32(b)
if first { if first {
first = false first = false
} else if tag <= prevTag { } else if tag <= prevTag {
return nil, errInvalidTableTagOrder return nil, false, errInvalidTableTagOrder
} }
prevTag = tag prevTag = tag
o, n := u32(b[8:12]), u32(b[12:16]) o, n := u32(b[8:12]), u32(b[12:16])
if o > maxTableOffset || n > maxTableLength { if o > maxTableOffset || n > maxTableLength {
return nil, errUnsupportedTableOffsetLength return nil, false, errUnsupportedTableOffsetLength
} }
// We ignore the checksums, but "all tables must begin on four byte // We ignore the checksums, but "all tables must begin on four byte
// boundries [sic]". // boundries [sic]".
if o&3 != 0 { if o&3 != 0 {
return nil, errInvalidTableOffset return nil, false, errInvalidTableOffset
} }
// Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32. // Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32.
@ -472,23 +484,23 @@ func (f *Font) initializeTables(buf []byte) ([]byte, error) {
f.post = table{o, n} f.post = table{o, n}
} }
} }
return buf, nil return buf, isPostScript, nil
} }
func (f *Font) parseCmap(buf []byte) ([]byte, error) { func (f *Font) parseCmap(buf []byte) (buf1 []byte, glyphIndex glyphIndexFunc, err error) {
// https://www.microsoft.com/typography/OTSPEC/cmap.htm // https://www.microsoft.com/typography/OTSPEC/cmap.htm
const headerSize, entrySize = 4, 8 const headerSize, entrySize = 4, 8
if f.cmap.length < headerSize { if f.cmap.length < headerSize {
return nil, errInvalidCmapTable return nil, nil, errInvalidCmapTable
} }
u, err := f.src.u16(buf, f.cmap, 2) u, err := f.src.u16(buf, f.cmap, 2)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
numSubtables := int(u) numSubtables := int(u)
if f.cmap.length < headerSize+entrySize*uint32(numSubtables) { if f.cmap.length < headerSize+entrySize*uint32(numSubtables) {
return nil, errInvalidCmapTable return nil, nil, errInvalidCmapTable
} }
var ( var (
@ -503,7 +515,7 @@ func (f *Font) parseCmap(buf []byte) ([]byte, error) {
for i := 0; i < numSubtables; i++ { for i := 0; i < numSubtables; i++ {
buf, err = f.src.view(buf, int(f.cmap.offset)+headerSize+entrySize*i, entrySize) buf, err = f.src.view(buf, int(f.cmap.offset)+headerSize+entrySize*i, entrySize)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
pid := u16(buf) pid := u16(buf)
psid := u16(buf[2:]) psid := u16(buf[2:])
@ -514,11 +526,11 @@ func (f *Font) parseCmap(buf []byte) ([]byte, error) {
offset := u32(buf[4:]) offset := u32(buf[4:])
if offset > f.cmap.length-4 { if offset > f.cmap.length-4 {
return nil, errInvalidCmapTable return nil, nil, errInvalidCmapTable
} }
buf, err = f.src.view(buf, int(f.cmap.offset+offset), 4) buf, err = f.src.view(buf, int(f.cmap.offset+offset), 4)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
format := u16(buf) format := u16(buf)
if !supportedCmapFormat(format, pid, psid) { if !supportedCmapFormat(format, pid, psid) {
@ -533,46 +545,46 @@ func (f *Font) parseCmap(buf []byte) ([]byte, error) {
} }
if bestWidth == 0 { if bestWidth == 0 {
return nil, errUnsupportedCmapEncodings return nil, nil, errUnsupportedCmapEncodings
} }
return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat) return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat)
} }
func (f *Font) parseHead(buf []byte) ([]byte, error) { func (f *Font) parseHead(buf []byte) (buf1 []byte, indexToLocFormat bool, unitsPerEm Units, err error) {
// https://www.microsoft.com/typography/otspec/head.htm // https://www.microsoft.com/typography/otspec/head.htm
if f.head.length != 54 { if f.head.length != 54 {
return nil, 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 { if err != nil {
return nil, err return nil, false, 0, err
} }
if u == 0 { if u == 0 {
return nil, errInvalidHeadTable return nil, false, 0, errInvalidHeadTable
} }
f.cached.unitsPerEm = Units(u) unitsPerEm = Units(u)
u, err = f.src.u16(buf, f.head, 50) u, err = f.src.u16(buf, f.head, 50)
if err != nil { if err != nil {
return nil, err return nil, false, 0, err
} }
f.cached.indexToLocFormat = u != 0 indexToLocFormat = u != 0
return buf, nil return buf, indexToLocFormat, unitsPerEm, nil
} }
func (f *Font) parseKern(buf []byte) ([]byte, error) { func (f *Font) parseKern(buf []byte) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
// https://www.microsoft.com/typography/otspec/kern.htm // https://www.microsoft.com/typography/otspec/kern.htm
if f.kern.length == 0 { if f.kern.length == 0 {
return buf, nil return buf, 0, 0, nil
} }
const headerSize = 4 const headerSize = 4
if f.kern.length < headerSize { if f.kern.length < headerSize {
return nil, errInvalidKernTable return nil, 0, 0, errInvalidKernTable
} }
buf, err := f.src.view(buf, int(f.kern.offset), headerSize) buf, err = f.src.view(buf, int(f.kern.offset), headerSize)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
offset := int(f.kern.offset) + headerSize offset := int(f.kern.offset) + headerSize
length := int(f.kern.length) - headerSize length := int(f.kern.length) - headerSize
@ -581,7 +593,7 @@ func (f *Font) parseKern(buf []byte) ([]byte, error) {
case 0: case 0:
// TODO: support numTables != 1. Testing that requires finding such a font. // TODO: support numTables != 1. Testing that requires finding such a font.
if numTables := int(u16(buf[2:])); numTables != 1 { if numTables := int(u16(buf[2:])); numTables != 1 {
return nil, errUnsupportedKernTable return nil, 0, 0, errUnsupportedKernTable
} }
return f.parseKernVersion0(buf, offset, length) return f.parseKernVersion0(buf, offset, length)
case 1: case 1:
@ -590,28 +602,28 @@ func (f *Font) parseKern(buf []byte) ([]byte, error) {
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html
// say that such fonts work on Mac OS but not on Windows. // say that such fonts work on Mac OS but not on Windows.
} }
return nil, errUnsupportedKernTable return nil, 0, 0, errUnsupportedKernTable
} }
func (f *Font) parseKernVersion0(buf []byte, offset, length int) ([]byte, error) { func (f *Font) parseKernVersion0(buf []byte, offset, length int) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
const headerSize = 6 const headerSize = 6
if length < headerSize { if length < headerSize {
return nil, errInvalidKernTable return nil, 0, 0, errInvalidKernTable
} }
buf, err := f.src.view(buf, offset, headerSize) buf, err = f.src.view(buf, offset, headerSize)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
if version := u16(buf); version != 0 { if version := u16(buf); version != 0 {
return nil, errUnsupportedKernTable return nil, 0, 0, errUnsupportedKernTable
} }
subtableLength := int(u16(buf[2:])) subtableLength := int(u16(buf[2:]))
if subtableLength < headerSize || length < subtableLength { if subtableLength < headerSize || length < subtableLength {
return nil, errInvalidKernTable return nil, 0, 0, errInvalidKernTable
} }
if coverageBits := buf[5]; coverageBits != 0x01 { if coverageBits := buf[5]; coverageBits != 0x01 {
// We only support horizontal kerning. // We only support horizontal kerning.
return nil, errUnsupportedKernTable return nil, 0, 0, errUnsupportedKernTable
} }
offset += headerSize offset += headerSize
length -= headerSize length -= headerSize
@ -623,99 +635,97 @@ func (f *Font) parseKernVersion0(buf []byte, offset, length int) ([]byte, error)
case 2: case 2:
// TODO: find such a (proprietary?) font, and support it. // TODO: find such a (proprietary?) font, and support it.
} }
return nil, errUnsupportedKernTable return nil, 0, 0, errUnsupportedKernTable
} }
func (f *Font) parseKernFormat0(buf []byte, offset, length int) ([]byte, error) { func (f *Font) parseKernFormat0(buf []byte, offset, length int) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
const headerSize, entrySize = 8, 6 const headerSize, entrySize = 8, 6
if length < headerSize { if length < headerSize {
return nil, errInvalidKernTable return nil, 0, 0, errInvalidKernTable
} }
buf, err := f.src.view(buf, offset, headerSize) buf, err = f.src.view(buf, offset, headerSize)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
numPairs := u16(buf) kernNumPairs = int32(u16(buf))
if length != headerSize+entrySize*int(numPairs) { if length != headerSize+entrySize*int(kernNumPairs) {
return nil, errInvalidKernTable return nil, 0, 0, errInvalidKernTable
} }
f.cached.kernNumPairs = int32(numPairs) return buf, kernNumPairs, int32(offset) + headerSize, nil
f.cached.kernOffset = int32(offset) + headerSize
return buf, nil
} }
func (f *Font) parseMaxp(buf []byte) ([]byte, error) { 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 // https://www.microsoft.com/typography/otspec/maxp.htm
if f.cached.isPostScript { if isPostScript {
if f.maxp.length != 6 { if f.maxp.length != 6 {
return nil, errInvalidMaxpTable return nil, 0, nil, errInvalidMaxpTable
} }
} else { } else {
if f.maxp.length != 32 { if f.maxp.length != 32 {
return nil, 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 { if err != nil {
return nil, err return nil, 0, nil, err
} }
numGlyphs := int(u) numGlyphs = int(u)
if f.cached.isPostScript { if isPostScript {
p := cffParser{ p := cffParser{
src: &f.src, src: &f.src,
base: int(f.cff.offset), base: int(f.cff.offset),
offset: int(f.cff.offset), offset: int(f.cff.offset),
end: int(f.cff.offset + f.cff.length), end: int(f.cff.offset + f.cff.length),
} }
f.cached.locations, err = p.parse() locations, err = p.parse()
if err != nil { if err != nil {
return nil, err return nil, 0, nil, err
} }
} else { } else {
f.cached.locations, err = parseLoca( locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
&f.src, f.loca, f.glyf.offset, f.cached.indexToLocFormat, numGlyphs)
if err != nil { if err != nil {
return nil, err return nil, 0, nil, err
} }
} }
if len(f.cached.locations) != numGlyphs+1 { if len(locations) != numGlyphs+1 {
return nil, errInvalidLocationData return nil, 0, nil, errInvalidLocationData
} }
return buf, nil return buf, numGlyphs, locations, nil
} }
func (f *Font) parsePost(buf []byte) ([]byte, error) { func (f *Font) parsePost(buf []byte, numGlyphs int) (buf1 []byte, postTableVersion uint32, err error) {
// https://www.microsoft.com/typography/otspec/post.htm // https://www.microsoft.com/typography/otspec/post.htm
const headerSize = 32 const headerSize = 32
if f.post.length < headerSize { if f.post.length < headerSize {
return nil, errInvalidPostTable return nil, 0, errInvalidPostTable
} }
u, err := f.src.u32(buf, f.post, 0) u, err := f.src.u32(buf, f.post, 0)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
switch u { switch u {
case 0x20000: case 0x20000:
if f.post.length < headerSize+2+2*uint32(f.NumGlyphs()) { if f.post.length < headerSize+2+2*uint32(numGlyphs) {
return nil, errInvalidPostTable return nil, 0, errInvalidPostTable
} }
case 0x30000: case 0x30000:
// No-op. // No-op.
default: default:
return nil, errUnsupportedPostTable return nil, 0, errUnsupportedPostTable
} }
f.cached.postTableVersion = u return buf, u, nil
return buf, nil
} }
// TODO: API for looking up glyph variants?? For example, some fonts may // TODO: API for looking up glyph variants?? For example, some fonts may
// provide both slashed and dotted zero glyphs ('0'), or regular and 'old // provide both slashed and dotted zero glyphs ('0'), or regular and 'old
// style' numerals, and users can direct software to choose a variant. // 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. // GlyphIndex returns the glyph index for the given rune.
// //
// It returns (0, nil) if there is no glyph for r. // It returns (0, nil) if there is no glyph for r.