From c8094ec963b12420a55651ce27279cb08578a99f Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Wed, 16 Oct 2013 18:54:54 +1100 Subject: [PATCH] freetype/truetype: refactor glyph.go; match C Freetype's rounding on one more test case. R=bsiegert CC=golang-dev https://codereview.appspot.com/14691043 --- freetype/truetype/glyph.go | 430 ++++++++++++++--------------- freetype/truetype/truetype_test.go | 15 +- 2 files changed, 213 insertions(+), 232 deletions(-) diff --git a/freetype/truetype/glyph.go b/freetype/truetype/glyph.go index 5eed49f..cbb3794 100644 --- a/freetype/truetype/glyph.go +++ b/freetype/truetype/glyph.go @@ -29,6 +29,13 @@ type GlyphBuf struct { // contour consists of points Point[End[i-1]:End[i]], where End[-1] // is interpreted to mean zero. End []int + + font *Font + hinter *Hinter + scale int32 + // pp1x is the X co-ordinate of the first phantom point. + pp1x int32 + metricsSet bool } // Flags for decoding a glyph's contours. These flags are documented at @@ -53,33 +60,180 @@ const ( flagThisYIsSame = flagPositiveYShortVector ) -// decodeFlags decodes a glyph's run-length encoded flags, -// and returns the remaining data. -func (g *GlyphBuf) decodeFlags(d []byte, offset int, np0, np int) (offset1 int) { - for i := np0; i < np; { - c := uint32(d[offset]) +// Load loads a glyph's contours from a Font, overwriting any previously +// loaded contours for this GlyphBuf. scale is the number of 26.6 fixed point +// units in 1 em. The Hinter is optional; if non-nil, then the resulting glyph +// will be hinted by the Font's bytecode instructions. +func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h *Hinter) error { + g.B = Bounds{} + g.Point = g.Point[:0] + g.Unhinted = g.Unhinted[:0] + g.InFontUnits = g.InFontUnits[:0] + g.End = g.End[:0] + g.font = f + g.hinter = h + g.scale = scale + g.pp1x = 0 + g.metricsSet = false + + if h != nil { + if err := h.init(f, scale); err != nil { + return err + } + } + if err := g.load(0, i, true); err != nil { + return err + } + if g.pp1x != 0 { + for i := range g.Point { + g.Point[i].X -= g.pp1x + } + // TODO: also adjust g.B? + } + return nil +} + +func (g *GlyphBuf) load(recursion int32, i Index, useMyMetrics bool) (err error) { + if recursion >= 4 { + return UnsupportedError("excessive compound glyph recursion") + } + // Find the relevant slice of g.font.glyf. + var g0, g1 uint32 + if g.font.locaOffsetFormat == locaOffsetFormatShort { + g0 = 2 * uint32(u16(g.font.loca, 2*int(i))) + g1 = 2 * uint32(u16(g.font.loca, 2*int(i)+2)) + } else { + g0 = u32(g.font.loca, 4*int(i)) + g1 = u32(g.font.loca, 4*int(i)+4) + } + if g0 == g1 { + return nil + } + glyf := g.font.glyf[g0:g1] + // Decode the contour end indices. + ne := int(int16(u16(glyf, 0))) + b := Bounds{ + XMin: int32(int16(u16(glyf, 2))), + YMin: int32(int16(u16(glyf, 4))), + XMax: int32(int16(u16(glyf, 6))), + YMax: int32(int16(u16(glyf, 8))), + } + uhm, pp1x := g.font.unscaledHMetric(i), int32(0) + if ne < 0 { + if ne != -1 { + // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that + // "the values -2, -3, and so forth, are reserved for future use." + return UnsupportedError("negative number of contours") + } + pp1x = g.font.scale(g.scale * (b.XMin - uhm.LeftSideBearing)) + if err := g.loadCompound(recursion, glyf); err != nil { + return err + } + } else { + np0, ne0 := len(g.Point), len(g.End) + program := g.loadSimple(glyf, ne) + // Set the four phantom points. Freetype-Go uses only the first two, + // but the hinting bytecode may expect four. + g.Point = append(g.Point, + Point{X: b.XMin - uhm.LeftSideBearing}, + Point{X: b.XMin - uhm.LeftSideBearing + uhm.AdvanceWidth}, + Point{}, + Point{}, + ) + // Scale and hint the glyph. + if g.hinter != nil { + g.InFontUnits = append(g.InFontUnits, g.Point[np0:]...) + } + for i := np0; 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 g.hinter != nil { + g.Unhinted = append(g.Unhinted, g.Point[np0:]...) + if len(program) != 0 { + err := g.hinter.run( + program, + g.Point[np0:], + g.Unhinted[np0:], + g.InFontUnits[np0:], + g.End[ne0:], + ) + if err != nil { + return err + } + } + } + // Drop the four phantom points. + pp1x = g.Point[len(g.Point)-4].X + g.Point = g.Point[:len(g.Point)-4] + if g.hinter != nil { + g.InFontUnits = g.InFontUnits[:len(g.InFontUnits)-4] + g.Unhinted = g.Unhinted[:len(g.Unhinted)-4] + } + if np0 != 0 { + // The hinting program expects the []End values to be indexed relative + // to the inner glyph, not the outer glyph, so we delay adding np0 until + // after the hinting program (if any) has run. + for i := ne0; i < len(g.End); i++ { + g.End[i] += np0 + } + } + } + if useMyMetrics && !g.metricsSet { + g.metricsSet = true + g.B.XMin = g.font.scale(g.scale * b.XMin) + g.B.YMin = g.font.scale(g.scale * b.YMin) + g.B.XMax = g.font.scale(g.scale * b.XMax) + g.B.YMax = g.font.scale(g.scale * b.YMax) + g.pp1x = pp1x + } + return nil +} + +// loadOffset is the initial offset for loadSimple and loadCompound. The first +// 10 bytes are the number of contours and the bounding box. +const loadOffset = 10 + +func (g *GlyphBuf) loadSimple(glyf []byte, ne int) (program []byte) { + offset := loadOffset + + for i := 0; i < ne; i++ { + g.End = append(g.End, 1+int(u16(glyf, offset))) + offset += 2 + } + + // Note the TrueType hinting instructions. + instrLen := int(u16(glyf, offset)) + offset += 2 + program = glyf[offset : offset+instrLen] + offset += instrLen + + np0 := len(g.Point) + np1 := np0 + int(g.End[len(g.End)-1]) + + // Decode the flags. + for i := np0; i < np1; { + c := uint32(glyf[offset]) offset++ - g.Point[i].Flags = c + g.Point = append(g.Point, Point{Flags: c}) i++ if c&flagRepeat != 0 { - count := d[offset] + count := glyf[offset] offset++ for ; count > 0; count-- { - g.Point[i].Flags = c + g.Point = append(g.Point, Point{Flags: c}) i++ } } } - return offset -} -// decodeCoords decodes a glyph's delta encoded co-ordinates. -func (g *GlyphBuf) decodeCoords(d []byte, offset int, np0, np int) int { + // Decode the co-ordinates. var x int16 - for i := np0; i < np; i++ { + for i := np0; i < np1; i++ { f := g.Point[i].Flags if f&flagXShortVector != 0 { - dx := int16(d[offset]) + dx := int16(glyf[offset]) offset++ if f&flagPositiveXShortVector == 0 { x -= dx @@ -87,16 +241,16 @@ func (g *GlyphBuf) decodeCoords(d []byte, offset int, np0, np int) int { x += dx } } else if f&flagThisXIsSame == 0 { - x += int16(u16(d, offset)) + x += int16(u16(glyf, offset)) offset += 2 } g.Point[i].X = int32(x) } var y int16 - for i := np0; i < np; i++ { + for i := np0; i < np1; i++ { f := g.Point[i].Flags if f&flagYShortVector != 0 { - dy := int16(d[offset]) + dy := int16(glyf[offset]) offset++ if f&flagPositiveYShortVector == 0 { y -= dy @@ -104,51 +258,16 @@ func (g *GlyphBuf) decodeCoords(d []byte, offset int, np0, np int) int { y += dy } } else if f&flagThisYIsSame == 0 { - y += int16(u16(d, offset)) + y += int16(u16(glyf, offset)) offset += 2 } g.Point[i].Y = int32(y) } - return offset + + return program } -// Load loads a glyph's contours from a Font, overwriting any previously -// loaded contours for this GlyphBuf. scale is the number of 26.6 fixed point -// units in 1 em. The Hinter is optional; if non-nil, then the resulting glyph -// will be hinted by the Font's bytecode instructions. -func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h *Hinter) error { - // Reset the GlyphBuf. - g.B = Bounds{} - g.Point = g.Point[:0] - g.Unhinted = g.Unhinted[:0] - g.InFontUnits = g.InFontUnits[:0] - g.End = g.End[:0] - if h != nil { - if err := h.init(f, scale); err != nil { - return err - } - } - if _, err := g.load(f, scale, i, h, 0, 0, false, 0); err != nil { - return err - } - g.B.XMin = f.scale(scale * g.B.XMin) - g.B.YMin = f.scale(scale * g.B.YMin) - g.B.XMax = f.scale(scale * g.B.XMax) - g.B.YMax = f.scale(scale * g.B.YMax) - return nil -} - -// TODO: all these extra parameters and return values for loadCompound and load -// are awkward. We should clean this up once all the tests pass, when we can -// refactor with confidence that we don't break anything. - -// loadCompound loads a glyph that is composed of other glyphs. -// -// metricsOverride is whether the sub-glyph overrides the super-glyph's -// metrics. pp1x is the x co-ordinate of the 1st phantom point. -func (g *GlyphBuf) loadCompound(f *Font, scale int32, h *Hinter, glyf []byte, offset int, - dx, dy int32, recursion int) (metricsOverride bool, pp1x int32, offset1 int, err error) { - +func (g *GlyphBuf) loadCompound(recursion int32, glyf []byte) error { // Flags for decoding a compound glyph. These flags are documented at // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html. const ( @@ -164,196 +283,55 @@ func (g *GlyphBuf) loadCompound(f *Font, scale int32, h *Hinter, glyf []byte, of flagUseMyMetrics flagOverlapCompound ) - for { + for offset := loadOffset; ; { flags := u16(glyf, offset) component := Index(u16(glyf, offset+2)) - dx1, dy1 := dx, dy + dx, dy := int32(0), int32(0) if flags&flagArg1And2AreWords != 0 { - dx1 += int32(int16(u16(glyf, offset+4))) - dy1 += int32(int16(u16(glyf, offset+6))) + dx = int32(int16(u16(glyf, offset+4))) + dy = int32(int16(u16(glyf, offset+6))) offset += 8 } else { - dx1 += int32(int16(int8(glyf[offset+4]))) - dy1 += int32(int16(int8(glyf[offset+5]))) + dx = int32(int16(int8(glyf[offset+4]))) + dy = int32(int16(int8(glyf[offset+5]))) offset += 6 } if flags&flagArgsAreXYValues == 0 { - return false, 0, 0, UnsupportedError("compound glyph transform vector") + return UnsupportedError("compound glyph transform vector") } if flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0 { - return false, 0, 0, UnsupportedError("compound glyph scale/transform") + return UnsupportedError("compound glyph scale/transform") } - b := g.B - subPP1x, err := g.load(f, scale, component, h, - dx1, dy1, flags&flagRoundXYToGrid != 0, recursion+1) - if err != nil { - return false, 0, 0, err + np0 := len(g.Point) + if err := g.load(recursion+1, component, flags&flagUseMyMetrics != 0); err != nil { + return err } - if flags&flagUseMyMetrics != 0 { - metricsOverride, pp1x = true, subPP1x - } else { - g.B = b + dx = g.font.scale(g.scale * dx) + dy = g.font.scale(g.scale * dy) + if flags&flagRoundXYToGrid != 0 { + dx = (dx + 32) &^ 63 + dy = (dy + 32) &^ 63 } + for i := np0; i < len(g.Point); i++ { + p := &g.Point[i] + p.X += dx + p.Y += dy + } + // TODO: also adjust g.InFontUnits and g.Unhinted? if flags&flagMoreComponents == 0 { break } } - return metricsOverride, pp1x, offset, nil + // TODO: hint the compound glyph. + return nil } -// load appends a glyph's contours to this GlyphBuf. -// -// pp1x is the x co-ordinate of the 1st phantom point. -func (g *GlyphBuf) load(f *Font, scale int32, i Index, h *Hinter, - dx, dy int32, roundDxDy bool, recursion int) (pp1x int32, err error) { - - if recursion >= 4 { - return 0, UnsupportedError("excessive compound glyph recursion") - } - // Find the relevant slice of f.glyf. - var g0, g1 uint32 - if f.locaOffsetFormat == locaOffsetFormatShort { - g0 = 2 * uint32(u16(f.loca, 2*int(i))) - g1 = 2 * uint32(u16(f.loca, 2*int(i)+2)) - } else { - g0 = u32(f.loca, 4*int(i)) - g1 = u32(f.loca, 4*int(i)+4) - } - if g0 == g1 { - return 0, nil - } - glyf := f.glyf[g0:g1] - // Decode the contour end indices. - ne := int(int16(u16(glyf, 0))) - b := Bounds{ - XMin: int32(int16(u16(glyf, 2))), - YMin: int32(int16(u16(glyf, 4))), - XMax: int32(int16(u16(glyf, 6))), - YMax: int32(int16(u16(glyf, 8))), - } - offset := 10 - ne0, np0, np, metricsOverride, program := len(g.End), 0, 0, false, []byte(nil) - if ne < 0 { - if ne != -1 { - // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that - // "the values -2, -3, and so forth, are reserved for future use." - return 0, UnsupportedError("negative number of contours") - } - var subPP1x int32 - metricsOverride, subPP1x, offset, err = - g.loadCompound(f, scale, h, glyf, offset, dx, dy, recursion) - if err != nil { - return 0, err - } - if metricsOverride { - pp1x = subPP1x - } - ne = ne0 - np0 = len(g.Point) - np = np0 - // TODO: find the program, if present, for a compound glyph. - - } else { - ne += ne0 - if ne <= cap(g.End) { - g.End = g.End[:ne] - } else { - g.End = make([]int, ne, ne*2) - } - for i := ne0; i < ne; i++ { - g.End[i] = 1 + int(u16(glyf, offset)) - offset += 2 - } - np0 = len(g.Point) - np = np0 + int(g.End[ne-1]) - - // Note the TrueType hinting instructions. - instrLen := int(u16(glyf, offset)) - offset += 2 - program = glyf[offset : offset+instrLen] - offset += instrLen - } - - // Decode the points, including room for the phantom points. - const nPhantomPoints = 4 - if np+nPhantomPoints <= cap(g.Point) { - g.Point = g.Point[:np+nPhantomPoints] - } else { - p := g.Point - g.Point = make([]Point, np+nPhantomPoints, (np+nPhantomPoints)*2) - copy(g.Point, p) - } - offset = g.decodeFlags(glyf, offset, np0, np) - g.decodeCoords(glyf, offset, np0, np) - - // Set the four phantom points. Freetype-Go uses only the first two, - // but the hinting bytecode may expect four. - g.B = b - uhm := f.unscaledHMetric(i) - g.Point[np+0] = Point{X: b.XMin - uhm.LeftSideBearing} - g.Point[np+1] = Point{X: b.XMin - uhm.LeftSideBearing + uhm.AdvanceWidth} - g.Point[np+2] = Point{} - g.Point[np+3] = Point{} - - // Delta-adjust, scale and hint. - if h != nil { - g.InFontUnits = append(g.InFontUnits, g.Point[np0:np+nPhantomPoints]...) - for i := np0; i < np+nPhantomPoints; i++ { - g.InFontUnits[i].X += dx - g.InFontUnits[i].Y += dy - } - } - scaledDx := int32(0) - if roundDxDy { - dx = (f.scale(scale*dx) + 32) &^ 63 - dy = (f.scale(scale*dy) + 32) &^ 63 - for i := np0; i < np+nPhantomPoints; i++ { - g.Point[i].X = dx + f.scale(scale*g.Point[i].X) - g.Point[i].Y = dy + f.scale(scale*g.Point[i].Y) - } - scaledDx = dx - } else { - for i := np0; i < np+nPhantomPoints; i++ { - g.Point[i].X = f.scale(scale * (g.Point[i].X + dx)) - g.Point[i].Y = f.scale(scale * (g.Point[i].Y + dy)) - } - scaledDx = f.scale(scale * dx) - } - if h != nil { - g.Unhinted = append(g.Unhinted, g.Point[np0:np+nPhantomPoints]...) - if program != nil { - err := h.run(program, g.Point[np0:], g.Unhinted[np0:], g.InFontUnits[np0:], g.End[ne0:]) - if err != nil { - return 0, err - } - } - g.Unhinted = g.Unhinted[:np] - g.InFontUnits = g.InFontUnits[:np] - } - if !metricsOverride { - pp1x = g.Point[np].X - scaledDx - } - g.Point = g.Point[:np] - if recursion == 0 && pp1x != 0 { - for i := range g.Point { - g.Point[i].X -= pp1x - } - } - - // The hinting program expects the []End values to be indexed relative - // to the inner glyph, not the outer glyph, so we delay adding np0 until - // after the hinting program (if any) has run. - for i := ne0; i < ne; i++ { - g.End[i] += np0 - } - - return pp1x, nil -} +// TODO: is this necessary? The zero-valued GlyphBuf is perfectly usable. // NewGlyphBuf returns a newly allocated GlyphBuf. func NewGlyphBuf() *GlyphBuf { - g := new(GlyphBuf) - g.Point = make([]Point, 0, 256) - g.End = make([]int, 0, 32) - return g + return &GlyphBuf{ + Point: make([]Point, 0, 256), + End: make([]int, 0, 32), + } } diff --git a/freetype/truetype/truetype_test.go b/freetype/truetype/truetype_test.go index 88ff7fb..54524da 100644 --- a/freetype/truetype/truetype_test.go +++ b/freetype/truetype/truetype_test.go @@ -57,11 +57,16 @@ func TestParse(t *testing.T) { t.Errorf("Kerning: got %v, want %v", got, want) } - g0 := NewGlyphBuf() - err = g0.Load(font, fupe, i0, nil) + g := NewGlyphBuf() + err = g.Load(font, fupe, i0, nil) if err != nil { t.Fatalf("Load: %v", err) } + g0 := &GlyphBuf{ + B: g.B, + Point: g.Point, + End: g.End, + } g1 := &GlyphBuf{ B: Bounds{19, 0, 1342, 1480}, Point: []Point{ @@ -255,6 +260,8 @@ var scalingTestCases = []struct { {"x-times-new-roman", 13, 0}, } +// TODO: also test bounding boxes, not just points. + func testScaling(t *testing.T, hinter *Hinter) { loop: for _, tc := range scalingTestCases { @@ -291,10 +298,6 @@ loop: glyphBuf := NewGlyphBuf() for i, want := range wants { - if tc.name == "x-times-new-roman" && i == 180 { - // TODO: figure out why Times New Roman glyph #180 is problematic. - continue - } // TODO: completely implement hinting. For now, only the first // tc.hintingBrokenAt glyphs of the test case's font are correctly hinted. if hinter != nil && i == tc.hintingBrokenAt {