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:
parent
51c4a7ede4
commit
49fa4a4de0
7 changed files with 91 additions and 51 deletions
|
@ -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++ {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue