font/sfnt: explicitly close glyph contours.

Change-Id: I4a59167cfe5d84f0ef6732711cca9b46a52b445c
Reviewed-on: https://go-review.googlesource.com/39930
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/example_test.go b/font/sfnt/example_test.go
index 63379eb..baddcfe 100644
--- a/font/sfnt/example_test.go
+++ b/font/sfnt/example_test.go
@@ -20,9 +20,9 @@
 	const (
 		ppem    = 32
 		width   = 24
-		height  = 32
+		height  = 36
 		originX = 0
-		originY = 28
+		originY = 32
 	)
 
 	f, err := sfnt.Parse(goregular.TTF)
@@ -30,12 +30,12 @@
 		log.Fatalf("Parse: %v", err)
 	}
 	var b sfnt.Buffer
-	x, err := f.GlyphIndex(&b, 'G')
+	x, err := f.GlyphIndex(&b, 'Ġ')
 	if err != nil {
 		log.Fatalf("GlyphIndex: %v", err)
 	}
 	if x == 0 {
-		log.Fatalf("GlyphIndex: no glyph index found for the rune 'G'")
+		log.Fatalf("GlyphIndex: no glyph index found for the rune 'Ġ'")
 	}
 	segments, err := f.LoadGlyph(&b, x, fixed.I(ppem), nil)
 	if err != nil {
@@ -76,7 +76,6 @@
 			)
 		}
 	}
-	// TODO: call ClosePath? Once overall or once per contour (i.e. MoveTo)?
 
 	dst := image.NewAlpha(image.Rect(0, 0, width, height))
 	r.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
@@ -96,6 +95,10 @@
 	// ........................
 	// ........................
 	// ........................
+	// ............888.........
+	// ............888.........
+	// ............888.........
+	// ............+++.........
 	// ........................
 	// ..........+++++++++.....
 	// .......+8888888888888+..
diff --git a/font/sfnt/postscript.go b/font/sfnt/postscript.go
index f166b6c..7c5db78 100644
--- a/font/sfnt/postscript.go
+++ b/font/sfnt/postscript.go
@@ -531,7 +531,10 @@
 type psType2CharstringsData struct {
 	f          *Font
 	b          *Buffer
-	x, y       int32
+	x          int32
+	y          int32
+	firstX     int32
+	firstY     int32
 	hintBits   int32
 	seenWidth  bool
 	ended      bool
@@ -550,6 +553,64 @@
 	}
 }
 
+func (d *psType2CharstringsData) closePath() {
+	if d.x != d.firstX || d.y != d.firstY {
+		d.b.segments = append(d.b.segments, Segment{
+			Op: SegmentOpLineTo,
+			Args: [3]fixed.Point26_6{{
+				X: fixed.Int26_6(d.firstX),
+				Y: fixed.Int26_6(d.firstY),
+			}},
+		})
+	}
+}
+
+func (d *psType2CharstringsData) moveTo(dx, dy int32) {
+	d.closePath()
+	d.x += dx
+	d.y += dy
+	d.b.segments = append(d.b.segments, Segment{
+		Op: SegmentOpMoveTo,
+		Args: [3]fixed.Point26_6{{
+			X: fixed.Int26_6(d.x),
+			Y: fixed.Int26_6(d.y),
+		}},
+	})
+	d.firstX = d.x
+	d.firstY = d.y
+}
+
+func (d *psType2CharstringsData) lineTo(dx, dy int32) {
+	d.x += dx
+	d.y += dy
+	d.b.segments = append(d.b.segments, Segment{
+		Op: SegmentOpLineTo,
+		Args: [3]fixed.Point26_6{{
+			X: fixed.Int26_6(d.x),
+			Y: fixed.Int26_6(d.y),
+		}},
+	})
+}
+
+func (d *psType2CharstringsData) cubeTo(dxa, dya, dxb, dyb, dxc, dyc int32) {
+	d.x += dxa
+	d.y += dya
+	xa := fixed.Int26_6(d.x)
+	ya := fixed.Int26_6(d.y)
+	d.x += dxb
+	d.y += dyb
+	xb := fixed.Int26_6(d.x)
+	yb := fixed.Int26_6(d.y)
+	d.x += dxc
+	d.y += dyc
+	xc := fixed.Int26_6(d.x)
+	yc := fixed.Int26_6(d.y)
+	d.b.segments = append(d.b.segments, Segment{
+		Op:   SegmentOpCubeTo,
+		Args: [3]fixed.Point26_6{{X: xa, Y: ya}, {X: xb, Y: yb}, {X: xc, Y: yc}},
+	})
+}
+
 // psInterpreter is a PostScript interpreter.
 type psInterpreter struct {
 	ctx          psContext
@@ -996,52 +1057,12 @@
 	return nil
 }
 
