From 28cc5fbc5d0b1041e49e461142e596f94ef33593 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Wed, 25 Jul 2012 22:10:25 +1000 Subject: [PATCH] freetype: move the scaling from FUnits to pixels from package freetype to package truetype. R=bsiegert CC=golang-dev http://codereview.appspot.com/6427062 --- example/freetype/main.go | 4 +- example/truetype/main.go | 11 +++-- freetype/freetype.go | 79 +++++++++--------------------- freetype/truetype/glyph.go | 43 ++++++++++------ freetype/truetype/hint.go | 11 +++-- freetype/truetype/hint_test.go | 2 +- freetype/truetype/truetype.go | 77 +++++++++++++++++++---------- freetype/truetype/truetype_test.go | 15 +++--- 8 files changed, 126 insertions(+), 116 deletions(-) diff --git a/example/freetype/main.go b/example/freetype/main.go index 3b96c30..2d52c7a 100644 --- a/example/freetype/main.go +++ b/example/freetype/main.go @@ -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 { diff --git a/example/truetype/main.go b/example/truetype/main.go index a739073..5078840 100644 --- a/example/truetype/main.go +++ b/example/truetype/main.go @@ -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)) } diff --git a/freetype/freetype.go b/freetype/freetype.go index 3755e65..d6285bc 100644 --- a/freetype/freetype.go +++ b/freetype/freetype.go @@ -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, } } diff --git a/freetype/truetype/glyph.go b/freetype/truetype/glyph.go index 9a75ed5..7ddf87e 100644 --- a/freetype/truetype/glyph.go +++ b/freetype/truetype/glyph.go @@ -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) diff --git a/freetype/truetype/hint.go b/freetype/truetype/hint.go index 921c1fa..9e5ee5d 100644 --- a/freetype/truetype/hint.go +++ b/freetype/truetype/hint.go @@ -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 } diff --git a/freetype/truetype/hint_test.go b/freetype/truetype/hint_test.go index 5039a6c..d29001c 100644 --- a/freetype/truetype/hint_test.go +++ b/freetype/truetype/hint_test.go @@ -508,7 +508,7 @@ func TestBytecode(t *testing.T) { } for _, tc := range testCases { - h := &hinter{} + h := &Hinter{} h.init(&Font{ maxStorage: 32, maxStackElements: 100, diff --git a/freetype/truetype/truetype.go b/freetype/truetype/truetype.go index 03dd54c..57bc9eb 100644 --- a/freetype/truetype/truetype.go +++ b/freetype/truetype/truetype.go @@ -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 diff --git a/freetype/truetype/truetype_test.go b/freetype/truetype/truetype_test.go index b861a36..a9d7875 100644 --- a/freetype/truetype/truetype_test.go +++ b/freetype/truetype/truetype_test.go @@ -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) }