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
}
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 {

View File

@ -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.