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)