All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jpedal.fonts.tt.hinting.TTVM Maven / Gradle / Ivy

There is a newer version: 20151002
Show newest version
/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.idrsolutions.com
 * Help section for developers at http://www.idrsolutions.com/support/
 *
 * (C) Copyright 1997-2017 IDRsolutions and Contributors.
 *
 * This file is part of JPedal/JPDF2HTML5
 *
     This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


 *
 * ---------------
 * TTVM.java
 * ---------------
 */
package org.jpedal.fonts.tt.hinting;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.text.NumberFormat;
import java.util.HashMap;
import javax.swing.*;
import org.jpedal.fonts.tt.BaseTTGlyph;
import org.jpedal.fonts.tt.FontFile2;
import org.jpedal.fonts.tt.Maxp;
import org.jpedal.utils.LogWriter;

public class TTVM implements Serializable {
    protected static final int TWILIGHT_ZONE = 0;
    protected static final int GLYPH_ZONE = 1;
    protected static final int ORIGINAL = 2;                //Use as modifier (GLYPH_ZONE + ORIGINAL)

    //debug flags

    /**
     * Activates the Hinting Debugger. This lets you step through the font program of a glyph, seeing what's
     * done exactly where. Again, I'd recommend isolating the glyph in TTGlyph's constructor before using this flag.
     */
    private static final boolean showDebugWindow = false;

    /**
     * Enabling this flag will print the instructions before executing the glyph program. As it executes it will also
     * print out the functions it's calling. I'd recommend you isolate the glyph you're looking at in TTGlyph's
     * constructor first!
     */
    private static final boolean printGlyphInstructions = false;

    /**
     * This flag prints out the line number, name of instruction just executed, and a list of the coordinates of each
     * point in the glyph as it is now and in the original outline. The format is designed to be pasted into a
     * spreadsheet - the first column is the point number, then it's current x and y, then original x and y. Use a
     * scatter graph to see what's happening. Again, I'd recommend isolating the glyph in TTGlyph's constructor before
     * using this flag!
     */
     private static final boolean printCoordsAfterEachInstruction = false;

    /**
     * This flag makes it such that the full coordinates are printed out immediately after the watch point is changed.
     * In order to place this in context the glyph program's execution path is printed with it. Again, I'd recommend
     * isolating the glyph in TTGlyph's constructor before using this flag.
     */

    private static boolean watchAPoint = true;
    static {
        watchAPoint = false;//off by default
    }

    private static final int watchPoint=53;
    private int watchX, watchY;

    //Variables used by debugger
    private int debugPointer, instructionsExecuted, functionsLineCount;
    private int[] programToDebug;
    private boolean[] programToDebugIsData;
    private JFrame debugWindow;
    private TTGraphicsState dGS;
    private JComponent stateDisplay,debugGlyphDisplay;
    private JCheckBox showInterpolatedShadow;
    private boolean stepInto, debuggerRunningInBackground;
    private static JList currentInstructionList;
    private static JList stackList;
    private static JList cvtList;
    private static JList storageList;
    private JLabel currentCode, debugXLabel, debugYLabel;
    private final java.util.Stack codeStack = new java.util.Stack();
    private final java.util.Stack numberStack = new java.util.Stack();

    /**
     * Prints the name and a brief description of an instruction when it is executed.
     */
    private boolean printOut;



    //Font-wide programs
    private final int[] preProgram;
    private final int[] fontProgram;
    private boolean fontProgramRun;
    private boolean scalerRun;

    //Scaling values
    private double ptSize;
    private double ppem;
    private double scaler;

    //These will currently always be false since we generate once irrespective of the transform
//    private final boolean isRotated=false;
//    private final boolean isStretched=false;

    //Arrays for holding the data of the current glyph
    private final int[][] x, y;
    private final boolean[][] curve, contour;
    private boolean[][][] touched;

    //Information about the maximum profile of various structures
    final Maxp maxp;

    //Flag for instrctrl telling a glyph to use the default graphics state
    private boolean useDefaultGS;

    /**
     * Data structures
     */
    //Stack containing each programs working data structures
    private Stack stack;

    //Control value table - contains values which are scaled with the font and used for aligning points
    private final Cvt cvt;

    //Font-wide copy of the graphics state, which may be copied and changed before being used by a glyph!
    private final TTGraphicsState graphicsState;

    //Area for storing generic values
    private final int[] storage;

    //A map of user defined int[] functions with a number as a key
    private final HashMap functions;

    //A map of user defined int[] functions with an opcode value as a key
    private final HashMap instructions;


    /*
     * Instruction Opcodes
     */
    private static final int SVTCAy = 0x00;
    private static final int SVTCAx = 0x01;
    private static final int SPVTCAy = 0x02;
    private static final int SPVTCAx = 0x03;
    private static final int SFVTCAy = 0x04;
    private static final int SFVTCAx = 0x05;
    private static final int SPVTL0 = 0x06;
    private static final int SPVTL1 = 0x07;
    private static final int SFVTL0 = 0x08;
    private static final int SFVTL1 = 0x09;
    private static final int SPVFS = 0x0A;
    private static final int SFVFS = 0x0B;
    private static final int GPV = 0x0C;
    private static final int GFV = 0x0D;
    private static final int SFVTPV = 0x0E;
    private static final int ISECT = 0x0F;

    private static final int SRP0 = 0x10;
    private static final int SRP1 = 0x11;
    private static final int SRP2 = 0x12;
    private static final int SZP0 = 0x13;
    private static final int SZP1 = 0x14;
    private static final int SZP2 = 0x15;
    private static final int SZPS = 0x16;
    private static final int SLOOP = 0x17;
    private static final int RTG = 0x18;
    private static final int RTHG = 0x19;
    private static final int SMD = 0x1A;
    private static final int ELSE = 0x1B;
    private static final int JMPR = 0x1C;
    private static final int SCVTCI = 0x1D;
    private static final int SSWCI = 0x1E;
    private static final int SSW = 0x1F;

    private static final int DUP = 0x20;
    private static final int POP = 0x21;
    private static final int CLEAR = 0x22;
    private static final int SWAP = 0x23;
    private static final int DEPTH = 0x24;
    private static final int CINDEX = 0x25;
    private static final int MINDEX = 0x26;
    private static final int ALIGNPTS = 0x27;
    private static final int UTP = 0x29;
    private static final int LOOPCALL = 0x2A;
    private static final int CALL = 0x2B;
    private static final int FDEF = 0x2C;
    private static final int ENDF = 0x2D;
    private static final int MDAP0 = 0x2E;
    private static final int MDAP1 = 0x2F;

    private static final int IUPy = 0x30;
    private static final int IUPx = 0x31;
    private static final int SHP0 = 0x32;
    private static final int SHP1 = 0x33;
    private static final int SHC0 = 0x34;
    private static final int SHC1 = 0x35;
    private static final int SHZ0 = 0x36;
    private static final int SHZ1 = 0x37;
    private static final int SHPIX = 0x38;
    private static final int IP = 0x39;
    private static final int MSIRP0 = 0x3A;
    private static final int MSIRP1 = 0x3B;
    private static final int ALIGNRP = 0x3C;
    private static final int RTDG = 0x3D;
    private static final int MIAP0 = 0x3E;
    private static final int MIAP1 = 0x3F;

    private static final int NPUSHB = 0x40;
    private static final int NPUSHW = 0x41;
    private static final int WS = 0x42;
    private static final int RS = 0x43;
    private static final int WCVTP = 0x44;
    private static final int RCVT = 0x45;
    private static final int GC0 = 0x46;
    private static final int GC1 = 0x47;
    private static final int SCFS = 0x48;
    private static final int MD0 = 0x49;
    private static final int MD1 = 0x4A;
    private static final int MPPEM = 0x4B;
    private static final int MPS = 0x4C;
    private static final int FLIPON = 0x4D;
    private static final int FLIPOFF = 0x4E;
    private static final int DEBUG = 0x4F;

    private static final int LT = 0x50;
    private static final int LTEQ = 0x51;
    private static final int GT = 0x52;
    private static final int GTEQ = 0x53;
    private static final int EQ = 0x54;
    private static final int NEQ = 0x55;
    private static final int ODD = 0x56;
    private static final int EVEN = 0x57;
    private static final int IF = 0x58;
    private static final int EIF = 0x59;
    private static final int AND = 0x5A;
    private static final int OR = 0x5B;
    private static final int NOT = 0x5C;
    private static final int DELTAP1 = 0x5D;
    private static final int SDB = 0x5E;
    private static final int SDS = 0x5F;

    private static final int ADD = 0x60;
    private static final int SUB = 0x61;
    private static final int DIV = 0x62;
    private static final int MUL = 0x63;
    private static final int ABS = 0x64;
    private static final int NEG = 0x65;
    private static final int FLOOR = 0x66;
    private static final int CEILING = 0x67;
    private static final int ROUND00 = 0x68;
    private static final int ROUND01 = 0x69;
    private static final int ROUND10 = 0x6A;
    private static final int ROUND11 = 0x6B;
    private static final int NROUND00 = 0x6C;
    private static final int NROUND01 = 0x6D;
    private static final int NROUND10 = 0x6E;
    private static final int NROUND11 = 0x6F;

    private static final int WCVTF = 0x70;
    private static final int DELTAP2 = 0x71;
    private static final int DELTAP3 = 0x72;
    private static final int DELTAC1 = 0x73;
    private static final int DELTAC2 = 0x74;
    private static final int DELTAC3 = 0x75;
    private static final int SROUND = 0x76;
    private static final int S45ROUND = 0x77;
    private static final int JROT = 0x78;
    private static final int JROF = 0x79;
    private static final int ROFF = 0x7A;
    private static final int RUTG = 0x7C;
    private static final int RDTG = 0x7D;
    private static final int SANGW = 0x7E;
    private static final int AA = 0x7F;

    private static final int FLIPPT = 0x80;
    private static final int FLIPRGON = 0x81;
    private static final int FLIPRGOFF = 0x82;
    private static final int SCANCTRL = 0x85;
    private static final int SDPVTL0 = 0x86;
    private static final int SDPVTL1 = 0x87;
    private static final int GETINFO = 0x88;
    private static final int IDEF = 0x89;
    private static final int ROLL = 0x8A;
    private static final int MAX = 0x8B;
    private static final int MIN = 0x8C;
    private static final int SCANTYPE = 0x8D;
    private static final int INSTCTRL = 0x8E;

    private static final int PUSHB = 0xB0;
    private static final int PUSHW = 0xB8;

    private static final int MDRP = 0xC0;
    private static final int MIRP = 0xE0;

    private static final String[] OPCODE_DESCRIPTIONS = {
            "SVTCAy    - Set both vectors to y",
            "SVTCAx    - Set both vectors to x",
            "SPVTCAy   - Sets projection vector to y",
            "SPVTCAx   - Sets projection vector to x",
            "SFVTCAy   - Sets freedom vector to y",
            "SFVTCAx   - Sets freedom vector to x",
            "SPVTL0    - Set projection vector to line",
            "SPVTL1    - Set projection vector perpendicular to line",
            "SFVTL0    - Set freedom vector to line",
            "SFVTL1    - Set freedom vector perpendicular to line",
            "SPVFS     - Sets the projection vector from the stack",
            "SFVFS     - Sets the freedom vector from the stack",
            "GPV       - Gets the projection vector onto the stack",
            "GFV       - Gets the freedom vector onto the stack",
            "SFVTPV    - Sets freedom vector to projection vector",
            "ISECT     - Set point to intersection of lines",
            "SRP0      - Set rp0",
            "SRP1      - Set rp1",
            "SRP2      - Set rp2",
            "SZP0      - Sets zp0",
            "SZP1      - Sets zp1",
            "SZP2      - Sets zp2",
            "SZPS      - Sets all zone pointers",
            "SLOOP     - Sets loop variable",
            "RTG       - Sets round state to grid",
            "RTHG      - Sets round state to half grid",
            "SMD       - Sets minimum distance",
            "ELSE      - ELSE",
            "JMPR      - Jump",
            "SCVTCI    - Set control value table cut in",
            "SSWCI     - Set single width cut in",
            "SSW       - Set single width",
            "DUP       - Duplicate the top stack element",
            "POP       - Remove the top stack element",
            "CLEAR     - Clear the stack",
            "SWAP      - Swap the top two stack elements",
            "DEPTH     - Returns depth of stack",
            "CINDEX    - Copy Indexed element to top of stack",
            "MINDEX    - Move Indexed element to top of stack",
            "ALIGNPTS  - Move points along fv to average of their pv positions","",
            "UTP       - Untouch point",
            "LOOPCALL  - Call a function many times",
            "CALL      - Call a function",
            "FDEF      - Define a function",
            "ENDF      - End a function definition",
            "MDAP0     - Sets a point as touched",
            "MDAP1     - Rounds a point along the pV and marks as touched",
            "IUPy      - Interpolate untouched points in the y axis",
            "IUPx      - Interpolate untouched points on the x axis",
            "SHP0      - Shift point using RP2",
            "SHP1      - Shift point using RP1",
            "SHC0      - Shift a contour using RP2",
            "SHC1      - Shift a contour using RP1",
            "SHZ0      - Shift a zone using RP2",
            "SHZ1      - Shift a zone using RP1",
            "SHPIX     - Move point along freedom vector",
            "IP        - Interpolate point",
            "MSIRP0    - Move stack indirect relative point",
            "MSIRP1    - Move stack indirect relative point",
            "ALIGNRP   - Align point to RP0",
            "RTDG      - Sets round state to double grid",
            "MIAP0     - Move point to CVT value",
            "MIAP1     - Move point to CVT using cut in and round",
            "NPUSHB    - Push N bytes from IS to stack",
            "NPUSHW    - Push N words from IS to stack",
            "WS        - Write Store",
            "RS        - Read Store",
            "WCVTP     - Write Control Value Table in Pixels",
            "RCVT      - Read Control Value Table",
            "GC0       - Get coords on the pv",
            "GC1       - Get original coords on the pv",
            "SCFS",
            "MD0       - Measure current distance",
            "MD1       - Measure original distance",
            "MPPEM     - Measure pixels per em in the direction of the projection vector",
            "MPS",
            "FLIPON    - Sets autoflip to true",
            "FLIPOFF   - Sets autoflip to false",
            "DEBUG     - Shouldn't be in live fonts",
            "LT        - Less Than",
            "LTEQ      - Less Than or Equal",
            "GT        - Greater Than",
            "GTEQ      - Greater Than or Equal",
            "EQ        - Equal",
            "NEQ       - Not Equal",
            "ODD       - Rounds, truncates, and returns if odd.",
            "EVEN      - Rounds, truncates, and returns if even",
            "IF        - IF",
            "EIF       - End IF",
            "AND       - Logical AND",
            "OR        - Logical OR",
            "NOT       - Logical NOT",
            "DELTAP1   - Delta exception p1",
            "SDB       - Set delta base",
            "SDS       - Set delta shift",
            "ADD       - Add two F26Dot6 numbers",
            "SUB       - Subtract a number from another",
            "DIV       - Divide two F26Dot6 numbers",
            "MUL       - Multiply two F26Dot6 numbers",
            "ABS       - Return the absolute value of a F26Dot6 number",
            "NEG       - Negate a number",
            "FLOOR     - Round a number down if it has a fractional component",
            "CEILING   - Round a number up if it has a fractional component",
            "ROUND00   - Round a number",
            "ROUND01   - Round a number",
            "ROUND10   - Round a number",
            "ROUND11   - Round a number",
            "NROUND00  - Compensate for engine characteristics",
            "NROUND01  - Compensate for engine characteristics",
            "NROUND10  - Compensate for engine characteristics",
            "NROUND11  - Compensate for engine characteristics",
            "WCVTF",
            "DELTAP2   - Delta exception p2",
            "DELTAP3   - Delta exception p3",
            "DELTAC1   - Delta exception c1",
            "DELTAC2   - Delta exception c2",
            "DELTAC3   - Delta exception c3",
            "SROUND    - Sets the roundState specifically",
            "S45ROUND  - Sets the round state for working at 45degrees",
            "JROT      - Jump Relative On True",
            "JROF      - Jump Relative On False",
            "ROFF      - Set round state to off","",
            "RUTG      - Set round state to up to grid",
            "RDTG      - Set round state to down to grid",
            "SANGW     - deprecated",
            "AA        - deprecated",
            "FLIPPT    - Flips a number of points on/off the curve",
            "FLIPRGON  - Flips a range of points onto the curve",
            "FLIPRGOFF - Flips a range of points off the curve", "", "",
            "SCANCTRL  - We don't scan convert, so only pops a value",
            "SDPVTL0   - Sets dual projection vector to line",
            "SDPVTL1   - Sets dual projection vector perpendicular to line",
            "GETINFO   - Gets info about current glyph & font engine",
            "IDEF      - Define an instruction",
            "ROLL      - Roll the top three stack elements",
            "MAX       - Returns the maximum of two values",
            "MIN       - Returns the minimum of two values",
            "SCANTYPE  - We don't scan convert, so only pops a value",
            "INSTCTRL  - Allows for setting flags to do with glyph execution"
    };

