From 500caaeda74dd9c660279036293f4b2997cf0b03 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Sat, 9 Sep 2017 09:42:37 +0200 Subject: Add vendor --- vendor/github.com/golang/freetype/truetype/face.go | 507 ++++++ .../golang/freetype/truetype/face_test.go | 48 + .../github.com/golang/freetype/truetype/glyph.go | 522 ++++++ vendor/github.com/golang/freetype/truetype/hint.go | 1770 ++++++++++++++++++++ .../golang/freetype/truetype/hint_test.go | 675 ++++++++ .../github.com/golang/freetype/truetype/opcodes.go | 289 ++++ .../golang/freetype/truetype/truetype.go | 653 ++++++++ .../golang/freetype/truetype/truetype_test.go | 400 +++++ 8 files changed, 4864 insertions(+) create mode 100644 vendor/github.com/golang/freetype/truetype/face.go create mode 100644 vendor/github.com/golang/freetype/truetype/face_test.go create mode 100644 vendor/github.com/golang/freetype/truetype/glyph.go create mode 100644 vendor/github.com/golang/freetype/truetype/hint.go create mode 100644 vendor/github.com/golang/freetype/truetype/hint_test.go create mode 100644 vendor/github.com/golang/freetype/truetype/opcodes.go create mode 100644 vendor/github.com/golang/freetype/truetype/truetype.go create mode 100644 vendor/github.com/golang/freetype/truetype/truetype_test.go (limited to 'vendor/github.com/golang/freetype/truetype') diff --git a/vendor/github.com/golang/freetype/truetype/face.go b/vendor/github.com/golang/freetype/truetype/face.go new file mode 100644 index 0000000..099006f --- /dev/null +++ b/vendor/github.com/golang/freetype/truetype/face.go @@ -0,0 +1,507 @@ +// Copyright 2015 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. + +package truetype + +import ( + "image" + "math" + + "github.com/golang/freetype/raster" + "golang.org/x/image/font" + "golang.org/x/image/math/fixed" +) + +func powerOf2(i int) bool { + return i != 0 && (i&(i-1)) == 0 +} + +// Options are optional arguments to NewFace. +type Options struct { + // Size is the font size in points, as in "a 10 point font size". + // + // A zero value means to use a 12 point font size. + Size float64 + + // DPI is the dots-per-inch resolution. + // + // A zero value means to use 72 DPI. + DPI float64 + + // Hinting is how to quantize the glyph nodes. + // + // A zero value means to use no hinting. + Hinting font.Hinting + + // GlyphCacheEntries is the number of entries in the glyph mask image + // cache. + // + // If non-zero, it must be a power of 2. + // + // A zero value means to use 512 entries. + GlyphCacheEntries int + + // SubPixelsX is the number of sub-pixel locations a glyph's dot is + // quantized to, in the horizontal direction. For example, a value of 8 + // means that the dot is quantized to 1/8th of a pixel. This quantization + // only affects the glyph mask image, not its bounding box or advance + // width. A higher value gives a more faithful glyph image, but reduces the + // effectiveness of the glyph cache. + // + // If non-zero, it must be a power of 2, and be between 1 and 64 inclusive. + // + // A zero value means to use 4 sub-pixel locations. + SubPixelsX int + + // SubPixelsY is the number of sub-pixel locations a glyph's dot is + // quantized to, in the vertical direction. For example, a value of 8 + // means that the dot is quantized to 1/8th of a pixel. This quantization + // only affects the glyph mask image, not its bounding box or advance + // width. A higher value gives a more faithful glyph image, but reduces the + // effectiveness of the glyph cache. + // + // If non-zero, it must be a power of 2, and be between 1 and 64 inclusive. + // + // A zero value means to use 1 sub-pixel location. + SubPixelsY int +} + +func (o *Options) size() float64 { + if o != nil && o.Size > 0 { + return o.Size + } + return 12 +} + +func (o *Options) dpi() float64 { + if o != nil && o.DPI > 0 { + return o.DPI + } + return 72 +} + +func (o *Options) hinting() font.Hinting { + if o != nil { + switch o.Hinting { + case font.HintingVertical, font.HintingFull: + // TODO: support vertical hinting. + return font.HintingFull + } + } + return font.HintingNone +} + +func (o *Options) glyphCacheEntries() int { + if o != nil && powerOf2(o.GlyphCacheEntries) { + return o.GlyphCacheEntries + } + // 512 is 128 * 4 * 1, which lets us cache 128 glyphs at 4 * 1 subpixel + // locations in the X and Y direction. + return 512 +} + +func (o *Options) subPixelsX() (value uint32, halfQuantum, mask fixed.Int26_6) { + if o != nil { + switch o.SubPixelsX { + case 1, 2, 4, 8, 16, 32, 64: + return subPixels(o.SubPixelsX) + } + } + // This default value of 4 isn't based on anything scientific, merely as + // small a number as possible that looks almost as good as no quantization, + // or returning subPixels(64). + return subPixels(4) +} + +func (o *Options) subPixelsY() (value uint32, halfQuantum, mask fixed.Int26_6) { + if o != nil { + switch o.SubPixelsX { + case 1, 2, 4, 8, 16, 32, 64: + return subPixels(o.SubPixelsX) + } + } + // This default value of 1 isn't based on anything scientific, merely that + // vertical sub-pixel glyph rendering is pretty rare. Baseline locations + // can usually afford to snap to the pixel grid, so the vertical direction + // doesn't have the deal with the horizontal's fractional advance widths. + return subPixels(1) +} + +// subPixels returns q and the bias and mask that leads to q quantized +// sub-pixel locations per full pixel. +// +// For example, q == 4 leads to a bias of 8 and a mask of 0xfffffff0, or -16, +// because we want to round fractions of fixed.Int26_6 as: +// - 0 to 7 rounds to 0. +// - 8 to 23 rounds to 16. +// - 24 to 39 rounds to 32. +// - 40 to 55 rounds to 48. +// - 56 to 63 rounds to 64. +// which means to add 8 and then bitwise-and with -16, in two's complement +// representation. +// +// When q == 1, we want bias == 32 and mask == -64. +// When q == 2, we want bias == 16 and mask == -32. +// When q == 4, we want bias == 8 and mask == -16. +// ... +// When q == 64, we want bias == 0 and mask == -1. (The no-op case). +// The pattern is clear. +func subPixels(q int) (value uint32, bias, mask fixed.Int26_6) { + return uint32(q), 32 / fixed.Int26_6(q), -64 / fixed.Int26_6(q) +} + +// glyphCacheEntry caches the arguments and return values of rasterize. +type glyphCacheEntry struct { + key glyphCacheKey + val glyphCacheVal +} + +type glyphCacheKey struct { + index Index + fx, fy uint8 +} + +type glyphCacheVal struct { + advanceWidth fixed.Int26_6 + offset image.Point + gw int + gh int +} + +type indexCacheEntry struct { + rune rune + index Index +} + +// NewFace returns a new font.Face for the given Font. +func NewFace(f *Font, opts *Options) font.Face { + a := &face{ + f: f, + hinting: opts.hinting(), + scale: fixed.Int26_6(0.5 + (opts.size() * opts.dpi() * 64 / 72)), + glyphCache: make([]glyphCacheEntry, opts.glyphCacheEntries()), + } + a.subPixelX, a.subPixelBiasX, a.subPixelMaskX = opts.subPixelsX() + a.subPixelY, a.subPixelBiasY, a.subPixelMaskY = opts.subPixelsY() + + // Fill the cache with invalid entries. Valid glyph cache entries have fx + // and fy in the range [0, 64). Valid index cache entries have rune >= 0. + for i := range a.glyphCache { + a.glyphCache[i].key.fy = 0xff + } + for i := range a.indexCache { + a.indexCache[i].rune = -1 + } + + // Set the rasterizer's bounds to be big enough to handle the largest glyph. + b := f.Bounds(a.scale) + 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 + a.maxw = xmax - xmin + a.maxh = ymax - ymin + a.masks = image.NewAlpha(image.Rect(0, 0, a.maxw, a.maxh*len(a.glyphCache))) + a.r.SetBounds(a.maxw, a.maxh) + a.p = facePainter{a} + + return a +} + +type face struct { + f *Font + hinting font.Hinting + scale fixed.Int26_6 + subPixelX uint32 + subPixelBiasX fixed.Int26_6 + subPixelMaskX fixed.Int26_6 + subPixelY uint32 + subPixelBiasY fixed.Int26_6 + subPixelMaskY fixed.Int26_6 + masks *image.Alpha + glyphCache []glyphCacheEntry + r raster.Rasterizer + p raster.Painter + paintOffset int + maxw int + maxh int + glyphBuf GlyphBuf + indexCache [indexCacheLen]indexCacheEntry + + // TODO: clip rectangle? +} + +const indexCacheLen = 256 + +func (a *face) index(r rune) Index { + const mask = indexCacheLen - 1 + c := &a.indexCache[r&mask] + if c.rune == r { + return c.index + } + i := a.f.Index(r) + c.rune = r + c.index = i + return i +} + +// Close satisfies the font.Face interface. +func (a *face) Close() error { return nil } + +// Metrics satisfies the font.Face interface. +func (a *face) Metrics() font.Metrics { + scale := float64(a.scale) + fupe := float64(a.f.FUnitsPerEm()) + return font.Metrics{ + Height: a.scale, + Ascent: fixed.Int26_6(math.Ceil(scale * float64(+a.f.ascent) / fupe)), + Descent: fixed.Int26_6(math.Ceil(scale * float64(-a.f.descent) / fupe)), + } +} + +// Kern satisfies the font.Face interface. +func (a *face) Kern(r0, r1 rune) fixed.Int26_6 { + i0 := a.index(r0) + i1 := a.index(r1) + kern := a.f.Kern(a.scale, i0, i1) + if a.hinting != font.HintingNone { + kern = (kern + 32) &^ 63 + } + return kern +} + +// Glyph satisfies the font.Face interface. +func (a *face) Glyph(dot fixed.Point26_6, r rune) ( + dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) { + + // Quantize to the sub-pixel granularity. + dotX := (dot.X + a.subPixelBiasX) & a.subPixelMaskX + dotY := (dot.Y + a.subPixelBiasY) & a.subPixelMaskY + + // Split the coordinates into their integer and fractional parts. + ix, fx := int(dotX>>6), dotX&0x3f + iy, fy := int(dotY>>6), dotY&0x3f + + index := a.index(r) + cIndex := uint32(index) + cIndex = cIndex*a.subPixelX - uint32(fx/a.subPixelMaskX) + cIndex = cIndex*a.subPixelY - uint32(fy/a.subPixelMaskY) + cIndex &= uint32(len(a.glyphCache) - 1) + a.paintOffset = a.maxh * int(cIndex) + k := glyphCacheKey{ + index: index, + fx: uint8(fx), + fy: uint8(fy), + } + var v glyphCacheVal + if a.glyphCache[cIndex].key != k { + var ok bool + v, ok = a.rasterize(index, fx, fy) + if !ok { + return image.Rectangle{}, nil, image.Point{}, 0, false + } + a.glyphCache[cIndex] = glyphCacheEntry{k, v} + } else { + v = a.glyphCache[cIndex].val + } + + dr.Min = image.Point{ + X: ix + v.offset.X, + Y: iy + v.offset.Y, + } + dr.Max = image.Point{ + X: dr.Min.X + v.gw, + Y: dr.Min.Y + v.gh, + } + return dr, a.masks, image.Point{Y: a.paintOffset}, v.advanceWidth, true +} + +func (a *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) { + if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil { + return fixed.Rectangle26_6{}, 0, false + } + xmin := +a.glyphBuf.Bounds.Min.X + ymin := -a.glyphBuf.Bounds.Max.Y + xmax := +a.glyphBuf.Bounds.Max.X + ymax := -a.glyphBuf.Bounds.Min.Y + if xmin > xmax || ymin > ymax { + return fixed.Rectangle26_6{}, 0, false + } + return fixed.Rectangle26_6{ + Min: fixed.Point26_6{ + X: xmin, + Y: ymin, + }, + Max: fixed.Point26_6{ + X: xmax, + Y: ymax, + }, + }, a.glyphBuf.AdvanceWidth, true +} + +func (a *face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) { + if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil { + return 0, false + } + return a.glyphBuf.AdvanceWidth, true +} + +// rasterize returns the advance width, integer-pixel offset to render at, and +// the width and height of 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 (a *face) rasterize(index Index, fx, fy fixed.Int26_6) (v glyphCacheVal, ok bool) { + if err := a.glyphBuf.Load(a.f, a.scale, index, a.hinting); err != nil { + return glyphCacheVal{}, false + } + // Calculate the integer-pixel bounds for the glyph. + xmin := int(fx+a.glyphBuf.Bounds.Min.X) >> 6 + ymin := int(fy-a.glyphBuf.Bounds.Max.Y) >> 6 + xmax := int(fx+a.glyphBuf.Bounds.Max.X+0x3f) >> 6 + ymax := int(fy-a.glyphBuf.Bounds.Min.Y+0x3f) >> 6 + if xmin > xmax || ymin > ymax { + return glyphCacheVal{}, false + } + // 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. + a.r.Clear() + pixOffset := a.paintOffset * a.maxw + clear(a.masks.Pix[pixOffset : pixOffset+a.maxw*a.maxh]) + e0 := 0 + for _, e1 := range a.glyphBuf.Ends { + a.drawContour(a.glyphBuf.Points[e0:e1], fx, fy) + e0 = e1 + } + a.r.Rasterize(a.p) + return glyphCacheVal{ + a.glyphBuf.AdvanceWidth, + image.Point{xmin, ymin}, + xmax - xmin, + ymax - ymin, + }, true +} + +func clear(pix []byte) { + for i := range pix { + pix[i] = 0 + } +} + +// drawContour draws the given closed contour with the given offset. +func (a *face) drawContour(ps []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, + } + var others []Point + 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 + } + } + a.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 { + a.r.Add1(q) + } else { + a.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, + } + a.r.Add2(q0, mid) + } + } + q0, on0 = q, on + } + // Close the curve. + if on0 { + a.r.Add1(start) + } else { + a.r.Add2(q0, start) + } +} + +// facePainter is like a raster.AlphaSrcPainter, with an additional Y offset +// (face.paintOffset) to the painted spans. +type facePainter struct { + a *face +} + +func (p facePainter) Paint(ss []raster.Span, done bool) { + m := p.a.masks + b := m.Bounds() + b.Min.Y = p.a.paintOffset + b.Max.Y = p.a.paintOffset + p.a.maxh + for _, s := range ss { + s.Y += p.a.paintOffset + if s.Y < b.Min.Y { + continue + } + if s.Y >= b.Max.Y { + return + } + if s.X0 < b.Min.X { + s.X0 = b.Min.X + } + if s.X1 > b.Max.X { + s.X1 = b.Max.X + } + if s.X0 >= s.X1 { + continue + } + base := (s.Y-m.Rect.Min.Y)*m.Stride - m.Rect.Min.X + p := m.Pix[base+s.X0 : base+s.X1] + color := uint8(s.Alpha >> 8) + for i := range p { + p[i] = color + } + } +} diff --git a/vendor/github.com/golang/freetype/truetype/face_test.go b/vendor/github.com/golang/freetype/truetype/face_test.go new file mode 100644 index 0000000..856581d --- /dev/null +++ b/vendor/github.com/golang/freetype/truetype/face_test.go @@ -0,0 +1,48 @@ +// Copyright 2015 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. + +package truetype + +import ( + "image" + "image/draw" + "io/ioutil" + "strings" + "testing" + + "golang.org/x/image/font" + "golang.org/x/image/math/fixed" +) + +func BenchmarkDrawString(b *testing.B) { + data, err := ioutil.ReadFile("../licenses/gpl.txt") + if err != nil { + b.Fatal(err) + } + lines := strings.Split(string(data), "\n") + data, err = ioutil.ReadFile("../testdata/luxisr.ttf") + if err != nil { + b.Fatal(err) + } + f, err := Parse(data) + if err != nil { + b.Fatal(err) + } + dst := image.NewRGBA(image.Rect(0, 0, 800, 600)) + draw.Draw(dst, dst.Bounds(), image.White, image.ZP, draw.Src) + d := &font.Drawer{ + Dst: dst, + Src: image.Black, + Face: NewFace(f, nil), + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j, line := range lines { + d.Dot = fixed.P(0, (j*16)%600) + d.DrawString(line) + } + } +} diff --git a/vendor/github.com/golang/freetype/truetype/glyph.go b/vendor/github.com/golang/freetype/truetype/glyph.go new file mode 100644 index 0000000..6157ad8 --- /dev/null +++ b/vendor/github.com/golang/freetype/truetype/glyph.go @@ -0,0 +1,522 @@ +// 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. + +package truetype + +import ( + "golang.org/x/image/font" + "golang.org/x/image/math/fixed" +) + +// TODO: implement VerticalHinting. + +// A Point is a co-ordinate pair plus whether it is 'on' a contour or an 'off' +// control point. +type Point struct { + X, Y fixed.Int26_6 + // The Flags' LSB means whether or not this Point is 'on' the contour. + // Other bits are reserved for internal use. + Flags uint32 +} + +// A GlyphBuf holds a glyph's contours. A GlyphBuf can be re-used to load a +// series of glyphs from a Font. +type GlyphBuf struct { + // AdvanceWidth is the glyph's advance width. + AdvanceWidth fixed.Int26_6 + // Bounds is the glyph's bounding box. + Bounds fixed.Rectangle26_6 + // Points contains all Points from all contours of the glyph. If hinting + // was used to load a glyph then Unhinted contains those Points before they + // were hinted, and InFontUnits contains those Points before they were + // hinted and scaled. + Points, Unhinted, InFontUnits []Point + // Ends is the point indexes of the end point of each contour. The length + // of Ends is the number of contours in the glyph. The i'th contour + // consists of points Points[Ends[i-1]:Ends[i]], where Ends[-1] is + // interpreted to mean zero. + Ends []int + + font *Font + scale fixed.Int26_6 + hinting font.Hinting + hinter hinter + // phantomPoints are the co-ordinates of the synthetic phantom points + // used for hinting and bounding box calculations. + phantomPoints [4]Point + // pp1x is the X co-ordinate of the first phantom point. The '1' is + // using 1-based indexing; pp1x is almost always phantomPoints[0].X. + // TODO: eliminate this and consistently use phantomPoints[0].X. + pp1x fixed.Int26_6 + // metricsSet is whether the glyph's metrics have been set yet. For a + // compound glyph, a sub-glyph may override the outer glyph's metrics. + metricsSet bool + // tmp is a scratch buffer. + tmp []Point +} + +// Flags for decoding a glyph's contours. These flags are documented at +// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html. +const ( + flagOnCurve = 1 << iota + flagXShortVector + flagYShortVector + flagRepeat + flagPositiveXShortVector + flagPositiveYShortVector + + // The remaining flags are for internal use. + flagTouchedX + flagTouchedY +) + +// The same flag bits (0x10 and 0x20) are overloaded to have two meanings, +// dependent on the value of the flag{X,Y}ShortVector bits. +const ( + flagThisXIsSame = flagPositiveXShortVector + flagThisYIsSame = flagPositiveYShortVector +) + +// Load loads a glyph's contours from a Font, overwriting any previously loaded +// contours for this GlyphBuf. scale is the number of 26.6 fixed point units in +// 1 em, i is the glyph index, and h is the hinting policy. +func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h font.Hinting) error { + g.Points = g.Points[:0] + g.Unhinted = g.Unhinted[:0] + g.InFontUnits = g.InFontUnits[:0] + g.Ends = g.Ends[:0] + g.font = f + g.hinting = h + g.scale = scale + g.pp1x = 0 + g.phantomPoints = [4]Point{} + g.metricsSet = false + + if h != font.HintingNone { + if err := g.hinter.init(f, scale); err != nil { + return err + } + } + if err := g.load(0, i, true); err != nil { + return err + } + // TODO: this selection of either g.pp1x or g.phantomPoints[0].X isn't ideal, + // and should be cleaned up once we have all the testScaling tests passing, + // plus additional tests for Freetype-Go's bounding boxes matching C Freetype's. + pp1x := g.pp1x + if h != font.HintingNone { + pp1x = g.phantomPoints[0].X + } + if pp1x != 0 { + for i := range g.Points { + g.Points[i].X -= pp1x + } + } + + advanceWidth := g.phantomPoints[1].X - g.phantomPoints[0].X + if h != font.HintingNone { + if len(f.hdmx) >= 8 { + if n := u32(f.hdmx, 4); n > 3+uint32(i) { + for hdmx := f.hdmx[8:]; uint32(len(hdmx)) >= n; hdmx = hdmx[n:] { + if fixed.Int26_6(hdmx[0]) == scale>>6 { + advanceWidth = fixed.Int26_6(hdmx[2+i]) << 6 + break + } + } + } + } + advanceWidth = (advanceWidth + 32) &^ 63 + } + g.AdvanceWidth = advanceWidth + + // Set g.Bounds to the 'control box', which is the bounding box of the + // Bézier curves' control points. This is easier to calculate, no smaller + // than and often equal to the tightest possible bounding box of the curves + // themselves. This approach is what C Freetype does. We can't just scale + // the nominal bounding box in the glyf data as the hinting process and + // phantom point adjustment may move points outside of that box. + if len(g.Points) == 0 { + g.Bounds = fixed.Rectangle26_6{} + } else { + p := g.Points[0] + g.Bounds.Min.X = p.X + g.Bounds.Max.X = p.X + g.Bounds.Min.Y = p.Y + g.Bounds.Max.Y = p.Y + for _, p := range g.Points[1:] { + if g.Bounds.Min.X > p.X { + g.Bounds.Min.X = p.X + } else if g.Bounds.Max.X < p.X { + g.Bounds.Max.X = p.X + } + if g.Bounds.Min.Y > p.Y { + g.Bounds.Min.Y = p.Y + } else if g.Bounds.Max.Y < p.Y { + g.Bounds.Max.Y = p.Y + } + } + // Snap the box to the grid, if hinting is on. + if h != font.HintingNone { + g.Bounds.Min.X &^= 63 + g.Bounds.Min.Y &^= 63 + g.Bounds.Max.X += 63 + g.Bounds.Max.X &^= 63 + g.Bounds.Max.Y += 63 + g.Bounds.Max.Y &^= 63 + } + } + return nil +} + +func (g *GlyphBuf) load(recursion uint32, i Index, useMyMetrics bool) (err error) { + // The recursion limit here is arbitrary, but defends against malformed glyphs. + if recursion >= 32 { + return UnsupportedError("excessive compound glyph recursion") + } + // Find the relevant slice of g.font.glyf. + var g0, g1 uint32 + if g.font.locaOffsetFormat == locaOffsetFormatShort { + g0 = 2 * uint32(u16(g.font.loca, 2*int(i))) + g1 = 2 * uint32(u16(g.font.loca, 2*int(i)+2)) + } else { + g0 = u32(g.font.loca, 4*int(i)) + g1 = u32(g.font.loca, 4*int(i)+4) + } + + // Decode the contour count and nominal bounding box, from the first + // 10 bytes of the glyf data. boundsYMin and boundsXMax, at offsets 4 + // and 6, are unused. + glyf, ne, boundsXMin, boundsYMax := []byte(nil), 0, fixed.Int26_6(0), fixed.Int26_6(0) + if g0+10 <= g1 { + glyf = g.font.glyf[g0:g1] + ne = int(int16(u16(glyf, 0))) + boundsXMin = fixed.Int26_6(int16(u16(glyf, 2))) + boundsYMax = fixed.Int26_6(int16(u16(glyf, 8))) + } + + // Create the phantom points. + uhm, pp1x := g.font.unscaledHMetric(i), fixed.Int26_6(0) + uvm := g.font.unscaledVMetric(i, boundsYMax) + g.phantomPoints = [4]Point{ + {X: boundsXMin - uhm.LeftSideBearing}, + {X: boundsXMin - uhm.LeftSideBearing + uhm.AdvanceWidth}, + {X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing}, + {X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing - uvm.AdvanceHeight}, + } + if len(glyf) == 0 { + g.addPhantomsAndScale(len(g.Points), len(g.Points), true, true) + copy(g.phantomPoints[:], g.Points[len(g.Points)-4:]) + g.Points = g.Points[:len(g.Points)-4] + // TODO: also trim g.InFontUnits and g.Unhinted? + return nil + } + + // Load and hint the contours. + if ne < 0 { + if ne != -1 { + // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that + // "the values -2, -3, and so forth, are reserved for future use." + return UnsupportedError("negative number of contours") + } + pp1x = g.font.scale(g.scale * (boundsXMin - uhm.LeftSideBearing)) + if err := g.loadCompound(recursion, uhm, i, glyf, useMyMetrics); err != nil { + return err + } + } else { + np0, ne0 := len(g.Points), len(g.Ends) + program := g.loadSimple(glyf, ne) + g.addPhantomsAndScale(np0, np0, true, true) + pp1x = g.Points[len(g.Points)-4].X + if g.hinting != font.HintingNone { + if len(program) != 0 { + err := g.hinter.run( + program, + g.Points[np0:], + g.Unhinted[np0:], + g.InFontUnits[np0:], + g.Ends[ne0:], + ) + if err != nil { + return err + } + } + // Drop the four phantom points. + g.InFontUnits = g.InFontUnits[:len(g.InFontUnits)-4] + g.Unhinted = g.Unhinted[:len(g.Unhinted)-4] + } + if useMyMetrics { + copy(g.phantomPoints[:], g.Points[len(g.Points)-4:]) + } + g.Points = g.Points[:len(g.Points)-4] + if np0 != 0 { + // The hinting program expects the []Ends values to be indexed + // relative to the inner glyph, not the outer glyph, so we delay + // adding np0 until after the hinting program (if any) has run. + for i := ne0; i < len(g.Ends); i++ { + g.Ends[i] += np0 + } + } + } + if useMyMetrics && !g.metricsSet { + g.metricsSet = true + g.pp1x = pp1x + } + return nil +} + +// loadOffset is the initial offset for loadSimple and loadCompound. The first +// 10 bytes are the number of contours and the bounding box. +const loadOffset = 10 + +func (g *GlyphBuf) loadSimple(glyf []byte, ne int) (program []byte) { + offset := loadOffset + for i := 0; i < ne; i++ { + g.Ends = append(g.Ends, 1+int(u16(glyf, offset))) + offset += 2 + } + + // Note the TrueType hinting instructions. + instrLen := int(u16(glyf, offset)) + offset += 2 + program = glyf[offset : offset+instrLen] + offset += instrLen + + if ne == 0 { + return program + } + + np0 := len(g.Points) + np1 := np0 + int(g.Ends[len(g.Ends)-1]) + + // Decode the flags. + for i := np0; i < np1; { + c := uint32(glyf[offset]) + offset++ + g.Points = append(g.Points, Point{Flags: c}) + i++ + if c&flagRepeat != 0 { + count := glyf[offset] + offset++ + for ; count > 0; count-- { + g.Points = append(g.Points, Point{Flags: c}) + i++ + } + } + } + + // Decode the co-ordinates. + var x int16 + for i := np0; i < np1; i++ { + f := g.Points[i].Flags + if f&flagXShortVector != 0 { + dx := int16(glyf[offset]) + offset++ + if f&flagPositiveXShortVector == 0 { + x -= dx + } else { + x += dx + } + } else if f&flagThisXIsSame == 0 { + x += int16(u16(glyf, offset)) + offset += 2 + } + g.Points[i].X = fixed.Int26_6(x) + } + var y int16 + for i := np0; i < np1; i++ { + f := g.Points[i].Flags + if f&flagYShortVector != 0 { + dy := int16(glyf[offset]) + offset++ + if f&flagPositiveYShortVector == 0 { + y -= dy + } else { + y += dy + } + } else if f&flagThisYIsSame == 0 { + y += int16(u16(glyf, offset)) + offset += 2 + } + g.Points[i].Y = fixed.Int26_6(y) + } + + return program +} + +func (g *GlyphBuf) loadCompound(recursion uint32, uhm HMetric, i Index, + glyf []byte, useMyMetrics bool) error { + + // Flags for decoding a compound glyph. These flags are documented at + // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html. + const ( + flagArg1And2AreWords = 1 << iota + flagArgsAreXYValues + flagRoundXYToGrid + flagWeHaveAScale + flagUnused + flagMoreComponents + flagWeHaveAnXAndYScale + flagWeHaveATwoByTwo + flagWeHaveInstructions + flagUseMyMetrics + flagOverlapCompound + ) + np0, ne0 := len(g.Points), len(g.Ends) + offset := loadOffset + for { + flags := u16(glyf, offset) + component := Index(u16(glyf, offset+2)) + dx, dy, transform, hasTransform := fixed.Int26_6(0), fixed.Int26_6(0), [4]int16{}, false + if flags&flagArg1And2AreWords != 0 { + dx = fixed.Int26_6(int16(u16(glyf, offset+4))) + dy = fixed.Int26_6(int16(u16(glyf, offset+6))) + offset += 8 + } else { + dx = fixed.Int26_6(int16(int8(glyf[offset+4]))) + dy = fixed.Int26_6(int16(int8(glyf[offset+5]))) + offset += 6 + } + if flags&flagArgsAreXYValues == 0 { + return UnsupportedError("compound glyph transform vector") + } + if flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0 { + hasTransform = true + switch { + case flags&flagWeHaveAScale != 0: + transform[0] = int16(u16(glyf, offset+0)) + transform[3] = transform[0] + offset += 2 + case flags&flagWeHaveAnXAndYScale != 0: + transform[0] = int16(u16(glyf, offset+0)) + transform[3] = int16(u16(glyf, offset+2)) + offset += 4 + case flags&flagWeHaveATwoByTwo != 0: + transform[0] = int16(u16(glyf, offset+0)) + transform[1] = int16(u16(glyf, offset+2)) + transform[2] = int16(u16(glyf, offset+4)) + transform[3] = int16(u16(glyf, offset+6)) + offset += 8 + } + } + savedPP := g.phantomPoints + np0 := len(g.Points) + componentUMM := useMyMetrics && (flags&flagUseMyMetrics != 0) + if err := g.load(recursion+1, component, componentUMM); err != nil { + return err + } + if flags&flagUseMyMetrics == 0 { + g.phantomPoints = savedPP + } + if hasTransform { + for j := np0; j < len(g.Points); j++ { + p := &g.Points[j] + newX := 0 + + fixed.Int26_6((int64(p.X)*int64(transform[0])+1<<13)>>14) + + fixed.Int26_6((int64(p.Y)*int64(transform[2])+1<<13)>>14) + newY := 0 + + fixed.Int26_6((int64(p.X)*int64(transform[1])+1<<13)>>14) + + fixed.Int26_6((int64(p.Y)*int64(transform[3])+1<<13)>>14) + p.X, p.Y = newX, newY + } + } + dx = g.font.scale(g.scale * dx) + dy = g.font.scale(g.scale * dy) + if flags&flagRoundXYToGrid != 0 { + dx = (dx + 32) &^ 63 + dy = (dy + 32) &^ 63 + } + for j := np0; j < len(g.Points); j++ { + p := &g.Points[j] + p.X += dx + p.Y += dy + } + // TODO: also adjust g.InFontUnits and g.Unhinted? + if flags&flagMoreComponents == 0 { + break + } + } + + instrLen := 0 + if g.hinting != font.HintingNone && offset+2 <= len(glyf) { + instrLen = int(u16(glyf, offset)) + offset += 2 + } + + g.addPhantomsAndScale(np0, len(g.Points), false, instrLen > 0) + points, ends := g.Points[np0:], g.Ends[ne0:] + g.Points = g.Points[:len(g.Points)-4] + for j := range points { + points[j].Flags &^= flagTouchedX | flagTouchedY + } + + if instrLen == 0 { + if !g.metricsSet { + copy(g.phantomPoints[:], points[len(points)-4:]) + } + return nil + } + + // Hint the compound glyph. + program := glyf[offset : offset+instrLen] + // Temporarily adjust the ends to be relative to this compound glyph. + if np0 != 0 { + for i := range ends { + ends[i] -= np0 + } + } + // Hinting instructions of a composite glyph completely refer to the + // (already) hinted subglyphs. + g.tmp = append(g.tmp[:0], points...) + if err := g.hinter.run(program, points, g.tmp, g.tmp, ends); err != nil { + return err + } + if np0 != 0 { + for i := range ends { + ends[i] += np0 + } + } + if !g.metricsSet { + copy(g.phantomPoints[:], points[len(points)-4:]) + } + return nil +} + +func (g *GlyphBuf) addPhantomsAndScale(np0, np1 int, simple, adjust bool) { + // Add the four phantom points. + g.Points = append(g.Points, g.phantomPoints[:]...) + // Scale the points. + if simple && g.hinting != font.HintingNone { + g.InFontUnits = append(g.InFontUnits, g.Points[np1:]...) + } + for i := np1; i < len(g.Points); i++ { + p := &g.Points[i] + p.X = g.font.scale(g.scale * p.X) + p.Y = g.font.scale(g.scale * p.Y) + } + if g.hinting == font.HintingNone { + return + } + // Round the 1st phantom point to the grid, shifting all other points equally. + // Note that "all other points" starts from np0, not np1. + // TODO: delete this adjustment and the np0/np1 distinction, when + // we update the compatibility tests to C Freetype 2.5.3. + // See http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=05c786d990390a7ca18e62962641dac740bacb06 + if adjust { + pp1x := g.Points[len(g.Points)-4].X + if dx := ((pp1x + 32) &^ 63) - pp1x; dx != 0 { + for i := np0; i < len(g.Points); i++ { + g.Points[i].X += dx + } + } + } + if simple { + g.Unhinted = append(g.Unhinted, g.Points[np1:]...) + } + // Round the 2nd and 4th phantom point to the grid. + p := &g.Points[len(g.Points)-3] + p.X = (p.X + 32) &^ 63 + p = &g.Points[len(g.Points)-1] + p.Y = (p.Y + 32) &^ 63 +} diff --git a/vendor/github.com/golang/freetype/truetype/hint.go b/vendor/github.com/golang/freetype/truetype/hint.go new file mode 100644 index 0000000..13f785b --- /dev/null +++ b/vendor/github.com/golang/freetype/truetype/hint.go @@ -0,0 +1,1770 @@ +// Copyright 2012 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. + +package truetype + +// This file implements a Truetype bytecode interpreter. +// The opcodes are described at https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html + +import ( + "errors" + "math" + + "golang.org/x/image/math/fixed" +) + +const ( + twilightZone = 0 + glyphZone = 1 + numZone = 2 +) + +type pointType uint32 + +const ( + current pointType = 0 + unhinted pointType = 1 + inFontUnits pointType = 2 + numPointType = 3 +) + +// callStackEntry is a bytecode call stack entry. +type callStackEntry struct { + program []byte + pc int + loopCount int32 +} + +// hinter implements bytecode hinting. A hinter can be re-used to hint a series +// of glyphs from a Font. +type hinter struct { + stack, store []int32 + + // functions is a map from function number to bytecode. + functions map[int32][]byte + + // font and scale are the font and scale last used for this hinter. + // Changing the font will require running the new font's fpgm bytecode. + // Changing either will require running the font's prep bytecode. + font *Font + scale fixed.Int26_6 + + // gs and defaultGS are the current and default graphics state. The + // default graphics state is the global default graphics state after + // the font's fpgm and prep programs have been run. + gs, defaultGS graphicsState + + // points and ends are the twilight zone's points, glyph's points + // and glyph's contour boundaries. + points [numZone][numPointType][]Point + ends []int + + // scaledCVT is the lazily initialized scaled Control Value Table. + scaledCVTInitialized bool + scaledCVT []fixed.Int26_6 +} + +// graphicsState is described at https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html +type graphicsState struct { + // Projection vector, freedom vector and dual projection vector. + pv, fv, dv [2]f2dot14 + // Reference points and zone pointers. + rp, zp [3]int32 + // Control Value / Single Width Cut-In. + controlValueCutIn, singleWidthCutIn, singleWidth fixed.Int26_6 + // Delta base / shift. + deltaBase, deltaShift int32 + // Minimum distance. + minDist fixed.Int26_6 + // Loop count. + loop int32 + // Rounding policy. + roundPeriod, roundPhase, roundThreshold fixed.Int26_6 + roundSuper45 bool + // Auto-flip. + autoFlip bool +} + +var globalDefaultGS = graphicsState{ + pv: [2]f2dot14{0x4000, 0}, // Unit vector along the X axis. + fv: [2]f2dot14{0x4000, 0}, + dv: [2]f2dot14{0x4000, 0}, + zp: [3]int32{1, 1, 1}, + controlValueCutIn: (17 << 6) / 16, // 17/16 as a fixed.Int26_6. + deltaBase: 9, + deltaShift: 3, + minDist: 1 << 6, // 1 as a fixed.Int26_6. + loop: 1, + roundPeriod: 1 << 6, // 1 as a fixed.Int26_6. + roundThreshold: 1 << 5, // 1/2 as a fixed.Int26_6. + roundSuper45: false, + autoFlip: true, +} + +func resetTwilightPoints(f *Font, p []Point) []Point { + if n := int(f.maxTwilightPoints) + 4; n <= cap(p) { + p = p[:n] + for i := range p { + p[i] = Point{} + } + } else { + p = make([]Point, n) + } + return p +} + +func (h *hinter) init(f *Font, scale fixed.Int26_6) error { + h.points[twilightZone][0] = resetTwilightPoints(f, h.points[twilightZone][0]) + h.points[twilightZone][1] = resetTwilightPoints(f, h.points[twilightZone][1]) + h.points[twilightZone][2] = resetTwilightPoints(f, h.points[twilightZone][2]) + + rescale := h.scale != scale + if h.font != f { + h.font, rescale = f, true + if h.functions == nil { + h.functions = make(map[int32][]byte) + } else { + for k := range h.functions { + delete(h.functions, k) + } + } + + if x := int(f.maxStackElements); x > len(h.stack) { + x += 255 + x &^= 255 + h.stack = make([]int32, x) + } + if x := int(f.maxStorage); x > len(h.store) { + x += 15 + x &^= 15 + h.store = make([]int32, x) + } + if len(f.fpgm) != 0 { + if err := h.run(f.fpgm, nil, nil, nil, nil); err != nil { + return err + } + } + } + + if rescale { + h.scale = scale + h.scaledCVTInitialized = false + + h.defaultGS = globalDefaultGS + + if len(f.prep) != 0 { + if err := h.run(f.prep, nil, nil, nil, nil); err != nil { + return err + } + h.defaultGS = h.gs + // The MS rasterizer doesn't allow the following graphics state + // variables to be modified by the CVT program. + h.defaultGS.pv = globalDefaultGS.pv + h.defaultGS.fv = globalDefaultGS.fv + h.defaultGS.dv = globalDefaultGS.dv + h.defaultGS.rp = globalDefaultGS.rp + h.defaultGS.zp = globalDefaultGS.zp + h.defaultGS.loop = globalDefaultGS.loop + } + } + return nil +} + +func (h *hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point, ends []int) error { + h.gs = h.defaultGS + h.points[glyphZone][current] = pCurrent + h.points[glyphZone][unhinted] = pUnhinted + h.points[glyphZone][inFontUnits] = pInFontUnits + h.ends = ends + + if len(program) > 50000 { + return errors.New("truetype: hinting: too many instructions") + } + var ( + steps, pc, top int + opcode uint8 + + callStack [32]callStackEntry + callStackTop int + ) + + for 0 <= pc && pc < len(program) { + steps++ + if steps == 100000 { + return errors.New("truetype: hinting: too many steps") + } + opcode = program[pc] + if top < int(popCount[opcode]) { + return errors.New("truetype: hinting: stack underflow") + } + switch opcode { + + case opSVTCA0: + h.gs.pv = [2]f2dot14{0, 0x4000} + h.gs.fv = [2]f2dot14{0, 0x4000} + h.gs.dv = [2]f2dot14{0, 0x4000} + + case opSVTCA1: + h.gs.pv = [2]f2dot14{0x4000, 0} + h.gs.fv = [2]f2dot14{0x4000, 0} + h.gs.dv = [2]f2dot14{0x4000, 0} + + case opSPVTCA0: + h.gs.pv = [2]f2dot14{0, 0x4000} + h.gs.dv = [2]f2dot14{0, 0x4000} + + case opSPVTCA1: + h.gs.pv = [2]f2dot14{0x4000, 0} + h.gs.dv = [2]f2dot14{0x4000, 0} + + case opSFVTCA0: + h.gs.fv = [2]f2dot14{0, 0x4000} + + case opSFVTCA1: + h.gs.fv = [2]f2dot14{0x4000, 0} + + case opSPVTL0, opSPVTL1, opSFVTL0, opSFVTL1: + top -= 2 + p1 := h.point(0, current, h.stack[top+0]) + p2 := h.point(0, current, h.stack[top+1]) + if p1 == nil || p2 == nil { + return errors.New("truetype: hinting: point out of range") + } + dx := f2dot14(p1.X - p2.X) + dy := f2dot14(p1.Y - p2.Y) + if dx == 0 && dy == 0 { + dx = 0x4000 + } else if opcode&1 != 0 { + // Counter-clockwise rotation. + dx, dy = -dy, dx + } + v := normalize(dx, dy) + if opcode < opSFVTL0 { + h.gs.pv = v + h.gs.dv = v + } else { + h.gs.fv = v + } + + case opSPVFS: + top -= 2 + h.gs.pv = normalize(f2dot14(h.stack[top]), f2dot14(h.stack[top+1])) + h.gs.dv = h.gs.pv + + case opSFVFS: + top -= 2 + h.gs.fv = normalize(f2dot14(h.stack[top]), f2dot14(h.stack[top+1])) + + case opGPV: + if top+1 >= len(h.stack) { + return errors.New("truetype: hinting: stack overflow") + } + h.stack[top+0] = int32(h.gs.pv[0]) + h.stack[top+1] = int32(h.gs.pv[1]) + top += 2 + + case opGFV: + if top+1 >= len(h.stack) { + return errors.New("truetype: hinting: stack overflow") + } + h.stack[top+0] = int32(h.gs.fv[0]) + h.stack[top+1] = int32(h.gs.fv[1]) + top += 2 + + case opSFVTPV: + h.gs.fv = h.gs.pv + + case opISECT: + top -= 5 + p := h.point(2, current, h.stack[top+0]) + a0 := h.point(1, current, h.stack[top+1]) + a1 := h.point(1, current, h.stack[top+2]) + b0 := h.point(0, current, h.stack[top+3]) + b1 := h.point(0, current, h.stack[top+4]) + if p == nil || a0 == nil || a1 == nil || b0 == nil || b1 == nil { + return errors.New("truetype: hinting: point out of range") + } + + dbx := b1.X - b0.X + dby := b1.Y - b0.Y + dax := a1.X - a0.X + day := a1.Y - a0.Y + dx := b0.X - a0.X + dy := b0.Y - a0.Y + discriminant := mulDiv(int64(dax), int64(-dby), 0x40) + + mulDiv(int64(day), int64(dbx), 0x40) + dotProduct := mulDiv(int64(dax), int64(dbx), 0x40) + + mulDiv(int64(day), int64(dby), 0x40) + // The discriminant above is actually a cross product of vectors + // da and db. Together with the dot product, they can be used as + // surrogates for sine and cosine of the angle between the vectors. + // Indeed, + // dotproduct = |da||db|cos(angle) + // discriminant = |da||db|sin(angle) + // We use these equations to reject grazing intersections by + // thresholding abs(tan(angle)) at 1/19, corresponding to 3 degrees. + absDisc, absDotP := discriminant, dotProduct + if absDisc < 0 { + absDisc = -absDisc + } + if absDotP < 0 { + absDotP = -absDotP + } + if 19*absDisc > absDotP { + val := mulDiv(int64(dx), int64(-dby), 0x40) + + mulDiv(int64(dy), int64(dbx), 0x40) + rx := mulDiv(val, int64(dax), discriminant) + ry := mulDiv(val, int64(day), discriminant) + p.X = a0.X + fixed.Int26_6(rx) + p.Y = a0.Y + fixed.Int26_6(ry) + } else { + p.X = (a0.X + a1.X + b0.X + b1.X) / 4 + p.Y = (a0.Y + a1.Y + b0.Y + b1.Y) / 4 + } + p.Flags |= flagTouchedX | flagTouchedY + + case opSRP0, opSRP1, opSRP2: + top-- + h.gs.rp[opcode-opSRP0] = h.stack[top] + + case opSZP0, opSZP1, opSZP2: + top-- + h.gs.zp[opcode-opSZP0] = h.stack[top] + + case opSZPS: + top-- + h.gs.zp[0] = h.stack[top] + h.gs.zp[1] = h.stack[top] + h.gs.zp[2] = h.stack[top] + + case opSLOOP: + top-- + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html#SLOOP + // says that "Setting the loop variable to zero is an error". In + // theory, the inequality on the next line should be "<=" instead + // of "<". In practice, some font files' bytecode, such as the '2' + // glyph in the DejaVuSansMono.ttf that comes with Ubuntu 14.04, + // issue SLOOP with a zero on top of the stack. Just like the C + // Freetype code, we allow the zero. + if h.stack[top] < 0 { + return errors.New("truetype: hinting: invalid data") + } + h.gs.loop = h.stack[top] + + case opRTG: + h.gs.roundPeriod = 1 << 6 + h.gs.roundPhase = 0 + h.gs.roundThreshold = 1 << 5 + h.gs.roundSuper45 = false + + case opRTHG: + h.gs.roundPeriod = 1 << 6 + h.gs.roundPhase = 1 << 5 + h.gs.roundThreshold = 1 << 5 + h.gs.roundSuper45 = false + + case opSMD: + top-- + h.gs.minDist = fixed.Int26_6(h.stack[top]) + + case opELSE: + opcode = 1 + goto ifelse + + case opJMPR: + top-- + pc += int(h.stack[top]) + continue + + case opSCVTCI: + top-- + h.gs.controlValueCutIn = fixed.Int26_6(h.stack[top]) + + case opSSWCI: + top-- + h.gs.singleWidthCutIn = fixed.Int26_6(h.stack[top]) + + case opSSW: + top-- + h.gs.singleWidth = h.font.scale(h.scale * fixed.Int26_6(h.stack[top])) + + case opDUP: + if top >= len(h.stack) { + return errors.New("truetype: hinting: stack overflow") + } + h.stack[top] = h.stack[top-1] + top++ + + case opPOP: + top-- + + case opCLEAR: + top = 0 + + case opSWAP: + h.stack[top-1], h.stack[top-2] = h.stack[top-2], h.stack[top-1] + + case opDEPTH: + if top >= len(h.stack) { + return errors.New("truetype: hinting: stack overflow") + } + h.stack[top] = int32(top) + top++ + + case opCINDEX, opMINDEX: + x := int(h.stack[top-1]) + if x <= 0 || x >= top { + return errors.New("truetype: hinting: invalid data") + } + h.stack[top-1] = h.stack[top-1-x] + if opcode == opMINDEX { + copy(h.stack[top-1-x:top-1], h.stack[top-x:top]) + top-- + } + + case opALIGNPTS: + top -= 2 + p := h.point(1, current, h.stack[top]) + q := h.point(0, current, h.stack[top+1]) + if p == nil || q == nil { + return errors.New("truetype: hinting: point out of range") + } + d := dotProduct(fixed.Int26_6(q.X-p.X), fixed.Int26_6(q.Y-p.Y), h.gs.pv) / 2 + h.move(p, +d, true) + h.move(q, -d, true) + + case opUTP: + top-- + p := h.point(0, current, h.stack[top]) + if p == nil { + return errors.New("truetype: hinting: point out of range") + } + p.Flags &^= flagTouchedX | flagTouchedY + + case opLOOPCALL, opCALL: + if callStackTop >= len(callStack) { + return errors.New("truetype: hinting: call stack overflow") + } + top-- + f, ok := h.functions[h.stack[top]] + if !ok { + return errors.New("truetype: hinting: undefined function") + } + callStack[callStackTop] = callStackEntry{program, pc, 1} + if opcode == opLOOPCALL { + top-- + if h.stack[top] == 0 { + break + } + callStack[callStackTop].loopCount = h.stack[top] + } + callStackTop++ + program, pc = f, 0 + continue + + case opFDEF: + // Save all bytecode up until the next ENDF. + startPC := pc + 1 + fdefloop: + for { + pc++ + if pc >= len(program) { + return errors.New("truetype: hinting: unbalanced FDEF") + } + switch program[pc] { + case opFDEF: + return errors.New("truetype: hinting: nested FDEF") + case opENDF: + top-- + h.functions[h.stack[top]] = program[startPC : pc+1] + break fdefloop + default: + var ok bool + pc, ok = skipInstructionPayload(program, pc) + if !ok { + return errors.New("truetype: hinting: unbalanced FDEF") + } + } + } + + case opENDF: + if callStackTop == 0 { + return errors.New("truetype: hinting: call stack underflow") + } + callStackTop-- + callStack[callStackTop].loopCount-- + if callStack[callStackTop].loopCount != 0 { + callStackTop++ + pc = 0 + continue + } + program, pc = callStack[callStackTop].program, callStack[callStackTop].pc + + case opMDAP0, opMDAP1: + top-- + i := h.stack[top] + p := h.point(0, current, i) + if p == nil { + return errors.New("truetype: hinting: point out of range") + } + distance := fixed.Int26_6(0) + if opcode == opMDAP1 { + distance = dotProduct(p.X, p.Y, h.gs.pv) + // TODO: metrics compensation. + distance = h.round(distance) - distance + } + h.move(p, distance, true) + h.gs.rp[0] = i + h.gs.rp[1] = i + + case opIUP0, opIUP1: + iupY, mask := opcode == opIUP0, uint32(flagTouchedX) + if iupY { + mask = flagTouchedY + } + prevEnd := 0 + for _, end := range h.ends { + for i := prevEnd; i < end; i++ { + for i < end && h.points[glyphZone][current][i].Flags&mask == 0 { + i++ + } + if i == end { + break + } + firstTouched, curTouched := i, i + i++ + for ; i < end; i++ { + if h.points[glyphZone][current][i].Flags&mask != 0 { + h.iupInterp(iupY, curTouched+1, i-1, curTouched, i) + curTouched = i + } + } + if curTouched == firstTouched { + h.iupShift(iupY, prevEnd, end, curTouched) + } else { + h.iupInterp(iupY, curTouched+1, end-1, curTouched, firstTouched) + if firstTouched > 0 { + h.iupInterp(iupY, prevEnd, firstTouched-1, curTouched, firstTouched) + } + } + } + prevEnd = end + } + + case opSHP0, opSHP1: + if top < int(h.gs.loop) { + return errors.New("truetype: hinting: stack underflow") + } + _, _, d, ok := h.displacement(opcode&1 == 0) + if !ok { + return errors.New("truetype: hinting: point out of range") + } + for ; h.gs.loop != 0; h.gs.loop-- { + top-- + p := h.point(2, current, h.stack[top]) + if p == nil { + return errors.New("truetype: hinting: point out of range") + } + h.move(p, d, true) + } + h.gs.loop = 1 + + case opSHC0, opSHC1: + top-- + zonePointer, i, d, ok := h.displacement(opcode&1 == 0) + if !ok { + return errors.New("truetype: hinting: point out of range") + } + if h.gs.zp[2] == 0 { + // TODO: implement this when we have a glyph that does this. + return errors.New("hinting: unimplemented SHC instruction") + } + contour := h.stack[top] + if contour < 0 || len(ends) <= int(contour) { + return errors.New("truetype: hinting: contour out of range") + } + j0, j1 := int32(0), int32(h.ends[contour]) + if contour > 0 { + j0 = int32(h.ends[contour-1]) + } + move := h.gs.zp[zonePointer] != h.gs.zp[2] + for j := j0; j < j1; j++ { + if move || j != i { + h.move(h.point(2, current, j), d, true) + } + } + + case opSHZ0, opSHZ1: + top-- + zonePointer, i, d, ok := h.displacement(opcode&1 == 0) + if !ok { + return errors.New("truetype: hinting: point out of range") + } + + // As per C Freetype, SHZ doesn't move the phantom points, or mark + // the points as touched. + limit := int32(len(h.points[h.gs.zp[2]][current])) + if h.gs.zp[2] == glyphZone { + limit -= 4 + } + for j := int32(0); j < limit; j++ { + if i != j || h.gs.zp[zonePointer] != h.gs.zp[2] { + h.move(h.point(2, current, j), d, false) + } + } + + case opSHPIX: + top-- + d := fixed.Int26_6(h.stack[top]) + if top < int(h.gs.loop) { + return errors.New("truetype: hinting: stack underflow") + } + for ; h.gs.loop != 0; h.gs.loop-- { + top-- + p := h.point(2, current, h.stack[top]) + if p == nil { + return errors.New("truetype: hinting: point out of range") + } + h.move(p, d, true) + } + h.gs.loop = 1 + + case opIP: + if top < int(h.gs.loop) { + return errors.New("truetype: hinting: stack underflow") + } + pointType := inFontUnits + twilight := h.gs.zp[0] == 0 || h.gs.zp[1] == 0 || h.gs.zp[2] == 0 + if twilight { + pointType = unhinted + } + p := h.point(1, pointType, h.gs.rp[2]) + oldP := h.point(0, pointType, h.gs.rp[1]) + oldRange := dotProduct(p.X-oldP.X, p.Y-oldP.Y, h.gs.dv) + + p = h.point(1, current, h.gs.rp[2]) + curP := h.point(0, current, h.gs.rp[1]) + curRange := dotProduct(p.X-curP.X, p.Y-curP.Y, h.gs.pv) + for ; h.gs.loop != 0; h.gs.loop-- { + top-- + i := h.stack[top] + p = h.point(2, pointType, i) + oldDist := dotProduct(p.X-oldP.X, p.Y-oldP.Y, h.gs.dv) + p = h.point(2, current, i) + curDist := dotProduct(p.X-curP.X, p.Y-curP.Y, h.gs.pv) + newDist := fixed.Int26_6(0) + if oldDist != 0 { + if oldRange != 0 { + newDist = fixed.Int26_6(mulDiv(int64(oldDist), int64(curRange), int64(oldRange))) + } else { + newDist = -oldDist + } + } + h.move(p, newDist-curDist, true) + } + h.gs.loop = 1 + + case opMSIRP0, opMSIRP1: + top -= 2 + i := h.stack[top] + distance := fixed.Int26_6(h.stack[top+1]) + + // TODO: special case h.gs.zp[1] == 0 in C Freetype. + ref := h.point(0, current, h.gs.rp[0]) + p := h.point(1, current, i) + if ref == nil || p == nil { + return errors.New("truetype: hinting: point out of range") + } + curDist := dotProduct(p.X-ref.X, p.Y-ref.Y, h.gs.pv) + + // Set-RP0 bit. + if opcode == opMSIRP1 { + h.gs.rp[0] = i + } + h.gs.rp[1] = h.gs.rp[0] + h.gs.rp[2] = i + + // Move the point. + h.move(p, distance-curDist, true) + + case opALIGNRP: + if top < int(h.gs.loop) { + return errors.New("truetype: hinting: stack underflow") + } + ref := h.point(0, current, h.gs.rp[0]) + if ref == nil { + return errors.New("truetype: hinting: point out of range") + } + for ; h.gs.loop != 0; h.gs.loop-- { + top-- + p := h.point(1, current, h.stack[top]) + if p == nil { + return errors.New("truetype: hinting: point out of range") + } + h.move(p, -dotProduct(p.X-ref.X, p.Y-ref.Y, h.gs.pv), true) + } + h.gs.loop = 1 + + case opRTDG: + h.gs.roundPeriod = 1 << 5 + h.gs.roundPhase = 0 + h.gs.roundThreshold = 1 << 4 + h.gs.roundSuper45 = false + + case opMIAP0, opMIAP1: + top -= 2 + i := h.stack[top] + distance := h.getScaledCVT(h.stack[top+1]) + if h.gs.zp[0] == 0 { + p := h.point(0, unhinted, i) + q := h.point(0, current, i) + p.X = fixed.Int26_6((int64(distance) * int64(h.gs.fv[0])) >> 14) + p.Y = fixed.Int26_6((int64(distance) * int64(h.gs.fv[1])) >> 14) + *q = *p + } + p := h.point(0, current, i) + oldDist := dotProduct(p.X, p.Y, h.gs.pv) + if opcode == opMIAP1 { + if fabs(distance-oldDist) > h.gs.controlValueCutIn { + distance = oldDist + } + // TODO: metrics compensation. + distance = h.round(distance) + } + h.move(p, distance-oldDist, true) + h.gs.rp[0] = i + h.gs.rp[1] = i + + case opNPUSHB: + opcode = 0 + goto push + + case opNPUSHW: + opcode = 0x80 + goto push + + case opWS: + top -= 2 + i := int(h.stack[top]) + if i < 0 || len(h.store) <= i { + return errors.New("truetype: hinting: invalid data") + } + h.store[i] = h.stack[top+1] + + case opRS: + i := int(h.stack[top-1]) + if i < 0 || len(h.store) <= i { + return errors.New("truetype: hinting: invalid data") + } + h.stack[top-1] = h.store[i] + + case opWCVTP: + top -= 2 + h.setScaledCVT(h.stack[top], fixed.Int26_6(h.stack[top+1])) + + case opRCVT: + h.stack[top-1] = int32(h.getScaledCVT(h.stack[top-1])) + + case opGC0, opGC1: + i := h.stack[top-1] + if opcode == opGC0 { + p := h.point(2, current, i) + h.stack[top-1] = int32(dotProduct(p.X, p.Y, h.gs.pv)) + } else { + p := h.point(2, unhinted, i) + // Using dv as per C Freetype. + h.stack[top-1] = int32(dotProduct(p.X, p.Y, h.gs.dv)) + } + + case opSCFS: + top -= 2 + i := h.stack[top] + p := h.point(2, current, i) + if p == nil { + return errors.New("truetype: hinting: point out of range") + } + c := dotProduct(p.X, p.Y, h.gs.pv) + h.move(p, fixed.Int26_6(h.stack[top+1])-c, true) + if h.gs.zp[2] != 0 { + break + } + q := h.point(2, unhinted, i) + if q == nil { + return errors.New("truetype: hinting: point out of range") + } + q.X = p.X + q.Y = p.Y + + case opMD0, opMD1: + top-- + pt, v, scale := pointType(0), [2]f2dot14{}, false + if opcode == opMD0 { + pt = current + v = h.gs.pv + } else if h.gs.zp[0] == 0 || h.gs.zp[1] == 0 { + pt = unhinted + v = h.gs.dv + } else { + pt = inFontUnits + v = h.gs.dv + scale = true + } + p := h.point(0, pt, h.stack[top-1]) + q := h.point(1, pt, h.stack[top]) + if p == nil || q == nil { + return errors.New("truetype: hinting: point out of range") + } + d := int32(dotProduct(p.X-q.X, p.Y-q.Y, v)) + if scale { + d = int32(int64(d*int32(h.scale)) / int64(h.font.fUnitsPerEm)) + } + h.stack[top-1] = d + + case opMPPEM, opMPS: + if top >= len(h.stack) { + return errors.New("truetype: hinting: stack overflow") + } + // For MPS, point size should be irrelevant; we return the PPEM. + h.stack[top] = int32(h.scale) >> 6 + top++ + + case opFLIPON, opFLIPOFF: + h.gs.autoFlip = opcode == opFLIPON + + case opDEBUG: + // No-op. + + case opLT: + top-- + h.stack[top-1] = bool2int32(h.stack[top-1] < h.stack[top]) + + case opLTEQ: + top-- + h.stack[top-1] = bool2int32(h.stack[top-1] <= h.stack[top]) + + case opGT: + top-- + h.stack[top-1] = bool2int32(h.stack[top-1] > h.stack[top]) + + case opGTEQ: + top-- + h.stack[top-1] = bool2int32(h.stack[top-1] >= h.stack[top]) + + case opEQ: + top-- + h.stack[top-1] = bool2int32(h.stack[top-1] == h.stack[top]) + + case opNEQ: + top-- + h.stack[top-1] = bool2int32(h.stack[top-1] != h.stack[top]) + + case opODD, opEVEN: + i := h.round(fixed.Int26_6(h.stack[top-1])) >> 6 + h.stack[top-1] = int32(i&1) ^ int32(opcode-opODD) + + case opIF: + top-- + if h.stack[top] == 0 { + opcode = 0 + goto ifelse + } + + case opEIF: + // No-op. + + case opAND: + top-- + h.stack[top-1] = bool2int32(h.stack[top-1] != 0 && h.stack[top] != 0) + + case opOR: + top-- + h.stack[top-1] = bool2int32(h.stack[top-1]|h.stack[top] != 0) + + case opNOT: + h.stack[top-1] = bool2int32(h.stack[top-1] == 0) + + case opDELTAP1: + goto delta + + case opSDB: + top-- + h.gs.deltaBase = h.stack[top] + + case opSDS: + top-- + h.gs.deltaShift = h.stack[top] + + case opADD: + top-- + h.stack[top-1] += h.stack[top] + + case opSUB: + top-- + h.stack[top-1] -= h.stack[top] + + case opDIV: + top-- + if h.stack[top] == 0 { + return errors.New("truetype: hinting: division by zero") + } + h.stack[top-1] = int32(fdiv(fixed.Int26_6(h.stack[top-1]), fixed.Int26_6(h.stack[top]))) + + case opMUL: + top-- + h.stack[top-1] = int32(fmul(fixed.Int26_6(h.stack[top-1]), fixed.Int26_6(h.stack[top]))) + + case opABS: + if h.stack[top-1] < 0 { + h.stack[top-1] = -h.stack[top-1] + } + + case opNEG: + h.stack[top-1] = -h.stack[top-1] + + case opFLOOR: + h.stack[top-1] &^= 63 + + case opCEILING: + h.stack[top-1] += 63 + h.stack[top-1] &^= 63 + + case opROUND00, opROUND01, opROUND10, opROUND11: + // The four flavors of opROUND are equivalent. See the comment below on + // opNROUND for the rationale. + h.stack[top-1] = int32(h.round(fixed.Int26_6(h.stack[top-1]))) + + case opNROUND00, opNROUND01, opNROUND10, opNROUND11: + // No-op. The spec says to add one of four "compensations for the engine + // characteristics", to cater for things like "different dot-size printers". + // https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#engine_compensation + // This code does not implement engine compensation, as we don't expect to + // be used to output on dot-matrix printers. + + case opWCVTF: + top -= 2 + h.setScaledCVT(h.stack[top], h.font.scale(h.scale*fixed.Int26_6(h.stack[top+1]))) + + case opDELTAP2, opDELTAP3, opDELTAC1, opDELTAC2, opDELTAC3: + goto delta + + case opSROUND, opS45ROUND: + top-- + switch (h.stack[top] >> 6) & 0x03 { + case 0: + h.gs.roundPeriod = 1 << 5 + case 1, 3: + h.gs.roundPeriod = 1 << 6 + case 2: + h.gs.roundPeriod = 1 << 7 + } + h.gs.roundSuper45 = opcode == opS45ROUND + if h.gs.roundSuper45 { + // The spec says to multiply by √2, but the C Freetype code says 1/√2. + // We go with 1/√2. + h.gs.roundPeriod *= 46341 + h.gs.roundPeriod /= 65536 + } + h.gs.roundPhase = h.gs.roundPeriod * fixed.Int26_6((h.stack[top]>>4)&0x03) / 4 + if x := h.stack[top] & 0x0f; x != 0 { + h.gs.roundThreshold = h.gs.roundPeriod * fixed.Int26_6(x-4) / 8 + } else { + h.gs.roundThreshold = h.gs.roundPeriod - 1 + } + + case opJROT: + top -= 2 + if h.stack[top+1] != 0 { + pc += int(h.stack[top]) + continue + } + + case opJROF: + top -= 2 + if h.stack[top+1] == 0 { + pc += int(h.stack[top]) + continue + } + + case opROFF: + h.gs.roundPeriod = 0 + h.gs.roundPhase = 0 + h.gs.roundThreshold = 0 + h.gs.roundSuper45 = false + + case opRUTG: + h.gs.roundPeriod = 1 << 6 + h.gs.roundPhase = 0 + h.gs.roundThreshold = 1<<6 - 1 + h.gs.roundSuper45 = false + + case opRDTG: + h.gs.roundPeriod = 1 << 6 + h.gs.roundPhase = 0 + h.gs.roundThreshold = 0 + h.gs.roundSuper45 = false + + case opSANGW, opAA: + // These ops are "anachronistic" and no longer used. + top-- + + case opFLIPPT: + if top < int(h.gs.loop) { + return errors.New("truetype: hinting: stack underflow") + } + points := h.points[glyphZone][current] + for ; h.gs.loop != 0; h.gs.loop-- { + top-- + i := h.stack[top] + if i < 0 || len(points) <= int(i) { + return errors.New("truetype: hinting: point out of range") + } + points[i].Flags ^= flagOnCurve + } + h.gs.loop = 1 + + case opFLIPRGON, opFLIPRGOFF: + top -= 2 + i, j, points := h.stack[top], h.stack[top+1], h.points[glyphZone][current] + if i < 0 || len(points) <= int(i) || j < 0 || len(points) <= int(j) { + return errors.New("truetype: hinting: point out of range") + } + for ; i <= j; i++ { + if opcode == opFLIPRGON { + points[i].Flags |= flagOnCurve + } else { + points[i].Flags &^= flagOnCurve + } + } + + case opSCANCTRL: + // We do not support dropout control, as we always rasterize grayscale glyphs. + top-- + + case opSDPVTL0, opSDPVTL1: + top -= 2 + for i := 0; i < 2; i++ { + pt := unhinted + if i != 0 { + pt = current + } + p := h.point(1, pt, h.stack[top]) + q := h.point(2, pt, h.stack[top+1]) + if p == nil || q == nil { + return errors.New("truetype: hinting: point out of range") + } + dx := f2dot14(p.X - q.X) + dy := f2dot14(p.Y - q.Y) + if dx == 0 && dy == 0 { + dx = 0x4000 + } else if opcode&1 != 0 { + // Counter-clockwise rotation. + dx, dy = -dy, dx + } + if i == 0 { + h.gs.dv = normalize(dx, dy) + } else { + h.gs.pv = normalize(dx, dy) + } + } + + case opGETINFO: + res := int32(0) + if h.stack[top-1]&(1<<0) != 0 { + // Set the engine version. We hard-code this to 35, the same as + // the C freetype code, which says that "Version~35 corresponds + // to MS rasterizer v.1.7 as used e.g. in Windows~98". + res |= 35 + } + if h.stack[top-1]&(1<<5) != 0 { + // Set that we support grayscale. + res |= 1 << 12 + } + // We set no other bits, as we do not support rotated or stretched glyphs. + h.stack[top-1] = res + + case opIDEF: + // IDEF is for ancient versions of the bytecode interpreter, and is no longer used. + return errors.New("truetype: hinting: unsupported IDEF instruction") + + case opROLL: + h.stack[top-1], h.stack[top-3], h.stack[top-2] = + h.stack[top-3], h.stack[top-2], h.stack[top-1] + + case opMAX: + top-- + if h.stack[top-1] < h.stack[top] { + h.stack[top-1] = h.stack[top] + } + + case opMIN: + top-- + if h.stack[top-1] > h.stack[top] { + h.stack[top-1] = h.stack[top] + } + + case opSCANTYPE: + // We do not support dropout control, as we always rasterize grayscale glyphs. + top-- + + case opINSTCTRL: + // TODO: support instruction execution control? It seems rare, and even when + // nominally used (e.g. Source Sans Pro), it seems conditional on extreme or + // unusual rasterization conditions. For example, the code snippet at + // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html#INSTCTRL + // uses INSTCTRL when grid-fitting a rotated or stretched glyph, but + // freetype-go does not support rotated or stretched glyphs. + top -= 2 + + default: + if opcode < opPUSHB000 { + return errors.New("truetype: hinting: unrecognized instruction") + } + + if opcode < opMDRP00000 { + // PUSHxxxx opcode. + + if opcode < opPUSHW000 { + opcode -= opPUSHB000 - 1 + } else { + opcode -= opPUSHW000 - 1 - 0x80 + } + goto push + } + + if opcode < opMIRP00000 { + // MDRPxxxxx opcode. + + top-- + i := h.stack[top] + ref := h.point(0, current, h.gs.rp[0]) + p := h.point(1, current, i) + if ref == nil || p == nil { + return errors.New("truetype: hinting: point out of range") + } + + oldDist := fixed.Int26_6(0) + if h.gs.zp[0] == 0 || h.gs.zp[1] == 0 { + p0 := h.point(1, unhinted, i) + p1 := h.point(0, unhinted, h.gs.rp[0]) + oldDist = dotProduct(p0.X-p1.X, p0.Y-p1.Y, h.gs.dv) + } else { + p0 := h.point(1, inFontUnits, i) + p1 := h.point(0, inFontUnits, h.gs.rp[0]) + oldDist = dotProduct(p0.X-p1.X, p0.Y-p1.Y, h.gs.dv) + oldDist = h.font.scale(h.scale * oldDist) + } + + // Single-width cut-in test. + if x := fabs(oldDist - h.gs.singleWidth); x < h.gs.singleWidthCutIn { + if oldDist >= 0 { + oldDist = +h.gs.singleWidth + } else { + oldDist = -h.gs.singleWidth + } + } + + // Rounding bit. + // TODO: metrics compensation. + distance := oldDist + if opcode&0x04 != 0 { + distance = h.round(oldDist) + } + + // Minimum distance bit. + if opcode&0x08 != 0 { + if oldDist >= 0 { + if distance < h.gs.minDist { + distance = h.gs.minDist + } + } else { + if distance > -h.gs.minDist { + distance = -h.gs.minDist + } + } + } + + // Set-RP0 bit. + h.gs.rp[1] = h.gs.rp[0] + h.gs.rp[2] = i + if opcode&0x10 != 0 { + h.gs.rp[0] = i + } + + // Move the point. + oldDist = dotProduct(p.X-ref.X, p.Y-ref.Y, h.gs.pv) + h.move(p, distance-oldDist, true) + + } else { + // MIRPxxxxx opcode. + + top -= 2 + i := h.stack[top] + cvtDist := h.getScaledCVT(h.stack[top+1]) + if fabs(cvtDist-h.gs.singleWidth) < h.gs.singleWidthCutIn { + if cvtDist >= 0 { + cvtDist = +h.gs.singleWidth + } else { + cvtDist = -h.gs.singleWidth + } + } + + if h.gs.zp[1] == 0 { + // TODO: implement once we have a .ttf file that triggers + // this, so that we can step through C's freetype. + return errors.New("truetype: hinting: unimplemented twilight point adjustment") + } + + ref := h.point(0, unhinted, h.gs.rp[0]) + p := h.point(1, unhinted, i) + if ref == nil || p == nil { + return errors.New("truetype: hinting: point out of range") + } + oldDist := dotProduct(p.X-ref.X, p.Y-ref.Y, h.gs.dv) + + ref = h.point(0, current, h.gs.rp[0]) + p = h.point(1, current, i) + if ref == nil || p == nil { + return errors.New("truetype: hinting: point out of range") + } + curDist := dotProduct(p.X-ref.X, p.Y-ref.Y, h.gs.pv) + + if h.gs.autoFlip && oldDist^cvtDist < 0 { + cvtDist = -cvtDist + } + + // Rounding bit. + // TODO: metrics compensation. + distance := cvtDist + if opcode&0x04 != 0 { + // The CVT value is only used if close enough to oldDist. + if (h.gs.zp[0] == h.gs.zp[1]) && + (fabs(cvtDist-oldDist) > h.gs.controlValueCutIn) { + + distance = oldDist + } + distance = h.round(distance) + } + + // Minimum distance bit. + if opcode&0x08 != 0 { + if oldDist >= 0 { + if distance < h.gs.minDist { + distance = h.gs.minDist + } + } else { + if distance > -h.gs.minDist { + distance = -h.gs.minDist + } + } + } + + // Set-RP0 bit. + h.gs.rp[1] = h.gs.rp[0] + h.gs.rp[2] = i + if opcode&0x10 != 0 { + h.gs.rp[0] = i + } + + // Move the point. + h.move(p, distance-curDist, true) + } + } + pc++ + continue + + ifelse: + // Skip past bytecode until the next ELSE (if opcode == 0) or the + // next EIF (for all opcodes). Opcode == 0 means that we have come + // from an IF. Opcode == 1 means that we have come from an ELSE. + { + ifelseloop: + for depth := 0; ; { + pc++ + if pc >= len(program) { + return errors.New("truetype: hinting: unbalanced IF or ELSE") + } + switch program[pc] { + case opIF: + depth++ + case opELSE: + if depth == 0 && opcode == 0 { + break ifelseloop + } + case opEIF: + depth-- + if depth < 0 { + break ifelseloop + } + default: + var ok bool + pc, ok = skipInstructionPayload(program, pc) + if !ok { + return errors.New("truetype: hinting: unbalanced IF or ELSE") + } + } + } + pc++ + continue + } + + push: + // Push n elements from the program to the stack, where n is the low 7 bits of + // opcode. If the low 7 bits are zero, then n is the next byte from the program. + // The high bit being 0 means that the elements are zero-extended bytes. + // The high bit being 1 means that the elements are sign-extended words. + { + width := 1 + if opcode&0x80 != 0 { + opcode &^= 0x80 + width = 2 + } + if opcode == 0 { + pc++ + if pc >= len(program) { + return errors.New("truetype: hinting: insufficient data") + } + opcode = program[pc] + } + pc++ + if top+int(opcode) > len(h.stack) { + return errors.New("truetype: hinting: stack overflow") + } + if pc+width*int(opcode) > len(program) { + return errors.New("truetype: hinting: insufficient data") + } + for ; opcode > 0; opcode-- { + if width == 1 { + h.stack[top] = int32(program[pc]) + } else { + h.stack[top] = int32(int8(program[pc]))<<8 | int32(program[pc+1]) + } + top++ + pc += width + } + continue + } + + delta: + { + if opcode >= opDELTAC1 && !h.scaledCVTInitialized { + h.initializeScaledCVT() + } + top-- + n := h.stack[top] + if int32(top) < 2*n { + return errors.New("truetype: hinting: stack underflow") + } + for ; n > 0; n-- { + top -= 2 + b := h.stack[top] + c := (b & 0xf0) >> 4 + switch opcode { + case opDELTAP2, opDELTAC2: + c += 16 + case opDELTAP3, opDELTAC3: + c += 32 + } + c += h.gs.deltaBase + if ppem := (int32(h.scale) + 1<<5) >> 6; ppem != c { + continue + } + b = (b & 0x0f) - 8 + if b >= 0 { + b++ + } + b = b * 64 / (1 << uint32(h.gs.deltaShift)) + if opcode >= opDELTAC1 { + a := h.stack[top+1] + if a < 0 || len(h.scaledCVT) <= int(a) { + return errors.New("truetype: hinting: index out of range") + } + h.scaledCVT[a] += fixed.Int26_6(b) + } else { + p := h.point(0, current, h.stack[top+1]) + if p == nil { + return errors.New("truetype: hinting: point out of range") + } + h.move(p, fixed.Int26_6(b), true) + } + } + pc++ + continue + } + } + return nil +} + +func (h *hinter) initializeScaledCVT() { + h.scaledCVTInitialized = true + if n := len(h.font.cvt) / 2; n <= cap(h.scaledCVT) { + h.scaledCVT = h.scaledCVT[:n] + } else { + if n < 32 { + n = 32 + } + h.scaledCVT = make([]fixed.Int26_6, len(h.font.cvt)/2, n) + } + for i := range h.scaledCVT { + unscaled := uint16(h.font.cvt[2*i])<<8 | uint16(h.font.cvt[2*i+1]) + h.scaledCVT[i] = h.font.scale(h.scale * fixed.Int26_6(int16(unscaled))) + } +} + +// getScaledCVT returns the scaled value from the font's Control Value Table. +func (h *hinter) getScaledCVT(i int32) fixed.Int26_6 { + if !h.scaledCVTInitialized { + h.initializeScaledCVT() + } + if i < 0 || len(h.scaledCVT) <= int(i) { + return 0 + } + return h.scaledCVT[i] +} + +// setScaledCVT overrides the scaled value from the font's Control Value Table. +func (h *hinter) setScaledCVT(i int32, v fixed.Int26_6) { + if !h.scaledCVTInitialized { + h.initializeScaledCVT() + } + if i < 0 || len(h.scaledCVT) <= int(i) { + return + } + h.scaledCVT[i] = v +} + +func (h *hinter) point(zonePointer uint32, pt pointType, i int32) *Point { + points := h.points[h.gs.zp[zonePointer]][pt] + if i < 0 || len(points) <= int(i) { + return nil + } + return &points[i] +} + +func (h *hinter) move(p *Point, distance fixed.Int26_6, touch bool) { + fvx := int64(h.gs.fv[0]) + pvx := int64(h.gs.pv[0]) + if fvx == 0x4000 && pvx == 0x4000 { + p.X += fixed.Int26_6(distance) + if touch { + p.Flags |= flagTouchedX + } + return + } + + fvy := int64(h.gs.fv[1]) + pvy := int64(h.gs.pv[1]) + if fvy == 0x4000 && pvy == 0x4000 { + p.Y += fixed.Int26_6(distance) + if touch { + p.Flags |= flagTouchedY + } + return + } + + fvDotPv := (fvx*pvx + fvy*pvy) >> 14 + + if fvx != 0 { + p.X += fixed.Int26_6(mulDiv(fvx, int64(distance), fvDotPv)) + if touch { + p.Flags |= flagTouchedX + } + } + + if fvy != 0 { + p.Y += fixed.Int26_6(mulDiv(fvy, int64(distance), fvDotPv)) + if touch { + p.Flags |= flagTouchedY + } + } +} + +func (h *hinter) iupInterp(interpY bool, p1, p2, ref1, ref2 int) { + if p1 > p2 { + return + } + if ref1 >= len(h.points[glyphZone][current]) || + ref2 >= len(h.points[glyphZone][current]) { + return + } + + var ifu1, ifu2 fixed.Int26_6 + if interpY { + ifu1 = h.points[glyphZone][inFontUnits][ref1].Y + ifu2 = h.points[glyphZone][inFontUnits][ref2].Y + } else { + ifu1 = h.points[glyphZone][inFontUnits][ref1].X + ifu2 = h.points[glyphZone][inFontUnits][ref2].X + } + if ifu1 > ifu2 { + ifu1, ifu2 = ifu2, ifu1 + ref1, ref2 = ref2, ref1 + } + + var unh1, unh2, delta1, delta2 fixed.Int26_6 + if interpY { + unh1 = h.points[glyphZone][unhinted][ref1].Y + unh2 = h.points[glyphZone][unhinted][ref2].Y + delta1 = h.points[glyphZone][current][ref1].Y - unh1 + delta2 = h.points[glyphZone][current][ref2].Y - unh2 + } else { + unh1 = h.points[glyphZone][unhinted][ref1].X + unh2 = h.points[glyphZone][unhinted][ref2].X + delta1 = h.points[glyphZone][current][ref1].X - unh1 + delta2 = h.points[glyphZone][current][ref2].X - unh2 + } + + var xy, ifuXY fixed.Int26_6 + if ifu1 == ifu2 { + for i := p1; i <= p2; i++ { + if interpY { + xy = h.points[glyphZone][unhinted][i].Y + } else { + xy = h.points[glyphZone][unhinted][i].X + } + + if xy <= unh1 { + xy += delta1 + } else { + xy += delta2 + } + + if interpY { + h.points[glyphZone][current][i].Y = xy + } else { + h.points[glyphZone][current][i].X = xy + } + } + return + } + + scale, scaleOK := int64(0), false + for i := p1; i <= p2; i++ { + if interpY { + xy = h.points[glyphZone][unhinted][i].Y + ifuXY = h.points[glyphZone][inFontUnits][i].Y + } else { + xy = h.points[glyphZone][unhinted][i].X + ifuXY = h.points[glyphZone][inFontUnits][i].X + } + + if xy <= unh1 { + xy += delta1 + } else if xy >= unh2 { + xy += delta2 + } else { + if !scaleOK { + scaleOK = true + scale = mulDiv(int64(unh2+delta2-unh1-delta1), 0x10000, int64(ifu2-ifu1)) + } + numer := int64(ifuXY-ifu1) * scale + if numer >= 0 { + numer += 0x8000 + } else { + numer -= 0x8000 + } + xy = unh1 + delta1 + fixed.Int26_6(numer/0x10000) + } + + if interpY { + h.points[glyphZone][current][i].Y = xy + } else { + h.points[glyphZone][current][i].X = xy + } + } +} + +func (h *hinter) iupShift(interpY bool, p1, p2, p int) { + var delta fixed.Int26_6 + if interpY { + delta = h.points[glyphZone][current][p].Y - h.points[glyphZone][unhinted][p].Y + } else { + delta = h.points[glyphZone][current][p].X - h.points[glyphZone][unhinted][p].X + } + if delta == 0 { + return + } + for i := p1; i < p2; i++ { + if i == p { + continue + } + if interpY { + h.points[glyphZone][current][i].Y += delta + } else { + h.points[glyphZone][current][i].X += delta + } + } +} + +func (h *hinter) displacement(useZP1 bool) (zonePointer uint32, i int32, d fixed.Int26_6, ok bool) { + zonePointer, i = uint32(0), h.gs.rp[1] + if useZP1 { + zonePointer, i = 1, h.gs.rp[2] + } + p := h.point(zonePointer, current, i) + q := h.point(zonePointer, unhinted, i) + if p == nil || q == nil { + return 0, 0, 0, false + } + d = dotProduct(p.X-q.X, p.Y-q.Y, h.gs.pv) + return zonePointer, i, d, true +} + +// skipInstructionPayload increments pc by the extra data that follows a +// variable length PUSHB or PUSHW instruction. +func skipInstructionPayload(program []byte, pc int) (newPC int, ok bool) { + switch program[pc] { + case opNPUSHB: + pc++ + if pc >= len(program) { + return 0, false + } + pc += int(program[pc]) + case opNPUSHW: + pc++ + if pc >= len(program) { + return 0, false + } + pc += 2 * int(program[pc]) + case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011, + opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111: + pc += int(program[pc] - (opPUSHB000 - 1)) + case opPUSHW000, opPUSHW001, opPUSHW010, opPUSHW011, + opPUSHW100, opPUSHW101, opPUSHW110, opPUSHW111: + pc += 2 * int(program[pc]-(opPUSHW000-1)) + } + return pc, true +} + +// f2dot14 is a 2.14 fixed point number. +type f2dot14 int16 + +func normalize(x, y f2dot14) [2]f2dot14 { + fx, fy := float64(x), float64(y) + l := 0x4000 / math.Hypot(fx, fy) + fx *= l + if fx >= 0 { + fx += 0.5 + } else { + fx -= 0.5 + } + fy *= l + if fy >= 0 { + fy += 0.5 + } else { + fy -= 0.5 + } + return [2]f2dot14{f2dot14(fx), f2dot14(fy)} +} + +// fabs returns abs(x) in 26.6 fixed point arithmetic. +func fabs(x fixed.Int26_6) fixed.Int26_6 { + if x < 0 { + return -x + } + return x +} + +// fdiv returns x/y in 26.6 fixed point arithmetic. +func fdiv(x, y fixed.Int26_6) fixed.Int26_6 { + return fixed.Int26_6((int64(x) << 6) / int64(y)) +} + +// fmul returns x*y in 26.6 fixed point arithmetic. +func fmul(x, y fixed.Int26_6) fixed.Int26_6 { + return fixed.Int26_6((int64(x)*int64(y) + 1<<5) >> 6) +} + +// dotProduct returns the dot product of [x, y] and q. It is almost the same as +// px := int64(x) +// py := int64(y) +// qx := int64(q[0]) +// qy := int64(q[1]) +// return fixed.Int26_6((px*qx + py*qy + 1<<13) >> 14) +// except that the computation is done with 32-bit integers to produce exactly +// the same rounding behavior as C Freetype. +func dotProduct(x, y fixed.Int26_6, q [2]f2dot14) fixed.Int26_6 { + // Compute x*q[0] as 64-bit value. + l := uint32((int32(x) & 0xFFFF) * int32(q[0])) + m := (int32(x) >> 16) * int32(q[0]) + + lo1 := l + (uint32(m) << 16) + hi1 := (m >> 16) + (int32(l) >> 31) + bool2int32(lo1 < l) + + // Compute y*q[1] as 64-bit value. + l = uint32((int32(y) & 0xFFFF) * int32(q[1])) + m = (int32(y) >> 16) * int32(q[1]) + + lo2 := l + (uint32(m) << 16) + hi2 := (m >> 16) + (int32(l) >> 31) + bool2int32(lo2 < l) + + // Add them. + lo := lo1 + lo2 + hi := hi1 + hi2 + bool2int32(lo < lo1) + + // Divide the result by 2^14 with rounding. + s := hi >> 31 + l = lo + uint32(s) + hi += s + bool2int32(l < lo) + lo = l + + l = lo + 0x2000 + hi += bool2int32(l < lo) + + return fixed.Int26_6((uint32(hi) << 18) | (l >> 14)) +} + +// mulDiv returns x*y/z, rounded to the nearest integer. +func mulDiv(x, y, z int64) int64 { + xy := x * y + if z < 0 { + xy, z = -xy, -z + } + if xy >= 0 { + xy += z / 2 + } else { + xy -= z / 2 + } + return xy / z +} + +// 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 fixed.Int26_6) fixed.Int26_6 { + if h.gs.roundPeriod == 0 { + // Rounding is off. + return x + } + if x >= 0 { + ret := x - h.gs.roundPhase + h.gs.roundThreshold + if h.gs.roundSuper45 { + ret /= h.gs.roundPeriod + ret *= h.gs.roundPeriod + } else { + ret &= -h.gs.roundPeriod + } + if x != 0 && ret < 0 { + ret = 0 + } + return ret + h.gs.roundPhase + } + ret := -x - h.gs.roundPhase + h.gs.roundThreshold + if h.gs.roundSuper45 { + ret /= h.gs.roundPeriod + ret *= h.gs.roundPeriod + } else { + ret &= -h.gs.roundPeriod + } + if ret < 0 { + ret = 0 + } + return -ret - h.gs.roundPhase +} + +func bool2int32(b bool) int32 { + if b { + return 1 + } + return 0 +} diff --git a/vendor/github.com/golang/freetype/truetype/hint_test.go b/vendor/github.com/golang/freetype/truetype/hint_test.go new file mode 100644 index 0000000..7eb43dd --- /dev/null +++ b/vendor/github.com/golang/freetype/truetype/hint_test.go @@ -0,0 +1,675 @@ +// Copyright 2012 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. + +package truetype + +import ( + "reflect" + "strings" + "testing" + + "golang.org/x/image/math/fixed" +) + +func TestBytecode(t *testing.T) { + testCases := []struct { + desc string + prog []byte + want []int32 + errStr string + }{ + { + "underflow", + []byte{ + opDUP, + }, + nil, + "underflow", + }, + { + "infinite loop", + []byte{ + opPUSHW000, // [-1] + 0xff, + 0xff, + opDUP, // [-1, -1] + opJMPR, // [-1] + }, + nil, + "too many steps", + }, + { + "unbalanced if/else", + []byte{ + opPUSHB000, // [0] + 0, + opIF, + }, + nil, + "unbalanced", + }, + { + "vector set/gets", + []byte{ + opSVTCA1, // [] + opGPV, // [0x4000, 0] + opSVTCA0, // [0x4000, 0] + opGFV, // [0x4000, 0, 0, 0x4000] + opNEG, // [0x4000, 0, 0, -0x4000] + opSPVFS, // [0x4000, 0] + opSFVTPV, // [0x4000, 0] + opPUSHB000, // [0x4000, 0, 1] + 1, + opGFV, // [0x4000, 0, 1, 0, -0x4000] + opPUSHB000, // [0x4000, 0, 1, 0, -0x4000, 2] + 2, + }, + []int32{0x4000, 0, 1, 0, -0x4000, 2}, + "", + }, + { + "jumps", + []byte{ + opPUSHB001, // [10, 2] + 10, + 2, + opJMPR, // [10] + opDUP, // not executed + opDUP, // [10, 10] + opPUSHB010, // [10, 10, 20, 2, 1] + 20, + 2, + 1, + opJROT, // [10, 10, 20] + opDUP, // not executed + opDUP, // [10, 10, 20, 20] + opPUSHB010, // [10, 10, 20, 20, 30, 2, 1] + 30, + 2, + 1, + opJROF, // [10, 10, 20, 20, 30] + opDUP, // [10, 10, 20, 20, 30, 30] + opDUP, // [10, 10, 20, 20, 30, 30, 30] + }, + []int32{10, 10, 20, 20, 30, 30, 30}, + "", + }, + { + "stack ops", + []byte{ + opPUSHB010, // [10, 20, 30] + 10, + 20, + 30, + opCLEAR, // [] + opPUSHB010, // [40, 50, 60] + 40, + 50, + 60, + opSWAP, // [40, 60, 50] + opDUP, // [40, 60, 50, 50] + opDUP, // [40, 60, 50, 50, 50] + opPOP, // [40, 60, 50, 50] + opDEPTH, // [40, 60, 50, 50, 4] + opCINDEX, // [40, 60, 50, 50, 40] + opPUSHB000, // [40, 60, 50, 50, 40, 4] + 4, + opMINDEX, // [40, 50, 50, 40, 60] + }, + []int32{40, 50, 50, 40, 60}, + "", + }, + { + "push ops", + []byte{ + opPUSHB000, // [255] + 255, + opPUSHW001, // [255, -2, 253] + 255, + 254, + 0, + 253, + opNPUSHB, // [1, -2, 253, 1, 2] + 2, + 1, + 2, + opNPUSHW, // [1, -2, 253, 1, 2, 0x0405, 0x0607, 0x0809] + 3, + 4, + 5, + 6, + 7, + 8, + 9, + }, + []int32{255, -2, 253, 1, 2, 0x0405, 0x0607, 0x0809}, + "", + }, + { + "store ops", + []byte{ + opPUSHB011, // [1, 22, 3, 44] + 1, + 22, + 3, + 44, + opWS, // [1, 22] + opWS, // [] + opPUSHB000, // [3] + 3, + opRS, // [44] + }, + []int32{44}, + "", + }, + { + "comparison ops", + []byte{ + opPUSHB001, // [10, 20] + 10, + 20, + opLT, // [1] + opPUSHB001, // [1, 10, 20] + 10, + 20, + opLTEQ, // [1, 1] + opPUSHB001, // [1, 1, 10, 20] + 10, + 20, + opGT, // [1, 1, 0] + opPUSHB001, // [1, 1, 0, 10, 20] + 10, + 20, + opGTEQ, // [1, 1, 0, 0] + opEQ, // [1, 1, 1] + opNEQ, // [1, 0] + }, + []int32{1, 0}, + "", + }, + { + "odd/even", + // Calculate odd(2+31/64), odd(2+32/64), even(2), even(1). + []byte{ + opPUSHB000, // [159] + 159, + opODD, // [0] + opPUSHB000, // [0, 160] + 160, + opODD, // [0, 1] + opPUSHB000, // [0, 1, 128] + 128, + opEVEN, // [0, 1, 1] + opPUSHB000, // [0, 1, 1, 64] + 64, + opEVEN, // [0, 1, 1, 0] + }, + []int32{0, 1, 1, 0}, + "", + }, + { + "if true", + []byte{ + opPUSHB001, // [255, 1] + 255, + 1, + opIF, + opPUSHB000, // [255, 2] + 2, + opEIF, + opPUSHB000, // [255, 2, 254] + 254, + }, + []int32{255, 2, 254}, + "", + }, + { + "if false", + []byte{ + opPUSHB001, // [255, 0] + 255, + 0, + opIF, + opPUSHB000, // [255] + 2, + opEIF, + opPUSHB000, // [255, 254] + 254, + }, + []int32{255, 254}, + "", + }, + { + "if/else true", + []byte{ + opPUSHB000, // [1] + 1, + opIF, + opPUSHB000, // [2] + 2, + opELSE, + opPUSHB000, // not executed + 3, + opEIF, + }, + []int32{2}, + "", + }, + { + "if/else false", + []byte{ + opPUSHB000, // [0] + 0, + opIF, + opPUSHB000, // not executed + 2, + opELSE, + opPUSHB000, // [3] + 3, + opEIF, + }, + []int32{3}, + "", + }, + { + "if/else true if/else false", + // 0x58 is the opcode for opIF. The literal 0x58s below are pushed data. + []byte{ + opPUSHB010, // [255, 0, 1] + 255, + 0, + 1, + opIF, + opIF, + opPUSHB001, // not executed + 0x58, + 0x58, + opELSE, + opPUSHW000, // [255, 0x5858] + 0x58, + 0x58, + opEIF, + opELSE, + opIF, + opNPUSHB, // not executed + 3, + 0x58, + 0x58, + 0x58, + opELSE, + opNPUSHW, // not executed + 2, + 0x58, + 0x58, + 0x58, + 0x58, + opEIF, + opEIF, + opPUSHB000, // [255, 0x5858, 254] + 254, + }, + []int32{255, 0x5858, 254}, + "", + }, + { + "if/else false if/else true", + // 0x58 is the opcode for opIF. The literal 0x58s below are pushed data. + []byte{ + opPUSHB010, // [255, 1, 0] + 255, + 1, + 0, + opIF, + opIF, + opPUSHB001, // not executed + 0x58, + 0x58, + opELSE, + opPUSHW000, // not executed + 0x58, + 0x58, + opEIF, + opELSE, + opIF, + opNPUSHB, // [255, 0x58, 0x58, 0x58] + 3, + 0x58, + 0x58, + 0x58, + opELSE, + opNPUSHW, // not executed + 2, + 0x58, + 0x58, + 0x58, + 0x58, + opEIF, + opEIF, + opPUSHB000, // [255, 0x58, 0x58, 0x58, 254] + 254, + }, + []int32{255, 0x58, 0x58, 0x58, 254}, + "", + }, + { + "logical ops", + []byte{ + opPUSHB010, // [0, 10, 20] + 0, + 10, + 20, + opAND, // [0, 1] + opOR, // [1] + opNOT, // [0] + }, + []int32{0}, + "", + }, + { + "arithmetic ops", + // Calculate abs((-(1 - (2*3)))/2 + 1/64). + // The answer is 5/2 + 1/64 in ideal numbers, or 161 in 26.6 fixed point math. + []byte{ + opPUSHB010, // [64, 128, 192] + 1 << 6, + 2 << 6, + 3 << 6, + opMUL, // [64, 384] + opSUB, // [-320] + opNEG, // [320] + opPUSHB000, // [320, 128] + 2 << 6, + opDIV, // [160] + opPUSHB000, // [160, 1] + 1, + opADD, // [161] + opABS, // [161] + }, + []int32{161}, + "", + }, + { + "floor, ceiling", + []byte{ + opPUSHB000, // [96] + 96, + opFLOOR, // [64] + opPUSHB000, // [64, 96] + 96, + opCEILING, // [64, 128] + }, + []int32{64, 128}, + "", + }, + { + "rounding", + // Round 1.40625 (which is 90/64) under various rounding policies. + // See figure 20 of https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding + []byte{ + opROFF, // [] + opPUSHB000, // [90] + 90, + opROUND00, // [90] + opRTG, // [90] + opPUSHB000, // [90, 90] + 90, + opROUND00, // [90, 64] + opRTHG, // [90, 64] + opPUSHB000, // [90, 64, 90] + 90, + opROUND00, // [90, 64, 96] + opRDTG, // [90, 64, 96] + opPUSHB000, // [90, 64, 96, 90] + 90, + opROUND00, // [90, 64, 96, 64] + opRUTG, // [90, 64, 96, 64] + opPUSHB000, // [90, 64, 96, 64, 90] + 90, + opROUND00, // [90, 64, 96, 64, 128] + opRTDG, // [90, 64, 96, 64, 128] + opPUSHB000, // [90, 64, 96, 64, 128, 90] + 90, + opROUND00, // [90, 64, 96, 64, 128, 96] + }, + []int32{90, 64, 96, 64, 128, 96}, + "", + }, + { + "super-rounding", + // See figure 20 of https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding + // and the sign preservation steps of the "Order of rounding operations" section. + []byte{ + opPUSHB000, // [0x58] + 0x58, + opSROUND, // [] + opPUSHW000, // [-81] + 0xff, + 0xaf, + opROUND00, // [-80] + opPUSHW000, // [-80, -80] + 0xff, + 0xb0, + opROUND00, // [-80, -80] + opPUSHW000, // [-80, -80, -17] + 0xff, + 0xef, + opROUND00, // [-80, -80, -16] + opPUSHW000, // [-80, -80, -16, -16] + 0xff, + 0xf0, + opROUND00, // [-80, -80, -16, -16] + opPUSHB000, // [-80, -80, -16, -16, 0] + 0, + opROUND00, // [-80, -80, -16, -16, 16] + opPUSHB000, // [-80, -80, -16, -16, 16, 16] + 16, + opROUND00, // [-80, -80, -16, -16, 16, 16] + opPUSHB000, // [-80, -80, -16, -16, 16, 16, 47] + 47, + opROUND00, // [-80, -80, -16, -16, 16, 16, 16] + opPUSHB000, // [-80, -80, -16, -16, 16, 16, 16, 48] + 48, + opROUND00, // [-80, -80, -16, -16, 16, 16, 16, 80] + }, + []int32{-80, -80, -16, -16, 16, 16, 16, 80}, + "", + }, + { + "roll", + []byte{ + opPUSHB010, // [1, 2, 3] + 1, + 2, + 3, + opROLL, // [2, 3, 1] + }, + []int32{2, 3, 1}, + "", + }, + { + "max/min", + []byte{ + opPUSHW001, // [-2, -3] + 0xff, + 0xfe, + 0xff, + 0xfd, + opMAX, // [-2] + opPUSHW001, // [-2, -4, -5] + 0xff, + 0xfc, + 0xff, + 0xfb, + opMIN, // [-2, -5] + }, + []int32{-2, -5}, + "", + }, + { + "functions", + []byte{ + opPUSHB011, // [3, 7, 0, 3] + 3, + 7, + 0, + 3, + + opFDEF, // Function #3 (not called) + opPUSHB000, + 98, + opENDF, + + opFDEF, // Function #0 + opDUP, + opADD, + opENDF, + + opFDEF, // Function #7 + opPUSHB001, + 10, + 0, + opCALL, + opDUP, + opENDF, + + opFDEF, // Function #3 (again) + opPUSHB000, + 99, + opENDF, + + opPUSHB001, // [2, 0] + 2, + 0, + opCALL, // [4] + opPUSHB000, // [4, 3] + 3, + opLOOPCALL, // [99, 99, 99, 99] + opPUSHB000, // [99, 99, 99, 99, 7] + 7, + opCALL, // [99, 99, 99, 99, 20, 20] + }, + []int32{99, 99, 99, 99, 20, 20}, + "", + }, + } + + for _, tc := range testCases { + h := &hinter{} + h.init(&Font{ + maxStorage: 32, + maxStackElements: 100, + }, 768) + err, errStr := h.run(tc.prog, nil, nil, nil, nil), "" + if err != nil { + errStr = err.Error() + } + if tc.errStr != "" { + if errStr == "" { + t.Errorf("%s: got no error, want %q", tc.desc, tc.errStr) + } else if !strings.Contains(errStr, tc.errStr) { + t.Errorf("%s: got error %q, want one containing %q", tc.desc, errStr, tc.errStr) + } + continue + } + if errStr != "" { + t.Errorf("%s: got error %q, want none", tc.desc, errStr) + continue + } + got := h.stack[:len(tc.want)] + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("%s: got %v, want %v", tc.desc, got, tc.want) + continue + } + } +} + +// TestMove tests that the hinter.move method matches the output of the C +// Freetype implementation. +func TestMove(t *testing.T) { + h, p := hinter{}, Point{} + testCases := []struct { + pvX, pvY, fvX, fvY f2dot14 + wantX, wantY fixed.Int26_6 + }{ + {+0x4000, +0x0000, +0x4000, +0x0000, +1000, +0}, + {+0x4000, +0x0000, -0x4000, +0x0000, +1000, +0}, + {-0x4000, +0x0000, +0x4000, +0x0000, -1000, +0}, + {-0x4000, +0x0000, -0x4000, +0x0000, -1000, +0}, + {+0x0000, +0x4000, +0x0000, +0x4000, +0, +1000}, + {+0x0000, +0x4000, +0x0000, -0x4000, +0, +1000}, + {+0x4000, +0x0000, +0x2d41, +0x2d41, +1000, +1000}, + {+0x4000, +0x0000, -0x2d41, +0x2d41, +1000, -1000}, + {+0x4000, +0x0000, +0x2d41, -0x2d41, +1000, -1000}, + {+0x4000, +0x0000, -0x2d41, -0x2d41, +1000, +1000}, + {-0x4000, +0x0000, +0x2d41, +0x2d41, -1000, -1000}, + {-0x4000, +0x0000, -0x2d41, +0x2d41, -1000, +1000}, + {-0x4000, +0x0000, +0x2d41, -0x2d41, -1000, +1000}, + {-0x4000, +0x0000, -0x2d41, -0x2d41, -1000, -1000}, + {+0x376d, +0x2000, +0x2d41, +0x2d41, +732, +732}, + {-0x376d, +0x2000, +0x2d41, +0x2d41, -2732, -2732}, + {+0x376d, +0x2000, +0x2d41, -0x2d41, +2732, -2732}, + {-0x376d, +0x2000, +0x2d41, -0x2d41, -732, +732}, + {-0x376d, -0x2000, +0x2d41, +0x2d41, -732, -732}, + {+0x376d, +0x2000, +0x4000, +0x0000, +1155, +0}, + {+0x376d, +0x2000, +0x0000, +0x4000, +0, +2000}, + } + for _, tc := range testCases { + p = Point{} + h.gs.pv = [2]f2dot14{tc.pvX, tc.pvY} + h.gs.fv = [2]f2dot14{tc.fvX, tc.fvY} + h.move(&p, 1000, true) + tx := p.Flags&flagTouchedX != 0 + ty := p.Flags&flagTouchedY != 0 + wantTX := tc.fvX != 0 + wantTY := tc.fvY != 0 + if p.X != tc.wantX || p.Y != tc.wantY || tx != wantTX || ty != wantTY { + t.Errorf("pv=%v, fv=%v\ngot %d, %d, %t, %t\nwant %d, %d, %t, %t", + h.gs.pv, h.gs.fv, p.X, p.Y, tx, ty, tc.wantX, tc.wantY, wantTX, wantTY) + continue + } + + // Check that p is aligned with the freedom vector. + a := int64(p.X) * int64(tc.fvY) + b := int64(p.Y) * int64(tc.fvX) + if a != b { + t.Errorf("pv=%v, fv=%v, p=%v not aligned with fv", h.gs.pv, h.gs.fv, p) + continue + } + + // Check that the projected p is 1000 away from the origin. + dotProd := (int64(p.X)*int64(tc.pvX) + int64(p.Y)*int64(tc.pvY) + 1<<13) >> 14 + if dotProd != 1000 { + t.Errorf("pv=%v, fv=%v, p=%v not 1000 from origin", h.gs.pv, h.gs.fv, p) + continue + } + } +} + +// TestNormalize tests that the normalize function matches the output of the C +// Freetype implementation. +func TestNormalize(t *testing.T) { + testCases := [][2]f2dot14{ + {-15895, 3974}, + {-15543, 5181}, + {-14654, 7327}, + {-11585, 11585}, + {0, 16384}, + {11585, 11585}, + {14654, 7327}, + {15543, 5181}, + {15895, 3974}, + {16066, 3213}, + {16161, 2694}, + {16219, 2317}, + {16257, 2032}, + {16284, 1809}, + } + for i, want := range testCases { + got := normalize(f2dot14(i)-4, 1) + if got != want { + t.Errorf("i=%d: got %v, want %v", i, got, want) + } + } +} diff --git a/vendor/github.com/golang/freetype/truetype/opcodes.go b/vendor/github.com/golang/freetype/truetype/opcodes.go new file mode 100644 index 0000000..1880e1e --- /dev/null +++ b/vendor/github.com/golang/freetype/truetype/opcodes.go @@ -0,0 +1,289 @@ +// Copyright 2012 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. + +package truetype + +// The Truetype opcodes are summarized at +// https://developer.apple.com/fonts/TTRefMan/RM07/appendixA.html + +const ( + opSVTCA0 = 0x00 // Set freedom and projection Vectors To Coordinate Axis + opSVTCA1 = 0x01 // . + opSPVTCA0 = 0x02 // Set Projection Vector To Coordinate Axis + opSPVTCA1 = 0x03 // . + opSFVTCA0 = 0x04 // Set Freedom Vector to Coordinate Axis + opSFVTCA1 = 0x05 // . + opSPVTL0 = 0x06 // Set Projection Vector To Line + opSPVTL1 = 0x07 // . + opSFVTL0 = 0x08 // Set Freedom Vector To Line + opSFVTL1 = 0x09 // . + opSPVFS = 0x0a // Set Projection Vector From Stack + opSFVFS = 0x0b // Set Freedom Vector From Stack + opGPV = 0x0c // Get Projection Vector + opGFV = 0x0d // Get Freedom Vector + opSFVTPV = 0x0e // Set Freedom Vector To Projection Vector + opISECT = 0x0f // moves point p to the InterSECTion of two lines + opSRP0 = 0x10 // Set Reference Point 0 + opSRP1 = 0x11 // Set Reference Point 1 + opSRP2 = 0x12 // Set Reference Point 2 + opSZP0 = 0x13 // Set Zone Pointer 0 + opSZP1 = 0x14 // Set Zone Pointer 1 + opSZP2 = 0x15 // Set Zone Pointer 2 + opSZPS = 0x16 // Set Zone PointerS + opSLOOP = 0x17 // Set LOOP variable + opRTG = 0x18 // Round To Grid + opRTHG = 0x19 // Round To Half Grid + opSMD = 0x1a // Set Minimum Distance + opELSE = 0x1b // ELSE clause + opJMPR = 0x1c // JuMP Relative + opSCVTCI = 0x1d // Set Control Value Table Cut-In + opSSWCI = 0x1e // Set Single Width Cut-In + opSSW = 0x1f // Set Single Width + opDUP = 0x20 // DUPlicate top stack element + opPOP = 0x21 // POP top stack element + opCLEAR = 0x22 // CLEAR the stack + opSWAP = 0x23 // SWAP the top two elements on the stack + opDEPTH = 0x24 // DEPTH of the stack + opCINDEX = 0x25 // Copy the INDEXed element to the top of the stack + opMINDEX = 0x26 // Move the INDEXed element to the top of the stack + opALIGNPTS = 0x27 // ALIGN PoinTS + op_0x28 = 0x28 // deprecated + opUTP = 0x29 // UnTouch Point + opLOOPCALL = 0x2a // LOOP and CALL function + opCALL = 0x2b // CALL function + opFDEF = 0x2c // Function DEFinition + opENDF = 0x2d // END Function definition + opMDAP0 = 0x2e // Move Direct Absolute Point + opMDAP1 = 0x2f // . + opIUP0 = 0x30 // Interpolate Untouched Points through the outline + opIUP1 = 0x31 // . + opSHP0 = 0x32 // SHift Point using reference point + opSHP1 = 0x33 // . + opSHC0 = 0x34 // SHift Contour using reference point + opSHC1 = 0x35 // . + opSHZ0 = 0x36 // SHift Zone using reference point + opSHZ1 = 0x37 // . + opSHPIX = 0x38 // SHift point by a PIXel amount + opIP = 0x39 // Interpolate Point + opMSIRP0 = 0x3a // Move Stack Indirect Relative Point + opMSIRP1 = 0x3b // . + opALIGNRP = 0x3c // ALIGN to Reference Point + opRTDG = 0x3d // Round To Double Grid + opMIAP0 = 0x3e // Move Indirect Absolute Point + opMIAP1 = 0x3f // . + opNPUSHB = 0x40 // PUSH N Bytes + opNPUSHW = 0x41 // PUSH N Words + opWS = 0x42 // Write Store + opRS = 0x43 // Read Store + opWCVTP = 0x44 // Write Control Value Table in Pixel units + opRCVT = 0x45 // Read Control Value Table entry + opGC0 = 0x46 // Get Coordinate projected onto the projection vector + opGC1 = 0x47 // . + opSCFS = 0x48 // Sets Coordinate From the Stack using projection vector and freedom vector + opMD0 = 0x49 // Measure Distance + opMD1 = 0x4a // . + opMPPEM = 0x4b // Measure Pixels Per EM + opMPS = 0x4c // Measure Point Size + opFLIPON = 0x4d // set the auto FLIP Boolean to ON + opFLIPOFF = 0x4e // set the auto FLIP Boolean to OFF + opDEBUG = 0x4f // DEBUG call + opLT = 0x50 // Less Than + opLTEQ = 0x51 // Less Than or EQual + opGT = 0x52 // Greater Than + opGTEQ = 0x53 // Greater Than or EQual + opEQ = 0x54 // EQual + opNEQ = 0x55 // Not EQual + opODD = 0x56 // ODD + opEVEN = 0x57 // EVEN + opIF = 0x58 // IF test + opEIF = 0x59 // End IF + opAND = 0x5a // logical AND + opOR = 0x5b // logical OR + opNOT = 0x5c // logical NOT + opDELTAP1 = 0x5d // DELTA exception P1 + opSDB = 0x5e // Set Delta Base in the graphics state + opSDS = 0x5f // Set Delta Shift in the graphics state + opADD = 0x60 // ADD + opSUB = 0x61 // SUBtract + opDIV = 0x62 // DIVide + opMUL = 0x63 // MULtiply + opABS = 0x64 // ABSolute value + opNEG = 0x65 // NEGate + opFLOOR = 0x66 // FLOOR + opCEILING = 0x67 // CEILING + opROUND00 = 0x68 // ROUND value + opROUND01 = 0x69 // . + opROUND10 = 0x6a // . + opROUND11 = 0x6b // . + opNROUND00 = 0x6c // No ROUNDing of value + opNROUND01 = 0x6d // . + opNROUND10 = 0x6e // . + opNROUND11 = 0x6f // . + opWCVTF = 0x70 // Write Control Value Table in Funits + opDELTAP2 = 0x71 // DELTA exception P2 + opDELTAP3 = 0x72 // DELTA exception P3 + opDELTAC1 = 0x73 // DELTA exception C1 + opDELTAC2 = 0x74 // DELTA exception C2 + opDELTAC3 = 0x75 // DELTA exception C3 + opSROUND = 0x76 // Super ROUND + opS45ROUND = 0x77 // Super ROUND 45 degrees + opJROT = 0x78 // Jump Relative On True + opJROF = 0x79 // Jump Relative On False + opROFF = 0x7a // Round OFF + op_0x7b = 0x7b // deprecated + opRUTG = 0x7c // Round Up To Grid + opRDTG = 0x7d // Round Down To Grid + opSANGW = 0x7e // Set ANGle Weight + opAA = 0x7f // Adjust Angle + opFLIPPT = 0x80 // FLIP PoinT + opFLIPRGON = 0x81 // FLIP RanGe ON + opFLIPRGOFF = 0x82 // FLIP RanGe OFF + op_0x83 = 0x83 // deprecated + op_0x84 = 0x84 // deprecated + opSCANCTRL = 0x85 // SCAN conversion ConTRoL + opSDPVTL0 = 0x86 // Set Dual Projection Vector To Line + opSDPVTL1 = 0x87 // . + opGETINFO = 0x88 // GET INFOrmation + opIDEF = 0x89 // Instruction DEFinition + opROLL = 0x8a // ROLL the top three stack elements + opMAX = 0x8b // MAXimum of top two stack elements + opMIN = 0x8c // MINimum of top two stack elements + opSCANTYPE = 0x8d // SCANTYPE + opINSTCTRL = 0x8e // INSTRuction execution ConTRoL + op_0x8f = 0x8f + op_0x90 = 0x90 + op_0x91 = 0x91 + op_0x92 = 0x92 + op_0x93 = 0x93 + op_0x94 = 0x94 + op_0x95 = 0x95 + op_0x96 = 0x96 + op_0x97 = 0x97 + op_0x98 = 0x98 + op_0x99 = 0x99 + op_0x9a = 0x9a + op_0x9b = 0x9b + op_0x9c = 0x9c + op_0x9d = 0x9d + op_0x9e = 0x9e + op_0x9f = 0x9f + op_0xa0 = 0xa0 + op_0xa1 = 0xa1 + op_0xa2 = 0xa2 + op_0xa3 = 0xa3 + op_0xa4 = 0xa4 + op_0xa5 = 0xa5 + op_0xa6 = 0xa6 + op_0xa7 = 0xa7 + op_0xa8 = 0xa8 + op_0xa9 = 0xa9 + op_0xaa = 0xaa + op_0xab = 0xab + op_0xac = 0xac + op_0xad = 0xad + op_0xae = 0xae + op_0xaf = 0xaf + opPUSHB000 = 0xb0 // PUSH Bytes + opPUSHB001 = 0xb1 // . + opPUSHB010 = 0xb2 // . + opPUSHB011 = 0xb3 // . + opPUSHB100 = 0xb4 // . + opPUSHB101 = 0xb5 // . + opPUSHB110 = 0xb6 // . + opPUSHB111 = 0xb7 // . + opPUSHW000 = 0xb8 // PUSH Words + opPUSHW001 = 0xb9 // . + opPUSHW010 = 0xba // . + opPUSHW011 = 0xbb // . + opPUSHW100 = 0xbc // . + opPUSHW101 = 0xbd // . + opPUSHW110 = 0xbe // . + opPUSHW111 = 0xbf // . + opMDRP00000 = 0xc0 // Move Direct Relative Point + opMDRP00001 = 0xc1 // . + opMDRP00010 = 0xc2 // . + opMDRP00011 = 0xc3 // . + opMDRP00100 = 0xc4 // . + opMDRP00101 = 0xc5 // . + opMDRP00110 = 0xc6 // . + opMDRP00111 = 0xc7 // . + opMDRP01000 = 0xc8 // . + opMDRP01001 = 0xc9 // . + opMDRP01010 = 0xca // . + opMDRP01011 = 0xcb // . + opMDRP01100 = 0xcc // . + opMDRP01101 = 0xcd // . + opMDRP01110 = 0xce // . + opMDRP01111 = 0xcf // . + opMDRP10000 = 0xd0 // . + opMDRP10001 = 0xd1 // . + opMDRP10010 = 0xd2 // . + opMDRP10011 = 0xd3 // . + opMDRP10100 = 0xd4 // . + opMDRP10101 = 0xd5 // . + opMDRP10110 = 0xd6 // . + opMDRP10111 = 0xd7 // . + opMDRP11000 = 0xd8 // . + opMDRP11001 = 0xd9 // . + opMDRP11010 = 0xda // . + opMDRP11011 = 0xdb // . + opMDRP11100 = 0xdc // . + opMDRP11101 = 0xdd // . + opMDRP11110 = 0xde // . + opMDRP11111 = 0xdf // . + opMIRP00000 = 0xe0 // Move Indirect Relative Point + opMIRP00001 = 0xe1 // . + opMIRP00010 = 0xe2 // . + opMIRP00011 = 0xe3 // . + opMIRP00100 = 0xe4 // . + opMIRP00101 = 0xe5 // . + opMIRP00110 = 0xe6 // . + opMIRP00111 = 0xe7 // . + opMIRP01000 = 0xe8 // . + opMIRP01001 = 0xe9 // . + opMIRP01010 = 0xea // . + opMIRP01011 = 0xeb // . + opMIRP01100 = 0xec // . + opMIRP01101 = 0xed // . + opMIRP01110 = 0xee // . + opMIRP01111 = 0xef // . + opMIRP10000 = 0xf0 // . + opMIRP10001 = 0xf1 // . + opMIRP10010 = 0xf2 // . + opMIRP10011 = 0xf3 // . + opMIRP10100 = 0xf4 // . + opMIRP10101 = 0xf5 // . + opMIRP10110 = 0xf6 // . + opMIRP10111 = 0xf7 // . + opMIRP11000 = 0xf8 // . + opMIRP11001 = 0xf9 // . + opMIRP11010 = 0xfa // . + opMIRP11011 = 0xfb // . + opMIRP11100 = 0xfc // . + opMIRP11101 = 0xfd // . + opMIRP11110 = 0xfe // . + opMIRP11111 = 0xff // . +) + +// popCount is the number of stack elements that each opcode pops. +var popCount = [256]uint8{ + // 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f + 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 5, // 0x00 - 0x0f + 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, // 0x10 - 0x1f + 1, 1, 0, 2, 0, 1, 1, 2, 0, 1, 2, 1, 1, 0, 1, 1, // 0x20 - 0x2f + 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 2, // 0x30 - 0x3f + 0, 0, 2, 1, 2, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, // 0x40 - 0x4f + 2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, 1, 1, 1, // 0x50 - 0x5f + 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f + 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0, 0, 0, 1, 1, // 0x70 - 0x7f + 0, 2, 2, 0, 0, 1, 2, 2, 1, 1, 3, 2, 2, 1, 2, 0, // 0x80 - 0x8f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0x9f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0 - 0xaf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0 - 0xcf + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0 - 0xdf + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xe0 - 0xef + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xf0 - 0xff +} diff --git a/vendor/github.com/golang/freetype/truetype/truetype.go b/vendor/github.com/golang/freetype/truetype/truetype.go new file mode 100644 index 0000000..7270bbf --- /dev/null +++ b/vendor/github.com/golang/freetype/truetype/truetype.go @@ -0,0 +1,653 @@ +// 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. + +// Package truetype provides a parser for the TTF and TTC file formats. +// Those formats are documented at http://developer.apple.com/fonts/TTRefMan/ +// and http://www.microsoft.com/typography/otspec/ +// +// 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 pixels in 1 em, expressed as a 26.6 fixed point value. For +// example, if 1 em is 10 pixels then scale is fixed.I(10), which is equal to +// fixed.Int26_6(10 << 6). +// +// To measure a TrueType font in ideal FUnit space, use scale equal to +// font.FUnitsPerEm(). +package truetype // import "github.com/golang/freetype/truetype" + +import ( + "fmt" + + "golang.org/x/image/math/fixed" +) + +// An Index is a Font's index of a rune. +type Index uint16 + +// A NameID identifies a name table entry. +// +// See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html +type NameID uint16 + +const ( + NameIDCopyright NameID = 0 + NameIDFontFamily = 1 + NameIDFontSubfamily = 2 + NameIDUniqueSubfamilyID = 3 + NameIDFontFullName = 4 + NameIDNameTableVersion = 5 + NameIDPostscriptName = 6 + NameIDTrademarkNotice = 7 + NameIDManufacturerName = 8 + NameIDDesignerName = 9 + NameIDFontDescription = 10 + NameIDFontVendorURL = 11 + NameIDFontDesignerURL = 12 + NameIDFontLicense = 13 + NameIDFontLicenseURL = 14 + NameIDPreferredFamily = 16 + NameIDPreferredSubfamily = 17 + NameIDCompatibleName = 18 + NameIDSampleText = 19 +) + +const ( + // A 32-bit encoding consists of a most-significant 16-bit Platform ID and a + // least-significant 16-bit Platform Specific ID. The magic numbers are + // specified at https://www.microsoft.com/typography/otspec/name.htm + unicodeEncodingBMPOnly = 0x00000003 // PID = 0 (Unicode), PSID = 3 (Unicode 2.0 BMP Only) + unicodeEncodingFull = 0x00000004 // PID = 0 (Unicode), PSID = 4 (Unicode 2.0 Full Repertoire) + microsoftSymbolEncoding = 0x00030000 // PID = 3 (Microsoft), PSID = 0 (Symbol) + microsoftUCS2Encoding = 0x00030001 // PID = 3 (Microsoft), PSID = 1 (UCS-2) + microsoftUCS4Encoding = 0x0003000a // PID = 3 (Microsoft), PSID = 10 (UCS-4) +) + +// An HMetric holds the horizontal metrics of a single glyph. +type HMetric struct { + AdvanceWidth, LeftSideBearing fixed.Int26_6 +} + +// A VMetric holds the vertical metrics of a single glyph. +type VMetric struct { + AdvanceHeight, TopSideBearing fixed.Int26_6 +} + +// A FormatError reports that the input is not a valid TrueType font. +type FormatError string + +func (e FormatError) Error() string { + return "freetype: invalid TrueType format: " + string(e) +} + +// An UnsupportedError reports that the input uses a valid but unimplemented +// TrueType feature. +type UnsupportedError string + +func (e UnsupportedError) Error() string { + return "freetype: unsupported TrueType feature: " + string(e) +} + +// u32 returns the big-endian uint32 at b[i:]. +func u32(b []byte, i int) uint32 { + return uint32(b[i])<<24 | uint32(b[i+1])<<16 | uint32(b[i+2])<<8 | uint32(b[i+3]) +} + +// u16 returns the big-endian uint16 at b[i:]. +func u16(b []byte, i int) uint16 { + return uint16(b[i])<<8 | uint16(b[i+1]) +} + +// readTable returns a slice of the TTF data given by a table's directory entry. +func readTable(ttf []byte, offsetLength []byte) ([]byte, error) { + offset := int(u32(offsetLength, 0)) + if offset < 0 { + return nil, FormatError(fmt.Sprintf("offset too large: %d", uint32(offset))) + } + length := int(u32(offsetLength, 4)) + if length < 0 { + return nil, FormatError(fmt.Sprintf("length too large: %d", uint32(length))) + } + end := offset + length + if end < 0 || end > len(ttf) { + return nil, FormatError(fmt.Sprintf("offset + length too large: %d", uint32(offset)+uint32(length))) + } + return ttf[offset:end], nil +} + +// parseSubtables returns the offset and platformID of the best subtable in +// table, where best favors a Unicode cmap encoding, and failing that, a +// Microsoft cmap encoding. offset is the offset of the first subtable in +// table, and size is the size of each subtable. +// +// If pred is non-nil, then only subtables that satisfy that predicate will be +// considered. +func parseSubtables(table []byte, name string, offset, size int, pred func([]byte) bool) ( + bestOffset int, bestPID uint32, retErr error) { + + if len(table) < 4 { + return 0, 0, FormatError(name + " too short") + } + nSubtables := int(u16(table, 2)) + if len(table) < size*nSubtables+offset { + return 0, 0, FormatError(name + " too short") + } + ok := false + for i := 0; i < nSubtables; i, offset = i+1, offset+size { + if pred != nil && !pred(table[offset:]) { + continue + } + // We read the 16-bit Platform ID and 16-bit Platform Specific ID as a single uint32. + // All values are big-endian. + pidPsid := u32(table, offset) + // We prefer the Unicode cmap encoding. Failing to find that, we fall + // back onto the Microsoft cmap encoding. + if pidPsid == unicodeEncodingBMPOnly || pidPsid == unicodeEncodingFull { + bestOffset, bestPID, ok = offset, pidPsid>>16, true + break + + } else if pidPsid == microsoftSymbolEncoding || + pidPsid == microsoftUCS2Encoding || + pidPsid == microsoftUCS4Encoding { + + bestOffset, bestPID, ok = offset, pidPsid>>16, true + // We don't break out of the for loop, so that Unicode can override Microsoft. + } + } + if !ok { + return 0, 0, UnsupportedError(name + " encoding") + } + return bestOffset, bestPID, nil +} + +const ( + locaOffsetFormatUnknown int = iota + locaOffsetFormatShort + locaOffsetFormatLong +) + +// A cm holds a parsed cmap entry. +type cm struct { + start, end, delta, offset uint32 +} + +// A Font represents a Truetype font. +type Font struct { + // Tables sliced from the TTF data. The different tables are documented + // at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html + cmap, cvt, fpgm, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, os2, prep, vmtx []byte + + cmapIndexes []byte + + // Cached values derived from the raw ttf data. + cm []cm + locaOffsetFormat int + nGlyph, nHMetric, nKern int + fUnitsPerEm int32 + ascent int32 // In FUnits. + descent int32 // In FUnits; typically negative. + bounds fixed.Rectangle26_6 // In FUnits. + // Values from the maxp section. + maxTwilightPoints, maxStorage, maxFunctionDefs, maxStackElements uint16 +} + +func (f *Font) parseCmap() error { + const ( + cmapFormat4 = 4 + cmapFormat12 = 12 + languageIndependent = 0 + ) + + offset, _, err := parseSubtables(f.cmap, "cmap", 4, 8, nil) + if err != nil { + return err + } + offset = int(u32(f.cmap, offset+4)) + if offset <= 0 || offset > len(f.cmap) { + return FormatError("bad cmap offset") + } + + cmapFormat := u16(f.cmap, offset) + switch cmapFormat { + case cmapFormat4: + language := u16(f.cmap, offset+4) + if language != languageIndependent { + return UnsupportedError(fmt.Sprintf("language: %d", language)) + } + segCountX2 := int(u16(f.cmap, offset+6)) + if segCountX2%2 == 1 { + return FormatError(fmt.Sprintf("bad segCountX2: %d", segCountX2)) + } + segCount := segCountX2 / 2 + offset += 14 + f.cm = make([]cm, segCount) + for i := 0; i < segCount; i++ { + f.cm[i].end = uint32(u16(f.cmap, offset)) + offset += 2 + } + offset += 2 + for i := 0; i < segCount; i++ { + f.cm[i].start = uint32(u16(f.cmap, offset)) + offset += 2 + } + for i := 0; i < segCount; i++ { + f.cm[i].delta = uint32(u16(f.cmap, offset)) + offset += 2 + } + for i := 0; i < segCount; i++ { + f.cm[i].offset = uint32(u16(f.cmap, offset)) + offset += 2 + } + f.cmapIndexes = f.cmap[offset:] + return nil + + case cmapFormat12: + if u16(f.cmap, offset+2) != 0 { + return FormatError(fmt.Sprintf("cmap format: % x", f.cmap[offset:offset+4])) + } + length := u32(f.cmap, offset+4) + language := u32(f.cmap, offset+8) + if language != languageIndependent { + return UnsupportedError(fmt.Sprintf("language: %d", language)) + } + nGroups := u32(f.cmap, offset+12) + if length != 12*nGroups+16 { + return FormatError("inconsistent cmap length") + } + offset += 16 + f.cm = make([]cm, nGroups) + for i := uint32(0); i < nGroups; i++ { + f.cm[i].start = u32(f.cmap, offset+0) + f.cm[i].end = u32(f.cmap, offset+4) + f.cm[i].delta = u32(f.cmap, offset+8) - f.cm[i].start + offset += 12 + } + return nil + } + return UnsupportedError(fmt.Sprintf("cmap format: %d", cmapFormat)) +} + +func (f *Font) parseHead() error { + if len(f.head) != 54 { + return FormatError(fmt.Sprintf("bad head length: %d", len(f.head))) + } + f.fUnitsPerEm = int32(u16(f.head, 18)) + f.bounds.Min.X = fixed.Int26_6(int16(u16(f.head, 36))) + f.bounds.Min.Y = fixed.Int26_6(int16(u16(f.head, 38))) + f.bounds.Max.X = fixed.Int26_6(int16(u16(f.head, 40))) + f.bounds.Max.Y = fixed.Int26_6(int16(u16(f.head, 42))) + switch i := u16(f.head, 50); i { + case 0: + f.locaOffsetFormat = locaOffsetFormatShort + case 1: + f.locaOffsetFormat = locaOffsetFormatLong + default: + return FormatError(fmt.Sprintf("bad indexToLocFormat: %d", i)) + } + return nil +} + +func (f *Font) parseHhea() error { + if len(f.hhea) != 36 { + return FormatError(fmt.Sprintf("bad hhea length: %d", len(f.hhea))) + } + f.ascent = int32(int16(u16(f.hhea, 4))) + f.descent = int32(int16(u16(f.hhea, 6))) + f.nHMetric = int(u16(f.hhea, 34)) + if 4*f.nHMetric+2*(f.nGlyph-f.nHMetric) != len(f.hmtx) { + return FormatError(fmt.Sprintf("bad hmtx length: %d", len(f.hmtx))) + } + return nil +} + +func (f *Font) parseKern() error { + // Apple's TrueType documentation (http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html) says: + // "Previous versions of the 'kern' table defined both the version and nTables fields in the header + // as UInt16 values and not UInt32 values. Use of the older format on the Mac OS is discouraged + // (although AAT can sense an old kerning table and still make correct use of it). Microsoft + // Windows still uses the older format for the 'kern' table and will not recognize the newer one. + // Fonts targeted for the Mac OS only should use the new format; fonts targeted for both the Mac OS + // and Windows should use the old format." + // Since we expect that almost all fonts aim to be Windows-compatible, we only parse the "older" format, + // just like the C Freetype implementation. + if len(f.kern) == 0 { + if f.nKern != 0 { + return FormatError("bad kern table length") + } + return nil + } + if len(f.kern) < 18 { + return FormatError("kern data too short") + } + version, offset := u16(f.kern, 0), 2 + if version != 0 { + return UnsupportedError(fmt.Sprintf("kern version: %d", version)) + } + + n, offset := u16(f.kern, offset), offset+2 + if n == 0 { + return UnsupportedError("kern nTables: 0") + } + // TODO: support multiple subtables. In practice, almost all .ttf files + // have only one subtable, if they have a kern table at all. But it's not + // impossible. Xolonium Regular (https://fontlibrary.org/en/font/xolonium) + // has 3 subtables. Those subtables appear to be disjoint, rather than + // being the same kerning pairs encoded in three different ways. + // + // For now, we'll use only the first subtable. + + offset += 2 // Skip the version. + length, offset := int(u16(f.kern, offset)), offset+2 + coverage, offset := u16(f.kern, offset), offset+2 + if coverage != 0x0001 { + // We only support horizontal kerning. + return UnsupportedError(fmt.Sprintf("kern coverage: 0x%04x", coverage)) + } + f.nKern, offset = int(u16(f.kern, offset)), offset+2 + if 6*f.nKern != length-14 { + return FormatError("bad kern table length") + } + return nil +} + +func (f *Font) parseMaxp() error { + if len(f.maxp) != 32 { + return FormatError(fmt.Sprintf("bad maxp length: %d", len(f.maxp))) + } + f.nGlyph = int(u16(f.maxp, 4)) + f.maxTwilightPoints = u16(f.maxp, 16) + f.maxStorage = u16(f.maxp, 18) + f.maxFunctionDefs = u16(f.maxp, 20) + f.maxStackElements = u16(f.maxp, 24) + return nil +} + +// scale returns x divided by f.fUnitsPerEm, rounded to the nearest integer. +func (f *Font) scale(x fixed.Int26_6) fixed.Int26_6 { + if x >= 0 { + x += fixed.Int26_6(f.fUnitsPerEm) / 2 + } else { + x -= fixed.Int26_6(f.fUnitsPerEm) / 2 + } + return x / fixed.Int26_6(f.fUnitsPerEm) +} + +// Bounds returns the union of a Font's glyphs' bounds. +func (f *Font) Bounds(scale fixed.Int26_6) fixed.Rectangle26_6 { + b := f.bounds + b.Min.X = f.scale(scale * b.Min.X) + b.Min.Y = f.scale(scale * b.Min.Y) + b.Max.X = f.scale(scale * b.Max.X) + b.Max.Y = f.scale(scale * b.Max.Y) + 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. +func (f *Font) Index(x rune) Index { + c := uint32(x) + for i, j := 0, len(f.cm); i < j; { + h := i + (j-i)/2 + cm := &f.cm[h] + if c < cm.start { + j = h + } else if cm.end < c { + i = h + 1 + } else if cm.offset == 0 { + return Index(c + cm.delta) + } else { + offset := int(cm.offset) + 2*(h-len(f.cm)+int(c-cm.start)) + return Index(u16(f.cmapIndexes, offset)) + } + } + return 0 +} + +// Name returns the Font's name value for the given NameID. It returns "" if +// there was an error, or if that name was not found. +func (f *Font) Name(id NameID) string { + x, platformID, err := parseSubtables(f.name, "name", 6, 12, func(b []byte) bool { + return NameID(u16(b, 6)) == id + }) + if err != nil { + return "" + } + offset, length := u16(f.name, 4)+u16(f.name, x+10), u16(f.name, x+8) + // Return the ASCII value of the encoded string. + // The string is encoded as UTF-16 on non-Apple platformIDs; Apple is platformID 1. + src := f.name[offset : offset+length] + var dst []byte + if platformID != 1 { // UTF-16. + if len(src)&1 != 0 { + return "" + } + dst = make([]byte, len(src)/2) + for i := range dst { + dst[i] = printable(u16(src, 2*i)) + } + } else { // ASCII. + dst = make([]byte, len(src)) + for i, c := range src { + dst[i] = printable(uint16(c)) + } + } + return string(dst) +} + +func printable(r uint16) byte { + if 0x20 <= r && r < 0x7f { + return byte(r) + } + return '?' +} + +// unscaledHMetric returns the unscaled horizontal metrics for the glyph with +// the given index. +func (f *Font) unscaledHMetric(i Index) (h HMetric) { + j := int(i) + if j < 0 || f.nGlyph <= j { + return HMetric{} + } + if j >= f.nHMetric { + p := 4 * (f.nHMetric - 1) + return HMetric{ + AdvanceWidth: fixed.Int26_6(u16(f.hmtx, p)), + LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, p+2*(j-f.nHMetric)+4))), + } + } + return HMetric{ + AdvanceWidth: fixed.Int26_6(u16(f.hmtx, 4*j)), + LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, 4*j+2))), + } +} + +// HMetric returns the horizontal metrics for the glyph with the given index. +func (f *Font) HMetric(scale fixed.Int26_6, i Index) HMetric { + h := f.unscaledHMetric(i) + h.AdvanceWidth = f.scale(scale * h.AdvanceWidth) + h.LeftSideBearing = f.scale(scale * h.LeftSideBearing) + return h +} + +// unscaledVMetric returns the unscaled vertical metrics for the glyph with +// the given index. yMax is the top of the glyph's bounding box. +func (f *Font) unscaledVMetric(i Index, yMax fixed.Int26_6) (v VMetric) { + j := int(i) + if j < 0 || f.nGlyph <= j { + return VMetric{} + } + if 4*j+4 <= len(f.vmtx) { + return VMetric{ + AdvanceHeight: fixed.Int26_6(u16(f.vmtx, 4*j)), + TopSideBearing: fixed.Int26_6(int16(u16(f.vmtx, 4*j+2))), + } + } + // The OS/2 table has grown over time. + // https://developer.apple.com/fonts/TTRefMan/RM06/Chap6OS2.html + // says that it was originally 68 bytes. Optional fields, including + // the ascender and descender, are described at + // http://www.microsoft.com/typography/otspec/os2.htm + if len(f.os2) >= 72 { + sTypoAscender := fixed.Int26_6(int16(u16(f.os2, 68))) + sTypoDescender := fixed.Int26_6(int16(u16(f.os2, 70))) + return VMetric{ + AdvanceHeight: sTypoAscender - sTypoDescender, + TopSideBearing: sTypoAscender - yMax, + } + } + return VMetric{ + AdvanceHeight: fixed.Int26_6(f.fUnitsPerEm), + TopSideBearing: 0, + } +} + +// VMetric returns the vertical metrics for the glyph with the given index. +func (f *Font) VMetric(scale fixed.Int26_6, i Index) VMetric { + // TODO: should 0 be bounds.YMax? + v := f.unscaledVMetric(i, 0) + v.AdvanceHeight = f.scale(scale * v.AdvanceHeight) + v.TopSideBearing = f.scale(scale * v.TopSideBearing) + return v +} + +// Kern returns the horizontal adjustment for the given glyph pair. A positive +// kern means to move the glyphs further apart. +func (f *Font) Kern(scale fixed.Int26_6, i0, i1 Index) fixed.Int26_6 { + if f.nKern == 0 { + return 0 + } + g := uint32(i0)<<16 | uint32(i1) + lo, hi := 0, f.nKern + for lo < hi { + i := (lo + hi) / 2 + ig := u32(f.kern, 18+6*i) + if ig < g { + lo = i + 1 + } else if ig > g { + hi = i + } else { + return f.scale(scale * fixed.Int26_6(int16(u16(f.kern, 22+6*i)))) + } + } + return 0 +} + +// Parse returns a new Font for the given TTF or TTC data. +// +// For TrueType Collections, the first font in the collection is parsed. +func Parse(ttf []byte) (font *Font, err error) { + return parse(ttf, 0) +} + +func parse(ttf []byte, offset int) (font *Font, err error) { + if len(ttf)-offset < 12 { + err = FormatError("TTF data is too short") + return + } + originalOffset := offset + magic, offset := u32(ttf, offset), offset+4 + switch magic { + case 0x00010000: + // No-op. + case 0x74746366: // "ttcf" as a big-endian uint32. + if originalOffset != 0 { + err = FormatError("recursive TTC") + return + } + ttcVersion, offset := u32(ttf, offset), offset+4 + if ttcVersion != 0x00010000 && ttcVersion != 0x00020000 { + err = FormatError("bad TTC version") + return + } + numFonts, offset := int(u32(ttf, offset)), offset+4 + if numFonts <= 0 { + err = FormatError("bad number of TTC fonts") + return + } + if len(ttf[offset:])/4 < numFonts { + err = FormatError("TTC offset table is too short") + return + } + // TODO: provide an API to select which font in a TrueType collection to return, + // not just the first one. This may require an API to parse a TTC's name tables, + // so users of this package can select the font in a TTC by name. + offset = int(u32(ttf, offset)) + if offset <= 0 || offset > len(ttf) { + err = FormatError("bad TTC offset") + return + } + return parse(ttf, offset) + default: + err = FormatError("bad TTF version") + return + } + n, offset := int(u16(ttf, offset)), offset+2 + offset += 6 // Skip the searchRange, entrySelector and rangeShift. + if len(ttf) < 16*n+offset { + err = FormatError("TTF data is too short") + return + } + f := new(Font) + // Assign the table slices. + for i := 0; i < n; i++ { + x := 16*i + offset + switch string(ttf[x : x+4]) { + case "cmap": + f.cmap, err = readTable(ttf, ttf[x+8:x+16]) + case "cvt ": + f.cvt, err = readTable(ttf, ttf[x+8:x+16]) + case "fpgm": + f.fpgm, err = readTable(ttf, ttf[x+8:x+16]) + case "glyf": + f.glyf, err = readTable(ttf, ttf[x+8:x+16]) + case "hdmx": + f.hdmx, err = readTable(ttf, ttf[x+8:x+16]) + case "head": + f.head, err = readTable(ttf, ttf[x+8:x+16]) + case "hhea": + f.hhea, err = readTable(ttf, ttf[x+8:x+16]) + case "hmtx": + f.hmtx, err = readTable(ttf, ttf[x+8:x+16]) + case "kern": + f.kern, err = readTable(ttf, ttf[x+8:x+16]) + case "loca": + f.loca, err = readTable(ttf, ttf[x+8:x+16]) + case "maxp": + f.maxp, err = readTable(ttf, ttf[x+8:x+16]) + case "name": + f.name, err = readTable(ttf, ttf[x+8:x+16]) + case "OS/2": + f.os2, err = readTable(ttf, ttf[x+8:x+16]) + case "prep": + f.prep, err = readTable(ttf, ttf[x+8:x+16]) + case "vmtx": + f.vmtx, err = readTable(ttf, ttf[x+8:x+16]) + } + if err != nil { + return + } + } + // Parse and sanity-check the TTF data. + if err = f.parseHead(); err != nil { + return + } + if err = f.parseMaxp(); err != nil { + return + } + if err = f.parseCmap(); err != nil { + return + } + if err = f.parseKern(); err != nil { + return + } + if err = f.parseHhea(); err != nil { + return + } + font = f + return +} diff --git a/vendor/github.com/golang/freetype/truetype/truetype_test.go b/vendor/github.com/golang/freetype/truetype/truetype_test.go new file mode 100644 index 0000000..a194f37 --- /dev/null +++ b/vendor/github.com/golang/freetype/truetype/truetype_test.go @@ -0,0 +1,400 @@ +// Copyright 2012 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. + +package truetype + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "os" + "strconv" + "strings" + "testing" + + "golang.org/x/image/font" + "golang.org/x/image/math/fixed" +) + +func parseTestdataFont(name string) (f *Font, testdataIsOptional bool, err error) { + b, err := ioutil.ReadFile(fmt.Sprintf("../testdata/%s.ttf", name)) + if err != nil { + // The "x-foo" fonts are optional tests, as they are not checked + // in for copyright or file size reasons. + return nil, strings.HasPrefix(name, "x-"), fmt.Errorf("%s: ReadFile: %v", name, err) + } + f, err = Parse(b) + if err != nil { + return nil, true, fmt.Errorf("%s: Parse: %v", name, err) + } + return f, false, nil +} + +func mkBounds(minX, minY, maxX, maxY fixed.Int26_6) fixed.Rectangle26_6 { + return fixed.Rectangle26_6{ + Min: fixed.Point26_6{ + X: minX, + Y: minY, + }, + Max: fixed.Point26_6{ + X: maxX, + Y: maxY, + }, + } +} + +// TestParse tests that the luxisr.ttf metrics and glyphs are parsed correctly. +// The numerical values can be manually verified by examining luxisr.ttx. +func TestParse(t *testing.T) { + f, _, err := parseTestdataFont("luxisr") + if err != nil { + t.Fatal(err) + } + if got, want := f.FUnitsPerEm(), int32(2048); got != want { + t.Errorf("FUnitsPerEm: got %v, want %v", got, want) + } + fupe := fixed.Int26_6(f.FUnitsPerEm()) + if got, want := f.Bounds(fupe), mkBounds(-441, -432, 2024, 2033); got != want { + t.Errorf("Bounds: got %v, want %v", got, want) + } + + i0 := f.Index('A') + i1 := f.Index('V') + if i0 != 36 || i1 != 57 { + t.Fatalf("Index: i0, i1 = %d, %d, want 36, 57", i0, i1) + } + if got, want := f.HMetric(fupe, i0), (HMetric{1366, 19}); got != want { + t.Errorf("HMetric: got %v, want %v", got, want) + } + if got, want := f.VMetric(fupe, i0), (VMetric{2465, 553}); got != want { + t.Errorf("VMetric: got %v, want %v", got, want) + } + if got, want := f.Kern(fupe, i0, i1), fixed.Int26_6(-144); got != want { + t.Errorf("Kern: got %v, want %v", got, want) + } + + g := &GlyphBuf{} + err = g.Load(f, fupe, i0, font.HintingNone) + if err != nil { + t.Fatalf("Load: %v", err) + } + g0 := &GlyphBuf{ + Bounds: g.Bounds, + Points: g.Points, + Ends: g.Ends, + } + g1 := &GlyphBuf{ + Bounds: mkBounds(19, 0, 1342, 1480), + Points: []Point{ + {19, 0, 51}, + {581, 1480, 1}, + {789, 1480, 51}, + {1342, 0, 1}, + {1116, 0, 35}, + {962, 410, 3}, + {368, 410, 33}, + {214, 0, 3}, + {428, 566, 19}, + {904, 566, 33}, + {667, 1200, 3}, + }, + Ends: []int{8, 11}, + } + if got, want := fmt.Sprint(g0), fmt.Sprint(g1); got != want { + t.Errorf("GlyphBuf:\ngot %v\nwant %v", got, want) + } +} + +func TestIndex(t *testing.T) { + testCases := map[string]map[rune]Index{ + "luxisr": { + ' ': 3, + '!': 4, + 'A': 36, + 'V': 57, + 'É': 101, + 'fl': 193, + '\u22c5': 385, + '中': 0, + }, + + // The x-etc test cases use those versions of the .ttf files provided + // by Ubuntu 14.04. See testdata/make-other-hinting-txts.sh for details. + + "x-arial-bold": { + ' ': 3, + '+': 14, + '0': 19, + '_': 66, + 'w': 90, + '~': 97, + 'Ä': 98, + 'fl': 192, + '½': 242, + 'σ': 305, + 'λ': 540, + 'ỹ': 1275, + '\u04e9': 1319, + '中': 0, + }, + "x-deja-vu-sans-oblique": { + ' ': 3, + '*': 13, + 'Œ': 276, + 'ω': 861, + '‡': 2571, + '⊕': 3110, + 'fl': 4728, + '\ufb03': 4729, + '\ufffd': 4813, + // TODO: '\U0001f640': ???, + '中': 0, + }, + "x-droid-sans-japanese": { + ' ': 0, + '\u3000': 3, + '\u3041': 25, + '\u30fe': 201, + '\uff61': 202, + '\uff67': 208, + '\uff9e': 263, + '\uff9f': 264, + '\u4e00': 265, + '\u557e': 1000, + '\u61b6': 2024, + '\u6ede': 3177, + '\u7505': 3555, + '\u81e3': 4602, + '\u81e5': 4603, + '\u81e7': 4604, + '\u81e8': 4605, + '\u81ea': 4606, + '\u81ed': 4607, + '\u81f3': 4608, + '\u81f4': 4609, + '\u91c7': 5796, + '\u9fa0': 6620, + '\u203e': 12584, + }, + "x-times-new-roman": { + ' ': 3, + ':': 29, + 'fl': 192, + 'Ŀ': 273, + '♠': 388, + 'Ŗ': 451, + 'Σ': 520, + '\u200D': 745, + 'Ẽ': 1216, + '\u04e9': 1319, + '中': 0, + }, + } + for name, wants := range testCases { + f, testdataIsOptional, err := parseTestdataFont(name) + if err != nil { + if testdataIsOptional { + t.Log(err) + } else { + t.Fatal(err) + } + continue + } + for r, want := range wants { + if got := f.Index(r); got != want { + t.Errorf("%s: Index of %q, aka %U: got %d, want %d", name, r, r, got, want) + } + } + } +} + +func TestName(t *testing.T) { + testCases := map[string]string{ + "luximr": "Luxi Mono", + "luxirr": "Luxi Serif", + "luxisr": "Luxi Sans", + } + + for name, want := range testCases { + f, testdataIsOptional, err := parseTestdataFont(name) + if err != nil { + if testdataIsOptional { + t.Log(err) + } else { + t.Fatal(err) + } + continue + } + if got := f.Name(NameIDFontFamily); got != want { + t.Errorf("%s: got %q, want %q", name, got, want) + } + } +} + +type scalingTestData struct { + advanceWidth fixed.Int26_6 + bounds fixed.Rectangle26_6 + points []Point +} + +// scalingTestParse parses a line of points like +// 213 -22 -111 236 555;-22 -111 1, 178 555 1, 236 555 1, 36 -111 1 +// The line will not have a trailing "\n". +func scalingTestParse(line string) (ret scalingTestData) { + next := func(s string) (string, fixed.Int26_6) { + t, i := "", strings.Index(s, " ") + if i != -1 { + s, t = s[:i], s[i+1:] + } + x, _ := strconv.Atoi(s) + return t, fixed.Int26_6(x) + } + + i := strings.Index(line, ";") + prefix, line := line[:i], line[i+1:] + + prefix, ret.advanceWidth = next(prefix) + prefix, ret.bounds.Min.X = next(prefix) + prefix, ret.bounds.Min.Y = next(prefix) + prefix, ret.bounds.Max.X = next(prefix) + prefix, ret.bounds.Max.Y = next(prefix) + + ret.points = make([]Point, 0, 1+strings.Count(line, ",")) + for len(line) > 0 { + s := line + if i := strings.Index(line, ","); i != -1 { + s, line = line[:i], line[i+1:] + for len(line) > 0 && line[0] == ' ' { + line = line[1:] + } + } else { + line = "" + } + s, x := next(s) + s, y := next(s) + s, f := next(s) + ret.points = append(ret.points, Point{X: x, Y: y, Flags: uint32(f)}) + } + return ret +} + +// scalingTestEquals is equivalent to, but faster than, calling +// reflect.DeepEqual(a, b), and also returns the index of the first non-equal +// element. It also treats a nil []Point and an empty non-nil []Point as equal. +// a and b must have equal length. +func scalingTestEquals(a, b []Point) (index int, equals bool) { + for i, p := range a { + if p != b[i] { + return i, false + } + } + return 0, true +} + +var scalingTestCases = []struct { + name string + size int +}{ + {"luxisr", 12}, + {"x-arial-bold", 11}, + {"x-deja-vu-sans-oblique", 17}, + {"x-droid-sans-japanese", 9}, + {"x-times-new-roman", 13}, +} + +func testScaling(t *testing.T, h font.Hinting) { + for _, tc := range scalingTestCases { + f, testdataIsOptional, err := parseTestdataFont(tc.name) + if err != nil { + if testdataIsOptional { + t.Log(err) + } else { + t.Error(err) + } + continue + } + hintingStr := "sans" + if h != font.HintingNone { + hintingStr = "with" + } + testFile, err := os.Open(fmt.Sprintf( + "../testdata/%s-%dpt-%s-hinting.txt", tc.name, tc.size, hintingStr)) + if err != nil { + t.Errorf("%s: Open: %v", tc.name, err) + continue + } + defer testFile.Close() + + wants := []scalingTestData{} + scanner := bufio.NewScanner(testFile) + if scanner.Scan() { + major, minor, patch := 0, 0, 0 + _, err := fmt.Sscanf(scanner.Text(), "freetype version %d.%d.%d", &major, &minor, &patch) + if err != nil { + t.Errorf("%s: version information: %v", tc.name, err) + } + if (major < 2) || (major == 2 && minor < 5) || (major == 2 && minor == 5 && patch < 1) { + t.Errorf("%s: need freetype version >= 2.5.1.\n"+ + "Try setting LD_LIBRARY_PATH=/path/to/freetype_built_from_src/objs/.libs/\n"+ + "and re-running testdata/make-other-hinting-txts.sh", + tc.name) + continue + } + } else { + t.Errorf("%s: no version information", tc.name) + continue + } + for scanner.Scan() { + wants = append(wants, scalingTestParse(scanner.Text())) + } + if err := scanner.Err(); err != nil && err != io.EOF { + t.Errorf("%s: Scanner: %v", tc.name, err) + continue + } + + glyphBuf := &GlyphBuf{} + for i, want := range wants { + if err = glyphBuf.Load(f, fixed.I(tc.size), Index(i), h); err != nil { + t.Errorf("%s: glyph #%d: Load: %v", tc.name, i, err) + continue + } + got := scalingTestData{ + advanceWidth: glyphBuf.AdvanceWidth, + bounds: glyphBuf.Bounds, + points: glyphBuf.Points, + } + + if got.advanceWidth != want.advanceWidth { + t.Errorf("%s: glyph #%d advance width:\ngot %v\nwant %v", + tc.name, i, got.advanceWidth, want.advanceWidth) + continue + } + + if got.bounds != want.bounds { + t.Errorf("%s: glyph #%d bounds:\ngot %v\nwant %v", + tc.name, i, got.bounds, want.bounds) + continue + } + + for i := range got.points { + got.points[i].Flags &= 0x01 + } + if len(got.points) != len(want.points) { + t.Errorf("%s: glyph #%d:\ngot %v\nwant %v\ndifferent slice lengths: %d versus %d", + tc.name, i, got.points, want.points, len(got.points), len(want.points)) + continue + } + if j, equals := scalingTestEquals(got.points, want.points); !equals { + t.Errorf("%s: glyph #%d:\ngot %v\nwant %v\nat index %d: %v versus %v", + tc.name, i, got.points, want.points, j, got.points[j], want.points[j]) + continue + } + } + } +} + +func TestScalingHintingNone(t *testing.T) { testScaling(t, font.HintingNone) } +func TestScalingHintingFull(t *testing.T) { testScaling(t, font.HintingFull) } -- cgit v1.2.3