freetype: add a Hinting enum in the top-level freetype package, and

hide the Hinter implementation in the freetype/truetype package.

LGTM=bsiegert
R=bsiegert
CC=golang-codereviews
https://codereview.appspot.com/58940043
This commit is contained in:
Nigel Tao 2014-02-01 14:12:48 +11:00
parent 51c4a7ede4
commit 49fa4a4de0
7 changed files with 91 additions and 51 deletions

View File

@ -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++ {

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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)
}