freetype: move the scaling from FUnits to pixels from package freetype

to package truetype.

R=bsiegert
CC=golang-dev
http://codereview.appspot.com/6427062
This commit is contained in:
Nigel Tao 2012-07-25 22:10:25 +10:00
parent f2517f3940
commit 28cc5fbc5d
8 changed files with 126 additions and 116 deletions

View file

@ -21,7 +21,7 @@ import (
)
var (
dpi = flag.Int("dpi", 72, "screen resolution in Dots Per Inch")
dpi = flag.Float64("dpi", 72, "screen resolution in Dots Per Inch")
fontfile = flag.String("fontfile", "../../luxi-fonts/luxisr.ttf", "filename of the ttf font")
size = flag.Float64("size", 12, "font size in points")
spacing = flag.Float64("spacing", 1.5, "line spacing (e.g. 2 means double spaced)")
@ -104,7 +104,7 @@ func main() {
}
// Draw the text.
pt := freetype.Pt(10, 10+c.FUnitToPixelRU(font.UnitsPerEm()))
pt := freetype.Pt(10, 10+int(c.PointToFix32(*size)>>8))
for _, s := range text {
_, err = c.DrawString(s, pt)
if err != nil {

View file

@ -51,15 +51,16 @@ func main() {
log.Println(err)
return
}
printBounds(font.Bounds())
fmt.Printf("UnitsPerEm:%d\n\n", font.UnitsPerEm())
fupe := font.FUnitsPerEm()
printBounds(font.Bounds(fupe))
fmt.Printf("FUnitsPerEm:%d\n\n", fupe)
c0, c1 := 'A', 'V'
i0 := font.Index(c0)
hm := font.HMetric(i0)
hm := font.HMetric(fupe, i0)
g := truetype.NewGlyphBuf()
err = g.Load(font, i0)
err = g.Load(font, fupe, i0, nil)
if err != nil {
log.Println(err)
return
@ -68,5 +69,5 @@ func main() {
fmt.Printf("AdvanceWidth:%d LeftSideBearing:%d\n", hm.AdvanceWidth, hm.LeftSideBearing)
printGlyph(g)
i1 := font.Index(c1)
fmt.Printf("\n'%c', '%c' Kerning:%d\n", c0, c1, font.Kerning(i0, i1))
fmt.Printf("\n'%c', '%c' Kerning:%d\n", c0, c1, font.Kerning(fupe, i0, i1))
}

View file

@ -63,42 +63,14 @@ type Context struct {
// dst and src are the destination and source images for drawing.
dst draw.Image
src image.Image
// fontSize, dpi and upe are used to calculate scale.
// 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.
fontSize float64
dpi int
upe int
scale int
// fontSize and dpi are used to calculate scale. scale is the number of
// 26.6 fixed point units in 1 em.
fontSize, dpi float64
scale int32
// cache is the glyph cache.
cache [nGlyphs * nXFractions * nYFractions]cacheEntry
}
// FUnitToFix32 converts the given number of FUnits into fixed point units,
// rounding to nearest.
func (c *Context) FUnitToFix32(x int) raster.Fix32 {
return raster.Fix32((x*c.scale + 128) >> 8)
}
// FUnitToPixelRD converts the given number of FUnits into pixel units,
// rounding down.
func (c *Context) FUnitToPixelRD(x int) int {
return x * c.scale >> 16
}
// FUnitToPixelRU converts the given number of FUnits into pixel units,
// rounding up.
func (c *Context) FUnitToPixelRU(x int) int {
return (x*c.scale + 0xffff) >> 16
}
// PointToFix32 converts the given number of points (as in ``a 12 point font'')
// into fixed point units.
func (c *Context) PointToFix32(x float64) raster.Fix32 {
@ -114,15 +86,15 @@ func (c *Context) drawContour(ps []truetype.Point, dx, dy raster.Fix32) {
// start is the same thing measured in fixed point units and positive Y
// going downwards, and offset by (dx, dy)
start := raster.Point{
X: dx + c.FUnitToFix32(int(ps[0].X)),
Y: dy + c.FUnitToFix32(-int(ps[0].Y)),
X: dx + raster.Fix32(ps[0].X<<2),
Y: dy - raster.Fix32(ps[0].Y<<2),
}
c.r.Start(start)
q0, on0 := start, true
for _, p := range ps[1:] {
q := raster.Point{
X: dx + c.FUnitToFix32(int(p.X)),
Y: dy + c.FUnitToFix32(-int(p.Y)),
X: dx + raster.Fix32(p.X<<2),
Y: dy - raster.Fix32(p.Y<<2),
}
on := p.Flags&0x01 != 0
if on {
@ -156,14 +128,14 @@ func (c *Context) drawContour(ps []truetype.Point, dx, dy raster.Fix32) {
// given glyph at the given sub-pixel offsets.
// The 24.8 fixed point arguments fx and fy must be in the range [0, 1).
func (c *Context) rasterize(glyph truetype.Index, fx, fy raster.Fix32) (*image.Alpha, image.Point, error) {
if err := c.glyphBuf.Load(c.font, glyph); err != nil {
if err := c.glyphBuf.Load(c.font, c.scale, glyph, nil); err != nil {
return nil, image.ZP, err
}
// Calculate the integer-pixel bounds for the glyph.
xmin := int(fx+c.FUnitToFix32(+int(c.glyphBuf.B.XMin))) >> 8
ymin := int(fy+c.FUnitToFix32(-int(c.glyphBuf.B.YMax))) >> 8
xmax := int(fx+c.FUnitToFix32(+int(c.glyphBuf.B.XMax))+0xff) >> 8
ymax := int(fy+c.FUnitToFix32(-int(c.glyphBuf.B.YMin))+0xff) >> 8
xmin := int(fx+raster.Fix32(c.glyphBuf.B.XMin<<2)) >> 8
ymin := int(fy-raster.Fix32(c.glyphBuf.B.YMax<<2)) >> 8
xmax := int(fx+raster.Fix32(c.glyphBuf.B.XMax<<2)+0xff) >> 8
ymax := int(fy-raster.Fix32(c.glyphBuf.B.YMin<<2)+0xff) >> 8
if xmin > xmax || ymin > ymax {
return nil, image.ZP, errors.New("freetype: negative sized glyph")
}
@ -226,13 +198,13 @@ func (c *Context) DrawString(s string, p raster.Point) (raster.Point, error) {
for _, rune := range s {
index := c.font.Index(rune)
if hasPrev {
p.X += c.FUnitToFix32(int(c.font.Kerning(prev, index)))
p.X += raster.Fix32(c.font.Kerning(c.scale, prev, index)) << 2
}
mask, offset, err := c.glyph(index, p)
if err != nil {
return raster.Point{}, err
}
p.X += c.FUnitToFix32(int(c.font.HMetric(index).AdvanceWidth))
p.X += raster.Fix32(c.font.HMetric(c.scale, index).AdvanceWidth) << 2
glyphRect := mask.Bounds().Add(offset)
dr := c.clip.Intersect(glyphRect)
if !dr.Empty() {
@ -247,16 +219,16 @@ func (c *Context) DrawString(s string, p raster.Point) (raster.Point, error) {
// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (c *Context) recalc() {
c.scale = int((c.fontSize * float64(c.dpi) * 256 * 256) / (float64(c.upe) * 72))
c.scale = int32(c.fontSize * c.dpi * (64.0 / 72.0))
if c.font == nil {
c.r.SetBounds(0, 0)
} else {
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
b := c.font.Bounds()
xmin := c.FUnitToPixelRD(+int(b.XMin))
ymin := c.FUnitToPixelRD(-int(b.YMax))
xmax := c.FUnitToPixelRU(+int(b.XMax))
ymax := c.FUnitToPixelRU(-int(b.YMin))
b := c.font.Bounds(c.scale)
xmin := +int(b.XMin) >> 6
ymin := -int(b.YMax) >> 6
xmax := +int(b.XMax+63) >> 6
ymax := -int(b.YMin-63) >> 6
c.r.SetBounds(xmax-xmin, ymax-ymin)
}
for i := range c.cache {
@ -265,7 +237,7 @@ func (c *Context) recalc() {
}
// SetDPI sets the screen resolution in dots per inch.
func (c *Context) SetDPI(dpi int) {
func (c *Context) SetDPI(dpi float64) {
if c.dpi == dpi {
return
}
@ -279,10 +251,6 @@ func (c *Context) SetFont(font *truetype.Font) {
return
}
c.font = font
c.upe = font.UnitsPerEm()
if c.upe <= 0 {
c.upe = 1
}
c.recalc()
}
@ -320,7 +288,6 @@ func NewContext() *Context {
glyphBuf: truetype.NewGlyphBuf(),
fontSize: 12,
dpi: 72,
upe: 2048,
scale: (12 * 72 * 256 * 256) / (2048 * 72),
scale: 12 << 6,
}
}

View file

@ -8,10 +8,10 @@ package truetype
// A Point is a co-ordinate pair plus whether it is ``on'' a contour or an
// ``off'' control point.
type Point struct {
X, Y int16
X, Y int32
// The Flags' LSB means whether or not this Point is ``on'' the contour.
// Other bits are reserved for internal use.
Flags uint8
Flags uint32
}
// A GlyphBuf holds a glyph's contours. A GlyphBuf can be re-used to load a
@ -49,7 +49,7 @@ const (
// and returns the remaining data.
func (g *GlyphBuf) decodeFlags(d []byte, offset int, np0 int) (offset1 int) {
for i := np0; i < len(g.Point); {
c := d[offset]
c := uint32(d[offset])
offset++
g.Point[i].Flags = c
i++
@ -82,7 +82,7 @@ func (g *GlyphBuf) decodeCoords(d []byte, offset int, np0 int) int {
x += int16(u16(d, offset))
offset += 2
}
g.Point[i].X = x
g.Point[i].X = int32(x)
}
var y int16
for i := np0; i < len(g.Point); i++ {
@ -99,19 +99,34 @@ func (g *GlyphBuf) decodeCoords(d []byte, offset int, np0 int) int {
y += int16(u16(d, offset))
offset += 2
}
g.Point[i].Y = y
g.Point[i].Y = int32(y)
}
return offset
}
// Load loads a glyph's contours from a Font, overwriting any previously
// loaded contours for this GlyphBuf.
func (g *GlyphBuf) Load(f *Font, i Index) error {
// loaded contours for this GlyphBuf. The Hinter is optional; if non-nil, then
// the resulting glyph will be hinted by the Font's bytecode instructions.
func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h *Hinter) error {
// Reset the GlyphBuf.
g.B = Bounds{}
g.Point = g.Point[:0]
g.End = g.End[:0]
return g.load(f, i, 0)
if err := g.load(f, i, 0); err != nil {
return err
}
g.B.XMin = f.scale(scale * g.B.XMin)
g.B.YMin = f.scale(scale * g.B.YMin)
g.B.XMax = f.scale(scale * g.B.XMax)
g.B.YMax = f.scale(scale * g.B.YMax)
for i := range g.Point {
g.Point[i].X = f.scale(scale * g.Point[i].X)
g.Point[i].Y = f.scale(scale * g.Point[i].Y)
}
if h != nil {
// TODO: invoke h.
}
return nil
}
// loadCompound loads a glyph that is composed of other glyphs.
@ -153,8 +168,8 @@ func (g *GlyphBuf) loadCompound(f *Font, glyf []byte, offset, recursion int) err
b0, i0 := g.B, len(g.Point)
g.load(f, Index(component), recursion+1)
for i := i0; i < len(g.Point); i++ {
g.Point[i].X += dx
g.Point[i].Y += dy
g.Point[i].X += int32(dx)
g.Point[i].Y += int32(dy)
}
if flags&flagUseMyMetrics == 0 {
g.B = b0
@ -186,10 +201,10 @@ func (g *GlyphBuf) load(f *Font, i Index, recursion int) error {
glyf := f.glyf[g0:g1]
// Decode the contour end indices.
ne := int(int16(u16(glyf, 0)))
g.B.XMin = int16(u16(glyf, 2))
g.B.YMin = int16(u16(glyf, 4))
g.B.XMax = int16(u16(glyf, 6))
g.B.YMax = int16(u16(glyf, 8))
g.B.XMin = int32(int16(u16(glyf, 2)))
g.B.YMin = int32(int16(u16(glyf, 4)))
g.B.XMax = int32(int16(u16(glyf, 6)))
g.B.YMax = int32(int16(u16(glyf, 8)))
offset := 10
if ne == -1 {
return g.loadCompound(f, glyf, offset, recursion)

View file

@ -12,7 +12,10 @@ import (
"errors"
)
type hinter struct {
// Hinter implements bytecode hinting. Pass a Hinter to GlyphBuf.Load to hint
// the resulting glyph. A Hinter can be re-used to hint a series of glyphs from
// a Font.
type Hinter struct {
stack, store []int32
// The fields below constitue the graphics state, which is described at
@ -28,7 +31,7 @@ type hinter struct {
roundPeriod, roundPhase, roundThreshold f26dot6
}
func (h *hinter) init(f *Font) {
func (h *Hinter) init(f *Font) {
if x := int(f.maxStackElements); x > len(h.stack) {
x += 255
x &^= 255
@ -41,7 +44,7 @@ func (h *hinter) init(f *Font) {
}
}
func (h *hinter) run(program []byte) error {
func (h *Hinter) run(program []byte) error {
// The default vectors are along the X axis.
h.pv = [2]f2dot14{0x4000, 0}
h.fv = [2]f2dot14{0x4000, 0}
@ -517,7 +520,7 @@ func (x f26dot6) mul(y f26dot6) f26dot6 {
// round rounds the given number. The rounding algorithm is described at
// https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding
func (h *hinter) round(x f26dot6) f26dot6 {
func (h *Hinter) round(x f26dot6) f26dot6 {
if h.roundPeriod == 0 {
return x
}

View file

@ -508,7 +508,7 @@ func TestBytecode(t *testing.T) {
}
for _, tc := range testCases {
h := &hinter{}
h := &Hinter{}
h.init(&Font{
maxStorage: 32,
maxStackElements: 100,

View file

@ -7,10 +7,15 @@
// Those formats are documented at http://developer.apple.com/fonts/TTRefMan/
// and http://www.microsoft.com/typography/otspec/
//
// All numbers (e.g. bounds, point co-ordinates, font metrics) are measured in
// FUnits. To convert from FUnits to pixels, scale by
// (pointSize * resolution) / (font.UnitsPerEm() * 72dpi)
// For example, 550 FUnits at 18pt, 72dpi and 2048upe is 4.83 pixels.
// Some of a font's methods provide lengths or co-ordinates, e.g. bounds, font
// metrics and control points. All these methods take a scale parameter, which
// is the number of device units in 1 em. For example, if 1 em is 10 pixels and
// 1 pixel is 64 units, then scale is 640. If the device space involves pixels,
// 64 units per pixel is recommended, since that is what the bytecode hinter
// uses when snapping point co-ordinates to the pixel grid.
//
// To measure a TrueType font in ideal FUnit space, use scale equal to
// font.FUnitsPerEm().
package truetype
import (
@ -23,13 +28,13 @@ type Index uint16
// A Bounds holds the co-ordinate range of one or more glyphs.
// The endpoints are inclusive.
type Bounds struct {
XMin, YMin, XMax, YMax int16
XMin, YMin, XMax, YMax int32
}
// An HMetric holds the horizontal metrics of a single glyph.
type HMetric struct {
AdvanceWidth uint16
LeftSideBearing int16
AdvanceWidth int32
LeftSideBearing int32
}
// A FormatError reports that the input is not a valid TrueType font.
@ -96,7 +101,7 @@ type Font struct {
cm []cm
locaOffsetFormat int
nGlyph, nHMetric, nKern int
unitsPerEm int
fUnitsPerEm int32
bounds Bounds
// Values from the maxp section.
maxTwilightPoints, maxStorage, maxFunctionDefs, maxStackElements uint16
@ -183,11 +188,11 @@ func (f *Font) parseHead() error {
if len(f.head) != 54 {
return FormatError(fmt.Sprintf("bad head length: %d", len(f.head)))
}
f.unitsPerEm = int(u16(f.head, 18))
f.bounds.XMin = int16(u16(f.head, 36))
f.bounds.YMin = int16(u16(f.head, 38))
f.bounds.XMax = int16(u16(f.head, 40))
f.bounds.YMax = int16(u16(f.head, 42))
f.fUnitsPerEm = int32(u16(f.head, 18))
f.bounds.XMin = int32(int16(u16(f.head, 36)))
f.bounds.YMin = int32(int16(u16(f.head, 38)))
f.bounds.XMax = int32(int16(u16(f.head, 40)))
f.bounds.YMax = int32(int16(u16(f.head, 42)))
switch i := u16(f.head, 50); i {
case 0:
f.locaOffsetFormat = locaOffsetFormatShort
@ -263,14 +268,29 @@ func (f *Font) parseMaxp() error {
return nil
}
// Bounds returns the union of a Font's glyphs' bounds.
func (f *Font) Bounds() Bounds {
return f.bounds
// scale returns x divided by f.fUnitsPerEm, rounded to the nearest integer.
func (f *Font) scale(x int32) int32 {
if x >= 0 {
x += f.fUnitsPerEm / 2
} else {
x -= f.fUnitsPerEm / 2
}
return x / f.fUnitsPerEm
}
// UnitsPerEm returns the number of FUnits in a Font's em-square.
func (f *Font) UnitsPerEm() int {
return f.unitsPerEm
// Bounds returns the union of a Font's glyphs' bounds.
func (f *Font) Bounds(scale int32) Bounds {
b := f.bounds
b.XMin = f.scale(scale * b.XMin)
b.YMin = f.scale(scale * b.YMin)
b.XMax = f.scale(scale * b.XMax)
b.YMax = f.scale(scale * b.YMax)
return b
}
// FUnitsPerEm returns the number of FUnits in a Font's em-square's side.
func (f *Font) FUnitsPerEm() int32 {
return f.fUnitsPerEm
}
// Index returns a Font's index for the given rune.
@ -290,23 +310,26 @@ func (f *Font) Index(x rune) Index {
}
// HMetric returns the horizontal metrics for the glyph with the given index.
func (f *Font) HMetric(i Index) HMetric {
func (f *Font) HMetric(scale int32, i Index) (h HMetric) {
j := int(i)
if j >= f.nGlyph {
return HMetric{}
}
if j >= f.nHMetric {
p := 4 * (f.nHMetric - 1)
return HMetric{
u16(f.hmtx, p),
int16(u16(f.hmtx, p+2*(j-f.nHMetric)+4)),
}
h.AdvanceWidth = int32(u16(f.hmtx, p))
h.LeftSideBearing = int32(int16(u16(f.hmtx, p+2*(j-f.nHMetric)+4)))
} else {
h.AdvanceWidth = int32(u16(f.hmtx, 4*j))
h.LeftSideBearing = int32(int16(u16(f.hmtx, 4*j+2)))
}
return HMetric{u16(f.hmtx, 4*j), int16(u16(f.hmtx, 4*j+2))}
h.AdvanceWidth = f.scale(scale * h.AdvanceWidth)
h.LeftSideBearing = f.scale(scale * h.LeftSideBearing)
return h
}
// Kerning returns the kerning for the given glyph pair.
func (f *Font) Kerning(i0, i1 Index) int16 {
func (f *Font) Kerning(scale int32, i0, i1 Index) int32 {
if f.nKern == 0 {
return 0
}
@ -320,7 +343,7 @@ func (f *Font) Kerning(i0, i1 Index) int16 {
} else if ig > g {
hi = i
} else {
return int16(u16(f.kern, 22+6*i))
return f.scale(scale * int32(int16(u16(f.kern, 22+6*i))))
}
}
return 0

View file

@ -22,11 +22,12 @@ func TestParse(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if got, want := font.Bounds(), (Bounds{-441, -432, 2024, 2033}); got != want {
t.Errorf("Bounds: got %v, want %v", got, want)
if got, want := font.FUnitsPerEm(), int32(2048); got != want {
t.Errorf("FUnitsPerEm: got %v, want %v", got, want)
}
if got, want := font.UnitsPerEm(), 2048; got != want {
t.Errorf("UnitsPerEm: got %v, want %v", got, want)
fupe := font.FUnitsPerEm()
if got, want := font.Bounds(fupe), (Bounds{-441, -432, 2024, 2033}); got != want {
t.Errorf("Bounds: got %v, want %v", got, want)
}
i0 := font.Index('A')
@ -34,15 +35,15 @@ func TestParse(t *testing.T) {
if i0 != 36 || i1 != 57 {
t.Fatalf("Index: i0, i1 = %d, %d, want 36, 57", i0, i1)
}
if got, want := font.HMetric(i0), (HMetric{1366, 19}); got != want {
if got, want := font.HMetric(fupe, i0), (HMetric{1366, 19}); got != want {
t.Errorf("HMetric: got %v, want %v", got, want)
}
if got, want := font.Kerning(i0, i1), int16(-144); got != want {
if got, want := font.Kerning(fupe, i0, i1), int32(-144); got != want {
t.Errorf("Kerning: got %v, want %v", got, want)
}
g0 := NewGlyphBuf()
err = g0.Load(font, i0)
err = g0.Load(font, fupe, i0, nil)
if err != nil {
t.Fatalf("Load: %v", err)
}