"Fossies" - the Fresh Open Source Software archive

Member "xfstt-1.9/libfstt/raster_hints.cc" of archive xfstt-1.9.tar.gz:


/*
 * Hint Interpreter
 *
 * Copyright © 1997-1998 Herbert Duerr
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Softaware
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "ttf.h"
#include <math.h>	// we need sqrt() or hypot() for normalisation

#define FSHIFT 1.0
#define MEMSLACK 2

void
Rasterizer::initInterpreter()
{
	if (!grid_fitting || !ttFont)
		return;

	assert(SCANLINEPAD == sizeof(TYPESLP) << 3);
	assert(SCANLINEPAD == 1 << LOGSLP);
	assert(SCANLINEPAD == SLPMASK + 1);

	nPoints[0] = ttFont->maxpTable->maxTwilightPoints;
	if (sizePoints[0] < nPoints[0]) {
		if (sizePoints[0]) delete[] p[0];
		sizePoints[0] = MEMSLACK * nPoints[0];
		p[0] = new Point[sizePoints[0]];
	}

	Point nullpoint = {0, 0, 0, 0, 0};
	for (int i = 0; i < nPoints[0]; ++i)
		p[0][i] = nullpoint;

	if (sizeStack < ttFont->maxpTable->maxStackSize) {
		if (sizeStack) delete[] stackbase;
		sizeStack = MEMSLACK * ttFont->maxpTable->maxStackSize;
		stackbase = stack = new int[sizeStack];
	}

	if (ttFont->cvtTable && sizeCvt < ttFont->cvtTable->numVals()) {
		if (sizeCvt) delete[] cvt;
		sizeCvt	= MEMSLACK * ttFont->cvtTable->numVals();
		cvt = new int[sizeCvt];
	}

	if (sizeStor < ttFont->maxpTable->maxStorage) {
		if (sizeStor) delete[] stor;
		sizeStor = MEMSLACK * ttFont->maxpTable->maxStorage;
		stor = new int[sizeStor];
	}

	if (sizeFDefs < ttFont->maxpTable->maxFunctionDefs) {
		if (sizeFDefs) delete[] fdefs;
		sizeFDefs = MEMSLACK * ttFont->maxpTable->maxFunctionDefs;
		fdefs = new FDefs[sizeFDefs];
	}

	if (0 == sizeIDefs) {
		sizeIDefs = 256;
		idefs = new IDefs[sizeIDefs];
	}

	for (int j = 0; j < sizeIDefs; ++j)
		idefs[j].length = 0;

#ifdef DEBUG
	setbuffer(stdout, 0, 0);
#endif
	if (ttFont->fpgmTable) {
		gs.init(p);
		stack = stackbase;
		debug("--- fpgm start ---\n");
		execHints(ttFont->fpgmTable, 0, ttFont->fpgmTable->getLength());
		debug("--- fpgm done ---\n");
	}
}

void
Rasterizer::endInterpreter()
{
	if (sizeIDefs)		delete[] idefs;
	if (sizeFDefs)		delete[] fdefs;
	if (sizeStor)		delete[] stor;
	if (sizeCvt)		delete[] cvt;
	if (sizeStack)		delete[] stackbase;
/*
	if (sizeContours)	delete[] endPoints;
	if (sizePoints[1])	delete[] p[1];
*/
	if (sizePoints[0])	delete[] p[0];
}

void
Rasterizer::calcCVT()
{
	CvtTable* cvtTab = ttFont->cvtTable;
	if (cvtTab == 0)
		return;

	cvtTab->reset();
	int n = cvtTab->numVals();
	assert(n >= 0 && n <= sizeCvt);

	int scale = xx + yy;
	for (int i = 0; i < n; ++i) {
		int val = cvtTab->nextVal();
		// (ld 2048 = 11) - (SHIFT = 6) = 5
		cvt[i] = ((val * scale + 32) >> 6) << xxexp;
		debug("cvt[%3d] = %5d  -> %5d\n", i, val, cvt[i]);
	}

	if (ttFont->prepTable == 0)
		return;

	debug("=== starting glyph prep ===\n");
	grid_fitting = 1;
	gs.init(p);
	int preplen = ttFont->prepTable->getLength();
	stack = stackbase;
	execHints(ttFont->prepTable, 0, preplen);
	default_gs = gs;
	debug("=== glyph prep done ===\n");
}

void
GraphicsState::init(Point **p)
{
	flags		= X_TOUCHED;
	move_x		= UNITY2D14;
	move_y		= 0;

	p_vec_x		= UNITY2D14;
	p_vec_y		= 0;
	f_vec_x		= UNITY2D14;
	f_vec_y		= 0;
	dp_vec_x	= UNITY2D14;
	dp_vec_y	= 0;
	zp2 = zp1 = zp0	= p[1];
	rp2 = rp1 = rp0	= 1;
	loop		= 1;
	auto_flip	= 1;
	cvt_cut_in	= (17 << SHIFT) >> 4;
	round_state	= ROUND_GRID;
	round_phase	= 0;
	round_period	= (1 << SHIFT);
	round_thold	= 0;
	min_distance	= (1 << SHIFT);
	swidth_cut_in	= 0;
	swidth_value	= 0;
	delta_base	= 9;
	delta_shift	= 3;
	instr_control	= 0;
	dropout_control	= 1;
}

// some inlined utilities

/*inline*/ int
Rasterizer::round(int x) const
{
	int y = (x >= 0) ? +x : -x;
	switch (gs.round_state) {
	case ROUND_OFF:
		return x;
	case ROUND_GRID:
		y += 32;
		// fall through
	case ROUND_DOWN:
		y &= -64;
		break;
	case ROUND_UP:
		y = (y | 63) + 1;
		break;
	case ROUND_HALF:
		y = (y | 63) - 31;
		break;
	case ROUND_DOUBLE:
		y = (y + 16) & -32;
		break;
	case ROUND_SUPER:
		y -= gs.round_phase - gs.round_thold;
		y &= -gs.round_period;
		y += gs.round_phase;
		debug("\tsround(%d) = %d\t", x, (x<0)?-y:+y);
		break;
	case ROUND_SUPER45:
		y -= gs.round_phase - gs.round_thold;
		y -= y % gs.round_period;
		y += gs.round_phase;
		debug("\tsround45(%d) = %d\t", x, (x<0)?-y:+y);
		break;
	}
	if (y < 0) return 0;
	return (x >= 0) ? +y : -y;
}

inline int
GraphicsState::absNewMeasure(int dx11D6, int dy11D6)
{
	debug("\ndx = %d, dy = %d", dx11D6, dy11D6);
	debug(",\tpx = %d, py = %d", p_vec_x, p_vec_y);

	int dist = dx11D6 * p_vec_x + dy11D6 * p_vec_y + 0x2000;
	dist >>= 14;
	return dist;
}

inline int
GraphicsState::absOldMeasure(int dx11D6, int dy11D6)
{
	debug("\ndx = %d, dy = %d", dx11D6, dy11D6);
	debug(",\tdpx = %d, dpy = %d", dp_vec_x, dp_vec_y);

	int dist = dx11D6 * dp_vec_x + dy11D6 * dp_vec_y + 0x2000;
	dist >>= 14;
	return dist;
}

inline int
Rasterizer::newMeasure(const Point &p2, const Point &p1)
{
	int dist = gs.absNewMeasure(p2.xnow - p1.xnow, p2.ynow - p1.ynow);
	debug("\nnewMeasure p[%d]-p[%d] = %f",
		 &p2 - p[1], &p1 - p[1], dist / FSHIFT);
	
	return dist;
}

