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 @@ 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+.. 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 @@ 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 } 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 @@ 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 []. 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 @@ func checkSegmentsEqual(got, want []Segment) error { 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 @@ 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)