    //parameters for MDRP/MIRP
    private static final int paramRESETRP0 = 16;
    private static final int paramUSEMINDIST = 8;
    private static final int paramROUND = 4;

    public TTVM(final FontFile2 currentFontFile, final Maxp maxp) {
        stack = new Stack();
        cvt = new Cvt(currentFontFile);
        graphicsState = new TTGraphicsState();

        preProgram = readProgramTable(currentFontFile, FontFile2.PREP);
        fontProgram= readProgramTable(currentFontFile, FontFile2.FPGM);

        storage = new int[maxp.getMaxStorage()];
        functions = new HashMap();
        instructions = new HashMap();

        this.maxp = maxp;

        //For some reason some prePrograms use points, even though this shouldn't theoretically be possible... set up
        //empty arrays just in case
        final int len = maxp.getMaxPoints();
        x = new int[4][len];
        y = new int[4][len];
        curve = new boolean[2][len];
        contour = new boolean[2][len];
        touched = new boolean[4][len][2];
        x[TWILIGHT_ZONE] = new int[maxp.getMaxTwilightPoints()];
        y[TWILIGHT_ZONE] = new int[maxp.getMaxTwilightPoints()];

    }


    /**
     * Sets the scale variables for a font - if it's changed, the CVT needs to be rescaled, and the PreProgram
     * (sometimes called CVT program) must be run again. As it's always called before a glyph is processed, it's also
     * the ideal place to ensure that the font program has been run before anything else.
     * @param scaler The value to multiply any unscaled values by
     * @param ppem The number of pixels per em square
     * @param ptSize The point size of the text
     */
    public void setScaleVars(final double scaler, final double ppem, final double ptSize) {

        scalerRun = false;
        this.ppem = (int)(ppem+0.5);
        this.ptSize = ptSize;

        if (!fontProgramRun) {
            execute(fontProgram, graphicsState);   //Defines functions
            fontProgramRun=true;
        }

        if (scaler != this.scaler) {
            this.scaler = scaler;
            cvt.scale(scaler);
            execute(preProgram, graphicsState);   //Sets up scan conversion (or would if we did it), CVT and store
            scalerRun = true;
        }
    }


    /**
     * Takes the information about a glyph specified and modifies it according to the instructions provided.
     * @param instructions The instructions to execute
     * @param glyfX A list of scaled X coordinates for the points
     * @param glyfY A list of scaled Y coordinates for the points
     * @param curves Whether each point is on the curve or not
     * @param contours Whether each point is the last in a contour
     */
    public void processGlyph(final int[] instructions, final int[] glyfX, final int[] glyfY, final boolean[] curves, final boolean[] contours) {

        if (printCoordsAfterEachInstruction || watchAPoint) {
            printOut=true;
        }

        x[GLYPH_ZONE] = glyfX;
        x[ORIGINAL+GLYPH_ZONE] = new int[glyfX.length];
        System.arraycopy(x[GLYPH_ZONE],0,x[ORIGINAL+GLYPH_ZONE],0,x[GLYPH_ZONE].length);

        y[GLYPH_ZONE] = glyfY;
        y[ORIGINAL+GLYPH_ZONE] = new int[glyfY.length];
        System.arraycopy(y[GLYPH_ZONE],0,y[ORIGINAL+GLYPH_ZONE],0,y[GLYPH_ZONE].length);

        curve[GLYPH_ZONE] = curves;
        contour[GLYPH_ZONE] = contours;

        int max = maxp.getMaxTwilightPoints();
        if (glyfX.length > max) {
            max = glyfX.length;
        }

        touched = new boolean[4][max][2];

        if (printGlyphInstructions) {
            print(instructions);
        }

        stack = new Stack();

        //Sort out graphicsState
        TTGraphicsState gs;
        if (useDefaultGS) {
            //If INSTCTRL flag 2 set use default values for glyph instructions
            gs = new TTGraphicsState();
        } else {
            //If glyph create a copy so any changes are only for this glyph
            try {
                gs = (TTGraphicsState)graphicsState.clone();
                gs.resetForGlyph();
            } catch(final CloneNotSupportedException e) {
                LogWriter.writeLog("Exception: " + e.getMessage());

                gs = new TTGraphicsState();
            }
        }

        //Disable glyph instructions if previously set by INSTCTRL
        if (gs.instructControl != 0) {
            return;
        }

        //record starting position of watch point
        if (watchAPoint) {
            watchX = x[GLYPH_ZONE][watchPoint];
            watchY = y[GLYPH_ZONE][watchPoint];
        }

        if (showDebugWindow) {
            programToDebug = instructions;
            programToDebugIsData = getInstructionStreamIsData(programToDebug);
        }

        execute(instructions, gs);

        if (printCoordsAfterEachInstruction || watchAPoint) {
            printOut=false;
        }

        if (showDebugWindow) {
            final Thread t = new Thread("getDebugListData") {
                @Override
                public void run() {
                    runDebugger();
                }
            };
            t.start();
        }
    }


    /**
     * Execute a section of code
     * @param program The code to execute
     * @param gs The graphics state to use
     */
    private void execute(final int[] program, final TTGraphicsState gs) {
        if (program==null) {
            return;
        }

        if (showDebugWindow && stepInto && debugWindow != null && debugWindow.isVisible()) {
            codeStack.push(programToDebug);
            numberStack.push(debugPointer);
            functionsLineCount += program.length;
            setCurrentCodeForDebug(program, -1, !debuggerRunningInBackground);
            return;
        }

        for (int currentPointer = 0; currentPointer < program.length; currentPointer++) {
            if (printOut) {
                System.out.print(currentPointer + "\t");
            }
            currentPointer = process(program[currentPointer], currentPointer, program, gs);

            //Check for change in position of watch point
            if (watchAPoint &&
                    (watchX != x[GLYPH_ZONE][watchPoint] || watchY != y[GLYPH_ZONE][watchPoint])) {
                final int diffX = x[GLYPH_ZONE][watchPoint] - watchX;
                final int diffY = y[GLYPH_ZONE][watchPoint] - watchY;
                watchX = x[GLYPH_ZONE][watchPoint];
                watchY = y[GLYPH_ZONE][watchPoint];
                System.out.print("Changed point " + watchPoint + " (");

                if (diffX > 0) {
                    System.out.print("x+" + diffX);
                } else if (diffX != 0) {
                    System.out.print("x" + diffX);
                }

                if (diffX != 0 && diffY != 0) {
                    System.out.print(", ");
                }

                if (diffY > 0) {
                    System.out.print("y+" + diffY);
                } else if (diffY != 0) {
                    System.out.print("y" + diffY);
                }

                System.out.println(")");
                printCoords();

            }

//            if (printOut)
//                stack.print();
            if (printCoordsAfterEachInstruction && printOut) {
                printCoords();
            }

            //Check if errors have been encountered and cease execution if they have
            if (BaseTTGlyph.redecodePage) {
                return;
            }
        }

    }


