diff --git a/font/sfnt/example_test.go b/font/sfnt/example_test.go index f9a6697..1743156 100644 --- a/font/sfnt/example_test.go +++ b/font/sfnt/example_test.go @@ -38,6 +38,9 @@ func ExampleRasterizeGlyph() { log.Fatalf("GlyphIndex: no glyph index found for the rune 'G'") } segments, err := f.LoadGlyph(&b, x, fixed.I(ppem), nil) + if err != nil { + log.Fatalf("LoadGlyph: %v", err) + } r := vector.NewRasterizer(width, height) r.DrawOp = draw.Src diff --git a/font/sfnt/postscript.go b/font/sfnt/postscript.go index 8142bdc..ca1b831 100644 --- a/font/sfnt/postscript.go +++ b/font/sfnt/postscript.go @@ -566,8 +566,8 @@ var psOperators = [...][2][]psOperator{ 23: {-1, "vstemhm", t2CStem}, 24: {}, // rcurveline. 25: {}, // rlinecurve. - 26: {}, // vvcurveto. - 27: {}, // hhcurveto. + 26: {-1, "vvcurveto", t2CVvcurveto}, + 27: {-1, "hhcurveto", t2CHhcurveto}, 28: {}, // shortint. 29: {}, // callgsubr. 30: {-1, "vhcurveto", t2CVhcurveto}, @@ -770,6 +770,12 @@ func t2CRlineto(p *psInterpreter) error { // As per 5177.Type2.pdf section 4.1 "Path Construction Operators", // +// hhcurveto is: +// - dy1 {dxa dxb dyb dxc}+ +// +// vvcurveto is: +// - dx1 {dya dxb dyb dyc}+ +// // hvcurveto is one of: // - dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf? // - {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? @@ -778,59 +784,84 @@ func t2CRlineto(p *psInterpreter) error { // - dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? // - {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? -func t2CHvcurveto(p *psInterpreter) error { return t2CCurveto(p, false) } -func t2CVhcurveto(p *psInterpreter) error { return t2CCurveto(p, true) } +func t2CHhcurveto(p *psInterpreter) error { return t2CCurveto(p, false, false) } +func t2CVvcurveto(p *psInterpreter) error { return t2CCurveto(p, false, true) } +func t2CHvcurveto(p *psInterpreter) error { return t2CCurveto(p, true, false) } +func t2CVhcurveto(p *psInterpreter) error { return t2CCurveto(p, true, true) } -func t2CCurveto(p *psInterpreter, vertical bool) error { +// t2CCurveto implements the hh / vv / hv / vh xxcurveto operators. N relative +// cubic curve requires 6*N control points, but only 4*N+0 or 4*N+1 are used +// here: all (or all but one) of the piecewise cubic curve's tangents are +// implicitly horizontal or vertical. +// +// swap is whether that implicit horizontal / vertical constraint swaps as you +// move along the piecewise cubic curve. If swap is false, the constraints are +// either all horizontal or all vertical. If swap is true, it alternates. +// +// vertical is whether the first implicit constraint is vertical. +func t2CCurveto(p *psInterpreter, swap, vertical bool) error { if !p.type2Charstrings.seenWidth || p.stack.top < 4 { return errInvalidCFFTable } - for i := int32(0); i != p.stack.top; vertical = !vertical { - if vertical { - i = t2CVcurveto(p, i) - } else { - i = t2CHcurveto(p, i) + + i := int32(0) + switch p.stack.top & 3 { + case 0: + // No-op. + case 1: + if swap { + break } + i = 1 + if vertical { + p.type2Charstrings.x += p.stack.a[0] + } else { + p.type2Charstrings.y += p.stack.a[0] + } + default: + return errInvalidCFFTable + } + + for i != p.stack.top { + i = t2CCurveto4(p, swap, vertical, i) if i < 0 { return errInvalidCFFTable } + if swap { + vertical = !vertical + } } return nil } -func t2CHcurveto(p *psInterpreter, i int32) (j int32) { +func t2CCurveto4(p *psInterpreter, swap bool, vertical bool, i int32) (j int32) { if i+4 > p.stack.top { return -1 } dxa := p.stack.a[i+0] - dxb := p.stack.a[i+1] - dyb := p.stack.a[i+2] - dyc := p.stack.a[i+3] - dxc := int32(0) - i += 4 - if i+1 == p.stack.top { - dxc = p.stack.a[i] - i++ - } - t2CAppendCubeto(p, dxa, 0, dxb, dyb, dxc, dyc) - return i -} - -func t2CVcurveto(p *psInterpreter, i int32) (j int32) { - if i+4 > p.stack.top { - return -1 - } - dya := p.stack.a[i+0] + dya := int32(0) dxb := p.stack.a[i+1] dyb := p.stack.a[i+2] dxc := p.stack.a[i+3] dyc := int32(0) i += 4 - if i+1 == p.stack.top { - dyc = p.stack.a[i] - i++ + + if vertical { + dxa, dya = dya, dxa } - t2CAppendCubeto(p, 0, dya, dxb, dyb, dxc, dyc) + + if swap { + if i+1 == p.stack.top { + dyc = p.stack.a[i] + i++ + } + } + + if swap != vertical { + dxc, dyc = dyc, dxc + } + + t2CAppendCubeto(p, dxa, dya, dxb, dyb, dxc, dyc) return i } diff --git a/font/sfnt/proprietary_test.go b/font/sfnt/proprietary_test.go index b534a5b..b105ee5 100644 --- a/font/sfnt/proprietary_test.go +++ b/font/sfnt/proprietary_test.go @@ -186,6 +186,23 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi } } + for r, want := range proprietaryGlyphTestCases[qualifiedFilename] { + x, err := f.GlyphIndex(&buf, r) + if err != nil { + t.Errorf("GlyphIndex(%q): %v", r, err) + continue + } + got, err := f.LoadGlyph(&buf, x, ppem, nil) + if err != nil { + t.Errorf("LoadGlyph(%q): %v", r, err) + continue + } + if err := checkSegmentsEqual(got, want); err != nil { + t.Errorf("LoadGlyph(%q): %v", r, err) + continue + } + } + kernLoop: for _, tc := range proprietaryKernTestCases[qualifiedFilename] { var indexes [2]GlyphIndex @@ -312,6 +329,113 @@ var proprietaryGlyphIndexTestCases = map[string]map[rune]GlyphIndex{ }, } +// proprietaryGlyphTestCases hold a sample of each font's glyph vectors. The +// numerical values can be verified by running the ttx tool, remembering that: +// - for PostScript glyphs, ttx coordinates are relative, and hstem / vstem +// operators are hinting-related and can be ignored. +// - for TrueType glyphs, ttx coordinates are absolute, and consecutive +// off-curve points implies an on-curve point at the midpoint. +var proprietaryGlyphTestCases = map[string]map[rune][]Segment{ + "adobe/SourceSansPro-Regular.otf": { + ',': { + // - contour #0 + // 67 -170 rmoveto + moveTo(67, -170), + // 81 34 50 67 86 vvcurveto + cubeTo(148, -136, 198, -69, 198, 17), + // 60 -26 37 -43 -33 -28 -22 -36 -37 27 -20 32 3 4 0 1 3 vhcurveto + cubeTo(198, 77, 172, 114, 129, 114), + cubeTo(96, 114, 68, 92, 68, 56), + cubeTo(68, 19, 95, -1, 127, -1), + cubeTo(130, -1, 134, -1, 137, 0), + // 1 -53 -34 -44 -57 -25 rrcurveto + cubeTo(138, -53, 104, -97, 47, -122), + }, + 'Q': { + // - contour #0 + // 332 57 rmoveto + moveTo(332, 57), + // -117 -77 106 168 163 77 101 117 117 77 -101 -163 -168 -77 -106 -117 hvcurveto + cubeTo(215, 57, 138, 163, 138, 331), + cubeTo(138, 494, 215, 595, 332, 595), + cubeTo(449, 595, 526, 494, 526, 331), + cubeTo(526, 163, 449, 57, 332, 57), + // - contour #1 + // 201 -222 rmoveto + moveTo(533, -165), + // 39 35 7 8 20 hvcurveto + cubeTo(572, -165, 607, -158, 627, -150), + // -16 64 rlineto + lineTo(611, -86), + // -5 -18 -22 -4 -29 hhcurveto + cubeTo(593, -91, 571, -95, 542, -95), + // -71 -60 29 58 -30 hvcurveto + cubeTo(471, -95, 411, -66, 381, -8), + // 139 24 93 126 189 vvcurveto + cubeTo(520, 16, 613, 142, 613, 331), + // 209 -116 128 -165 -165 -115 -127 -210 -193 96 -127 143 -20 vhcurveto + cubeTo(613, 540, 497, 668, 332, 668), + cubeTo(167, 668, 52, 541, 52, 331), + cubeTo(52, 138, 148, 11, 291, -9), + // -90 38 83 -66 121 hhcurveto + cubeTo(329, -99, 412, -165, 533, -165), + }, + }, + + "microsoft/Arial.ttf": { + ',': { + // - contour #0 + moveTo(182, 0), + lineTo(182, 205), + lineTo(387, 205), + lineTo(387, 0), + quadTo(387, -113, 347, -182), + quadTo(307, -252, 220, -290), + lineTo(170, -213), + quadTo(227, -188, 254, -139), + quadTo(281, -91, 284, 0), + lineTo(182, 0), + }, + 'i': { + // - contour #0 + moveTo(136, 1259), + lineTo(136, 1466), + lineTo(316, 1466), + lineTo(316, 1259), + lineTo(136, 1259), + // - contour #1 + moveTo(136, 0), + lineTo(136, 1062), + lineTo(316, 1062), + lineTo(316, 0), + lineTo(136, 0), + }, + 'o': { + // - contour #0 + moveTo(68, 531), + quadTo(68, 826, 232, 968), + quadTo(369, 1086, 566, 1086), + quadTo(785, 1086, 924, 942), + quadTo(1063, 799, 1063, 546), + quadTo(1063, 341, 1001, 223), + quadTo(940, 106, 822, 41), + quadTo(705, -24, 566, -24), + quadTo(343, -24, 205, 119), + quadTo(68, 262, 68, 531), + // - contour #1 + moveTo(253, 531), + quadTo(253, 327, 342, 225), + quadTo(431, 124, 566, 124), + quadTo(700, 124, 789, 226), + quadTo(878, 328, 878, 537), + quadTo(878, 734, 788, 835), + quadTo(699, 937, 566, 937), + quadTo(431, 937, 342, 836), + quadTo(253, 735, 253, 531), + }, + }, +} + type kernTestCase struct { ppem fixed.Int26_6 hinting font.Hinting