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:
parent
7a598da6de
commit
3cc748686b
6 changed files with 81 additions and 74 deletions
|
@ -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 {
|
||||
|
|
16
freetype.go
16
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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue