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