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 <crawshaw@golang.org>
This commit is contained in:
Nigel Tao 2017-03-31 12:11:21 +11:00
parent f36ba34967
commit ce0faa1867
5 changed files with 102 additions and 120 deletions

View File

@ -50,29 +50,29 @@ func ExampleRasterizeGlyph() {
switch seg.Op { switch seg.Op {
case sfnt.SegmentOpMoveTo: case sfnt.SegmentOpMoveTo:
r.MoveTo( r.MoveTo(
originX+float32(seg.Args[0])/64, originX+float32(seg.Args[0].X)/64,
originY-float32(seg.Args[1])/64, originY+float32(seg.Args[0].Y)/64,
) )
case sfnt.SegmentOpLineTo: case sfnt.SegmentOpLineTo:
r.LineTo( r.LineTo(
originX+float32(seg.Args[0])/64, originX+float32(seg.Args[0].X)/64,
originY-float32(seg.Args[1])/64, originY+float32(seg.Args[0].Y)/64,
) )
case sfnt.SegmentOpQuadTo: case sfnt.SegmentOpQuadTo:
r.QuadTo( r.QuadTo(
originX+float32(seg.Args[0])/64, originX+float32(seg.Args[0].X)/64,
originY-float32(seg.Args[1])/64, originY+float32(seg.Args[0].Y)/64,
originX+float32(seg.Args[2])/64, originX+float32(seg.Args[1].X)/64,
originY-float32(seg.Args[3])/64, originY+float32(seg.Args[1].Y)/64,
) )
case sfnt.SegmentOpCubeTo: case sfnt.SegmentOpCubeTo:
r.CubeTo( r.CubeTo(
originX+float32(seg.Args[0])/64, originX+float32(seg.Args[0].X)/64,
originY-float32(seg.Args[1])/64, originY+float32(seg.Args[0].Y)/64,
originX+float32(seg.Args[2])/64, originX+float32(seg.Args[1].X)/64,
originY-float32(seg.Args[3])/64, originY+float32(seg.Args[1].Y)/64,
originX+float32(seg.Args[4])/64, originX+float32(seg.Args[2].X)/64,
originY-float32(seg.Args[5])/64, originY+float32(seg.Args[2].Y)/64,
) )
} }
} }

View File

@ -999,46 +999,39 @@ func t2CMask(p *psInterpreter) error {
func t2CAppendMoveto(p *psInterpreter) { func t2CAppendMoveto(p *psInterpreter) {
p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{ p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{
Op: SegmentOpMoveTo, Op: SegmentOpMoveTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{{
0: fixed.Int26_6(p.type2Charstrings.x), X: fixed.Int26_6(p.type2Charstrings.x),
1: fixed.Int26_6(p.type2Charstrings.y), Y: fixed.Int26_6(p.type2Charstrings.y),
}, }},
}) })
} }
func t2CAppendLineto(p *psInterpreter) { func t2CAppendLineto(p *psInterpreter) {
p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{ p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{
Op: SegmentOpLineTo, Op: SegmentOpLineTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{{
0: fixed.Int26_6(p.type2Charstrings.x), X: fixed.Int26_6(p.type2Charstrings.x),
1: fixed.Int26_6(p.type2Charstrings.y), Y: fixed.Int26_6(p.type2Charstrings.y),
}, }},
}) })
} }
func t2CAppendCubeto(p *psInterpreter, dxa, dya, dxb, dyb, dxc, dyc int32) { func t2CAppendCubeto(p *psInterpreter, dxa, dya, dxb, dyb, dxc, dyc int32) {
p.type2Charstrings.x += dxa p.type2Charstrings.x += dxa
p.type2Charstrings.y += dya p.type2Charstrings.y += dya
xa := p.type2Charstrings.x xa := fixed.Int26_6(p.type2Charstrings.x)
ya := p.type2Charstrings.y ya := fixed.Int26_6(p.type2Charstrings.y)
p.type2Charstrings.x += dxb p.type2Charstrings.x += dxb
p.type2Charstrings.y += dyb p.type2Charstrings.y += dyb
xb := p.type2Charstrings.x xb := fixed.Int26_6(p.type2Charstrings.x)
yb := p.type2Charstrings.y yb := fixed.Int26_6(p.type2Charstrings.y)
p.type2Charstrings.x += dxc p.type2Charstrings.x += dxc
p.type2Charstrings.y += dyc p.type2Charstrings.y += dyc
xc := p.type2Charstrings.x xc := fixed.Int26_6(p.type2Charstrings.x)
yc := p.type2Charstrings.y yc := fixed.Int26_6(p.type2Charstrings.y)
p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{ p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{
Op: SegmentOpCubeTo, Op: SegmentOpCubeTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{{X: xa, Y: ya}, {X: xb, Y: yb}, {X: xc, Y: yc}},
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),
},
}) })
} }

