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