org.jpedal.fonts.tt.hinting.TTVM Maven / Gradle / Ivy
/*
* ===========================================
* 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
*
@LICENSE@
*
* ---------------
* TTVM.java
* ---------------
*/
package org.jpedal.fonts.tt.hinting;
import java.awt.*;
import java.awt.event.*;
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.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)
/**
* NOTE ON ORIGINAL POINTS:
*
* The Twilight Zone does not behave like the Glyph Zone. This particularly
* holds true when it comes to referencing the original positions of points.
* In the Twilight Zone, a point is considered to have been created when it
* is moved by MIAP, MIRP, MSIRP or SCFS. Thus, even if the original position
* has already been set, it should (sometimes?!?) be overwritten in these
* circumstances. Some cut in values should also be ignored.
*
* Don't ask me why, though. You're moving into a land of both shadow and
* substance, of things and ideas. You've just crossed over into the Twilight
* Zone.
*/
//debug flags
/**
* Activates the Hinting Debugger. This lets you step through the font
* program of a glyph, seeing what's done exactly where. I'd recommend
* isolating the glyph in BaseTTGlyph.createHintedGlyph() 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. Again, I'd recommend you isolate the glyph you're looking at in
* BaseTTGlyph.createHintedGlyph() 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
* BaseTTGlyph.createHintedGlyph() 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 BaseTTGlyph.createHintedGlyph() 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();
private JButton stepOutButton;
private String currentFunc;
/**
* 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;
boolean forceRunPrep = false;
this.ppem = (int) (ppem + 0.5);
this.ptSize = ptSize;
if (!fontProgramRun) {
execute(fontProgram, graphicsState); //Defines functions
fontProgramRun = true;
forceRunPrep = true;
}
if (scaler != this.scaler || forceRunPrep) {
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 boolean processGlyph(final int[] instructions, final int[] glyfX, final int[] glyfY, final boolean[] curves, final boolean[] contours, final double pixelSize, final double scaler) {
if (printCoordsAfterEachInstruction || watchAPoint) {
printOut = true;
}
x[ORIGINAL + TWILIGHT_ZONE] = new int[x[TWILIGHT_ZONE].length];
x[TWILIGHT_ZONE] = new int[x[TWILIGHT_ZONE].length];
y[TWILIGHT_ZONE] = new int[y[TWILIGHT_ZONE].length];
y[ORIGINAL + TWILIGHT_ZONE] = new int[y[TWILIGHT_ZONE].length];
setScaleVars(scaler, pixelSize, (pixelSize * 72) / 96);
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 false;
}
//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);
}
final boolean failedOnHinting = execute(instructions, gs);
if (printCoordsAfterEachInstruction || watchAPoint) {
printOut = false;
}
if (showDebugWindow) {
final Thread t = new Thread("getDebugListData") {
@Override
public void run() {
runDebugger();
}
};
t.start();
}
return failedOnHinting;
}
/**
* Execute a section of code
*
* @param program The code to execute
* @param gs The graphics state to use
*/
private boolean execute(final int[] program, final TTGraphicsState gs) {
boolean failedOnHinting = false;
if (program == null) {
return false;
}
if (showDebugWindow && stepInto && debugWindow != null && debugWindow.isVisible()) {
codeStack.push(programToDebug);
numberStack.push(debugPointer);
functionsLineCount += program.length;
setCurrentCodeForDebug(program, -1, !debuggerRunningInBackground);
return false;
}
for (int currentPointer = 0; currentPointer < program.length; currentPointer++) {
if (printOut) {
System.out.print(currentPointer + "\t");
}
currentPointer = process(program[currentPointer], currentPointer, program, gs);
if (currentPointer < 0) { //negative value used as boolean flag
currentPointer = -currentPointer;
failedOnHinting = true;
}
//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 (failedOnHinting) {
return true;
}
}
return false;
}
/**
* 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) {
boolean failedOnHinting = false, failed;
if (showDebugWindow) {
currentFunc = getCurrentFunc();
}
//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], currentPointer, currentFunc);
stack.push(pv[1], currentPointer, currentFunc);
break;
}
case GFV: {
final int[] fv = TTGraphicsState.getVectorComponents(gs.freedomVector);
stack.push(fv[0], currentPointer, currentFunc);
stack.push(fv[1], currentPointer, currentFunc);
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) {
LogWriter.writeLog("ZP0 set incorrectly! " + value);
}
gs.zp0 = value;
break;
}
case SZP1: {
final int value = stack.pop();
if (value > 1 || value < 0) {
LogWriter.writeLog("ZP1 set incorrectly! " + value);
}
gs.zp1 = value;
break;
}
case SZP2: {
final int value = stack.pop();
if (value > 1 || value < 0) {
LogWriter.writeLog("ZP2 set incorrectly! " + value);
}
gs.zp2 = value;
break;
}
case SZPS: {
final int value = stack.pop();
if (value > 1 || value < 0) {
LogWriter.writeLog("All zone pointers set incorrectly! " + value);
}
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, currentPointer, currentFunc);
stack.push(value, currentPointer, currentFunc);
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, currentPointer, currentFunc);
stack.push(under, currentPointer, currentFunc);
break;
}
case DEPTH: {
stack.push(stack.size(), currentPointer, currentFunc);
break;
}
case CINDEX: {
final int key = stack.pop();
final int value = stack.elementAt(key);
stack.push(value, currentPointer, currentFunc);
break;
}
case MINDEX: {
final int key = stack.pop();
final int value = stack.remove(key);
stack.push(value, currentPointer, currentFunc);
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++) {
failed = execute(function, gs);
if (failed) {
failedOnHinting = true;
}
}
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("");
}
failed = execute(function, gs);
if (failed) {
failedOnHinting = true;
}
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]));
y[gs.zp2][point] += (magnitude * getDoubleFromF2Dot14(fv[1]));
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])));
//Create original if p is in twilight zone
if (gs.zp1 == TWILIGHT_ZONE) {
x[ORIGINAL + TWILIGHT_ZONE][p] += shift[0];
y[ORIGINAL + TWILIGHT_ZONE][p] += shift[1];
}
//Apply 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 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])));
//Create original if p is in twilight zone
if (gs.zp1 == TWILIGHT_ZONE) {
x[ORIGINAL + TWILIGHT_ZONE][p] += shift[0];
y[ORIGINAL + TWILIGHT_ZONE][p] += shift[1];
}
//Apply 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 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);
//Create original if p is in twilight zone
if (gs.zp0 == TWILIGHT_ZONE) {
x[ORIGINAL + TWILIGHT_ZONE][p] += shift[0];
y[ORIGINAL + TWILIGHT_ZONE][p] += shift[1];
}
//Apply shift
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 cvt value or whether to use existing position
int pMove = Math.abs(target - current);
if (gs.zp0 != TWILIGHT_ZONE && pMove > gs.controlValueTableCutIn) {
target = current;
}
//round
target = storeDoubleAsF26Dot6(gs.round(getDoubleFromF26Dot6(target)));
//Generate shift
pMove = target - current;
final int[] shift = gs.getFVMoveforPVDistance(pMove);
//Create original if p is in twilight zone
if (gs.zp0 == TWILIGHT_ZONE) {
x[ORIGINAL + TWILIGHT_ZONE][p] += shift[0];
y[ORIGINAL + TWILIGHT_ZONE][p] += shift[1];
}
//Apply shift
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], currentPointer, currentFunc);
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), currentPointer, currentFunc);
break;
}
case GC0: {
final int p = stack.pop();
stack.push(TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp2][p], y[gs.zp2][p]), currentPointer, currentFunc);
break;
}
case GC1: {
final int p = stack.pop();
stack.push(TTGraphicsState.getCoordsOnVector(gs.dualProjectionVector, x[ORIGINAL + gs.zp2][p], y[ORIGINAL + gs.zp2][p]), currentPointer, currentFunc);
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);
//Create original if p is in twilight zone
if (gs.zp2 == TWILIGHT_ZONE) {
x[ORIGINAL + TWILIGHT_ZONE][p] += shift[0];
y[ORIGINAL + TWILIGHT_ZONE][p] += shift[1];
}
//Apply shift
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.zp0][p2], y[gs.zp0][p2])
- TTGraphicsState.getCoordsOnVector(gs.projectionVector, x[gs.zp1][p1], y[gs.zp1][p1]);
stack.push(distance, currentPointer, currentFunc);
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, currentPointer, currentFunc);
break;
}
case MPPEM: {
int pvppem = TTGraphicsState.getCoordsOnVector(gs.projectionVector, (int) (ppem * 64), (int) (ppem * 64)) / 64;
if (pvppem < 0) {
pvppem = -pvppem;
}
stack.push(pvppem, currentPointer, currentFunc);
break;
}
case MPS: {
stack.push((int) (ptSize * 64), currentPointer, currentFunc);
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, currentPointer, currentFunc);
} else {
stack.push(0, currentPointer, currentFunc);
}
break;
}
case LTEQ: {
final int right = stack.pop();
final int left = stack.pop();
if (left <= right) {
stack.push(1, currentPointer, currentFunc);
} else {
stack.push(0, currentPointer, currentFunc);
}
break;
}
case GT: {
final int right = stack.pop();
final int left = stack.pop();
if (left > right) {
stack.push(1, currentPointer, currentFunc);
} else {
stack.push(0, currentPointer, currentFunc);
}
break;
}
case GTEQ: {
final int right = stack.pop();
final int left = stack.pop();
if (left >= right) {
stack.push(1, currentPointer, currentFunc);
} else {
stack.push(0, currentPointer, currentFunc);
}
break;
}
case EQ: {
final int right = stack.pop();
final int left = stack.pop();
if (left == right) {
stack.push(1, currentPointer, currentFunc);
} else {
stack.push(0, currentPointer, currentFunc);
}
break;
}
case NEQ: {
final int right = stack.pop();
final int left = stack.pop();
if (left != right) {
stack.push(1, currentPointer, currentFunc);
} else {
stack.push(0, currentPointer, currentFunc);
}
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, currentPointer, currentFunc);
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, currentPointer, currentFunc);
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, currentPointer, currentFunc);
} else {
stack.push(0, currentPointer, currentFunc);
}
break;
}
case OR: {
final boolean right = stack.pop() != 0;
final boolean left = stack.pop() != 0;
if (left || right) {
stack.push(1, currentPointer, currentFunc);
} else {
stack.push(0, currentPointer, currentFunc);
}
break;
}
case NOT: {
final boolean value = stack.pop() != 0;
if (!value) {
stack.push(1, currentPointer, currentFunc);
} else {
stack.push(0, currentPointer, currentFunc);
}
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(), currentPointer, currentFunc);
break;
case SUB: {
final int right = stack.pop();
final int left = stack.pop();
stack.push(left - right, currentPointer, currentFunc);
break;
}
case DIV: {
final int right = stack.pop();
final int left = stack.pop();
if (right != 0) {
stack.push((left * 64) / right, currentPointer, currentFunc);
} else {
stack.push(0, currentPointer, currentFunc);
}
break;
}
case MUL: {
final int a = stack.pop();
final int b = stack.pop();
stack.push(a * b / 64, currentPointer, currentFunc);
break;
}
case ABS: {
int value = stack.pop();
if (value < 0) {
value = -value;
}
stack.push(value, currentPointer, currentFunc);
break;
}
case NEG:
stack.push(-stack.pop(), currentPointer, currentFunc);
break;
case FLOOR:
stack.push((stack.pop() >> 6) << 6, currentPointer, currentFunc);
break;
case CEILING: {
int value = stack.pop();
if ((value & 63) != 0) {
value = (((value >> 6) + 1) << 6);
}
stack.push(value, currentPointer, currentFunc);
break;
}
case ROUND00: {
int n = stack.pop();
n = engineCompensation(n, 0);
final double num = getDoubleFromF26Dot6(n);
stack.push(storeDoubleAsF26Dot6(gs.round(num)), currentPointer, currentFunc);
break;
}
case ROUND01: {
int n = stack.pop();
n = engineCompensation(n, 1);
final double num = getDoubleFromF26Dot6(n);
stack.push(storeDoubleAsF26Dot6(gs.round(num)), currentPointer, currentFunc);
break;
}
case ROUND10: {
int n = stack.pop();
n = engineCompensation(n, 2);
final double num = getDoubleFromF26Dot6(n);
stack.push(storeDoubleAsF26Dot6(gs.round(num)), currentPointer, currentFunc);
break;
}
case ROUND11: {
int n = stack.pop();
n = engineCompensation(n, 3);
final double num = getDoubleFromF26Dot6(n);
stack.push(storeDoubleAsF26Dot6(gs.round(num)), currentPointer, currentFunc);
break;
}
case NROUND00:
stack.push(engineCompensation(stack.pop(), 0), currentPointer, currentFunc);
break;
case NROUND01:
stack.push(engineCompensation(stack.pop(), 1), currentPointer, currentFunc);
break;
case NROUND10:
stack.push(engineCompensation(stack.pop(), 2), currentPointer, currentFunc);
break;
case NROUND11:
stack.push(engineCompensation(stack.pop(), 3), currentPointer, currentFunc);
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, currentPointer, currentFunc);
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, currentPointer, currentFunc);
stack.push(top, currentPointer, currentFunc);
stack.push(bottom, currentPointer, currentFunc);
break;
}
case MAX: {
final int value1 = stack.pop();
final int value2 = stack.pop();
if (value1 > value2) {
stack.push(value1, currentPointer, currentFunc);
} else {
stack.push(value2, currentPointer, currentFunc);
}
break;
}
case MIN: {
final int value1 = stack.pop();
final int value2 = stack.pop();
if (value1 < value2) {
stack.push(value1, currentPointer, currentFunc);
} else {
stack.push(value2, currentPointer, currentFunc);
}
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 = (args & paramRESETRP0) == paramRESETRP0;
boolean useMinimumDistance = (args & paramUSEMINDIST) == paramUSEMINDIST;
boolean roundDistanceAndCheckCutIn = (args & paramROUND) == paramROUND;
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;
}
if (gs.zp1 == TWILIGHT_ZONE) {
x[ORIGINAL + TWILIGHT_ZONE][p] = x[ORIGINAL + gs.zp0][gs.rp0] +
(int) (getDoubleFromF2Dot14(TTGraphicsState.getVectorComponents(gs.freedomVector)[0]) * cvtEntry);
x[TWILIGHT_ZONE][p] = x[ORIGINAL + TWILIGHT_ZONE][p];
y[ORIGINAL + TWILIGHT_ZONE][p] = y[ORIGINAL + gs.zp0][gs.rp0] +
(int) (getDoubleFromF2Dot14(TTGraphicsState.getVectorComponents(gs.freedomVector)[1]) * cvtEntry);
y[TWILIGHT_ZONE][p] = y[ORIGINAL + TWILIGHT_ZONE][p];
}
//Check autoflip and match CVT sign to distance sign
if (gs.autoFlip && ((distance < 0 && cvtEntry > 0) || (distance > 0 && cvtEntry < 0))) {
cvtEntry = -cvtEntry;
}
//Check CVT cut-in
if (roundDistanceAndCheckCutIn && gs.zp0 == gs.zp1) {
//Test if close enough to use cvt value
int pMove = Math.abs(distance - cvtEntry);
if (pMove < 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);
//Create original if p is in twilight zone
if (gs.zp1 == TWILIGHT_ZONE) {
x[ORIGINAL + TWILIGHT_ZONE][p] += shift[0];
y[ORIGINAL + TWILIGHT_ZONE][p] += shift[1];
}
//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");
}
failed = execute(instructions.get(code), gs);
if (failed) {
failedOnHinting = true;
}
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;
failedOnHinting = true;
}
if (showDebugWindow && debugWindow != null && debugWindow.isVisible()) {
instructionsExecuted += currentPointer + 1 - originalPointer;
if (debugPointer == -1) {
return debugPointer;
}
}
if (failedOnHinting) {
return -currentPointer;
} else {
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 < this.touched[GLYPH_ZONE].length; i++) {
touched[i] = this.touched[GLYPH_ZONE][i][0];
}
} else {
points = y[GLYPH_ZONE];
original = y[ORIGINAL + GLYPH_ZONE];
for (int i = 0; i < this.touched[GLYPH_ZONE].length; i++) {
touched[i] = this.touched[GLYPH_ZONE][i][1];
}
}
//go through contours
int contourStart = 0;
while (contourStart < points.length) {
//get info on touched points
final int[] touchedPointNumbers = new int[original.length];
int touchedCount = 0, point = 0;
do {
if (touched[contourStart + point]) {
touchedPointNumbers[touchedCount] = contourStart + point;
touchedCount++;
}
point++;
} while (!contour[GLYPH_ZONE][contourStart + point - 1] && (contourStart + point) < contour[GLYPH_ZONE].length);
//process points
if (touchedCount == 1) {
//If only one touched point, shift all points in contour to match
final int shift = points[touchedPointNumbers[0]] - original[touchedPointNumbers[0]];
for (int i = contourStart; i < contourStart + point; i++) {
if (!touched[i]) {
points[i] += shift;
}
}
} else if (touchedCount > 1) {
//Loop through pairs interpolating the points between them
for (int i = 0; i < touchedCount; i++) {
if (i + 1 >= 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 < number; i++) {
final int data;
currentPointer++;
if (!readWord) {
data = program[currentPointer];
} else {
final int d1 = program[currentPointer];
currentPointer++;
final int d2 = program[currentPointer];
data = getIntFrom2Uint8(d1, d2);
}
//push to stack
stack.push(data, currentPointer, currentFunc);
}
return currentPointer;
}
/**
* Takes two Uint8s containing 8 bits of data each and converts them to a
* signed integer.
*
* @param high first int
* @param low second int
* @return signed int
*/
protected static int getIntFrom2Uint8(final int high, final int low) {
return ((high << 8) + low)
+ //main concatenation
((high >> 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;
private String[] pushedByLine;
Stack() {
stack = new int[10];
pushedByLine = new String[10];
}
/**
* Adds an item to the top of the stack, expanding the stack if needed
* @param a New item for stack
* @param currentPointer
* @param currentFunc
*/
public void push(final int a, int currentPointer, String currentFunc) {
if (pointer >= stack.length) {
final int[] newStack = new int[(int) (stack.length * 1.5)];
System.arraycopy(stack, 0, newStack, 0, stack.length);
stack = newStack;
final String[] newMBL = new String[(int) (stack.length * 1.5)];
System.arraycopy(pushedByLine, 0, newMBL, 0, pushedByLine.length);
pushedByLine = newMBL;
}
stack[pointer] = a;
pushedByLine[pointer] = currentFunc + " Line " + currentPointer;
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;
final String[] newPBL = new String[stack.length];
System.arraycopy(pushedByLine, 0, newPBL, 0, valPos);
System.arraycopy(pushedByLine, valPos + 1, newPBL, valPos, (stack.length - valPos) - 1);
pushedByLine = newPBL;
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;
}
public String getPushedBy(int i) {
return pushedByLine[pointer - (i + 1)];
}
}
/**
* 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);
stepOutButton = new JButton("Step Out");
stepOutButton.setEnabled(false);
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();
for (MouseMotionListener l : currentInstructionList.getMouseMotionListeners()) {
currentInstructionList.removeMouseMotionListener(l);
}
for (MouseListener l : currentInstructionList.getMouseListeners()) {
currentInstructionList.removeMouseListener(l);
}
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 < x[GLYPH_ZONE].length; i++) {
int val = x[GLYPH_ZONE][i];
if (val > maxX) {
maxX = val;
}
if (val < minX) {
minX = val;
}
val = y[GLYPH_ZONE][i];
if (val > maxY) {
maxY = val;
}
if (val < minY) {
minY = val;
}
}
for (int i = 0; i < x[TWILIGHT_ZONE].length; i++) {
int val = x[TWILIGHT_ZONE][i];
if (val > maxX) {
maxX = val;
}
if (val < minX) {
minX = val;
}
val = y[TWILIGHT_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 < x[GLYPH_ZONE].length; i++) {
final int xVal = x[GLYPH_ZONE][i];
final int yVal = y[GLYPH_ZONE][i];
//Point
if (curve[GLYPH_ZONE][i]) {
g2.setPaint(Color.BLACK);
final Shape s = new Ellipse2D.Double(xVal - (2 / scale), yVal - (2 / scale), (4 / scale), (4 / scale));
g2.fill(s);
} else {
g2.setPaint(Color.RED);
g2.drawLine(xVal - len, yVal - len, xVal + len, yVal + len);
g2.drawLine(xVal + len, yVal - len, xVal - len, yVal + len);
}
//Number
final AffineTransform store = g2.getTransform();
g2.translate(xVal, yVal);
g2.scale(1 / scale, -1 / scale);
g2.drawString(String.valueOf(i), 3, -3);
g2.setTransform(store);
}
//Draw interpolated shadow
if (showInterpolatedShadow.isSelected()) {
final int c = x[GLYPH_ZONE].length;
final int[] xStore = new int[c];
System.arraycopy(x[GLYPH_ZONE], 0, xStore, 0, c);
final int[] yStore = new int[c];
System.arraycopy(y[GLYPH_ZONE], 0, yStore, 0, c);
final boolean[] curveStore = new boolean[c];
System.arraycopy(curve[GLYPH_ZONE], 0, curveStore, 0, c);
final boolean[] contourStore = new boolean[c];
System.arraycopy(contour[GLYPH_ZONE], 0, contourStore, 0, c);
interpolateUntouchedPoints(IUPy);
interpolateUntouchedPoints(IUPx);
final GeneralPath shape = getPathFromPoints(x[GLYPH_ZONE], y[GLYPH_ZONE], curve[GLYPH_ZONE], contour[GLYPH_ZONE]);
g2.setPaint(new Color(255, 0, 0, 100));
g2.draw(shape);
g2.setPaint(new Color(0, 0, 0, 30));
g2.fill(shape);
System.arraycopy(xStore, 0, x[GLYPH_ZONE], 0, c);
System.arraycopy(yStore, 0, y[GLYPH_ZONE], 0, c);
System.arraycopy(curveStore, 0, curve[GLYPH_ZONE], 0, c);
System.arraycopy(contourStore, 0, contour[GLYPH_ZONE], 0, c);
}
//Draw glyph
final GeneralPath shape = getPathFromPoints(x[GLYPH_ZONE], y[GLYPH_ZONE], curve[GLYPH_ZONE], contour[GLYPH_ZONE]);
g2.setPaint(new Color(100, 100, 255, 100));
g2.fill(shape);
g2.setPaint(Color.BLACK);
g2.draw(shape);
//Draw Twilight points
g2.setPaint(Color.BLUE);
for (int i = 0; i < x[TWILIGHT_ZONE].length; i++) {
final int xVal = x[TWILIGHT_ZONE][i];
final int yVal = y[TWILIGHT_ZONE][i];
if (xVal != 0 || yVal != 0) {
//Point
if (curve[TWILIGHT_ZONE][i]) {
final Shape s = new Ellipse2D.Double(xVal - (2 / scale), yVal - (2 / scale), (4 / scale), (4 / scale));
g2.fill(s);
} else {
g2.drawLine(xVal - len, yVal - len, xVal + len, yVal + len);
g2.drawLine(xVal + len, yVal - len, xVal - len, yVal + len);
}
//Number
final AffineTransform store = g2.getTransform();
g2.translate(xVal, yVal);
g2.scale(1 / scale, -1 / scale);
g2.drawString(String.valueOf(i), 3, -3);
g2.setTransform(store);
}
}
}
};
debugGlyphDisplay.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(final MouseEvent e) {
double eX = e.getX();
double eY = e.getY();
final int w = debugGlyphDisplay.getWidth();
final int h = debugGlyphDisplay.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 < x[GLYPH_ZONE].length; i++) {
int val = x[GLYPH_ZONE][i];
if (val > 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();
stackList.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
int i = stackList.locationToIndex(e.getPoint());
if (i >= 0) {
stackList.setToolTipText("Pushed by " + stack.getPushedBy(i));
}
}
});
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; ii < ptCount; ii++) {
if (endOfContour[ii]) {
if (firstPt != -1 && (!onCurve[start] || !onCurve[ii])) { //last point not on curve and we have a first point
final int diff = firstPt - start;
int newPos;
//make a deep copy of values
final int pXlength = pX.length;
final int[] old_pX = new int[pXlength];
System.arraycopy(pX, 0, old_pX, 0, pXlength);
final int[] old_pY = new int[pXlength];
System.arraycopy(pY, 0, old_pY, 0, pXlength);
final boolean[] old_onCurve = new boolean[pXlength];
System.arraycopy(onCurve, 0, old_onCurve, 0, pXlength);
//rotate values to ensure point at start
for (int oldPos = start; oldPos < ii + 1; oldPos++) {
newPos = oldPos + diff;
if (newPos > ii) {
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; jj < c; jj++) {
if (endOfContour[jj]) {
fc = jj + 1;
jj = c;
}
}
int x1, y1, x2 = 0, y2 = 0, x3 = 0, y3 = 0;
x1 = pX[0];
y1 = pY[0];
current_path.moveTo(x1, y1);
int xs = 0, ys = 0, lc = 0;
boolean isEnd = false;
for (int j = 0; j < ptCount; j++) {
final int p = j % fc;
int p1 = (j + 1) % fc;
int p2 = (j + 2) % fc;
int pm1 = (j - 1) % fc;
/* special cases
*
*round up to last point at end
*First point
*/
if (j == 0) {
pm1 = fc - 1;
}
if (p1 < lc) {
p1 += lc;
}
if (p2 < lc) {
p2 += lc;
}
//allow for wrap around on contour
if (endOfContour[j]) {
isEnd = true;
if (onCurve[fc]) {
xs = pX[fc];
ys = pY[fc];
} else {
xs = pX[j + 1];
ys = pY[j + 1];
}
//remember start point
lc = fc;
//find next contour
for (int jj = j + 1; jj < c; jj++) {
if (endOfContour[jj]) {
fc = jj + 1;
jj = c;
}
}
}
if (lc == fc && onCurve[p]) {
j = c;
} else {
if (onCurve[p] && onCurve[p1]) { //straight line
x3 = pX[p1];
y3 = pY[p1];
current_path.lineTo(x3, y3);
isFirstDraw = false;
//curves
} else if (j < (c - 3) && ((fc - lc) > 1 || 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 < c; jj++) {
if (endOfContour[jj]) {
fc = jj + 1;
jj = c;
}
}
}
}
if (endOfContour[p]) {
current_path.closePath();
}
if (isEnd) {
current_path.moveTo(xs, ys);
isEnd = false;
}
}
}
return current_path;
}
/**
* DEBUG METHOD -
*
* Runs the debugger such that it is at a specified point in the current
* code. This may be less than the current number, in which case the glyph
* program is restarted and run to that point.
*
* @param targetPointer
*/
private void runDebuggerTo(final int targetPointer) {
if (targetPointer == debugPointer) {
return;
}
int targetInstr = targetPointer;
int diff = -1;
if (targetInstr >= 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);
if (!stepInto && debugPointer < 0) { //negative value used as boolean flag
debugPointer = -debugPointer;
}
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();
graphicsState.resetForGlyph();
} 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];
fontProgramRun = false;
setScaleVars(scaler, ppem, ptSize);
if (!codeStack.isEmpty()) {
programToDebug = codeStack.get(0);
programToDebugIsData = getInstructionStreamIsData(programToDebug);
}
codeStack.clear();
numberStack.clear();
functionsLineCount = 0;
debugPointer = 0;
instructionsExecuted = 0;
currentCode.setText("Glyph Program");
stepOutButton.setEnabled(false);
}
/**
* 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);
if (!stepInto && debugPointer < 0) { //negative value used as boolean flag
debugPointer = -debugPointer;
}
stepInto = false;
debugPointer++;
refreshDebugger(false);
while (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 < storage.length; i++) {
result[i] = i + ": " + storage[i] + " (" + NumberFormat.getNumberInstance().format(storage[i] / 64d) + ')';
}
return result;
}
/**
* DEBUG METHOD -
*
* Refreshes the debugger's display components.
*
* @param programHasChanged
*/
private void refreshDebugger(final boolean programHasChanged) {
if (programHasChanged) {
currentInstructionList.setListData(getInstructionsAsStringArray(programToDebug));
}
final int start = debugPointer;
int end = debugPointer;
while (end + 1 < programToDebug.length && programToDebugIsData[end + 1]) {
end++;
}
currentInstructionList.setSelectionInterval(start, end);
if (start != 0) {
int forward = end + 3;
if (forward >= 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);
currentCode.setText(getCurrentFunc());
stepOutButton.setEnabled(!"Glyph program".equals(currentFunc));
}
private String getCurrentFunc() {
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;
}
}
return 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 < x[GLYPH_ZONE].length; i++) {
System.out.print(i + "\t" + x[GLYPH_ZONE][i] + '\t' + y[GLYPH_ZONE][i] + '\t' + x[ORIGINAL + GLYPH_ZONE][i] + '\t' + y[ORIGINAL + GLYPH_ZONE][i]);
System.out.println("");
if (contour[GLYPH_ZONE][i]) {
System.out.println();
}
}
System.out.println("");
System.out.println("");
}
/**
* DEBUG METHOD -
*
* Prints out a set of instructions without executing them. It's not a quick
* method, as it uses reflection to do so!
*
* @param program Instructions to print
*/
public static void print(final int[] program) {
System.out.println("");
final String[] toPrint = getInstructionsAsStringArray(program);
for (final String aToPrint : toPrint) {
System.out.println(aToPrint);
}
System.out.println("");
System.out.println("");
}
/**
* DEBUG METHOD -
*
* Converts an array of instructions in bytecode into a human-readable
* string array for display.
*
* @param program
* @return
*/
public static String[] getInstructionsAsStringArray(final int[] program) {
if (program == null) {
return new String[]{};
}
final String[] result = new String[program.length];
final StringBuilder depth = new StringBuilder();
final int[] notFoundCount = new int[0xFFFF];
for (int n = 0; n < program.length; n++) {
boolean found = false;
int test = program[n];
if (test >= MDRP && test <= MDRP + 0x1F) {
test = MDRP;
}
if (test >= 0xE0 && test <= 0xFF) {
test = 0xE0;
}
if (test >= PUSHB && test <= PUSHB + 0x7) {
test = PUSHB;
}
if (test >= PUSHW && test <= PUSHW + 0x7) {
test = PUSHW;
}
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()) ||
"OPCODE_DESCRIPTIONS".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 < count; j++) {
n++;
result[n] = (depth + " " + program[n]);
}
} else if ("NPUSHW".equals(declaredField.getName())) {
n++;
final int count = program[n];
result[n] = (depth + " count: " + count);
for (int j = 0; j < count; j++) {
n++;
final int word = getIntFrom2Uint8(program[n], program[n + 1]);
result[n] = (depth + " (first half of number)");
n++;
result[n] = (depth + " " + word);
}
} else if (program[n] >= 0xb0 && program[n] <= 0xb7) {
final int count = (program[n] - 0xAF);
for (int j = 0; j < count; j++) {
n++;
result[n] = (depth + " " + program[n]);
}
} else if (program[n] >= 0xb8 && program[n] <= 0xbF) {
final int count = (program[n] - 0xb7);
for (int j = 0; j < count; j++) {
n++;
final int word = getIntFrom2Uint8(program[n], program[n + 1]);
result[n] = (depth + " (first half of number)");
n++;
result[n] = (depth + " " + word);
}
} else if ("FDEF".equals(declaredField.getName()) || "IDEF".equals(declaredField.getName()) || "ELSE".equals(declaredField.getName()) || "IF".equals(declaredField.getName())) {
depth.append(" ");
}
found = true;
}
} catch (final IllegalAccessException e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}
}
if (!found) {
notFoundCount[program[n]]++;
System.out.println(depth + "0x" + Integer.toHexString(program[n]) + " (Unimplemented)");
}
}
for (int i = 0; i < 0xFF; i++) {
if (notFoundCount[i] > 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 < program.length; n++) {
boolean found = false;
int test = program[n];
if (test >= 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())
|| "OPCODE_DESCRIPTIONS".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 < count; j++) {
n++;
result[n] = true;
}
} else if ("NPUSHW".equals(declaredField.getName())) {
n++;
final int count = program[n];
result[n] = true;
for (int j = 0; j < count; j++) {
n++;
result[n] = true;
n++;
result[n] = true;
}
} else if (program[n] >= 0xb0 && program[n] <= 0xb7) {
final int count = (program[n] - 0xAF);
for (int j = 0; j < count; j++) {
n++;
result[n] = true;
}
} else if (program[n] >= 0xb8 && program[n] <= 0xbF) {
final int count = (program[n] - 0xb7);
for (int j = 0; j < count; j++) {
n++;
result[n] = true;
n++;
result[n] = true;
}
}
found = true;
}
} catch (final IllegalAccessException e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}
}
}
return result;
}
}