font/sfnt: explicitly close glyph contours.

Change-Id: I4a59167cfe5d84f0ef6732711cca9b46a52b445c
Reviewed-on: https://go-review.googlesource.com/39930
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Nigel Tao 2017-04-07 15:46:26 +10:00
parent 1de9a5bb2a
commit 7c3fafc74f
4 changed files with 136 additions and 71 deletions

View File

@ -20,9 +20,9 @@ func ExampleRasterizeGlyph() {
const ( const (
ppem = 32 ppem = 32
width = 24 width = 24
height = 32 height = 36
originX = 0 originX = 0
originY = 28 originY = 32
) )
f, err := sfnt.Parse(goregular.TTF) f, err := sfnt.Parse(goregular.TTF)
@ -30,12 +30,12 @@ func ExampleRasterizeGlyph() {
log.Fatalf("Parse: %v", err) log.Fatalf("Parse: %v", err)
} }
var b sfnt.Buffer var b sfnt.Buffer
x, err := f.GlyphIndex(&b, 'G') x, err := f.GlyphIndex(&b, 'Ġ')
if err != nil { if err != nil {
log.Fatalf("GlyphIndex: %v", err) log.Fatalf("GlyphIndex: %v", err)
} }
if x == 0 { 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) segments, err := f.LoadGlyph(&b, x, fixed.I(ppem), nil)
if err != nil { if err != nil {
@ -76,7 +76,6 @@ func ExampleRasterizeGlyph() {
) )
} }
} }
// TODO: call ClosePath? Once overall or once per contour (i.e. MoveTo)?
dst := image.NewAlpha(image.Rect(0, 0, width, height)) dst := image.NewAlpha(image.Rect(0, 0, width, height))
r.Draw(dst, dst.Bounds(), image.Opaque, image.Point{}) r.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
@ -96,6 +95,10 @@ func ExampleRasterizeGlyph() {
// ........................ // ........................
// ........................ // ........................
// ........................ // ........................
// ............888.........
// ............888.........
// ............888.........
// ............+++.........
// ........................ // ........................
// ..........+++++++++..... // ..........+++++++++.....
// .......+8888888888888+.. // .......+8888888888888+..

View File

@ -531,7 +531,10 @@ func (d *psPrivateDictData) initialize() {
type psType2CharstringsData struct { type psType2CharstringsData struct {
f *Font f *Font
b *Buffer b *Buffer
x, y int32 x int32
y int32
firstX int32
firstY int32
hintBits int32 hintBits int32
seenWidth bool seenWidth bool
ended bool ended bool
@ -550,6 +553,64 @@ func (d *psType2CharstringsData) initialize(f *Font, b *Buffer, glyphIndex Glyph
} }
} }
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. // psInterpreter is a PostScript interpreter.
type psInterpreter struct { type psInterpreter struct {
ctx psContext ctx psContext
@ -996,52 +1057,12 @@ func t2CMask(p *psInterpreter) error {
return nil 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 { func t2CHmoveto(p *psInterpreter) error {
t2CReadWidth(p, 1) t2CReadWidth(p, 1)
if p.argStack.top != 1 { if p.argStack.top != 1 {
return errInvalidCFFTable return errInvalidCFFTable
} }
p.type2Charstrings.x += p.argStack.a[0] p.type2Charstrings.moveTo(p.argStack.a[0], 0)
t2CAppendMoveto(p)
return nil return nil
} }
@ -1050,8 +1071,7 @@ func t2CVmoveto(p *psInterpreter) error {
if p.argStack.top != 1 { if p.argStack.top != 1 {
return errInvalidCFFTable return errInvalidCFFTable
} }
p.type2Charstrings.y += p.argStack.a[0] p.type2Charstrings.moveTo(0, p.argStack.a[0])
t2CAppendMoveto(p)
return nil return nil
} }
@ -1060,9 +1080,7 @@ func t2CRmoveto(p *psInterpreter) error {
if p.argStack.top != 2 { if p.argStack.top != 2 {
return errInvalidCFFTable return errInvalidCFFTable
} }
p.type2Charstrings.x += p.argStack.a[0] p.type2Charstrings.moveTo(p.argStack.a[0], p.argStack.a[1])
p.type2Charstrings.y += p.argStack.a[1]
t2CAppendMoveto(p)
return nil return nil
} }
@ -1074,12 +1092,11 @@ func t2CLineto(p *psInterpreter, vertical bool) error {
return errInvalidCFFTable return errInvalidCFFTable
} }
for i := int32(0); i < p.argStack.top; i, vertical = i+1, !vertical { for i := int32(0); i < p.argStack.top; i, vertical = i+1, !vertical {
dx, dy := p.argStack.a[i], int32(0)
if vertical { if vertical {
p.type2Charstrings.y += p.argStack.a[i] dx, dy = dy, dx
} else {
p.type2Charstrings.x += p.argStack.a[i]
} }
t2CAppendLineto(p) p.type2Charstrings.lineTo(dx, dy)
} }
return nil return nil
} }
@ -1089,9 +1106,7 @@ func t2CRlineto(p *psInterpreter) error {
return errInvalidCFFTable return errInvalidCFFTable
} }
for i := int32(0); i < p.argStack.top; i += 2 { for i := int32(0); i < p.argStack.top; i += 2 {
p.type2Charstrings.x += p.argStack.a[i+0] p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
p.type2Charstrings.y += p.argStack.a[i+1]
t2CAppendLineto(p)
} }
return nil return nil
} }
@ -1110,7 +1125,7 @@ func t2CRcurveline(p *psInterpreter) error {
} }
i := int32(0) i := int32(0)
for iMax := p.argStack.top - 2; i < iMax; i += 6 { for iMax := p.argStack.top - 2; i < iMax; i += 6 {
t2CAppendCubeto(p, p.type2Charstrings.cubeTo(
p.argStack.a[i+0], p.argStack.a[i+0],
p.argStack.a[i+1], p.argStack.a[i+1],
p.argStack.a[i+2], p.argStack.a[i+2],
@ -1119,9 +1134,7 @@ func t2CRcurveline(p *psInterpreter) error {
p.argStack.a[i+5], p.argStack.a[i+5],
) )
} }
p.type2Charstrings.x += p.argStack.a[i+0] p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
p.type2Charstrings.y += p.argStack.a[i+1]
t2CAppendLineto(p)
return nil return nil
} }
@ -1131,11 +1144,9 @@ func t2CRlinecurve(p *psInterpreter) error {
} }
i := int32(0) i := int32(0)
for iMax := p.argStack.top - 6; i < iMax; i += 2 { for iMax := p.argStack.top - 6; i < iMax; i += 2 {
p.type2Charstrings.x += p.argStack.a[i+0] p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
p.type2Charstrings.y += p.argStack.a[i+1]
t2CAppendLineto(p)
} }
t2CAppendCubeto(p, p.type2Charstrings.cubeTo(
p.argStack.a[i+0], p.argStack.a[i+0],
p.argStack.a[i+1], p.argStack.a[i+1],
p.argStack.a[i+2], p.argStack.a[i+2],
@ -1239,7 +1250,7 @@ func t2CCurveto4(p *psInterpreter, swap bool, vertical bool, i int32) (j int32)
dxc, dyc = dyc, dxc dxc, dyc = dyc, dxc
} }
t2CAppendCubeto(p, dxa, dya, dxb, dyb, dxc, dyc) p.type2Charstrings.cubeTo(dxa, dya, dxb, dyb, dxc, dyc)
return i return i
} }
@ -1248,7 +1259,7 @@ func t2CRrcurveto(p *psInterpreter) error {
return errInvalidCFFTable return errInvalidCFFTable
} }
for i := int32(0); i != p.argStack.top; i += 6 { for i := int32(0); i != p.argStack.top; i += 6 {
t2CAppendCubeto(p, p.type2Charstrings.cubeTo(
p.argStack.a[i+0], p.argStack.a[i+0],
p.argStack.a[i+1], p.argStack.a[i+1],
p.argStack.a[i+2], p.argStack.a[i+2],
@ -1358,6 +1369,7 @@ func t2CEndchar(p *psInterpreter) error {
} }
return errInvalidCFFTable return errInvalidCFFTable
} }
p.type2Charstrings.closePath()
p.type2Charstrings.ended = true p.type2Charstrings.ended = true
return nil return nil
} }

