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:
parent
5286ed5c2a
commit
e2d0a9f0e6
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
)
|
||||
|
|
BIN
font/testdata/CFFTest.otf
vendored
BIN
font/testdata/CFFTest.otf
vendored
Binary file not shown.
4
font/testdata/CFFTest.sfd
vendored
4
font/testdata/CFFTest.sfd
vendored
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user