freetype.go 10.2 KB
Newer Older
1 2
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
3 4
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
5 6 7 8

// The freetype package provides a convenient API to draw text onto an image.
// Use the freetype/raster and freetype/truetype packages for lower level
// control over rasterization and TrueType parsing.
N
Nigel Tao 已提交
9
package freetype // import "github.com/golang/freetype"
10 11

import (
N
Nigel Tao 已提交
12
	"errors"
13
	"image"
14
	"image/draw"
N
Nigel Tao 已提交
15

N
Nigel Tao 已提交
16 17
	"github.com/golang/freetype/raster"
	"github.com/golang/freetype/truetype"
18
	"golang.org/x/image/font"
19
	"golang.org/x/image/math/fixed"
20 21
)

22 23 24 25 26 27 28 29 30 31 32 33 34 35
// These constants determine the size of the glyph cache. The cache is keyed
// primarily by the glyph index modulo nGlyphs, and secondarily by sub-pixel
// position for the mask image. Sub-pixel positions are quantized to
// nXFractions possible values in both the x and y directions.
const (
	nGlyphs     = 256
	nXFractions = 4
	nYFractions = 1
)

// An entry in the glyph cache is keyed explicitly by the glyph index and
// implicitly by the quantized x and y fractional offset. It maps to a mask
// image and an offset.
type cacheEntry struct {
36 37
	valid        bool
	glyph        truetype.Index
38
	advanceWidth fixed.Int26_6
39 40
	mask         *image.Alpha
	offset       image.Point
41 42
}

43 44 45
// ParseFont just calls the Parse function from the freetype/truetype package.
// It is provided here so that code that imports this package doesn't need
// to also include the freetype/truetype package.
N
Nigel Tao 已提交
46
func ParseFont(b []byte) (*truetype.Font, error) {
47 48 49
	return truetype.Parse(b)
}

50 51 52 53 54 55
// Pt converts from a co-ordinate pair measured in pixels to a fixed.Point26_6
// co-ordinate pair measured in fixed.Int26_6 units.
func Pt(x, y int) fixed.Point26_6 {
	return fixed.Point26_6{
		X: fixed.Int26_6(x << 6),
		Y: fixed.Int26_6(y << 6),
56
	}
57 58
}

59 60
// A Context holds the state for drawing text in a given font and size.
type Context struct {
61
	r        *raster.Rasterizer
62
	f        *truetype.Font
63
	glyphBuf truetype.GlyphBuf
N
Nigel Tao 已提交
64 65
	// clip is the clip rectangle for drawing.
	clip image.Rectangle
66 67 68
	// dst and src are the destination and source images for drawing.
	dst draw.Image
	src image.Image
69
	// fontSize and dpi are used to calculate scale. scale is the number of
70
	// 26.6 fixed point units in 1 em. hinting is the hinting policy.
71
	fontSize, dpi float64
72
	scale         fixed.Int26_6
73
	hinting       font.Hinting
74 75
	// cache is the glyph cache.
	cache [nGlyphs * nXFractions * nYFractions]cacheEntry
76 77
}

78
// PointToFixed converts the given number of points (as in "a 12 point font")
79 80
// into a 26.6 fixed point number of pixels.
func (c *Context) PointToFixed(x float64) fixed.Int26_6 {
81
	return fixed.Int26_6(x * float64(c.dpi) * (64.0 / 72.0))
82 83 84
}

// drawContour draws the given closed contour with the given offset.
85
func (c *Context) drawContour(ps []truetype.Point, dx, dy fixed.Int26_6) {
86 87 88
	if len(ps) == 0 {
		return
	}
89 90 91 92 93 94 95 96 97 98 99

	// 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).
100
	start := fixed.Point26_6{
101 102
		X: dx + ps[0].X,
		Y: dy - ps[0].Y,
103
	}
104 105 106 107
	others := []truetype.Point(nil)
	if ps[0].Flags&0x01 != 0 {
		others = ps[1:]
	} else {
108
		last := fixed.Point26_6{
109 110
			X: dx + ps[len(ps)-1].X,
			Y: dy - ps[len(ps)-1].Y,
111 112 113 114 115
		}
		if ps[len(ps)-1].Flags&0x01 != 0 {
			start = last
			others = ps[:len(ps)-1]
		} else {
116
			start = fixed.Point26_6{
117 118 119 120 121 122
				X: (start.X + last.X) / 2,
				Y: (start.Y + last.Y) / 2,
			}
			others = ps
		}
	}
123 124
	c.r.Start(start)
	q0, on0 := start, true
125
	for _, p := range others {
126
		q := fixed.Point26_6{
127 128
			X: dx + p.X,
			Y: dy - p.Y,
129 130 131 132 133 134 135 136 137 138 139 140
		}
		on := p.Flags&0x01 != 0
		if on {
			if on0 {
				c.r.Add1(q)
			} else {
				c.r.Add2(q0, q)
			}
		} else {
			if on0 {
				// No-op.
			} else {
141
				mid := fixed.Point26_6{
142 143
					X: (q0.X + q.X) / 2,
					Y: (q0.Y + q.Y) / 2,
144 145 146 147 148 149 150 151 152 153 154 155 156 157
				}
				c.r.Add2(q0, mid)
			}
		}
		q0, on0 = q, on
	}
	// Close the curve.
	if on0 {
		c.r.Add1(start)
	} else {
		c.r.Add2(q0, start)
	}
}

