257 lines
7.5 KiB
Go
257 lines
7.5 KiB
Go
package canvas
|
|
|
|
// Copyright 2010 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.
|
|
|
|
import (
|
|
"errors"
|
|
"image"
|
|
|
|
"github.com/golang/freetype/raster"
|
|
"github.com/golang/freetype/truetype"
|
|
"golang.org/x/image/font"
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
// 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 {
|
|
valid bool
|
|
glyph truetype.Index
|
|
advanceWidth fixed.Int26_6
|
|
mask *image.Alpha
|
|
offset image.Point
|
|
}
|
|
|
|
// A Context holds the state for drawing text in a given font and size.
|
|
type frContext struct {
|
|
r *raster.Rasterizer
|
|
f *truetype.Font
|
|
glyphBuf truetype.GlyphBuf
|
|
|
|
fontSize fixed.Int26_6
|
|
hinting font.Hinting
|
|
// cache is the glyph cache.
|
|
cache [nGlyphs * nXFractions * nYFractions]cacheEntry
|
|
}
|
|
|
|
// drawContour draws the given closed contour with the given offset.
|
|
func (c *frContext) drawContour(ps []truetype.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 + ps[0].X,
|
|
Y: dy - ps[0].Y,
|
|
}
|
|
others := []truetype.Point(nil)
|
|
if ps[0].Flags&0x01 != 0 {
|
|
others = ps[1:]
|
|
} else {
|
|
last := fixed.Point26_6{
|
|
X: dx + ps[len(ps)-1].X,
|
|
Y: dy - 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
|
|
}
|
|
}
|
|
c.r.Start(start)
|
|
q0, on0 := start, true
|
|
for _, p := range others {
|
|
q := fixed.Point26_6{
|
|
X: dx + p.X,
|
|
Y: dy - p.Y,
|
|
}
|
|
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 {
|
|
mid := fixed.Point26_6{
|
|
X: (q0.X + q.X) / 2,
|
|
Y: (q0.Y + q.Y) / 2,
|
|
}
|
|
c.r.Add2(q0, mid)
|
|
}
|
|
}
|
|
q0, on0 = q, on
|
|
}
|
|
// Close the curve.
|
|
if on0 {
|
|
c.r.Add1(start)
|
|
} else {
|
|
c.r.Add2(q0, start)
|
|
}
|
|
}
|
|
|
|
// 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 (c *frContext) rasterize(glyph truetype.Index, fx, fy fixed.Int26_6) (fixed.Int26_6, *image.Alpha, image.Point, error) {
|
|
|
|
if err := c.glyphBuf.Load(c.f, c.fontSize, glyph, c.hinting); err != nil {
|
|
return 0, nil, image.Point{}, err
|
|
}
|
|
// Calculate the integer-pixel bounds for the glyph.
|
|
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
|
|
if xmin > xmax || ymin > ymax {
|
|
return 0, nil, image.Point{}, errors.New("freetype: negative sized glyph")
|
|
}
|
|
// 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.
|
|
c.r.Clear()
|
|
e0 := 0
|
|
for _, e1 := range c.glyphBuf.Ends {
|
|
c.drawContour(c.glyphBuf.Points[e0:e1], fx, fy)
|
|
e0 = e1
|
|
}
|
|
a := image.NewAlpha(image.Rect(0, 0, xmax-xmin, ymax-ymin))
|
|
c.r.Rasterize(raster.NewAlphaSrcPainter(a))
|
|
return c.glyphBuf.AdvanceWidth, a, image.Point{xmin, ymin}, nil
|
|
}
|
|
|
|
// 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).
|
|
func (c *frContext) glyph(glyph truetype.Index, p fixed.Point26_6) (fixed.Int26_6, *image.Alpha, image.Point, error) {
|
|
|
|
// Split p.X and p.Y into their integer and fractional parts.
|
|
ix, fx := int(p.X>>6), p.X&0x3f
|
|
iy, fy := int(p.Y>>6), p.Y&0x3f
|
|
// Calculate the index t into the cache array.
|
|
tg := int(glyph) % nGlyphs
|
|
tx := int(fx) / (64 / nXFractions)
|
|
ty := int(fy) / (64 / nYFractions)
|
|
t := ((tg*nXFractions)+tx)*nYFractions + ty
|
|
// Check for a cache hit.
|
|
if e := c.cache[t]; e.valid && e.glyph == glyph {
|
|
return e.advanceWidth, e.mask, e.offset.Add(image.Point{ix, iy}), nil
|
|
}
|
|
// Rasterize the glyph and put the result into the cache.
|
|
advanceWidth, mask, offset, err := c.rasterize(glyph, fx, fy)
|
|
if err != nil {
|
|
return 0, nil, image.Point{}, err
|
|
}
|
|
c.cache[t] = cacheEntry{true, glyph, advanceWidth, mask, offset}
|
|
return advanceWidth, mask, offset.Add(image.Point{ix, iy}), nil
|
|
}
|
|
|
|
func (c *frContext) glyphAdvance(glyph truetype.Index) (fixed.Int26_6, error) {
|
|
if err := c.glyphBuf.Load(c.f, c.fontSize, glyph, c.hinting); err != nil {
|
|
return 0, err
|
|
}
|
|
return c.glyphBuf.AdvanceWidth, nil
|
|
}
|
|
|
|
func (c *frContext) glyphMeasure(glyph truetype.Index, p fixed.Point26_6) (fixed.Int26_6, image.Rectangle, error) {
|
|
if err := c.glyphBuf.Load(c.f, c.fontSize, glyph, c.hinting); err != nil {
|
|
return 0, image.Rectangle{}, err
|
|
}
|
|
|
|
fx := p.X & 0x3f
|
|
fy := p.Y & 0x3f
|
|
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
|
|
bounds := image.Rectangle{
|
|
Min: image.Point{X: xmin, Y: ymin},
|
|
Max: image.Point{X: xmax, Y: ymax}}
|
|
|
|
return c.glyphBuf.AdvanceWidth, bounds, nil
|
|
}
|
|
|
|
func (c *frContext) glyphBounds(glyph truetype.Index, p fixed.Point26_6) (image.Rectangle, error) {
|
|
_, bounds, err := c.glyphMeasure(glyph, p)
|
|
return bounds, err
|
|
}
|
|
|
|
const maxInt = int(^uint(0) >> 1)
|
|
|
|
func (c *frContext) recalc() {
|
|
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.f.Bounds(c.fontSize)
|
|
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
|
|
c.r.SetBounds(xmax-xmin, ymax-ymin)
|
|
}
|
|
for i := range c.cache {
|
|
c.cache[i] = cacheEntry{}
|
|
}
|
|
}
|
|
|
|
func (c *frContext) cacheSize() int {
|
|
if c.f == nil {
|
|
return 0
|
|
}
|
|
|
|
b := c.f.Bounds(c.fontSize)
|
|
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
|
|
w := xmax - xmin
|
|
h := ymax - ymin
|
|
return w * h * len(c.cache)
|
|
}
|
|
|
|
func newFRContext() *frContext {
|
|
return &frContext{
|
|
r: raster.NewRasterizer(0, 0),
|
|
fontSize: fixed.I(12),
|
|
hinting: font.HintingFull,
|
|
}
|
|
}
|