Use the common golang.org/x/exp/shiny/font.Hinting type.

This commit is contained in:
Nigel Tao 2015-08-20 16:07:51 +10:00
parent 7e2154db58
commit f42630ca7a
9 changed files with 79 additions and 99 deletions

View file

@ -23,6 +23,7 @@ import (
"os"
"github.com/golang/freetype"
"golang.org/x/exp/shiny/font"
)
var (
@ -80,7 +81,7 @@ func main() {
log.Println(err)
return
}
font, err := freetype.ParseFont(fontBytes)
f, err := freetype.ParseFont(fontBytes)
if err != nil {
log.Println(err)
return
@ -97,16 +98,16 @@ func main() {
draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src)
c := freetype.NewContext()
c.SetDPI(*dpi)
c.SetFont(font)
c.SetFont(f)
c.SetFontSize(*size)
c.SetClip(rgba.Bounds())
c.SetDst(rgba)
c.SetSrc(fg)
switch *hinting {
default:
c.SetHinting(freetype.NoHinting)
c.SetHinting(font.HintingNone)
case "full":
c.SetHinting(freetype.FullHinting)
c.SetHinting(font.HintingFull)
}
// Draw the guidelines.
@ -127,13 +128,13 @@ func main() {
}
// Save that RGBA image to disk.
f, err := os.Create("out.png")
outFile, err := os.Create("out.png")
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
defer outFile.Close()
b := bufio.NewWriter(outFile)
err = png.Encode(b, rgba)
if err != nil {
log.Println(err)

View file

@ -64,13 +64,13 @@ func main() {
}
// Save that RGBA image to disk.
f, err := os.Create("out.png")
outFile, err := os.Create("out.png")
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
defer outFile.Close()
b := bufio.NewWriter(outFile)
err = png.Encode(b, rgba)
if err != nil {
log.Println(err)

View file

@ -163,13 +163,13 @@ func main() {
showNodes(rgba, inside)
// Save that RGBA image to disk.
f, err := os.Create("out.png")
outFile, err := os.Create("out.png")
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
defer outFile.Close()
b := bufio.NewWriter(outFile)
err = png.Encode(b, rgba)
if err != nil {
log.Println(err)

View file

@ -88,13 +88,13 @@ func main() {
}
// Save that RGBA image to disk.
f, err := os.Create("out.png")
outFile, err := os.Create("out.png")
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
defer outFile.Close()
b := bufio.NewWriter(outFile)
err = png.Encode(b, m)
if err != nil {
log.Println(err)

View file

@ -17,6 +17,7 @@ import (
"log"
"github.com/golang/freetype/truetype"
"golang.org/x/exp/shiny/font"
"golang.org/x/image/math/fixed"
)
@ -52,21 +53,21 @@ func main() {
log.Println(err)
return
}
font, err := truetype.Parse(b)
f, err := truetype.Parse(b)
if err != nil {
log.Println(err)
return
}
fupe := fixed.Int26_6(font.FUnitsPerEm())
printBounds(font.Bounds(fupe))
fupe := fixed.Int26_6(f.FUnitsPerEm())
printBounds(f.Bounds(fupe))
fmt.Printf("FUnitsPerEm:%d\n\n", fupe)
c0, c1 := 'A', 'V'
i0 := font.Index(c0)
hm := font.HMetric(fupe, i0)
i0 := f.Index(c0)
hm := f.HMetric(fupe, i0)
g := truetype.NewGlyphBuf()
err = g.Load(font, fupe, i0, truetype.NoHinting)
err = g.Load(f, fupe, i0, font.HintingNone)
if err != nil {
log.Println(err)
return
@ -74,6 +75,6 @@ func main() {
fmt.Printf("'%c' glyph\n", c0)
fmt.Printf("AdvanceWidth:%d LeftSideBearing:%d\n", hm.AdvanceWidth, hm.LeftSideBearing)
printGlyph(g)
i1 := font.Index(c1)
fmt.Printf("\n'%c', '%c' Kerning:%d\n", c0, c1, font.Kerning(fupe, i0, i1))
i1 := f.Index(c1)
fmt.Printf("\n'%c', '%c' Kerning:%d\n", c0, c1, f.Kerning(fupe, i0, i1))
}

View file

@ -15,6 +15,7 @@ import (
"github.com/golang/freetype/raster"
"github.com/golang/freetype/truetype"
"golang.org/x/exp/shiny/font"
"golang.org/x/image/math/fixed"
)
@ -55,20 +56,10 @@ func Pt(x, y int) fixed.Point26_6 {
}
}
// 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
font *truetype.Font
f *truetype.Font
glyphBuf *truetype.GlyphBuf
// clip is the clip rectangle for drawing.
clip image.Rectangle
@ -79,7 +70,7 @@ type Context struct {
// 26.6 fixed point units in 1 em. hinting is the hinting policy.
fontSize, dpi float64
scale fixed.Int26_6
hinting Hinting
hinting font.Hinting
// cache is the glyph cache.
cache [nGlyphs * nXFractions * nYFractions]cacheEntry
}
@ -170,7 +161,7 @@ func (c *Context) drawContour(ps []truetype.Point, dx, dy fixed.Int26_6) {
func (c *Context) rasterize(glyph truetype.Index, fx, fy fixed.Int26_6) (
fixed.Int26_6, *image.Alpha, image.Point, error) {
if err := c.glyphBuf.Load(c.font, c.scale, glyph, truetype.Hinting(c.hinting)); err != nil {
if err := c.glyphBuf.Load(c.f, c.scale, glyph, c.hinting); err != nil {
return 0, nil, image.Point{}, err
}
// Calculate the integer-pixel bounds for the glyph.
@ -237,15 +228,15 @@ func (c *Context) glyph(glyph truetype.Index, p fixed.Point26_6) (
//
// p is a fixed.Point26_6 and can therefore represent sub-pixel positions.
func (c *Context) DrawString(s string, p fixed.Point26_6) (fixed.Point26_6, error) {
if c.font == nil {
if c.f == nil {
return fixed.Point26_6{}, errors.New("freetype: DrawText called with a nil font")
}
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := c.font.Index(rune)
index := c.f.Index(rune)
if hasPrev {
kern := fixed.Int26_6(c.font.Kerning(c.scale, prev, index))
if c.hinting != NoHinting {
kern := fixed.Int26_6(c.f.Kerning(c.scale, prev, index))
if c.hinting != font.HintingNone {
kern = (kern + 32) &^ 63
}
p.X += kern
@ -270,11 +261,11 @@ func (c *Context) DrawString(s string, p fixed.Point26_6) (fixed.Point26_6, erro
// resolution and font metrics, and invalidates the glyph cache.
func (c *Context) recalc() {
c.scale = fixed.Int26_6(c.fontSize * c.dpi * (64.0 / 72.0))
if c.font == nil {
if c.f == nil {
c.r.SetBounds(0, 0)
} else {
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
b := c.font.Bounds(c.scale)
b := c.f.Bounds(c.scale)
xmin := +int(b.XMin) >> 6
ymin := -int(b.YMax) >> 6
xmax := +int(b.XMax+63) >> 6
@ -296,11 +287,11 @@ func (c *Context) SetDPI(dpi float64) {
}
// SetFont sets the font used to draw text.
func (c *Context) SetFont(font *truetype.Font) {
if c.font == font {
func (c *Context) SetFont(f *truetype.Font) {
if c.f == f {
return
}
c.font = font
c.f = f
c.recalc()
}
@ -314,7 +305,7 @@ func (c *Context) SetFontSize(fontSize float64) {
}
// SetHinting sets the hinting policy.
func (c *Context) SetHinting(hinting Hinting) {
func (c *Context) SetHinting(hinting font.Hinting) {
c.hinting = hinting
for i := range c.cache {
c.cache[i] = cacheEntry{}

View file

@ -25,7 +25,7 @@ func BenchmarkDrawString(b *testing.B) {
if err != nil {
b.Fatal(err)
}
font, err := ParseFont(data)
f, err := ParseFont(data)
if err != nil {
b.Fatal(err)
}
@ -37,7 +37,7 @@ func BenchmarkDrawString(b *testing.B) {
c.SetDst(dst)
c.SetClip(dst.Bounds())
c.SetSrc(image.Black)
c.SetFont(font)
c.SetFont(f)
var ms runtime.MemStats
runtime.ReadMemStats(&ms)

View file

@ -6,20 +6,11 @@
package truetype
import (
"golang.org/x/exp/shiny/font"
"golang.org/x/image/math/fixed"
)
// Hinting is the policy for snapping a glyph's contours to pixel boundaries.
type Hinting uint32
const (
// NoHinting means to not perform any hinting.
NoHinting Hinting = iota
// FullHinting means to use the font's hinting instructions.
FullHinting
// TODO: implement VerticalHinting.
)
// TODO: implement VerticalHinting.
// A Point is a co-ordinate pair plus whether it is 'on' a contour or an 'off'
// control point.
@ -50,7 +41,7 @@ type GlyphBuf struct {
font *Font
scale fixed.Int26_6
hinting Hinting
hinting font.Hinting
hinter hinter
// phantomPoints are the co-ordinates of the synthetic phantom points
// used for hinting and bounding box calculations.
@ -91,7 +82,7 @@ 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, i is the glyph index, and h is the hinting policy.
func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h Hinting) error {
func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h font.Hinting) error {
g.Point = g.Point[:0]
g.Unhinted = g.Unhinted[:0]
g.InFontUnits = g.InFontUnits[:0]
@ -103,7 +94,7 @@ func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h Hinting) error
g.phantomPoints = [4]Point{}
g.metricsSet = false
if h != NoHinting {
if h != font.HintingNone {
if err := g.hinter.init(f, scale); err != nil {
return err
}
@ -115,7 +106,7 @@ func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h Hinting) 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 != NoHinting {
if h != font.HintingNone {
pp1x = g.phantomPoints[0].X
}
if pp1x != 0 {
@ -125,7 +116,7 @@ func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h Hinting) error
}
advanceWidth := g.phantomPoints[1].X - g.phantomPoints[0].X
if h != NoHinting {
if h != font.HintingNone {
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:] {
@ -167,7 +158,7 @@ func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h Hinting) error
}
}
// Snap the box to the grid, if hinting is on.
if h != NoHinting {
if h != font.HintingNone {
g.B.XMin &^= 63
g.B.YMin &^= 63
g.B.XMax += 63
@ -237,7 +228,7 @@ func (g *GlyphBuf) load(recursion uint32, 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.hinting != NoHinting {
if g.hinting != font.HintingNone {
if len(program) != 0 {
err := g.hinter.run(
program,
@ -443,7 +434,7 @@ func (g *GlyphBuf) loadCompound(recursion uint32, uhm HMetric, i Index,
}
instrLen := 0
if g.hinting != NoHinting && offset+2 <= len(glyf) {
if g.hinting != font.HintingNone && offset+2 <= len(glyf) {
instrLen = int(u16(glyf, offset))
offset += 2
}
@ -491,7 +482,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.hinting != NoHinting {
if simple && g.hinting != font.HintingNone {
g.InFontUnits = append(g.InFontUnits, g.Point[np1:]...)
}
for i := np1; i < len(g.Point); i++ {
@ -499,7 +490,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.hinting == NoHinting {
if g.hinting == font.HintingNone {
return
}
// Round the 1st phantom point to the grid, shifting all other points equally.

View file

@ -15,55 +15,56 @@ import (
"strings"
"testing"
"golang.org/x/exp/shiny/font"
"golang.org/x/image/math/fixed"
)
func parseTestdataFont(name string) (font *Font, testdataIsOptional bool, err error) {
func parseTestdataFont(name string) (f *Font, testdataIsOptional bool, err error) {
b, err := ioutil.ReadFile(fmt.Sprintf("../testdata/%s.ttf", name))
if err != nil {
// The "x-foo" fonts are optional tests, as they are not checked
// in for copyright or file size reasons.
return nil, strings.HasPrefix(name, "x-"), fmt.Errorf("%s: ReadFile: %v", name, err)
}
font, err = Parse(b)
f, err = Parse(b)
if err != nil {
return nil, true, fmt.Errorf("%s: Parse: %v", name, err)
}
return font, false, nil
return f, false, nil
}
// TestParse tests that the luxisr.ttf metrics and glyphs are parsed correctly.
// The numerical values can be manually verified by examining luxisr.ttx.
func TestParse(t *testing.T) {
font, _, err := parseTestdataFont("luxisr")
f, _, err := parseTestdataFont("luxisr")
if err != nil {
t.Fatal(err)
}
if got, want := font.FUnitsPerEm(), int32(2048); got != want {
if got, want := f.FUnitsPerEm(), int32(2048); got != want {
t.Errorf("FUnitsPerEm: got %v, want %v", got, want)
}
fupe := fixed.Int26_6(font.FUnitsPerEm())
if got, want := font.Bounds(fupe), (Bounds{-441, -432, 2024, 2033}); got != want {
fupe := fixed.Int26_6(f.FUnitsPerEm())
if got, want := f.Bounds(fupe), (Bounds{-441, -432, 2024, 2033}); got != want {
t.Errorf("Bounds: got %v, want %v", got, want)
}
i0 := font.Index('A')
i1 := font.Index('V')
i0 := f.Index('A')
i1 := f.Index('V')
if i0 != 36 || i1 != 57 {
t.Fatalf("Index: i0, i1 = %d, %d, want 36, 57", i0, i1)
}
if got, want := font.HMetric(fupe, i0), (HMetric{1366, 19}); got != want {
if got, want := f.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 {
if got, want := f.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), fixed.Int26_6(-144); got != want {
if got, want := f.Kerning(fupe, i0, i1), fixed.Int26_6(-144); got != want {
t.Errorf("Kerning: got %v, want %v", got, want)
}
g := NewGlyphBuf()
err = g.Load(font, fupe, i0, NoHinting)
err = g.Load(f, fupe, i0, font.HintingNone)
if err != nil {
t.Fatalf("Load: %v", err)
}
@ -180,7 +181,7 @@ func TestIndex(t *testing.T) {
},
}
for name, wants := range testCases {
font, testdataIsOptional, err := parseTestdataFont(name)
f, testdataIsOptional, err := parseTestdataFont(name)
if err != nil {
if testdataIsOptional {
t.Log(err)
@ -190,7 +191,7 @@ func TestIndex(t *testing.T) {
continue
}
for r, want := range wants {
if got := font.Index(r); got != want {
if got := f.Index(r); got != want {
t.Errorf("%s: Index of %q, aka %U: got %d, want %d", name, r, r, got, want)
}
}
@ -268,9 +269,9 @@ var scalingTestCases = []struct {
{"x-times-new-roman", 13},
}
func testScaling(t *testing.T, h Hinting) {
func testScaling(t *testing.T, h font.Hinting) {
for _, tc := range scalingTestCases {
font, testdataIsOptional, err := parseTestdataFont(tc.name)
f, testdataIsOptional, err := parseTestdataFont(tc.name)
if err != nil {
if testdataIsOptional {
t.Log(err)
@ -280,19 +281,19 @@ func testScaling(t *testing.T, h Hinting) {
continue
}
hintingStr := "sans"
if h != NoHinting {
if h != font.HintingNone {
hintingStr = "with"
}
f, err := os.Open(fmt.Sprintf(
testFile, err := os.Open(fmt.Sprintf(
"../testdata/%s-%dpt-%s-hinting.txt", tc.name, tc.size, hintingStr))
if err != nil {
t.Errorf("%s: Open: %v", tc.name, err)
continue
}
defer f.Close()
defer testFile.Close()
wants := []scalingTestData{}
scanner := bufio.NewScanner(f)
scanner := bufio.NewScanner(testFile)
if scanner.Scan() {
major, minor, patch := 0, 0, 0
_, err := fmt.Sscanf(scanner.Text(), "freetype version %d.%d.%d", &major, &minor, &patch)
@ -320,7 +321,7 @@ func testScaling(t *testing.T, h Hinting) {
glyphBuf := NewGlyphBuf()
for i, want := range wants {
if err = glyphBuf.Load(font, fixed.I(tc.size), Index(i), h); err != nil {
if err = glyphBuf.Load(f, fixed.I(tc.size), Index(i), h); err != nil {
t.Errorf("%s: glyph #%d: Load: %v", tc.name, i, err)
continue
}
@ -359,10 +360,5 @@ func testScaling(t *testing.T, h Hinting) {
}
}
func TestScalingSansHinting(t *testing.T) {
testScaling(t, NoHinting)
}
func TestScalingWithHinting(t *testing.T) {
testScaling(t, FullHinting)
}
func TestScalingHintingNone(t *testing.T) { testScaling(t, font.HintingNone) }
func TestScalingHintingFull(t *testing.T) { testScaling(t, font.HintingFull) }