static OpCode SkipNext(ref InstructionStream stream) { // grab the next opcode, and if it's one of the push instructions skip over its arguments var opcode = stream.NextOpCode(); switch (opcode) { case OpCode.NPUSHB: case OpCode.PUSHB1: case OpCode.PUSHB2: case OpCode.PUSHB3: case OpCode.PUSHB4: case OpCode.PUSHB5: case OpCode.PUSHB6: case OpCode.PUSHB7: case OpCode.PUSHB8: { var count = opcode == OpCode.NPUSHB ? stream.NextByte() : opcode - OpCode.PUSHB1 + 1; for (int i = 0; i < count; i++) stream.NextByte(); } break; case OpCode.NPUSHW: case OpCode.PUSHW1: case OpCode.PUSHW2: case OpCode.PUSHW3: case OpCode.PUSHW4: case OpCode.PUSHW5: case OpCode.PUSHW6: case OpCode.PUSHW7: case OpCode.PUSHW8: { var count = opcode == OpCode.NPUSHW ? stream.NextByte() : opcode - OpCode.PUSHW1 + 1; for (int i = 0; i < count; i++) stream.NextWord(); } break; } return opcode; }
void Execute(InstructionStream stream, bool inFunction, bool allowFunctionDefs) { // dispatch each instruction in the stream while (!stream.Done) { var opcode = stream.NextOpCode(); debugList.Add(opcode); switch (opcode) { // ==== PUSH INSTRUCTIONS ==== case OpCode.NPUSHB: case OpCode.PUSHB1: case OpCode.PUSHB2: case OpCode.PUSHB3: case OpCode.PUSHB4: case OpCode.PUSHB5: case OpCode.PUSHB6: case OpCode.PUSHB7: case OpCode.PUSHB8: { var count = opcode == OpCode.NPUSHB ? stream.NextByte() : opcode - OpCode.PUSHB1 + 1; for (int i = 0; i < count; i++) stack.Push(stream.NextByte()); } break; case OpCode.NPUSHW: case OpCode.PUSHW1: case OpCode.PUSHW2: case OpCode.PUSHW3: case OpCode.PUSHW4: case OpCode.PUSHW5: case OpCode.PUSHW6: case OpCode.PUSHW7: case OpCode.PUSHW8: { var count = opcode == OpCode.NPUSHW ? stream.NextByte() : opcode - OpCode.PUSHW1 + 1; for (int i = 0; i < count; i++) stack.Push(stream.NextWord()); } break; // ==== STORAGE MANAGEMENT ==== case OpCode.RS: { var loc = CheckIndex(stack.Pop(), storage.Length); stack.Push(storage[loc]); } break; case OpCode.WS: { var value = stack.Pop(); var loc = CheckIndex(stack.Pop(), storage.Length); storage[loc] = value; } break; // ==== CONTROL VALUE TABLE ==== case OpCode.WCVTP: { var value = stack.PopFloat(); var loc = CheckIndex(stack.Pop(), controlValueTable.Length); controlValueTable[loc] = value; } break; case OpCode.WCVTF: { var value = stack.Pop(); var loc = CheckIndex(stack.Pop(), controlValueTable.Length); controlValueTable[loc] = value * scale; } break; case OpCode.RCVT: stack.Push(ReadCvt()); break; // ==== STATE VECTORS ==== case OpCode.SVTCA0: case OpCode.SVTCA1: { var axis = opcode - OpCode.SVTCA0; SetFreedomVectorToAxis(axis); SetProjectionVectorToAxis(axis); } break; case OpCode.SFVTPV: state.Freedom = state.Projection; OnVectorsUpdated(); break; case OpCode.SPVTCA0: case OpCode.SPVTCA1: SetProjectionVectorToAxis(opcode - OpCode.SPVTCA0); break; case OpCode.SFVTCA0: case OpCode.SFVTCA1: SetFreedomVectorToAxis(opcode - OpCode.SFVTCA0); break; case OpCode.SPVTL0: case OpCode.SPVTL1: case OpCode.SFVTL0: case OpCode.SFVTL1: SetVectorToLine(opcode - OpCode.SPVTL0, false); break; case OpCode.SDPVTL0: case OpCode.SDPVTL1: SetVectorToLine(opcode - OpCode.SDPVTL0, true); break; case OpCode.SPVFS: case OpCode.SFVFS: { var y = stack.Pop(); var x = stack.Pop(); var vec = Vector2.Normalize(new Vector2(F2Dot14ToFloat(x), F2Dot14ToFloat(y))); if (opcode == OpCode.SFVFS) state.Freedom = vec; else { state.Projection = vec; state.DualProjection = vec; } OnVectorsUpdated(); } break; case OpCode.GPV: case OpCode.GFV: { var vec = opcode == OpCode.GPV ? state.Projection : state.Freedom; stack.Push(FloatToF2Dot14(vec.X)); stack.Push(FloatToF2Dot14(vec.Y)); } break; // ==== GRAPHICS STATE ==== case OpCode.SRP0: state.Rp0 = stack.Pop(); break; case OpCode.SRP1: state.Rp1 = stack.Pop(); break; case OpCode.SRP2: state.Rp2 = stack.Pop(); break; case OpCode.SZP0: zp0 = GetZoneFromStack(); break; case OpCode.SZP1: zp1 = GetZoneFromStack(); break; case OpCode.SZP2: zp2 = GetZoneFromStack(); break; case OpCode.SZPS: zp0 = zp1 = zp2 = GetZoneFromStack(); break; case OpCode.RTHG: state.RoundState = RoundMode.ToHalfGrid; break; case OpCode.RTG: state.RoundState = RoundMode.ToGrid; break; case OpCode.RTDG: state.RoundState = RoundMode.ToDoubleGrid; break; case OpCode.RDTG: state.RoundState = RoundMode.DownToGrid; break; case OpCode.RUTG: state.RoundState = RoundMode.UpToGrid; break; case OpCode.ROFF: state.RoundState = RoundMode.Off; break; case OpCode.SROUND: state.RoundState = RoundMode.Super; SetSuperRound(1.0f); break; case OpCode.S45ROUND: state.RoundState = RoundMode.Super45; SetSuperRound(Sqrt2Over2); break; case OpCode.INSTCTRL: { var selector = stack.Pop(); if (selector >= 1 && selector <= 2) { // value is false if zero, otherwise shift the right bit into the flags var bit = 1 << (selector - 1); if (stack.Pop() == 0) state.InstructionControl = (InstructionControlFlags)((int)state.InstructionControl & ~bit); else state.InstructionControl = (InstructionControlFlags)((int)state.InstructionControl | bit); } } break; case OpCode.SCANCTRL: /* instruction unspported */ stack.Pop(); break; case OpCode.SCANTYPE: /* instruction unspported */ stack.Pop(); break; case OpCode.SANGW: /* instruction unspported */ stack.Pop(); break; case OpCode.SLOOP: state.Loop = stack.Pop(); break; case OpCode.SMD: state.MinDistance = stack.PopFloat(); break; case OpCode.SCVTCI: state.ControlValueCutIn = stack.PopFloat(); break; case OpCode.SSWCI: state.SingleWidthCutIn = stack.PopFloat(); break; case OpCode.SSW: state.SingleWidthValue = stack.Pop() * scale; break; case OpCode.FLIPON: state.AutoFlip = true; break; case OpCode.FLIPOFF: state.AutoFlip = false; break; case OpCode.SDB: state.DeltaBase = stack.Pop(); break; case OpCode.SDS: state.DeltaShift = stack.Pop(); break; // ==== POINT MEASUREMENT ==== case OpCode.GC0: stack.Push(Project(zp2.GetCurrent(stack.Pop()))); break; case OpCode.GC1: stack.Push(DualProject(zp2.GetOriginal(stack.Pop()))); break; case OpCode.SCFS: { var value = stack.PopFloat(); var index = stack.Pop(); var point = zp2.GetCurrent(index); MovePoint(zp2, index, value - Project(point)); // moving twilight points moves their "original" value also if (zp2.IsTwilight) zp2.Original[index].P = zp2.Current[index].P; } break; case OpCode.MD0: { var p1 = zp1.GetOriginal(stack.Pop()); var p2 = zp0.GetOriginal(stack.Pop()); stack.Push(DualProject(p2 - p1)); } break; case OpCode.MD1: { var p1 = zp1.GetCurrent(stack.Pop()); var p2 = zp0.GetCurrent(stack.Pop()); stack.Push(Project(p2 - p1)); } break; case OpCode.MPS: // MPS should return point size, but we assume DPI so it's the same as pixel size case OpCode.MPPEM: stack.Push(ppem); break; case OpCode.AA: /* deprecated instruction */ stack.Pop(); break; // ==== POINT MODIFICATION ==== case OpCode.FLIPPT: { for (int i = 0; i < state.Loop; i++) { var index = stack.Pop(); if (points.Current[index].Type == PointType.OnCurve) points.Current[index].Type = PointType.Quadratic; else points.Current[index].Type = PointType.OnCurve; } state.Loop = 1; } break; case OpCode.FLIPRGON: { var end = stack.Pop(); for (int i = stack.Pop(); i <= end; i++) points.Current[i].Type = PointType.OnCurve; } break; case OpCode.FLIPRGOFF: { var end = stack.Pop(); for (int i = stack.Pop(); i <= end; i++) points.Current[i].Type = PointType.Quadratic; } break; case OpCode.SHP0: case OpCode.SHP1: { Zone zone; int point; var displacement = ComputeDisplacement((int)opcode, out zone, out point); ShiftPoints(displacement); } break; case OpCode.SHPIX: ShiftPoints(stack.PopFloat() * state.Freedom); break; case OpCode.SHC0: case OpCode.SHC1: { Zone zone; int point; var displacement = ComputeDisplacement((int)opcode, out zone, out point); var touch = GetTouchState(); var contour = stack.Pop(); var start = contour == 0 ? 0 : contours[contour - 1] + 1; var count = zp2.IsTwilight ? zp2.Current.Length : contours[contour] + 1; for (int i = start; i < count; i++) { // don't move the reference point if (zone.Current != zp2.Current || point != i) { zp2.Current[i].P += displacement; zp2.TouchState[i] |= touch; } } } break; case OpCode.SHZ0: case OpCode.SHZ1: { Zone zone; int point; var displacement = ComputeDisplacement((int)opcode, out zone, out point); var count = 0; if (zp2.IsTwilight) count = zp2.Current.Length; else if (contours.Length > 0) count = contours[contours.Length - 1] + 1; for (int i = 0; i < count; i++) { // don't move the reference point if (zone.Current != zp2.Current || point != i) zp2.Current[i].P += displacement; } } break; case OpCode.MIAP0: case OpCode.MIAP1: { var distance = ReadCvt(); var pointIndex = stack.Pop(); // this instruction is used in the CVT to set up twilight points with original values if (zp0.IsTwilight) { var original = state.Freedom * distance; zp0.Original[pointIndex].P = original; zp0.Current[pointIndex].P = original; } // current position of the point along the projection vector var point = zp0.GetCurrent(pointIndex); var currentPos = Project(point); if (opcode == OpCode.MIAP1) { // only use the CVT if we are above the cut-in point if (Math.Abs(distance - currentPos) > state.ControlValueCutIn) distance = currentPos; distance = Round(distance); } MovePoint(zp0, pointIndex, distance - currentPos); state.Rp0 = pointIndex; state.Rp1 = pointIndex; } break; case OpCode.MDAP0: case OpCode.MDAP1: { var pointIndex = stack.Pop(); var point = zp0.GetCurrent(pointIndex); var distance = 0.0f; if (opcode == OpCode.MDAP1) { distance = Project(point); distance = Round(distance) - distance; } MovePoint(zp0, pointIndex, distance); state.Rp0 = pointIndex; state.Rp1 = pointIndex; } break; case OpCode.MSIRP0: case OpCode.MSIRP1: { var targetDistance = stack.PopFloat(); var pointIndex = stack.Pop(); // if we're operating on the twilight zone, initialize the points if (zp1.IsTwilight) { zp1.Original[pointIndex].P = zp0.Original[state.Rp0].P + targetDistance * state.Freedom / fdotp; zp1.Current[pointIndex].P = zp1.Original[pointIndex].P; } var currentDistance = Project(zp1.GetCurrent(pointIndex) - zp0.GetCurrent(state.Rp0)); MovePoint(zp1, pointIndex, targetDistance - currentDistance); state.Rp1 = state.Rp0; state.Rp2 = pointIndex; if (opcode == OpCode.MSIRP1) state.Rp0 = pointIndex; } break; case OpCode.IP: { var originalBase = zp0.GetOriginal(state.Rp1); var currentBase = zp0.GetCurrent(state.Rp1); var originalRange = DualProject(zp1.GetOriginal(state.Rp2) - originalBase); var currentRange = Project(zp1.GetCurrent(state.Rp2) - currentBase); for (int i = 0; i < state.Loop; i++) { var pointIndex = stack.Pop(); var point = zp2.GetCurrent(pointIndex); var currentDistance = Project(point - currentBase); var originalDistance = DualProject(zp2.GetOriginal(pointIndex) - originalBase); var newDistance = 0.0f; if (originalDistance != 0.0f) { // a range of 0.0f is invalid according to the spec (would result in a div by zero) if (originalRange == 0.0f) newDistance = originalDistance; else newDistance = originalDistance * currentRange / originalRange; } MovePoint(zp2, pointIndex, newDistance - currentDistance); } state.Loop = 1; } break; case OpCode.ALIGNRP: { for (int i = 0; i < state.Loop; i++) { var pointIndex = stack.Pop(); var p1 = zp1.GetCurrent(pointIndex); var p2 = zp0.GetCurrent(state.Rp0); MovePoint(zp1, pointIndex, -Project(p1 - p2)); } state.Loop = 1; } break; case OpCode.ALIGNPTS: { var p1 = stack.Pop(); var p2 = stack.Pop(); var distance = Project(zp0.GetCurrent(p2) - zp1.GetCurrent(p1)) / 2; MovePoint(zp1, p1, distance); MovePoint(zp0, p2, -distance); } break; case OpCode.UTP: zp0.TouchState[stack.Pop()] &= ~GetTouchState(); break; case OpCode.IUP0: case OpCode.IUP1: unsafe { // bail if no contours (empty outline) if (contours.Length == 0) break; fixed (PointF* currentPtr = points.Current) fixed (PointF* originalPtr = points.Original) { // opcode controls whether we care about X or Y direction // do some pointer trickery so we can operate on the // points in a direction-agnostic manner TouchState touchMask; byte* current; byte* original; if (opcode == OpCode.IUP0) { touchMask = TouchState.Y; current = (byte*)¤tPtr->P.Y; original = (byte*)&originalPtr->P.Y; } else { touchMask = TouchState.X; current = (byte*)¤tPtr->P.X; original = (byte*)&originalPtr->P.X; } var point = 0; for (int i = 0; i < contours.Length; i++) { var endPoint = contours[i]; var firstPoint = point; var firstTouched = -1; var lastTouched = -1; for (; point <= endPoint; point++) { // check whether this point has been touched if ((points.TouchState[point] & touchMask) != 0) { // if this is the first touched point in the contour, note it and continue if (firstTouched < 0) { firstTouched = point; lastTouched = point; continue; } // otherwise, interpolate all untouched points // between this point and our last touched point InterpolatePoints(current, original, lastTouched + 1, point - 1, lastTouched, point); lastTouched = point; } } // check if we had any touched points at all in this contour if (firstTouched >= 0) { // there are two cases left to handle: // 1. there was only one touched point in the whole contour, in // which case we want to shift everything relative to that one // 2. several touched points, in which case handle the gap from the // beginning to the first touched point and the gap from the last // touched point to the end of the contour if (lastTouched == firstTouched) { var delta = *GetPoint(current, lastTouched) - *GetPoint(original, lastTouched); if (delta != 0.0f) { for (int j = firstPoint; j < lastTouched; j++) *GetPoint(current, j) += delta; for (int j = lastTouched + 1; j <= endPoint; j++) *GetPoint(current, j) += delta; } } else { InterpolatePoints(current, original, lastTouched + 1, endPoint, lastTouched, firstTouched); if (firstTouched > 0) InterpolatePoints(current, original, firstPoint, firstTouched - 1, lastTouched, firstTouched); } } } } } break; case OpCode.ISECT: { // move point P to the intersection of lines A and B var b1 = zp0.GetCurrent(stack.Pop()); var b0 = zp0.GetCurrent(stack.Pop()); var a1 = zp1.GetCurrent(stack.Pop()); var a0 = zp1.GetCurrent(stack.Pop()); var index = stack.Pop(); // calculate intersection using determinants: https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line var da = a0 - a1; var db = b0 - b1; var den = (da.X * db.Y) - (da.Y * db.X); if (Math.Abs(den) <= Epsilon) { // parallel lines; spec says to put the ppoint "into the middle of the two lines" zp2.Current[index].P = (a0 + a1 + b0 + b1) / 4; } else { var t = (a0.X * a1.Y) - (a0.Y * a1.X); var u = (b0.X * b1.Y) - (b0.Y * b1.X); var p = new Vector2( (t * db.X) - (da.X * u), (t * db.Y) - (da.Y * u) ); zp2.Current[index].P = p / den; } zp2.TouchState[index] = TouchState.Both; } break; // ==== STACK MANAGEMENT ==== case OpCode.DUP: stack.Duplicate(); break; case OpCode.POP: stack.Pop(); break; case OpCode.CLEAR: stack.Clear(); break; case OpCode.SWAP: stack.Swap(); break; case OpCode.DEPTH: stack.Depth(); break; case OpCode.CINDEX: stack.Copy(); break; case OpCode.MINDEX: stack.Move(); break; case OpCode.ROLL: stack.Roll(); break; // ==== FLOW CONTROL ==== case OpCode.IF: { // value is false; jump to the next else block or endif marker // otherwise, we don't have to do anything; we'll keep executing this block if (!stack.PopBool()) { int indent = 1; while (indent > 0) { opcode = SkipNext(ref stream); switch (opcode) { case OpCode.IF: indent++; break; case OpCode.EIF: indent--; break; case OpCode.ELSE: if (indent == 1) indent = 0; break; } } } } break; case OpCode.ELSE: { // assume we hit the true statement of some previous if block // if we had hit false, we would have jumped over this int indent = 1; while (indent > 0) { opcode = SkipNext(ref stream); switch (opcode) { case OpCode.IF: indent++; break; case OpCode.EIF: indent--; break; } } } break; case OpCode.EIF: /* nothing to do */ break; case OpCode.JROT: case OpCode.JROF: { if (stack.PopBool() == (opcode == OpCode.JROT)) stream.Jump(stack.Pop() - 1); else stack.Pop(); // ignore the offset } break; case OpCode.JMPR: stream.Jump(stack.Pop() - 1); break; // ==== LOGICAL OPS ==== case OpCode.LT: { var b = stack.Pop(); var a = stack.Pop(); stack.Push(a < b); } break; case OpCode.LTEQ: { var b = stack.Pop(); var a = stack.Pop(); stack.Push(a <= b); } break; case OpCode.GT: { var b = stack.Pop(); var a = stack.Pop(); stack.Push(a > b); } break; case OpCode.GTEQ: { var b = stack.Pop(); var a = stack.Pop(); stack.Push(a >= b); } break; case OpCode.EQ: { var b = stack.Pop(); var a = stack.Pop(); stack.Push(a == b); } break; case OpCode.NEQ: { var b = stack.Pop(); var a = stack.Pop(); stack.Push(a != b); } break; case OpCode.AND: { var b = stack.PopBool(); var a = stack.PopBool(); stack.Push(a && b); } break; case OpCode.OR: { var b = stack.PopBool(); var a = stack.PopBool(); stack.Push(a || b); } break; case OpCode.NOT: stack.Push(!stack.PopBool()); break; case OpCode.ODD: { var value = (int)Round(stack.PopFloat()); stack.Push(value % 2 != 0); } break; case OpCode.EVEN: { var value = (int)Round(stack.PopFloat()); stack.Push(value % 2 == 0); } break; // ==== ARITHMETIC ==== case OpCode.ADD: { var b = stack.Pop(); var a = stack.Pop(); stack.Push(a + b); } break; case OpCode.SUB: { var b = stack.Pop(); var a = stack.Pop(); stack.Push(a - b); } break; case OpCode.DIV: { var b = stack.Pop(); if (b == 0) throw new InvalidFontException("Division by zero."); var a = stack.Pop(); var result = ((long)a << 6) / b; stack.Push((int)result); } break; case OpCode.MUL: { var b = stack.Pop(); var a = stack.Pop(); var result = ((long)a * b) >> 6; stack.Push((int)result); } break; case OpCode.ABS: stack.Push(Math.Abs(stack.Pop())); break; case OpCode.NEG: stack.Push(-stack.Pop()); break; case OpCode.FLOOR: stack.Push(stack.Pop() & ~63); break; case OpCode.CEILING: stack.Push((stack.Pop() + 63) & ~63); break; case OpCode.MAX: stack.Push(Math.Max(stack.Pop(), stack.Pop())); break; case OpCode.MIN: stack.Push(Math.Min(stack.Pop(), stack.Pop())); break; // ==== FUNCTIONS ==== case OpCode.FDEF: { if (!allowFunctionDefs || inFunction) throw new InvalidFontException("Can't define functions here."); functions[stack.Pop()] = stream; while (SkipNext(ref stream) != OpCode.ENDF) ; } break; case OpCode.IDEF: { if (!allowFunctionDefs || inFunction) throw new InvalidFontException("Can't define functions here."); instructionDefs[stack.Pop()] = stream; while (SkipNext(ref stream) != OpCode.ENDF) ; } break; case OpCode.ENDF: { if (!inFunction) throw new InvalidFontException("Found invalid ENDF marker outside of a function definition."); return; } case OpCode.CALL: case OpCode.LOOPCALL: { callStackSize++; if (callStackSize > MaxCallStack) throw new InvalidFontException("Stack overflow; infinite recursion?"); var function = functions[stack.Pop()]; var count = opcode == OpCode.LOOPCALL ? stack.Pop() : 1; for (int i = 0; i < count; i++) Execute(function, true, false); callStackSize--; } break; // ==== ROUNDING ==== // we don't have "engine compensation" so the variants are unnecessary case OpCode.ROUND0: case OpCode.ROUND1: case OpCode.ROUND2: case OpCode.ROUND3: stack.Push(Round(stack.PopFloat())); break; case OpCode.NROUND0: case OpCode.NROUND1: case OpCode.NROUND2: case OpCode.NROUND3: break; // ==== DELTA EXCEPTIONS ==== case OpCode.DELTAC1: case OpCode.DELTAC2: case OpCode.DELTAC3: { var last = stack.Pop(); for (int i = 1; i <= last; i++) { var cvtIndex = stack.Pop(); var arg = stack.Pop(); // upper 4 bits of the 8-bit arg is the relative ppem // the opcode specifies the base to add to the ppem var triggerPpem = (arg >> 4) & 0xF; triggerPpem += (opcode - OpCode.DELTAC1) * 16; triggerPpem += state.DeltaBase; // if the current ppem matches the trigger, apply the exception if (ppem == triggerPpem) { // the lower 4 bits of the arg is the amount to shift // it's encoded such that 0 isn't an allowable value (who wants to shift by 0 anyway?) var amount = (arg & 0xF) - 8; if (amount >= 0) amount++; amount *= 1 << (6 - state.DeltaShift); // update the CVT CheckIndex(cvtIndex, controlValueTable.Length); controlValueTable[cvtIndex] += F26Dot6ToFloat(amount); } } } break; case OpCode.DELTAP1: case OpCode.DELTAP2: case OpCode.DELTAP3: { var last = stack.Pop(); for (int i = 1; i <= last; i++) { var pointIndex = stack.Pop(); var arg = stack.Pop(); // upper 4 bits of the 8-bit arg is the relative ppem // the opcode specifies the base to add to the ppem var triggerPpem = (arg >> 4) & 0xF; triggerPpem += state.DeltaBase; if (opcode != OpCode.DELTAP1) triggerPpem += (opcode - OpCode.DELTAP2 + 1) * 16; // if the current ppem matches the trigger, apply the exception if (ppem == triggerPpem) { // the lower 4 bits of the arg is the amount to shift // it's encoded such that 0 isn't an allowable value (who wants to shift by 0 anyway?) var amount = (arg & 0xF) - 8; if (amount >= 0) amount++; amount *= 1 << (6 - state.DeltaShift); MovePoint(zp0, pointIndex, F26Dot6ToFloat(amount)); } } } break; // ==== MISCELLANEOUS ==== case OpCode.DEBUG: stack.Pop(); break; case OpCode.GETINFO: { var selector = stack.Pop(); var result = 0; if ((selector & 0x1) != 0) { // pretend we are MS Rasterizer v35 result = 35; } // TODO: rotation and stretching //if ((selector & 0x2) != 0) //if ((selector & 0x4) != 0) // we're always rendering in grayscale if ((selector & 0x20) != 0) result |= 1 << 12; // TODO: ClearType flags stack.Push(result); } break; default: if (opcode >= OpCode.MIRP) MoveIndirectRelative(opcode - OpCode.MIRP); else if (opcode >= OpCode.MDRP) MoveDirectRelative(opcode - OpCode.MDRP); else { // check if this is a runtime-defined opcode var index = (int)opcode; if (index > instructionDefs.Length || !instructionDefs[index].IsValid) throw new InvalidFontException("Unknown opcode in font program."); callStackSize++; if (callStackSize > MaxCallStack) throw new InvalidFontException("Stack overflow; infinite recursion?"); Execute(instructionDefs[index], true, false); callStackSize--; } break; } } }