Add a truetype.Face type.

Its implementation is mostly a copy/paste of the freetype.Context type.
Follow-up commits will make it more efficient.

Also add an example that uses a truetype.Face and the
golang.org/x/exp/shiny/font package to draw text.
This commit is contained in:
Nigel Tao 2015-08-22 14:46:12 +10:00
parent 7166253831
commit 6deea24143
2 changed files with 384 additions and 0 deletions

147
example/drawer/main.go Normal file
View File

@ -0,0 +1,147 @@
// Copyright 2015 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
// +build ignore
//
// This build tag means that "go install github.com/golang/freetype/..."
// doesn't install this example program. Use "go run main.go" to run it.
package main
import (
"bufio"
"flag"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"io/ioutil"
"log"
"math"
"os"
"github.com/golang/freetype/truetype"
"golang.org/x/exp/shiny/font"
"golang.org/x/image/math/fixed"
)
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")
)
var text = []string{
"Twas brillig, and the slithy toves",
"Did gyre and gimble in the wabe;",
"All mimsy were the borogoves,",
"And the mome raths outgrabe.",
"",
"“Beware the Jabberwock, my son!",
"The jaws that bite, the claws that catch!",
"Beware the Jubjub bird, and shun",
"The frumious Bandersnatch!”",
"",
"He took his vorpal sword in hand:",
"Long time the manxome foe he sought—",
"So rested he by the Tumtum tree,",
"And stood awhile in thought.",
"",
"And as in uffish thought he stood,",
"The Jabberwock, with eyes of flame,",
"Came whiffling through the tulgey wood,",
"And burbled as it came!",
"",
"One, two! One, two! and through and through",
"The vorpal blade went snicker-snack!",
"He left it dead, and with its head",
"He went galumphing back.",
"",
"“And hast thou slain the Jabberwock?",
"Come to my arms, my beamish boy!",
"O frabjous day! Callooh! Callay!”",
"He chortled in his joy.",
"",
"Twas brillig, and the slithy toves",
"Did gyre and gimble in the wabe;",
"All mimsy were the borogoves,",
"And the mome raths outgrabe.",
}
func main() {
flag.Parse()
// Read the font data.
fontBytes, err := ioutil.ReadFile(*fontfile)
if err != nil {
log.Println(err)
return
}
f, err := truetype.Parse(fontBytes)
if err != nil {
log.Println(err)
return
}
// Draw the background and the guidelines.
fg, bg := image.Black, image.White
ruler := color.RGBA{0xdd, 0xdd, 0xdd, 0xff}
if *wonb {
fg, bg = image.White, image.Black
ruler = color.RGBA{0x22, 0x22, 0x22, 0xff}
}
rgba := image.NewRGBA(image.Rect(0, 0, 640, 480))
draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src)
for i := 0; i < 200; i++ {
rgba.Set(10, 10+i, ruler)
rgba.Set(10+i, 10, ruler)
}
// Draw the text.
h := font.HintingNone
switch *hinting {
case "full":
h = font.HintingFull
}
d := &font.Drawer{
Dst: rgba,
Src: fg,
Face: truetype.NewFace(f, truetype.Options{
Size: *size,
DPI: *dpi,
Hinting: h,
}),
}
dy0 := int(math.Ceil(*size * *dpi / 72))
dy := int(math.Ceil(*size * *spacing * *dpi / 72))
for i, s := range text {
d.Dot = fixed.P(10, 10+dy0+i*dy)
d.DrawString(s)
}
// Save that RGBA image to disk.
outFile, err := os.Create("out.png")
if err != nil {
log.Println(err)
os.Exit(1)
}
defer outFile.Close()
b := bufio.NewWriter(outFile)
err = png.Encode(b, rgba)
if err != nil {
log.Println(err)
os.Exit(1)
}
err = b.Flush()
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println("Wrote out.png OK.")
}

237
truetype/face.go Normal file
View File

