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" "image/draw" "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 // dst and src are the destination and source images for drawing. dst draw.Image 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{} } } // setFont sets the font used to draw text. func (c *frContext) setFont(f *truetype.Font) { if c.f == f { return } c.f = f c.recalc() } // setFontSize sets the font size in points (as in "a 12 point font"). func (c *frContext) setFontSize(fontSize fixed.Int26_6) { if c.fontSize == fontSize { return } c.fontSize = fontSize c.recalc() } // setHinting sets the hinting policy. func (c *frContext) setHinting(hinting font.Hinting) { c.hinting = hinting for i := range c.cache { c.cache[i] = cacheEntry{} } } // setDst sets the destination image for draw operations. func (c *frContext) setDst(dst draw.Image) { c.dst = dst } 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) } // TODO(nigeltao): implement Context.SetGamma. // NewContext creates a new Context. func newFRContext() *frContext { return &frContext{ r: raster.NewRasterizer(0, 0), fontSize: fixed.I(12), } }