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:
parent
1de9a5bb2a
commit
7c3fafc74f
|
@ -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+..
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 [].
|
||||||
|
|
|
@ -81,7 +81,42 @@ func checkSegmentsEqual(got, want []Segment) error {
|
||||||
i, g, w, got, want)
|
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) {
|
func TestTrueTypeParse(t *testing.T) {
|
||||||
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user