@ -0,0 +1,237 @@
// Copyright 2015 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
package truetype
import (
"image"
"github.com/golang/freetype/raster"
"golang.org/x/exp/shiny/font"
"golang.org/x/image/math/fixed"
)
// Options are optional arguments to NewFace.
type Options struct {
// Size is the font size in points, as in "a 10 point font size".
//
// A zero value means to use a 12 point font size.
Size float64
// DPI is the dots-per-inch resolution.
//
// A zero value means to use 72 DPI.
DPI float64
// Hinting is how to quantize the glyph nodes.
//
// A zero value means to use no hinting.
Hinting font.Hinting
}
func (o *Options) size() float64 {
if o.Size > 0 {
return o.Size
}
return 12
}
func (o *Options) dpi() float64 {
if o.DPI > 0 {
return o.DPI
}
return 72
}
func (o *Options) hinting() font.Hinting {
switch o.Hinting {
case font.HintingVertical, font.HintingFull:
// TODO: support vertical hinting.
return font.HintingFull
}
return font.HintingNone
}
// NewFace returns a new font.Face for the given Font.
func NewFace(f *Font, opts Options) font.Face {
a := &face{
f: f,
hinting: opts.hinting(),
scale: fixed.Int26_6(0.5 + (opts.size() * opts.dpi() * 64 / 72)),
}
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
b := f.Bounds(a.scale)
xmin := +int(b.XMin) >> 6
ymin := -int(b.YMax) >> 6
xmax := +int(b.XMax+63) >> 6
ymax := -int(b.YMin-63) >> 6
a.r.SetBounds(xmax-xmin, ymax-ymin)
return a
}
type face struct {
f *Font
hinting font.Hinting
scale fixed.Int26_6
r raster.Rasterizer
glyphBuf GlyphBuf
// TODO: clip rectangle?
}
// Close satisfies the font.Face interface.
func (a *face) Close() error { return nil }
// Kern satisfies the font.Face interface.
func (a *face) Kern(r0, r1 rune) fixed.Int26_6 {
i0 := a.f.Index(r0)
i1 := a.f.Index(r1)
kern := fixed.Int26_6(a.f.Kerning(a.scale, i0, i1))
if a.hinting != font.HintingNone {
kern = (kern + 32) &^ 63
}
return kern
}
// Glyph satisfies the font.Face interface.
func (a *face) Glyph(dot fixed.Point26_6, r rune) (
newDot fixed.Point26_6, dr image.Rectangle, mask image.Image, maskp image.Point, ok bool) {
// Split p.X and p.Y into their integer and fractional parts.
ix, fx := int(dot.X>>6), dot.X&0x3f
iy, fy := int(dot.Y>>6), dot.Y&0x3f
advanceWidth, mask, offset, ok := a.rasterize(a.f.Index(r), fx, fy)
if !ok {
return fixed.Point26_6{}, image.Rectangle{}, nil, image.Point{}, false
}
newDot = fixed.Point26_6{
X: dot.X + advanceWidth,
Y: dot.Y,
}
mb := mask.Bounds()
dr.Min = image.Point{
X: ix + offset.X,
Y: iy + offset.Y,
}
dr.Max = image.Point{
X: dr.Min.X + mb.Dx(),
Y: dr.Min.Y + mb.Dy(),
}
return newDot, dr, mask, image.Point{}, true
}
// rasterize returns the advance width, glyph mask and integer-pixel offset
// to render the given glyph at the given sub-pixel offsets.
// The 26.6 fixed point arguments fx and fy must be in the range [0, 1).
func (a *face) rasterize(index Index, fx, fy fixed.Int26_6) (
fixed.Int26_6, *image.Alpha, image.Point, bool) {
if err := a.glyphBuf.Load(a.f, a.scale, index, a.hinting); err != nil {
return 0, nil, image.Point{}, false
}
// Calculate the integer-pixel bounds for the glyph.
xmin := int(fx+fixed.Int26_6(a.glyphBuf.B.XMin)) >> 6
ymin := int(fy-fixed.Int26_6(a.glyphBuf.B.YMax)) >> 6
xmax := int(fx+fixed.Int26_6(a.glyphBuf.B.XMax)+0x3f) >> 6
ymax := int(fy-fixed.Int26_6(a.glyphBuf.B.YMin)+0x3f) >> 6
if xmin > xmax || ymin > ymax {
return 0, nil, image.Point{}, false
}
// A TrueType's glyph's nodes can have negative co-ordinates, but the
// rasterizer clips anything left of x=0 or above y=0. xmin and ymin are
// the pixel offsets, based on the font's FUnit metrics, that let a
// negative co-ordinate in TrueType space be non-negative in rasterizer
// space. xmin and ymin are typically <= 0.
fx += fixed.Int26_6(-xmin << 6)
fy += fixed.Int26_6(-ymin << 6)
// Rasterize the glyph's vectors.
a.r.Clear()
e0 := 0
for _, e1 := range a.glyphBuf.End {
a.drawContour(a.glyphBuf.Point[e0:e1], fx, fy)
e0 = e1
}
// TODO: don't allocate a new mask each time.
mask := image.NewAlpha(image.Rect(0, 0, xmax-xmin, ymax-ymin))
a.r.Rasterize(raster.NewAlphaSrcPainter(mask))
return fixed.Int26_6(a.glyphBuf.AdvanceWidth), mask, image.Point{xmin, ymin}, true
}
// drawContour draws the given closed contour with the given offset.
func (a *face) drawContour(ps []Point, dx, dy fixed.Int26_6) {
if len(ps) == 0 {
return
}
// The low bit of each point's Flags value is whether the point is on the
// curve. Truetype fonts only have quadratic Bézier curves, not cubics.
// Thus, two consecutive off-curve points imply an on-curve point in the
// middle of those two.
//
// See http://chanae.walon.org/pub/ttf/ttf_glyphs.htm for more details.
// ps[0] is a truetype.Point measured in FUnits and positive Y going
// upwards. start is the same thing measured in fixed point units and
// positive Y going downwards, and offset by (dx, dy).
start := fixed.Point26_6{
X: dx + fixed.Int26_6(ps[0].X),
Y: dy - fixed.Int26_6(ps[0].Y),
}
var others []Point
if ps[0].Flags&0x01 != 0 {
others = ps[1:]
} else {
last := fixed.Point26_6{
X: dx + fixed.Int26_6(ps[len(ps)-1].X),
Y: dy - fixed.Int26_6(ps[len(ps)-1].Y),
}
if ps[len(ps)-1].Flags&0x01 != 0 {
start = last
others = ps[:len(ps)-1]
} else {
start = fixed.Point26_6{
X: (start.X + last.X) / 2,
Y: (start.Y + last.Y) / 2,
}
others = ps
}
}
a.r.Start(start)
q0, on0 := start, true
for _, p := range others {
q := fixed.Point26_6{
X: dx + fixed.Int26_6(p.X),
Y: dy - fixed.Int26_6(p.Y),
}
on := p.Flags&0x01 != 0
if on {
if on0 {
a.r.Add1(q)
} else {
a.r.Add2(q0, q)
}
} else {
if on0 {
// No-op.
} else {
mid := fixed.Point26_6{
X: (q0.X + q.X) / 2,
Y: (q0.Y + q.Y) / 2,
}
a.r.Add2(q0, mid)
}
}
q0, on0 = q, on
}
// Close the curve.
if on0 {
a.r.Add1(start)
} else {
a.r.Add2(q0, start)
}
}