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 {
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,
)
}
}

View File

@ -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}},
})
}

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.
//
// 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),
}
}

View File

@ -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)

View File

@ -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