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>
diff --git a/font/sfnt/postscript.go b/font/sfnt/postscript.go
index 1363dd8..cc277cb 100644
--- a/font/sfnt/postscript.go
+++ b/font/sfnt/postscript.go
@@ -20,9 +20,8 @@
// - 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 @@
import (
"fmt"
+ "math"
+ "strconv"
)
const (
@@ -80,10 +81,13 @@
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 @@
// stack or executing an operator.
func (p *cffParser) step() {
if number, res := p.parseNumber(); res != prNone {
- if res == prBad || p.stack.top == psStackSize {
- p.err = errInvalidCFFTable
+ 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,33 +284,54 @@
b0 := p.instructions[0]
p.instructions = p.instructions[1:]
- if int(b0) < len(cff1ByteOperators) {
- if op := cff1ByteOperators[b0]; op.name != "" {
- if p.stack.top < op.numPop {
+
+ for b, escaped, operators := b0, false, topDictOperators[0]; ; {
+ if b == escapeByte && !escaped {
+ if len(p.instructions) <= 0 {
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
+ b = p.instructions[0]
+ p.instructions = p.instructions[1:]
+ escaped = true
+ operators = topDictOperators[1]
+ continue
}
- }
- 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
const (
- prBad parseResult = -1
- prNone parseResult = +0
- prGood parseResult = +1
+ prUnsupportedRNE parseResult = -2
+ prInvalid parseResult = -1
+ prNone parseResult = +0
+ prGood parseResult = +1
)
// See 5176.CFF.pdf section 4 "DICT Data".
@@ -314,7 +343,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 205031d..801cb0b 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -22,7 +22,9 @@
// These constants are not part of the specifications, but are limitations used
// by this implementation.
const (
- maxNumTables = 256
+ 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
@@ -41,9 +43,10 @@
errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
errInvalidVersion = errors.New("sfnt: invalid version")
- errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
- errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
- errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length")
+ 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")
)
// Units are an integral number of abstract, scalable "font units". The em
diff --git a/font/testdata/CFFTest.otf b/font/testdata/CFFTest.otf
index 7f21c52..1f10cfc 100644
--- a/font/testdata/CFFTest.otf
+++ b/font/testdata/CFFTest.otf
Binary files differ
diff --git a/font/testdata/CFFTest.sfd b/font/testdata/CFFTest.sfd
index deda7fe..f0d3ce9 100644
--- a/font/testdata/CFFTest.sfd
+++ b/font/testdata/CFFTest.sfd
@@ -5,7 +5,7 @@
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 @@
OS2_WeightWidthSlopeOnly: 0
OS2_UseTypoMetrics: 1
CreationTime: 1479626795
-ModificationTime: 1480238616
+ModificationTime: 1480661567
PfmFamily: 17
TTFWeight: 400
TTFWidth: 5