inline int
Rasterizer::oldMeasure(const Point &p2, const Point &p1)
{
	int dist = gs.absOldMeasure(p2.xold - p1.xold, p2.yold - p1.yold);
	debug("\noldMeasure p[%d]-p[%d] = %f",
		 &p2 - p[1], &p1 - p[1], dist / FSHIFT);
	return dist;
}

void
Rasterizer::newLine2vector(const Point &p2, const Point &p1, int &vx, int &vy)
{
	// XXX: how can it be that df1 or df2 are bigger than 16 bits ?
	float f1 = p2.xnow - p1.xnow;
	float f2 = p2.ynow - p1.ynow;

	if (f1 != 0.0f || f2 != 0.0f) {
#if 1
		float f3 = sqrt(UNITY2D14 * UNITY2D14 / (f1 * f1 + f2 * f2));
#else
		float f3 = UNITY2D14 / hypot(f1, f2);
#endif
		vx = (int)(f1 * f3);
		vy = (int)(f2 * f3);
	} else {
		vx = 0;
		vy = 0;
	}
	debug("\t(%d %d) - ", p2.xnow, p2.ynow);
	debug("(%d %d)", p1.xnow, p1.ynow);
	debug("\nvx vy = %f %f", vx / FSHIFT, vy / FSHIFT);
}

void
Rasterizer::oldLine2vector(const Point &p2, const Point &p1, int &vx, int &vy)
{
	// XXX: how can it be that df1 or df2 are bigger than 16 bits ?
	float f1 = p2.xold - p1.xold;
	float f2 = p2.yold - p1.yold;

	if (f1 != 0.0f || f2 != 0.0f) {
#if 1
		float f3 = sqrt(UNITY2D14 * UNITY2D14 / (f1 * f1 + f2 * f2));
#else
		float f3 = UNITY2D14 / hypot(f1, f2);
#endif
		vx = (int)(f1 * f3);
		vy = (int)(f2 * f3);
	} else {
		vx = 0;
		vy = 0;
	}
	debug("\t(%d %d) - ", p2.xold, p2.yold);
	debug("(%d %d)", p1.xold, p1.yold);
	debug("\nvx vy = %f %f", vx / FSHIFT, vy / FSHIFT);
}

inline void
GraphicsState::movePoint(Point &pp, int len11D6)
{
	debug("\nmovePoint by %f", len11D6 / FSHIFT);
	debug("\t(%d %d)", pp.xnow, pp.ynow);

	pp.xnow += (len11D6 * move_x) >> 14;
	pp.ynow += (len11D6 * move_y) >> 14;
	pp.flags |= flags;

	debug("\t-> (%d %d)\n", pp.xnow, pp.ynow);
}

void
GraphicsState::recalc()
{
	flags = f_vec_x ? X_TOUCHED : 0;
	if (f_vec_y) flags |= Y_TOUCHED;

	int fp_cross = absNewMeasure(f_vec_x, f_vec_y);
	if (fp_cross) {
		move_x = (f_vec_x << 14) / fp_cross;
		move_y = (f_vec_y << 14) / fp_cross;
	} else
		move_x = move_y = 0;
}

// interpreter

