2010-05-14 05:29:53 +02:00
|
|
|
// 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,
|
|
|
|
// both of which can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
package freetype
|
|
|
|
|
|
|
|
import (
|
|
|
|
"freetype-go.googlecode.com/hg/freetype/raster"
|
|
|
|
"freetype-go.googlecode.com/hg/freetype/truetype"
|
|
|
|
"image"
|
|
|
|
"os"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
func ParseFont(b []byte) (*truetype.Font, os.Error) {
|
|
|
|
return truetype.Parse(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pt converts from a co-ordinate pair measured in pixels to a raster.Point
|
|
|
|
// co-ordinate pair measured in raster.Fixed units.
|
|
|
|
func Pt(x, y int) raster.Point {
|
|
|
|
return raster.Point{raster.Fixed(x << 8), raster.Fixed(y << 8)}
|
|
|
|
}
|
|
|
|
|
|
|
|
// An RGBAContext holds the state for drawing text from a given font at a
|
|
|
|
// given size.
|
|
|
|
type RGBAContext struct {
|
|
|
|
r *raster.Rasterizer
|
2010-05-20 02:37:01 +02:00
|
|
|
rp *raster.RGBAPainter
|
|
|
|
gp *raster.GammaCorrectionPainter
|
2010-05-14 05:29:53 +02:00
|
|
|
font *truetype.Font
|
|
|
|
glyphBuf *truetype.GlyphBuf
|
|
|
|
fontSize float
|
|
|
|
dpi int
|
|
|
|
upe int
|
|
|
|
// 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.
|
|
|
|
xmin, ymin int
|
|
|
|
// scale is a multiplication factor to convert 256 FUnits (which is truetype's
|
|
|
|
// native unit) to 24.8 fixed point units (which is the rasterizer's native unit).
|
|
|
|
// At the default values of 72 DPI and 2048 units-per-em, one em of a 12 point
|
|
|
|
// font is 12 pixels, which is 3072 fixed point units, and scale is
|
|
|
|
// (pointSize * resolution * 256 * 256) / (unitsPerEm * 72), or
|
|
|
|
// (12 * 72 * 256 * 256) / (2048 * 72),
|
|
|
|
// which equals 384 fixed point units per 256 FUnits.
|
|
|
|
// To check this, 1 em * 2048 FUnits per em * 384 fixed point units per 256 FUnits
|
|
|
|
// equals 3072 fixed point units.
|
|
|
|
scale int
|
|
|
|
}
|
|
|
|
|
|
|
|
// FUnitToFixed converts the given number of FUnits into fixed point units,
|
|
|
|
// rounding to nearest.
|
|
|
|
func (c *RGBAContext) FUnitToFixed(x int) raster.Fixed {
|
|
|
|
return raster.Fixed((x*c.scale + 128) >> 8)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FUnitToPixelRD converts the given number of FUnits into pixel units,
|
|
|
|
// rounding down.
|
|
|
|
func (c *RGBAContext) FUnitToPixelRD(x int) int {
|
|
|
|
return x * c.scale >> 16
|
|
|
|
}
|
|
|
|
|
|
|
|
// FUnitToPixelRU converts the given number of FUnits into pixel units,
|
|
|
|
// rounding up.
|
|
|
|
func (c *RGBAContext) FUnitToPixelRU(x int) int {
|
|
|
|
return (x*c.scale + 0xffff) >> 16
|
|
|
|
}
|
|
|
|
|
|
|
|
// PointToFixed converts the given number of points (as in ``a 12 point font'')
|
|
|
|
// into fixed point units.
|
|
|
|
func (c *RGBAContext) PointToFixed(x float) raster.Fixed {
|
|
|
|
return raster.Fixed(x * float(c.dpi) * (256.0 / 72.0))
|
|
|
|
}
|
|
|
|
|
|
|
|
// drawContour draws the given closed contour with the given offset.
|
|
|
|
func (c *RGBAContext) drawContour(ps []truetype.Point, dx, dy raster.Fixed) {
|
|
|
|
if len(ps) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// 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 := raster.Point{
|
|
|
|
dx + c.FUnitToFixed(int(ps[0].X)),
|
|
|
|
dy + c.FUnitToFixed(c.upe-int(ps[0].Y)),
|
|
|
|
}
|
|
|
|
c.r.Start(start)
|
|
|
|
q0, on0 := start, true
|
|
|
|
for _, p := range ps[1:] {
|
|
|
|
q := raster.Point{
|
|
|
|
dx + c.FUnitToFixed(int(p.X)),
|
|
|
|
dy + c.FUnitToFixed(c.upe-int(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 := raster.Point{
|
|
|
|
(q0.X + q.X) / 2,
|
|
|
|
(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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// DrawText draws s at pt. The text is placed so that the top left of the em
|
|
|
|
// square of the first character of s is equal to pt. The majority of the
|
|
|
|
// affected pixels will be below and to the right of pt, but some may be above
|
|
|
|
// or to the left. For example, drawing a string that starts with a 'J' in an
|
|
|
|
// italic font may affect pixels to the left of pt.
|
|
|
|
// pt is a raster.Point and can therefore represent sub-pixel positions.
|
|
|
|
func (c *RGBAContext) DrawText(pt raster.Point, s string) (err os.Error) {
|
|
|
|
if c.font == nil {
|
|
|
|
return os.NewError("freetype: DrawText called with a nil font")
|
|
|
|
}
|
|
|
|
// pt.X, pt.Y, x, y, dx, dy and x0 are measured in raster.Fixed units,
|
|
|
|
// c.p.Dx, c.p.Dy, c.xmin and c.ymin are measured in pixels, and
|
|
|
|
// advance is measured in FUnits.
|
|
|
|
var x, y raster.Fixed
|
|
|
|
advance, x0 := 0, pt.X
|
|
|
|
dx := raster.Fixed(-c.xmin << 8)
|
|
|
|
dy := raster.Fixed(-c.ymin << 8)
|
2010-05-26 22:23:24 +02:00
|
|
|
c.r.Dy, y = c.ymin+int(pt.Y>>8), pt.Y&0xff
|
2010-05-14 05:29:53 +02:00
|
|
|
y += dy
|
|
|
|
prev, hasPrev := truetype.Index(0), false
|
|
|
|
for _, ch := range s {
|
|
|
|
index := c.font.Index(ch)
|
|
|
|
// Load the next glyph (if it was different from the previous one)
|
|
|
|
// and add any kerning adjustment.
|
|
|
|
if hasPrev {
|
|
|
|
advance += int(c.font.Kerning(prev, index))
|
|
|
|
if prev != index {
|
|
|
|
err = c.glyphBuf.Load(c.font, index)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err = c.glyphBuf.Load(c.font, index)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Convert the advance from FUnits to raster.Fixed units.
|
|
|
|
x = x0 + c.FUnitToFixed(advance)
|
|
|
|
// Break the co-ordinate down into an integer pixel part and a
|
|
|
|
// sub-pixel part, making sure that the latter is non-negative.
|
2010-05-26 22:23:24 +02:00
|
|
|
c.r.Dx, x = c.xmin+int(x>>8), x&0xff
|
2010-05-14 05:29:53 +02:00
|
|
|
x += dx
|
|
|
|
// Draw the contours.
|
|
|
|
c.r.Clear()
|
|
|
|
e0 := 0
|
|
|
|
for _, e := range c.glyphBuf.End {
|
|
|
|
c.drawContour(c.glyphBuf.Point[e0:e], x, y)
|
|
|
|
e0 = e
|
|
|
|
}
|
2010-05-20 02:37:01 +02:00
|
|
|
c.r.Rasterize(c.gp)
|
2010-05-14 05:29:53 +02:00
|
|
|
// Advance the cursor.
|
|
|
|
advance += int(c.font.HMetric(index).AdvanceWidth)
|
|
|
|
prev, hasPrev = index, true
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// recalc recalculates scale and bounds values from the font size, screen
|
|
|
|
// resolution and font metrics.
|
|
|
|
func (c *RGBAContext) recalc() {
|
|
|
|
c.scale = int((c.fontSize * float(c.dpi) * 256 * 256) / (float(c.upe) * 72))
|
|
|
|
if c.font == nil {
|
|
|
|
c.xmin, c.ymin = 0, 0
|
|
|
|
} else {
|
|
|
|
b := c.font.Bounds()
|
|
|
|
c.xmin = c.FUnitToPixelRD(int(b.XMin))
|
|
|
|
c.ymin = c.FUnitToPixelRD(c.upe - int(b.YMax))
|
|
|
|
xmax := c.FUnitToPixelRU(int(b.XMax))
|
|
|
|
ymax := c.FUnitToPixelRU(c.upe - int(b.YMin))
|
|
|
|
c.r.SetBounds(xmax-c.xmin, ymax-c.ymin)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetColor sets the color to draw text.
|
|
|
|
func (c *RGBAContext) SetColor(color image.Color) {
|
2010-05-20 02:37:01 +02:00
|
|
|
c.rp.SetColor(color)
|
2010-05-14 05:29:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetDPI sets the screen resolution in dots per inch.
|
|
|
|
func (c *RGBAContext) SetDPI(dpi int) {
|
|
|
|
c.dpi = dpi
|
|
|
|
c.recalc()
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetFont sets the font used to draw text.
|
|
|
|
func (c *RGBAContext) SetFont(font *truetype.Font) {
|
|
|
|
c.font = font
|
|
|
|
c.upe = font.UnitsPerEm()
|
|
|
|
if c.upe <= 0 {
|
|
|
|
c.upe = 1
|
|
|
|
}
|
|
|
|
c.recalc()
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetFontSize sets the font size in points (as in ``a 12 point font'').
|
|
|
|
func (c *RGBAContext) SetFontSize(fontSize float) {
|
|
|
|
c.fontSize = fontSize
|
|
|
|
c.recalc()
|
|
|
|
}
|
|
|
|
|
2010-05-20 02:37:01 +02:00
|
|
|
// SetGamma sets the gamma correction parameter.
|
|
|
|
func (c *RGBAContext) SetGamma(g float) {
|
|
|
|
c.gp.SetGamma(g)
|
|
|
|
}
|
|
|
|
|
2010-05-14 05:29:53 +02:00
|
|
|
// SetRGBA sets the image that the RGBAContext draws onto.
|
|
|
|
func (c *RGBAContext) SetRGBA(m *image.RGBA) {
|
2010-05-20 02:37:01 +02:00
|
|
|
c.rp.Image = m
|
2010-05-14 05:29:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewRGBAContext creates a new RGBAContext.
|
|
|
|
func NewRGBAContext(m *image.RGBA) *RGBAContext {
|
2010-05-20 02:37:01 +02:00
|
|
|
c := &RGBAContext{
|
2010-05-14 05:29:53 +02:00
|
|
|
r: raster.NewRasterizer(0, 0),
|
2010-05-20 02:37:01 +02:00
|
|
|
rp: raster.NewRGBAPainter(m),
|
2010-05-14 05:29:53 +02:00
|
|
|
glyphBuf: truetype.NewGlyphBuf(),
|
|
|
|
fontSize: 12,
|
|
|
|
dpi: 72,
|
|
|
|
upe: 2048,
|
|
|
|
scale: (12 * 72 * 256 * 256) / (2048 * 72),
|
|
|
|
}
|
2010-05-20 02:37:01 +02:00
|
|
|
c.gp = raster.NewGammaCorrectionPainter(c.rp, 1.0)
|
|
|
|
return c
|
2010-05-14 05:29:53 +02:00
|
|
|
}
|