From cdaff3c7166312fdbdcc5d7a5759893187d4a568 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Tue, 5 Nov 2013 09:58:40 +1100 Subject: [PATCH] freetype/truetype: run compound glyph hinting instructions. Also add API for vertical metrics. R=bsiegert CC=golang-dev, remyoudompheng https://codereview.appspot.com/21330043 --- freetype/truetype/glyph.go | 95 +++++++++++++++++++++--------- freetype/truetype/truetype.go | 41 +++++++++++-- freetype/truetype/truetype_test.go | 5 +- 3 files changed, 107 insertions(+), 34 deletions(-) diff --git a/freetype/truetype/glyph.go b/freetype/truetype/glyph.go index c6fc786..adf6b79 100644 --- a/freetype/truetype/glyph.go +++ b/freetype/truetype/glyph.go @@ -34,8 +34,12 @@ type GlyphBuf struct { hinter *Hinter scale int32 // pp1x is the X co-ordinate of the first phantom point. - pp1x int32 + 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. metricsSet bool + // tmp is a scratch buffer. + tmp []Point } // Flags for decoding a glyph's contours. These flags are documented at @@ -127,31 +131,14 @@ func (g *GlyphBuf) load(recursion int32, i Index, useMyMetrics bool) (err error) return UnsupportedError("negative number of contours") } pp1x = g.font.scale(g.scale * (b.XMin - uhm.LeftSideBearing)) - if err := g.loadCompound(recursion, glyf, useMyMetrics); err != nil { + if err := g.loadCompound(recursion, b, uhm, i, glyf, useMyMetrics); 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. + g.addPhantomsAndScale(b, uhm, i, np0, g.hinter != nil) 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, @@ -204,7 +191,6 @@ 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 @@ -274,7 +260,9 @@ func (g *GlyphBuf) loadSimple(glyf []byte, ne int) (program []byte) { return program } -func (g *GlyphBuf) loadCompound(recursion int32, glyf []byte, useMyMetrics bool) error { +func (g *GlyphBuf) loadCompound(recursion int32, b Bounds, uhm HMetric, i Index, + glyf []byte, useMyMetrics bool) error { + // Flags for decoding a compound glyph. These flags are documented at // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html. const ( @@ -290,7 +278,9 @@ func (g *GlyphBuf) loadCompound(recursion int32, glyf []byte, useMyMetrics bool) flagUseMyMetrics flagOverlapCompound ) - for offset := loadOffset; ; { + np0, ne0 := len(g.Point), len(g.End) + offset := loadOffset + for { flags := u16(glyf, offset) component := Index(u16(glyf, offset+2)) dx, dy, transform, hasTransform := int32(0), int32(0), [4]int32{}, false @@ -331,8 +321,8 @@ func (g *GlyphBuf) loadCompound(recursion int32, glyf []byte, useMyMetrics bool) return err } if hasTransform { - for i := np0; i < len(g.Point); i++ { - p := &g.Point[i] + for j := np0; j < len(g.Point); j++ { + p := &g.Point[j] newX := int32((int64(p.X)*int64(transform[0])+1<<13)>>14) + int32((int64(p.Y)*int64(transform[2])+1<<13)>>14) newY := int32((int64(p.X)*int64(transform[1])+1<<13)>>14) + @@ -346,8 +336,8 @@ func (g *GlyphBuf) loadCompound(recursion int32, glyf []byte, useMyMetrics bool) dx = (dx + 32) &^ 63 dy = (dy + 32) &^ 63 } - for i := np0; i < len(g.Point); i++ { - p := &g.Point[i] + for j := np0; j < len(g.Point); j++ { + p := &g.Point[j] p.X += dx p.Y += dy } @@ -356,8 +346,55 @@ func (g *GlyphBuf) loadCompound(recursion int32, glyf []byte, useMyMetrics bool) break } } - // TODO: hint the compound glyph. - return nil + + // Hint the compound glyph. + if g.hinter == nil || offset+2 > len(glyf) { + return nil + } + instrLen := int(u16(glyf, offset)) + offset += 2 + if instrLen == 0 { + return nil + } + program := glyf[offset : offset+instrLen] + g.addPhantomsAndScale(b, uhm, i, len(g.Point), false) + points := g.Point[np0:] + g.Point = g.Point[:len(g.Point)-4] + for j := range points { + points[j].Flags &^= flagTouchedX | flagTouchedY + } + // Hinting instructions of a composite glyph completely refer to the + // (already) hinted subglyphs. + g.tmp = append(g.tmp[:0], points...) + return g.hinter.run(program, points, g.tmp, g.tmp, g.End[ne0:]) +} + +func (g *GlyphBuf) addPhantomsAndScale(b Bounds, uhm HMetric, i Index, np0 int, appendOther bool) { + // Add the four phantom points. + uvm := g.font.unscaledVMetric(i) + g.Point = append(g.Point, + Point{X: b.XMin - uhm.LeftSideBearing}, + Point{X: b.XMin - uhm.LeftSideBearing + uhm.AdvanceWidth}, + Point{Y: b.YMax + uvm.TopSideBearing}, + Point{Y: b.YMax + uvm.TopSideBearing - uvm.AdvanceHeight}, + ) + // Scale the points. + if appendOther { + 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 appendOther { + g.Unhinted = append(g.Unhinted, g.Point[np0:]...) + } + // Round the 2nd and 4th phantom point to the grid. + p := &g.Point[len(g.Point)-3] + p.X = (p.X + 32) &^ 63 + p = &g.Point[len(g.Point)-1] + p.Y = (p.Y + 32) &^ 63 } // TODO: is this necessary? The zero-valued GlyphBuf is perfectly usable. diff --git a/freetype/truetype/truetype.go b/freetype/truetype/truetype.go index 60453cd..5b9e654 100644 --- a/freetype/truetype/truetype.go +++ b/freetype/truetype/truetype.go @@ -33,8 +33,12 @@ type Bounds struct { // An HMetric holds the horizontal metrics of a single glyph. type HMetric struct { - AdvanceWidth int32 - LeftSideBearing int32 + AdvanceWidth, LeftSideBearing int32 +} + +// A VMetric holds the vertical metrics of a single glyph. +type VMetric struct { + AdvanceHeight, TopSideBearing int32 } // A FormatError reports that the input is not a valid TrueType font. @@ -94,7 +98,7 @@ type cm struct { type Font struct { // Tables sliced from the TTF data. The different tables are documented // at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html - cmap, cvt, fpgm, glyf, head, hhea, hmtx, kern, loca, maxp, prep []byte + cmap, cvt, fpgm, glyf, head, hhea, hmtx, kern, loca, maxp, prep, vmtx []byte cmapIndexes []byte @@ -344,7 +348,7 @@ func (f *Font) Index(x rune) Index { // the given index. func (f *Font) unscaledHMetric(i Index) (h HMetric) { j := int(i) - if j >= f.nGlyph { + if j < 0 || f.nGlyph <= j { return HMetric{} } if j >= f.nHMetric { @@ -368,6 +372,33 @@ func (f *Font) HMetric(scale int32, i Index) HMetric { return h } +// unscaledVMetric returns the unscaled vertical metrics for the glyph with +// the given index. +func (f *Font) unscaledVMetric(i Index) (v VMetric) { + j := int(i) + if j < 0 || f.nGlyph <= j { + return VMetric{} + } + if 4*j+4 <= len(f.vmtx) { + return VMetric{ + AdvanceHeight: int32(u16(f.vmtx, 4*j)), + TopSideBearing: int32(int16(u16(f.vmtx, 4*j+2))), + } + } + return VMetric{ + AdvanceHeight: f.fUnitsPerEm, + TopSideBearing: 0, + } +} + +// VMetric returns the vertical metrics for the glyph with the given index. +func (f *Font) VMetric(scale int32, i Index) VMetric { + v := f.unscaledVMetric(i) + v.AdvanceHeight = f.scale(scale * v.AdvanceHeight) + v.TopSideBearing = f.scale(scale * v.TopSideBearing) + return v +} + // Kerning returns the kerning for the given glyph pair. func (f *Font) Kerning(scale int32, i0, i1 Index) int32 { if f.nKern == 0 { @@ -471,6 +502,8 @@ func parse(ttf []byte, offset int) (font *Font, err error) { f.maxp, err = readTable(ttf, ttf[x+8:x+16]) case "prep": f.prep, err = readTable(ttf, ttf[x+8:x+16]) + case "vmtx": + f.vmtx, err = readTable(ttf, ttf[x+8:x+16]) } if err != nil { return diff --git a/freetype/truetype/truetype_test.go b/freetype/truetype/truetype_test.go index 5b37c1c..e791cdb 100644 --- a/freetype/truetype/truetype_test.go +++ b/freetype/truetype/truetype_test.go @@ -53,6 +53,9 @@ func TestParse(t *testing.T) { if got, want := font.HMetric(fupe, i0), (HMetric{1366, 19}); got != want { t.Errorf("HMetric: got %v, want %v", got, want) } + if got, want := font.VMetric(fupe, i0), (VMetric{2465, 553}); got != want { + t.Errorf("VMetric: got %v, want %v", got, want) + } if got, want := font.Kerning(fupe, i0, i1), int32(-144); got != want { t.Errorf("Kerning: got %v, want %v", got, want) } @@ -253,7 +256,7 @@ var scalingTestCases = []struct { }{ {"luxisr", 12, -1}, {"x-arial-bold", 11, 0}, - {"x-deja-vu-sans-oblique", 17, 513}, + {"x-deja-vu-sans-oblique", 17, 2077}, {"x-droid-sans-japanese", 9, 0}, {"x-times-new-roman", 13, 0}, }