158 159
// rasterize returns the advance width, glyph mask and integer-pixel offset
// to render the given glyph at the given sub-pixel offsets.
N
Nigel Tao 已提交
160
// The 26.6 fixed point arguments fx and fy must be in the range [0, 1).
161 162
func (c *Context) rasterize(glyph truetype.Index, fx, fy fixed.Int26_6) (
	fixed.Int26_6, *image.Alpha, image.Point, error) {
163

164
	if err := c.glyphBuf.Load(c.f, c.scale, glyph, c.hinting); err != nil {
165
		return 0, nil, image.Point{}, err
166 167
	}
	// Calculate the integer-pixel bounds for the glyph.
168 169 170 171
	xmin := int(fx+c.glyphBuf.Bounds.Min.X) >> 6
	ymin := int(fy-c.glyphBuf.Bounds.Max.Y) >> 6
	xmax := int(fx+c.glyphBuf.Bounds.Max.X+0x3f) >> 6
	ymax := int(fy-c.glyphBuf.Bounds.Min.Y+0x3f) >> 6
172
	if xmin > xmax || ymin > ymax {
173
		return 0, nil, image.Point{}, errors.New("freetype: negative sized glyph")
174 175
	}
	// A TrueType's glyph's nodes can have negative co-ordinates, but the
176 177 178 179
	// 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.
180 181
	fx -= fixed.Int26_6(xmin << 6)
	fy -= fixed.Int26_6(ymin << 6)
182 183 184
	// Rasterize the glyph's vectors.
	c.r.Clear()
	e0 := 0
185 186
	for _, e1 := range c.glyphBuf.Ends {
		c.drawContour(c.glyphBuf.Points[e0:e1], fx, fy)
187 188
		e0 = e1
	}
189
	a := image.NewAlpha(image.Rect(0, 0, xmax-xmin, ymax-ymin))
190
	c.r.Rasterize(raster.NewAlphaSrcPainter(a))
191
	return c.glyphBuf.AdvanceWidth, a, image.Point{xmin, ymin}, nil
192 193
}

194 195 196 197
// glyph returns the advance width, glyph mask and integer-pixel offset to
// render the given glyph at the given sub-pixel point. It is a cache for the
// rasterize method. Unlike rasterize, p's co-ordinates do not have to be in
// the range [0, 1).
198 199
func (c *Context) glyph(glyph truetype.Index, p fixed.Point26_6) (
	fixed.Int26_6, *image.Alpha, image.Point, error) {
200

201
	// Split p.X and p.Y into their integer and fractional parts.
202 203
	ix, fx := int(p.X>>6), p.X&0x3f
	iy, fy := int(p.Y>>6), p.Y&0x3f
204 205
	// Calculate the index t into the cache array.
	tg := int(glyph) % nGlyphs
206 207
	tx := int(fx) / (64 / nXFractions)
	ty := int(fy) / (64 / nYFractions)
208 209
	t := ((tg*nXFractions)+tx)*nYFractions + ty
	// Check for a cache hit.
210 211
	if e := c.cache[t]; e.valid && e.glyph == glyph {
		return e.advanceWidth, e.mask, e.offset.Add(image.Point{ix, iy}), nil
212 213
	}
	// Rasterize the glyph and put the result into the cache.
214
	advanceWidth, mask, offset, err := c.rasterize(glyph, fx, fy)
215
	if err != nil {
216
		return 0, nil, image.Point{}, err
217
	}
218 219
	c.cache[t] = cacheEntry{true, glyph, advanceWidth, mask, offset}
	return advanceWidth, mask, offset.Add(image.Point{ix, iy}), nil
220 221
}

