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
|
// - push the number 800
|
||||||
// - FontBBox operator
|
// - FontBBox operator
|
||||||
// - etc
|
// - etc
|
||||||
// defines a DICT that maps "version" to the String ID (SID) 379, the copyright
|
// defines a DICT that maps "version" to the String ID (SID) 379, "Notice" to
|
||||||
// "Notice" to the SID 392, the font bounding box "FontBBox" to the four
|
// the SID 392, "FontBBox" to the four numbers [100, 0, 500, 800], etc.
|
||||||
// numbers [100, 0, 500, 800], etc.
|
|
||||||
//
|
//
|
||||||
// The first 391 String IDs (starting at 0) are predefined as per the CFF spec
|
// 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
|
// Appendix A, in 5176.CFF.pdf referenced below. For example, 379 means
|
||||||
|
@ -50,6 +49,8 @@ package sfnt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -80,10 +81,13 @@ type cffParser struct {
|
||||||
base int
|
base int
|
||||||
offset int
|
offset int
|
||||||
end int
|
end int
|
||||||
buf []byte
|
|
||||||
err error
|
err error
|
||||||
|
|
||||||
|
buf []byte
|
||||||
locBuf [2]uint32
|
locBuf [2]uint32
|
||||||
|
|
||||||
|
parseNumberBuf [maxRealNumberStrLen]byte
|
||||||
|
|
||||||
instructions []byte
|
instructions []byte
|
||||||
|
|
||||||
stack struct {
|
stack struct {
|
||||||
|
@ -265,8 +269,12 @@ func (p *cffParser) parseIndexLocations(dst []uint32, count, offSize int32) (ok
|
||||||
// stack or executing an operator.
|
// stack or executing an operator.
|
||||||
func (p *cffParser) step() {
|
func (p *cffParser) step() {
|
||||||
if number, res := p.parseNumber(); res != prNone {
|
if number, res := p.parseNumber(); res != prNone {
|
||||||
if res == prBad || p.stack.top == psStackSize {
|
if res < 0 || p.stack.top == psStackSize {
|
||||||
p.err = errInvalidCFFTable
|
if res == prUnsupportedRNE {
|
||||||
|
p.err = errUnsupportedRealNumberEncoding
|
||||||
|
} else {
|
||||||
|
p.err = errInvalidCFFTable
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.stack.a[p.stack.top] = number
|
p.stack.a[p.stack.top] = number
|
||||||
|
@ -276,33 +284,54 @@ func (p *cffParser) step() {
|
||||||
|
|
||||||
b0 := p.instructions[0]
|
b0 := p.instructions[0]
|
||||||
p.instructions = p.instructions[1:]
|
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 p.stack.top < op.numPop {
|
if b == escapeByte && !escaped {
|
||||||
|
if len(p.instructions) <= 0 {
|
||||||
p.err = errInvalidCFFTable
|
p.err = errInvalidCFFTable
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if op.run != nil {
|
b = p.instructions[0]
|
||||||
op.run(p)
|
p.instructions = p.instructions[1:]
|
||||||
}
|
escaped = true
|
||||||
if op.numPop < 0 {
|
operators = topDictOperators[1]
|
||||||
p.stack.top = 0
|
continue
|
||||||
} else {
|
|
||||||
p.stack.top -= op.numPop
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
p.err = fmt.Errorf("sfnt: unrecognized CFF 1-byte operator %d", b0)
|
if int(b) < len(operators) {
|
||||||
|
if op := operators[b]; op.name != "" {
|
||||||
|
if p.stack.top < op.numPop {
|
||||||
|
p.err = errInvalidCFFTable
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if op.run != nil {
|
||||||
|
op.run(p)
|
||||||
|
}
|
||||||
|
if op.numPop < 0 {
|
||||||
|
p.stack.top = 0
|
||||||
|
} else {
|
||||||
|
p.stack.top -= op.numPop
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
type parseResult int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
prBad parseResult = -1
|
prUnsupportedRNE parseResult = -2
|
||||||
prNone parseResult = +0
|
prInvalid parseResult = -1
|
||||||
prGood parseResult = +1
|
prNone parseResult = +0
|
||||||
|
prGood parseResult = +1
|
||||||
)
|
)
|
||||||
|
|
||||||
// See 5176.CFF.pdf section 4 "DICT Data".
|
// See 5176.CFF.pdf section 4 "DICT Data".
|
||||||
|
@ -314,7 +343,7 @@ func (p *cffParser) parseNumber() (number int32, res parseResult) {
|
||||||
switch b0 := p.instructions[0]; {
|
switch b0 := p.instructions[0]; {
|
||||||
case b0 == 28:
|
case b0 == 28:
|
||||||
if len(p.instructions) < 3 {
|
if len(p.instructions) < 3 {
|
||||||
return 0, prBad
|
return 0, prInvalid
|
||||||
}
|
}
|
||||||
number = int32(int16(u16(p.instructions[1:])))
|
number = int32(int16(u16(p.instructions[1:])))
|
||||||
p.instructions = p.instructions[3:]
|
p.instructions = p.instructions[3:]
|
||||||
|
@ -322,12 +351,48 @@ func (p *cffParser) parseNumber() (number int32, res parseResult) {
|
||||||
|
|
||||||
case b0 == 29:
|
case b0 == 29:
|
||||||
if len(p.instructions) < 5 {
|
if len(p.instructions) < 5 {
|
||||||
return 0, prBad
|
return 0, prInvalid
|
||||||
}
|
}
|
||||||
number = int32(u32(p.instructions[1:]))
|
number = int32(u32(p.instructions[1:]))
|
||||||
p.instructions = p.instructions[5:]
|
p.instructions = p.instructions[5:]
|
||||||
return number, prGood
|
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:
|
case b0 < 32:
|
||||||
// No-op.
|
// No-op.
|
||||||
|
|
||||||
|
@ -337,7 +402,7 @@ func (p *cffParser) parseNumber() (number int32, res parseResult) {
|
||||||
|
|
||||||
case b0 < 251:
|
case b0 < 251:
|
||||||
if len(p.instructions) < 2 {
|
if len(p.instructions) < 2 {
|
||||||
return 0, prBad
|
return 0, prInvalid
|
||||||
}
|
}
|
||||||
b1 := p.instructions[1]
|
b1 := p.instructions[1]
|
||||||
p.instructions = p.instructions[2:]
|
p.instructions = p.instructions[2:]
|
||||||
|
@ -345,7 +410,7 @@ func (p *cffParser) parseNumber() (number int32, res parseResult) {
|
||||||
|
|
||||||
case b0 < 255:
|
case b0 < 255:
|
||||||
if len(p.instructions) < 2 {
|
if len(p.instructions) < 2 {
|
||||||
return 0, prBad
|
return 0, prInvalid
|
||||||
}
|
}
|
||||||
b1 := p.instructions[1]
|
b1 := p.instructions[1]
|
||||||
p.instructions = p.instructions[2:]
|
p.instructions = p.instructions[2:]
|
||||||
|
@ -355,6 +420,28 @@ func (p *cffParser) parseNumber() (number int32, res parseResult) {
|
||||||
return 0, prNone
|
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 {
|
type cffOperator struct {
|
||||||
// numPop is the number of stack values to pop. -1 means "array" and -2
|
// 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".
|
// means "delta" as per 5176.CFF.pdf Table 6 "Operand Types".
|
||||||
|
@ -367,9 +454,11 @@ type cffOperator struct {
|
||||||
run func(*cffParser)
|
run func(*cffParser)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cff1ByteOperators encodes the subset of 5176.CFF.pdf Table 9 "Top DICT
|
// topDictOperators encodes the subset of 5176.CFF.pdf Table 9 "Top DICT
|
||||||
// Operator Entries" used by this implementation.
|
// Operator Entries" and Table 10 "CIDFont Operator Extensions" used by this
|
||||||
var cff1ByteOperators = [...]cffOperator{
|
// implementation.
|
||||||
|
var topDictOperators = [2][]cffOperator{{
|
||||||
|
// 1-byte operators.
|
||||||
0: {+1, "version", nil},
|
0: {+1, "version", nil},
|
||||||
1: {+1, "Notice", nil},
|
1: {+1, "Notice", nil},
|
||||||
2: {+1, "FullName", nil},
|
2: {+1, "FullName", nil},
|
||||||
|
@ -384,6 +473,32 @@ var cff1ByteOperators = [...]cffOperator{
|
||||||
p.saved.charStrings = p.stack.a[p.stack.top-1]
|
p.saved.charStrings = p.stack.a[p.stack.top-1]
|
||||||
}},
|
}},
|
||||||
18: {+2, "Private", nil},
|
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
|
||||||
|
|
|
@ -22,7 +22,9 @@ import (
|
||||||
// These constants are not part of the specifications, but are limitations used
|
// These constants are not part of the specifications, but are limitations used
|
||||||
// by this implementation.
|
// by this implementation.
|
||||||
const (
|
const (
|
||||||
maxNumTables = 256
|
maxNumTables = 256
|
||||||
|
maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
|
||||||
|
|
||||||
// (maxTableOffset + maxTableLength) will not overflow an int32.
|
// (maxTableOffset + maxTableLength) will not overflow an int32.
|
||||||
maxTableLength = 1 << 29
|
maxTableLength = 1 << 29
|
||||||
maxTableOffset = 1 << 29
|
maxTableOffset = 1 << 29
|
||||||
|
@ -41,9 +43,10 @@ var (
|
||||||
errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
|
errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
|
||||||
errInvalidVersion = errors.New("sfnt: invalid version")
|
errInvalidVersion = errors.New("sfnt: invalid version")
|
||||||
|
|
||||||
errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
|
errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
|
||||||
errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
|
errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
|
||||||
errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length")
|
errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
|
||||||
|
errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Units are an integral number of abstract, scalable "font units". The em
|
// Units are an integral number of abstract, scalable "font units". The em
|
||||||
|
|
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
|
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.
|
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
|
Version: 001.000
|
||||||
ItalicAngle: 0
|
ItalicAngle: -11.25
|
||||||
UnderlinePosition: -100
|
UnderlinePosition: -100
|
||||||
UnderlineWidth: 50
|
UnderlineWidth: 50
|
||||||
Ascent: 800
|
Ascent: 800
|
||||||
|
@ -19,7 +19,7 @@ OS2Version: 0
|
||||||
OS2_WeightWidthSlopeOnly: 0
|
OS2_WeightWidthSlopeOnly: 0
|
||||||
OS2_UseTypoMetrics: 1
|
OS2_UseTypoMetrics: 1
|
||||||
CreationTime: 1479626795
|
CreationTime: 1479626795
|
||||||
ModificationTime: 1480238616
|
ModificationTime: 1480661567
|
||||||
PfmFamily: 17
|
PfmFamily: 17
|
||||||
TTFWeight: 400
|
TTFWeight: 400
|
||||||
TTFWidth: 5
|
TTFWidth: 5
|
||||||
|
|
Loading…
Reference in New Issue
Block a user