inline void
Rasterizer::execOpcode(RandomAccessFile* const f)
{
	// optimisation question: how does one convince
	// g++ to keep the "this" pointer in a register?
	// all that is needed to make hinting really fast
	// is keeping these variables in registers
	// 1) this 2) m 3) n,
	// keeping i and pp in registers would help too.
	//
	// While I'm at it: Why the heck does g++/egcs/pgcc take
	// a much bigger (up to 5K) stack frame than it really
	// needs? MS VC++ code only grabs 40 bytes!

	register int m;
	register Point *pp;
	int n;

	assert(stack >= stackbase);

	int opc = f->readUByte();
	debug("\n::%05X %02X\t", f->fileOffset() - 1, opc);
	switch (opc) {

	// pushing onto the stack

	case NPUSHB:
		opc = f->readUByte() + PUSHB00 - 1;
		// fall through
	case PUSHB00: case PUSHB01:
	case PUSHB02: case PUSHB03:
	case PUSHB04: case PUSHB05:
	case PUSHB06: case PUSHB07:
		m = opc - (PUSHB00 - 1);
		debug("PUSHB * %d\n>\t\t", m);
		while (--m >= 0) {
			++stack;
			*stack = f->readUByte();
			debug("%d,\t", *stack);
			if ((m & 7) == 0)
				debug("\n>\t\t");
		}
		break;

	case NPUSHW:
		opc = f->readUByte() + PUSHW00 - 1;
		// fall through
	case PUSHW00: case PUSHW01:
	case PUSHW02: case PUSHW03:
	case PUSHW04: case PUSHW05:
	case PUSHW06: case PUSHW07:
		m = opc - (PUSHW00 - 1);
		debug("PUSHW * %d\n>\t\t", m);
		while (--m >= 0) {
			*(++stack) = f->readSShort();
			debug("%d,\t", *stack);
			if ((m & 7) == 0)
				debug("\n>\t\t");
		}
		break;

	// accessing the storage area

	case RS:
		m = *stack;
		assert(m >= 0 && m < sizeStor);
		*stack = stor[m];
		debug("RS store[%d] = %d", m, *stack);
		break;
	case WS:
		m = *(stack--);
		n = *(stack--);
		debug("WS %d -> store[%d]", m, n);
		assert(n >= 0 && n < sizeStor);
		stor[n] = m;
		break;

	// accessing the control value table

	case WCVTP:
		m = *(stack--);
		n = *(stack--);
		assert(n >= 0 && n < sizeCvt);
		cvt[n] = m;
		debug("WCVTP %d -> cvt[%d] = %d", m, n, cvt[n]);
		break;
	case WCVTF:
		m = *(stack--);
		n = *(stack--);
		assert(n >= 0 && n < sizeCvt);
		//XXX: how does one scale a scalar with the ((xx,xy),(yx,yy)) matrix???
		cvt[n] = ((m * (xx + yy) + 32) >> 6) << xxexp;
		debug("#WCVTF %d -> cvt[%d] = %d\n", m, n, cvt[n]);
		break;
	case RCVT:
		m = *stack;
		assert(m >= 0 && m < sizeCvt);
		*stack = cvt[m];
		debug("RCVT cvt[%d] = %d", m, *stack);
		break;

	// accessing the graphics state

	case SVTCA0:
		debug("SVTCA0");
		gs.dp_vec_x = gs.p_vec_x = gs.f_vec_x = 0;
		gs.dp_vec_y = gs.p_vec_y = gs.f_vec_y = UNITY2D14;
		gs.move_x = 0;
		gs.move_y = UNITY2D14;
		gs.flags = Y_TOUCHED;
		break;
	case SVTCA1:
		debug("SVTCA1");
		gs.dp_vec_x = gs.p_vec_x = gs.f_vec_x = UNITY2D14;
		gs.dp_vec_y = gs.p_vec_y = gs.f_vec_y = 0;
		gs.move_x = UNITY2D14;
		gs.move_y = 0;
		gs.flags = X_TOUCHED;
		break;
	case SPVTCA0:
		debug("SPVTCA0");
		gs.dp_vec_x = gs.p_vec_x = 0;
		gs.dp_vec_y = gs.p_vec_y = UNITY2D14;
		if (gs.f_vec_y) {
			gs.move_x = (gs.f_vec_x << 14) / gs.f_vec_y;
			gs.move_y = (gs.f_vec_y << 14) / gs.f_vec_y;
		} else
			gs.move_x = gs.move_y = 0;
		break;
	case SPVTCA1:
		debug("SPVTCA1");
		gs.dp_vec_x = gs.p_vec_x = UNITY2D14;
		gs.dp_vec_y = gs.p_vec_y = 0;
		if (gs.f_vec_x) {
			gs.move_x = (gs.f_vec_x << 14) / gs.f_vec_x;
			gs.move_y = (gs.f_vec_y << 14) / gs.f_vec_x;
		} else
			gs.move_x = gs.move_y = 0;
		break;
	case SFVTCA0:
		debug("SFVTCA0");
		gs.f_vec_x = 0;
		gs.f_vec_y = UNITY2D14;
		gs.move_x = 0;
		if (gs.p_vec_y) {
			gs.move_y = (UNITY2D14 << 14) / gs.p_vec_y;
			gs.flags = Y_TOUCHED;
		} else
			gs.move_y = gs.flags = 0;
		break;
	case SFVTCA1:
		debug("SFVTCA1");
		gs.f_vec_x = UNITY2D14;
		gs.f_vec_y = 0;
		if (gs.p_vec_x) {
			gs.move_x = (UNITY2D14 << 14) / gs.p_vec_x;
			gs.flags = X_TOUCHED;
		} else
			gs.move_x = gs.flags = 0;
		gs.move_y = 0;
		break;
	case SDPVTL0:
		m = *(stack--);
		n = *(stack--);
		debug("SDPVTL0 p[%d] p[%d]", m, n);
		oldLine2vector(gs.zp1[n], gs.zp2[m], gs.dp_vec_x, gs.dp_vec_y);
		newLine2vector(gs.zp1[n], gs.zp2[m], gs.p_vec_x, gs.p_vec_y);
		gs.recalc();
		break;
	case SDPVTL1:
		m = *(stack--);
		n = *(stack--);
		debug("SDPVTL1 p[%d] p[%d]", m, n);
		oldLine2vector(gs.zp1[n], gs.zp2[m], gs.dp_vec_y, gs.dp_vec_x);
		gs.dp_vec_x = -gs.dp_vec_x;
		newLine2vector(gs.zp1[n], gs.zp2[m], gs.p_vec_y, gs.p_vec_x);
		gs.p_vec_x = -gs.p_vec_x;
		gs.recalc();
		break;
	case SPVTL0:
		m = *(stack--);
		n = *(stack--);
		debug("SPVTL0 p[%d] p[%d]", m, n);
		newLine2vector(gs.zp1[n], gs.zp2[m], gs.p_vec_x, gs.p_vec_y);
		gs.dp_vec_x = gs.p_vec_x;
		gs.dp_vec_y = gs.p_vec_y;
		gs.recalc();
		break;
	case SPVTL1:
		m = *(stack--);
		n = *(stack--);
		debug("SPVTL1 p[%d] p[%d]\t", m, n);
		newLine2vector(gs.zp1[n], gs.zp2[m], gs.p_vec_y, gs.p_vec_x);
		gs.dp_vec_y = gs.p_vec_y = -gs.p_vec_y;
		gs.dp_vec_x = gs.p_vec_x;
		gs.recalc();
		break;
	case SFVTL0:
		m = *(stack--);
		n = *(stack--);
		debug("SFVTL0 p[%d] p[%d]\t", m, n);
		newLine2vector(gs.zp1[n], gs.zp2[m], gs.f_vec_x, gs.f_vec_y);
		gs.recalc();
		break;
	case SFVTL1:
		m = *(stack--);
		n = *(stack--);
		debug("SFVTL1 p[%d] p[%d]", m, n);
		newLine2vector(gs.zp1[n], gs.zp2[m], gs.f_vec_y, gs.f_vec_x);
		gs.f_vec_y = -gs.f_vec_y;
		gs.recalc();
		break;
	case SFVTPV:
		debug("SFVTPV");
		gs.f_vec_x = gs.p_vec_x;
		gs.f_vec_y = gs.p_vec_y;
		gs.recalc();
		break;
	case SPVFS:
		gs.dp_vec_y = gs.p_vec_y = *(stack--);
		gs.dp_vec_x = gs.p_vec_x = *(stack--);
		debug("#SPVFS = %d %d", gs.p_vec_x, gs.p_vec_y);
		gs.recalc();
		break;
	case SFVFS:
		gs.f_vec_y = *(stack--);
		gs.f_vec_x = *(stack--);
		debug("#SFVFS = %d %d", gs.f_vec_x, gs.f_vec_y);
		gs.recalc();
		break;
	case GPV:
		*(++stack) = gs.p_vec_x;
		*(++stack) = gs.p_vec_y;
		debug("GPV = %d %d", gs.p_vec_x, gs.p_vec_y);
		break;
	case GFV:
		*(++stack) = gs.f_vec_x;
		*(++stack) = gs.f_vec_y;
		debug("GFV = %d %d", gs.f_vec_x, gs.f_vec_y);
		break;
	case SRP0:
		gs.rp0 = *(stack--);
		debug("SRP0 p[%d]", gs.rp0);
		break;
	case SRP1:
		gs.rp1 = *(stack--);
		debug("SRP1 p[%d]", gs.rp1);
		break;
	case SRP2:
		gs.rp2 = *(stack--);
		debug("SRP2 p[%d]", gs.rp2);
		break;
	case SZP0:
		m = *(stack--);
		assert(m >= 0 && m <= 1);
		gs.zp0 = p[m];
		debug("SZP0 %d", m);
		break;
	case SZP1:
		m = *(stack--);
		assert(m >= 0 && m <= 1);
		gs.zp1 = p[m];
		debug("SZP1 %d", m);
		break;
	case SZP2:
		m = *(stack--);
		assert(m >= 0 && m <= 1);
		gs.zp2 = p[m];
		debug("SZP2 %d", m);
		break;
	case SZPS:
		m = *(stack--);
		debug("SZPS %d", m);
		assert(m >= 0 && m <= 1);
		gs.zp2 = gs.zp1 = gs.zp0 = p[m];
		break;
	case RTHG:
		debug("RTHG");
		gs.round_state = ROUND_HALF;
		break;
	case RTG:
		debug("RTG");
		gs.round_state = ROUND_GRID;
		break;
	case RTDG:
		debug("RTDG");
		gs.round_state = ROUND_DOUBLE;
		break;
	case RDTG:
		debug("RDTG");
		gs.round_state = ROUND_DOWN;
		break;
	case RUTG:
		debug("RUTG");
		gs.round_state = ROUND_UP;
		break;
	case ROFF:
		debug("ROFF");
		gs.round_state = ROUND_OFF;
		break;
	case SROUND:
		m = *(stack--);
		debug("SROUND %d %d %d", (m >> 6) & 3, (m >> 4) & 3, m);
		gs.round_state = ROUND_SUPER;
		n = (m >> 6) & 3;
		gs.round_period = 0x20 << n;
		n = (m >> 4) & 3;
		if (n == 3) gs.round_phase = 48;
		else gs.round_phase = (gs.round_period >> 2) * n;
		m &= 0x0F;
		if (m)
			gs.round_thold = (gs.round_period >> 3) * (m - 4);
		else
			gs.round_thold = gs.round_period - 1;
		debug("-> period 0x%02X, thold 0x%02X, phase 0x%02X",
		      gs.round_period, gs.round_thold, gs.round_phase);
		break;
	case S45ROUND:
		m = *(stack--);
		debug("SROUND45 %d %d %d", (m >> 6) & 3, (m >> 4) & 3, m);
		gs.round_state = ROUND_SUPER45;
		gs.round_period = 1444 >> (7 - ((m >> 6) & 3));
		gs.round_phase = (gs.round_period * (m & 0x30)) >> 6;
		m &= 0x0F;
		if (m)
			gs.round_thold = (gs.round_period * (m - 4)) >> 3;
		else
			gs.round_thold = gs.round_period - 1;
		debug("-> period 0x%02X, thold 0x%02X, phase 0x%02X",
		      gs.round_period, gs.round_thold, gs.round_phase);
		break;
	case SLOOP:
		gs.loop = *(stack--);
		debug("SLOOP %d", gs.loop);
		break;
	case SMD:
		gs.min_distance = *(stack--);
		debug("SMD %d", gs.min_distance);
		break;
	case INSTCTRL:
		gs.instr_control = *(stack--);
		m = *(stack--);
		debug("###INSTCTRL %d %d", gs.instr_control, m);
		if (gs.instr_control == 1)
			if (m && grid_fitting >= 0)
				grid_fitting = -grid_fitting;
		break;
	case SCANCTRL:
		m = *(stack--);
		gs.dropout_control = 0;
		if (m & 0x0100 && mppem <= (m & 0xff))	gs.dropout_control = 1;
		if (m & 0x0200 && !xy && !yx)		gs.dropout_control = 1;
		if (m & 0x0400 && xx != yy)		gs.dropout_control = 1;
		if (m & 0x0800 && mppem > (m & 0xff))	gs.dropout_control = 0;
		if (m & 0x1000 && (xy || yx))		gs.dropout_control = 0;
		if (m & 0x2000 && xx == yy)		gs.dropout_control = 0;
		debug("SCANCTRL %04X -> %d", m, gs.dropout_control);
		break;
	case SCANTYPE:
		m = *(stack--);
		debug("###SCANTYPE %d", m);
		// TODO
		break;
	case SCVTCI:
		gs.cvt_cut_in = *(stack--);
		debug("SCVTCI %d", gs.cvt_cut_in);
		break;
	case SSWCI:
		gs.swidth_cut_in = *(stack--);
		debug("SSWCI %d", gs.swidth_cut_in);
		break;
	case SSW:
		gs.swidth_value = *(stack--);
		debug("SSW %d", gs.swidth_value);
		break;
	case FLIPON:
		gs.auto_flip = 1;
		debug("FLIPON");
		break;
	case FLIPOFF:
		gs.auto_flip = 0;
		debug("FLIPOFF");
		break;
	case SANGW:
		// angle_weight is obsolete!
		m = *(stack--);
		debug("SANGW %d is obsolete", m);
		break;
	case SDB:
		gs.delta_base = *(stack--);
		debug("SDB %d", gs.delta_base);
		break;
	case SDS:
		gs.delta_shift = *(stack--);
		debug("SDS %d", gs.delta_shift);
		break;

	// do some measurements

	case GC0:
		pp = &gs.zp2[*stack];
		debug("GC0 p[%d][%d]\t", gs.zp2 == p[1], pp - gs.zp2);
		*stack = gs.absNewMeasure(pp->xnow, pp->ynow);
		debug("\t=> %d", *stack);
		break;
	case GC1:
		pp = &gs.zp2[*stack];
		debug("GC1 p[%d][%d]\t", gs.zp2 == p[1], pp - gs.zp2);
		*stack = gs.absOldMeasure(pp->xold, pp->yold);
		debug("\t=> %d", *stack);
		break;
	case SCFS:
		// move point along freedom vector, so that
		// projection gets desired length
		m = *(stack--);
		n = *(stack--);
		debug("SCFS p[%d][%d] to %f", gs.zp2 == p[1], n, m / FSHIFT);
		pp = &gs.zp2[n];
		if (gs.zp2 == p[1]) {
			int i = gs.absNewMeasure(pp->xnow, pp->ynow);
			gs.movePoint(*pp, m - i);
		} else { // magic in the twilight zone
			pp->xold = pp->xnow = (m * gs.p_vec_x) >> 14;
			pp->yold = pp->ynow = (m * gs.p_vec_y) >> 14;
		}
		break;
	case MD0:
		m = *(stack--);
		n = *stack;
		debug("MD0 p[%d][%d] ", gs.zp1 == p[1], m);
		debug("- p[%d][%d]", gs.zp0 == p[1], n);
		*stack = newMeasure(gs.zp0[n], gs.zp1[m]);
		break;
	case MD1:
		m = *(stack--);
		n = *stack;
		debug("MD1 p[%d][%d] ", gs.zp1 == p[1], m);
		debug("- p[%d][%d]", gs.zp0 == p[1], n);
		*stack = oldMeasure(gs.zp0[n], gs.zp1[m]); // Thanks David
		break;
	case MPPEM:
		debug("MPPEM\t");
		m = gs.absNewMeasure(mppemx, mppemy);
		if (m < 0)
			m = -m;
		*(++stack) = m;
		debug("\t => mppem = %d", m);
		break;
	case MPS:
		*(++stack) = pointSize;
		debug("MPS %d", *stack);
		break;

	// outline manipulation

	case FLIPPT:
		for (m = gs.loop; --m >= 0;) {
			n = *(stack--);
			debug("FLIPPT * %d p[%d][%d]", m, gs.zp0 == p[1], n);
			gs.zp1[n].flags ^= ON_CURVE;
		}
		gs.loop = 1;
		break;
	case FLIPRGON:
		m = *(stack--);
		n = *(stack--);
		debug("FLIPRGON p[%d][%d .. %d]", gs.zp0 == p[1], n, m);
		pp = &gs.zp1[n];
		for (m -= n-1; --m >= 0; ++pp)
			pp->flags |= ON_CURVE;
		break;
	case FLIPRGOFF:
		m = *(stack--);
		n = *(stack--);
		debug("FLIPRGOFF p[%d][%d .. %d]", gs.zp0 == p[1], n, m);
		pp = &gs.zp1[n];
		for (m -= n-1; --m >= 0; ++pp)
			pp->flags &= ~ON_CURVE;
		break;
	case SHP0:
	case SHP1:
		pp = (opc & 1) ? &gs.zp0[gs.rp1] : &gs.zp1[gs.rp2];
		n = gs.absNewMeasure(pp->xnow - pp->xold, pp->ynow - pp->yold);
		for (m = gs.loop; --m >= 0;) {
			int i = *(stack--);
			debug("SHP * %d p[%d], rp = p[%d]", m, i, pp-p[1]);
			debug(" moved by %f", n / FSHIFT);
			gs.movePoint(gs.zp2[i], n);
		}
		gs.loop = 1;
		break;
	case SHC0:
	case SHC1:
		{
		m = *(stack--);
		assert(m >= 0 && m < sizeContours);
		pp = (opc & 1) ? &gs.zp0[gs.rp1] : &gs.zp1[gs.rp2];
		debug("SHC%d rp[%d]", opc & 1, pp - p[1]);
		n = gs.absNewMeasure(pp->xnow - pp->xold, pp->ynow - pp->yold);
		int i = (m <= 0) ? 0 : endPoints[m-1] + 1;
		m = (gs.zp2 == p[0]) ? nPoints[0] : endPoints[m];
		for (; i <= m; ++i) {
			if (pp == &gs.zp2[i])
				continue;
			debug("SHC%d p[%d] by %f\n", opc & 1, i, n / FSHIFT);
			gs.movePoint(gs.zp2[i], n);
		}
		}
		break;
	case SHZ0:
	case SHZ1:
		m = *(stack--);
		debug("SHZ%d rp = p[%d]\n ", opc & 1,
		      (opc & 1) ? gs.rp1 : gs.rp2);
		pp = (opc & 1) ? &gs.zp0[gs.rp1] : &gs.zp1[gs.rp2];
		n = gs.absNewMeasure(pp->xnow - pp->xold, pp->ynow - pp->yold);
		assert(m >= 0 && m <= 1);
		for (Point *pp1 = p[m], *pp2 = pp1 + nPoints[m]; pp1 < pp2; ++pp1) {
			if (pp1 == pp) continue;
			debug("\nSHZ p[%d] by %f", pp1 - p[m], n / FSHIFT);
			debug("\t(%d %d) -> ", pp1->xnow, pp1->ynow);
			pp1->xnow += (n * gs.move_x) >> 14;
			pp1->ynow += (n * gs.move_y) >> 14;
			debug("(%d %d)\n", pp1->xnow, pp1->ynow);
		}
		break;
	case SHPIX:
		m = *(stack--);
		for (n = gs.loop; --n >= 0;) {
			int i = *(stack--);
			debug("SHPIX * %d p[%d][%d] ", n, gs.zp2 == p[1], i);
			debug("by %f", m / FSHIFT);
			pp = &gs.zp2[i];
			debug("\n%d %d ->", pp->xnow, pp->ynow);
			pp->xnow += (m * gs.f_vec_x) >> 14;
			pp->ynow += (m * gs.f_vec_y) >> 14;
			pp->flags |= gs.flags;
			debug("\t%d %d", pp->xnow, pp->ynow);
		}
		gs.loop = 1;
		break;
	case MSIRP0:
	case MSIRP1:
		m = *(stack--);
		n = *(stack--);
		gs.rp2 = n;
		gs.rp1 = gs.rp0;
		debug("MSIRP%d p[%d][%d] ", opc & 1, gs.zp1 == p[1], n);
		debug("to %f, rp = p[%d][%d]", m / FSHIFT,
		      gs.zp0 == p[1], gs.rp0);
		if (gs.zp1 == p[1]) {
			int i = newMeasure(p[1][n], gs.zp0[gs.rp0]);
			gs.movePoint(p[1][n], m - i);
		} else {	// magic in the twilight zone
			pp = &p[0][n];
			pp->xnow = pp->xold =
				((m * gs.p_vec_x) >> 14) + gs.zp0[gs.rp0].xnow;
			pp->ynow = pp->yold =
				((m * gs.p_vec_y) >> 14) + gs.zp0[gs.rp0].ynow;
		}
		if (opc & 1)
			gs.rp0 = n;
		break;
	case MDAP0:
	case MDAP1:
		gs.rp0 = gs.rp1 = *(stack--);
		debug("MDAP%d p[%d]", opc & 1, gs.rp0);
		pp = &gs.zp0[gs.rp0];
		debug("\nxy %d %d", pp->xnow, pp->ynow);
		pp->flags |= gs.flags;
		if (opc & 1) {
#if 0 // XXX
			if (gs.f_vec_x)
				pp->xnow = round(pp->xnow);
			if (gs.f_vec_y)
				pp->ynow = round(pp->ynow);
			debug("\t-> %d %d", pp->xnow, pp->ynow);
#else
			m = gs.absNewMeasure(pp->xnow, pp->ynow);
			gs.movePoint(*pp, round(m) - m);
			debug("\t-> %d %d", pp->xnow, pp->ynow);
#endif
		}
		break;
	case MIAP0:
	case MIAP1:
		m = *(stack--);
		gs.rp0 = gs.rp1 = *(stack--);
		debug("MIAP%d p[%d][%d] ", opc & 1, gs.zp0 == p[1], gs.rp0);
		debug("to cvt[%d] = ", m);
		m = cvt[m];
		debug("%f", m / FSHIFT);
		if (gs.zp0 != p[0]) {
			pp = &p[1][gs.rp0];
			int i = gs.absNewMeasure(pp->xnow, pp->ynow);
			if (opc & 0x01) {
				if (((m > i) ? m-i : i-m) > gs.cvt_cut_in)
					m = i;
				m = round(m);
			}
			debug("\nabsdist = %f", i / FSHIFT);
			gs.movePoint(gs.zp0[gs.rp0], m - i);
		} else {	// magic in the twilight zone
			pp = &p[0][gs.rp0];
			pp->xold = pp->xnow = (m * gs.p_vec_x) >> 14;
			pp->yold = pp->ynow = (m * gs.p_vec_y) >> 14;
			if (opc & 0x01) {
				pp->xnow = round(pp->xnow);
				pp->ynow = round(pp->ynow);
			}
		}
		break;
	case MDRP00: case MDRP01:
	case MDRP02: case MDRP03:
	case MDRP04: case MDRP05:
	case MDRP06: case MDRP07:
	case MDRP08: case MDRP09:
	case MDRP0A: case MDRP0B:
	case MDRP0C: case MDRP0D:
	case MDRP0E: case MDRP0F:
	case MDRP10: case MDRP11:
	case MDRP12: case MDRP13:
	case MDRP14: case MDRP15:
	case MDRP16: case MDRP17:
	case MDRP18: case MDRP19:
	case MDRP1A: case MDRP1B:
	case MDRP1C: case MDRP1D:
	case MDRP1E: case MDRP1F:
		gs.rp2 = *(stack--);
		gs.rp1 = gs.rp0;
		debug("#MDRP%02X p[%d], rp = p[%d]", opc & 15, gs.rp2, gs.rp0);
		n = oldMeasure(gs.zp1[gs.rp2], gs.zp0[gs.rp0]);
		m = newMeasure(gs.zp1[gs.rp2], gs.zp0[gs.rp0]);
		debug("\nwgoaldist = %f, nowdist = %f", n / FSHIFT, m / FSHIFT);
		debug("\n(%d %d)-", gs.zp1[gs.rp2].xnow, gs.zp1[gs.rp2].ynow);
		debug("rp0(%d %d)", gs.zp0[gs.rp0].xnow, gs.zp0[gs.rp0].ynow);

		if (((n >= 0) ? +n : -n) < gs.swidth_cut_in)
			n = (n >= 0) ? +gs.swidth_value : -gs.swidth_value;
		if (opc & 0x10)
			gs.rp0 = gs.rp2;
		debug("\nmdrp1.wanted = %d", n);
		if (opc & 0x08) {
			if (n >= 0) {
				if (n < +gs.min_distance)
					n = +gs.min_distance;
			} else {
				if (n > -gs.min_distance)
					n = -gs.min_distance;
			}
		}
		if (opc & 0x04)
			n = round(n);
		debug("\nmdrp2.wanted = %d", n);
		// XXX: ignore black/gray/white for now
		gs.movePoint(gs.zp1[gs.rp2], n - m);
		break;
	case MIRP00: case MIRP01:
	case MIRP02: case MIRP03:
	case MIRP04: case MIRP05:
	case MIRP06: case MIRP07:
	case MIRP08: case MIRP09:
	case MIRP0A: case MIRP0B:
	case MIRP0C: case MIRP0D:
	case MIRP0E: case MIRP0F:
	case MIRP10: case MIRP11:
	case MIRP12: case MIRP13:
	case MIRP14: case MIRP15:
	case MIRP16: case MIRP17:
	case MIRP18: case MIRP19:
	case MIRP1A: case MIRP1B:
	case MIRP1C: case MIRP1D:
	case MIRP1E: case MIRP1F:
		gs.rp1 = gs.rp0;
		m = *(stack--);
		gs.rp2 = *(stack--);
		pp = &gs.zp1[gs.rp2];
		debug("#MIRP%02X p[%d] with cvt[%d]", opc & 15, gs.rp2, m);

		m = cvt[m];
		debug(" = %f, rp = p[%d]", m / FSHIFT, gs.rp0);
		if (((m >= 0)? +m : -m) < +gs.swidth_cut_in)
			m = (m >= 0) ? +gs.swidth_value : -gs.swidth_value;

		n = oldMeasure(*pp, gs.zp0[gs.rp0]);

		if ((n^m) < 0 && gs.auto_flip) {
			m = -m;
			debug("\nautoflip m = %f", m / FSHIFT);
		}
		if (opc & 0x04) {
			if (((m>n) ? m - n : n - m) >= +gs.cvt_cut_in)
				m = n;
			m = round(m);
		}
		if (opc & 0x08) {
			if (n >= 0) {
				if (m < +gs.min_distance)
					m = +gs.min_distance;
			} else {
				if (m > -gs.min_distance)
					m = -gs.min_distance;
			}
		}
		// XXX: ignore black/gray/white for now
		m -= newMeasure(*pp, gs.zp0[gs.rp0]);
		gs.movePoint(*pp, m);
		if (opc & 0x10)
			gs.rp0 = gs.rp2;
		break;
	case ALIGNRP:
		for (m = gs.loop; --m >= 0;) {
			int n = *(stack--);
			debug("ALIGNRP * %d p[%d], rp0 = p[%d]", m, n, gs.rp0);
			int i = newMeasure(gs.zp0[gs.rp0], gs.zp1[n]);
			gs.movePoint(gs.zp1[n], i);
		}
		gs.loop = 1;
		break;
	case ALIGNPTS:
		{
		m = *(stack--);
		n = *(stack--);
		debug("ALIGNPTS %d %d", m, n);
		int i = newMeasure(gs.zp0[m], gs.zp1[n]) >> 1;
		gs.movePoint(gs.zp0[m], -i);
		gs.movePoint(gs.zp1[n], +i);
		}
		break;
	case ISECT:
		{
		Point *pp1 = &gs.zp1[*(stack--)];
		Point *pp2 = &gs.zp1[*(stack--)];
		Point *pp3 = &gs.zp0[*(stack--)];
		Point *pp4 = &gs.zp0[*(stack--)];
		m = *(stack--);

		debug("ISECT p[%d] ", m);
		debug("between p[%d]-p[%d] ", pp1-gs.zp1, pp2-gs.zp1);
		debug("and p[%d]-p[%d] ", pp3-gs.zp0, pp4-gs.zp0);

		int f1 = (pp1->xnow - pp3->xnow) * (pp4->ynow - pp3->ynow) -
			 (pp1->ynow - pp3->ynow) * (pp4->xnow - pp3->xnow);
		int f2 = (pp2->ynow - pp1->ynow) * (pp4->xnow - pp3->xnow) -
			 (pp2->xnow - pp1->xnow) * (pp4->ynow - pp3->ynow);

		pp3 = &gs.zp2[m];
		pp3->flags |= X_TOUCHED | Y_TOUCHED;
		if (f2 == 0) { // parallel => no intersection
			pp3->xnow = (pp2->xnow + pp1->xnow + 1) >> 1;
			pp3->ynow = (pp2->ynow + pp1->ynow + 1) >> 1;
			debug("are parallel!\n");
		} else {
			pp3->xnow = pp1->xnow +
				    MULDIV(f1, pp2->xnow - pp1->xnow, f2);
			pp3->ynow = pp1->ynow +
				    MULDIV(f1, pp2->ynow - pp1->ynow, f2);
		}

		debug("\n-> %d %d", pp3->xnow, pp3->ynow);
		}
		break;
	case AA:
		stack--;
		debug("AA is obsolete and not supported!");
		break;
	case IP:
		for (m = gs.loop; --m >= 0;) {
			int n = *(stack--);
			debug("IP * %d p[%d] ", m, n);
			debug("between p[%d][%d] ", gs.zp1 == p[1], gs.rp2);
			debug("and p[%d][%d]", gs.zp0 == p[1], gs.rp1);
			interpolate(gs.zp2[n], gs.zp1[gs.rp2], gs.zp0[gs.rp1]);
			debug("\n");
		}
		gs.loop = 1;
		break;
	case UTP:
		m = *(stack--);
		gs.zp0[m].flags &= ~(X_TOUCHED | Y_TOUCHED);
		debug("UTP p[%d]", m);
		break;
	case IUP0:
		pp = p[1];
		for (m = 0; m < nEndPoints; ++m) {
			Point *last = p[1] + endPoints[m];
			debug("IUP0 p[%d .. %d]", pp - p[1], last - p[1]);
			doIUP0(pp, last);
			pp = last + 1;
		}
		break;
	case IUP1:
		pp = p[1];
		for (m = 0; m < nEndPoints; ++m) {
			Point *last = p[1] + endPoints[m];
			debug("IUP1 p[%d .. %d]", pp - p[1], last - p[1]);
			doIUP1(pp, last);
			pp = last + 1;
		}
		break;
	case DELTAP3:
		n = -32;
		goto deltap_label;
	case DELTAP2:
		n = -16;
		goto deltap_label;
	case DELTAP1:
		n = 0;
deltap_label:
		m = *(stack--);
		debug("DELTAP%d * %d", (-n >> 4) + 1, m);
		debug("\tmppem=%d, deltabase=%d", mppem, gs.delta_base);
		n += mppem - gs.delta_base;
		if (n < 0 || n > 15) {
			debug("\n=> skipping %d exceptions", m);
			stack -= m << 1;
			break;
		}
		n <<= 4;
		while (--m >= 0) {
			int pno = *(stack--);
			int arg = *(stack--);
			debug("\np[%d] arg %04X", pno, arg);
			debug("\targ.n=%d, n=%d", arg >> 4, n >> 4);
			if (n > (arg & 0xf0))
				break;
			if (n == (arg & 0xf0)) {
				arg = (arg & 15) - 8;
				if (arg >= 0)
					++arg;
				arg <<= (SHIFT - gs.delta_shift);
				debug("\tmoving by %f from (%d %d) ",
				      arg / FSHIFT,
				      gs.zp0[pno].xnow, gs.zp0[pno].ynow);
#if 0
				gs.movePoint(gs.zp0[pno], arg);
#else
				gs.zp0[pno].xnow += (arg * gs.f_vec_x) >> 14;
				gs.zp0[pno].ynow += (arg * gs.f_vec_y) >> 14;
				gs.zp0[pno].flags |= gs.flags;
#endif
				debug("to (%d %d)\n",
				      gs.zp0[pno].xnow, gs.zp0[pno].ynow);
			}
		}

#ifndef DEBUG
		if (m > 0)
			stack -= 2 * m;
#else
		while (--m >= 0) {
			int pno = *(stack--);
			int arg = *(stack--);
			debug("\n(p[%d] arg %04X", pno, arg);
			debug("\targ.n=%d, n=%d)", arg >> 4, n >> 4);
		};
#endif
		debug("\n");
		break;

	case DELTAC3:
		n = -32;
		goto deltac_label;
	case DELTAC2:
		n = -16;
		goto deltac_label;
	case DELTAC1:
		n = 0;
deltac_label:
		m = *(stack--);
		debug("DELTAC%d * %d", (-n >> 4) + 1, m);
		debug("\tmppem=%d, deltabase=%d", mppem, gs.delta_base);
		n += mppem - gs.delta_base;
		if (n < 0 || n > 15) {
			stack -= m << 1;
			break;
		}
		n <<= 4;
		while (--m >= 0) {
			int cno = *(stack--);
			int arg = *(stack--);
			debug("\ncvt[%d] arg = %04X, n = %d",
			      cno, arg, n >> 4);
			if (n > (arg & 0xf0))
				break;
			if (n == (arg & 0xf0)) {
				arg = (arg & 15) - 8;
				if (arg >= 0) ++arg;
				arg <<= SHIFT - gs.delta_shift;
				debug("\tmoved by %f,\t%d ",
				      arg / FSHIFT, cvt[cno]);
				cvt[cno] += arg;
				debug("-> %d", cvt[cno]);
			}
		}
#ifndef DEBUG
		if (m > 0)
			stack -= 2 * m;
#else
		while (--m >= 0) {
			int cno = *(stack--);
			int arg = *(stack--);
			debug("\n(cvt[%d] arg %04X", cno, arg);
			debug("\targ.n=%d, n=%d)", arg >> 4, n >> 4);
		};
#endif
		break;

	// stack manipulation

	case DUP:
		m = *stack;
		*(++stack) = m;
		debug("DUP = %d", m);
		break;
	case POP:
		debug("POP");
		--stack;
		break;
	case CLEAR:
		debug("CLEAR");
		stack = stackbase;
		break;
	case SWAP:
		debug("SWAP");
		m = *stack;
		*stack = *(stack - 1);
		*(stack - 1) = m;
		break;
	case DEPTH:
		m = stack - stackbase;
		*(++stack) = m;
		debug("DEPTH = %d", m);
		break;
	case CINDEX:
		m = *stack;
		assert(stack - m >= stackbase);
		*stack = *(stack - m);
		debug("CINDEX %d = %d", m, *stack);
		break;
	case MINDEX:
		m = *stack;
		stack -= m;
		assert(stack >= stackbase);
		n = *stack;
		debug("MINDEX %d = %d", m, n);
		for (; --m > 0; ++stack)
			stack[0] = stack[1];
		*stack = n;
		break;
	case ROLL:
		m = *(stack - 0);
		*(stack - 0) = *(stack - 2);
		*(stack - 2) = *(stack - 1);
		*(stack - 1) = m;
		debug("ROLL %d %d %d", m, *(stack - 2), *stack);
		debug(" => %d %d %d", *stack, m, *(stack - 2));
		break;

	// control flow

	case IF:
		m = *(stack--);
		debug("IF %d", m);
		if (!m)
			skipHints(f);
		break;
	case ELSE:
		// if we hit ELSE we didn't skip -> skip from here
		debug("ELSE");
		skipHints(f);
		break;
	case EIF:
		debug("EIF");
		break;
	case JROT:
		m = *(stack--);
		debug("JROT %d -> ", m);
		if (m)
			goto jump_relative;
		debug("not taken");
		--stack;
		break;
	case JROF:
		m = *(stack--);
		debug("JROF %d -> ", m);
		if (!m)
			goto jump_relative;
		debug("not taken");
		--stack;
		break;
	case JMPR:
jump_relative:
		m = *(stack--);
		debug("JMPR %d", m);
		f->seekRelative(m-1);
		break;
	case LT:
		m = *(stack--);
		n = *stack;
		*stack = (n < m);
		debug("LT %d %d = %d", m, n, *stack);
		break;
	case LTEQ:
		m = *(stack--);
		n = *stack;
		*stack = (n <= m);
		debug("LTEQ %d %d = %d", m, n, *stack);
		break;
	case GT:
		m = *(stack--);
		n = *stack;
		*stack = (n > m);
		debug("GT %d %d = %d", m, n, *stack);
		break;
	case GTEQ:
		m = *(stack--);
		n = *stack;
		*stack = (n >= m);
		debug("GTEQ %d %d = %d", m, n, *stack);
		break;
	case EQ:
		m = *(stack--);
		n = *stack;
		*stack = (m == n);
		debug("EQ %d %d = %d", m, n, *stack);
		break;
	case NEQ:
		m = *(stack--);
		n = *stack;
		*stack = (m != n);
		debug("NEQ %d %d = %d", m, n, *stack);
		break;
	case ODD:
		m = *stack;
		*stack = (round(m) >> SHIFT) & 1;
		debug("ODD %d = %d", m, *stack);
		break;
	case EVEN:
		m = *stack;
		*stack = ((~round(m)) >> SHIFT) & 1;
		debug("EVEN %d = %d", m, *stack);
		break;
	case AND:
		m = *(stack--);
		n = *stack;
		*stack = n && m;
		debug("AND %d %d = %d", m, n, *stack);
		break;
	case OR:
		m = *(stack--);
		n = *stack;
		*stack = n || m;
		debug("OR %d %d = %d", m, n, *stack);
		break;
	case NOT:
		m = *stack;
		*stack = !m;
		debug("NOT %d = %d", m, *stack);
		break;
	case ADD:
		m = *(stack--);
		*stack += m;
		debug("ADD %d %d = %d", m, *stack - m, *stack);
		break;
	case SUB:
		m = *(stack--);
		*stack -= m;
		debug("SUB %d %d = %d", m, *stack + m, *stack);
		break;
	case DIV:
		m = *(stack--);
		n = *stack;
		if (m)
			*stack = (n << SHIFT) / m;
		else
			*stack = (n >= 0) ? 0x7ffffff : -0x7ffffff;
		debug("DIV %d %d = %d", m, n, *stack);
		break;
	case MUL:
		m = *(stack--);
		n = *stack;
		*stack = (m * n + 32) >> SHIFT;
		debug("MUL %d %d = %d", m, n, *stack);
		break;
	case ABS:
		m = *stack;
		if (m < 0) *stack = -m;
		debug("ABS %d = %d", m, *stack);
		break;
	case NEG:
		*stack = -*stack;
		debug("NEG %d = %d", -*stack, *stack);
		break;
	case FLOOR:
		m = *stack;
		*stack = m & -64;
		debug("FLOOR %d = %d", m, *stack);
		break;
	case CEILING:
		m = *stack;
		*stack = (m + 63) & -64;
		debug("CEILING %d = %d", m, *stack);
		break;
	case MAX:
		m = *(stack--);
		n = *stack;
		if (m > n)
			*stack = m;
		debug("MAX %d %d = %d", m, n, *stack);
		break;
	case MIN:
		m = *(stack--);
		n = *stack;
		if (m < n)
			*stack = m;
		debug("MIN %d %d = %d", m, n, *stack);
		break;
	case ROUND00: case ROUND01:
	case ROUND02: case ROUND03:
		// 00: gray, 01: black, 02: white, 03: ???
		m = *stack;
		// XXX: ignore black/gray/white for now
		*stack = round(m);
		debug("#ROUND%02X %d = %d", opc & 3, m, *stack);
		break;
	case NROUND00: case NROUND01:
	case NROUND02: case NROUND03:
		// 00: gray, 01: black, 02: white, 03: ???
		m = *stack;
		// XXX: ignore black/gray/white for now
		*stack = m;
		debug("#NROUND%02X %d = %d", opc & 3, m, *stack);
		break;
	case FDEF:
		m = *(stack--);
		debug("FDEF %d", m);
		assert(m >= 0 && m < sizeFDefs);
		fdefs[m].f = f;
		fdefs[m].offset = f->tell();
		skipHints(f);
		fdefs[m].length = f->tell() - fdefs[m].offset;
		break;
	case ENDF:
		debug("ENDF\n");
		return;
	case IDEF:
		m = *(stack--);
		debug("IDEF %02X", m);
		assert(m >= 0 && m < sizeIDefs);
		idefs[m].f = f;
		idefs[m].offset = f->tell();
		skipHints(f);
		idefs[m].length = f->tell() - idefs[m].offset;
		break;
	case CALL:
		{
		m = *(stack--);
		debug("CALL %d\n", m);
		assert(m >= 0 && m < sizeFDefs);

		int ofs = f->tell();
		FDefs* fd = &fdefs[m];
		execHints(fd->f, fd->offset, fd->length);
		f->seekAbsolute(ofs);

		}
		break;
	case LOOPCALL:
		{
		m = *(stack--);
		n = *(stack--);
		debug("LOOPCALL %d * %d\n", m, n);

		int ofs = f->tell();
		FDefs* fd = &fdefs[m];
		while (--n >= 0)
			execHints(fd->f, fd->offset, fd->length);
		f->seekAbsolute(ofs);

		}
		break;
	case DBG:
		debug("DBG not implemented");
		break;
	case GETINFO:
		m = *stack;
		*stack = 0;
		if (m & SCALER_VERSION)
			*stack = WIN_SCALER;
		if (m & GLYPH_ROTATED)
			if (xy | yx)
				*stack |= IS_ROTATED;
		if (m & GLYPH_STRETCHED)
			if (xx != yy)
				*stack |= IS_STRETCHED;
		debug("GETINFO %d = 0x%03X", m, *stack);
		break;
	default:
		{
			int ofs = f->tell();
			IDefs* idef = &idefs[opc];
			debug("IDEF_CALL 0x%02X, ofs = %05X, len = %d\n",
			      opc, idef->offset, idef->length);

			if (idef->length) // Thanks Colin McCormack
				execHints(idef->f, idef->offset, idef->length);
			else
				debug("illegal instruction %02X\n", opc);
			f->seekAbsolute(ofs);
		}

		break;
	}
}

