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 (
ppem = 32
width = 24
height = 32
height = 36
originX = 0
originY = 28
originY = 32
)
f, err := sfnt.Parse(goregular.TTF)
@ -30,12 +30,12 @@ func ExampleRasterizeGlyph() {
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 @@ func ExampleRasterizeGlyph() {
)
}
}
// 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 @@ func ExampleRasterizeGlyph() {
// ........................
// ........................
// ........................
// ............888.........
// ............888.........
// ............888.........
// ............+++.........
// ........................
// ..........+++++++++.....
// .......+8888888888888+..

View File

@ -531,7 +531,10 @@ func (d *psPrivateDictData) initialize() {
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) 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.
type psInterpreter struct {
ctx psContext
@ -996,52 +1057,12 @@ func t2CMask(p *psInterpreter) error {
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 @@ func t2CVmoveto(p *psInterpreter) error {
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 @@ func t2CRmoveto(p *psInterpreter) error {
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 @@ func t2CLineto(p *psInterpreter, vertical bool) error {
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 @@ func t2CRlineto(p *psInterpreter) error {
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 @@ func t2CRcurveline(p *psInterpreter) error {
}
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 @@ func t2CRcurveline(p *psInterpreter) error {
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 @@ func t2CRlinecurve(p *psInterpreter) error {
}
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 @@ func t2CCurveto4(p *psInterpreter, swap bool, vertical bool, i int32) (j int32)
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 @@ func t2CRrcurveto(p *psInterpreter) error {
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 @@ func t2CEndchar(p *psInterpreter) error {
}
return errInvalidCFFTable
}
p.type2Charstrings.closePath()
p.type2Charstrings.ended = true
return nil
}

View File

@ -493,6 +493,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// 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 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
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 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// 1 -53 -34 -44 -57 -25 rrcurveto
cubeTo(138, -53, 104, -97, 47, -122),
// endchar
lineTo(67, -170),
},
'Q': {
@ -615,6 +619,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// 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 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// -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 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// 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 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// -96 hlineto
lineTo(209, 656),
// endchar
lineTo(0, 0),
},
'Ḫ': { // 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
// : # Arg stack is [235 -887].
// : rmoveto
lineTo(90, 0),
moveTo(325, -231),
// : -84 callsubr # -84 + bias = 23
// : : # Arg stack is [].

View File

@ -81,8 +81,43 @@ func checkSegmentsEqual(got, want []Segment) error {
i, g, w, got, want)
}
}
// 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) {
f, err := Parse(goregular.TTF)
@ -416,11 +451,13 @@ func TestPostScriptSegments(t *testing.T) {
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 @@ func TestPostScriptSegments(t *testing.T) {
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 @@ func TestPostScriptSegments(t *testing.T) {
cubeTo(369, -39, 641, 18, 722, 93),
lineTo(802, 3),
lineTo(864, 83),
lineTo(792, 169),
}, {
// uni4E2D
// - contour #0
@ -470,7 +510,7 @@ func TestPostScriptSegments(t *testing.T) {
lineTo(331, 758),
lineTo(243, 752),
lineTo(235, 562),
// TODO: explicitly (not implicitly) close these contours?
lineTo(141, 520),
}}
testSegments(t, "CFFTest.otf", wants)