    /**
     * Process a command
     * @param code The command to process
     * @param currentPointer The location in the program (passed in so can be modified and passed out)
     * @param program The program
     * @param gs The graphics state to use
     * @return The (possibly modified) location in the program
     */
    @SuppressWarnings("OverlyLongMethod")
    private int process(int code, int currentPointer, final int[] program, final TTGraphicsState gs) {

        //Warning supressed as originalPointer is used by debug code
        @SuppressWarnings("UnusedAssignment") final
        int originalPointer = currentPointer;

        //If it's reading data find how much to read & redirect to first command
        int bytesToRead=0;
        if (code >= 0xB0 && code <= 0xBF) {
            bytesToRead = code %8;
            code -= bytesToRead;
            bytesToRead++;
        }

        if (printOut && code < OPCODE_DESCRIPTIONS.length) {
            System.out.println(OPCODE_DESCRIPTIONS[code]);
        }

        try {

            switch(code) {
                case SVTCAy:
                    gs.freedomVector = TTGraphicsState.y_axis;
                    gs.projectionVector = TTGraphicsState.y_axis;
                    gs.dualProjectionVector = TTGraphicsState.y_axis;
                    break;

                case SVTCAx:
                    gs.freedomVector = TTGraphicsState.x_axis;
                    gs.projectionVector = TTGraphicsState.x_axis;
                    gs.dualProjectionVector = TTGraphicsState.x_axis;
                    break;

                case SPVTCAy:
                    gs.projectionVector = TTGraphicsState.y_axis;
                    gs.dualProjectionVector = TTGraphicsState.y_axis;
                    break;

                case SPVTCAx:
                    gs.projectionVector = TTGraphicsState.x_axis;
                    gs.dualProjectionVector = TTGraphicsState.x_axis;
                    break;

                case SFVTCAy:
                    gs.freedomVector = TTGraphicsState.y_axis;
                    break;

                case SFVTCAx:
                    gs.freedomVector = TTGraphicsState.x_axis;
                    break;

                case SPVTL0: {
                    final int p1 = stack.pop();
                    final int p2 = stack.pop();

                    //Note: The MS and Apple documentation disagree on which zone pointers to use - Apple
                    //matches Freetype so we're using that for now.

                    double xdiff = getDoubleFromF26Dot6(x[gs.zp2][p2] - x[gs.zp1][p1]);
                    double ydiff = getDoubleFromF26Dot6(y[gs.zp2][p2] - y[gs.zp1][p1]);
                    final double factor = Math.sqrt((xdiff * xdiff) + (ydiff * ydiff));
                    xdiff /= factor;
                    ydiff /= factor;
                    gs.projectionVector = TTGraphicsState.createVector(storeDoubleAsF2Dot14(xdiff), storeDoubleAsF2Dot14(ydiff));
                    gs.dualProjectionVector = gs.projectionVector;
                    break;
                }
                case SPVTL1: {
                    final int p1 = stack.pop();
                    final int p2 = stack.pop();

                    //Note: The MS and Apple documentation disagree on which zone pointers to use - Apple
                    //matches Freetype so we're using that for now.

                    double xdiff = getDoubleFromF26Dot6(x[gs.zp2][p2] - x[gs.zp1][p1]);
                    double ydiff = getDoubleFromF26Dot6(y[gs.zp2][p2] - y[gs.zp1][p1]);
                    final double factor = Math.sqrt((xdiff * xdiff) + (ydiff * ydiff));
                    xdiff /= factor;
                    ydiff /= factor;
                    gs.projectionVector = TTGraphicsState.createVector(storeDoubleAsF2Dot14(-ydiff), storeDoubleAsF2Dot14(xdiff));
                    gs.dualProjectionVector = gs.projectionVector;
                    break;
                }
                case SFVTL0: {
                    final int p1 = stack.pop();
                    final int p2 = stack.pop();
                    double xdiff = getDoubleFromF26Dot6(x[gs.zp1][p2] - x[gs.zp2][p1]);
                    double ydiff = getDoubleFromF26Dot6(y[gs.zp1][p2] - y[gs.zp2][p1]);
                    final double factor = Math.sqrt((xdiff * xdiff) + (ydiff * ydiff));
                    xdiff /= factor;
                    ydiff /= factor;
                    gs.freedomVector = TTGraphicsState.createVector(storeDoubleAsF2Dot14(xdiff), storeDoubleAsF2Dot14(ydiff));
                    break;
                }
                case SFVTL1: {
                    final int p1 = stack.pop();
                    final int p2 = stack.pop();
                    double xdiff = getDoubleFromF26Dot6(x[gs.zp1][p2] - x[gs.zp2][p1]);
                    double ydiff = getDoubleFromF26Dot6(y[gs.zp1][p2] - y[gs.zp2][p1]);
                    final double factor = Math.sqrt((xdiff * xdiff) + (ydiff * ydiff));
                    xdiff /= factor;
                    ydiff /= factor;
                    gs.freedomVector = TTGraphicsState.createVector(storeDoubleAsF2Dot14(-ydiff), storeDoubleAsF2Dot14(xdiff));
                    break;
                }
                case SPVFS: {
                    final int y = stack.pop();
                    final int x = stack.pop();
                    gs.projectionVector = TTGraphicsState.createVector(x, y);
                    gs.dualProjectionVector = gs.projectionVector;
                    break;
                }
                case SFVFS: {
                    final int y = stack.pop();
                    final int x = stack.pop();
                    gs.freedomVector = TTGraphicsState.createVector(x, y);
                    break;
                }
                case GPV: {
                    final int[] pv = TTGraphicsState.getVectorComponents(gs.projectionVector);
                    stack.push(pv[0]);
                    stack.push(pv[1]);
                    break;
                }
                case GFV: {
                    final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                    stack.push(fv[0]);
                    stack.push(fv[1]);
                    break;
                }
                case SFVTPV:
                    gs.freedomVector = gs.projectionVector;
                    break;

                case ISECT: {
                    final int b1 = stack.pop();        //line b end
                    final int b0 = stack.pop();        //line b start
                    final int a1 = stack.pop();        //line a end
                    final int a0 = stack.pop();        //line a start

                    //Note: The Apple and Microsoft specifications differ on which zone pointers to use
                    //for lines A and B. I had a look at Freetype which matched the MS documentation,
                    //so that's what we're using for now.
                    final int ax = x[gs.zp1][a0];
                    final int ay = y[gs.zp1][a0];
                    final int adx = x[gs.zp1][a1] - ax;  //change in x for line a
                    final int ady = y[gs.zp1][a1] - ay;

                    final int bx = x[gs.zp0][b0];
                    final int by = y[gs.zp0][b0];
                    final int bdx = x[gs.zp0][b1] - bx;
                    final int bdy = y[gs.zp0][b1] - by;

                    final int x;
                    final int y;
                    if (adx == 0 && bdx == 0) {       //both lines vertical
                        x = ax + bx / 2;
                        y = (ay + by + (ay + ady) + (by + bdy)) / 4;
                    } else if (adx == 0) {                                 //a vertical - use x & find b's y
                        final double bm = getDoubleFromF26Dot6(bdy) / getDoubleFromF26Dot6(bdx);
                        final double bc = getDoubleFromF26Dot6(by) - (bm * getDoubleFromF26Dot6(bx));
                        x = ax;
                        y = storeDoubleAsF26Dot6((bm * getDoubleFromF26Dot6(ax)) + bc);
                    } else if (bdx == 0) {                                 //b vertical - use x & find a's y
                        final double am = getDoubleFromF26Dot6(ady) / getDoubleFromF26Dot6(adx);
                        final double ac = getDoubleFromF26Dot6(ay) - (am * getDoubleFromF26Dot6(ax));
                        x = bx;
                        y = storeDoubleAsF26Dot6((am * getDoubleFromF26Dot6(bx)) + ac);
                    } else {                                               //neither line vertical - find mx+c form
                        final double am = getDoubleFromF26Dot6(ady) / getDoubleFromF26Dot6(adx);
                        final double ac = getDoubleFromF26Dot6(ay) - (am * getDoubleFromF26Dot6(ax));
                        final double bm = getDoubleFromF26Dot6(bdy) / getDoubleFromF26Dot6(bdx);
                        final double bc = getDoubleFromF26Dot6(by) - (bm * getDoubleFromF26Dot6(bx));
                        if (am == bm) {                                    //lines parallel
                            x = (ax + bx + (ax + adx) + (bx + bdx)) / 4;
                            y = (ay + by + (ay + ady) + (by + bdy)) / 4;
                        } else {                                           //lines intersect
                            final double fx = (bc - ac) / (am - bm);             //use rearranged mx1+c1 = mx2+c2 to find x
                            x = storeDoubleAsF26Dot6(fx);
                            y = storeDoubleAsF26Dot6((am * fx) + ac);       //substitute into y=mx+c to get y
                        }
                    }

                    //set point
                    final int p = stack.pop();
                    this.x[gs.zp2][p] = x;
                    this.y[gs.zp2][p] = y;
                    break;
                }
                case SRP0:
                    gs.rp0 = stack.pop();
                    break;

                case SRP1:
                    gs.rp1 = stack.pop();
                    break;

                case SRP2:
                    gs.rp2 = stack.pop();
                    break;

                case SZP0: {
                   // final int value = stack.pop();
//                    if (value > 1 || value < 0) {
//                        System.out.println("ZP0 set incorrectly!");
//                    }

                    gs.zp0 = stack.pop();
                    break;
                }
                case SZP1: {
                    final int value = stack.pop();
                    if (value > 1 || value < 0) {
                        System.out.println("ZP1 set incorrectly!");
                    }

                    gs.zp1 = value;
                    break;
                }
                case SZP2: {
                    gs.zp2 = stack.pop();
//                    if (value > 1 || value < 0) {
//                        System.out.println("ZP2 set incorrectly!");
//                    }

                   // gs.zp2 = value;
                    break;
                }
                case SZPS: {
                    final int value = stack.pop();
//                    if (value > 1 || value < 0) {
//                        System.out.println("All zone pointers set incorrectly!");
//                    }

                    gs.zp0 = value;
                    gs.zp1 = value;
                    gs.zp2 = value;
                    break;
                }
                case SLOOP:
                    gs.loop = stack.pop();
                    break;

                case RTG:
                    gs.roundState = TTGraphicsState.g;
                    gs.gridPeriod = 1.0;
                    break;

                case RTHG:
                    gs.roundState = TTGraphicsState.hg;
                    gs.gridPeriod = 1.0;
                    break;

                case SMD:
                    gs.minimumDistance = stack.pop();
                    break;

                case ELSE: {
                    //only processed at all if preceeding IF is true - skip until EIF
                    int curr = 0;
                    int nest = 0;
                    do {
                        //Deal with nested IF's
                        if (curr == EIF && nest != 0) {
                            nest--;
                        }

                        currentPointer++;
                        curr = program[currentPointer];

                        //deal with nested IF's
                        if (curr == IF) {
                            nest++;
                        }

                        //skip over any data in stream
                        if (curr == NPUSHB) {
                            currentPointer++;
                            currentPointer += program[currentPointer];
                        } else if (curr == NPUSHW) {
                            currentPointer++;
                            currentPointer += program[currentPointer] * 2;
                        } else if (curr >= PUSHB && curr <= PUSHB+7) {
                            currentPointer += (curr + 1) - PUSHB;
                        } else if (curr >= PUSHW && curr <= PUSHW+7) {
                            currentPointer += ((curr + 1) - PUSHW) * 2;
                        }
                    } while (curr != EIF || nest != 0);
                    break;
                }
                case JMPR: {
                    final int value = stack.pop();
                    currentPointer = (currentPointer + value) - 1;
                    if (currentPointer < 0) {
                        throw new RuntimeException("Jumped back further than the start of the instruction.");
                    }
                    break;
                }
                case SCVTCI:
                    gs.controlValueTableCutIn = stack.pop();
                    break;

                case SSWCI:
                    gs.singleWidthCutIn = stack.pop();
                    break;

                case SSW:
                    gs.singleWidthValue = stack.pop();
                    break;

                case DUP: {
                    final int value = stack.pop();
                    stack.push(value);
                    stack.push(value);
                    break;
                }
                case POP:
                    stack.pop();
                    break;

                case CLEAR:
                    stack = new Stack();
                    break;

                case SWAP: {
                    final int top = stack.pop();
                    final int under = stack.pop();
                    stack.push(top);
                    stack.push(under);
                    break;
                }
                case DEPTH: {
                    stack.push(stack.size());
                    break;
                }
                case CINDEX: {
                    final int key = stack.pop();
                    final int value = stack.elementAt(key);
                    stack.push(value);
                    break;
                }
                case MINDEX: {
                    final int key = stack.pop();
                    final int value = stack.remove(key);
                    stack.push(value);
                    break;
                }
                case ALIGNPTS: {
                    final int p1 = stack.pop();
                    final int p2 = stack.pop();
                    final int p1loc = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp1][p1], y[gs.zp1][p1]);
                    final int p2loc = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][p2], y[gs.zp0][p2]);
                    final int target = (p1loc + p2loc) / 2;
                    final int[] shift = gs.getFVMoveforPVDistance(target - p1loc);
                    x[gs.zp1][p1] += shift[0];
                    y[gs.zp1][p1] += shift[1];
                    x[gs.zp0][p2] -= shift[0];
                    y[gs.zp0][p2] -= shift[1];