void
Rasterizer::hintGlyph(GlyphTable* g, int offset, int length)
{
	if (grid_fitting <= 0 || length == 0)
		return;

	// XXX: gs = default_gs;
	gs.init(p);
	gs.cvt_cut_in = default_gs.cvt_cut_in;
	gs.zp2 = gs.zp1 = gs.zp0 = p[1];
	stack = stackbase;
	execHints(g, offset, length);
}

void
Rasterizer::execHints(RandomAccessFile* const f, int offset, int length)
{
	assert(0 <= length && length <= 10000);

	f->seekAbsolute(offset);
	for (length += offset; f->tell() < length;)
		execOpcode(f);
	debug("\n\n");
}

void
Rasterizer::skipHints(RandomAccessFile* const f)
{
	debug("\nskipping...");
	for (int depth = 0;;) {
		int opc = f->readUByte();
		debug(" %02X ", opc);
		switch (opc) {
		case NPUSHB:
			opc = f->readUByte() + PUSHB00 - 1;
			// fall through
		case PUSHB00: case PUSHB01:
		case PUSHB02: case PUSHB03:
		case PUSHB04: case PUSHB05:
		case PUSHB06: case PUSHB07:
			f->seekRelative(opc - (PUSHB00 - 1));
			break;
		case NPUSHW:
			opc = f->readUByte() + PUSHW00 - 1;
			// fall through
		case PUSHW00: case PUSHW01:
		case PUSHW02: case PUSHW03:
		case PUSHW04: case PUSHW05:
		case PUSHW06: case PUSHW07:
			f->seekRelative((opc - (PUSHW00 - 1)) << 1);
			break;
		case IF:
		case FDEF:
		case IDEF:
			++depth;
			break;
		case EIF:
		case ENDF:
			if (--depth < 0)
				return;
			break;
		case ELSE:
			if (depth <= 0)
				return;
			break;
		}
	}
}