-func t2CAppendMoveto(p *psInterpreter) {
-	p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{
-		Op: SegmentOpMoveTo,
-		Args: [3]fixed.Point26_6{{
-			X: fixed.Int26_6(p.type2Charstrings.x),
-			Y: fixed.Int26_6(p.type2Charstrings.y),
-		}},
-	})
-}
-
-func t2CAppendLineto(p *psInterpreter) {
-	p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{
-		Op: SegmentOpLineTo,
-		Args: [3]fixed.Point26_6{{
-			X: fixed.Int26_6(p.type2Charstrings.x),
-			Y: fixed.Int26_6(p.type2Charstrings.y),
-		}},
-	})
-}
-
-func t2CAppendCubeto(p *psInterpreter, dxa, dya, dxb, dyb, dxc, dyc int32) {
-	p.type2Charstrings.x += dxa
-	p.type2Charstrings.y += dya
-	xa := fixed.Int26_6(p.type2Charstrings.x)
-	ya := fixed.Int26_6(p.type2Charstrings.y)
-	p.type2Charstrings.x += dxb
-	p.type2Charstrings.y += dyb
-	xb := fixed.Int26_6(p.type2Charstrings.x)
-	yb := fixed.Int26_6(p.type2Charstrings.y)
-	p.type2Charstrings.x += dxc
-	p.type2Charstrings.y += dyc
-	xc := fixed.Int26_6(p.type2Charstrings.x)
-	yc := fixed.Int26_6(p.type2Charstrings.y)
-	p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{
-		Op:   SegmentOpCubeTo,
-		Args: [3]fixed.Point26_6{{X: xa, Y: ya}, {X: xb, Y: yb}, {X: xc, Y: yc}},
-	})
-}
-
 func t2CHmoveto(p *psInterpreter) error {
 	t2CReadWidth(p, 1)
 	if p.argStack.top != 1 {
 		return errInvalidCFFTable
 	}
-	p.type2Charstrings.x += p.argStack.a[0]
-	t2CAppendMoveto(p)
+	p.type2Charstrings.moveTo(p.argStack.a[0], 0)
 	return nil
 }
 
@@ -1050,8 +1071,7 @@
 	if p.argStack.top != 1 {
 		return errInvalidCFFTable
 	}
-	p.type2Charstrings.y += p.argStack.a[0]
-	t2CAppendMoveto(p)
+	p.type2Charstrings.moveTo(0, p.argStack.a[0])
 	return nil
 }
 
@@ -1060,9 +1080,7 @@
 	if p.argStack.top != 2 {
 		return errInvalidCFFTable
 	}
-	p.type2Charstrings.x += p.argStack.a[0]
-	p.type2Charstrings.y += p.argStack.a[1]
-	t2CAppendMoveto(p)
+	p.type2Charstrings.moveTo(p.argStack.a[0], p.argStack.a[1])
 	return nil
 }
 
@@ -1074,12 +1092,11 @@
 		return errInvalidCFFTable
 	}
 	for i := int32(0); i < p.argStack.top; i, vertical = i+1, !vertical {
+		dx, dy := p.argStack.a[i], int32(0)
 		if vertical {
-			p.type2Charstrings.y += p.argStack.a[i]
-		} else {
-			p.type2Charstrings.x += p.argStack.a[i]
+			dx, dy = dy, dx
 		}
-		t2CAppendLineto(p)
+		p.type2Charstrings.lineTo(dx, dy)
 	}
 	return nil
 }
