diff --git a/example/freetype/main.go b/example/freetype/main.go index 2f5404b..83b8c3e 100644 --- a/example/freetype/main.go +++ b/example/freetype/main.go @@ -23,6 +23,7 @@ import ( var ( dpi = flag.Float64("dpi", 72, "screen resolution in Dots Per Inch") fontfile = flag.String("fontfile", "../../testdata/luxisr.ttf", "filename of the ttf font") + hinting = flag.String("hinting", "none", "none | full") size = flag.Float64("size", 12, "font size in points") spacing = flag.Float64("spacing", 1.5, "line spacing (e.g. 2 means double spaced)") wonb = flag.Bool("whiteonblack", false, "white text on a black background") @@ -96,6 +97,12 @@ func main() { c.SetClip(rgba.Bounds()) c.SetDst(rgba) c.SetSrc(fg) + switch *hinting { + default: + c.SetHinting(freetype.NoHinting) + case "full": + c.SetHinting(freetype.FullHinting) + } // Draw the guidelines. for i := 0; i < 200; i++ { diff --git a/example/truetype/main.go b/example/truetype/main.go index 68dc6fb..747694b 100644 --- a/example/truetype/main.go +++ b/example/truetype/main.go @@ -60,7 +60,7 @@ func main() { i0 := font.Index(c0) hm := font.HMetric(fupe, i0) g := truetype.NewGlyphBuf() - err = g.Load(font, fupe, i0, nil) + err = g.Load(font, fupe, i0, truetype.NoHinting) if err != nil { log.Println(err) return diff --git a/freetype/freetype.go b/freetype/freetype.go index a8471ff..f9e6d7f 100644 --- a/freetype/freetype.go +++ b/freetype/freetype.go @@ -54,6 +54,16 @@ func Pt(x, y int) raster.Point { } } +// Hinting is the policy for snapping a glyph's contours to pixel boundaries. +type Hinting int32 + +const ( + // NoHinting means to not perform any hinting. + NoHinting = Hinting(truetype.NoHinting) + // FullHinting means to use the font's hinting instructions. + FullHinting = Hinting(truetype.FullHinting) +) + // A Context holds the state for drawing text in a given font and size. type Context struct { r *raster.Rasterizer @@ -65,9 +75,10 @@ type Context struct { dst draw.Image src image.Image // fontSize and dpi are used to calculate scale. scale is the number of - // 26.6 fixed point units in 1 em. + // 26.6 fixed point units in 1 em. hinting is the hinting policy. fontSize, dpi float64 scale int32 + hinting Hinting // cache is the glyph cache. cache [nGlyphs * nXFractions * nYFractions]cacheEntry } @@ -131,7 +142,7 @@ func (c *Context) drawContour(ps []truetype.Point, dx, dy raster.Fix32) { func (c *Context) rasterize(glyph truetype.Index, fx, fy raster.Fix32) ( raster.Fix32, *image.Alpha, image.Point, error) { - if err := c.glyphBuf.Load(c.font, c.scale, glyph, nil); err != nil { + if err := c.glyphBuf.Load(c.font, c.scale, glyph, truetype.Hinting(c.hinting)); err != nil { return 0, nil, image.Point{}, err } // Calculate the integer-pixel bounds for the glyph. @@ -204,8 +215,11 @@ func (c *Context) DrawString(s string, p raster.Point) (raster.Point, error) { for _, rune := range s { index := c.font.Index(rune) if hasPrev { - // TODO: adjust for hinting. - p.X += raster.Fix32(c.font.Kerning(c.scale, prev, index)) << 2 + kern := raster.Fix32(c.font.Kerning(c.scale, prev, index)) << 2 + if c.hinting != NoHinting { + kern = (kern + 128) &^ 255 + } + p.X += kern } advanceWidth, mask, offset, err := c.glyph(index, p) if err != nil { @@ -270,6 +284,14 @@ func (c *Context) SetFontSize(fontSize float64) { c.recalc() } +// SetHinting sets the hinting policy. +func (c *Context) SetHinting(hinting Hinting) { + c.hinting = hinting + for i := range c.cache { + c.cache[i] = cacheEntry{} + } +} + // SetDst sets the destination image for draw operations. func (c *Context) SetDst(dst draw.Image) { c.dst = dst diff --git a/freetype/truetype/glyph.go b/freetype/truetype/glyph.go index 5c6240a..b5f3278 100644 --- a/freetype/truetype/glyph.go +++ b/freetype/truetype/glyph.go @@ -5,6 +5,18 @@ package truetype +// Hinting is the policy for snapping a glyph's contours to pixel boundaries. +type Hinting int32 + +const ( + // NoHinting means to not perform any hinting. + NoHinting Hinting = iota + // FullHinting means to use the font's hinting instructions. + FullHinting + + // TODO: implement VerticalHinting. +) + // A Point is a co-ordinate pair plus whether it is ``on'' a contour or an // ``off'' control point. type Point struct { @@ -21,8 +33,8 @@ type GlyphBuf struct { AdvanceWidth int32 // B is the glyph's bounding box. B Bounds - // Point contains all Points from all contours of the glyph. If a - // Hinter was used to load a glyph then Unhinted contains those + // 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 // Points before they were hinted and scaled. Point, Unhinted, InFontUnits []Point @@ -32,9 +44,10 @@ type GlyphBuf struct { // is interpreted to mean zero. End []int - font *Font - hinter *Hinter - scale int32 + font *Font + scale int32 + hinting Hinting + hinter hinter // phantomPoints are the co-ordinates of the synthetic phantom points // used for hinting and bounding box calculations. phantomPoints [4]Point @@ -73,22 +86,21 @@ const ( // 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 { +// units in 1 em, i is the glyph index, and h is the hinting policy. +func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h Hinting) error { 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.hinting = h g.scale = scale g.pp1x = 0 g.phantomPoints = [4]Point{} g.metricsSet = false - if h != nil { - if err := h.init(f, scale); err != nil { + if h != NoHinting { + if err := g.hinter.init(f, scale); err != nil { return err } } @@ -99,7 +111,7 @@ func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h *Hinter) error { // 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 { + if h != NoHinting { pp1x = g.phantomPoints[0].X } if pp1x != 0 { @@ -109,7 +121,7 @@ func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h *Hinter) error { } advanceWidth := g.phantomPoints[1].X - g.phantomPoints[0].X - if h != nil { + if h != NoHinting { if len(f.hdmx) >= 8 { if n := u32(f.hdmx, 4); n > 3+uint32(i) { for hdmx := f.hdmx[8:]; uint32(len(hdmx)) >= n; hdmx = hdmx[n:] { @@ -151,7 +163,7 @@ func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h *Hinter) error { } } // Snap the box to the grid, if hinting is on. - if h != nil { + if h != NoHinting { g.B.XMin &^= 63 g.B.YMin &^= 63 g.B.XMax += 63 @@ -221,7 +233,7 @@ func (g *GlyphBuf) load(recursion int32, i Index, useMyMetrics bool) (err error) program := g.loadSimple(glyf, ne) g.addPhantomsAndScale(np0, np0, true, true) pp1x = g.Point[len(g.Point)-4].X - if g.hinter != nil { + if g.hinting != NoHinting { if len(program) != 0 { err := g.hinter.run( program, @@ -425,7 +437,7 @@ func (g *GlyphBuf) loadCompound(recursion int32, uhm HMetric, i Index, } instrLen := 0 - if g.hinter != nil && offset+2 <= len(glyf) { + if g.hinting != NoHinting && offset+2 <= len(glyf) { instrLen = int(u16(glyf, offset)) offset += 2 } @@ -473,7 +485,7 @@ func (g *GlyphBuf) addPhantomsAndScale(np0, np1 int, simple, adjust bool) { // Add the four phantom points. g.Point = append(g.Point, g.phantomPoints[:]...) // Scale the points. - if simple && g.hinter != nil { + if simple && g.hinting != NoHinting { g.InFontUnits = append(g.InFontUnits, g.Point[np1:]...) } for i := np1; i < len(g.Point); i++ { @@ -481,7 +493,7 @@ func (g *GlyphBuf) addPhantomsAndScale(np0, np1 int, simple, adjust bool) { p.X = g.font.scale(g.scale * p.X) p.Y = g.font.scale(g.scale * p.Y) } - if g.hinter == nil { + if g.hinting == NoHinting { return } // Round the 1st phantom point to the grid, shifting all other points equally. diff --git a/freetype/truetype/hint.go b/freetype/truetype/hint.go index 83ba4d1..35f1fc5 100644 --- a/freetype/truetype/hint.go +++ b/freetype/truetype/hint.go @@ -35,16 +35,15 @@ type callStackEntry struct { loopCount int32 } -// Hinter implements bytecode hinting. Pass a Hinter to GlyphBuf.Load to hint -// the resulting glyph. A Hinter can be re-used to hint a series of glyphs from -// a Font. -type Hinter struct { +// hinter implements bytecode hinting. A hinter can be re-used to hint a series +// of glyphs from a Font. +type hinter struct { stack, store []int32 // functions is a map from function number to bytecode. functions map[int32][]byte - // font and scale are the font and scale last used for this Hinter. + // font and scale are the font and scale last used for this hinter. // Changing the font will require running the new font's fpgm bytecode. // Changing either will require running the font's prep bytecode. font *Font @@ -114,7 +113,7 @@ func resetTwilightPoints(f *Font, p []Point) []Point { return p } -func (h *Hinter) init(f *Font, scale int32) error { +func (h *hinter) init(f *Font, scale int32) error { h.points[twilightZone][0] = resetTwilightPoints(f, h.points[twilightZone][0]) h.points[twilightZone][1] = resetTwilightPoints(f, h.points[twilightZone][1]) h.points[twilightZone][2] = resetTwilightPoints(f, h.points[twilightZone][2]) @@ -171,7 +170,7 @@ func (h *Hinter) init(f *Font, scale int32) error { return nil } -func (h *Hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point, ends []int) error { +func (h *hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point, ends []int) error { h.gs = h.defaultGS h.points[glyphZone][current] = pCurrent h.points[glyphZone][unhinted] = pUnhinted @@ -1388,7 +1387,7 @@ func (h *Hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point, return nil } -func (h *Hinter) initializeScaledCVT() { +func (h *hinter) initializeScaledCVT() { h.scaledCVTInitialized = true if n := len(h.font.cvt) / 2; n <= cap(h.scaledCVT) { h.scaledCVT = h.scaledCVT[:n] @@ -1405,7 +1404,7 @@ func (h *Hinter) initializeScaledCVT() { } // getScaledCVT returns the scaled value from the font's Control Value Table. -func (h *Hinter) getScaledCVT(i int32) f26dot6 { +func (h *hinter) getScaledCVT(i int32) f26dot6 { if !h.scaledCVTInitialized { h.initializeScaledCVT() } @@ -1416,7 +1415,7 @@ func (h *Hinter) getScaledCVT(i int32) f26dot6 { } // setScaledCVT overrides the scaled value from the font's Control Value Table. -func (h *Hinter) setScaledCVT(i int32, v f26dot6) { +func (h *hinter) setScaledCVT(i int32, v f26dot6) { if !h.scaledCVTInitialized { h.initializeScaledCVT() } @@ -1426,7 +1425,7 @@ func (h *Hinter) setScaledCVT(i int32, v f26dot6) { h.scaledCVT[i] = v } -func (h *Hinter) point(zonePointer uint32, pt pointType, i int32) *Point { +func (h *hinter) point(zonePointer uint32, pt pointType, i int32) *Point { points := h.points[h.gs.zp[zonePointer]][pt] if i < 0 || len(points) <= int(i) { return nil @@ -1434,7 +1433,7 @@ func (h *Hinter) point(zonePointer uint32, pt pointType, i int32) *Point { return &points[i] } -func (h *Hinter) move(p *Point, distance f26dot6, touch bool) { +func (h *hinter) move(p *Point, distance f26dot6, touch bool) { fvx := int64(h.gs.fv[0]) pvx := int64(h.gs.pv[0]) if fvx == 0x4000 && pvx == 0x4000 { @@ -1472,7 +1471,7 @@ func (h *Hinter) move(p *Point, distance f26dot6, touch bool) { } } -func (h *Hinter) iupInterp(interpY bool, p1, p2, ref1, ref2 int) { +func (h *hinter) iupInterp(interpY bool, p1, p2, ref1, ref2 int) { if p1 > p2 { return } @@ -1567,7 +1566,7 @@ func (h *Hinter) iupInterp(interpY bool, p1, p2, ref1, ref2 int) { } } -func (h *Hinter) iupShift(interpY bool, p1, p2, p int) { +func (h *hinter) iupShift(interpY bool, p1, p2, p int) { var delta int32 if interpY { delta = h.points[glyphZone][current][p].Y - h.points[glyphZone][unhinted][p].Y @@ -1589,7 +1588,7 @@ func (h *Hinter) iupShift(interpY bool, p1, p2, p int) { } } -func (h *Hinter) displacement(useZP1 bool) (zonePointer uint32, i int32, d f26dot6, ok bool) { +func (h *hinter) displacement(useZP1 bool) (zonePointer uint32, i int32, d f26dot6, ok bool) { zonePointer, i = uint32(0), h.gs.rp[1] if useZP1 { zonePointer, i = 1, h.gs.rp[2] @@ -1726,7 +1725,7 @@ func mulDiv(x, y, z int64) int64 { // round rounds the given number. The rounding algorithm is described at // https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding -func (h *Hinter) round(x f26dot6) f26dot6 { +func (h *hinter) round(x f26dot6) f26dot6 { if h.gs.roundPeriod == 0 { // Rounding is off. return x diff --git a/freetype/truetype/hint_test.go b/freetype/truetype/hint_test.go index ee4f716..c8b8d60 100644 --- a/freetype/truetype/hint_test.go +++ b/freetype/truetype/hint_test.go @@ -554,7 +554,7 @@ func TestBytecode(t *testing.T) { } for _, tc := range testCases { - h := &Hinter{} + h := &hinter{} h.init(&Font{ maxStorage: 32, maxStackElements: 100, @@ -583,10 +583,10 @@ func TestBytecode(t *testing.T) { } } -// TestMove tests that the Hinter.move method matches the output of the C +// TestMove tests that the hinter.move method matches the output of the C // Freetype implementation. func TestMove(t *testing.T) { - h, p := Hinter{}, Point{} + h, p := hinter{}, Point{} testCases := []struct { pvX, pvY, fvX, fvY f2dot14 wantX, wantY int32 diff --git a/freetype/truetype/truetype_test.go b/freetype/truetype/truetype_test.go index 8c520cc..765b3fb 100644 --- a/freetype/truetype/truetype_test.go +++ b/freetype/truetype/truetype_test.go @@ -61,7 +61,7 @@ func TestParse(t *testing.T) { } g := NewGlyphBuf() - err = g.Load(font, fupe, i0, nil) + err = g.Load(font, fupe, i0, NoHinting) if err != nil { t.Fatalf("Load: %v", err) } @@ -262,7 +262,7 @@ var scalingTestCases = []struct { {"x-times-new-roman", 13}, } -func testScaling(t *testing.T, hinter *Hinter) { +func testScaling(t *testing.T, h Hinting) { for _, tc := range scalingTestCases { font, testdataIsOptional, err := parseTestdataFont(tc.name) if err != nil { @@ -273,12 +273,12 @@ func testScaling(t *testing.T, hinter *Hinter) { } continue } - hinting := "sans" - if hinter != nil { - hinting = "with" + hintingStr := "sans" + if h != NoHinting { + hintingStr = "with" } f, err := os.Open(fmt.Sprintf( - "../../testdata/%s-%dpt-%s-hinting.txt", tc.name, tc.size, hinting)) + "../../testdata/%s-%dpt-%s-hinting.txt", tc.name, tc.size, hintingStr)) if err != nil { t.Errorf("%s: Open: %v", tc.name, err) continue @@ -314,7 +314,7 @@ func testScaling(t *testing.T, hinter *Hinter) { glyphBuf := NewGlyphBuf() for i, want := range wants { - if err = glyphBuf.Load(font, tc.size*64, Index(i), hinter); err != nil { + if err = glyphBuf.Load(font, tc.size*64, Index(i), h); err != nil { t.Errorf("%s: glyph #%d: Load: %v", tc.name, i, err) continue } @@ -354,9 +354,9 @@ func testScaling(t *testing.T, hinter *Hinter) { } func TestScalingSansHinting(t *testing.T) { - testScaling(t, nil) + testScaling(t, NoHinting) } func TestScalingWithHinting(t *testing.T) { - testScaling(t, &Hinter{}) + testScaling(t, FullHinting) }