                    final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                    if (fv[0] != 0) {
                        touched[gs.zp1][p1][0] = true;
                        touched[gs.zp0][p2][0] = true;
                    }
                    if (fv[1] != 0) {
                        touched[gs.zp1][p1][1] = true;
                        touched[gs.zp0][p2][1] = true;
                    }
                    break;
                }
                case UTP: {
                    final int p = stack.pop();
                    if (gs.freedomVector == TTGraphicsState.x_axis) {
                        touched[gs.zp0][p][0] = false;
                    } else if (gs.freedomVector == TTGraphicsState.y_axis) {
                        touched[gs.zp0][p][1] = false;
                    } else {
                        touched[gs.zp0][p][0] = false;
                        touched[gs.zp0][p][1] = false;
                    }
                    break;
                }
                case LOOPCALL: {
                    final int func = stack.pop();
                    final int count = stack.pop();
                    final int[] function = functions.get(func);

                    //debug code
                    if (printGlyphInstructions && scalerRun) {
                        System.out.println("Function " + func + " on line " + currentPointer);
                        print(function);
                        System.out.println("");
                    }

                    for (int i = 0; i < count; i++) {
                        execute(function, gs);
                    }
                    if (printOut) {
                        System.out.println("LOOPCALL finished");
                    }
                    break;
                }
                case CALL: {
                    final int func = stack.pop();
                    final int[] function = functions.get(func);

                    //debug code
                    if (printGlyphInstructions && scalerRun) {
                        System.out.println("Function " + func + " on line " + currentPointer);
                        print(function);
                        System.out.println("");
                    }

                    execute(function, gs);
                    if (printOut) {
                        System.out.println("CALL finished");
                    }
                    break;
                }
                case FDEF: {
                    final int num = stack.pop();
                    final int start = currentPointer;

                    //work out length
                    int curr;
                    do {
                        currentPointer++;
                        curr = program[currentPointer];

                        //skip over any data in stream
                        if (curr == NPUSHB) {
                            currentPointer++;
                            currentPointer += program[currentPointer];
                        } else if (curr == NPUSHW) {
                            currentPointer++;
                            currentPointer += program[currentPointer] * 2;
                        } else if (curr >= PUSHB && curr <= PUSHB+7) {
                            currentPointer += (curr + 1) - PUSHB;
                        } else if (curr >= PUSHW && curr <= PUSHW+7) {
                            currentPointer += ((curr + 1) - PUSHW) * 2;
                        }
                    } while (curr != ENDF);
                    final int len = (currentPointer - start) - 1;
                    currentPointer = start;

                    //create function
                    final int[] function = new int[len];
                    for (int i = 0; i < len; i++) {
                        currentPointer++;
                        function[i] = program[currentPointer];
                    }
                    functions.put(num, function);

                    //skip past ENDF
                    currentPointer++;
                    break;
                }
                case ENDF:
                    //No definition required
                    break;

                case MDAP0: {
                    final int p = stack.pop();
                    gs.rp0 = p;
                    gs.rp1 = p;
                    if (gs.freedomVector == TTGraphicsState.x_axis) {
                        touched[gs.zp0][p][0] = true;
                    } else if (gs.freedomVector == TTGraphicsState.y_axis) {
                        touched[gs.zp0][p][1] = true;
                    } else {
                        touched[gs.zp0][p][0] = true;
                        touched[gs.zp0][p][1] = true;
                    }
                    break;
                }
                case MDAP1: {
                    final int p = stack.pop();
                    gs.rp0 = p;
                    gs.rp1 = p;

                    int m = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][p], y[gs.zp0][p]);
                    m = storeDoubleAsF26Dot6(gs.round(getDoubleFromF26Dot6(m))) - m;
                    final int[] shift = gs.getFVMoveforPVDistance(m);

                    x[gs.zp0][p] += shift[0];
                    y[gs.zp0][p] += shift[1];

                    final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                    if (fv[0] != 0) {
                        touched[gs.zp0][p][0] = true;
                    }
                    if (fv[1] != 0) {
                        touched[gs.zp0][p][1] = true;
                    }
                    break;
                }
                case IUPy: {
                    interpolateUntouchedPoints(IUPy);
                    break;
                }
                case IUPx: {
                    interpolateUntouchedPoints(IUPx);
                    break;
                }
                case SHP0: {
                    for (int i = 0; i < gs.loop; i++) {
                        final int p = stack.pop();

                        if (p > x[gs.zp2].length || gs.rp2 > x[gs.zp1].length) {
                            LogWriter.writeLog("Trying to use a point which doesn't exist! (SHP0, zone " + gs.zp2 + ')');
                            break;
                        }

                        final int newRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp1][gs.rp2], y[gs.zp1][gs.rp2]);
                        final int oldRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[ORIGINAL + gs.zp1][gs.rp2], y[ORIGINAL + gs.zp1][gs.rp2]);
                        final int pMove = newRP - oldRP;
                        final int[] shift = gs.getFVMoveforPVDistance(pMove);
                        x[gs.zp2][p] += shift[0];
                        y[gs.zp2][p] += shift[1];

                        final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                        if (fv[0] != 0) {
                            touched[gs.zp2][p][0] = true;
                        }
                        if (fv[1] != 0) {
                            touched[gs.zp2][p][1] = true;
                        }
                    }
                    gs.loop = 1;
                    break;
                }
                case SHP1: {
                    for (int i = 0; i < gs.loop; i++) {
                        final int p = stack.pop();
                        final int newRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][gs.rp1], y[gs.zp0][gs.rp1]);
                        final int oldRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[ORIGINAL + gs.zp0][gs.rp1], y[ORIGINAL + gs.zp0][gs.rp1]);
                        final int shift = newRP - oldRP;
                        final int[] move = gs.getFVMoveforPVDistance(shift);
                        x[gs.zp2][p] += move[0];
                        y[gs.zp2][p] += move[1];
                        if (move[0] != 0) {
                            touched[gs.zp2][p][0] = true;
                        }
                        if (move[1] != 0) {
                            touched[gs.zp2][p][1] = true;
                        }
                    }
                    gs.loop = 1;
                    break;
                }
                case SHC0: {
                    final int c = stack.pop();

                    //Note: The spec doesn't clearly say how a contour is identified - for now we are finding
                    //all of the contours and using the cth one, but another possibility is that you use the
                    //contour point c is a part of.

                    //get start and length of contours
                    final int[] contourLengths = new int[contour[GLYPH_ZONE].length];
                    final int[] contourStarts = new int[contour[GLYPH_ZONE].length];
                    int contourCount = 0, lastContour = 0;
                    contourStarts[0] = 0;
                    for (int i = 0; i < contour[GLYPH_ZONE].length; i++) {
                        if (contour[GLYPH_ZONE][i]) {
                            contourStarts[contourCount + 1] = i + 1;
                            contourLengths[contourCount] = i + 1 - lastContour;
                            lastContour = i + 1;
                            contourCount++;
                        }
                    }

                    //Get move required
                    final int newRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp1][gs.rp2], y[gs.zp1][gs.rp2]);
                    final int oldRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[ORIGINAL + gs.zp1][gs.rp2], y[ORIGINAL + gs.zp1][gs.rp2]);
                    final int shift = newRP - oldRP;
                    final int[] move = gs.getFVMoveforPVDistance(shift);

                    //Move contour
                    for (int i = contourStarts[c]; i < contourStarts[c] + contourLengths[c]; i++) {
                        if (gs.zp1 == gs.zp2 || i != gs.rp2) {
                            x[gs.zp2][i] += move[0];
                            y[gs.zp2][i] += move[1];
                        }
                    }
                    break;
                }
                case SHC1: {
                    final int c = stack.pop();

                    //Note: The spec doesn't clearly say how a contour is identified - for now we are finding
                    //all of the contours and using the cth one, but another possibility is that you use the
                    //contour point c is a part of.

                    //get start and length of contours
                    final int[] contourLengths = new int[contour[GLYPH_ZONE].length];
                    final int[] contourStarts = new int[contour[GLYPH_ZONE].length];
                    int contourCount = 0, lastContour = 0;
                    contourStarts[0] = 0;
                    for (int i = 0; i < contour[GLYPH_ZONE].length; i++) {
                        if (contour[GLYPH_ZONE][i]) {
                            contourStarts[contourCount + 1] = i + 1;
                            contourLengths[contourCount] = i + 1 - lastContour;
                            lastContour = i + 1;
                            contourCount++;
                        }
                    }

                    //Get move required
                    final int newRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][gs.rp1], y[gs.zp0][gs.rp1]);
                    final int oldRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[ORIGINAL + gs.zp0][gs.rp1], y[ORIGINAL + gs.zp0][gs.rp1]);
                    final int shift = newRP - oldRP;
                    final int[] move = gs.getFVMoveforPVDistance(shift);

                    //Move contour
                    for (int i = contourStarts[c]; i < contourStarts[c] + contourLengths[c]; i++) {
                        if (gs.zp2 != gs.zp0 || i != gs.rp1) {
                            x[gs.zp2][i] += move[0];
                            y[gs.zp2][i] += move[1];
                        }
                    }
                    break;
                }
                case SHZ0: {
                    final int z = stack.pop();

                    //Get move required
                    final int newRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp1][gs.rp2], y[gs.zp1][gs.rp2]);
                    final int oldRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[ORIGINAL + gs.zp1][gs.rp2], y[ORIGINAL + gs.zp1][gs.rp2]);
                    final int shift = newRP - oldRP;
                    final int[] move = gs.getFVMoveforPVDistance(shift);

                    //Move zone
                    for (int i = 0; i < x[z].length; i++) {
                        if (z != gs.zp1 || i != gs.rp2) {
                            x[z][i] += move[0];
                            y[z][i] += move[1];
                        }
                    }
                    break;
                }
                case SHZ1: {
                    final int z = stack.pop();

                    //Get move required
                    final int newRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][gs.rp1], y[gs.zp0][gs.rp1]);
                    final int oldRP = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[ORIGINAL + gs.zp0][gs.rp1], y[ORIGINAL + gs.zp0][gs.rp1]);
                    final int shift = newRP - oldRP;
                    final int[] move = gs.getFVMoveforPVDistance(shift);

                    //Move zone
                    for (int i = 0; i < x[z].length; i++) {
                        if (z != gs.zp0 || i != gs.rp1) {
                            x[z][i] += move[0];
                            y[z][i] += move[1];
                        }
                    }
                    break;
                }
                case SHPIX: {
                    final int magnitude = stack.pop();
                    final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                    for (int i = 0; i < gs.loop; i++) {
                        final int point = stack.pop();
                        x[gs.zp2][point] += (magnitude * getDoubleFromF2Dot14(fv[0])/64);
                        y[gs.zp2][point] += (magnitude * getDoubleFromF2Dot14(fv[1])/64);
                        if (fv[0] != 0) {
                            touched[gs.zp2][point][0] = true;
                        }
                        if (fv[1] != 0) {
                            touched[gs.zp2][point][1] = true;
                        }
                    }
                    gs.loop = 1;
                    break;
                }
                case IP: {
                    for (int i = 0; i < gs.loop; i++) {
                        final int p = stack.pop();

                        if (p < 0 || p > x[gs.zp2].length || gs.rp1 > x[gs.zp0].length || gs.rp2 > x[gs.zp1].length) {
                            LogWriter.writeLog("Trying to use a point which doesn't exist! (IP, zone " + gs.zp2 + ')');
                            break;
                        }

                        //work out points relationship to reference points
                        final int originalRP1 = TTGraphicsState.getCoordsOnVector(gs.dualProjectionVector, x[ORIGINAL + gs.zp0][gs.rp1], y[ORIGINAL + gs.zp0][gs.rp1]);
                        final int originalRP2 = TTGraphicsState.getCoordsOnVector(gs.dualProjectionVector, x[ORIGINAL + gs.zp1][gs.rp2], y[ORIGINAL + gs.zp1][gs.rp2]);

                        //The instruction is illegal if rp1 and rp2 occupy the same position on the projection vector
                        if (originalRP1 != originalRP2) {

                            final int originalP = TTGraphicsState.getCoordsOnVector(gs.dualProjectionVector, x[ORIGINAL + gs.zp2][p], y[ORIGINAL + gs.zp2][p]);
                            final double pos = (double) (originalP - originalRP1) / (originalRP2 - originalRP1);

                            //find move along PV required
                            final int newRP1 = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][gs.rp1], y[gs.zp0][gs.rp1]);
                            final int newRP2 = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp1][gs.rp2], y[gs.zp1][gs.rp2]);
                            final int pMove = (int) (((pos * (newRP2 - newRP1)) + newRP1) + 0.5) - originalP;

                            //calculate and apply shift
                            final int[] shift = gs.getFVMoveforPVDistance(pMove);
                            x[gs.zp2][p] += shift[0];
                            y[gs.zp2][p] += shift[1];

                            final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                            if (fv[0] != 0) {
                                touched[gs.zp2][p][0] = true;
                            }
                            if (fv[1] != 0) {
                                touched[gs.zp2][p][1] = true;
                            }
                        }
                    }
                    gs.loop = 1;
                    break;
                }
                case MSIRP0: {
                    final int d = stack.pop();
                    final int p = stack.pop();


                    //move to rp0 + d
                    final int[] shift = gs.getFVMoveforPVDistance(d -
                            (TTGraphicsState.getCoordsOnVector(gs.projectionVector,x[gs.zp1][p],y[gs.zp1][p])-TTGraphicsState.getCoordsOnVector(gs.projectionVector,x[gs.zp0][gs.rp0],y[gs.zp0][gs.rp0])));
                    x[gs.zp1][p] += shift[0];
                    y[gs.zp1][p] += shift[1];

                    //Mark as touched
                    final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                    if (fv[0] != 0) {
                        touched[gs.zp1][p][0] = true;
                    }
                    if (fv[1] != 0) {
                        touched[gs.zp1][p][1] = true;
                    }

                    //inexplicable value settings as described in the guide..
                    gs.rp1 = gs.rp0;
                    gs.rp2 = p;
                    break;
                }
                case MSIRP1: {
                    final int d = stack.pop();
                    final int p = stack.pop();

                    //move to rp0 + d
                    final int[] shift = gs.getFVMoveforPVDistance(d -
                            (TTGraphicsState.getCoordsOnVector(gs.projectionVector,x[gs.zp1][p],y[gs.zp1][p])-TTGraphicsState.getCoordsOnVector(gs.projectionVector,x[gs.zp0][gs.rp0],y[gs.zp0][gs.rp0])));
                    x[gs.zp1][p] += shift[0];
                    y[gs.zp1][p] += shift[1];

                    //Mark as touched
                    final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                    if (fv[0] != 0) {
                        touched[gs.zp1][p][0] = true;
                    }
                    if (fv[1] != 0) {
                        touched[gs.zp1][p][1] = true;
                    }

                    //inexplicable value settings as described in the guide..
                    gs.rp1 = gs.rp0;
                    gs.rp2 = p;
                    gs.rp0 = p;
                    break;
                }
                case ALIGNRP:
                    for (int i = 0; i < gs.loop; i++) {
                        final int p = stack.pop();
                        final int target = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][gs.rp0], y[gs.zp0][gs.rp0]);
                        final int pMove = target - TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp1][p], y[gs.zp1][p]);
                        final int[] shift = gs.getFVMoveforPVDistance(pMove);
                        x[gs.zp1][p] += shift[0];
                        y[gs.zp1][p] += shift[1];

                        final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                        if (fv[0] != 0) {
                            touched[gs.zp1][p][0] = true;
                        }
                        if (fv[1] != 0) {
                            touched[gs.zp1][p][1] = true;
                        }
                    }
                    gs.loop = 1;
                    break;

                case RTDG:
                    gs.roundState = TTGraphicsState.dg;
                    gs.gridPeriod = 1.0;
                    break;

                case MIAP0: {
                    final int cvtEntry = stack.pop();
                    final int p = stack.pop();
                    final int target = cvt.get(cvtEntry);
                    final int current = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][p], y[gs.zp0][p]);
                    final int pMove = target - current;
                    final int[] shift = gs.getFVMoveforPVDistance(pMove);
                    x[gs.zp0][p] += shift[0];
                    y[gs.zp0][p] += shift[1];

                    final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                    if (fv[0] != 0) {
                        touched[gs.zp0][p][0] = true;
                    }
                    if (fv[1] != 0) {
                        touched[gs.zp0][p][1] = true;
                    }

                    gs.rp0 = gs.rp1 = p;
                    break;
                }
                case MIAP1: {
                    final int cvtEntry = stack.pop();
                    final int p = stack.pop();
                    int target = cvt.get(cvtEntry);
                    final int current = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][p], y[gs.zp0][p]);

                    //Test if close enough to use cut in value
                    int pMove = target - current;
                    if (Math.abs(pMove) > gs.controlValueTableCutIn) {
                        target = current;
                    }

                    //round
                    target = storeDoubleAsF26Dot6(gs.round(getDoubleFromF26Dot6(target)));

                    //Get and use shift
                    pMove = target - current;
                    final int[] shift = gs.getFVMoveforPVDistance(pMove);
                    x[gs.zp0][p] += shift[0];
                    y[gs.zp0][p] += shift[1];

                    final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                    if (fv[0] != 0) {
                        touched[gs.zp0][p][0] = true;
                    }
                    if (fv[1] != 0) {
                        touched[gs.zp0][p][1] = true;
                    }

                    gs.rp0 = gs.rp1 = p;
                    break;
                }
                case NPUSHB:
                    currentPointer++;
                    currentPointer = readFromIS(program[currentPointer], false, currentPointer, program);
                    break;

                case NPUSHW:
                    currentPointer++;
                    currentPointer = readFromIS(program[currentPointer], true, currentPointer, program);
                    break;

                case WS: {
                    final int value = stack.pop();
                    final int key = stack.pop();
                    storage[key] = value;
                    break;
                }
                case RS: {
                    final int key = stack.pop();
                    stack.push(storage[key]);
                    break;
                }
                case WCVTP: {
                    final int value = stack.pop();
                    final int key = stack.pop();
                    cvt.putInPixels(key, value);
                    break;
                }
                case RCVT: {
                    final int key = stack.pop();
                    stack.push(cvt.get(key));
                    break;
                }
                case GC0: {
                    final int p = stack.pop();
                    stack.push(TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp2][p], y[gs.zp2][p]));
                    break;
                }
                case GC1: {
                    final int p = stack.pop();
                    stack.push(TTGraphicsState.getCoordsOnVector(gs.dualProjectionVector, x[ORIGINAL + gs.zp2][p], y[ORIGINAL + gs.zp2][p]));
                    break;
                }
                case SCFS: {
                    final int value = stack.pop();
                    final int p = stack.pop();
                    final int current = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp2][p], y[gs.zp2][p]);
                    final int pMove = value - current;
                    final int[] shift = gs.getFVMoveforPVDistance(pMove);
                    x[gs.zp2][p] += shift[0];
                    y[gs.zp2][p] += shift[1];

                    final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                    if (fv[0] != 0) {
                        touched[gs.zp2][p][0] = true;
                    }
                    if (fv[1] != 0) {
                        touched[gs.zp2][p][1] = true;
                    }
                    break;
                }
                case MD0: {
                    final int p1 = stack.pop();
                    final int p2 = stack.pop();
                    final int distance = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp1][p2], y[gs.zp1][p2]) -
                            TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][p1], y[gs.zp0][p1]);
                    stack.push(distance);
                    break;
                }
                case MD1: {
                    final int p1 = stack.pop();
                    final int p2 = stack.pop();
                    final int distance = TTGraphicsState.getCoordsOnVector(gs.dualProjectionVector, x[ORIGINAL + gs.zp1][p2], y[ORIGINAL + gs.zp1][p2]) -
                            TTGraphicsState.getCoordsOnVector(gs.dualProjectionVector, x[ORIGINAL + gs.zp0][p1], y[ORIGINAL + gs.zp0][p1]);
                    stack.push(distance);
                    break;
                }
                case MPPEM: {
                    int pvppem = TTGraphicsState.getCoordsOnVector(gs.projectionVector, (int) (ppem * 64), (int) (ppem * 64)) / 64;
                    if (pvppem < 0) {
                        pvppem = -pvppem;
                    }
                    stack.push(pvppem);
                    break;
                }
                case MPS: {
                    stack.push((int) (ptSize * 64));
                    break;
                }
                case FLIPON:
                    gs.autoFlip = true;
                    break;

                case FLIPOFF:
                    gs.autoFlip = false;
                    break;

                case DEBUG:
                    //shouldn't be in a live font - pops a number
                    stack.pop();
                    break;

                case LT: {
                    final int right = stack.pop();
                    final int left = stack.pop();

                    if (left < right) {
                        stack.push(1);
                    } else {
                        stack.push(0);
                    }
                    break;
                }
                case LTEQ: {
                    final int right = stack.pop();
                    final int left = stack.pop();

                    if (left <= right) {
                        stack.push(1);
                    } else {
                        stack.push(0);
                    }
                    break;
                }
                case GT: {
                    final int right = stack.pop();
                    final int left = stack.pop();

                    if (left > right) {
                        stack.push(1);
                    } else {
                        stack.push(0);
                    }
                    break;
                }
                case GTEQ: {
                    final int right = stack.pop();
                    final int left = stack.pop();

                    if (left >= right) {
                        stack.push(1);
                    } else {
                        stack.push(0);
                    }
                    break;
                }
                case EQ: {
                    final int right = stack.pop();
                    final int left = stack.pop();
                    if (left == right) {
                        stack.push(1);
                    } else {
                        stack.push(0);
                    }
                    break;
                }
                case NEQ: {
                    final int right = stack.pop();
                    final int left = stack.pop();
                    if (left != right) {
                        stack.push(1);
                    } else {
                        stack.push(0);
                    }
                    break;
                }
                case ODD: {
                    int value = stack.pop();
                    value = storeDoubleAsF26Dot6(gs.round(getDoubleFromF26Dot6(value)));
                    value = (value >> 6) % 2;     //remove fractional part and test if odd
                    stack.push(value);
                    break;
                }
                case EVEN: {
                    int value = stack.pop();
                    value = storeDoubleAsF26Dot6(gs.round(getDoubleFromF26Dot6(value)));
                    value = ((value >> 6) + 1) % 2;   //remove fractional part , add 1 and test if odd
                    stack.push(value);
                    break;
                }
                case IF: {
                    //continue or move forward to else/endif
                    final boolean value = stack.pop() != 0;
                    if (!value) {
                        int curr = 0;
                        int nest = 0;
                        do {
                            //deal with nested IF's
                            if (curr == EIF && nest != 0) {
                                nest--;
                            }

                            currentPointer++;
                            curr = program[currentPointer];

                            //deal with nested IF's
                            if (curr == IF) {
                                nest++;
                            }

                            //skip over any data in stream
                            if (curr == NPUSHB) {
                                currentPointer++;
                                currentPointer += program[currentPointer];
                            } else if (curr == NPUSHW) {
                                currentPointer++;
                                currentPointer += program[currentPointer] * 2;
                            } else if (curr >= PUSHB && curr <= PUSHB+7) {
                                currentPointer += (curr + 1) - PUSHB;
                            } else if (curr >= PUSHW && curr <= PUSHW+7) {
                                currentPointer += ((curr + 1) - PUSHW) * 2;
                            }
                        } while ((curr != ELSE && curr != EIF) || nest != 0);
                    }
                    break;
                }
                case EIF:
                    //no implementation necessary
                    break;

                case AND: {
                    final boolean right = stack.pop() != 0;
                    final boolean left = stack.pop() != 0;
                    if (left && right) {
                        stack.push(1);
                    } else {
                        stack.push(0);
                    }
                    break;
                }
                case OR: {
                    final boolean right = stack.pop() != 0;
                    final boolean left = stack.pop() != 0;
                    if (left || right) {
                        stack.push(1);
                    } else {
                        stack.push(0);
                    }
                    break;
                }
                case NOT: {
                    final boolean value = stack.pop() != 0;
                    if (!value) {
                        stack.push(1);
                    } else {
                        stack.push(0);
                    }
                    break;
                }
                case DELTAP1: {
                    final int loop = stack.pop();
                    for (int i = 0; i < loop; i++) {
                        final int p = stack.pop();
                        final int arg = stack.pop();

                        //test ppm
                        final int ppem = gs.deltaBase + (arg >> 4);
                        if (ppem == this.ppem) {

                            //get move
                            int mag = (arg & 0xF) - 7;
                            if (mag <= 0) {
                                mag -= 1;
                            }
                            final int pMove = storeDoubleAsF26Dot6(mag * (1 / Math.pow(2, gs.deltaShift)));

                            //move point
                            final int[] shift = gs.getFVMoveforPVDistance(pMove);
                            x[gs.zp0][p] += shift[0];
                            y[gs.zp0][p] += shift[1];

                            final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                            if (fv[0] != 0) {
                                touched[gs.zp0][p][0] = true;
                            }
                            if (fv[1] != 0) {
                                touched[gs.zp0][p][1] = true;
                            }
                        }
                    }
                    break;
                }
                case SDB:
                    gs.deltaBase = stack.pop();
                    break;

                case SDS:
                    gs.deltaShift = stack.pop();
                    break;

                case ADD:
                    stack.push(stack.pop() + stack.pop());
                    break;

                case SUB: {
                    final int right = stack.pop();
                    final int left = stack.pop();
                    stack.push(left - right);
                    break;
                }
                case DIV: {
                    final int right = stack.pop();
                    final int left = stack.pop();
                    if (right != 0) {
                        stack.push((left * 64) / right);
                    } else {
                        stack.push(0);
                    }
                    break;
                }
                case MUL: {
                    final int a = stack.pop();
                    final int b = stack.pop();
                    stack.push(a * b / 64);
                    break;
                }
                case ABS: {
                    int value = stack.pop();
                    if (value < 0) {
                        value = -value;
                    }
                    stack.push(value);
                    break;
                }
                case NEG:
                    stack.push(-stack.pop());
                    break;

                case FLOOR:
                    stack.push((stack.pop() >> 6) << 6);
                    break;

                case CEILING: {
                    int value = stack.pop();
                    if ((value & 63) != 0) {
                        value = (((value >> 6) + 1) << 6);
                    }
                    stack.push(value);
                    break;
                }
                case ROUND00: {
                    int n = stack.pop();
                    n = engineCompensation(n, 0);
                    final double num = getDoubleFromF26Dot6(n);
                    stack.push(storeDoubleAsF26Dot6(gs.round(num)));
                    break;
                }
                case ROUND01: {
                    int n = stack.pop();
                    n = engineCompensation(n, 1);
                    final double num = getDoubleFromF26Dot6(n);
                    stack.push(storeDoubleAsF26Dot6(gs.round(num)));
                    break;
                }
                case ROUND10: {
                    int n = stack.pop();
                    n = engineCompensation(n, 2);
                    final double num = getDoubleFromF26Dot6(n);
                    stack.push(storeDoubleAsF26Dot6(gs.round(num)));
                    break;
                }
                case ROUND11: {
                    int n = stack.pop();
                    n = engineCompensation(n, 3);
                    final double num = getDoubleFromF26Dot6(n);
                    stack.push(storeDoubleAsF26Dot6(gs.round(num)));
                    break;
                }
                case NROUND00:
                    stack.push(engineCompensation(stack.pop(), 0));
                    break;

                case NROUND01:
                    stack.push(engineCompensation(stack.pop(), 1));
                    break;

                case NROUND10:
                    stack.push(engineCompensation(stack.pop(), 2));
                    break;

                case NROUND11:
                    stack.push(engineCompensation(stack.pop(), 3));
                    break;

                case WCVTF: {
                    final int value = stack.pop();
                    final int key = stack.pop();
                    cvt.putInFUnits(key, value);
                    break;
                }
                case DELTAP2: {
                    final int loop = stack.pop();
                    for (int i = 0; i < loop; i++) {
                        final int p = stack.pop();
                        final int arg = stack.pop();

                        //test ppm
                        final int ppem = gs.deltaBase + 16 + (arg >> 4);
                        if (ppem == this.ppem) {

                            //get move
                            int mag = (arg & 0xF) - 7;
                            if (mag <= 0) {
                                mag -= 1;
                            }
                            final int pMove = storeDoubleAsF26Dot6(mag * (1 / Math.pow(2, gs.deltaShift)));

                            //move point
                            final int[] shift = gs.getFVMoveforPVDistance(pMove);
                            x[gs.zp0][p] += shift[0];
                            y[gs.zp0][p] += shift[1];

                            final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                            if (fv[0] != 0) {
                                touched[gs.zp0][p][0] = true;
                            }
                            if (fv[1] != 0) {
                                touched[gs.zp0][p][1] = true;
                            }
                        }
                    }
                    break;
                }
                case DELTAP3: {
                    final int loop = stack.pop();
                    for (int i = 0; i < loop; i++) {
                        final int p = stack.pop();
                        final int arg = stack.pop();

                        //test ppm
                        final int ppem = gs.deltaBase + 32 + (arg >> 4);
                        if (ppem == this.ppem) {

                            //get move
                            int mag = (arg & 0xF) - 7;
                            if (mag <= 0) {
                                mag -= 1;
                            }
                            final int pMove = storeDoubleAsF26Dot6(mag * (1 / Math.pow(2, gs.deltaShift)));

                            //move point
                            final int[] shift = gs.getFVMoveforPVDistance(pMove);
                            x[gs.zp0][p] += shift[0];
                            y[gs.zp0][p] += shift[1];

                            final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                            if (fv[0] != 0) {
                                touched[gs.zp0][p][0] = true;
                            }
                            if (fv[1] != 0) {
                                touched[gs.zp0][p][1] = true;
                            }
                        }
                    }
                    break;
                }
                case DELTAC1: {
                    final int loop = stack.pop();
                    for (int i = 0; i < loop; i++) {
                        final int cvtEntry = stack.pop();
                        final int arg = stack.pop();

                        //test ppm
                        final int ppem = gs.deltaBase + (arg >> 4);
                        if (ppem == this.ppem) {

                            //get change
                            int mag = (arg & 0xF) - 7;
                            if (mag <= 0) {
                                mag -= 1;
                            }
                            final int change = storeDoubleAsF26Dot6(mag * (1 / Math.pow(2, gs.deltaShift)));

                            //change value
                            int value = cvt.get(cvtEntry);
                            value += change;
                            cvt.putInPixels(cvtEntry, value);
                        }
                    }
                    break;
                }
                case DELTAC2: {
                    final int loop = stack.pop();
                    for (int i = 0; i < loop; i++) {
                        final int cvtEntry = stack.pop();
                        final int arg = stack.pop();

                        //test ppm
                        final int ppem = gs.deltaBase + 16 + (arg >> 4);
                        if (ppem == this.ppem) {

                            //get change
                            int mag = (arg & 0xF) - 7;
                            if (mag <= 0) {
                                mag -= 1;
                            }
                            final int change = storeDoubleAsF26Dot6(mag * (1 / Math.pow(2, gs.deltaShift)));

                            //change value
                            int value = cvt.get(cvtEntry);
                            value += change;
                            cvt.putInPixels(cvtEntry, value);
                        }
                    }
                    break;
                }
                case DELTAC3: {
                    final int loop = stack.pop();
                    for (int i = 0; i < loop; i++) {
                        final int cvtEntry = stack.pop();
                        final int arg = stack.pop();

                        //test ppm
                        final int ppem = gs.deltaBase + 32 + (arg >> 4);
                        if (ppem == this.ppem) {

                            //get change
                            int mag = (arg & 0xF) - 7;
                            if (mag <= 0) {
                                mag -= 1;
                            }
                            final int change = storeDoubleAsF26Dot6(mag * (1 / Math.pow(2, gs.deltaShift)));

                            //change value
                            int value = cvt.get(cvtEntry);
                            value += change;
                            cvt.putInPixels(cvtEntry, value);
                        }
                    }
                    break;
                }
                case SROUND:
                    gs.roundState = stack.pop();
                    gs.gridPeriod = 1.0;
                    break;

                case S45ROUND:
                    gs.roundState = stack.pop();
                    gs.gridPeriod = 0.7071067811865476;  //Math.sqrt(2)/2
                    break;

                case JROT: {
                    final boolean jump = stack.pop() != 0;
                    final int amount = stack.pop();
                    if (jump) {
                        currentPointer = currentPointer + amount - 1;
                    }
                    break;
                }
                case JROF: {
                    final boolean jump = stack.pop() != 0;
                    final int amount = stack.pop();
                    if (!jump) {
                        currentPointer = currentPointer + amount - 1;
                    }
                    break;
                }
                case ROFF:
                    gs.roundState = TTGraphicsState.off;
                    break;

                case RUTG:
                    gs.roundState = TTGraphicsState.utg;
                    gs.gridPeriod = 1.0;
                    break;

                case RDTG:
                    gs.roundState = TTGraphicsState.dtg;
                    gs.gridPeriod = 1.0;
                    break;

                case SANGW:
                    //Deprecated method - now only pops a value from stack
                    stack.pop();
                    break;

                case AA:
                    //Deprecated method - now only pops a value from stack
                    stack.pop();
                    break;

                case FLIPPT: {
                    for (int i = 0; i < gs.loop; i++) {
                        final int point = stack.pop();
                        curve[gs.zp0][point] = !curve[gs.zp0][point];
                    }
                    gs.loop = 1;
                    break;
                }
                case FLIPRGON: {
                    final int high = stack.pop();
                    final int low = stack.pop();
                    for (int i = low; i <= high; i++) {
                        curve[gs.zp0][i] = true;
                    }
                    break;
                }
                case FLIPRGOFF: {
                    final int high = stack.pop();
                    final int low = stack.pop();
                    for (int i = low; i <= high; i++) {
                        curve[gs.zp0][i] = false;
                    }
                    break;
                }
                case SCANCTRL:
                    stack.pop();
                    break;

                case SDPVTL0: {
                    final int p2 = stack.pop();
                    final int p1 = stack.pop();
                    double xdiff = getDoubleFromF26Dot6(x[gs.zp2][p2] - x[gs.zp1][p1]);
                    double ydiff = getDoubleFromF26Dot6(y[gs.zp2][p2] - y[gs.zp1][p1]);
                    double dxdiff = getDoubleFromF26Dot6(x[ORIGINAL + gs.zp2][p2] - x[ORIGINAL + gs.zp1][p1]);
                    double dydiff = getDoubleFromF26Dot6(y[ORIGINAL + gs.zp2][p2] - y[ORIGINAL + gs.zp1][p1]);
                    final double factor = Math.sqrt((xdiff * xdiff) + (ydiff * ydiff));
                    final double dfactor = Math.sqrt((dxdiff * dxdiff) + (dydiff * dydiff));
                    xdiff /= factor;
                    ydiff /= factor;
                    dxdiff /= dfactor;
                    dydiff /= dfactor;
                    gs.projectionVector = TTGraphicsState.createVector(storeDoubleAsF2Dot14(xdiff), storeDoubleAsF2Dot14(ydiff));
                    gs.dualProjectionVector = TTGraphicsState.createVector(storeDoubleAsF2Dot14(dxdiff), storeDoubleAsF2Dot14(dydiff));
                    break;
                }
                case SDPVTL1: {
                    final int p2 = stack.pop();
                    final int p1 = stack.pop();
                    double xdiff = getDoubleFromF26Dot6(x[gs.zp2][p2] - x[gs.zp1][p1]);
                    double ydiff = getDoubleFromF26Dot6(y[gs.zp2][p2] - y[gs.zp1][p1]);
                    double dxdiff = getDoubleFromF26Dot6(x[ORIGINAL + gs.zp2][p2] - x[ORIGINAL + gs.zp1][p1]);
                    double dydiff = getDoubleFromF26Dot6(y[ORIGINAL + gs.zp2][p2] - y[ORIGINAL + gs.zp1][p1]);
                    final double factor = Math.sqrt((xdiff * xdiff) + (ydiff * ydiff));
                    final double dfactor = Math.sqrt((dxdiff * dxdiff) + (dydiff * dydiff));
                    xdiff /= factor;
                    ydiff /= factor;
                    dxdiff /= dfactor;
                    dydiff /= dfactor;
                    gs.projectionVector = TTGraphicsState.createVector(storeDoubleAsF2Dot14(ydiff), storeDoubleAsF2Dot14(-xdiff));
                    gs.dualProjectionVector = TTGraphicsState.createVector(storeDoubleAsF2Dot14(dydiff), storeDoubleAsF2Dot14(-dxdiff));
                    break;
                }
                case GETINFO: {
                    final int selector = stack.pop();
                    int result = 0;

                    if ((selector & 1) == 1) {
                        result += 3;
                    }

//                    //Currently not needed as we don't use isRotated or isStretched
//                    if ((selector & 2) == 2 && isRotated)
//                        result += 0x100;
//
//                    if ((selector & 4) == 4 && isStretched)
//                        result += 0x200;

                    stack.push(result);
                    break;
                }
                case IDEF: {
                    final int func = stack.pop();
                    final int start = currentPointer;

                    //work out length
                    int curr;
                    do {
                        currentPointer++;
                        curr = program[currentPointer];
                    } while (curr != ENDF);
                    final int len = (currentPointer - start) - 1;
                    currentPointer = start;

                    //create function
                    final int[] instruction = new int[len];
                    for (int i = 0; i < len; i++) {
                        currentPointer++;
                        instruction[i] = program[currentPointer];
                    }
                    instructions.put(func, instruction);

                    //skip past ENDF
                    currentPointer++;

                    break;
                }
                case ROLL: {
                    final int top = stack.pop();
                    final int middle = stack.pop();
                    final int bottom = stack.pop();
                    stack.push(middle);
                    stack.push(top);
                    stack.push(bottom);
                    break;
                }
                case MAX: {
                    final int value1 = stack.pop();
                    final int value2 = stack.pop();

                    if (value1 > value2) {
                        stack.push(value1);
                    } else {
                        stack.push(value2);
                    }
                    break;
                }
                case MIN: {
                    final int value1 = stack.pop();
                    final int value2 = stack.pop();

                    if (value1 < value2) {
                        stack.push(value1);
                    } else {
                        stack.push(value2);
                    }
                    break;
                }
                case SCANTYPE:
                    stack.pop();
                    break;

                case INSTCTRL: {
                    final int s = stack.pop();
                    final int value = stack.pop();
                    if (s == 1) {
                        gs.instructControl = value;
                    } else if (s == 2) {
                        useDefaultGS = value == 2;
                    }
                    break;
                }
                case PUSHB:
                    if (printOut) {
                        System.out.println("PUSHB1    - Push bytes from IS to stack");
                    }
                    currentPointer = readFromIS(bytesToRead, false, currentPointer, program);
                    break;

                case PUSHW:
                    if (printOut) {
                        System.out.println("PUSHW1    - Push words from IS to stack");
                    }
                    currentPointer = readFromIS(bytesToRead, true, currentPointer, program);
                    break;

                default:

                    if (code >= MDRP && code < MDRP + 0x20) {
                        final int args = code - MDRP;

                        if (printOut) {
                            System.out.println("MDRP      - Move direct relative point (" + Integer.toBinaryString(args) + ')');
                        }

                        //read args
                        boolean setRP0toP = false, useMinimumDistance = false, roundDistance = false;
                        if ((args & paramRESETRP0) == paramRESETRP0) {
                            setRP0toP = true;
                        }
                        if ((args & paramUSEMINDIST) == paramUSEMINDIST) {
                            useMinimumDistance = true;
                        }
                        if ((args & paramROUND) == paramROUND) {
                            roundDistance = true;
                        }
                        final int distanceType = args & 3;

                        final int p = stack.pop();

                        //get original distance
                        int originalDistance = TTGraphicsState.getCoordsOnVector(gs.dualProjectionVector, x[ORIGINAL + gs.zp1][p], y[ORIGINAL + gs.zp1][p]) -
                                TTGraphicsState.getCoordsOnVector(gs.dualProjectionVector, x[ORIGINAL + gs.zp0][gs.rp0], y[ORIGINAL + gs.zp0][gs.rp0]);

                        //check single width cut in
                        if (Math.abs(originalDistance) < gs.singleWidthCutIn) {
                            if (originalDistance > 0) {
                                originalDistance = gs.singleWidthValue;
                            } else {
                                originalDistance = -gs.singleWidthValue;
                            }
                        }

                        //compensate for engine characteristics
                        originalDistance = engineCompensation(originalDistance, distanceType);

                        //round
                        if (roundDistance) {
                            originalDistance = storeDoubleAsF26Dot6(gs.round(getDoubleFromF26Dot6(originalDistance)));
                        }

                        //use minimum distance
                        if (useMinimumDistance && Math.abs(originalDistance) < gs.minimumDistance) {
                            if (originalDistance < 0) {
                                originalDistance = -gs.minimumDistance;
                            } else {
                                originalDistance = gs.minimumDistance;
                            }
                        }

                        //Get move needed
                        final int target = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][gs.rp0], y[gs.zp0][gs.rp0]) + originalDistance;
                        final int pVMove = target - TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp1][p], y[gs.zp1][p]);

                        //move point
                        final int[] shift = gs.getFVMoveforPVDistance(pVMove);
                        x[gs.zp1][p] += shift[0];
                        y[gs.zp1][p] += shift[1];

                        //mark as touched
                        final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                        if (fv[0] != 0) {
                            touched[gs.zp1][p][0] = true;
                        }
                        if (fv[1] != 0) {
                            touched[gs.zp1][p][1] = true;
                        }

                        //inexplicable reference point settings
                        gs.rp1 = gs.rp0;
                        gs.rp2 = p;
                        if (setRP0toP) {
                            gs.rp0 = p;
                        }

                    } else if (code >= MIRP && code <= MIRP + 0x1F) {
                        final int args = code - MIRP;
                        if (printOut) {
                            System.out.println("MIRP      - Move Indirect Relative Point(" + Integer.toBinaryString(args) + ')');
                        }

                        //read args
                        boolean setRP0toP = false, useMinimumDistance = false, roundDistanceAndCheckCutIn = false;
                        if ((args & paramRESETRP0) == paramRESETRP0) {
                            setRP0toP = true;
                        }
                        if ((args & paramUSEMINDIST) == paramUSEMINDIST) {
                            useMinimumDistance = true;
                        }
                        if ((args & paramROUND) == paramROUND) {
                            roundDistanceAndCheckCutIn = true;
                        }
                        final int distanceType = args & 3;

                        int cvtEntry = cvt.get(stack.pop());
                        final int p = stack.pop();

                        //Get original distance
                        int distance = TTGraphicsState.getCoordsOnVector(gs.dualProjectionVector, x[ORIGINAL + gs.zp1][p], y[ORIGINAL + gs.zp1][p]) -
                                TTGraphicsState.getCoordsOnVector(gs.dualProjectionVector, x[ORIGINAL + gs.zp0][gs.rp0], y[ORIGINAL + gs.zp0][gs.rp0]);

                        //Check single width cutin
                        if (Math.abs(distance - gs.singleWidthValue) < gs.singleWidthCutIn) {
                            distance = gs.singleWidthValue;
                        }

                        //Check CVT cut-in
                        if (roundDistanceAndCheckCutIn) {
                            //Check autoflip and match CVT sign to distance sign
                            if (gs.autoFlip && ((distance < 0 && cvtEntry > 0) || (distance > 0 && cvtEntry < 0))) {
                                cvtEntry = -cvtEntry;
                            }

                            if (Math.abs(distance - cvtEntry) < gs.controlValueTableCutIn) {
                                distance = cvtEntry;
                            }
                        }

                        //Compensate for engine characteristics
                        distance = engineCompensation(distance, distanceType);

                        //Round
                        if (roundDistanceAndCheckCutIn) {
                            distance = gs.round(distance);
                        }

                        //Check minimum distance
                        if (useMinimumDistance && Math.abs(distance) < gs.minimumDistance) {
                            if (distance > 0) {
                                distance = gs.minimumDistance;
                            } else {
                                distance = -gs.minimumDistance;
                            }
                        }

                        //Get move needed
                        final int target = TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp0][gs.rp0], y[gs.zp0][gs.rp0]) + distance;
                        final int pVMove = target - TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp1][p], y[gs.zp1][p]);
                        final int[] shift = gs.getFVMoveforPVDistance(pVMove);

                        //Perform shift
                        x[gs.zp1][p] += shift[0];
                        y[gs.zp1][p] += shift[1];

                        //mark as touched
                        final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
                        if (fv[0] != 0) {
                            touched[gs.zp1][p][0] = true;
                        }
                        if (fv[1] != 0) {
                            touched[gs.zp1][p][1] = true;
                        }

                        //inexplicable reference point settings
                        gs.rp1 = gs.rp0;
                        gs.rp2 = p;
                        if (setRP0toP) {
                            gs.rp0 = p;
                        }

                    } else if (instructions.containsKey(code)) {
                        if (printOut) {
                            System.out.println("I 0x" + Integer.toHexString(code) + "    - Custom Instruction");
                        }
                        execute(instructions.get(code), gs);
                        if (printOut) {
                            System.out.println("I 0x" + Integer.toHexString(code) + " finished");
                        }

                    } else if(LogWriter.isRunningFromIDE){
                        System.out.println("Unknown truetype opcode 0x" + Integer.toHexString(code) + " at line " + currentPointer);
                    }
            }
        } catch(final Exception e) {

            LogWriter.writeLog("Exception: " + e.getMessage()+ " at line "+currentPointer+"- hinting turned off");

            BaseTTGlyph.useHinting = false;
            BaseTTGlyph.redecodePage = true;
        }

        if (showDebugWindow && debugWindow != null && debugWindow.isVisible()) {
            instructionsExecuted += currentPointer + 1 - originalPointer;

            if (debugPointer==-1) {
                return debugPointer;
            }
        }

        return currentPointer;
    }


    /**
     * Goes through the glyph contour by contour finding pairs of touched points and moving the points between them to
     * preserve the shape of the original outline.
     *
     * @param direction Whether to interpolate in the x or y axis (value is originating instruction)
     */
    private void interpolateUntouchedPoints(final int direction) {

        //Set values according to direction
        final int[] points;
        final int[] original;
        final boolean[] touched = new boolean[this.touched[GLYPH_ZONE].length];

        if (direction == IUPx) {
            points = x[GLYPH_ZONE];
            original = x[ORIGINAL+GLYPH_ZONE];
            for (int i =0; i 1) {

                //Loop through pairs interpolating the points between them
                for (int i=0; i= touchedCount) {
                        //Special case for between the last and first touched points
                        interpolateRange(touchedPointNumbers[i]+1,   contourStart+point-1,       touchedPointNumbers[i],     touchedPointNumbers[0],     points,original);
                        interpolateRange(contourStart,               touchedPointNumbers[0]-1,   touchedPointNumbers[i],     touchedPointNumbers[0],     points,original);
                    } else {
                        interpolateRange(touchedPointNumbers[i]+1,   touchedPointNumbers[i+1]-1, touchedPointNumbers[i],     touchedPointNumbers[i+1],   points,original);
                    }
                }
            }


            //Move to start of next contour
            contourStart += point;
        }
    }

    /**
     * Interpolates the values of a range of points using two reference points. If the points coordinates were
     * originally between those of the two reference points, the relationship is maintained. If not, it is shifted by
     * the same shift which has been applied to the nearest of the two reference points.
     *
     * @param start The first point to be interpolated
     * @param end The last point to be interpolated
     * @param ref1 The first reference point
     * @param ref2 The second reference point
     * @param points The current coordinates of all points
     * @param original The original coordinates of all points
     */
    private static void interpolateRange(final int start, final int end, final int ref1, final int ref2, final int[] points, final int[] original) {

        //Work out which reference point is higher/lower
        final int lowerRef;
        final int higherRef;
        if (original[ref2] < original[ref1]) {
            lowerRef = ref2;
            higherRef = ref1;
        } else {
            lowerRef = ref1;
            higherRef = ref2;
        }

        //Go through points
        for (int i=start; i<=end; i++) {

            //If below/left of both reference points shift by the bottom/left point
            if (original[i] < original[lowerRef]) {
                points[i] += (points[lowerRef] - original[lowerRef]);

                //If above/right of both reference points shift by the top/right point
            } else if (original[i] > original[higherRef]) {
                points[i] += (points[higherRef] - original[higherRef]);

                //If between the reference points interpolate the new value
            } else {
                final double pos = (double)(original[i] - original[lowerRef])/(original[higherRef] - original[lowerRef]);
                points[i] = points[lowerRef] + (int)(pos * (points[higherRef] - points[lowerRef]));
            }
        }
    }



    /**
     * Doesn't currently do anything - should compensate for large dot sizes on some printers
     * @param num Number to compensate
     * @param characteristics Type of compensation to use
     * @return Compensated number
     */
    @SuppressWarnings("UnusedParameters")
    private static int engineCompensation(final int num, final int characteristics) {
        return num;
    }

    /**
     * Reads data from the Input Stream and puts it on the stack
     * @param number How many items to read
     * @param readWord Whether you're reading a word or a byte
     * @param currentPointer The current location in the stream
     * @param program The current input stream
     * @return The final location in the stream
     */
    private int readFromIS(final int number, final boolean readWord, int currentPointer, final int[] program) {
        for (int i=0; i> 7 & 1)*-65536);   //account for negative option
    }

    /**
     * Takes a F26Dot6 number and returns the value as a double.
     * @param a F26Dot6 value
     * @return Double value
     */
    protected static double getDoubleFromF26Dot6(final int a) {
        return (double)a/64;
    }

    /**
     * Takes a F2Dot14 number and returns the value as a double.
     * @param a F2Dot14 value
     * @return Double value
     */
    protected static double getDoubleFromF2Dot14(final int a) {
        return (double)a/0x4000;
    }

    /**
     * Takes a double and returns the value as a F26Dot6 number.
     * @param a Double value
     * @return F26Dot6 value
     */
    protected static int storeDoubleAsF26Dot6(final double a) {
        return (int)((a*64)+0.5);
    }

    /**
     * Takes a double and returns the value as a F2Dot14 number.
     * @param a Double value
     * @return F2Dot14 value
     */
    protected static int storeDoubleAsF2Dot14(final double a) {
        return (int)((a*16384)+0.5);
    }


    /**
     * Reads a program from a table in the font file.
     * @param currentFontFile Font file to use
     * @param table Table ID
     * @return The program
     */
    private static int[] readProgramTable(final FontFile2 currentFontFile, final int table) {
        int[] program = {};

        //move to start and check exists
        final int startPointer=currentFontFile.selectTable(table);

        //read table
        if(startPointer==0){
            LogWriter.writeLog("No program table found: " + table);
        }else{
            final int len = currentFontFile.getOffset(table);
            program = new int[len];
            for (int i = 0; i < len; i++) {
                program[i] =currentFontFile.getNextUint8();
            }
        }

        return program;
    }


    /**
     * Stack used by programs
     */
    private static class Stack implements Serializable {
        private int pointer;
        private int[] stack;

        Stack() {
            stack = new int[10];
        }

        /**
         * Adds an item to the top of the stack, expanding the stack if needed
         * @param a New item for stack
         */
        public void push(final int a) {
            if (pointer >= stack.length) {
                final int[] newStack = new int[(int)(stack.length*1.5)];
                System.arraycopy(stack,0,newStack,0,stack.length);
                stack = newStack;

            }
            stack[pointer] = a;

            pointer++;
        }

        /**
         * Removes an item from the top of the stack
         * @return removed item
         */
        public int pop() {
            pointer--;
            if (pointer >= 0) {
                return stack[pointer];
            }

            throw new RuntimeException("Popped an empty stack!");
        }

        /**
         * @return The number of items on the stack
         */
        public int size() {
            return pointer;
        }

        /**
         * Accesses an element further down the stack
         * @param key The number (from the top down) of the item to access
         * @return  The item
         */
        public int elementAt(final int key) {
            return stack[pointer-key];
        }

        /**
         * Removes an item from the stack
         * @param key The number (from the top down) of the item to remove
         * @return The removed item
         */
        public int remove(final int key) {
            final int valPos = pointer-key;
            final int result = stack[valPos];
            final int[] newStack = new int[stack.length];
            System.arraycopy(stack, 0, newStack, 0, valPos);
            System.arraycopy(stack, valPos+1, newStack, valPos, (stack.length-valPos)-1);
            stack = newStack;
            pointer--;
            return result;
        }

        /**
         * DEBUG METHOD -
         *
         * Print out the top 5 elements on the stack
         */
        public void print() {
            System.out.println("stack: ");
            int i;
            for (i=pointer-1; i>=0 && i >=pointer-5; i--) {
                System.out.println(i+": "+stack[i]);
            }
            if (i>0) {
                System.out.println("...");
            }
            System.out.println("");
        }

        public String[] toStringArray() {
            final String[] result = new String[pointer];

            for (int i=pointer-1; i >= 0; i--) {
                result[(pointer - i) - 1] = (pointer - i) - 1 + ": " + stack[i] + "       (" + NumberFormat.getNumberInstance().format(stack[i] / 64d) + ')';
            }

            return result;
        }
    }

    /**
     * DEBUG METHOD -
     *
     * Sets up the Hinting Debugger.
     */
    private void runDebugger() {
        if (programToDebug == null) {
            JOptionPane.showMessageDialog(debugWindow, "No glyph program found to debug!");
            return;
        }

        debugWindow = new JFrame("TrueType Hinting Debugger");
        debugWindow.setSize(1000,700);
        debugWindow.setLayout(new BorderLayout());


        /*
         * Top panel
         */
        //Buttons
        final JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new FlowLayout(FlowLayout.LEFT));

        final JButton stepOverButton = new JButton("Step Over");
        stepOverButton.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(final java.awt.event.ActionEvent e) {
                advanceDebugger(false);
            }
        });
        buttonPanel.add(stepOverButton);

        final JButton stepIntoButton = new JButton("Step Into");
        stepIntoButton.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(final java.awt.event.ActionEvent e) {
                advanceDebugger(true);
            }
        });
        buttonPanel.add(stepIntoButton);

        final JButton stepOutButton = new JButton("Step Out");
        stepOutButton.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(final java.awt.event.ActionEvent e) {
                runDebuggerTo(programToDebug.length);
            }
        });
        buttonPanel.add(stepOutButton);

        final JButton restartButton = new JButton("Restart");
        restartButton.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(final java.awt.event.ActionEvent e) {
                restartDebugger();
                refreshDebugger(true);
            }
        });
        buttonPanel.add(restartButton);

        final JButton backButton = new JButton("Back");
        backButton.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(final java.awt.event.ActionEvent e) {
                runDebuggerTo(debugPointer - 1);
            }
        });
        buttonPanel.add(backButton);

        debugWindow.add(BorderLayout.NORTH,buttonPanel);

        /*
         * Left panel
         */
        final JPanel instructionPanel = new JPanel();
        instructionPanel.setLayout(new BorderLayout());
        instructionPanel.setBorder(new javax.swing.border.LineBorder(Color.BLACK));

        //Code list
        currentInstructionList = new JList();
        final JScrollPane codePane = new JScrollPane(currentInstructionList) {
            @Override
            public Dimension getPreferredSize() {
                final Dimension pref = super.getPreferredSize();
                final Dimension min = getMinimumSize();

                final int w = pref.width < min.width ? min.width : pref.width;
                final int h = pref.height < min.height ? min.height : pref.height;

                return new Dimension(w, h);
            }
        };
        codePane.setMinimumSize(new Dimension(150,100));

        //Label
        currentCode = new JLabel("Glyph program");

        //Add to panel
        instructionPanel.add(BorderLayout.CENTER,codePane);
        instructionPanel.add(BorderLayout.SOUTH,currentCode);

        debugWindow.add(BorderLayout.WEST,instructionPanel);

        /*
         * Centre panel
         */
        final JPanel glyphPanel = new JPanel();
        glyphPanel.setLayout(new BorderLayout());
        glyphPanel.setBorder(new javax.swing.border.LineBorder(Color.BLACK));

        debugGlyphDisplay = new JComponent() {
            @Override
            public void paint(final Graphics g) {
                final Graphics2D g2 = (Graphics2D)g;

                //Calculate Scale
                final int w = getWidth();
                final int h = getHeight();

                int minX=Integer.MAX_VALUE;
                int minY=Integer.MAX_VALUE;
                int maxX=Integer.MIN_VALUE;
                int maxY=Integer.MIN_VALUE;
                for (int i=0; i maxX) {
                        maxX = val;
                    }
                    if (val < minX) {
                        minX = val;
                    }

                    val = y[GLYPH_ZONE][i];
                    if (val > maxY) {
                        maxY = val;
                    }
                    if (val < minY) {
                        minY = val;
                    }
                }

                int xRange = maxX - minX;
                int yRange = maxY - minY;
                double xScale = (double)w / xRange;
                double yScale = (double)h / yRange;
                double scale = xScale < yScale ? xScale : yScale;

                //add buffer area
                final int borderWidth = 15;
                minX -= (borderWidth/scale);
                maxX += (borderWidth/scale);
                minY -= (borderWidth/scale);
                maxY += (borderWidth/scale);

                //recalculate scale
                xRange = maxX - minX;
                yRange = maxY - minY;
                xScale = (double)w / xRange;
                yScale = (double)h / yRange;
                scale = xScale < yScale ? xScale : yScale;


                //Apply transform
                g2.translate(0, h);
                g2.scale(scale, -scale);

                g2.translate(-minX, -minY);


                //Fill with white
                g2.setPaint(Color.WHITE);
                g2.fillRect(minX,minY,(int)(w/scale),(int)(h/scale));


                //Draw axes
                g2.setPaint(new Color(180,180,255));
                g2.drawLine(0, minY, 0, (int)(h/scale));
                g2.drawLine(minX, 0, (int)(w/scale), 0);


                //Draw points
                final int len = (int)(3 / scale);
                for (int i=0; i maxX) {
                        maxX = val;
                    }
                    if (val < minX) {
                        minX = val;
                    }

                    val = y[GLYPH_ZONE][i];
                    if (val > maxY) {
                        maxY = val;
                    }
                    if (val < minY) {
                        minY = val;
                    }
                }

                int xRange = maxX - minX;
                int yRange = maxY - minY;

                double xScale = (double) w / xRange;
                double yScale = (double) h / yRange;
                double scale = xScale < yScale ? xScale : yScale;

                //add buffer area
                final int borderWidth = 15;
                minX -= (borderWidth/scale);
                maxX += (borderWidth/scale);
                minY -= (borderWidth/scale);
                maxY += (borderWidth/scale);

                //recalculate scale
                xRange = maxX - minX;
                yRange = maxY - minY;
                xScale = (double)w / xRange;
                yScale = (double)h / yRange;
                scale = xScale < yScale ? xScale : yScale;

                eX = (eX / scale) + minX;
                eY = h - eY;
                eY = ((eY / scale) + minY);

                debugXLabel.setText("  X: "+eX);
                debugYLabel.setText("  Y: "+eY);
            }

            @Override
            public void mouseExited(final MouseEvent e) {
                debugXLabel.setText("  X: ");
                debugYLabel.setText("  Y: ");
            }
        });
        glyphPanel.add(BorderLayout.CENTER, debugGlyphDisplay);

        showInterpolatedShadow = new JCheckBox("Show Interpolated Shadow");
        showInterpolatedShadow.setSelected(true);
        showInterpolatedShadow.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(final java.awt.event.ActionEvent e) {
                glyphPanel.repaint();
            }
        });
        glyphPanel.add(BorderLayout.NORTH, showInterpolatedShadow);

        debugWindow.add(BorderLayout.CENTER, glyphPanel);




        /*
         * Right panel
         */
        final JPanel dataPanel = new JPanel(){
            @Override
            public Dimension getPreferredSize() {
                final Dimension pref = super.getPreferredSize();
                final Dimension min = getMinimumSize();
                final Dimension max = getMaximumSize();

                int w = pref.width < min.width ? min.width : pref.width;
                int h = pref.height < min.height ? min.height : pref.height;

                w = w > max.width ? max.width : w;
                h = h > max.height ? max.height : h;

                return new Dimension(w, h);
            }
        };
        dataPanel.setMinimumSize(new Dimension(200,100));
        dataPanel.setMaximumSize(new Dimension(200,1000000));
        dataPanel.setLayout(new BorderLayout());
        dataPanel.setBorder(new javax.swing.border.LineBorder(Color.BLACK));

        //Stack
        final JPanel stackPanel = new JPanel(new BorderLayout());
        stackList = new JList();
        final JScrollPane stackScroll = new JScrollPane(stackList);
        stackPanel.add(BorderLayout.NORTH, new JLabel("Stack:"));
        stackPanel.add(BorderLayout.CENTER, stackScroll);

        //CVT
        final JPanel cvtPanel = new JPanel(new BorderLayout());
        cvtList = new JList(cvt.getCVTForDebug());
        final JScrollPane cvtScroll = new JScrollPane(cvtList);
        cvtPanel.add(BorderLayout.NORTH, new JLabel("CVT:"));
        cvtPanel.add(BorderLayout.CENTER, cvtScroll);

        //Storage
        final JPanel storagePanel = new JPanel(new BorderLayout());
        storageList = new JList(getStorageAsArray());
        final JScrollPane storageScroll = new JScrollPane(storageList);
        storagePanel.add(BorderLayout.NORTH, new JLabel("Storage:"));
        storagePanel.add(BorderLayout.CENTER, storageScroll);

        dataPanel.add(BorderLayout.NORTH, stackPanel);
        dataPanel.add(BorderLayout.CENTER, cvtPanel);
        dataPanel.add(BorderLayout.SOUTH, storagePanel);

        debugWindow.add(BorderLayout.EAST, dataPanel);


        /*
         * Bottom panel
         */
        final JPanel statePanel = new JPanel();
        statePanel.setLayout(new BorderLayout());
        statePanel.setBorder(new javax.swing.border.LineBorder(Color.BLACK));

        stateDisplay = new JComponent() {
            @Override
            public void paint(final Graphics g) {
                final Graphics2D g2 = (Graphics2D)g;

                g2.setPaint(Color.WHITE);
                g2.fillRect(0,0,81,81);
                g2.setPaint(Color.BLACK);
                g2.drawRect(0,0,81,81);
                g2.setPaint(Color.GRAY);
                g2.drawOval(0,0,81,81);

                g2.drawLine(0,40,5,40);
                g2.drawLine(81,40,76,40);
                g2.drawLine(40,0,40,5);
                g2.drawLine(40,81,40,76);

                g2.drawLine(12,12,15,15);
                g2.drawLine(69,12,66,15);
                g2.drawLine(12,69,15,66);
                g2.drawLine(69,69,66,66);

                //freedom vector
                g2.setPaint(new Color(0,100,0));
                int[] vec = TTGraphicsState.getVectorComponents(dGS.freedomVector);
                g2.drawLine(40,40,40+((vec[0]*40)/16384),40-((vec[1]*40)/16384));
                g2.drawString("Freedom Vector", 84, 13);
                g2.drawString("("+(vec[0]/16384d)+", "+(vec[1]/16384d)+ ')',98,23);

                //dual projection vector
                g2.setPaint(Color.BLUE);
                vec = TTGraphicsState.getVectorComponents(dGS.dualProjectionVector);
                g2.drawLine(41,41,41+((vec[0]*40)/16384),41-((vec[1]*40)/16384));
                g2.drawString("Dual Projection Vector", 84, 65);
                g2.drawString("("+(vec[0]/16384d)+", "+(vec[1]/16384d)+ ')',98,75);

                //projection vector
                g2.setPaint(Color.MAGENTA);
                vec = TTGraphicsState.getVectorComponents(dGS.projectionVector);
                g2.drawLine(41,41,41+((vec[0]*40)/16384),41-((vec[1]*40)/16384));
                g2.drawString("Projection Vector", 84, 39);
                g2.drawString("("+(vec[0]/16384d)+", "+(vec[1]/16384d)+ ')',98,49);

                //Separator
                g2.setPaint(Color.GRAY);
                g2.drawLine(240, 4, 240, 77);

                //Zone pointers
                g2.setPaint(Color.BLACK);
                g2.drawString("zp0: " + dGS.zp0 + (dGS.zp0 == 0 ? " (Twilight Zone)" : " (Glyph Zone)"), 250, 13);
                g2.drawString("zp1: " + dGS.zp1 + (dGS.zp1 == 0 ? " (Twilight Zone)" : " (Glyph Zone)"), 250, 25);
                g2.drawString("zp2: " + dGS.zp2 + (dGS.zp2 == 0 ? " (Twilight Zone)" : " (Glyph Zone)"), 250, 37);

                //Reference Points
                g2.drawString("rp0: " + dGS.rp0, 250, 51);
                g2.drawString("rp1: " + dGS.rp1, 250, 63);
                g2.drawString("rp2: " + dGS.rp2, 250, 75);

                //Separator
                g2.setPaint(Color.GRAY);
                g2.drawLine(404, 4, 404, 77);

                //Instruct Control
                g2.setPaint(Color.BLACK);
                g2.drawString("Instruct Control: "+dGS.instructControl, 414, 13);

                //Auto Flip
                g2.drawString("Auto Flip: " + dGS.autoFlip, 414, 30);

                //Deltas
                g2.drawString("Delta Base: " + dGS.deltaBase, 414, 47);
                g2.drawString("Delta Shift: " + dGS.deltaShift, 414, 59);

                //Loop
                g2.drawString("Loop: " + dGS.loop, 414, 75);

                //Separator
                g2.setPaint(Color.GRAY);
                g2.drawLine(548, 4, 548, 77);

                //Round state
                g2.setPaint(Color.BLACK);
                g2.drawString("Round State: " + dGS.getRoundStateAsString(), 558, 13);

                //Minimum distance
                g2.drawString("Minimum Distance: " + dGS.minimumDistance, 558, 30);

                //CVT cut in
                g2.drawString("CVT Cut In: " + dGS.controlValueTableCutIn, 558, 46);

                //Single Width
                g2.drawString("Single Width Cut In: " + dGS.singleWidthCutIn, 558, 63);
                g2.drawString("Single Width Value: " + dGS.singleWidthValue, 558, 75);
            }
        };
        stateDisplay.setMinimumSize(new Dimension(700,81));
        stateDisplay.setPreferredSize(new Dimension(700,81));
        stateDisplay.setMaximumSize(new Dimension(700,81));
        statePanel.add(BorderLayout.WEST, stateDisplay);

        final JPanel mousePanel = new JPanel();
        mousePanel.setLayout(new GridLayout(0,1));

        debugXLabel = new JLabel("  X: "){
            @Override
            public Dimension getPreferredSize() {
                final Dimension pref = super.getPreferredSize();
                final Dimension min = getMinimumSize();

                final int w = pref.width < min.width ? min.width : pref.width;
                final int h = pref.height < min.height ? min.height : pref.height;

                return new Dimension(w, h);
            }
        };
        debugXLabel.setBackground(Color.WHITE);
        debugXLabel.setOpaque(true);
        debugXLabel.setBorder(new javax.swing.border.LineBorder(Color.BLACK));
        debugXLabel.setMinimumSize(new Dimension(200, 20));

        debugYLabel = new JLabel("  Y: ");
        debugYLabel.setBackground(Color.WHITE);
        debugYLabel.setOpaque(true);
        debugYLabel.setBorder(new javax.swing.border.LineBorder(Color.BLACK));
        debugYLabel.setMinimumSize(new Dimension(200, 20));

        mousePanel.add(debugXLabel);
        mousePanel.add(debugYLabel);

        statePanel.add(BorderLayout.EAST, mousePanel);

        debugWindow.add(BorderLayout.SOUTH, statePanel);


        try {
            dGS = (TTGraphicsState)graphicsState.clone();
        } catch(final CloneNotSupportedException e) {
            LogWriter.writeLog("Exception: " + e.getMessage());
        }

        stack = new Stack();

        final int twilightCount = maxp.getMaxTwilightPoints();
        System.arraycopy(x[ORIGINAL+GLYPH_ZONE],0,x[GLYPH_ZONE],0,x[GLYPH_ZONE].length);
        x[TWILIGHT_ZONE] = new int[twilightCount];
        x[ORIGINAL+TWILIGHT_ZONE] = new int[twilightCount];
        System.arraycopy(y[ORIGINAL+GLYPH_ZONE],0,y[GLYPH_ZONE],0,y[GLYPH_ZONE].length);
        y[TWILIGHT_ZONE] = new int[twilightCount];
        y[ORIGINAL+TWILIGHT_ZONE] = new int[twilightCount];
        System.arraycopy(touched[ORIGINAL+GLYPH_ZONE],0,touched[GLYPH_ZONE],0,touched[GLYPH_ZONE].length);
        touched[TWILIGHT_ZONE] = new boolean[twilightCount][2];
        touched[ORIGINAL+TWILIGHT_ZONE] = new boolean[twilightCount][2];

        refreshDebugger(true);
        debugWindow.setVisible(true);
    }

    /**
     * DEBUG METHOD -
     *
     * Returns the midpoint of two values. Used when creating paths from points.
     * @param a
     * @param b
     * @return
     */
    private static int midPt(final int a, final int b) {
		return a + (b - a)/2;
	}

    /**
     * DEBUG METHOD -
     *
     * Creates paths for a given set of points, making sure not to modify any of the values passed in.
     * @param x
     * @param y
     * @param curve
     * @param contour
     * @return
     */
    private static GeneralPath getPathFromPoints(final int[] x, final int[] y, final boolean[] curve, final boolean[] contour) {
        final int ptCount=x.length;
        final int[] pX = new int[ptCount];
        System.arraycopy(x,0,pX,0,ptCount);
        final int[] pY = new int[ptCount];
        System.arraycopy(y,0,pY,0,ptCount);
        final boolean[] endOfContour = new boolean[ptCount];
        System.arraycopy(contour,0,endOfContour,0,ptCount);
        final boolean[] onCurve = new boolean[ptCount];
        System.arraycopy(curve,0,onCurve,0,ptCount);

        int start=0, firstPt=-1;
        for(int ii=0;iiii) {
                            newPos -= (ii - start + 1);
                        }
                        pX[oldPos]=old_pX[newPos];
                        pY[oldPos]=old_pY[newPos];
                        onCurve[oldPos]=old_onCurve[newPos];

                    }
                }

                //reset values
                start=ii+1;
                firstPt=-1;

            }else if(onCurve[ii] && firstPt==-1){ //track first point
                firstPt=ii;
            }

        }

        boolean isFirstDraw=true;

        final GeneralPath current_path =new GeneralPath(Path2D.WIND_NON_ZERO);

        final int c= pX.length;
        int fc=-1;

        //find first end contour
        for(int jj=0;jj1 || fc==lc)){
                    boolean checkEnd=false;
                    if(onCurve[p] && !onCurve[p1] && onCurve[p2] ){ //2 points + control

                        x1=pX[p];
                        y1=pY[p];
                        x2=pX[p1];
                        y2=pY[p1];
                        x3=pX[p2];
                        y3=pY[p2];
                        j++;
                        checkEnd=true;

                    }else if(onCurve[p] && !onCurve[p1] && !onCurve[p2]){ //1 point + 2 control

                        x1=pX[p];
                        y1=pY[p];
                        x2=pX[p1];
                        y2=pY[p1];
                        x3=midPt(pX[p1], pX[p2]);
                        y3=midPt(pY[p1], pY[p2]);
                        j++;

                        checkEnd=true;

                    }else if(!onCurve[p] && !onCurve[p1] && (!endOfContour[p2] ||fc-p2==1)){ // 2 control + 1 point (final check allows for last point to complete loop

                        x1=midPt(pX[pm1], pX[p]);
                        y1=midPt(pY[pm1], pY[p]);
                        x2=pX[p];
                        y2=pY[p];

                        x3=midPt(pX[p], pX[p1]);
                        y3=midPt(pY[p], pY[p1]);

                    }else if(!onCurve[p] && onCurve[p1]){ // 1 control + 2 point

                        x1=midPt(pX[pm1], pX[p]);
                        y1=midPt(pY[pm1], pY[p]);
                        x2=pX[p];
                        y2=pY[p];
                        x3=pX[p1];
                        y3=pY[p1];
                    }

                    if(isFirstDraw){
                        current_path.moveTo(x1,y1);
                        isFirstDraw=false;
                    }

                    if (!(endOfContour[p] && p > 0 && endOfContour[p-1])) {
                        current_path.curveTo(x1, y1, x2, y2, x3, y3);
                    }

                    /*if end after curve, roll back so we pick up the end*/
                    if( checkEnd && endOfContour[j]){

                        isEnd=true;

                        xs=pX[fc];
                        ys=pY[fc];
                        //remmeber start point
                        lc=fc;
                        //find next contour
                        for(int jj=j+1;jj= 0) {
            int add = 0;
            if (targetInstr >= programToDebug.length) {
                add = targetInstr - (programToDebug.length-1);
                targetInstr = programToDebug.length-1;
            }

            while (programToDebugIsData[targetInstr]) {
                targetInstr--;
            }
            diff = targetInstr - debugPointer + add;
        }

        final int target = instructionsExecuted + diff;

        boolean skipFunctions = true;
        if (target < instructionsExecuted) {
            restartDebugger();
            skipFunctions = false;
        }

        final int startLineCount = functionsLineCount;

        debuggerRunningInBackground = true;
        while (instructionsExecuted < target + (skipFunctions ? (functionsLineCount - startLineCount) : 0)) {
            stepInto = true;
            debugPointer = process(programToDebug[debugPointer], debugPointer, programToDebug, dGS);
            debugPointer++;

            if (debugPointer == programToDebug.length && !codeStack.empty()) {
                setCurrentCodeForDebug(codeStack.pop(), numberStack.pop() + 1, false);
            }
        }
        debuggerRunningInBackground = false;

        setCurrentCodeForDebug(programToDebug, debugPointer, true);

        stepInto = false;
        instructionsExecuted = target;
    }


    /**
     * DEBUG METHOD -
     *
     * Restarts the debugger, resetting all relevant data structures.
     */
    private void restartDebugger() {

        stack = new Stack();

        try {
            dGS = (TTGraphicsState)graphicsState.clone();
        } catch(final CloneNotSupportedException e) {
            LogWriter.writeLog("Exception: " + e.getMessage());
        }

        final int twilightCount = maxp.getMaxTwilightPoints();
        System.arraycopy(x[ORIGINAL+GLYPH_ZONE],0,x[GLYPH_ZONE],0,x[GLYPH_ZONE].length);
        x[TWILIGHT_ZONE] = new int[twilightCount];
        x[ORIGINAL+TWILIGHT_ZONE] = new int[twilightCount];
        System.arraycopy(y[ORIGINAL+GLYPH_ZONE],0,y[GLYPH_ZONE],0,y[GLYPH_ZONE].length);
        y[TWILIGHT_ZONE] = new int[twilightCount];
        y[ORIGINAL+TWILIGHT_ZONE] = new int[twilightCount];
        System.arraycopy(touched[ORIGINAL+GLYPH_ZONE],0,touched[GLYPH_ZONE],0,touched[GLYPH_ZONE].length);
        touched[TWILIGHT_ZONE] = new boolean[twilightCount][2];
        touched[ORIGINAL+TWILIGHT_ZONE] = new boolean[twilightCount][2];

        if (!codeStack.isEmpty()) {
            programToDebug = codeStack.get(0);
            programToDebugIsData = getInstructionStreamIsData(programToDebug);
        }
        codeStack.clear();
        numberStack.clear();
        functionsLineCount = 0;
        debugPointer = 0;
        instructionsExecuted = 0;
        currentCode.setText("Glyph Program");
    }

    /**
     * DEBUG METHOD -
     *
     * Process the current instruction, update the display, and move the debugger onto the next.
     * @param stepIntoCall
     */
    private void advanceDebugger(final boolean stepIntoCall) {
        if (debugPointer < programToDebug.length) {
            final Thread t = new Thread() {
                @Override
                public void run() {
                    stepInto = stepIntoCall;
                    debugPointer = process(programToDebug[debugPointer], debugPointer, programToDebug, dGS);
                    stepInto = false;
                    debugPointer++;

                    refreshDebugger(false);

                    if (debugPointer == programToDebug.length && !codeStack.empty()) {
                        setCurrentCodeForDebug(codeStack.pop(), numberStack.pop() + 1, true);
                    }
                }
            };
            SwingUtilities.invokeLater(t);
        }
    }

    /**
     * DEBUG METHOD -
     *
     * Return the storage as a string array for displaying.
     * @return
     */
    private String[] getStorageAsArray() {
        final String[] result = new String[storage.length];
        for (int i=0; i= programToDebug.length) {
                forward = programToDebug.length - 1;
            }
            currentInstructionList.ensureIndexIsVisible(forward);
            int back = start - 2;
            if (back < 0) {
                back = 0;
            }
            currentInstructionList.ensureIndexIsVisible(back);
            currentInstructionList.ensureIndexIsVisible(end);
        }
        currentInstructionList.ensureIndexIsVisible(start);


        stackList.setListData(stack.toStringArray());
        cvtList.setListData(cvt.getCVTForDebug());
        storageList.setListData(getStorageAsArray());
        stateDisplay.repaint();
        debugGlyphDisplay.repaint();
    }

    /**
     * DEBUG METHOD -
     *
     * Set the program (or function etc) currently displayed.
     * @param code
     * @param pointer
     * @param updateDisplay
     */
    private void setCurrentCodeForDebug(final int[] code, final int pointer, final boolean updateDisplay) {
        programToDebug = code;
        debugPointer = pointer;

        if (!updateDisplay) {
            return;
        }

        programToDebugIsData = getInstructionStreamIsData(programToDebug);

        refreshDebugger(true);

        String function = "Glyph program";
        Object[] keySet = functions.keySet().toArray();
        for (final Object aKeySet1 : keySet) {
            //noinspection ArrayEquality
            if (functions.get(aKeySet1) == programToDebug) {
                function = "Function " + aKeySet1;
            }
        }
        keySet = instructions.keySet().toArray();
        for (final Object aKeySet : keySet) {
            //noinspection ArrayEquality
            if (instructions.get(aKeySet) == programToDebug) {
                function = "Instruction " + aKeySet;
            }
        }
        currentCode.setText(function);
    }


    /**
     * DEBUG METHOD -
     *
     * Prints out all of the coordinates in a form well suited to pasting into a spreadsheet
     */
    private void printCoords() {
        for (int i=0; i= MDRP && test <= MDRP+0x1F) {
                test = MDRP;
            }
            if (test >= 0xE0 && test <= 0xFF) {
                test = 0xE0;
            }

            for (final Field declaredField : TTVM.class.getDeclaredFields()) {
                if (found) {
                    break;
                }
                try {

                    //ignore if not an instruction
                    if (Character.isLowerCase(declaredField.getName().charAt(0)) || declaredField.getName().contains("ZONE") || "ORIGINAL".equals(declaredField.getName())) {
                        continue;
                    }

                    if (declaredField.getInt(declaredField)==test) {
                        if ("ENDF".equals(declaredField.getName()) || "ELSE".equals(declaredField.getName()) || "EIF".equals(declaredField.getName())) {
                            depth.delete(0, 2);
                        }

                        result[n] = (n+": "+depth+ declaredField.getName());
                        if ("NPUSHB".equals(declaredField.getName())) {
                            n++;
                            final int count = program[n];
                            result[n] =(depth+"  count: "+count);
                            for (int j=0; j= 0xb0 && program[n] <= 0xb7) {
                            final int count = (program[n]-0xAF);
                            for (int j=0; j= 0xb8 && program[n] <= 0xbF) {
                            final int count = (program[n]-0xb7);
                            for (int j=0; j 0) {
                System.out.println(Integer.toHexString(i) + " not found " + notFoundCount[i] + " times.");
            }
        }

        return result;
    }

    /**
     * DEBUG METHOD -
     *
     * Converts an instruction stream into a boolean array indicating whether a line is an instruction or data.
     * @param program
     * @return
     */
    public static boolean[] getInstructionStreamIsData(final int[] program) {
        if (program == null) {
            return new boolean[]{};
        }

        final boolean[] result = new boolean[program.length];

        for (int n=0; n= MDRP && test <= MDRP+0x1F) {
                test = MDRP;
            }
            if (test >= 0xE0 && test <= 0xFF) {
                test = 0xE0;
            }

            for (final Field declaredField : TTVM.class.getDeclaredFields()) {
                if (found) {
                    break;
                }
                try {
                    //ignore if not an instruction
                    if (Character.isLowerCase(declaredField.getName().charAt(0)) || declaredField.getName().contains("ZONE") || "ORIGINAL".equals(declaredField.getName())) {
                        continue;
                    }

                    if (declaredField.getInt(declaredField)==test) {

                        result[n] = false;
                        if ("NPUSHB".equals(declaredField.getName())) {
                            n++;
                            final int count = program[n];
                            result[n] = true;
                            for (int j=0; j= 0xb0 && program[n] <= 0xb7) {
                            final int count = (program[n]-0xAF);
                            for (int j=0; j= 0xb8 && program[n] <= 0xbF) {
                            final int count = (program[n]-0xb7);
                            for (int j=0; j




© 2015 - 2024 Weber Informatics LLC | Privacy Policy