@@ -1089,9 +1106,7 @@
 		return errInvalidCFFTable
 	}
 	for i := int32(0); i < p.argStack.top; i += 2 {
-		p.type2Charstrings.x += p.argStack.a[i+0]
-		p.type2Charstrings.y += p.argStack.a[i+1]
-		t2CAppendLineto(p)
+		p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
 	}
 	return nil
 }
@@ -1110,7 +1125,7 @@
 	}
 	i := int32(0)
 	for iMax := p.argStack.top - 2; i < iMax; i += 6 {
-		t2CAppendCubeto(p,
+		p.type2Charstrings.cubeTo(
 			p.argStack.a[i+0],
 			p.argStack.a[i+1],
 			p.argStack.a[i+2],
@@ -1119,9 +1134,7 @@
 			p.argStack.a[i+5],
 		)
 	}
-	p.type2Charstrings.x += p.argStack.a[i+0]
-	p.type2Charstrings.y += p.argStack.a[i+1]
-	t2CAppendLineto(p)
+	p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
 	return nil
 }
 
@@ -1131,11 +1144,9 @@
 	}
 	i := int32(0)
 	for iMax := p.argStack.top - 6; i < iMax; i += 2 {
-		p.type2Charstrings.x += p.argStack.a[i+0]
-		p.type2Charstrings.y += p.argStack.a[i+1]
-		t2CAppendLineto(p)
+		p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
 	}
-	t2CAppendCubeto(p,
+	p.type2Charstrings.cubeTo(
 		p.argStack.a[i+0],
 		p.argStack.a[i+1],
 		p.argStack.a[i+2],
@@ -1239,7 +1250,7 @@
 		dxc, dyc = dyc, dxc
 	}
 
-	t2CAppendCubeto(p, dxa, dya, dxb, dyb, dxc, dyc)
+	p.type2Charstrings.cubeTo(dxa, dya, dxb, dyb, dxc, dyc)
 	return i
 }
 
@@ -1248,7 +1259,7 @@
 		return errInvalidCFFTable
 	}
 	for i := int32(0); i != p.argStack.top; i += 6 {
-		t2CAppendCubeto(p,
+		p.type2Charstrings.cubeTo(
 			p.argStack.a[i+0],
 			p.argStack.a[i+1],
 			p.argStack.a[i+2],
@@ -1358,6 +1369,7 @@
 		}
 		return errInvalidCFFTable
 	}
+	p.type2Charstrings.closePath()
 	p.type2Charstrings.ended = true
 	return nil
 }
diff --git a/font/sfnt/proprietary_test.go b/font/sfnt/proprietary_test.go
index 3b57ecd..f0917d6 100644
--- a/font/sfnt/proprietary_test.go
+++ b/font/sfnt/proprietary_test.go
@@ -493,6 +493,7 @@
 			// 106 callsubr # 106 + bias = 213
 			// :	# Arg stack is [].
 			// :	43 -658 rmoveto
+			lineTo(130, 221),
 			moveTo(161, -13),
 			// :	37 29 28 41 return
 			// :	# Arg stack is [37 29 28 41].
@@ -520,12 +521,14 @@
 			lineTo(857, 614),
 			lineTo(857, 693),
 			// -797 -589 rmoveto
+			lineTo(144, 693),
 			moveTo(60, 104),
 			// -81 881 81 vlineto
 			lineTo(60, 23),
 			lineTo(941, 23),
 			lineTo(941, 104),
 			// endchar
+			lineTo(60, 104),
 		},
 	},
 
@@ -545,6 +548,7 @@
 			// 1 -53 -34 -44 -57 -25 rrcurveto
 			cubeTo(138, -53, 104, -97, 47, -122),
 			// endchar
