font/sfnt: parse CFF 2-byte operators and real numbers.

Change-Id: I6f2cdfb44817832cf833883ef6fca692a001b6b1
Reviewed-on: https://go-review.googlesource.com/33813
Reviewed-by: Dave Day <djd@golang.org>
This commit is contained in:
Nigel Tao 2016-12-02 17:58:28 +11:00
parent 5286ed5c2a
commit e2d0a9f0e6
4 changed files with 156 additions and 38 deletions

View File

@ -20,9 +20,8 @@ package sfnt
// - push the number 800
// - FontBBox operator
// - etc
// defines a DICT that maps "version" to the String ID (SID) 379, the copyright
// "Notice" to the SID 392, the font bounding box "FontBBox" to the four
// numbers [100, 0, 500, 800], etc.
// defines a DICT that maps "version" to the String ID (SID) 379, "Notice" to
// the SID 392, "FontBBox" to the four numbers [100, 0, 500, 800], etc.
//
// The first 391 String IDs (starting at 0) are predefined as per the CFF spec
// Appendix A, in 5176.CFF.pdf referenced below. For example, 379 means
@ -50,6 +49,8 @@ package sfnt
import (
"fmt"
"math"
"strconv"
)
const (
@ -80,10 +81,13 @@ type cffParser struct {
base int
offset int
end int
buf []byte
err error
buf []byte
locBuf [2]uint32
parseNumberBuf [maxRealNumberStrLen]byte
instructions []byte
stack struct {
@ -265,8 +269,12 @@ func (p *cffParser) parseIndexLocations(dst []uint32, count, offSize int32) (ok
// stack or executing an operator.
func (p *cffParser) step() {
if number, res := p.parseNumber(); res != prNone {
if res == prBad || p.stack.top == psStackSize {
if res < 0 || p.stack.top == psStackSize {
if res == prUnsupportedRNE {
p.err = errUnsupportedRealNumberEncoding
} else {
p.err = errInvalidCFFTable
}
return
}
p.stack.a[p.stack.top] = number
@ -276,8 +284,22 @@ func (p *cffParser) step() {
b0 := p.instructions[0]
p.instructions = p.instructions[1:]
if int(b0) < len(cff1ByteOperators) {
if op := cff1ByteOperators[b0]; op.name != "" {
for b, escaped, operators := b0, false, topDictOperators[0]; ; {
if b == escapeByte && !escaped {
if len(p.instructions) <= 0 {
p.err = errInvalidCFFTable
return
}
b = p.instructions[0]
p.instructions = p.instructions[1:]
escaped = true
operators = topDictOperators[1]
continue
}
if int(b) < len(operators) {
if op := operators[b]; op.name != "" {
if p.stack.top < op.numPop {
p.err = errInvalidCFFTable
return
@ -294,13 +316,20 @@ func (p *cffParser) step() {
}
}
p.err = fmt.Errorf("sfnt: unrecognized CFF 1-byte operator %d", b0)
if escaped {
p.err = fmt.Errorf("sfnt: unrecognized CFF 2-byte operator (12 %d)", b)
} else {
p.err = fmt.Errorf("sfnt: unrecognized CFF 1-byte operator (%d)", b)
}
return
}
}
type parseResult int32
const (
prBad parseResult = -1
prUnsupportedRNE parseResult = -2
prInvalid parseResult = -1
prNone parseResult = +0
prGood parseResult = +1
)
@ -314,7 +343,7 @@ func (p *cffParser) parseNumber() (number int32, res parseResult) {
switch b0 := p.instructions[0]; {
case b0 == 28:
if len(p.instructions) < 3 {
return 0, prBad
return 0, prInvalid
}
number = int32(int16(u16(p.instructions[1:])))
p.instructions = p.instructions[3:]
@ -322,12 +351,48 @@ func (p *cffParser) parseNumber() (number int32, res parseResult) {
case b0 == 29:
if len(p.instructions) < 5 {
return 0, prBad
return 0, prInvalid
}
number = int32(u32(p.instructions[1:]))
p.instructions = p.instructions[5:]
return number, prGood
case b0 == 30:
// Parse a real number. This isn't listed in 5176.CFF.pdf Table 3
// "Operand Encoding" but that table lists integer encodings. Further
// down the page it says "A real number operand is provided in addition
// to integer operands. This operand begins with a byte value of 30
// followed by a variable-length sequence of bytes."
s := p.parseNumberBuf[:0]
p.instructions = p.instructions[1:]
for {
if len(p.instructions) == 0 {
return 0, prInvalid
}
b := p.instructions[0]
p.instructions = p.instructions[1:]
// Process b's two nibbles, high then low.
for i := 0; i < 2; i++ {
nib := b >> 4
b = b << 4
if nib == 0x0f {
f, err := strconv.ParseFloat(string(s), 32)
if err != nil {
return 0, prInvalid
}
return int32(math.Float32bits(float32(f))), prGood
}
if nib == 0x0d {
return 0, prInvalid
}
if len(s)+maxNibbleDefsLength > len(p.parseNumberBuf) {
return 0, prUnsupportedRNE
}
s = append(s, nibbleDefs[nib]...)
}
}
case b0 < 32:
// No-op.
@ -337,7 +402,7 @@ func (p *cffParser) parseNumber() (number int32, res parseResult) {
case b0 < 251:
if len(p.instructions) < 2 {
return 0, prBad
return 0, prInvalid
}
b1 := p.instructions[1]
p.instructions = p.instructions[2:]
@ -345,7 +410,7 @@ func (p *cffParser) parseNumber() (number int32, res parseResult) {
case b0 < 255:
if len(p.instructions) < 2 {
return 0, prBad
return 0, prInvalid
}
b1 := p.instructions[1]
p.instructions = p.instructions[2:]
@ -355,6 +420,28 @@ func (p *cffParser) parseNumber() (number int32, res parseResult) {
return 0, prNone
}
const maxNibbleDefsLength = len("E-")
// nibbleDefs encodes 5176.CFF.pdf Table 5 "Nibble Definitions".
var nibbleDefs = [16]string{
0x00: "0",
0x01: "1",
0x02: "2",
0x03: "3",
0x04: "4",
0x05: "5",
0x06: "6",
0x07: "7",
0x08: "8",
0x09: "9",
0x0a: ".",
0x0b: "E",
0x0c: "E-",
0x0d: "",
0x0e: "-",
0x0f: "",
}
type cffOperator struct {
// numPop is the number of stack values to pop. -1 means "array" and -2
// means "delta" as per 5176.CFF.pdf Table 6 "Operand Types".
@ -367,9 +454,11 @@ type cffOperator struct {
run func(*cffParser)
}
// cff1ByteOperators encodes the subset of 5176.CFF.pdf Table 9 "Top DICT
// Operator Entries" used by this implementation.
var cff1ByteOperators = [...]cffOperator{
// topDictOperators encodes the subset of 5176.CFF.pdf Table 9 "Top DICT
// Operator Entries" and Table 10 "CIDFont Operator Extensions" used by this
// implementation.
var topDictOperators = [2][]cffOperator{{
// 1-byte operators.
0: {+1, "version", nil},
1: {+1, "Notice", nil},
2: {+1, "FullName", nil},
@ -384,6 +473,32 @@ var cff1ByteOperators = [...]cffOperator{
p.saved.charStrings = p.stack.a[p.stack.top-1]
}},
18: {+2, "Private", nil},
}
}, {
// 2-byte operators. The first byte is the escape byte.
0: {+1, "Copyright", nil},
1: {+1, "isFixedPitch", nil},
2: {+1, "ItalicAngle", nil},
3: {+1, "UnderlinePosition", nil},
4: {+1, "UnderlineThickness", nil},
5: {+1, "PaintType", nil},
6: {+1, "CharstringType", nil},
7: {-1, "FontMatrix", nil},
8: {+1, "StrokeWidth", nil},
20: {+1, "SyntheticBase", nil},
21: {+1, "PostScript", nil},
22: {+1, "BaseFontName", nil},
23: {-2, "BaseFontBlend", nil},
30: {+3, "ROS", nil},
31: {+1, "CIDFontVersion", nil},
32: {+1, "CIDFontRevision", nil},
33: {+1, "CIDFontType", nil},
34: {+1, "CIDCount", nil},
35: {+1, "UIDBase", nil},
36: {+1, "FDArray", nil},
37: {+1, "FDSelect", nil},
38: {+1, "FontName", nil},
}}
// TODO: 2-byte operators.
// 5176.CFF.pdf section 4 "DICT Data" says that "Two-byte operators have an
// initial escape byte of 12".
const escapeByte = 12

View File

@ -23,6 +23,8 @@ import (
// by this implementation.
const (
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
@ -42,6 +44,7 @@ var (
errInvalidVersion = errors.New("sfnt: invalid version")
errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length")
)

Binary file not shown.

View File

@ -5,7 +5,7 @@ FamilyName: CFFTest
Weight: Regular
Copyright: Copyright 2016 The Go Authors. All rights reserved.\nUse of this font is governed by a BSD-style license that can be found at https://golang.org/LICENSE.
Version: 001.000
ItalicAngle: 0
ItalicAngle: -11.25
UnderlinePosition: -100
UnderlineWidth: 50
Ascent: 800
@ -19,7 +19,7 @@ OS2Version: 0
OS2_WeightWidthSlopeOnly: 0
OS2_UseTypoMetrics: 1
CreationTime: 1479626795
ModificationTime: 1480238616
ModificationTime: 1480661567
PfmFamily: 17
TTFWeight: 400
TTFWidth: 5