コード例 #1
0
ファイル: truetype.go プロジェクト: maleck13/jigsaws
// 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,
	}
}
コード例 #2
0
ファイル: geom.go プロジェクト: maleck13/jigsaws
// 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)}
}
コード例 #3
0
ファイル: ftgc.go プロジェクト: maleck13/jigsaws
// 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
}
コード例 #4
0
ファイル: truetype_test.go プロジェクト: maleck13/jigsaws
// 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)
	}
}
コード例 #5
0
ファイル: truetype.go プロジェクト: maleck13/jigsaws
// 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)
}
コード例 #6
0
ファイル: geom.go プロジェクト: maleck13/jigsaws
// 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)}
}
コード例 #7
0
ファイル: face.go プロジェクト: maleck13/jigsaws
// 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
}
コード例 #8
0
ファイル: plan9font.go プロジェクト: maleck13/jigsaws
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
}
コード例 #9
0
ファイル: hint.go プロジェクト: maleck13/jigsaws
// 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))
}
コード例 #10
0
ファイル: truetype.go プロジェクト: maleck13/jigsaws
// 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))),
	}
}
コード例 #11
0
ファイル: raster.go プロジェクト: maleck13/jigsaws
// 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--
		}
	}
}
コード例 #12
0
ファイル: text.go プロジェクト: maleck13/jigsaws
// 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,
	}
}
コード例 #13
0
ファイル: truetype.go プロジェクト: maleck13/jigsaws
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
}
コード例 #14
0
ファイル: ftgc.go プロジェクト: maleck13/jigsaws
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
}
コード例 #15
0
ファイル: ftgc.go プロジェクト: maleck13/jigsaws
// 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
}
コード例 #16
0
ファイル: hint.go プロジェクト: maleck13/jigsaws
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
		}
	}
}
コード例 #17
0
ファイル: face.go プロジェクト: maleck13/jigsaws
// 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
}
コード例 #18
0
ファイル: hint.go プロジェクト: maleck13/jigsaws
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)))
	}
}
コード例 #19
0
ファイル: plan9font.go プロジェクト: maleck13/jigsaws
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
}
コード例 #20
0
ファイル: truetype.go プロジェクト: maleck13/jigsaws
// 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
}
コード例 #21
0
ファイル: truetype_test.go プロジェクト: maleck13/jigsaws
// 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
}
コード例 #22
0
ファイル: plan9font.go プロジェクト: maleck13/jigsaws
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
}
コード例 #23
0
ファイル: raster.go プロジェクト: maleck13/jigsaws
// 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
}
コード例 #24
0
ファイル: raster.go プロジェクト: maleck13/jigsaws
// 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--
		}
	}
}
コード例 #25
0
ファイル: face.go プロジェクト: maleck13/jigsaws
// 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)
}
コード例 #26
0
ファイル: hint.go プロジェクト: maleck13/jigsaws
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
		}
	}
}
コード例 #27
0
ファイル: hint.go プロジェクト: maleck13/jigsaws
// 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)
}
コード例 #28
0
ファイル: hint.go プロジェクト: maleck13/jigsaws
// 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))
}
コード例 #29
0
ファイル: ftpath.go プロジェクト: maleck13/jigsaws
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)})
}
コード例 #30
0
ファイル: hint.go プロジェクト: maleck13/jigsaws
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
}