font/sfnt: parse the glyf table.

Change-Id: Ib7ff75d99d3641f68f621db4ba2279cc1bda8c3a
Reviewed-on: https://go-review.googlesource.com/35271
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 805b7a6..d4929a8 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -25,6 +25,7 @@
 // These constants are not part of the specifications, but are limitations used
 // by this implementation.
 const (
+	maxGlyphDataLength  = 64 * 1024
 	maxHintBits         = 256
 	maxNumTables        = 256
 	maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
@@ -40,7 +41,9 @@
 
 	errInvalidBounds        = errors.New("sfnt: invalid bounds")
 	errInvalidCFFTable      = errors.New("sfnt: invalid CFF table")
+	errInvalidGlyphData     = errors.New("sfnt: invalid glyph data")
 	errInvalidHeadTable     = errors.New("sfnt: invalid head table")
+	errInvalidLocaTable     = errors.New("sfnt: invalid loca table")
 	errInvalidLocationData  = errors.New("sfnt: invalid location data")
 	errInvalidMaxpTable     = errors.New("sfnt: invalid maxp table")
 	errInvalidNameTable     = errors.New("sfnt: invalid name table")
@@ -51,6 +54,8 @@
 	errInvalidVersion       = errors.New("sfnt: invalid version")
 
 	errUnsupportedCFFVersion         = errors.New("sfnt: unsupported CFF version")
+	errUnsupportedCompoundGlyph      = errors.New("sfnt: unsupported compound glyph")
+	errUnsupportedGlyphDataLength    = errors.New("sfnt: unsupported glyph data length")
 	errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
 	errUnsupportedNumberOfHints      = errors.New("sfnt: unsupported number of hints")
 	errUnsupportedNumberOfTables     = errors.New("sfnt: unsupported number of tables")
@@ -281,8 +286,9 @@
 	// TODO: hdmx, kern, vmtx? Others?
 
 	cached struct {
-		isPostScript bool
-		unitsPerEm   Units
+		indexToLocFormat bool // false means short, true means long.
+		isPostScript     bool
+		unitsPerEm       Units
 
 		// The glyph data for the glyph index i is in
 		// src[locations[i+0]:locations[i+1]].
@@ -388,6 +394,11 @@
 		return errInvalidHeadTable
 	}
 	f.cached.unitsPerEm = Units(u)
+	u, err = f.src.u16(buf, f.head, 50)
+	if err != nil {
+		return err
+	}
+	f.cached.indexToLocFormat = u != 0
 
 	// https://www.microsoft.com/typography/otspec/maxp.htm
 	if f.cached.isPostScript {
@@ -417,8 +428,11 @@
 			return err
 		}
 	} else {
-		// TODO: locaParser for TrueType fonts.
-		f.cached.locations = make([]uint32, numGlyphs+1)
+		f.cached.locations, err = parseLoca(
+			&f.src, f.loca, f.glyf.offset, f.cached.indexToLocFormat, numGlyphs)
+		if err != nil {
+			return err
+		}
 	}
 	if len(f.cached.locations) != numGlyphs+1 {
 		return errInvalidLocationData
@@ -436,6 +450,9 @@
 	}
 	i := f.cached.locations[xx+0]
 	j := f.cached.locations[xx+1]
+	if j-i > maxGlyphDataLength {
+		return nil, errUnsupportedGlyphDataLength
+	}
 	return b.view(&f.src, int(i), int(j-i))
 }
 
@@ -467,7 +484,11 @@
 		}
 		b.segments = b.psi.type2Charstrings.segments
 	} else {
-		return nil, errors.New("sfnt: TODO: load glyf data")
+		segments, err := appendGlyfSegments(b.segments, buf)
+		if err != nil {
+			return nil, err
+		}
+		b.segments = segments
 	}
 
 	// TODO: look at opts to scale / transform / hint the Buffer.segments.
diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go
index f2c2805..4ffd72f 100644
--- a/font/sfnt/sfnt_test.go
+++ b/font/sfnt/sfnt_test.go
@@ -34,6 +34,18 @@
 	}
 }
 