222
// DrawString draws s at p and returns p advanced by the text extent. The text
223
// is placed so that the left edge of the em square of the first character of s
224 225 226 227
// and the baseline intersect at p. The majority of the affected pixels will be
// above and to the right of the point, but some may be below or to the left.
// For example, drawing a string that starts with a 'J' in an italic font may
// affect pixels below and left of the point.
228 229 230
//
// 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) {
231
	if c.f == nil {
232
		return fixed.Point26_6{}, errors.New("freetype: DrawText called with a nil font")
233 234
	}
	prev, hasPrev := truetype.Index(0), false
235
	for _, rune := range s {
236
		index := c.f.Index(rune)
237
		if hasPrev {
N
Nigel Tao 已提交
238
			kern := c.f.Kern(c.scale, prev, index)
239
			if c.hinting != font.HintingNone {
240
				kern = (kern + 32) &^ 63
241 242
			}
			p.X += kern
243
		}
244
		advanceWidth, mask, offset, err := c.glyph(index, p)
245
		if err != nil {
246
			return fixed.Point26_6{}, err
247
		}
248
		p.X += advanceWidth
N
Nigel Tao 已提交
249 250 251 252 253 254
		glyphRect := mask.Bounds().Add(offset)
		dr := c.clip.Intersect(glyphRect)
		if !dr.Empty() {
			mp := image.Point{0, dr.Min.Y - glyphRect.Min.Y}
			draw.DrawMask(c.dst, dr, c.src, image.ZP, mask, mp, draw.Over)
		}
255 256
		prev, hasPrev = index, true
	}
257
	return p, nil
258 259 260
}

// recalc recalculates scale and bounds values from the font size, screen
261
// resolution and font metrics, and invalidates the glyph cache.
262
func (c *Context) recalc() {
263
	c.scale = fixed.Int26_6(c.fontSize * c.dpi * (64.0 / 72.0))
264
	if c.f == nil {
265
		c.r.SetBounds(0, 0)
266
	} else {
267
		// Set the rasterizer's bounds to be big enough to handle the largest glyph.
268
		b := c.f.Bounds(c.scale)
269 270 271 272
		xmin := +int(b.Min.X) >> 6
		ymin := -int(b.Max.Y) >> 6
		xmax := +int(b.Max.X+63) >> 6
		ymax := -int(b.Min.Y-63) >> 6
273 274 275 276
		c.r.SetBounds(xmax-xmin, ymax-ymin)
	}
	for i := range c.cache {
		c.cache[i] = cacheEntry{}
277 278 279 280
	}
}

// SetDPI sets the screen resolution in dots per inch.
281
func (c *Context) SetDPI(dpi float64) {
282 283 284
	if c.dpi == dpi {
		return
	}
285 286 287 288 289
	c.dpi = dpi
	c.recalc()
}

// SetFont sets the font used to draw text.
290 291
func (c *Context) SetFont(f *truetype.Font) {
	if c.f == f {
292 293
		return
	}
294
	c.f = f
295 296 297
	c.recalc()
}

298
// SetFontSize sets the font size in points (as in "a 12 point font").
N
Nigel Tao 已提交
299
func (c *Context) SetFontSize(fontSize float64) {
300 301 302
	if c.fontSize == fontSize {
		return
	}
303 304 305 306
	c.fontSize = fontSize
	c.recalc()
}

307
// SetHinting sets the hinting policy.
308
func (c *Context) SetHinting(hinting font.Hinting) {
309 310 311 312 313 314
	c.hinting = hinting
	for i := range c.cache {
		c.cache[i] = cacheEntry{}
	}
}

315 316 317 318 319 320
// SetDst sets the destination image for draw operations.
func (c *Context) SetDst(dst draw.Image) {
	c.dst = dst
}

// SetSrc sets the source image for draw operations. This is typically an
321
// image.Uniform.
322 323 324 325
func (c *Context) SetSrc(src image.Image) {
	c.src = src
}

N
Nigel Tao 已提交
326 327 328 329 330
// SetClip sets the clip rectangle for drawing.
func (c *Context) SetClip(clip image.Rectangle) {
	c.clip = clip
}

331 332
// TODO(nigeltao): implement Context.SetGamma.

333 334 335
// NewContext creates a new Context.
func NewContext() *Context {
	return &Context{
336 337 338
		r:        raster.NewRasterizer(0, 0),
		fontSize: 12,
		dpi:      72,
339
		scale:    12 << 6,
340 341
	}
}