Use fixed.Rectangle26_6 instead of truetype.Bounds.

The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.

Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
This commit is contained in:
Nigel Tao 2015-08-30 22:06:37 +10:00
parent 7a598da6de
commit 3cc748686b
6 changed files with 81 additions and 74 deletions

View file

@ -23,12 +23,12 @@ import (
var fontfile = flag.String("fontfile", "../../testdata/luxisr.ttf", "filename of the ttf font")
func printBounds(b truetype.Bounds) {
fmt.Printf("XMin:%d YMin:%d XMax:%d YMax:%d\n", b.XMin, b.YMin, b.XMax, b.YMax)
func printBounds(b fixed.Rectangle26_6) {
fmt.Printf("Min.X:%d Min.Y:%d Max.X:%d Max.Y:%d\n", b.Min.X, b.Min.Y, b.Max.X, b.Max.Y)
}
func printGlyph(g *truetype.GlyphBuf) {
printBounds(g.B)
printBounds(g.Bounds)
fmt.Print("Points:\n---\n")
e := 0
for i, p := range g.Point {

View file

@ -165,10 +165,10 @@ func (c *Context) rasterize(glyph truetype.Index, fx, fy fixed.Int26_6) (
return 0, nil, image.Point{}, err
}
// Calculate the integer-pixel bounds for the glyph.
xmin := int(fx+c.glyphBuf.B.XMin) >> 6
ymin := int(fy-c.glyphBuf.B.YMax) >> 6
xmax := int(fx+c.glyphBuf.B.XMax+0x3f) >> 6
ymax := int(fy-c.glyphBuf.B.YMin+0x3f) >> 6
xmin := int(fx+c.glyphBuf.Bounds.Min.X) >> 6
ymin := int(fy-c.glyphBuf.Bounds.Max.Y) >> 6
xmax := int(fx+c.glyphBuf.Bounds.Max.X+0x3f) >> 6
ymax := int(fy-c.glyphBuf.Bounds.Min.Y+0x3f) >> 6
if xmin > xmax || ymin > ymax {
return 0, nil, image.Point{}, errors.New("freetype: negative sized glyph")
}
@ -266,10 +266,10 @@ func (c *Context) recalc() {
} else {
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
b := c.f.Bounds(c.scale)
xmin := +int(b.XMin) >> 6
ymin := -int(b.YMax) >> 6
xmax := +int(b.XMax+63) >> 6
ymax := -int(b.YMin-63) >> 6
xmin := +int(b.Min.X) >> 6
ymin := -int(b.Max.Y) >> 6
xmax := +int(b.Max.X+63) >> 6
ymax := -int(b.Min.Y-63) >> 6
c.r.SetBounds(xmax-xmin, ymax-ymin)
}
for i := range c.cache {

View file

@ -188,10 +188,10 @@ func NewFace(f *Font, opts *Options) font.Face {
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
b := f.Bounds(a.scale)
xmin := +int(b.XMin) >> 6
ymin := -int(b.YMax) >> 6
xmax := +int(b.XMax+63) >> 6
ymax := -int(b.YMin-63) >> 6
xmin := +int(b.Min.X) >> 6
ymin := -int(b.Max.Y) >> 6
xmax := +int(b.Max.X+63) >> 6
ymax := -int(b.Min.Y-63) >> 6
a.maxw = xmax - xmin
a.maxh = ymax - ymin
a.masks = image.NewAlpha(image.Rect(0, 0, a.maxw, a.maxh*len(a.cache)))
@ -291,10 +291,10 @@ func (a *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.In
if err := a.glyphBuf.Load(a.f, a.scale, a.f.Index(r), a.hinting); err != nil {
return fixed.Rectangle26_6{}, 0, false
}
xmin := +a.glyphBuf.B.XMin
ymin := -a.glyphBuf.B.YMax
xmax := +a.glyphBuf.B.XMax
ymax := -a.glyphBuf.B.YMin
xmin := +a.glyphBuf.Bounds.Min.X
ymin := -a.glyphBuf.Bounds.Max.Y
xmax := +a.glyphBuf.Bounds.Max.X
ymax := -a.glyphBuf.Bounds.Min.Y
if xmin > xmax || ymin > ymax {
return fixed.Rectangle26_6{}, 0, false
}
@ -326,10 +326,10 @@ func (a *face) rasterize(index Index, fx, fy fixed.Int26_6) (v cacheVal, ok bool
return cacheVal{}, false
}
// Calculate the integer-pixel bounds for the glyph.
xmin := int(fx+a.glyphBuf.B.XMin) >> 6
ymin := int(fy-a.glyphBuf.B.YMax) >> 6
xmax := int(fx+a.glyphBuf.B.XMax+0x3f) >> 6
ymax := int(fy-a.glyphBuf.B.YMin+0x3f) >> 6
xmin := int(fx+a.glyphBuf.Bounds.Min.X) >> 6
ymin := int(fy-a.glyphBuf.Bounds.Max.Y) >> 6
xmax := int(fx+a.glyphBuf.Bounds.Max.X+0x3f) >> 6
ymax := int(fy-a.glyphBuf.Bounds.Min.Y+0x3f) >> 6
if xmin > xmax || ymin > ymax {
return cacheVal{}, false
}

View file

@ -26,8 +26,8 @@ type Point struct {
type GlyphBuf struct {
// AdvanceWidth is the glyph's advance width.
AdvanceWidth fixed.Int26_6
// B is the glyph's bounding box.
B Bounds
// Bounds is the glyph's bounding box.
Bounds fixed.Rectangle26_6
// Point contains all Points from all contours of the glyph. If
// hinting was used to load a glyph then Unhinted contains those
// Points before they were hinted, and InFontUnits contains those
@ -131,40 +131,40 @@ func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h font.Hinting) e
}
g.AdvanceWidth = advanceWidth
// Set g.B to the 'control box', which is the bounding box of the Bézier
// curves' control points. This is easier to calculate, no smaller than
// and often equal to the tightest possible bounding box of the curves
// Set g.Bounds to the 'control box', which is the bounding box of the
// Bézier curves' control points. This is easier to calculate, no smaller
// than and often equal to the tightest possible bounding box of the curves
// themselves. This approach is what C Freetype does. We can't just scale
// the nominal bounding box in the glyf data as the hinting process and
// phantom point adjustment may move points outside of that box.
if len(g.Point) == 0 {
g.B = Bounds{}
g.Bounds = fixed.Rectangle26_6{}
} else {
p := g.Point[0]
g.B.XMin = p.X
g.B.XMax = p.X
g.B.YMin = p.Y
g.B.YMax = p.Y
g.Bounds.Min.X = p.X
g.Bounds.Max.X = p.X
g.Bounds.Min.Y = p.Y
g.Bounds.Max.Y = p.Y
for _, p := range g.Point[1:] {
if g.B.XMin > p.X {
g.B.XMin = p.X
} else if g.B.XMax < p.X {
g.B.XMax = p.X
if g.Bounds.Min.X > p.X {
g.Bounds.Min.X = p.X
} else if g.Bounds.Max.X < p.X {
g.Bounds.Max.X = p.X
}
if g.B.YMin > p.Y {
g.B.YMin = p.Y
} else if g.B.YMax < p.Y {
g.B.YMax = p.Y
if g.Bounds.Min.Y > p.Y {
g.Bounds.Min.Y = p.Y
} else if g.Bounds.Max.Y < p.Y {
g.Bounds.Max.Y = p.Y
}
}
// Snap the box to the grid, if hinting is on.
if h != font.HintingNone {
g.B.XMin &^= 63
g.B.YMin &^= 63
g.B.XMax += 63
g.B.XMax &^= 63
g.B.YMax += 63
g.B.YMax &^= 63
g.Bounds.Min.X &^= 63
g.Bounds.Min.Y &^= 63
g.Bounds.Max.X += 63
g.Bounds.Max.X &^= 63
g.Bounds.Max.Y += 63
g.Bounds.Max.Y &^= 63
}
}
return nil

View file

@ -26,12 +26,6 @@ import (
// An Index is a Font's index of a rune.
type Index uint16
// A Bounds holds the co-ordinate range of one or more glyphs.
// The endpoints are inclusive.
type Bounds struct {
XMin, YMin, XMax, YMax fixed.Int26_6
}
// An HMetric holds the horizontal metrics of a single glyph.
type HMetric struct {
AdvanceWidth, LeftSideBearing fixed.Int26_6
@ -108,7 +102,7 @@ type Font struct {
locaOffsetFormat int
nGlyph, nHMetric, nKern int
fUnitsPerEm int32
bounds Bounds
bounds fixed.Rectangle26_6
// Values from the maxp section.
maxTwilightPoints, maxStorage, maxFunctionDefs, maxStackElements uint16
}
@ -227,10 +221,10 @@ func (f *Font) parseHead() error {
return FormatError(fmt.Sprintf("bad head length: %d", len(f.head)))
}
f.fUnitsPerEm = int32(u16(f.head, 18))
f.bounds.XMin = fixed.Int26_6(int16(u16(f.head, 36)))
f.bounds.YMin = fixed.Int26_6(int16(u16(f.head, 38)))
f.bounds.XMax = fixed.Int26_6(int16(u16(f.head, 40)))
f.bounds.YMax = fixed.Int26_6(int16(u16(f.head, 42)))
f.bounds.Min.X = fixed.Int26_6(int16(u16(f.head, 36)))
f.bounds.Min.Y = fixed.Int26_6(int16(u16(f.head, 38)))
f.bounds.Max.X = fixed.Int26_6(int16(u16(f.head, 40)))
f.bounds.Max.Y = fixed.Int26_6(int16(u16(f.head, 42)))
switch i := u16(f.head, 50); i {
case 0:
f.locaOffsetFormat = locaOffsetFormatShort
@ -317,12 +311,12 @@ func (f *Font) scale(x fixed.Int26_6) fixed.Int26_6 {
}
// Bounds returns the union of a Font's glyphs' bounds.
func (f *Font) Bounds(scale fixed.Int26_6) Bounds {
func (f *Font) Bounds(scale fixed.Int26_6) fixed.Rectangle26_6 {
b := f.bounds
b.XMin = f.scale(scale * b.XMin)
b.YMin = f.scale(scale * b.YMin)
b.XMax = f.scale(scale * b.XMax)
b.YMax = f.scale(scale * b.YMax)
b.Min.X = f.scale(scale * b.Min.X)
b.Min.Y = f.scale(scale * b.Min.Y)
b.Max.X = f.scale(scale * b.Max.X)
b.Max.Y = f.scale(scale * b.Max.Y)
return b
}

View file

@ -33,6 +33,19 @@ func parseTestdataFont(name string) (f *Font, testdataIsOptional bool, err error
return f, false, nil
}
func mkBounds(minX, minY, maxX, maxY fixed.Int26_6) fixed.Rectangle26_6 {
return fixed.Rectangle26_6{
Min: fixed.Point26_6{
X: minX,
Y: minY,
},
Max: fixed.Point26_6{
X: maxX,
Y: maxY,
},
}
}
// TestParse tests that the luxisr.ttf metrics and glyphs are parsed correctly.
// The numerical values can be manually verified by examining luxisr.ttx.
func TestParse(t *testing.T) {
@ -44,7 +57,7 @@ func TestParse(t *testing.T) {
t.Errorf("FUnitsPerEm: got %v, want %v", got, want)
}
fupe := fixed.Int26_6(f.FUnitsPerEm())
if got, want := f.Bounds(fupe), (Bounds{-441, -432, 2024, 2033}); got != want {
if got, want := f.Bounds(fupe), mkBounds(-441, -432, 2024, 2033); got != want {
t.Errorf("Bounds: got %v, want %v", got, want)
}
@ -69,12 +82,12 @@ func TestParse(t *testing.T) {
t.Fatalf("Load: %v", err)
}
g0 := &GlyphBuf{
B: g.B,
Point: g.Point,
End: g.End,
Bounds: g.Bounds,
Point: g.Point,
End: g.End,
}
g1 := &GlyphBuf{
B: Bounds{19, 0, 1342, 1480},
Bounds: mkBounds(19, 0, 1342, 1480),
Point: []Point{
{19, 0, 51},
{581, 1480, 1},
@ -200,7 +213,7 @@ func TestIndex(t *testing.T) {
type scalingTestData struct {
advanceWidth fixed.Int26_6
bounds Bounds
bounds fixed.Rectangle26_6
points []Point
}
@ -221,10 +234,10 @@ func scalingTestParse(line string) (ret scalingTestData) {
prefix, line := line[:i], line[i+1:]
prefix, ret.advanceWidth = next(prefix)
prefix, ret.bounds.XMin = next(prefix)
prefix, ret.bounds.YMin = next(prefix)
prefix, ret.bounds.XMax = next(prefix)
prefix, ret.bounds.YMax = next(prefix)
prefix, ret.bounds.Min.X = next(prefix)
prefix, ret.bounds.Min.Y = next(prefix)
prefix, ret.bounds.Max.X = next(prefix)
prefix, ret.bounds.Max.Y = next(prefix)
ret.points = make([]Point, 0, 1+strings.Count(line, ","))
for len(line) > 0 {
@ -327,7 +340,7 @@ func testScaling(t *testing.T, h font.Hinting) {
}
got := scalingTestData{
advanceWidth: glyphBuf.AdvanceWidth,
bounds: glyphBuf.B,
bounds: glyphBuf.Bounds,
points: glyphBuf.Point,
}