From ce0faa1867f5944a366e0174e7197b82089e9fa8 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Fri, 31 Mar 2017 12:11:21 +1100 Subject: [PATCH] font/sfnt: flip the Y axis for LoadGlyph's Segments. The underlying font format's Y axis increases up. The Go standard graphics libraries' Y axis increases down. This change makes the Go API consistent with the other Go libraries. Also change Segment.Args from [6]fixed.Int26_6 to [3]fixed.Point26_6 to emphasize that the Args are consistent with other fixed.Point26_6 use. Change-Id: Idd7b89eb4d86890dea477ac2ef96ff8f6b1dee8d Reviewed-on: https://go-review.googlesource.com/39072 Reviewed-by: David Crawshaw --- font/sfnt/example_test.go | 28 +++++++-------- font/sfnt/postscript.go | 39 +++++++++----------- font/sfnt/sfnt.go | 57 ++++++++++++++++------------- font/sfnt/sfnt_test.go | 22 +++++++++--- font/sfnt/truetype.go | 76 +++++++++++---------------------------- 5 files changed, 102 insertions(+), 120 deletions(-) diff --git a/font/sfnt/example_test.go b/font/sfnt/example_test.go index 1743156..63379eb 100644 --- a/font/sfnt/example_test.go +++ b/font/sfnt/example_test.go @@ -50,29 +50,29 @@ func ExampleRasterizeGlyph() { switch seg.Op { case sfnt.SegmentOpMoveTo: r.MoveTo( - originX+float32(seg.Args[0])/64, - originY-float32(seg.Args[1])/64, + originX+float32(seg.Args[0].X)/64, + originY+float32(seg.Args[0].Y)/64, ) case sfnt.SegmentOpLineTo: r.LineTo( - originX+float32(seg.Args[0])/64, - originY-float32(seg.Args[1])/64, + originX+float32(seg.Args[0].X)/64, + originY+float32(seg.Args[0].Y)/64, ) case sfnt.SegmentOpQuadTo: r.QuadTo( - originX+float32(seg.Args[0])/64, - originY-float32(seg.Args[1])/64, - originX+float32(seg.Args[2])/64, - originY-float32(seg.Args[3])/64, + originX+float32(seg.Args[0].X)/64, + originY+float32(seg.Args[0].Y)/64, + originX+float32(seg.Args[1].X)/64, + originY+float32(seg.Args[1].Y)/64, ) case sfnt.SegmentOpCubeTo: r.CubeTo( - originX+float32(seg.Args[0])/64, - originY-float32(seg.Args[1])/64, - originX+float32(seg.Args[2])/64, - originY-float32(seg.Args[3])/64, - originX+float32(seg.Args[4])/64, - originY-float32(seg.Args[5])/64, + originX+float32(seg.Args[0].X)/64, + originY+float32(seg.Args[0].Y)/64, + originX+float32(seg.Args[1].X)/64, + originY+float32(seg.Args[1].Y)/64, + originX+float32(seg.Args[2].X)/64, + originY+float32(seg.Args[2].Y)/64, ) } } diff --git a/font/sfnt/postscript.go b/font/sfnt/postscript.go index 183f0cb..2f746c4 100644 --- a/font/sfnt/postscript.go +++ b/font/sfnt/postscript.go @@ -999,46 +999,39 @@ func t2CMask(p *psInterpreter) error { func t2CAppendMoveto(p *psInterpreter) { p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{ Op: SegmentOpMoveTo, - Args: [6]fixed.Int26_6{ - 0: fixed.Int26_6(p.type2Charstrings.x), - 1: fixed.Int26_6(p.type2Charstrings.y), - }, + 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: [6]fixed.Int26_6{ - 0: fixed.Int26_6(p.type2Charstrings.x), - 1: fixed.Int26_6(p.type2Charstrings.y), - }, + 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 := p.type2Charstrings.x - ya := p.type2Charstrings.y + xa := fixed.Int26_6(p.type2Charstrings.x) + ya := fixed.Int26_6(p.type2Charstrings.y) p.type2Charstrings.x += dxb p.type2Charstrings.y += dyb - xb := p.type2Charstrings.x - yb := p.type2Charstrings.y + xb := fixed.Int26_6(p.type2Charstrings.x) + yb := fixed.Int26_6(p.type2Charstrings.y) p.type2Charstrings.x += dxc p.type2Charstrings.y += dyc - xc := p.type2Charstrings.x - yc := p.type2Charstrings.y + 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: [6]fixed.Int26_6{ - 0: fixed.Int26_6(xa), - 1: fixed.Int26_6(ya), - 2: fixed.Int26_6(xb), - 3: fixed.Int26_6(yb), - 4: fixed.Int26_6(xc), - 5: fixed.Int26_6(yc), - }, + Op: SegmentOpCubeTo, + Args: [3]fixed.Point26_6{{X: xa, Y: ya}, {X: xb, Y: yb}, {X: xc, Y: yc}}, }) } diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go index 73ec6c9..6668606 100644 --- a/font/sfnt/sfnt.go +++ b/font/sfnt/sfnt.go @@ -902,6 +902,8 @@ type LoadGlyphOptions struct { // // If b is non-nil, the segments become invalid to use once b is re-used. // +// In the returned Segments' (x, y) coordinates, the Y axis increases down. +// // It returns ErrNotFound if the glyph index is out of range. func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) ([]Segment, error) { if b == nil { @@ -932,10 +934,14 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *Load // loading code, such as the appendGlyfSegments body, since TrueType // hinting bytecode works on the scaled glyph vectors. For now, though, // it's simpler to scale as a post-processing step. + // + // We also flip the Y coordinates. OpenType's Y axis increases up. Go's + // standard graphics libraries' Y axis increases down. for i := range b.segments { - s := &b.segments[i] - for j := range s.Args { - s.Args[j] = scale(s.Args[j]*ppem, f.cached.unitsPerEm) + a := &b.segments[i].Args + for j := range a { + a[j].X = +scale(a[j].X*ppem, f.cached.unitsPerEm) + a[j].Y = -scale(a[j].Y*ppem, f.cached.unitsPerEm) } } @@ -1194,8 +1200,10 @@ func (b *Buffer) view(src *source, offset, length int) ([]byte, error) { // Segment is a segment of a vector path. type Segment struct { - Op SegmentOp - Args [6]fixed.Int26_6 + // Op is the operator. + Op SegmentOp + // Args is up to three (x, y) coordinates. The Y axis increases down. + Args [3]fixed.Point26_6 } // SegmentOp is a vector path segment's operator. @@ -1209,30 +1217,31 @@ const ( ) // translateArgs applies a translation to args. -func translateArgs(args *[6]fixed.Int26_6, dx, dy fixed.Int26_6) { - args[0] += dx - args[1] += dy - args[2] += dx - args[3] += dy - args[4] += dx - args[5] += dy +func translateArgs(args *[3]fixed.Point26_6, dx, dy fixed.Int26_6) { + args[0].X += dx + args[0].Y += dy + args[1].X += dx + args[1].Y += dy + args[2].X += dx + args[2].Y += dy } // transformArgs applies an affine transformation to args. The t?? arguments // are 2.14 fixed point values. -func transformArgs(args *[6]fixed.Int26_6, txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6) { - args[0], args[1] = tform(txx, txy, tyx, tyy, dx, dy, args[0], args[1]) - args[2], args[3] = tform(txx, txy, tyx, tyy, dx, dy, args[2], args[3]) - args[4], args[5] = tform(txx, txy, tyx, tyy, dx, dy, args[4], args[5]) +func transformArgs(args *[3]fixed.Point26_6, txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6) { + args[0] = tform(txx, txy, tyx, tyy, dx, dy, args[0]) + args[1] = tform(txx, txy, tyx, tyy, dx, dy, args[1]) + args[2] = tform(txx, txy, tyx, tyy, dx, dy, args[2]) } -func tform(txx, txy, tyx, tyy int16, dx, dy, x, y fixed.Int26_6) (newX, newY fixed.Int26_6) { +func tform(txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6, p fixed.Point26_6) fixed.Point26_6 { const half = 1 << 13 - newX = dx + - fixed.Int26_6((int64(x)*int64(txx)+half)>>14) + - fixed.Int26_6((int64(y)*int64(tyx)+half)>>14) - newY = dy + - fixed.Int26_6((int64(x)*int64(txy)+half)>>14) + - fixed.Int26_6((int64(y)*int64(tyy)+half)>>14) - return newX, newY + return fixed.Point26_6{ + X: dx + + fixed.Int26_6((int64(p.X)*int64(txx)+half)>>14) + + fixed.Int26_6((int64(p.Y)*int64(tyx)+half)>>14), + Y: dy + + fixed.Int26_6((int64(p.X)*int64(txy)+half)>>14) + + fixed.Int26_6((int64(p.Y)*int64(tyy)+half)>>14), + } } diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go index c0a7fb0..2bb508b 100644 --- a/font/sfnt/sfnt_test.go +++ b/font/sfnt/sfnt_test.go @@ -15,31 +15,35 @@ import ( "golang.org/x/image/math/fixed" ) +func pt(x, y fixed.Int26_6) fixed.Point26_6 { + return fixed.Point26_6{X: x, Y: y} +} + func moveTo(xa, ya fixed.Int26_6) Segment { return Segment{ Op: SegmentOpMoveTo, - Args: [6]fixed.Int26_6{xa, ya}, + Args: [3]fixed.Point26_6{pt(xa, ya)}, } } func lineTo(xa, ya fixed.Int26_6) Segment { return Segment{ Op: SegmentOpLineTo, - Args: [6]fixed.Int26_6{xa, ya}, + Args: [3]fixed.Point26_6{pt(xa, ya)}, } } func quadTo(xa, ya, xb, yb fixed.Int26_6) Segment { return Segment{ Op: SegmentOpQuadTo, - Args: [6]fixed.Int26_6{xa, ya, xb, yb}, + Args: [3]fixed.Point26_6{pt(xa, ya), pt(xb, yb)}, } } func cubeTo(xa, ya, xb, yb, xc, yc fixed.Int26_6) Segment { return Segment{ Op: SegmentOpCubeTo, - Args: [6]fixed.Int26_6{xa, ya, xb, yb, xc, yc}, + Args: [3]fixed.Point26_6{pt(xa, ya), pt(xb, yb), pt(xc, yc)}, } } @@ -54,6 +58,16 @@ func transform(txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6, s Segment) Segmen } func checkSegmentsEqual(got, want []Segment) error { + // Flip got's Y axis. The test cases' coordinates are given with the Y axis + // increasing up, as that is what the ttx tool gives, and is the model for + // the underlying font format. The Go API returns coordinates with the Y + // axis increasing down, the same as the standard graphics libraries. + for i := range got { + for j := range got[i].Args { + got[i].Args[j].Y *= -1 + } + } + if len(got) != len(want) { return fmt.Errorf("got %d elements, want %d\noverall:\ngot %v\nwant %v", len(got), len(want), got, want) diff --git a/font/sfnt/truetype.go b/font/sfnt/truetype.go index 4181961..25455e5 100644 --- a/font/sfnt/truetype.go +++ b/font/sfnt/truetype.go @@ -414,44 +414,28 @@ func (g *glyfIter) close() { case !g.firstOffCurveValid && !g.lastOffCurveValid: g.closed = true g.seg = Segment{ - Op: SegmentOpLineTo, - Args: [6]fixed.Int26_6{ - g.firstOnCurve.X, - g.firstOnCurve.Y, - }, + Op: SegmentOpLineTo, + Args: [3]fixed.Point26_6{g.firstOnCurve}, } case !g.firstOffCurveValid && g.lastOffCurveValid: g.closed = true g.seg = Segment{ - Op: SegmentOpQuadTo, - Args: [6]fixed.Int26_6{ - g.lastOffCurve.X, - g.lastOffCurve.Y, - g.firstOnCurve.X, - g.firstOnCurve.Y, - }, + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{g.lastOffCurve, g.firstOnCurve}, } case g.firstOffCurveValid && !g.lastOffCurveValid: g.closed = true g.seg = Segment{ - Op: SegmentOpQuadTo, - Args: [6]fixed.Int26_6{ - g.firstOffCurve.X, - g.firstOffCurve.Y, - g.firstOnCurve.X, - g.firstOnCurve.Y, - }, + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{g.firstOffCurve, g.firstOnCurve}, } case g.firstOffCurveValid && g.lastOffCurveValid: - mid := midPoint(g.lastOffCurve, g.firstOffCurve) g.lastOffCurveValid = false g.seg = Segment{ Op: SegmentOpQuadTo, - Args: [6]fixed.Int26_6{ - g.lastOffCurve.X, - g.lastOffCurve.Y, - mid.X, - mid.Y, + Args: [3]fixed.Point26_6{ + g.lastOffCurve, + midPoint(g.lastOffCurve, g.firstOffCurve), }, } } @@ -484,11 +468,8 @@ func (g *glyfIter) nextSegment() (ok bool) { g.firstOnCurve = p g.firstOnCurveValid = true g.seg = Segment{ - Op: SegmentOpMoveTo, - Args: [6]fixed.Int26_6{ - p.X, - p.Y, - }, + Op: SegmentOpMoveTo, + Args: [3]fixed.Point26_6{p}, } return true } else if !g.firstOffCurveValid { @@ -496,17 +477,13 @@ func (g *glyfIter) nextSegment() (ok bool) { g.firstOffCurveValid = true continue } else { - midp := midPoint(g.firstOffCurve, p) - g.firstOnCurve = midp + g.firstOnCurve = midPoint(g.firstOffCurve, p) g.firstOnCurveValid = true g.lastOffCurve = p g.lastOffCurveValid = true g.seg = Segment{ - Op: SegmentOpMoveTo, - Args: [6]fixed.Int26_6{ - midp.X, - midp.Y, - }, + Op: SegmentOpMoveTo, + Args: [3]fixed.Point26_6{g.firstOnCurve}, } return true } @@ -518,25 +495,19 @@ func (g *glyfIter) nextSegment() (ok bool) { continue } else { g.seg = Segment{ - Op: SegmentOpLineTo, - Args: [6]fixed.Int26_6{ - p.X, - p.Y, - }, + Op: SegmentOpLineTo, + Args: [3]fixed.Point26_6{p}, } return true } } else { if !g.on { - midp := midPoint(g.lastOffCurve, p) g.seg = Segment{ Op: SegmentOpQuadTo, - Args: [6]fixed.Int26_6{ - g.lastOffCurve.X, - g.lastOffCurve.Y, - midp.X, - midp.Y, + Args: [3]fixed.Point26_6{ + g.lastOffCurve, + midPoint(g.lastOffCurve, p), }, } g.lastOffCurve = p @@ -544,13 +515,8 @@ func (g *glyfIter) nextSegment() (ok bool) { return true } else { g.seg = Segment{ - Op: SegmentOpQuadTo, - Args: [6]fixed.Int26_6{ - g.lastOffCurve.X, - g.lastOffCurve.Y, - p.X, - p.Y, - }, + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{g.lastOffCurve, p}, } g.lastOffCurveValid = false return true