View File

@ -902,6 +902,8 @@ type LoadGlyphOptions struct {
// //
// If b is non-nil, the segments become invalid to use once b is re-used. // 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. // 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) { func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) ([]Segment, error) {
if b == nil { 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 // loading code, such as the appendGlyfSegments body, since TrueType
// hinting bytecode works on the scaled glyph vectors. For now, though, // hinting bytecode works on the scaled glyph vectors. For now, though,
// it's simpler to scale as a post-processing step. // 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 { for i := range b.segments {
s := &b.segments[i] a := &b.segments[i].Args
for j := range s.Args { for j := range a {
s.Args[j] = scale(s.Args[j]*ppem, f.cached.unitsPerEm) 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. // Segment is a segment of a vector path.
type Segment struct { type Segment struct {
Op SegmentOp // Op is the operator.
Args [6]fixed.Int26_6 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. // SegmentOp is a vector path segment's operator.
@ -1209,30 +1217,31 @@ const (
) )
// translateArgs applies a translation to args. // translateArgs applies a translation to args.
func translateArgs(args *[6]fixed.Int26_6, dx, dy fixed.Int26_6) { func translateArgs(args *[3]fixed.Point26_6, dx, dy fixed.Int26_6) {
args[0] += dx args[0].X += dx
args[1] += dy args[0].Y += dy
args[2] += dx args[1].X += dx
args[3] += dy args[1].Y += dy
args[4] += dx args[2].X += dx
args[5] += dy args[2].Y += dy
} }
// transformArgs applies an affine transformation to args. The t?? arguments // transformArgs applies an affine transformation to args. The t?? arguments
// are 2.14 fixed point values. // are 2.14 fixed point values.
func transformArgs(args *[6]fixed.Int26_6, txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6) { func transformArgs(args *[3]fixed.Point26_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[0] = tform(txx, txy, tyx, tyy, dx, dy, args[0])
args[2], args[3] = tform(txx, txy, tyx, tyy, dx, dy, args[2], args[3]) args[1] = tform(txx, txy, tyx, tyy, dx, dy, args[1])
args[4], args[5] = tform(txx, txy, tyx, tyy, dx, dy, args[4], args[5]) 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 const half = 1 << 13
newX = dx + return fixed.Point26_6{
fixed.Int26_6((int64(x)*int64(txx)+half)>>14) + X: dx +
fixed.Int26_6((int64(y)*int64(tyx)+half)>>14) fixed.Int26_6((int64(p.X)*int64(txx)+half)>>14) +
newY = dy + fixed.Int26_6((int64(p.Y)*int64(tyx)+half)>>14),
fixed.Int26_6((int64(x)*int64(txy)+half)>>14) + Y: dy +
fixed.Int26_6((int64(y)*int64(tyy)+half)>>14) fixed.Int26_6((int64(p.X)*int64(txy)+half)>>14) +
return newX, newY fixed.Int26_6((int64(p.Y)*int64(tyy)+half)>>14),
}
} }

View File

@ -15,31 +15,35 @@ import (
"golang.org/x/image/math/fixed" "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 { func moveTo(xa, ya fixed.Int26_6) Segment {
return Segment{ return Segment{
Op: SegmentOpMoveTo, 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 { func lineTo(xa, ya fixed.Int26_6) Segment {
return Segment{ return Segment{
Op: SegmentOpLineTo, 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 { func quadTo(xa, ya, xb, yb fixed.Int26_6) Segment {
return Segment{ return Segment{
Op: SegmentOpQuadTo, 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 { func cubeTo(xa, ya, xb, yb, xc, yc fixed.Int26_6) Segment {
return Segment{ return Segment{
Op: SegmentOpCubeTo, 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 { 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) { if len(got) != len(want) {
return fmt.Errorf("got %d elements, want %d\noverall:\ngot %v\nwant %v", return fmt.Errorf("got %d elements, want %d\noverall:\ngot %v\nwant %v",
len(got), len(want), got, want) len(got), len(want), got, want)

View File

@ -414,44 +414,28 @@ func (g *glyfIter) close() {
case !g.firstOffCurveValid && !g.lastOffCurveValid: case !g.firstOffCurveValid && !g.lastOffCurveValid:
g.closed = true g.closed = true
g.seg = Segment{ g.seg = Segment{
Op: SegmentOpLineTo, Op: SegmentOpLineTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{g.firstOnCurve},
g.firstOnCurve.X,
g.firstOnCurve.Y,
},
} }
case !g.firstOffCurveValid && g.lastOffCurveValid: case !g.firstOffCurveValid && g.lastOffCurveValid:
g.closed = true g.closed = true
g.seg = Segment{ g.seg = Segment{
Op: SegmentOpQuadTo, Op: SegmentOpQuadTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{g.lastOffCurve, g.firstOnCurve},
g.lastOffCurve.X,
g.lastOffCurve.Y,
g.firstOnCurve.X,
g.firstOnCurve.Y,
},
} }
case g.firstOffCurveValid && !g.lastOffCurveValid: case g.firstOffCurveValid && !g.lastOffCurveValid:
g.closed = true g.closed = true
g.seg = Segment{ g.seg = Segment{
Op: SegmentOpQuadTo, Op: SegmentOpQuadTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{g.firstOffCurve, g.firstOnCurve},
g.firstOffCurve.X,
g.firstOffCurve.Y,
g.firstOnCurve.X,
g.firstOnCurve.Y,
},
} }
case g.firstOffCurveValid && g.lastOffCurveValid: case g.firstOffCurveValid && g.lastOffCurveValid:
mid := midPoint(g.lastOffCurve, g.firstOffCurve)
g.lastOffCurveValid = false g.lastOffCurveValid = false
g.seg = Segment{ g.seg = Segment{
Op: SegmentOpQuadTo, Op: SegmentOpQuadTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{
g.lastOffCurve.X, g.lastOffCurve,
g.lastOffCurve.Y, midPoint(g.lastOffCurve, g.firstOffCurve),
mid.X,
mid.Y,
}, },
} }
} }
@ -484,11 +468,8 @@ func (g *glyfIter) nextSegment() (ok bool) {
g.firstOnCurve = p g.firstOnCurve = p
g.firstOnCurveValid = true g.firstOnCurveValid = true
g.seg = Segment{ g.seg = Segment{
Op: SegmentOpMoveTo, Op: SegmentOpMoveTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{p},
p.X,
p.Y,
},
} }
return true return true
} else if !g.firstOffCurveValid { } else if !g.firstOffCurveValid {
@ -496,17 +477,13 @@ func (g *glyfIter) nextSegment() (ok bool) {
g.firstOffCurveValid = true g.firstOffCurveValid = true
continue continue
} else { } else {
midp := midPoint(g.firstOffCurve, p) g.firstOnCurve = midPoint(g.firstOffCurve, p)
g.firstOnCurve = midp
g.firstOnCurveValid = true g.firstOnCurveValid = true
g.lastOffCurve = p g.lastOffCurve = p
g.lastOffCurveValid = true g.lastOffCurveValid = true
g.seg = Segment{ g.seg = Segment{
Op: SegmentOpMoveTo, Op: SegmentOpMoveTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{g.firstOnCurve},
midp.X,
midp.Y,
},
} }
return true return true
} }
@ -518,25 +495,19 @@ func (g *glyfIter) nextSegment() (ok bool) {
continue continue
} else { } else {
g.seg = Segment{ g.seg = Segment{
Op: SegmentOpLineTo, Op: SegmentOpLineTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{p},
p.X,
p.Y,
},
} }
return true return true
} }
} else { } else {
if !g.on { if !g.on {
midp := midPoint(g.lastOffCurve, p)
g.seg = Segment{ g.seg = Segment{
Op: SegmentOpQuadTo, Op: SegmentOpQuadTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{
g.lastOffCurve.X, g.lastOffCurve,
g.lastOffCurve.Y, midPoint(g.lastOffCurve, p),
midp.X,
midp.Y,
}, },
} }
g.lastOffCurve = p g.lastOffCurve = p
@ -544,13 +515,8 @@ func (g *glyfIter) nextSegment() (ok bool) {
return true return true
} else { } else {
g.seg = Segment{ g.seg = Segment{
Op: SegmentOpQuadTo, Op: SegmentOpQuadTo,
Args: [6]fixed.Int26_6{ Args: [3]fixed.Point26_6{g.lastOffCurve, p},
g.lastOffCurve.X,
g.lastOffCurve.Y,
p.X,
p.Y,
},
} }
g.lastOffCurveValid = false g.lastOffCurveValid = false
return true return true