+func quadTo(xa, ya, xb, yb int) Segment {
+	return Segment{
+		Op: SegmentOpQuadTo,
+		Args: [6]fixed.Int26_6{
+			0: fixed.I(xa),
+			1: fixed.I(ya),
+			2: fixed.I(xb),
+			3: fixed.I(yb),
+		},
+	}
+}
+
 func cubeTo(xa, ya, xb, yb, xc, yc int) Segment {
 	return Segment{
 		Op: SegmentOpCubeTo,
@@ -76,16 +88,7 @@
 	}
 }
 
-func TestPostScript(t *testing.T) {
-	data, err := ioutil.ReadFile(filepath.Join("..", "testdata", "CFFTest.otf"))
-	if err != nil {
-		t.Fatal(err)
-	}
-	f, err := Parse(data)
-	if err != nil {
-		t.Fatal(err)
-	}
-
+func TestPostScriptSegments(t *testing.T) {
 	// wants' vectors correspond 1-to-1 to what's in the CFFTest.sfd file,
 	// although OpenType/CFF and FontForge's SFD have reversed orders.
 	// https://fontforge.github.io/validation.html says that "All paths must be
@@ -96,8 +99,8 @@
 	// again when it saves them, of course)."
 	//
 	// The .notdef glyph isn't explicitly in the SFD file, but for some unknown
-	// reason, FontForge generates a .notdef glyph in the OpenType/CFF file.
-	wants := [...][]Segment{{
+	// reason, FontForge generates it in the OpenType/CFF file.
+	wants := [][]Segment{{
 		// .notdef
 		// - contour #0
 		moveTo(50, 0),
@@ -158,8 +161,80 @@
 		lineTo(331, 758),
 		lineTo(243, 752),
 		lineTo(235, 562),
+		// TODO: explicitly (not implicitly) close these contours?
 	}}
 
+	testSegments(t, "CFFTest.otf", wants)
+}
+
+func TestTrueTypeSegments(t *testing.T) {
+	// wants' vectors correspond 1-to-1 to what's in the glyfTest.sfd file,
+	// although FontForge's SFD format stores quadratic Bézier curves as cubics
+	// with duplicated off-curve points. quadTo(bx, by, cx, cy) is stored as
+	// "bx by bx by cx cy".
+	//
+	// The .notdef, .null and nonmarkingreturn glyphs aren't explicitly in the
+	// SFD file, but for some unknown reason, FontForge generates them in the
+	// TrueType file.
+	wants := [][]Segment{{
+		// .notdef
+		// - contour #0
+		moveTo(68, 0),
+		lineTo(68, 1365),
+		lineTo(612, 1365),
+		lineTo(612, 0),
+		lineTo(68, 0),
+		// - contour #1
+		moveTo(136, 68),
+		lineTo(544, 68),
+		lineTo(544, 1297),
+		lineTo(136, 1297),
+		lineTo(136, 68),
+	}, {
+	// .null
+	// Empty glyph.
+	}, {
+	// nonmarkingreturn
+	// Empty glyph.
+	}, {
+		// zero
+		// - contour #0
+		moveTo(614, 1434),
+		quadTo(369, 1434, 369, 614),
+		quadTo(369, 471, 435, 338),
+		quadTo(502, 205, 614, 205),
+		quadTo(860, 205, 860, 1024),
+		quadTo(860, 1167, 793, 1300),
+		quadTo(727, 1434, 614, 1434),
+		// - contour #1
+		moveTo(614, 1638),
+		quadTo(1024, 1638, 1024, 819),
+		quadTo(1024, 0, 614, 0),
+		quadTo(205, 0, 205, 819),
+		quadTo(205, 1638, 614, 1638),
+	}, {
+		// one
+		// - contour #0
+		moveTo(205, 0),
+		lineTo(205, 1638),
+		lineTo(614, 1638),
+		lineTo(614, 0),
+		lineTo(205, 0),
+	}}
+
+	testSegments(t, "glyfTest.ttf", wants)
+}
+
+func testSegments(t *testing.T, filename string, wants [][]Segment) {
+	data, err := ioutil.ReadFile(filepath.Join("..", "testdata", filename))
+	if err != nil {
+		t.Fatal(err)
+	}
+	f, err := Parse(data)
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	if ng := f.NumGlyphs(); ng != len(wants) {
 		t.Fatalf("NumGlyphs: got %d, want %d", ng, len(wants))
 	}
@@ -191,7 +266,7 @@
 	name, err := f.Name(nil, NameIDFamily)
 	if err != nil {
 		t.Errorf("Name: %v", err)
-	} else if want := "CFFTest"; name != want {
+	} else if want := filename[:len(filename)-len(".ttf")]; name != want {
 		t.Errorf("Name:\ngot  %q\nwant %q", name, want)
 	}
 }
diff --git a/font/sfnt/truetype.go b/font/sfnt/truetype.go
new file mode 100644
index 0000000..851904d
--- /dev/null
+++ b/font/sfnt/truetype.go
@@ -0,0 +1,489 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sfnt
+
+import (
+	"golang.org/x/image/math/fixed"
+)
+
+// Flags for simple (non-compound) glyphs.
+//
+// See https://www.microsoft.com/typography/OTSPEC/glyf.htm
+const (
+	flagOnCurve      = 1 << 0 // 0x0001
+	flagXShortVector = 1 << 1 // 0x0002
+	flagYShortVector = 1 << 2 // 0x0004
+	flagRepeat       = 1 << 3 // 0x0008
+
+	// The same flag bits are overloaded to have two meanings, dependent on the
+	// value of the flag{X,Y}ShortVector bits.
+	flagPositiveXShortVector = 1 << 4 // 0x0010
+	flagThisXIsSame          = 1 << 4 // 0x0010
+	flagPositiveYShortVector = 1 << 5 // 0x0020
+	flagThisYIsSame          = 1 << 5 // 0x0020
+)
+
+// Flags for compound glyphs.
+//
+// See https://www.microsoft.com/typography/OTSPEC/glyf.htm
+const (
+	flagArg1And2AreWords        = 1 << 0  // 0x0001
+	flagArgsAreXYValues         = 1 << 1  // 0x0002
+	flagRoundXYToGrid           = 1 << 2  // 0x0004
+	flagWeHaveAScale            = 1 << 3  // 0x0008
+	flagReserved4               = 1 << 4  // 0x0010
+	flagMoreComponents          = 1 << 5  // 0x0020
+	flagWeHaveAnXAndYScale      = 1 << 6  // 0x0040
+	flagWeHaveATwoByTwo         = 1 << 7  // 0x0080
+	flagWeHaveInstructions      = 1 << 8  // 0x0100
+	flagUseMyMetrics            = 1 << 9  // 0x0200
+	flagOverlapCompound         = 1 << 10 // 0x0400
+	flagScaledComponentOffset   = 1 << 11 // 0x0800
+	flagUnscaledComponentOffset = 1 << 12 // 0x1000
+)
+
+func midPoint(p, q fixed.Point26_6) fixed.Point26_6 {
+	return fixed.Point26_6{
+		X: (p.X + q.X) / 2,
+		Y: (p.Y + q.Y) / 2,
+	}
+}
+
+func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool, numGlyphs int) (locations []uint32, err error) {
+	if indexToLocFormat {
+		if loca.length != 4*uint32(numGlyphs+1) {
+			return nil, errInvalidLocaTable
+		}
+	} else {
+		if loca.length != 2*uint32(numGlyphs+1) {
+			return nil, errInvalidLocaTable
+		}
+	}
+
+	locations = make([]uint32, numGlyphs+1)
+	buf, err := src.view(nil, int(loca.offset), int(loca.length))
+	if err != nil {
+		return nil, err
+	}
+
+	if indexToLocFormat {
+		for i := range locations {
+			locations[i] = 1*uint32(u32(buf[4*i:])) + glyfOffset
+		}
+	} else {
+		for i := range locations {
+			locations[i] = 2*uint32(u16(buf[2*i:])) + glyfOffset
+		}
+	}
+	return locations, err
+}
+
+// https://www.microsoft.com/typography/OTSPEC/glyf.htm says that "Each
+// glyph begins with the following [10 byte] header".
+const glyfHeaderLen = 10
+
+// appendGlyfSegments appends to dst the segments encoded in the glyf data.
+func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
+	if len(data) == 0 {
+		return dst, nil
+	}
+	if len(data) < glyfHeaderLen {
+		return nil, errInvalidGlyphData
+	}
+	index := glyfHeaderLen
+
+	numContours, numPoints := int16(u16(data)), 0
+	switch {
+	case numContours == -1:
+		// We have a compound glyph. No-op.
+	case numContours == 0:
+		return dst, nil
+	case numContours > 0:
+		// We have a simple (non-compound) glyph.
+		index += 2 * int(numContours)
+		if index > len(data) {
+			return nil, errInvalidGlyphData
+		}
+		// The +1 for numPoints is because the value in the file format is
+		// inclusive, but Go's slice[:index] semantics are exclusive.
+		numPoints = 1 + int(u16(data[index-2:]))
+	default:
+		return nil, errInvalidGlyphData
+	}
+
+	// Skip the hinting instructions.
+	index += 2
+	if index > len(data) {
+		return nil, errInvalidGlyphData
+	}
+	hintsLength := int(u16(data[index-2:]))
+	index += hintsLength
+	if index > len(data) {
+		return nil, errInvalidGlyphData
+	}
+
+	// TODO: support compound glyphs.
+	if numContours < 0 {
+		return nil, errUnsupportedCompoundGlyph
+	}
+
+	// For simple (non-compound) glyphs, the remainder of the glyf data
+	// consists of (flags, x, y) points: the Bézier curve segments. These are
+	// stored in columns (all the flags first, then all the x co-ordinates,
+	// then all the y co-ordinates), not rows, as it compresses better.
+	//
+	// Decoding those points in row order involves two passes. The first pass
+	// determines the indexes (relative to the data slice) of where the flags,
+	// the x co-ordinates and the y co-ordinates each start.
+	flagIndex := int32(index)
+	xIndex, yIndex, ok := findXYIndexes(data, index, numPoints)
+	if !ok {
+		return nil, errInvalidGlyphData
+	}
+
+	// The second pass decodes each (flags, x, y) tuple in row order.
+	g := glyfIter{
+		data:      data,
+		flagIndex: flagIndex,
+		xIndex:    xIndex,
+		yIndex:    yIndex,
+		endIndex:  glyfHeaderLen,
+		// The -1 is because the contour-end index in the file format is
+		// inclusive, but Go's slice[:index] semantics are exclusive.
+		prevEnd:     -1,
+		numContours: int32(numContours),
+	}
+	for g.nextContour() {
+		for g.nextSegment() {
+			dst = append(dst, g.seg)
+		}
+	}
+	if g.err != nil {
+		return nil, g.err
+	}
+	return dst, nil
+}
+
+func findXYIndexes(data []byte, index, numPoints int) (xIndex, yIndex int32, ok bool) {
+	xDataLen := 0
+	yDataLen := 0
+	for i := 0; ; {
+		if i > numPoints {
+			return 0, 0, false
+		}
+		if i == numPoints {
+			break
+		}
+
+		repeatCount := 1
+		if index >= len(data) {
+			return 0, 0, false
+		}
+		flag := data[index]
+		index++
+		if flag&flagRepeat != 0 {
+			if index >= len(data) {
+				return 0, 0, false
+			}
+			repeatCount += int(data[index])
+			index++
+		}
+
+		xSize := 0
+		if flag&flagXShortVector != 0 {
+			xSize = 1
+		} else if flag&flagThisXIsSame == 0 {
+			xSize = 2
+		}
+		xDataLen += xSize * repeatCount
+
+		ySize := 0
+		if flag&flagYShortVector != 0 {
+			ySize = 1
+		} else if flag&flagThisYIsSame == 0 {
+			ySize = 2
+		}
+		yDataLen += ySize * repeatCount
+
+		i += repeatCount
+	}
+	if index+xDataLen+yDataLen > len(data) {
+		return 0, 0, false
+	}
+	return int32(index), int32(index + xDataLen), true
+}
+
+type glyfIter struct {
+	data []byte
+	err  error
+
+	// Various indices into the data slice. See the "Decoding those points in
+	// row order" comment above.
+	flagIndex int32
+	xIndex    int32
+	yIndex    int32
+
+	// endIndex points to the uint16 that is the inclusive point index of the
+	// current contour's end. prevEnd is the previous contour's end.
+	endIndex int32
+	prevEnd  int32
+
+	// c and p count the current contour and point, up to numContours and
+	// numPoints.
+	c, numContours int32
+	p, nPoints     int32
+
+	// The next two groups of fields track points and segments. Points are what
+	// the underlying file format provides. Bézier curve segments are what the
+	// rasterizer consumes.
+	//
+	// Points are either on-curve or off-curve. Two consecutive on-curve points
+	// define a linear curve segment between them. N off-curve points between
+	// on-curve points define N quadratic curve segments. The TrueType glyf
+	// format does not use cubic curves. If N is greater than 1, some of these
+	// segment end points are implicit, the midpoint of two off-curve points.
+	// Given the points A, B1, B2, ..., BN, C, where A and C are on-curve and
+	// all the Bs are off-curve, the segments are:
+	//
+	//	- A,                  B1, midpoint(B1, B2)
+	//	- midpoint(B1, B2),   B2, midpoint(B2, B3)
+	//	- midpoint(B2, B3),   B3, midpoint(B3, B4)
+	//	- ...
+	//	- midpoint(BN-1, BN), BN, C
+	//
+	// Note that the sequence of Bs may wrap around from the last point in the
+	// glyf data to the first. A and C may also be the same point (the only
+	// explicit on-curve point), or there may be no explicit on-curve points at
+	// all (but still implicit ones between explicit off-curve points).
+
+	// Points.
+	x, y    int16
+	on      bool
+	flag    uint8
+	repeats uint8
+
+	// Segments.
+	closing            bool
+	closed             bool
+	firstOnCurveValid  bool
+	firstOffCurveValid bool
+	lastOffCurveValid  bool
+	firstOnCurve       fixed.Point26_6
+	firstOffCurve      fixed.Point26_6
+	lastOffCurve       fixed.Point26_6
+	seg                Segment
+}
+
+func (g *glyfIter) nextContour() (ok bool) {
+	if g.c == g.numContours {
+		return false
+	}
+	g.c++
+
+	end := int32(u16(g.data[g.endIndex:]))
+	g.endIndex += 2
+	if end <= g.prevEnd {
+		g.err = errInvalidGlyphData
+		return false
+	}
+	g.nPoints = end - g.prevEnd
+	g.p = 0
+	g.prevEnd = end
+
+	g.closing = false
+	g.closed = false
+	g.firstOnCurveValid = false
+	g.firstOffCurveValid = false
+	g.lastOffCurveValid = false
+
+	return true
+}
+
+func (g *glyfIter) close() {
+	switch {
+	case !g.firstOffCurveValid && !g.lastOffCurveValid:
+		g.closed = true
+		g.seg = Segment{
+			Op: SegmentOpLineTo,
+			Args: [6]fixed.Int26_6{
+				g.firstOnCurve.X,
+				g.firstOnCurve.Y,
+			},
+		}
+	case !g.firstOffCurveValid && g.lastOffCurveValid:
+		g.closed = true
+		g.seg = Segment{
+			Op: SegmentOpQuadTo,
+			Args: [6]fixed.Int26_6{
+				g.lastOffCurve.X,
+				g.lastOffCurve.Y,
+				g.firstOnCurve.X,
+				g.firstOnCurve.Y,
+			},
+		}
+	case g.firstOffCurveValid && !g.lastOffCurveValid:
+		g.closed = true
+		g.seg = Segment{
+			Op: SegmentOpQuadTo,
+			Args: [6]fixed.Int26_6{
+				g.firstOffCurve.X,
+				g.firstOffCurve.Y,
+				g.firstOnCurve.X,
+				g.firstOnCurve.Y,
+			},
+		}
+	case g.firstOffCurveValid && g.lastOffCurveValid:
+		mid := midPoint(g.lastOffCurve, g.firstOffCurve)
+		g.lastOffCurveValid = false
+		g.seg = Segment{
+			Op: SegmentOpQuadTo,
+			Args: [6]fixed.Int26_6{
+				g.lastOffCurve.X,
+				g.lastOffCurve.Y,
+				mid.X,
+				mid.Y,
+			},
+		}
+	}
+}
+
+func (g *glyfIter) nextSegment() (ok bool) {
+	for !g.closed {
+		if g.closing || !g.nextPoint() {
+			g.closing = true
+			g.close()
+			return true
+		}
+
+		p := fixed.Point26_6{
+			X: fixed.Int26_6(g.x) << 6,
+			Y: fixed.Int26_6(g.y) << 6,
+		}
+
+		if !g.firstOnCurveValid {
+			if g.on {
+				g.firstOnCurve = p
+				g.firstOnCurveValid = true
+				g.seg = Segment{
+					Op: SegmentOpMoveTo,
+					Args: [6]fixed.Int26_6{
+						p.X,
+						p.Y,
+					},
+				}
+				return true
+			} else if !g.firstOffCurveValid {
+				g.firstOffCurve = p
+				g.firstOffCurveValid = true
+				continue
+			} else {
+				midp := midPoint(g.firstOffCurve, p)
+				g.firstOnCurve = midp
+				g.firstOnCurveValid = true
+				g.lastOffCurve = p
+				g.lastOffCurveValid = true
+				g.seg = Segment{
+					Op: SegmentOpMoveTo,
+					Args: [6]fixed.Int26_6{
+						midp.X,
+						midp.Y,
+					},
+				}
+				return true
+			}
+
+		} else if !g.lastOffCurveValid {
+			if !g.on {
+				g.lastOffCurve = p
+				g.lastOffCurveValid = true
+				continue
+			} else {
+				g.seg = Segment{
+					Op: SegmentOpLineTo,
+					Args: [6]fixed.Int26_6{
+						p.X,
+						p.Y,
+					},
+				}
+				return true
+			}
+
+		} else {
+			if !g.on {
+				midp := midPoint(g.lastOffCurve, p)
+				g.seg = Segment{
+					Op: SegmentOpQuadTo,
+					Args: [6]fixed.Int26_6{
+						g.lastOffCurve.X,
+						g.lastOffCurve.Y,
+						midp.X,
+						midp.Y,
+					},
+				}
+				g.lastOffCurve = p
+				g.lastOffCurveValid = true
+				return true
+			} else {
+				g.seg = Segment{
+					Op: SegmentOpQuadTo,
+					Args: [6]fixed.Int26_6{
+						g.lastOffCurve.X,
+						g.lastOffCurve.Y,
+						p.X,
+						p.Y,
+					},
+				}
+				g.lastOffCurveValid = false
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func (g *glyfIter) nextPoint() (ok bool) {
+	if g.p == g.nPoints {
+		return false
+	}
+	g.p++
+
+	if g.repeats > 0 {
+		g.repeats--
+	} else {
+		g.flag = g.data[g.flagIndex]
+		g.flagIndex++
+		if g.flag&flagRepeat != 0 {
+			g.repeats = g.data[g.flagIndex]
+			g.flagIndex++
+		}
+	}
+
+	if g.flag&flagXShortVector != 0 {
+		if g.flag&flagPositiveXShortVector != 0 {
+			g.x += int16(g.data[g.xIndex])
+		} else {
+			g.x -= int16(g.data[g.xIndex])
+		}
+		g.xIndex += 1
+	} else if g.flag&flagThisXIsSame == 0 {
+		g.x += int16(u16(g.data[g.xIndex:]))
+		g.xIndex += 2
+	}
+
+	if g.flag&flagYShortVector != 0 {
+		if g.flag&flagPositiveYShortVector != 0 {
+			g.y += int16(g.data[g.yIndex])
+		} else {
+			g.y -= int16(g.data[g.yIndex])
+		}
+		g.yIndex += 1
+	} else if g.flag&flagThisYIsSame == 0 {
+		g.y += int16(u16(g.data[g.yIndex:]))
+		g.yIndex += 2
+	}
+
+	g.on = g.flag&flagOnCurve != 0
+	return true
+}
diff --git a/font/testdata/glyfTest.sfd b/font/testdata/glyfTest.sfd
new file mode 100644
index 0000000..e61c54c
--- /dev/null
+++ b/font/testdata/glyfTest.sfd
@@ -0,0 +1,102 @@
+SplineFontDB: 3.0
+FontName: glyfTest
+FullName: glyfTest
+FamilyName: glyfTest
+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: -11.25
+UnderlinePosition: -204
+UnderlineWidth: 102
+Ascent: 1638
+Descent: 410
+LayerCount: 2
+Layer: 0 1 "Back"  1
+Layer: 1 1 "Fore"  0
+XUID: [1021 367 888937226 7862908]
+FSType: 8
+OS2Version: 0
+OS2_WeightWidthSlopeOnly: 0
+OS2_UseTypoMetrics: 1
+CreationTime: 1484386143
+ModificationTime: 1484386143
+PfmFamily: 17
+TTFWeight: 400
+TTFWidth: 5
+LineGap: 184
+VLineGap: 0
+OS2TypoAscent: 0
+OS2TypoAOffset: 1
+OS2TypoDescent: 0
+OS2TypoDOffset: 1
+OS2TypoLinegap: 184
+OS2WinAscent: 0
+OS2WinAOffset: 1
+OS2WinDescent: 0
+OS2WinDOffset: 1
+HheadAscent: 0
+HheadAOffset: 1
+HheadDescent: 0
+HheadDOffset: 1
+OS2Vendor: 'PfEd'
+MarkAttachClasses: 1
+DEI: 91125
+LangName: 1033 
+Encoding: UnicodeBmp
+UnicodeInterp: none
+NameList: Adobe Glyph List
+DisplaySize: -24
+AntiAlias: 1
+FitToEm: 1
+WinInfo: 0 32 23
+BeginPrivate: 0
+EndPrivate
+TeXData: 1 0 0 346030 173015 115343 0 -1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144
+BeginChars: 65536 2
+
+StartChar: zero
+Encoding: 48 48 0
+Width: 1228
+VWidth: 0
+Flags: W
+HStem: 0 205<508 700> 1434 205<529 720>
+VStem: 205 164<500 1088> 860 164<550 1139>
+LayerCount: 2
+Fore
+SplineSet
+614 1434 m 0,0,1
+ 369 1434 369 1434 369 614 c 0,2,3
+ 369 471 369 471 435 338 c 0,4,5
+ 502 205 502 205 614 205 c 0,6,7
+ 860 205 860 205 860 1024 c 0,8,9
+ 860 1167 860 1167 793 1300 c 0,10,11
+ 727 1434 727 1434 614 1434 c 0,0,1
+614 1638 m 0,12,13
+ 1024 1638 1024 1638 1024 819 c 128,-1,14
+ 1024 0 1024 0 614 0 c 0,15,16
+ 205 0 205 0 205 819 c 128,-1,17
+ 205 1638 205 1638 614 1638 c 0,12,13
+EndSplineSet
+Validated: 1
+EndChar
+
+StartChar: one
+Encoding: 49 49 1
+Width: 819
+VWidth: 0
+Flags: W
+HStem: 0 43G<205 614>
+VStem: 205 410<0 1638>
+LayerCount: 2
+Fore
+SplineSet
+205 0 m 25,0,-1
+ 205 1638 l 1,1,-1
+ 614 1638 l 1,2,-1
+ 614 0 l 1,3,-1
+ 205 0 l 25,0,-1
+EndSplineSet
+Validated: 1
+EndChar
+EndChars
+EndSplineFont
diff --git a/font/testdata/glyfTest.ttf b/font/testdata/glyfTest.ttf
new file mode 100644
index 0000000..587b3fe
--- /dev/null
+++ b/font/testdata/glyfTest.ttf
Binary files differ