freetype/truetype: track phantom points across compound glyphs.

The very subtle pp1x versus phantomPoints[0].X distinction is pretty
ugly code, but let's get all the tests passing first, so that we can
refactor with confidence.

R=bsiegert
CC=golang-dev, remyoudompheng
https://codereview.appspot.com/35850043
This commit is contained in:
Nigel Tao 2013-12-03 08:21:05 +11:00
parent 5cb0a99dcd
commit 11bef13aa5
2 changed files with 42 additions and 18 deletions

View File

@ -33,7 +33,12 @@ type GlyphBuf struct {
font *Font
hinter *Hinter
scale int32
// pp1x is the X co-ordinate of the first phantom point.
// phantomPoints are the co-ordinates of the synthetic phantom points
// used for hinting and bounding box calculations.
phantomPoints [4]Point
// pp1x is the X co-ordinate of the first phantom point. The '1' is
// using 1-based indexing; pp1x is almost always phantomPoints[0].X.
// TODO: eliminate this and consistently use phantomPoints[0].X.
pp1x int32
// metricsSet is whether the glyph's metrics have been set yet. For a
// compound glyph, a sub-glyph may override the outer glyph's metrics.
@ -78,6 +83,7 @@ func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h *Hinter) error {
g.hinter = h
g.scale = scale
g.pp1x = 0
g.phantomPoints = [4]Point{}
g.metricsSet = false
if h != nil {
@ -88,9 +94,16 @@ func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h *Hinter) error {
if err := g.load(0, i, true); err != nil {
return err
}
if g.pp1x != 0 {
// TODO: this selection of either g.pp1x or g.phantomPoints[0].X isn't ideal,
// and should be cleaned up once we have all the testScaling tests passing,
// plus additional tests for Freetype-Go's bounding boxes matching C Freetype's.
pp1x := g.pp1x
if h != nil {
pp1x = g.phantomPoints[0].X
}
if pp1x != 0 {
for i := range g.Point {
g.Point[i].X -= g.pp1x
g.Point[i].X -= pp1x
}
// TODO: also adjust g.B?
}
@ -124,6 +137,13 @@ func (g *GlyphBuf) load(recursion int32, i Index, useMyMetrics bool) (err error)
YMax: int32(int16(u16(glyf, 8))),
}
uhm, pp1x := g.font.unscaledHMetric(i), int32(0)
uvm := g.font.unscaledVMetric(i, b.YMax)
g.phantomPoints = [4]Point{
{X: b.XMin - uhm.LeftSideBearing},
{X: b.XMin - uhm.LeftSideBearing + uhm.AdvanceWidth},
{X: uhm.AdvanceWidth / 2, Y: b.YMax + uvm.TopSideBearing},
{X: uhm.AdvanceWidth / 2, Y: b.YMax + uvm.TopSideBearing - uvm.AdvanceHeight},
}
if ne < 0 {
if ne != -1 {
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that
@ -137,7 +157,7 @@ func (g *GlyphBuf) load(recursion int32, i Index, useMyMetrics bool) (err error)
} else {
np0, ne0 := len(g.Point), len(g.End)
program := g.loadSimple(glyf, ne)
g.addPhantomsAndScale(b, uhm, i, np0, true)
g.addPhantomsAndScale(b, np0, np0, true)
pp1x = g.Point[len(g.Point)-4].X
if g.hinter != nil {
if len(program) != 0 {
@ -156,6 +176,7 @@ func (g *GlyphBuf) load(recursion int32, i Index, useMyMetrics bool) (err error)
g.InFontUnits = g.InFontUnits[:len(g.InFontUnits)-4]
g.Unhinted = g.Unhinted[:len(g.Unhinted)-4]
}
copy(g.phantomPoints[:], g.Point[len(g.Point)-4:])
g.Point = g.Point[:len(g.Point)-4]
if np0 != 0 {
// The hinting program expects the []End values to be indexed relative
@ -307,11 +328,15 @@ func (g *GlyphBuf) loadCompound(recursion int32, b Bounds, uhm HMetric, i Index,
offset += 8
}
}
savedPP := g.phantomPoints
np0 := len(g.Point)
componentUMM := useMyMetrics && (flags&flagUseMyMetrics != 0)
if err := g.load(recursion+1, component, componentUMM); err != nil {
return err
}
if flags&flagUseMyMetrics == 0 {
g.phantomPoints = savedPP
}
if hasTransform {
for j := np0; j < len(g.Point); j++ {
p := &g.Point[j]
@ -349,7 +374,7 @@ func (g *GlyphBuf) loadCompound(recursion int32, b Bounds, uhm HMetric, i Index,
return nil
}
program := glyf[offset : offset+instrLen]
g.addPhantomsAndScale(b, uhm, i, len(g.Point), false)
g.addPhantomsAndScale(b, np0, len(g.Point), false)
points, ends := g.Point[np0:], g.End[ne0:]
g.Point = g.Point[:len(g.Point)-4]
for j := range points {
@ -372,36 +397,35 @@ func (g *GlyphBuf) loadCompound(recursion int32, b Bounds, uhm HMetric, i Index,
ends[i] += np0
}
}
copy(g.phantomPoints[:], points[len(points)-4:])
return nil
}
func (g *GlyphBuf) addPhantomsAndScale(b Bounds, uhm HMetric, i Index, np0 int, simple bool) {
func (g *GlyphBuf) addPhantomsAndScale(b Bounds, np0, np1 int, simple bool) {
// Add the four phantom points.
uvm := g.font.unscaledVMetric(i, b.YMax)
g.Point = append(g.Point,
Point{X: b.XMin - uhm.LeftSideBearing},
Point{X: b.XMin - uhm.LeftSideBearing + uhm.AdvanceWidth},
Point{X: uhm.AdvanceWidth / 2, Y: b.YMax + uvm.TopSideBearing},
Point{X: uhm.AdvanceWidth / 2, Y: b.YMax + uvm.TopSideBearing - uvm.AdvanceHeight},
)
g.Point = append(g.Point, g.phantomPoints[:]...)
// Scale the points.
if simple && g.hinter != nil {
g.InFontUnits = append(g.InFontUnits, g.Point[np0:]...)
g.InFontUnits = append(g.InFontUnits, g.Point[np1:]...)
}
for i := np0; i < len(g.Point); i++ {
for i := np1; i < len(g.Point); i++ {
p := &g.Point[i]
p.X = g.font.scale(g.scale * p.X)
p.Y = g.font.scale(g.scale * p.Y)
}
if simple && g.hinter != nil {
if g.hinter != nil {
// Round the 1st phantom point to the grid, shifting all other points equally.
// Note that "all other points" starts from np0, not np1.
// TODO: is the np0/np1 distinction actually a bug in C Freetype?
pp1x := g.Point[len(g.Point)-4].X
if dx := ((pp1x + 32) &^ 63) - pp1x; dx != 0 {
for i := np0; i < len(g.Point); i++ {
g.Point[i].X += dx
}
}
g.Unhinted = append(g.Unhinted, g.Point[np0:]...)
if simple {
g.Unhinted = append(g.Unhinted, g.Point[np1:]...)
}
}
// Round the 2nd and 4th phantom point to the grid.
p := &g.Point[len(g.Point)-3]

View File

@ -255,7 +255,7 @@ var scalingTestCases = []struct {
hintingBrokenAt int
}{
{"luxisr", 12, -1},
{"x-arial-bold", 11, 274},
{"x-arial-bold", 11, 1162},
{"x-deja-vu-sans-oblique", 17, -1},
{"x-droid-sans-japanese", 9, 0},
{"x-times-new-roman", 13, 0},