void
Rasterizer::interpolate(Point &pp, const Point &p2, const Point &p1)
{
	// interpolate pp to satisfy
	//
	//   dist(pp, p1)        dist(pp, p2)
	// ---------------- == ----------------
	//  dist(pp', p1')      dist(pp', p2')

	int dold21 = oldMeasure(p2, p1);
	int doldp1 = oldMeasure(pp, p1);

	int dist;

	if ((dold21 ^ doldp1) < 0 || doldp1 == 0)
		dist = newMeasure(p1, pp) + doldp1; //- oldMeasure(p1, pp);
	else if ((dold21 >= 0 && doldp1 >= dold21) ||
		 (dold21 <= 0 && doldp1 <= dold21))
		dist = newMeasure(p2, pp) - oldMeasure(p2, pp);
	else {
		int dnew21 = newMeasure(p2, p1);
		int dnewp1 = newMeasure(pp, p1);
		dist = MULDIV(doldp1, dnew21, dold21) - dnewp1;
	}

	debug("\nmove by %f", dist / FSHIFT);
	gs.movePoint(pp, dist);
}

void
Rasterizer::iup0(Point *const pp, const Point *const p1, const Point *const p2)
{
	int dold21 = p2->yold - p1->yold;
	int doldp1 = pp->yold - p1->yold;

	debug("\np[%d] between p[%d] and p[%d]", pp - p[1], p1 - p[1],
	      p2 - p[1]);
	debug("\nd21o dp1o %f %f", dold21 / FSHIFT, doldp1 / FSHIFT);

	debug("\tchanging y: %d %d", pp->xnow, pp->ynow);

	if ((dold21 ^ doldp1) < 0 || doldp1 == 0)
		pp->ynow = pp->yold + p1->ynow - p1->yold;
	else if ((dold21 >= 0 && doldp1 >= dold21) ||
		 (dold21 <= 0 && doldp1 <= dold21))
		pp->ynow = pp->yold + p2->ynow - p2->yold;
	else {
		int dnew21 = p2->ynow - p1->ynow;
		debug("\nd21n %8.3f", dnew21 / FSHIFT);
		pp->ynow = MULDIV(doldp1 + 1, dnew21, dold21) + p1->ynow;
	}

	debug(" -> %d %d\n", pp->xnow, pp->ynow);
}

