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 (
|
||||
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+..
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 [].
|
||||
|
|
|
@ -81,7 +81,42 @@ 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) {
|
||||
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user