From 3cc748686ba03f41287913b55e1452f83bc0414d Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Sun, 30 Aug 2015 22:06:37 +1000 Subject: [PATCH] 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. --- example/truetype/main.go | 6 ++--- freetype.go | 16 ++++++------- truetype/face.go | 24 ++++++++++---------- truetype/glyph.go | 48 +++++++++++++++++++-------------------- truetype/truetype.go | 26 ++++++++------------- truetype/truetype_test.go | 35 +++++++++++++++++++--------- 6 files changed, 81 insertions(+), 74 deletions(-) diff --git a/example/truetype/main.go b/example/truetype/main.go index d188d9b..af98224 100644 --- a/example/truetype/main.go +++ b/example/truetype/main.go @@ -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 { diff --git a/freetype.go b/freetype.go index a1d4ad1..0ae56ad 100644 --- a/freetype.go +++ b/freetype.go @@ -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 { diff --git a/truetype/face.go b/truetype/face.go index c8a41e9..dc66675 100644 --- a/truetype/face.go +++ b/truetype/face.go @@ -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 } diff --git a/truetype/glyph.go b/truetype/glyph.go index 7a4946f..411e1b7 100644 --- a/truetype/glyph.go +++ b/truetype/glyph.go @@ -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 diff --git a/truetype/truetype.go b/truetype/truetype.go index 7e0a1e7..fb39d52 100644 --- a/truetype/truetype.go +++ b/truetype/truetype.go @@ -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 } diff --git a/truetype/truetype_test.go b/truetype/truetype_test.go index 53b81dc..b7cc0ad 100644 --- a/truetype/truetype_test.go +++ b/truetype/truetype_test.go @@ -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, }