void
Rasterizer::iup1(Point *const pp, const Point *const p1, const Point *const p2)
{
	int dold21 = p2->xold - p1->xold;
	int doldp1 = pp->xold - p1->xold;

	debug("\np[%d] between p[%d] and p[%d]", pp - p[1], p1 - p[1],
	      p2 - p[1]);
	debug("\nd21o dp1o %f %f", dold21 / FSHIFT, doldp1 / FSHIFT);

	debug("\nchanging x: %d %d", pp->xnow, pp->ynow);

	if ((dold21 ^ doldp1) < 0 || doldp1 == 0)
		pp->xnow = pp->xold + p1->xnow - p1->xold;
	else if ((dold21 >= 0 && doldp1 >= dold21) ||
		 (dold21 <= 0 && doldp1 <= dold21))
		pp->xnow = pp->xold + p2->xnow - p2->xold;
	else {
		int dnew21 = p2->xnow - p1->xnow;
		debug("\t(d21n %8.3f)", dnew21 / FSHIFT);
		pp->xnow = MULDIV(doldp1 + 1, dnew21, dold21) + p1->xnow;
	}

	debug(" -> %d %d\n", pp->xnow, pp->ynow);
}

void
Rasterizer::doIUP0(Point *const first, Point *const last)
{
	Point *p0;
	for (p0 = first; p0 <= last; ++p0)
		if (p0->flags & Y_TOUCHED)
			break;
	Point *i, *j;
	for (i = j = p0; i <= last; i = j) {
		while (++j <= last)
			if (j->flags & Y_TOUCHED)
				break;
		if (j > last)
			break;
		for (Point *k = i; ++k < j;)
			iup0(k, i, j);
	}
	if (i > last)
		return;
	for (j = i; ++j <= last;)
		iup0(j, i, p0);
	for (j = first; j < p0; ++j)
		iup0(j, i, p0);
}

void
Rasterizer::doIUP1(Point *const first, Point *const last)
{
	Point *p0;
	for (p0 = first; p0 <= last; ++p0)
		if (p0->flags & X_TOUCHED)
			break;
	Point *i, *j;
	for (i = j = p0; i <= last; i = j) {
		while (++j <= last)
			if (j->flags & X_TOUCHED)
				break;
		if (j > last)
			break;
		for (Point *k = i; ++k < j;)
			iup1(k, i, j);
	}
	if (i > last)
		return;
	for (j = i; ++j <= last;)
		iup1(j, i, p0);
	for (j = first; j < p0; ++j)
		iup1(j, i, p0);
}