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:
parent
7166253831
commit
6deea24143
2 changed files with 384 additions and 0 deletions
147
example/drawer/main.go
Normal file
147
example/drawer/main.go
Normal 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
237
truetype/face.go
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue