From 069db1da13841f750892417d97b1b059aec960cd Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Thu, 2 Mar 2017 14:53:40 +1100 Subject: [PATCH] font/sfnt: make parseXxx dependencies explicit. Change-Id: Ib0b76c48cd0b4288700458407077aae4e5911233 Reviewed-on: https://go-review.googlesource.com/37553 Reviewed-by: David Crawshaw --- font/sfnt/cmap.go | 51 ++++++----- font/sfnt/sfnt.go | 210 ++++++++++++++++++++++++---------------------- 2 files changed, 134 insertions(+), 127 deletions(-) diff --git a/font/sfnt/cmap.go b/font/sfnt/cmap.go index cc5aa51..42338f1 100644 --- a/font/sfnt/cmap.go +++ b/font/sfnt/cmap.go @@ -88,7 +88,7 @@ var supportedCmapFormat = func(format, pid, psid uint16) bool { 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 { case 0: return f.makeCachedGlyphIndexFormat0(buf, offset, length) @@ -100,18 +100,18 @@ func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format ui 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 { - return nil, errInvalidCmapTable + return nil, nil, errInvalidCmapTable } var err error buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(length)) if err != nil { - return nil, err + return nil, nil, err } var table [256]byte 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 // golang.org/x/text/encoding/charmap API needs to allocate a new // 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 GlyphIndex(table[dst[0]]), nil - } - return buf, nil + }, 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 if offset+headerSize > f.cmap.length { - return nil, errInvalidCmapTable + return nil, nil, errInvalidCmapTable } var err error buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize) if err != nil { - return nil, err + return nil, nil, err } offset += headerSize segCount := u16(buf[6:]) if segCount&1 != 0 { - return nil, errInvalidCmapTable + return nil, nil, errInvalidCmapTable } segCount /= 2 if segCount > maxCmapSegments { - return nil, errUnsupportedNumberOfCmapSegments + return nil, nil, errUnsupportedNumberOfCmapSegments } eLength := 8*uint32(segCount) + 2 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)) if err != nil { - return nil, err + return nil, nil, err } offset += eLength @@ -173,7 +172,7 @@ func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([ indexesBase := f.cmap.offset + 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 { return 0, nil } @@ -201,38 +200,37 @@ func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([ } } return 0, nil - } - return buf, nil + }, 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 if offset+headerSize > f.cmap.length { - return nil, errInvalidCmapTable + return nil, nil, errInvalidCmapTable } var err error buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize) if err != nil { - return nil, err + return nil, nil, err } length := u32(buf[4:]) if f.cmap.length < offset || length > f.cmap.length-offset { - return nil, errInvalidCmapTable + return nil, nil, errInvalidCmapTable } offset += headerSize numGroups := u32(buf[12:]) if numGroups > maxCmapSegments { - return nil, errUnsupportedNumberOfCmapSegments + return nil, nil, errUnsupportedNumberOfCmapSegments } eLength := 12 * numGroups if headerSize+eLength != length { - return nil, errInvalidCmapTable + return nil, nil, errInvalidCmapTable } buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength)) if err != nil { - return nil, err + return nil, nil, err } 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) for i, j := 0, len(entries); i < j; { h := i + (j-i)/2 @@ -259,8 +257,7 @@ func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byt } } return 0, nil - } - return buf, nil + }, nil } type cmapEntry16 struct { diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go index 79a9d14..02efd5f 100644 --- a/font/sfnt/sfnt.go +++ b/font/sfnt/sfnt.go @@ -340,7 +340,7 @@ type Font struct { kern table cached struct { - glyphIndex func(f *Font, b *Buffer, r rune) (GlyphIndex, error) + glyphIndex glyphIndexFunc indexToLocFormat bool // false means short, true means long. isPostScript bool kernNumPairs int32 @@ -364,84 +364,96 @@ func (f *Font) initialize() error { if !f.src.valid() { return errInvalidSourceData } - buf, err := f.initializeTables(nil) + buf, isPostScript, err := f.initializeTables(nil) if err != nil { return err } // 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 - // the font being parsed by parseMaxp. + // 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. - // 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 } -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 // 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 nil, err + return nil, false, err } switch u32(buf) { default: - return nil, 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 nil, 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 nil, 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 nil, errInvalidTableTagOrder + return nil, false, errInvalidTableTagOrder } prevTag = tag o, n := u32(b[8:12]), u32(b[12:16]) if o > maxTableOffset || n > maxTableLength { - return nil, errUnsupportedTableOffsetLength + return nil, false, errUnsupportedTableOffsetLength } // We ignore the checksums, but "all tables must begin on four byte // boundries [sic]". 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. @@ -472,23 +484,23 @@ func (f *Font) initializeTables(buf []byte) ([]byte, error) { 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 const headerSize, entrySize = 4, 8 if f.cmap.length < headerSize { - return nil, errInvalidCmapTable + return nil, nil, errInvalidCmapTable } u, err := f.src.u16(buf, f.cmap, 2) if err != nil { - return nil, err + return nil, nil, err } numSubtables := int(u) if f.cmap.length < headerSize+entrySize*uint32(numSubtables) { - return nil, errInvalidCmapTable + return nil, nil, errInvalidCmapTable } var ( @@ -503,7 +515,7 @@ func (f *Font) parseCmap(buf []byte) ([]byte, error) { 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, err + return nil, nil, err } pid := u16(buf) psid := u16(buf[2:]) @@ -514,11 +526,11 @@ func (f *Font) parseCmap(buf []byte) ([]byte, error) { offset := u32(buf[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) if err != nil { - return nil, err + return nil, nil, err } format := u16(buf) if !supportedCmapFormat(format, pid, psid) { @@ -533,46 +545,46 @@ func (f *Font) parseCmap(buf []byte) ([]byte, error) { } if bestWidth == 0 { - return nil, errUnsupportedCmapEncodings + return nil, nil, errUnsupportedCmapEncodings } 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 if f.head.length != 54 { - return nil, errInvalidHeadTable + return nil, false, 0, errInvalidHeadTable } u, err := f.src.u16(buf, f.head, 18) if err != nil { - return nil, err + return nil, false, 0, err } 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) if err != nil { - return nil, err + return nil, false, 0, err } - f.cached.indexToLocFormat = u != 0 - return buf, nil + indexToLocFormat = u != 0 + 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 if f.kern.length == 0 { - return buf, nil + return buf, 0, 0, nil } const headerSize = 4 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 { - return nil, err + return nil, 0, 0, err } offset := int(f.kern.offset) + headerSize length := int(f.kern.length) - headerSize @@ -581,7 +593,7 @@ func (f *Font) parseKern(buf []byte) ([]byte, error) { case 0: // TODO: support numTables != 1. Testing that requires finding such a font. if numTables := int(u16(buf[2:])); numTables != 1 { - return nil, errUnsupportedKernTable + return nil, 0, 0, errUnsupportedKernTable } return f.parseKernVersion0(buf, offset, length) 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 // 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 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 { - return nil, err + return nil, 0, 0, err } if version := u16(buf); version != 0 { - return nil, errUnsupportedKernTable + return nil, 0, 0, errUnsupportedKernTable } subtableLength := int(u16(buf[2:])) if subtableLength < headerSize || length < subtableLength { - return nil, errInvalidKernTable + return nil, 0, 0, errInvalidKernTable } if coverageBits := buf[5]; coverageBits != 0x01 { // We only support horizontal kerning. - return nil, errUnsupportedKernTable + return nil, 0, 0, errUnsupportedKernTable } offset += headerSize length -= headerSize @@ -623,99 +635,97 @@ func (f *Font) parseKernVersion0(buf []byte, offset, length int) ([]byte, error) case 2: // 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 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 { - return nil, err + return nil, 0, 0, err } - numPairs := u16(buf) - if length != headerSize+entrySize*int(numPairs) { - return nil, errInvalidKernTable + kernNumPairs = int32(u16(buf)) + if length != headerSize+entrySize*int(kernNumPairs) { + return nil, 0, 0, errInvalidKernTable } - f.cached.kernNumPairs = int32(numPairs) - f.cached.kernOffset = int32(offset) + headerSize - return buf, nil + return buf, kernNumPairs, int32(offset) + headerSize, 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 - if f.cached.isPostScript { + if isPostScript { if f.maxp.length != 6 { - return nil, errInvalidMaxpTable + return nil, 0, nil, errInvalidMaxpTable } } else { if f.maxp.length != 32 { - return nil, errInvalidMaxpTable + return nil, 0, nil, errInvalidMaxpTable } } u, err := f.src.u16(buf, f.maxp, 4) 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{ 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 nil, 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 nil, err + return nil, 0, nil, err } } - if len(f.cached.locations) != numGlyphs+1 { - return nil, errInvalidLocationData + if len(locations) != numGlyphs+1 { + 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 const headerSize = 32 if f.post.length < headerSize { - return nil, errInvalidPostTable + return nil, 0, errInvalidPostTable } u, err := f.src.u32(buf, f.post, 0) if err != nil { - return nil, err + return nil, 0, err } switch u { case 0x20000: - if f.post.length < headerSize+2+2*uint32(f.NumGlyphs()) { - return nil, errInvalidPostTable + if f.post.length < headerSize+2+2*uint32(numGlyphs) { + return nil, 0, errInvalidPostTable } case 0x30000: // No-op. default: - return nil, errUnsupportedPostTable + return nil, 0, errUnsupportedPostTable } - f.cached.postTableVersion = u - return buf, nil + 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.