View File

@ -493,6 +493,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// 106 callsubr # 106 + bias = 213 // 106 callsubr # 106 + bias = 213
// : # Arg stack is []. // : # Arg stack is [].
// : 43 -658 rmoveto // : 43 -658 rmoveto
lineTo(130, 221),
moveTo(161, -13), moveTo(161, -13),
// : 37 29 28 41 return // : 37 29 28 41 return
// : # Arg stack is [37 29 28 41]. // : # Arg stack is [37 29 28 41].
@ -520,12 +521,14 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
lineTo(857, 614), lineTo(857, 614),
lineTo(857, 693), lineTo(857, 693),
// -797 -589 rmoveto // -797 -589 rmoveto
lineTo(144, 693),
moveTo(60, 104), moveTo(60, 104),
// -81 881 81 vlineto // -81 881 81 vlineto
lineTo(60, 23), lineTo(60, 23),
lineTo(941, 23), lineTo(941, 23),
lineTo(941, 104), lineTo(941, 104),
// endchar // endchar
lineTo(60, 104),
}, },
}, },
@ -545,6 +548,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// 1 -53 -34 -44 -57 -25 rrcurveto // 1 -53 -34 -44 -57 -25 rrcurveto
cubeTo(138, -53, 104, -97, 47, -122), cubeTo(138, -53, 104, -97, 47, -122),
// endchar // endchar
lineTo(67, -170),
}, },
'Q': { 'Q': {
@ -615,6 +619,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// 113 91 15 callgsubr # 15 + bias = 122 // 113 91 15 callgsubr # 15 + bias = 122
// : # Arg stack is [113 91]. // : # Arg stack is [113 91].
// : rmoveto // : rmoveto
lineTo(82, 0),
moveTo(195, 577), moveTo(195, 577),
// : 69 29 58 77 3 hvcurveto // : 69 29 58 77 3 hvcurveto
cubeTo(264, 577, 293, 635, 296, 712), cubeTo(264, 577, 293, 635, 296, 712),
@ -681,12 +686,14 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// -92 115 -60 callgsubr # -60 + bias = 47 // -92 115 -60 callgsubr # -60 + bias = 47
// : # Arg stack is [-92 115]. // : # Arg stack is [-92 115].
// : rmoveto // : rmoveto
lineTo(82, 0),
moveTo(-10, 601), moveTo(-10, 601),
// : 266 57 -266 hlineto // : 266 57 -266 hlineto
lineTo(256, 601), lineTo(256, 601),
lineTo(256, 658), lineTo(256, 658),
lineTo(-10, 658), lineTo(-10, 658),
// : endchar // : endchar
lineTo(-10, 601),
}, },
'ĭ': { // U+012D LATIN SMALL LETTER I WITH BREVE 'ĭ': { // U+012D LATIN SMALL LETTER I WITH BREVE
@ -717,6 +724,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// 42 85 143 callsubr # 143 + bias = 250 // 42 85 143 callsubr # 143 + bias = 250
// : # Arg stack is [42 85]. // : # Arg stack is [42 85].
// : rmoveto // : rmoveto
lineTo(82, 0),
moveTo(124, 571), moveTo(124, 571),
// : -84 callsubr # -84 + bias = 23 // : -84 callsubr # -84 + bias = 23
// : : # Arg stack is []. // : : # Arg stack is [].
@ -765,6 +773,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// -96 hlineto // -96 hlineto
lineTo(209, 656), lineTo(209, 656),
// endchar // endchar
lineTo(0, 0),
}, },
'Ḫ': { // U+1E2A LATIN CAPITAL LETTER H WITH BREVE BELOW 'Ḫ': { // U+1E2A LATIN CAPITAL LETTER H WITH BREVE BELOW
@ -812,6 +821,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// 235 -887 143 callsubr # 143 + bias = 250 // 235 -887 143 callsubr # 143 + bias = 250
// : # Arg stack is [235 -887]. // : # Arg stack is [235 -887].
// : rmoveto // : rmoveto
lineTo(90, 0),
moveTo(325, -231), moveTo(325, -231),
// : -84 callsubr # -84 + bias = 23 // : -84 callsubr # -84 + bias = 23
// : : # Arg stack is []. // : : # Arg stack is [].

View File

@ -81,8 +81,43 @@ func checkSegmentsEqual(got, want []Segment) error {
i, g, w, got, want) i, g, w, got, want)
} }
} }
// Check that every contour is closed.
if len(got) == 0 {
return nil 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) { func TestTrueTypeParse(t *testing.T) {
f, err := Parse(goregular.TTF) f, err := Parse(goregular.TTF)
@ -416,11 +451,13 @@ func TestPostScriptSegments(t *testing.T) {
lineTo(450, 0), lineTo(450, 0),
lineTo(450, 533), lineTo(450, 533),
lineTo(50, 533), lineTo(50, 533),
lineTo(50, 0),
// - contour #1 // - contour #1
moveTo(100, 50), moveTo(100, 50),
lineTo(100, 483), lineTo(100, 483),
lineTo(400, 483), lineTo(400, 483),
lineTo(400, 50), lineTo(400, 50),
lineTo(100, 50),
}, { }, {
// zero // zero
// - contour #0 // - contour #0
@ -442,12 +479,14 @@ func TestPostScriptSegments(t *testing.T) {
lineTo(300, 0), lineTo(300, 0),
lineTo(300, 800), lineTo(300, 800),
lineTo(100, 800), lineTo(100, 800),
lineTo(100, 0),
}, { }, {
// Q // Q
// - contour #0 // - contour #0
moveTo(657, 237), moveTo(657, 237),
lineTo(289, 387), lineTo(289, 387),
lineTo(519, 615), lineTo(519, 615),
lineTo(657, 237),
// - contour #1 // - contour #1
moveTo(792, 169), moveTo(792, 169),
cubeTo(867, 263, 926, 502, 791, 665), cubeTo(867, 263, 926, 502, 791, 665),
@ -456,6 +495,7 @@ func TestPostScriptSegments(t *testing.T) {
cubeTo(369, -39, 641, 18, 722, 93), cubeTo(369, -39, 641, 18, 722, 93),
lineTo(802, 3), lineTo(802, 3),
lineTo(864, 83), lineTo(864, 83),
lineTo(792, 169),
}, { }, {
// uni4E2D // uni4E2D
// - contour #0 // - contour #0
@ -470,7 +510,7 @@ func TestPostScriptSegments(t *testing.T) {
lineTo(331, 758), lineTo(331, 758),
lineTo(243, 752), lineTo(243, 752),
lineTo(235, 562), lineTo(235, 562),
// TODO: explicitly (not implicitly) close these contours? lineTo(141, 520),
}} }}
testSegments(t, "CFFTest.otf", wants) testSegments(t, "CFFTest.otf", wants)