// 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, } }
// pRot45CCW returns the vector p rotated counter-clockwise by 45 degrees. // // Note that the Y-axis grows downwards, so {1, 0}.Rot45CCW is {1/√2, -1/√2}. func pRot45CCW(p fixed.Point26_6) fixed.Point26_6 { // 181/256 is approximately 1/√2, or sin(π/4). px, py := int64(p.X), int64(p.Y) qx := (+px + py) * 181 / 256 qy := (-px + py) * 181 / 256 return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} }
// GetStringBounds returns the approximate pixel bounds of the string s at x, y. // The the left edge of the em square of the first character of s // and the baseline intersect at 0, 0 in the returned coordinates. // Therefore the top and left coordinates may well be negative. func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) { f, err := gc.loadCurrentFont() if err != nil { log.Println(err) return 0, 0, 0, 0 } top, left, bottom, right = 10e6, 10e6, -10e6, -10e6 cursor := 0.0 prev, hasPrev := truetype.Index(0), false for _, rune := range s { index := f.Index(rune) if hasPrev { cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index)) } if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil { log.Println(err) return 0, 0, 0, 0 } e0 := 0 for _, e1 := range gc.glyphBuf.Ends { ps := gc.glyphBuf.Points[e0:e1] for _, p := range ps { x, y := pointToF64Point(p) top = math.Min(top, y) bottom = math.Max(bottom, y) left = math.Min(left, x+cursor) right = math.Max(right, x+cursor) } } cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth) prev, hasPrev = index, true } return left, top, right, bottom }
// 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) } }
// 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) }
// pNorm returns the vector p normalized to the given length, or zero if p is // degenerate. func pNorm(p fixed.Point26_6, length fixed.Int26_6) fixed.Point26_6 { d := pLen(p) if d == 0 { return fixed.Point26_6{} } s, t := int64(length), int64(d) x := int64(p.X) * s / t y := int64(p.Y) * s / t return fixed.Point26_6{fixed.Int26_6(x), fixed.Int26_6(y)} }
// 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 }
func (f *subface) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) { r -= f.firstRune if r < 0 || f.n <= int(r) { return 0, false } return fixed.Int26_6(f.fontchars[r].width) << 6, true }
// 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)) }
// 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))), } }
// Add2 adds a quadratic segment to the current curve. func (r *Rasterizer) Add2(b, c fixed.Point26_6) { // Calculate nSplit (the number of recursive decompositions) based on how // 'curvy' it is. Specifically, how much the middle point b deviates from // (a+c)/2. dev := maxAbs(r.a.X-2*b.X+c.X, r.a.Y-2*b.Y+c.Y) / fixed.Int26_6(r.splitScale2) nsplit := 0 for dev > 0 { dev /= 4 nsplit++ } // dev is 32-bit, and nsplit++ every time we shift off 2 bits, so maxNsplit // is 16. const maxNsplit = 16 if nsplit > maxNsplit { panic("freetype/raster: Add2 nsplit too large: " + strconv.Itoa(nsplit)) } // Recursively decompose the curve nSplit levels deep. var ( pStack [2*maxNsplit + 3]fixed.Point26_6 sStack [maxNsplit + 1]int i int ) sStack[0] = nsplit pStack[0] = c pStack[1] = b pStack[2] = r.a for i >= 0 { s := sStack[i] p := pStack[2*i:] if s > 0 { // Split the quadratic curve p[:3] into an equivalent set of two // shorter curves: p[:3] and p[2:5]. The new p[4] is the old p[2], // and p[0] is unchanged. mx := p[1].X p[4].X = p[2].X p[3].X = (p[4].X + mx) / 2 p[1].X = (p[0].X + mx) / 2 p[2].X = (p[1].X + p[3].X) / 2 my := p[1].Y p[4].Y = p[2].Y p[3].Y = (p[4].Y + my) / 2 p[1].Y = (p[0].Y + my) / 2 p[2].Y = (p[1].Y + p[3].Y) / 2 // The two shorter curves have one less split to do. sStack[i] = s - 1 sStack[i+1] = s - 1 i++ } else { // Replace the level-0 quadratic with a two-linear-piece // approximation. midx := (p[0].X + 2*p[1].X + p[2].X) / 4 midy := (p[0].Y + 2*p[1].Y + p[2].Y) / 4 r.Add1(fixed.Point26_6{midx, midy}) r.Add1(p[0]) i-- } } }
// Extents returns the FontExtents for a font. // TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro func Extents(font *truetype.Font, size float64) FontExtents { bounds := font.Bounds(fixed.Int26_6(font.FUnitsPerEm())) scale := size / float64(font.FUnitsPerEm()) return FontExtents{ Ascent: float64(bounds.Max.Y) * scale, Descent: float64(bounds.Min.Y) * scale, Height: float64(bounds.Max.Y-bounds.Min.Y) * scale, } }
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 (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error { if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil { return err } e0 := 0 for _, e1 := range gc.glyphBuf.Ends { DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy) e0 = e1 } return nil }
// CreateStringPath creates a path from the string s at x, y, and returns the string width. // The text is placed so that the left edge of the em square of the first character of s // and the baseline intersect at x, y. The majority of the affected pixels will be // above and to the right of the point, but some may be below or to the left. // For example, drawing a string that starts with a 'J' in an italic font may // affect pixels below and left of the point. func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 { f, err := gc.loadCurrentFont() if err != nil { log.Println(err) return 0.0 } startx := x prev, hasPrev := truetype.Index(0), false for _, rune := range s { index := f.Index(rune) if hasPrev { x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index)) } err := gc.drawGlyph(index, x, y) if err != nil { log.Println(err) return startx - x } x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth) prev, hasPrev = index, true } return x - startx }
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 } } }
// 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 (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))) } }
func (f *subface) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) { r -= f.firstRune if r < 0 || f.n <= int(r) { return fixed.Rectangle26_6{}, 0, false } i := &f.fontchars[r+0] j := &f.fontchars[r+1] bounds = fixed.R( int(i.left), int(i.top)-f.ascent, int(i.left)+int(j.x-i.x), int(i.bottom)-f.ascent, ) return bounds, fixed.Int26_6(i.width) << 6, true }
// 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 }
// 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 }
func (f *subface) Glyph(dot fixed.Point26_6, r rune) ( dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) { r -= f.firstRune if r < 0 || f.n <= int(r) { return image.Rectangle{}, nil, image.Point{}, 0, false } i := &f.fontchars[r+0] j := &f.fontchars[r+1] minX := int(dot.X+32)>>6 + int(i.left) minY := int(dot.Y+32)>>6 + int(i.top) - f.ascent dr = image.Rectangle{ Min: image.Point{ X: minX, Y: minY, }, Max: image.Point{ X: minX + int(j.x-i.x), Y: minY + int(i.bottom) - int(i.top), }, } return dr, f.img, image.Point{int(i.x), int(i.top)}, fixed.Int26_6(i.width) << 6, true }
// Add1 adds a linear segment to the current curve. func (r *Rasterizer) Add1(b fixed.Point26_6) { x0, y0 := r.a.X, r.a.Y x1, y1 := b.X, b.Y dx, dy := x1-x0, y1-y0 // Break the 26.6 fixed point Y co-ordinates into integral and fractional // parts. y0i := int(y0) / 64 y0f := y0 - fixed.Int26_6(64*y0i) y1i := int(y1) / 64 y1f := y1 - fixed.Int26_6(64*y1i) if y0i == y1i { // There is only one scanline. r.scan(y0i, x0, y0f, x1, y1f) } else if dx == 0 { // This is a vertical line segment. We avoid calling r.scan and instead // manipulate r.area and r.cover directly. var ( edge0, edge1 fixed.Int26_6 yiDelta int ) if dy > 0 { edge0, edge1, yiDelta = 0, 64, 1 } else { edge0, edge1, yiDelta = 64, 0, -1 } x0i, yi := int(x0)/64, y0i x0fTimes2 := (int(x0) - (64 * x0i)) * 2 // Do the first pixel. dcover := int(edge1 - y0f) darea := int(x0fTimes2 * dcover) r.area += darea r.cover += dcover yi += yiDelta r.setCell(x0i, yi) // Do all the intermediate pixels. dcover = int(edge1 - edge0) darea = int(x0fTimes2 * dcover) for yi != y1i { r.area += darea r.cover += dcover yi += yiDelta r.setCell(x0i, yi) } // Do the last pixel. dcover = int(y1f - edge0) darea = int(x0fTimes2 * dcover) r.area += darea r.cover += dcover } else { // There are at least two scanlines. Apart from the first and last // scanlines, all intermediate scanlines go through the full height of // the row, or 64 units in 26.6 fixed point format. var ( p, q, edge0, edge1 fixed.Int26_6 yiDelta int ) if dy > 0 { p, q = (64-y0f)*dx, dy edge0, edge1, yiDelta = 0, 64, 1 } else { p, q = y0f*dx, -dy edge0, edge1, yiDelta = 64, 0, -1 } xDelta, xRem := p/q, p%q if xRem < 0 { xDelta -= 1 xRem += q } // Do the first scanline. x, yi := x0, y0i r.scan(yi, x, y0f, x+xDelta, edge1) x, yi = x+xDelta, yi+yiDelta r.setCell(int(x)/64, yi) if yi != y1i { // Do all the intermediate scanlines. p = 64 * dx fullDelta, fullRem := p/q, p%q if fullRem < 0 { fullDelta -= 1 fullRem += q } xRem -= q for yi != y1i { xDelta = fullDelta xRem += fullRem if xRem >= 0 { xDelta += 1 xRem -= q } r.scan(yi, x, edge0, x+xDelta, edge1) x, yi = x+xDelta, yi+yiDelta r.setCell(int(x)/64, yi) } } // Do the last scanline. r.scan(yi, x, edge0, x1, y1f) } // The next lineTo starts from b. r.a = b }
// Add3 adds a cubic segment to the current curve. func (r *Rasterizer) Add3(b, c, d fixed.Point26_6) { // Calculate nSplit (the number of recursive decompositions) based on how // 'curvy' it is. dev2 := maxAbs(r.a.X-3*(b.X+c.X)+d.X, r.a.Y-3*(b.Y+c.Y)+d.Y) / fixed.Int26_6(r.splitScale2) dev3 := maxAbs(r.a.X-2*b.X+d.X, r.a.Y-2*b.Y+d.Y) / fixed.Int26_6(r.splitScale3) nsplit := 0 for dev2 > 0 || dev3 > 0 { dev2 /= 8 dev3 /= 4 nsplit++ } // devN is 32-bit, and nsplit++ every time we shift off 2 bits, so // maxNsplit is 16. const maxNsplit = 16 if nsplit > maxNsplit { panic("freetype/raster: Add3 nsplit too large: " + strconv.Itoa(nsplit)) } // Recursively decompose the curve nSplit levels deep. var ( pStack [3*maxNsplit + 4]fixed.Point26_6 sStack [maxNsplit + 1]int i int ) sStack[0] = nsplit pStack[0] = d pStack[1] = c pStack[2] = b pStack[3] = r.a for i >= 0 { s := sStack[i] p := pStack[3*i:] if s > 0 { // Split the cubic curve p[:4] into an equivalent set of two // shorter curves: p[:4] and p[3:7]. The new p[6] is the old p[3], // and p[0] is unchanged. m01x := (p[0].X + p[1].X) / 2 m12x := (p[1].X + p[2].X) / 2 m23x := (p[2].X + p[3].X) / 2 p[6].X = p[3].X p[5].X = m23x p[1].X = m01x p[2].X = (m01x + m12x) / 2 p[4].X = (m12x + m23x) / 2 p[3].X = (p[2].X + p[4].X) / 2 m01y := (p[0].Y + p[1].Y) / 2 m12y := (p[1].Y + p[2].Y) / 2 m23y := (p[2].Y + p[3].Y) / 2 p[6].Y = p[3].Y p[5].Y = m23y p[1].Y = m01y p[2].Y = (m01y + m12y) / 2 p[4].Y = (m12y + m23y) / 2 p[3].Y = (p[2].Y + p[4].Y) / 2 // The two shorter curves have one less split to do. sStack[i] = s - 1 sStack[i+1] = s - 1 i++ } else { // Replace the level-0 cubic with a two-linear-piece approximation. midx := (p[0].X + 3*(p[1].X+p[2].X) + p[3].X) / 8 midy := (p[0].Y + 3*(p[1].Y+p[2].Y) + p[3].Y) / 8 r.Add1(fixed.Point26_6{midx, midy}) r.Add1(p[0]) i-- } } }
// 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) }
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 } } }
// 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) }
// 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)) }
func (liner FtLineBuilder) LineTo(x, y float64) { liner.Adder.Add1(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)}) }
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-- 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 }