+			lineTo(67, -170),
 		},
 
 		'Q': {
@@ -615,6 +619,7 @@
 			// 113 91 15 callgsubr # 15 + bias = 122
 			// :	# Arg stack is [113 91].
 			// :	rmoveto
+			lineTo(82, 0),
 			moveTo(195, 577),
 			// :	69 29 58 77 3 hvcurveto
 			cubeTo(264, 577, 293, 635, 296, 712),
@@ -681,12 +686,14 @@
 			// -92 115 -60 callgsubr # -60 + bias = 47
 			// :	# Arg stack is [-92 115].
 			// :	rmoveto
+			lineTo(82, 0),
 			moveTo(-10, 601),
 			// :	266 57 -266 hlineto
 			lineTo(256, 601),
 			lineTo(256, 658),
 			lineTo(-10, 658),
 			// :	endchar
+			lineTo(-10, 601),
 		},
 
 		'ĭ': { // U+012D LATIN SMALL LETTER I WITH BREVE
@@ -717,6 +724,7 @@
 			// 42 85 143 callsubr # 143 + bias = 250
 			// :	# Arg stack is [42 85].
 			// :	rmoveto
+			lineTo(82, 0),
 			moveTo(124, 571),
 			// :	-84 callsubr # -84 + bias = 23
 			// :	:	# Arg stack is [].
@@ -765,6 +773,7 @@
 			// -96 hlineto
 			lineTo(209, 656),
 			// endchar
+			lineTo(0, 0),
 		},
 
 		'Ḫ': { // U+1E2A LATIN CAPITAL LETTER H WITH BREVE BELOW
@@ -812,6 +821,7 @@
 			// 235 -887 143 callsubr # 143 + bias = 250
 			// :	# Arg stack is [235 -887].
 			// :	rmoveto
+			lineTo(90, 0),
 			moveTo(325, -231),
 			// :	-84 callsubr # -84 + bias = 23
 			// :	:	# Arg stack is [].
diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go
index e27fa2b..74de278 100644
--- a/font/sfnt/sfnt_test.go
+++ b/font/sfnt/sfnt_test.go
@@ -81,7 +81,42 @@
 				i, g, w, got, want)
 		}
 	}
-	return nil
+
+	// Check that every contour is closed.
+	if len(got) == 0 {
+		return nil
+	}
+	if got[0].Op != SegmentOpMoveTo {
+		return fmt.Errorf("segments do not start with a moveTo")
+	}
+	var (
+		first, last fixed.Point26_6
+		firstI      int
+	)
+	checkClosed := func(lastI int) error {
+		if first != last {
+			return fmt.Errorf("segments[%d:%d] not closed:\nfirst %v\nlast  %v", firstI, lastI, first, last)
+		}
+		return nil
+	}
+	for i, g := range got {
+		switch g.Op {
+		case SegmentOpMoveTo:
+			if i != 0 {
+				if err := checkClosed(i); err != nil {
+					return err
+				}
+			}
+			firstI, first, last = i, g.Args[0], g.Args[0]
+		case SegmentOpLineTo:
+			last = g.Args[0]
+		case SegmentOpQuadTo:
+			last = g.Args[1]
+		case SegmentOpCubeTo:
+			last = g.Args[2]
+		}
+	}
+	return checkClosed(len(got))
 }
 
 func TestTrueTypeParse(t *testing.T) {
@@ -416,11 +451,13 @@
 		lineTo(450, 0),
 		lineTo(450, 533),
 		lineTo(50, 533),
+		lineTo(50, 0),
 		// - contour #1
 		moveTo(100, 50),
 		lineTo(100, 483),
 		lineTo(400, 483),
 		lineTo(400, 50),
+		lineTo(100, 50),
 	}, {
 		// zero
 		// - contour #0
@@ -442,12 +479,14 @@
 		lineTo(300, 0),
 		lineTo(300, 800),
 		lineTo(100, 800),
+		lineTo(100, 0),
 	}, {
 		// Q
 		// - contour #0
 		moveTo(657, 237),
 		lineTo(289, 387),
 		lineTo(519, 615),
+		lineTo(657, 237),
 		// - contour #1
 		moveTo(792, 169),
 		cubeTo(867, 263, 926, 502, 791, 665),
@@ -456,6 +495,7 @@
 		cubeTo(369, -39, 641, 18, 722, 93),
 		lineTo(802, 3),
 		lineTo(864, 83),
+		lineTo(792, 169),
 	}, {
 		// uni4E2D
 		// - contour #0
@@ -470,7 +510,7 @@
 		lineTo(331, 758),
 		lineTo(243, 752),
 		lineTo(235, 562),
-		// TODO: explicitly (not implicitly) close these contours?
+		lineTo(141, 520),
 	}}
 
 	testSegments(t, "CFFTest.otf", wants)