| /* VirtualMachine.java -- Virtual machine for TrueType bytecodes. |
| Copyright (C) 2006 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| package gnu.java.awt.font.opentype.truetype; |
| |
| import java.awt.FontFormatException; |
| import java.awt.geom.AffineTransform; |
| import java.nio.ByteBuffer; |
| import java.nio.ShortBuffer; |
| |
| |
| /** |
| * A virtual machine for interpreting TrueType bytecodes. |
| * |
| * <p><b>Lack of Thread Safety:</b> The virtual machine is |
| * intentionally <i>not</i> safe to access from multiple concurrent |
| * threads. Synchronization needs to be performed externally. Usually, |
| * the font has already obtained a lock before calling the scaler, |
| * which in turn calls the VM. It would be wasteful to acquire |
| * additional locks for the VM. |
| * |
| * <p><b>Implementation Status:</b> The current implementation can |
| * execute pre-programs of fonts, but it does not yet actually move |
| * any points. Control flow and arithmeti instructions are |
| * implemented, but most geometric instructions are not working |
| * yet. So, the VirtualMachine class is currently a no-op. However, |
| * not very much is missing. You are more than welcome to complete the |
| * implementation. |
| * |
| * <p><b>Patents:</b> Apple Computer holds three United States Patents |
| * for the mathematical algorithms that are used by TrueType |
| * instructions. The monopoly granted by these patents will expire in |
| * October 2009. Before the expiration date, a license must be |
| * obtained from Apple Computer to use the patented technology inside |
| * the United States. For other countries, different dates might |
| * apply, or no license might be needed. |
| * |
| * <p>The default build of this class does not use the patented |
| * algorithms. If you have obtained a license from Apple, or if the |
| * patent protection has expired, or if no license is required for |
| * your contry, you can set a flag in the source file which will |
| * enable the use of the patented mathematical algorithms.</p> |
| * |
| * <p>The relevant patents are listed subsequently.</p> |
| * |
| * <p><ol><li>United States Patent 5155805, <i>Method and Apparatus |
| * for Moving Control Points in Displaying Digital Typeface on Raster |
| * Output Devices,</i> invented by Sampo Kaasila, assigned to Apple |
| * Computer. Filing date: May 8, 1989. Date of patent: October 13, |
| * 1992.</li> |
| * |
| * <li>United States Patent 5159668, <i>Method and Apparatus for |
| * Manipulating Outlines in Improving Digital Typeface on Raster |
| * Output Devices,</i> invented by Sampo Kaasila, assigned to Apple |
| * Computer. Filing date: May 8, 1989. Date of patent: October 27, |
| * 1992.</li> |
| * |
| * <li>United States Patent 5325479, <i>Method and Apparatus for |
| * Moving Control Points in Displaying Digital Typeface on Raster |
| * Output Devices,</i> invented by Sampo Kaasila, assigned to Apple |
| * Computer. Filing date: May 28, 1989. Date of patent: June 28, 1994 |
| * (with a statement that “[t]he portion of the term of this |
| * patent subsequent to Oct. 13, 2009 has been |
| * disclaimed”).</li></ol> |
| * |
| * @author Sascha Brawer (brawer@dandelis.ch) |
| */ |
| class VirtualMachine |
| { |
| /** |
| * Indicates whether or not to perform hinting operations that are |
| * protected by a number of US patents, two of which will expire on |
| * October 13, 2009, and one of which will expire on October 27, |
| * 2009. |
| */ |
| private final static boolean PATENTED_HINTING = false; |
| |
| |
| /** |
| * Indicates whether the execution of the Virtual Machine is traced |
| * to System.out. |
| */ |
| private final static boolean TRACE_EXECUTION = false; |
| |
| |
| /** |
| * The value 1 in 2-dot-14 fixed notation. |
| */ |
| private static final short ONE_214 = 0x4000; // 1 << 14 |
| |
| |
| /** |
| * The storage area of the virtual machine. |
| */ |
| private final int[] storage; |
| |
| |
| /** |
| * The stack. The stack grows from bottom to top, so |
| * <code>sp[0]</code> gets used before <code>sp[1]</code>. |
| */ |
| private int[] stack; |
| |
| |
| /** |
| * The maximum number of stack elements. |
| */ |
| private final int maxStackElements; |
| |
| |
| /** |
| * The current stack pointer of the virtual machine. |
| */ |
| private int sp; |
| |
| |
| /** |
| * fdefBuffer[i] is the buffer that contains the TrueType |
| * instructions of function #i. Most of the time, functions are |
| * defined in the font program, but a font may also re-define |
| * functions in its CVT program. |
| */ |
| private ByteBuffer[] fdefBuffer; |
| |
| |
| /** |
| * fdefEntryPoint[i] is the position in fdefBuffer[i] where the |
| * first TrueType instruction after the FDEF is located. |
| */ |
| private int[] fdefEntryPoint; |
| |
| |
| /** |
| * The original Control Value Table, sometimes abbreviated as CVT. |
| * The table contains signed 16-bit FUnits. Some fonts have no CVT, |
| * in which case the field will be <code>null</code>. |
| */ |
| private ShortBuffer controlValueTable; |
| |
| |
| /** |
| * The scaled values inside the control value table. |
| */ |
| private int[] cvt; |
| |
| |
| /** |
| * A value that is used by rounding operations to compensate for dot |
| * gain. |
| */ |
| private int engineCompensation = 0; |
| |
| |
| /** |
| * The contents of the font’s <code>fpgm</code> table, or |
| * <code>null</code> after the font program has been executed once. |
| */ |
| private ByteBuffer fontProgram; |
| |
| |
| /** |
| * The <code>prep</code> table of the font, which contains a program |
| * that is executed whenever the point size or the device transform |
| * have changed. This program is called pre-program because it gets |
| * executed before the instructions of the individual glyphs. If |
| * the font does not contain a pre-program, the value of this field |
| * is <code>null</code>. |
| */ |
| private ByteBuffer preProgram; |
| |
| |
| /** |
| * The number of points in the Twilight Zone. |
| */ |
| private int numTwilightPoints; |
| |
| |
| /** |
| * The current point size of the scaled font. The value is in Fixed |
| * 26.6 notation. |
| */ |
| private int pointSize; // 26.6 |
| |
| private AffineTransform deviceTransform; |
| |
| private int scaleX, scaleY, shearX, shearY; // 26.6 |
| |
| |
| /** |
| * Indicates whether or not scan-line conversion will use |
| * anti-aliasing (with gray levels). Font programs can ask for this |
| * value with the <code>GETINFO</code> instruction, and some |
| * programs may behave differently according to this setting. |
| */ |
| private boolean antialiased; |
| |
| |
| /* Graphics State. FIXME: Move this to its own class? Some |
| * documentation would not hurt, either. |
| */ |
| private int cvtCutIn; // 26.6 |
| private int deltaBase; // uint32 |
| private int deltaShift; // uint32 |
| private short freeX; // 2.14 |
| private short freeY; // 2.14 |
| private int loop; // int |
| private int minimumDistance; // 26.6 |
| private short projX; // 2.14 |
| private short projY; // 2.14 |
| private short dualX; // 2.14 |
| private short dualY; // 2.14 |
| private int rp0, rp1, rp2; // point numbers |
| private boolean scanControl; |
| private int scanType; |
| private int singleWidthValue; // 26.6 |
| private Zone zp0, zp1, zp2; |
| |
| private Zone twilightZone; |
| private Zone glyphZone; |
| |
| |
| /** |
| * Indicates whether or not the instructions that are associated |
| * with individual glyphs shall be executed. Set as a side effect |
| * of executing the pre-program when the point size, device |
| * transform or some other relevant parameter have changed. |
| */ |
| private boolean executeGlyphInstructions; |
| |
| |
| /** |
| * Indicates whether to ignore any modifications to the control |
| * value table that the font’s pre-program might have |
| * performed. Set as a side effect of executing the pre-program |
| * when the point size, device transform or some other relevant |
| * parameter have changed. |
| */ |
| private boolean ignoreCVTProgram; |
| |
| |
| /** |
| * The length of the space between rounded values. A value |
| * of zero means that rounding has been switched off. |
| */ |
| private int roundPeriod; // 26.6 |
| |
| |
| /** |
| * The offset of the rounded values from multiples of |
| * <code>roundPeriod</code>. |
| */ |
| private int roundPhase; // 26.6 |
| |
| |
| private int roundThreshold; // 26.6 |
| |
| |
| /** |
| * A cache for the number of pixels per EM. The value is a normal |
| * integer, not a fixed point notation. |
| * |
| * @see #getPixelsPerEM() |
| */ |
| private int cachedPixelsPerEM; |
| |
| |
| /** |
| * The number of font units per EM. |
| */ |
| private int unitsPerEm; |
| |
| |
| /** |
| * Constructs a new Virtual Machine for executing TrueType |
| * instructions. |
| * |
| * @param unitsPerEm the number of font units in one typographic |
| * em. |
| * |
| * @param preProgram the <code>prep</code> table of the font, which |
| * contains a program that is executed whenever the point size or |
| * the device transform have changed. This program is called |
| * pre-program because it gets executed before the instructions of |
| * the individual glyphs. If the font does not contain a |
| * pre-program, pass <code>null</code>. |
| */ |
| VirtualMachine(int unitsPerEm, |
| ByteBuffer maxp, |
| ByteBuffer controlValueTable, |
| ByteBuffer fontProgram, |
| ByteBuffer preProgram) |
| throws FontFormatException |
| { |
| int maxStorage, numFunctionDefs, maxInstructionDefs; |
| |
| if (maxp.getInt(0) != 0x00010000) |
| throw new FontFormatException("unsupported maxp version"); |
| |
| this.unitsPerEm = unitsPerEm; |
| maxStorage = maxp.getChar(18); |
| |
| /* FreeType says that there exist some broken fonts (like |
| * "Keystrokes MT") that contain function defs, but have a zero |
| * value in their maxp table. |
| */ |
| numFunctionDefs = maxp.getChar(20); |
| if (numFunctionDefs == 0) |
| numFunctionDefs = 64; |
| fdefBuffer = new ByteBuffer[numFunctionDefs]; |
| fdefEntryPoint = new int[numFunctionDefs]; |
| |
| /* Read the contents of the Control Value Table. */ |
| if (controlValueTable != null) |
| this.controlValueTable = controlValueTable.asShortBuffer(); |
| |
| maxInstructionDefs = maxp.getChar(22); |
| maxStackElements = maxp.getChar(24); |
| storage = new int[maxStorage]; |
| this.fontProgram = fontProgram; |
| this.preProgram = preProgram; |
| numTwilightPoints = maxp.getChar(16); |
| } |
| |
| |
| /** |
| * Sets the graphics state to default values. |
| */ |
| private void resetGraphicsState() |
| { |
| /* The freedom, projection and dual vector default to the x axis. */ |
| freeX = projX = dualX = ONE_214; |
| freeY = projY = dualX = 0; |
| cachedPixelsPerEM = 0; |
| |
| cvtCutIn = 68; // 17/16 in 26.6 notation |
| deltaBase = 9; |
| deltaShift = 3; |
| loop = 1; |
| minimumDistance = Fixed.ONE; |
| singleWidthValue = 0; |
| rp0 = rp1 = rp2 = 0; |
| scanControl = false; |
| scanType = 2; |
| zp0 = zp1 = zp2 = getZone(1); |
| |
| setRoundingMode(Fixed.ONE, 0x48); // round to grid |
| } |
| |
| |
| /** |
| * Reloads the control value table and scales each entry from font |
| * units to pixel values. |
| */ |
| private void reloadControlValueTable() |
| { |
| /* Some TrueType fonts have no control value table. */ |
| if (controlValueTable == null) |
| return; |
| |
| /* Read in the Control Value Table. */ |
| if (cvt == null) |
| cvt = new int[controlValueTable.capacity()]; |
| |
| /* Scale the entries. */ |
| for (int i = 0; i < cvt.length; i++) |
| cvt[i] = funitsToPixels(controlValueTable.get(i)); |
| } |
| |
| |
| /** |
| * Scales a value from font unites to pixels. |
| * |
| * @return the scaled value. |
| */ |
| private int funitsToPixels(int funits) |
| { |
| return (int) (((long) funits * scaleY + (unitsPerEm>>1)) |
| / unitsPerEm); |
| } |
| |
| |
| /** |
| * Sets up the virtual machine for the specified parameters. If |
| * there is no change to the last set-up, the method will quickly |
| * return. Otherwise, the font’s pre-program will be |
| * executed. |
| * |
| * @param pointSize the point size of the scaled font. |
| * |
| * @param deviceTransform an affine transformation which gets |
| * applied in addition to scaling by <code>pointSize</code>. Font |
| * programs can separately inquire about the point size. For this |
| * reason, it is not recommended to pre-multiply the point size to |
| * the device transformation. |
| * |
| * @param antialiased <code>true</code> if the scan-line conversion |
| * algorithm will use gray levels to give a smoother appearance, |
| * <code>false</code> otherwise. Font programs can ask for this |
| * value with the <code>GETINFO</code> instruction, and some |
| * programs may behave differently according to this setting. |
| */ |
| public boolean setup(double pointSize, |
| AffineTransform deviceTransform, |
| boolean antialiased) |
| { |
| boolean changeCTM; |
| int pointSize_Fixed; |
| |
| if (stack == null) |
| stack = new int[maxStackElements]; |
| |
| if (twilightZone == null) |
| twilightZone = new Zone(numTwilightPoints); |
| |
| /* If the font program has not yet been executed, do so. */ |
| if (fontProgram != null) |
| { |
| resetGraphicsState(); |
| sp = -1; |
| execute(fontProgram, 0); |
| fontProgram = null; // prevent further execution |
| } |
| |
| /* Determine whether the transformation matrix has changed. */ |
| pointSize_Fixed = Fixed.valueOf(pointSize); |
| changeCTM = ((pointSize_Fixed != this.pointSize) |
| || !deviceTransform.equals(this.deviceTransform) |
| || (antialiased != this.antialiased)); |
| |
| if (changeCTM) |
| { |
| this.pointSize = pointSize_Fixed; |
| this.deviceTransform = deviceTransform; |
| this.antialiased = antialiased; |
| scaleX = (int) (deviceTransform.getScaleX() * pointSize * 64); |
| scaleY = (int) (deviceTransform.getScaleY() * pointSize * 64); |
| shearX = (int) (deviceTransform.getShearX() * pointSize * 64); |
| shearY = (int) (deviceTransform.getShearY() * pointSize * 64); |
| |
| resetGraphicsState(); |
| reloadControlValueTable(); |
| executeGlyphInstructions = true; |
| ignoreCVTProgram = false; |
| |
| if (preProgram != null) |
| { |
| sp = -1; |
| execute(preProgram, 0); |
| if (ignoreCVTProgram) |
| reloadControlValueTable(); |
| } |
| } |
| |
| return executeGlyphInstructions; |
| } |
| |
| |
| /** |
| * Executes a stream of TrueType instructions. |
| */ |
| private void execute(ByteBuffer instructions, int pos) |
| { |
| instructions.position(pos); |
| |
| // FIXME: SECURITY: Possible denial-of-service attack |
| // via instructions that have an endless loop. |
| while (instructions.hasRemaining() |
| && executeInstruction(instructions)) |
| ; |
| } |
| |
| |
| /** |
| * Writes a textual description of the current TrueType instruction, |
| * including the top stack elements, to <code>System.out</code>. |
| * This is useful for debugging. |
| * |
| * @param inst the instruction stream, positioned at the current |
| * instruction. |
| */ |
| private void dumpInstruction(ByteBuffer inst) |
| { |
| StringBuffer sbuf = new StringBuffer(40); |
| int pc = inst.position(); |
| int bcode = inst.get(pc) & 0xff; |
| int count; |
| int delta; |
| |
| char pcPrefix = 'c'; |
| for (int i = 0; i < fdefBuffer.length; i++) |
| { |
| if (fdefBuffer[i] == inst) |
| { |
| pcPrefix = 'f'; |
| break; |
| } |
| } |
| sbuf.append(pcPrefix); |
| |
| |
| sbuf.append(getHex((short) inst.position())); |
| sbuf.append(": "); |
| sbuf.append(getHex((byte) bcode)); |
| sbuf.append(" "); |
| sbuf.append(INST_NAME[bcode]); |
| |
| if (bcode == 0x40) // NPUSHB |
| { |
| count = inst.get(pc + 1) & 0xff; |
| sbuf.append(" ("); |
| sbuf.append(count); |
| sbuf.append(") "); |
| for (int i = 0; i < count; i++) |
| { |
| if (i > 0) |
| sbuf.append(" "); |
| sbuf.append('$'); |
| sbuf.append(getHex(inst.get(pc + 2 + i))); |
| } |
| } |
| if (bcode == 0x41) // NPUSHW |
| { |
| count = inst.get(pc + 1) & 0xff; |
| sbuf.append(" ("); |
| sbuf.append(count); |
| sbuf.append(") "); |
| for (int i = 0; i < count; i++) |
| { |
| if (i > 0) |
| sbuf.append(' '); |
| sbuf.append('$'); |
| sbuf.append(getHex(inst.getShort(pc + 2 + 2*i))); |
| } |
| } |
| else |
| { |
| count = getInstructionLength(bcode) - 1; |
| for (int i = 0; i < count; i++) |
| { |
| sbuf.append(" $"); |
| sbuf.append(getHex(inst.get(pc + 1 + i))); |
| } |
| } |
| |
| while (sbuf.length() < 30) |
| sbuf.append(' '); |
| sbuf.append('|'); |
| sbuf.append(sp + 1); |
| sbuf.append("| "); |
| for (int i = sp; i >= Math.max(0, sp - 5); i = i - 1) |
| { |
| if (i < sp) |
| sbuf.append(" "); |
| if ((stack[i] >> 16) != 0) |
| sbuf.append(getHex((short) (stack[i] >> 16))); |
| sbuf.append(getHex((short) stack[i])); |
| } |
| System.out.println(sbuf); |
| } |
| |
| |
| private static char getNibble(int i, int rightShift) |
| { |
| i = (i >> rightShift) & 15; |
| if (i < 10) |
| return (char) (i + '0'); |
| else |
| return (char) (i + 'a' - 10); |
| } |
| |
| |
| private static String getHex(byte b) |
| { |
| char[] a = new char[2]; |
| a[0] = getNibble(b, 4); |
| a[1] = getNibble(b, 0); |
| return new String(a); |
| } |
| |
| |
| private static String getHex(short b) |
| { |
| char[] a = new char[4]; |
| a[0] = getNibble(b, 12); |
| a[1] = getNibble(b, 8); |
| a[2] = getNibble(b, 4); |
| a[3] = getNibble(b, 0); |
| return new String(a); |
| } |
| |
| |
| /** |
| * Skips any instructions until the specified opcode has been |
| * encoutered. |
| * |
| * @param inst the current instruction stream. After the call, |
| * the position of <code>inst</code> is right after the first |
| * occurence of <code>opcode</code>. |
| * |
| * @param opcode1 the opcode for which to look. |
| * |
| * @param opcode2 another opcode for which to look. Pass -1 |
| * if only <code>opcode1</code> would terminate skipping. |
| * |
| * @param illegalCode1 an opcode that must not be encountered |
| * while skipping. Pass -1 if any opcode is acceptable. |
| * |
| * @param illegalCode2 another opcode that must not be encountered |
| * while skipping. Pass -1 to perform no check. |
| * |
| * @param handleNestedIfClauses <code>true</code> to handle |
| * nested <code>IF [ELSE] EIF</code> clauses, <code>false</code> |
| * to ignore them. From the TrueType specification document, |
| * one would think that nested if clauses would not be valid, |
| * but they do appear in some fonts. |
| * |
| * @throws IllegalStateException if <code>illegalCode1</code> or |
| * <code>illegalCode2</code> has been encountered while skipping. |
| */ |
| private static void skipAfter(ByteBuffer inst, |
| int opcode1, int opcode2, |
| int illegalCode1, int illegalCode2, |
| boolean handleNestedIfClauses) |
| { |
| int pos = inst.position(); |
| int curOpcode; |
| int instLen; |
| int nestingLevel = 0; // increased inside IF [ELSE] EIF sequences |
| |
| while (true) |
| { |
| curOpcode = inst.get(pos) & 0xff; |
| instLen = getInstructionLength(curOpcode); |
| |
| if (false && TRACE_EXECUTION) |
| { |
| for (int i = 0; i < nestingLevel; i++) |
| System.out.print("--"); |
| System.out.print("--" + pos + "-" + INST_NAME[curOpcode]); |
| if (nestingLevel > 0) |
| System.out.print(", ifNestingLevel=" + nestingLevel); |
| System.out.println(); |
| } |
| |
| if (curOpcode == 0x40) // NPUSHB |
| pos += 1 + (inst.get(pos + 1) & 0xff); |
| else if (curOpcode == 0x41) // NPUSHW |
| pos += 1 + 2 * (inst.get(pos + 1) & 0xff); |
| else |
| pos += instLen; |
| |
| if ((nestingLevel == 0) |
| && ((curOpcode == opcode1) || (curOpcode == opcode2))) |
| break; |
| |
| if (handleNestedIfClauses) |
| { |
| if (curOpcode == /* IF */ 0x58) |
| ++nestingLevel; |
| else if (curOpcode == /* EIF */ 0x59) |
| --nestingLevel; |
| } |
| |
| if ((nestingLevel < 0) |
| || (curOpcode == illegalCode1) |
| || (curOpcode == illegalCode2)) |
| throw new IllegalStateException(); |
| } |
| |
| inst.position(pos); |
| } |
| |
| |
| /** |
| * Returns the number of bytes that a TrueType instruction occupies. |
| * |
| * @param opcode the instruction. |
| * |
| * @return the number of bytes occupied by the instructions and its |
| * operands. For <code>NPUSHB</code> and <code>NPUSHW</code>, where |
| * the instruction length depends on the first operand byte, the |
| * result is -1. |
| */ |
| private static int getInstructionLength(int opcode) |
| { |
| /* NPUSHB, NPUSHW --> see following byte */ |
| if ((opcode == 0x40) || (opcode == 0x41)) |
| return -1; |
| |
| /* PUSHB[0] .. PUSHB[7] --> 2, 3, 4, 5, 6, 7, 8, 9 */ |
| if ((opcode >= 0xb0) && (opcode <= 0xb7)) |
| return opcode - 0xae; |
| |
| /* PUSHW[0] .. PUSHW[7] --> 3, 5, 6, 7, 11, 13, 15, 17*/ |
| if ((opcode >= 0xb8) && (opcode <= 0xbf)) |
| return 1 + ((opcode - 0xb7) << 1); |
| |
| return 1; |
| } |
| |
| |
| /** |
| * Executes a single TrueType instruction. This is the core |
| * routine of the Virtual Machine. |
| * |
| * @return <code>true</code> if another instruction shall be |
| * executed in the same call frame; <code>false</code> if the |
| * current call frame shall be popped. |
| */ |
| private boolean executeInstruction(ByteBuffer inst) |
| { |
| if (TRACE_EXECUTION) |
| dumpInstruction(inst); |
| |
| int i, count, e1, e2, e3, e4, x, y; |
| int bcode = inst.get() & 0xff; |
| |
| switch (bcode) |
| { |
| case 0x00: // SVTCA[0], Set freedom and proj. Vectors To Coord. Axis [y] |
| setFreedomVector((short) 0, ONE_214); |
| setProjectionVector((short) 0, ONE_214); |
| break; |
| |
| case 0x01: // SVTCA[1], Set freedom and proj. Vectors To Coord. Axis [x] |
| setFreedomVector(ONE_214, (short) 0); |
| setProjectionVector(ONE_214, (short) 0); |
| break; |
| |
| case 0x02: // SPVTCA[0], Set Projection Vector To Coordinate Axis [y] |
| setProjectionVector((short) 0, ONE_214); |
| break; |
| |
| case 0x03: // SPVTCA[1], Set Projection Vector To Coordinate Axis [x] |
| setProjectionVector(ONE_214, (short) 0); |
| break; |
| |
| case 0x0c: // GPV, Get Projection Vector |
| stack[++sp] = projX; |
| stack[++sp] = projY; |
| break; |
| |
| case 0x0d: // GPV, Get Freedom Vector |
| stack[++sp] = freeX; |
| stack[++sp] = freeY; |
| break; |
| |
| case 0x0F: // ISECT, move point p to the InterSECTION of two lines |
| sp -= 4; |
| handleISECT(stack[sp], stack[sp+1], stack[sp+2], |
| stack[sp+3], stack[sp+4]); |
| break; |
| |
| case 0x10: // SRP0, Set Reference Point 0 |
| rp0 = stack[sp--]; |
| break; |
| |
| case 0x11: // SRP1, Set Reference Point 1 |
| rp1 = stack[sp--]; |
| break; |
| |
| case 0x12: // SRP2, Set Reference Point 2 |
| rp2 = stack[sp--]; |
| break; |
| |
| case 0x13: // SZP0, Set Zone Pointer 0 |
| zp0 = getZone(stack[sp--]); |
| break; |
| |
| case 0x14: // SZP1, Set Zone Pointer 1 |
| zp1 = getZone(stack[sp--]); |
| break; |
| |
| case 0x15: // SZP2, Set Zone Pointer 2 |
| zp2 = getZone(stack[sp--]); |
| break; |
| |
| case 0x16: // SZPS, Set Zone PointerS |
| zp0 = zp1 = zp2 = getZone(stack[sp--]); |
| break; |
| |
| case 0x17: // SLOOP, Set LOOP variable |
| loop = stack[sp--]; |
| break; |
| |
| case 0x18: // RTG, Round To Grid |
| setRoundingMode(Fixed.ONE, 0x48); |
| break; |
| |
| case 0x19: // RTHG, Round To Half Grid |
| setRoundingMode(Fixed.ONE, 0x68); |
| break; |
| |
| case 0x1a: // SMD, Set Minimum Distance |
| minimumDistance = stack[sp--]; |
| break; |
| |
| case 0x1B: // ELSE, ELSE clause |
| skipAfter(inst, |
| /* look for: EIF, -- */ 0x59, -1, |
| /* illegal: --, -- */ -1, -1, |
| /* handle nested if clauses */ true); |
| break; |
| |
| case 0x1C: // JMPR, JuMP Relative |
| inst.position(inst.position() - 1 + stack[sp--]); |
| break; |
| |
| case 0x1D: // SCVTCI, Set Control Value Table Cut-In |
| cvtCutIn = stack[sp--]; |
| break; |
| |
| case 0x1F: // SSW, Set Single Width |
| singleWidthValue = stack[sp--]; |
| break; |
| |
| case 0x20: // DUP, DUPlicate top stack element |
| e1 = stack[sp]; |
| stack[++sp] = e1; |
| break; |
| |
| case 0x21: // POP, POP top stack element |
| sp--; |
| break; |
| |
| case 0x22: // CLEAR, CLEAR the stack |
| sp = -1; |
| break; |
| |
| case 0x23: // SWAP, SWAP the top two elements on the stack |
| e1 = stack[sp--]; |
| e2 = stack[sp]; |
| stack[sp] = e1; |
| stack[++sp] = e2; |
| break; |
| |
| case 0x24: // DEPTH, DEPTH of the stack |
| stack[++sp] = sp + 1; |
| break; |
| |
| case 0x25: // CINDEX, Copy the INDEXed element to the top of the stack |
| stack[sp] = stack[sp - stack[sp]]; |
| break; |
| |
| case 0x26: // MINDEX, Move the INDEXed element to the top of the stack |
| i = stack[sp]; |
| e1 = stack[sp - i]; |
| System.arraycopy(/* src */ stack, /* srcPos */ sp - i + 1, |
| /* dest */ stack, /* destPos*/ sp - i, |
| /* length */ i - 1); |
| --sp; |
| stack[sp] = e1; |
| break; |
| |
| case 0x2a: // LOOPCALL, LOOP and CALL function |
| i = stack[sp--]; |
| count = stack[sp--]; |
| e1 = inst.position(); |
| e2 = sp; |
| for (int j = 0; j < count; j++) |
| execute(fdefBuffer[i], fdefEntryPoint[i]); |
| inst.position(e1); |
| break; |
| |
| case 0x2B: // CALL, CALL function |
| i = stack[sp--]; |
| e1 = inst.position(); |
| e2 = sp; |
| execute(fdefBuffer[i], fdefEntryPoint[i]); |
| inst.position(e1); |
| break; |
| |
| case 0x2C: // FDEF, Function DEFinition |
| i = stack[sp--]; |
| fdefBuffer[i] = inst; |
| fdefEntryPoint[i] = inst.position(); |
| skipAfter(inst, |
| /* look for: ENDF */ 0x2d, |
| /* look for: --- */ -1, |
| /* illegal: IDEF */ 0x89, |
| /* illegal: FDEF */ 0x2c, |
| /* do not handle nested if clauses */ false); |
| break; |
| |
| case 0x2D: // ENDF, END Function definition |
| /* Pop the current stack frame. */ |
| return false; |
| |
| case 0x2e: // MDAP[0], Move Direct Absolute Point |
| handleMDAP(stack[sp--], /* round */ false); |
| break; |
| |
| case 0x2f: // MDAP[1], Move Direct Absolute Point |
| handleMDAP(stack[sp--], /* round */ true); |
| break; |
| |
| case 0x39: // IP, Interpolate Point by the last relative stretch |
| handleIP(); |
| break; |
| |
| case 0x3d: // RTDG, Round To Double Grid |
| setRoundingMode(Fixed.ONE, 0x08); |
| roundThreshold = roundThreshold / 64; // period/128 |
| break; |
| |
| case 0x3e: // MIAP[0], Move Indirect Absolute Point |
| e1 = stack[sp--]; |
| handleMIAP(e1, stack[sp--], /* round */ false); |
| break; |
| |
| case 0x3f: // MIAP[1], Move Indirect Absolute Point |
| e1 = stack[sp--]; |
| handleMIAP(e1, stack[sp--], /* round */ true); |
| break; |
| |
| case 0x40: // NPUSHB |
| count = inst.get() & 0xff; |
| for (i = 0; i < count; i++) |
| stack[++sp] = inst.get() & 0xff; |
| break; |
| |
| case 0x41: // NPUSHW |
| count = inst.get() & 0xff; |
| for (i = 0; i < count; i++) |
| stack[++sp] = inst.getShort(); |
| break; |
| |
| case 0x42: // WS, Write Store |
| e1 = stack[sp--]; i = stack[sp--]; |
| storage[i] = e1; |
| break; |
| |
| case 0x43: // RS, Read Store |
| stack[sp] = storage[stack[sp]]; |
| break; |
| |
| case 0x44: // WCVTP, Write Control Value Table in Pixel units |
| e1 = stack[sp--]; |
| i = stack[sp--]; |
| if (i < cvt.length) |
| cvt[i] = e1; |
| break; |
| |
| case 0x45: // RCVT, Read Control Value Table entry |
| if (stack[sp] < cvt.length) |
| stack[sp] = cvt[stack[sp]]; |
| else |
| stack[sp] = 0; |
| break; |
| |
| case 0x46: // GC[0], Get Coordinate projected onto the projection vector |
| stack[sp] = getProjection(zp2, stack[sp]); |
| break; |
| |
| case 0x47: // GC[1], Get Coordinate projected onto the projection vector |
| stack[sp] = getOriginalProjection(zp2, stack[sp]); |
| break; |
| |
| case 0x4B: // MPPEM, Measure Pixels Per EM |
| stack[++sp] = getPixelsPerEM(); |
| break; |
| |
| case 0x4c: // MPS, Measure Point Size |
| /* FreeType2 returns pixels per em here, because they think that |
| * the point size would be irrelevant in a given font program. |
| * This is extremely surprising, because the appearance of good |
| * fonts _should_ change with point size. For example, a good |
| * font should be wider at small point sizes, and the holes |
| * inside glyphs ("Punzen" in German, I do not know the correct |
| * English expression) should be larger. Note that this change |
| * of appearance is dependent on point size, _not_ the |
| * resolution of the display device. |
| */ |
| stack[++sp] = pointSize; |
| break; |
| |
| case 0x4f: // DEBUG, DEBUG call |
| sp--; |
| break; |
| |
| case 0x50: // LT, Less Than |
| e1 = stack[sp--]; |
| stack[sp] = (stack[sp] < e1) ? 1 : 0; |
| break; |
| |
| case 0x51: // LTEQ, Greater Than or EQual |
| e1 = stack[sp--]; |
| stack[sp] = (stack[sp] <= e1) ? 1 : 0; |
| break; |
| |
| case 0x52: // GT, Greater Than |
| e1 = stack[sp--]; |
| stack[sp] = (stack[sp] > e1) ? 1 : 0; |
| break; |
| |
| case 0x53: // GTEQ, Greater Than or EQual |
| e1 = stack[sp--]; |
| stack[sp] = (stack[sp] >= e1) ? 1 : 0; |
| break; |
| |
| case 0x54: // EQ, EQual |
| e1 = stack[sp--]; |
| stack[sp] = (stack[sp] == e1) ? 1 : 0; |
| break; |
| |
| case 0x55: // NEQ, Not EQual |
| e1 = stack[sp--]; |
| stack[sp] = (stack[sp] != e1) ? 1 : 0; |
| break; |
| |
| case 0x58: // IF, IF test |
| if (stack[sp--] == 0) |
| skipAfter(inst, |
| /* look for: ELSE */ 0x1B, |
| /* look for: EIF */ 0x59, |
| /* illegal: -- */ -1, |
| /* illegal: -- */ -1, |
| /* handle nested if clauses */ true); |
| break; |
| |
| case 0x59: // EIF, End IF |
| // Do nothing. |
| break; |
| |
| case 0x5A: // AND |
| e1 = stack[sp--]; |
| stack[sp] = ((e1 != 0) && (stack[sp] != 0)) ? 1 : 0; |
| break; |
| |
| case 0x5B: // OR |
| e1 = stack[sp--]; |
| stack[sp] = ((e1 != 0) || (stack[sp] != 0)) ? 1 : 0; |
| break; |
| |
| case 0x5C: // NOT |
| stack[sp] = (stack[sp] != 0) ? 0 : 1; |
| break; |
| |
| case 0x5e: // SDB, Set Delta Base in the graphics state |
| deltaBase = stack[sp--]; |
| break; |
| |
| case 0x5f: // SDS, Set Delta Shift in the graphics state |
| deltaShift = stack[sp--]; |
| break; |
| |
| case 0x60: // ADD |
| e1 = stack[sp--]; |
| stack[sp] += e1; |
| break; |
| |
| case 0x61: // SUB, SUBtract |
| e1 = stack[sp--]; |
| stack[sp] -= e1; |
| break; |
| |
| case 0x62: // DIV, DIVide |
| e1 = stack[sp--]; |
| stack[sp] = Fixed.div(e1, stack[sp]); |
| break; |
| |
| case 0x63: // MUL, MULtiply |
| e1 = stack[sp--]; |
| stack[sp] = Fixed.mul(e1, stack[sp]); |
| break; |
| |
| case 0x64: // ABS, ABSolute value |
| stack[sp] = Math.abs(stack[sp]); |
| break; |
| |
| case 0x65: // NEG, NEGate |
| stack[sp] = -stack[sp]; |
| break; |
| |
| case 0x66: // FLOOR |
| stack[sp] = Fixed.floor(stack[sp]); |
| break; |
| |
| case 0x67: // CEILING |
| stack[sp] = Fixed.ceil(stack[sp]); |
| break; |
| |
| case 0x68: // ROUND[0] -- round grey distance |
| stack[sp] = round(stack[sp], /* no engine compensation */ 0); |
| break; |
| |
| case 0x69: // ROUND[1] -- round black distance |
| stack[sp] = round(stack[sp], -engineCompensation); |
| break; |
| |
| case 0x6a: // ROUND[2] -- round white distance |
| stack[sp] = round(stack[sp], engineCompensation); |
| break; |
| |
| case 0x6b: // ROUND[3] -- round distance (not yet defined) |
| stack[sp] = round(stack[sp], /* no engine compensation */ 0); |
| break; |
| |
| case 0x6c: // NROUND[0] -- compensate grey distance |
| stack[sp] = nround(stack[sp], 0); |
| break; |
| |
| case 0x6d: // NROUND[1] -- compensate black distance |
| stack[sp] = nround(stack[sp], -engineCompensation); |
| break; |
| |
| case 0x6e: // NROUND[2] -- compensate white distance |
| stack[sp] = nround(stack[sp], engineCompensation); |
| break; |
| |
| case 0x6f: // NROUND[3] -- compensate distance (not yet defined) |
| stack[sp] = nround(stack[sp], 0); |
| break; |
| |
| case 0x70: // WCVTF, Write Control Value Table in Funits |
| e1 = stack[sp--]; |
| cvt[stack[sp--]] = e1 * getPixelsPerEM(); |
| break; |
| |
| case 0x73: // DELTAC1, DELTA exception C1 |
| count = stack[sp--]; |
| sp -= 2 * count; |
| deltaC(stack, sp + 1, count, 0); |
| break; |
| |
| case 0x74: // DELTAC2, DELTA exception C2 |
| count = stack[sp--]; |
| sp -= 2 * count; |
| deltaC(stack, sp + 1, count, 16); |
| break; |
| |
| case 0x75: // DELTAC3, DELTA exception C3 |
| count = stack[sp--]; |
| sp -= 2 * count; |
| deltaC(stack, sp + 1, count, 32); |
| break; |
| |
| case 0x76: // SROUND, Super ROUND |
| setRoundingMode(Fixed.ONE, stack[sp--]); |
| break; |
| |
| case 0x77: // S45ROUND, Super ROUND 45 degrees |
| setRoundingMode(/* sqrt(2)/2 */ 0x2d, stack[sp--]); |
| break; |
| |
| case 0x78: // JROT, Jump Relative On True |
| e1 = stack[sp--]; |
| i = inst.position() - 1 + stack[sp--]; |
| if (e1 != 0) |
| inst.position(i); |
| break; |
| |
| case 0x79: // JROF, Jump Relative On False |
| e1 = stack[sp--]; |
| i = inst.position() - 1 + stack[sp--]; |
| if (e1 == 0) |
| inst.position(i); |
| break; |
| |
| case 0x7a: // ROFF, Round OFF |
| roundPeriod = 0; |
| break; |
| |
| case 0x7c: // RUTG, Round Up To Grid |
| setRoundingMode(Fixed.ONE, 0x40); |
| break; |
| |
| case 0x7d: // RDTG, Round Down To Grid |
| setRoundingMode(Fixed.ONE, 0x40); |
| roundThreshold = 0; |
| break; |
| |
| case 0x7e: // SANGW, Set ANGle Weight (no-op according to TrueType spec) |
| case 0x7f: // AA, Adjust Angle (no-op according to TrueType spec) |
| sp--; |
| break; |
| |
| case 0x85: // SCANCTRL, SCAN conversion ConTRoL |
| e1 = stack[sp--]; |
| int ppemThreshold = e1 & 255; |
| scanControl = false; |
| boolean ppemCondition = (ppemThreshold == 255) |
| || ((ppemThreshold != 0) && (getPixelsPerEM() > ppemThreshold)); |
| if (((e1 & (1<<8)) != 0) && ppemCondition) |
| scanControl = true; |
| if (((e1 & (1<<9)) != 0) && isRotated()) |
| scanControl = true; |
| if (((e1 & (1<<10)) != 0) && isStretched()) |
| scanControl = true; |
| if (((e1 & (1<<11)) != 0) && !ppemCondition) |
| scanControl = false; |
| if (((e1 & (1<<12)) != 0) && !isRotated()) |
| scanControl = false; |
| if (((e1 & (1<<13)) != 0) && !isStretched()) |
| scanControl = false; |
| break; |
| |
| case 0x88: // GETINFO, GET INFOrmation |
| e1 = 0; |
| if ((stack[sp] & 1) != 0) // ask for rasterizer version |
| e1 |= 35; // "Microsoft Rasterizer version 1.7" (grayscale-capable) |
| if (((stack[sp] & 2) != 0) && isRotated()) |
| e1 |= 1 << 8; // bit 8: glyph has been rotated |
| if (((stack[sp] & 4) != 0) && isStretched()) |
| e1 |= 1 << 9; // bit 9: glyph has been stretched |
| if (((stack[sp] & 32) != 0) && antialiased) |
| e1 |= 1 << 12; // bit 12: antialiasing is active |
| stack[sp] = e1; |
| break; |
| |
| case 0x8a: // ROLL, ROLL the top three stack elements |
| e1 = stack[sp - 2]; |
| stack[sp - 2] = stack[sp - 1]; |
| stack[sp - 1] = stack[sp]; |
| stack[sp] = e1; |
| break; |
| |
| case 0x8b: // MAX, MAXimum of top two stack elements |
| e1 = stack[sp--]; |
| stack[sp] = Math.max(e1, stack[sp]); |
| break; |
| |
| case 0x8c: // MIN, MINimum of top two stack elements |
| e1 = stack[sp--]; |
| stack[sp] = Math.min(e1, stack[sp]); |
| break; |
| |
| case 0x8d: // SCANTYPE |
| scanType = stack[sp--]; |
| break; |
| |
| case 0x8e: // INSTCTRL, INSTRuction execution ConTRoL |
| e1 = stack[sp--]; // selector |
| e2 = stack[sp--]; // value |
| switch (e1) |
| { |
| case 1: |
| executeGlyphInstructions = (e2 == 0); |
| break; |
| |
| case 2: |
| ignoreCVTProgram = (e2 != 0); |
| break; |
| } |
| break; |
| |
| case 0xb0: // PUSHB[0] |
| case 0xb1: // PUSHB[1] |
| case 0xb2: // PUSHB[2] |
| case 0xb3: // PUSHB[3] |
| case 0xb4: // PUSHB[4] |
| case 0xb5: // PUSHB[5] |
| case 0xb6: // PUSHB[6] |
| case 0xb7: // PUSHB[7] |
| count = bcode - 0xb0 + 1; |
| for (i = 0; i < count; i++) |
| stack[++sp] = inst.get() & 0xff; |
| break; |
| |
| case 0xb8: // PUSHW[0] |
| case 0xb9: // PUSHW[1] |
| case 0xba: // PUSHW[2] |
| case 0xbb: // PUSHW[3] |
| case 0xbc: // PUSHW[4] |
| case 0xbd: // PUSHW[5] |
| case 0xbe: // PUSHW[6] |
| case 0xbf: // PUSHW[7] |
| count = bcode - 0xb8 + 1; |
| for (i = 0; i < count; i++) |
| stack[++sp] = inst.getShort(); |
| break; |
| |
| // MIRPxxxx, Move Indirect Relative Point |
| case 0xe0: case 0xe1: case 0xe2: case 0xe3: |
| case 0xe4: case 0xe5: case 0xe6: case 0xe7: |
| case 0xe8: case 0xe9: case 0xea: case 0xeb: |
| case 0xec: case 0xed: case 0xee: case 0xef: |
| case 0xf0: case 0xf1: case 0xf2: case 0xf3: |
| case 0xf4: case 0xf5: case 0xf6: case 0xf7: |
| case 0xf8: case 0xf9: case 0xfa: case 0xfb: |
| case 0xfc: case 0xfd: case 0xfe: case 0xff: |
| e1 = stack[sp--]; |
| handleMIRP(bcode, /* point */ e1, /* cvtIndex */ stack[sp--]); |
| break; |
| |
| default: |
| throw new IllegalStateException(); |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * Sets the rounding mode. |
| * |
| * @param period the grid period in fixed-point notation, such as |
| * {@link Fixed#ONE} for the <code>SROUND</code> instruction or |
| * <code>sqrt(2)/2</code> for the <code>S45ROUND</code> instruction. |
| * |
| * @param mode a byte whose bits are set according to the TrueType |
| * specification for SROUND and S45ROUND parameters. |
| */ |
| private void setRoundingMode(int period, int mode) |
| { |
| /* Set the period. */ |
| switch ((mode & 0xc0) >> 6) |
| { |
| case 0: |
| roundPeriod = period / 2; |
| break; |
| |
| case 2: |
| roundPeriod = period * 2; |
| break; |
| |
| default: |
| roundPeriod = period; |
| break; |
| } |
| |
| /* Set the phase. */ |
| switch ((mode & 0x30) >> 4) |
| { |
| case 0: |
| roundPhase = 0; |
| break; |
| |
| case 1: |
| roundPhase = roundPeriod >> 2; // period/4 |
| break; |
| |
| case 2: |
| roundPhase = roundPeriod >> 1; // period/2 |
| break; |
| |
| case 3: |
| roundPhase = (roundPeriod >> 1) + (roundPeriod >> 2); // period * 3/4 |
| break; |
| } |
| |
| /* Set the threshold. */ |
| int threshold = mode & 0x0f; |
| if (threshold == 0) |
| roundThreshold = roundPeriod - Fixed.ONE; |
| else |
| roundThreshold = ((threshold - 4) * roundPeriod) / 8; |
| } |
| |
| |
| |
| /** |
| * Implements the DELTAC instructions. These instructions check |
| * whether the current number of pixels per em is contained in an |
| * exception table. If it is, a delta value is determined, and the |
| * specified entry in the Control Value Table is modified according |
| * to the delta. |
| * |
| * @param pairs the delta table. Because the delta table is on |
| * the stack, callers usually just want to pass the stack array. |
| * |
| * @param offset the offset of the first pair in <code>pairs</code>. |
| * |
| * @param numPairs the number of pairs. |
| * |
| * @param base 0 for <code>DELTAC1</code>, 16 for <code>DELTAC2</code>, |
| * or 32 for <code>DELTAC2</code>. |
| * |
| * @see <a href= |
| * "http://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html#DELTAC1" |
| * >Apple’s documentation for <code>DELTAC1</code></a>, <a href= |
| * "http://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html#DELTAC2" |
| * ><code>DELTAC2</code></a>, and <a href= |
| * "http://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html#DELTAC3" |
| * ><code>DELTAC3</code></a> |
| */ |
| private void deltaC(int[] pairs, int offset, int numPairs, int base) |
| { |
| int arg, relativePpem; |
| int ppemTrigger = getPixelsPerEM() - (deltaBase + base); |
| int delta, cvtIndex, rightShift; |
| for (int i = 0; i < numPairs; i++) |
| { |
| arg = pairs[offset + 2 * i]; |
| relativePpem = (arg >> 4) & 15; |
| if (relativePpem == ppemTrigger) |
| { |
| delta = (arg & 15) - 8; |
| if (delta >= 0) |
| ++delta; |
| |
| rightShift = deltaShift - 6; |
| if (rightShift > 0) |
| delta = delta >> rightShift; |
| else if (rightShift < 0) |
| delta = delta << (-rightShift); |
| cvt[pairs[offset + 2 * i + 1]] += delta; |
| |
| break; |
| } |
| } |
| } |
| |
| |
| private Zone getZone(int zoneNumber) |
| { |
| return (zoneNumber == 0) ? twilightZone : glyphZone; |
| } |
| |
| |
| /** |
| * Projects the specified vector along the current projection |
| * vector. |
| * |
| * @param x the x component of the input vector, in 26.6 fixed-point |
| * notation. |
| * |
| * @param y the y component of the input vector, in 26.6 fixed-point |
| * notation. |
| * |
| * @return the projected distance, in 26.6 fixed-point notation. |
| */ |
| private int getProjection(int x, int y) |
| { |
| return (int) (((((long) x) * projX + ((long) y) * projY)) >> 14); |
| } |
| |
| |
| /** |
| * Projects the specified vector along the current dual projection |
| * vector. |
| * |
| * @param x the x component of the input vector, in 26.6 fixed-point |
| * notation. |
| * |
| * @param y the y component of the input vector, in 26.6 fixed-point |
| * notation. |
| * |
| * @return the projected distance, in 26.6 fixed-point notation. |
| */ |
| private int getDualProjection(int x, int y) |
| { |
| return (int) (((((long) x) * dualX + ((long) y) * dualY)) >> 14); |
| } |
| |
| |
| private int getProjection(Zone zone, int point) |
| { |
| return getProjection(zone.getX(point), zone.getY(point)); |
| } |
| |
| |
| private int getOriginalProjection(Zone zone, int point) |
| { |
| return getDualProjection(zone.getOriginalX(point), |
| zone.getOriginalY(point)); |
| } |
| |
| |
| private void handleISECT(int a0, int a1, int b0, int b1, int p) |
| { |
| System.out.println("FIXME: Unimplemented ISECT " + p); |
| } |
| |
| |
| private static int muldiv(int a, int b, int c) |
| { |
| int s; |
| s = a; a = Math.abs(a); |
| s ^= b; b = Math.abs(b); |
| s ^= c; c = Math.abs(c); |
| a = (int) ((((long) a) * b + (c>>1)) / c); |
| return (s < 0) ? -a : a; |
| } |
| |
| |
| private int getFreeDotProj() |
| { |
| int result; |
| |
| result = ((((int) projX) * freeX) << 2) |
| + ((((int) projY) * freeY) << 2); |
| |
| /* FIXME: This seems somewhat bogus. Need to contact the |
| * developers of FreeType. |
| */ |
| if (Math.abs(result) < 0x4000000) |
| result = 0x40000000; |
| return result; |
| } |
| |
| |
| private void movePoint(Zone zone, int point, int distance) |
| { |
| int freeDotProj = getFreeDotProj(); |
| int c; |
| |
| if (freeX != 0) |
| { |
| c = zone.getX(point); |
| c += muldiv(distance, freeX << 16, freeDotProj); |
| zone.setX(point, c, /* touch */ true); |
| } |
| |
| if (freeY != 0) |
| { |
| c = zone.getY(point); |
| c += muldiv(distance, freeY << 16, freeDotProj); |
| zone.setY(point, c, /* touch */ true); |
| } |
| |
| if (TRACE_EXECUTION) |
| { |
| System.out.println("point[" + point + "] moved to " |
| + Fixed.toString(zone.getX(point), |
| zone.getY(point))); |
| dumpVectors(); |
| } |
| } |
| |
| private void dumpVectors() |
| { |
| System.out.println(" proj=" + Fixed.toString(projX>>8, projY>>8) |
| + ", free=" + Fixed.toString(freeX>>8, freeY>>8)); |
| } |
| |
| |
| private void handleIP() |
| { |
| // Implementation taken from FreeType. |
| int p, org_a, org_b, org_x, cur_a, cur_b, cur_x, distance; |
| int freeDotProj; |
| |
| org_a = getOriginalProjection(zp0, rp1); |
| cur_a = getProjection(zp0, rp1); |
| |
| org_b = getOriginalProjection(zp1, rp2); |
| cur_b = getProjection(zp1, rp2); |
| |
| while (--loop >= 0) |
| { |
| p = stack[sp--]; |
| org_x = getOriginalProjection(zp2, p); |
| cur_x = getProjection(zp2, p); |
| |
| if (((org_a <= org_b) && (org_x <= org_a)) |
| || ((org_a > org_b) && (org_x >= org_a))) |
| distance = (cur_a - org_a) + (org_x - cur_x); |
| else if (((org_a <= org_b) && (org_x >= org_b)) |
| || ((org_a > org_b) && (org_x < org_b))) |
| distance = (cur_b - org_b) + (org_x - cur_x); |
| else |
| distance = muldiv(cur_b - cur_a, org_x - org_a, org_b - org_a) |
| + (cur_a - cur_x); |
| movePoint(zp2, p, distance); |
| } |
| loop = 1; |
| } |
| |
| |
| private void handleMDAP(int point, boolean round) |
| { |
| System.out.println("FIXME: Unimplemented MDAP: point " |
| + point + "/" + zp0); |
| } |
| |
| |
| private void handleMIAP(int cvtIndex, int point, boolean round) |
| { |
| int previousPos, pos; |
| |
| previousPos = getProjection(zp0, point); |
| pos = cvt[cvtIndex]; |
| |
| if (round) |
| { |
| if (Math.abs(pos - previousPos) > cvtCutIn) |
| pos = previousPos; |
| pos = round(pos, /* no engine compensation */ 0); |
| } |
| movePoint(zp0, point, pos - previousPos); |
| rp0 = rp1 = point; |
| } |
| |
| |
| private void handleMIRP(int bcode, int point, int cvtIndex) |
| { |
| System.out.println("FIXME: Unimplemented mirp " + point + ", " + cvtIndex); |
| } |
| |
| |
| |
| private int round(int distance, int compensation) |
| { |
| int result; |
| |
| if (roundPeriod == 0) |
| return nround(distance, compensation); |
| |
| if (distance >= 0) |
| { |
| result = distance + compensation - roundPhase + roundThreshold; |
| result &= -roundPeriod; // truncate to the next lowest periodic value |
| return Math.max(result, 0) + roundPhase; |
| } |
| else |
| { |
| result = compensation - roundPhase + roundThreshold - distance; |
| result &= -roundPeriod; |
| return Math.max(-result, 0) - roundPhase; |
| } |
| } |
| |
| |
| private static int nround(int distance, int compensation) |
| { |
| if (distance >= 0) |
| return Math.max(distance + compensation, 0); |
| else |
| return Math.min(distance - compensation, 0); |
| } |
| |
| |
| /** |
| * Determines whether the current glyph is rotated. |
| * |
| * @return <code>false</code> if the shearing factors for the |
| * <i>x</i> and <i>y</i> axes are zero; <code>true</code> if they |
| * are non-zero. |
| */ |
| private boolean isRotated() |
| { |
| return (shearX != 0) || (shearY != 0); |
| } |
| |
| |
| /** |
| * Determines whether the current glyph is stretched. |
| * |
| * @return <code>false</code> if the scaling factors for the |
| * <i>x</i> and <i>y</i> axes are are equal; <code>true</code> if |
| * they differ. |
| */ |
| private boolean isStretched() |
| { |
| return scaleX != scaleY; |
| } |
| |
| |
| /** |
| * Returns how many pixels there are per EM, in direction of the |
| * current projection vector. The result is a normal integer, |
| * not a Fixed. |
| */ |
| private int getPixelsPerEM() |
| { |
| if (cachedPixelsPerEM == 0) |
| { |
| cachedPixelsPerEM = Fixed.intValue(Fixed.vectorLength( |
| applyCTM_x(projX >> 8, projY >> 8), |
| applyCTM_y(projX >> 8, projY >> 8))); |
| } |
| |
| return cachedPixelsPerEM; |
| } |
| |
| |
| private void setProjectionVector(short x, short y) |
| { |
| if (PATENTED_HINTING) |
| { |
| if ((x != projX) || (y != projY)) |
| cachedPixelsPerEM = 0; |
| |
| projX = x; |
| projY = y; |
| } |
| } |
| |
| |
| private void setFreedomVector(short x, short y) |
| { |
| if (PATENTED_HINTING) |
| { |
| freeX = x; |
| freeY = y; |
| } |
| } |
| |
| |
| private void setDualVector(short x, short y) |
| { |
| if (PATENTED_HINTING) |
| { |
| dualX = x; |
| dualY = y; |
| } |
| } |
| |
| |
| private int applyCTM_x(int x, int y) |
| { |
| return (int) (((long) scaleX * x + (long) shearX * y) >> 6); |
| } |
| |
| private int applyCTM_y(int x, int y) |
| { |
| return (int) (((long) shearY * x + (long) scaleY * y) >> 6); |
| } |
| |
| |
| private static final String[] INST_NAME = |
| { |
| /* 00 */ "SVTCA[0]", "SVTCA[1]", "SPVTCA[0]", "SPVTCA[1]", |
| /* 04 */ "INST_04", "INST_05", "INST_06", "INST_07", |
| /* 08 */ "INST_08", "INST_09", "INST_0A", "INST_0B", |
| /* 0c */ "GPV", "GFV", "INST_0E", "ISECT", |
| /* 10 */ "SRP0", "SRP1", "SRP2", "SZP0", |
| /* 14 */ "SZP1", "SZP2", "SZPS", "SLOOP", |
| /* 18 */ "RTG", "RTHG", "SMD", "ELSE", |
| /* 1c */ "JMPR", "SCVTCI", "INST_1E", "SSW", |
| /* 20 */ "DUP", "POP", "CLEAR", "SWAP", |
| /* 24 */ "DEPTH", "CINDEX", "MINDEX", "INST_27", |
| /* 28 */ "INST_28", "INST_29", "LOOPCALL", "CALL", |
| /* 2c */ "FDEF", "ENDF", "MDAP[0]", "MDAP[1]", |
| /* 30 */ "IUP[0]", "IUP[1]", "SHP[0]", "SHP[1]", |
| /* 34 */ "INST_34", "INST_35", "INST_36", "INST_37", |
| /* 38 */ "INST_38", "IP", "INST_3A", "INST_3B", |
| /* 3c */ "INST_3C", "RTDG", "MIAP[0]", "MIAP[1]", |
| /* 40 */ "NPUSHB", "NPUSHW", "WS", "RS", |
| /* 44 */ "WCVTP", "RCVT", "GC[0]", "GC[1]", |
| /* 48 */ "INST_48", "INST_49", "INST_4A", "MPPEM", |
| /* 4c */ "MPS", "FLIPON", "FLIPOFF", "DEBUG", |
| /* 50 */ "LT", "LTEQ", "GT", "GTEQ", |
| /* 54 */ "EQ", "NEQ", "INST_56", "INST_57", |
| /* 58 */ "IF", "EIF", "AND", "OR", |
| /* 5c */ "NOT", "INST_5D", "SDB", "SDS", |
| /* 60 */ "ADD", "SUB", "DIV", "MUL", |
| /* 64 */ "ABS", "NEG", "FLOOR", "CEILING", |
| /* 68 */ "ROUND[0]", "ROUND[1]", "ROUND[2]", "ROUND[3]", |
| /* 6c */ "NROUND[0]", "NROUND[1]", "NROUND[2]", "NROUND[3]", |
| /* 70 */ "WCVTF", "INST_71", "INST_72", "DELTAC1", |
| /* 74 */ "DELTAC2", "DELTAC3", "SROUND", "S45ROUND", |
| /* 78 */ "JROT", "JROF", "ROFF", "INST_7B", |
| /* 7c */ "RUTG", "RDTG", "SANGW", "AA", |
| /* 80 */ "FLIPPT", "FLIPRGON", "FLIPRGOFF", "INST_83", |
| /* 84 */ "INST_84", "SCANCTRL", "INST_86", "INST_87", |
| /* 88 */ "GETINFO", "INST_89", "ROLL", "MAX", |
| /* 8c */ "MIN", "SCANTYPE", "INSTCTRL", "INST_8F", |
| /* 90 */ "INST_90", "INST_91", "INST_92", "INST_93", |
| /* 94 */ "INST_94", "INST_95", "INST_96", "INST_97", |
| /* 98 */ "INST_98", "INST_99", "INST_9A", "INST_9B", |
| /* 9c */ "INST_9C", "INST_9D", "INST_9E", "INST_9F", |
| /* a0 */ "INST_A0", "INST_A1", "INST_A2", "INST_A3", |
| /* a4 */ "INST_A4", "INST_A5", "INST_A6", "INST_A7", |
| /* a8 */ "INST_A8", "INST_A9", "INST_AA", "INST_AB", |
| /* ac */ "INST_AC", "INST_AD", "INST_AE", "INST_AF", |
| /* b0 */ "PUSHB[0]", "PUSHB[1]", "PUSHB[2]", "PUSHB[3]", |
| /* b4 */ "PUSHB[4]", "PUSHB[5]", "PUSHB[6]", "PUSHB[7]", |
| /* b8 */ "PUSHW[0]", "PUSHW[1]", "PUSHW[2]", "PUSHW[3]", |
| /* bc */ "PUSHW[4]", "PUSHW[5]", "PUSHW[6]", "PUSHW[7]", |
| /* c0 */ "INST_C0", "INST_C1", "INST_C2", "INST_C3", |
| /* c4 */ "INST_C4", "INST_C5", "INST_C6", "INST_C7", |
| /* c8 */ "INST_C8", "INST_C9", "INST_CA", "INST_CB", |
| /* cc */ "INST_CC", "INST_CD", "INST_CE", "INST_CF", |
| /* d0 */ "INST_D0", "INST_D1", "INST_D2", "INST_D3", |
| /* d4 */ "INST_D4", "INST_D5", "INST_D6", "INST_D7", |
| /* d8 */ "INST_D8", "INST_D9", "INST_DA", "INST_DB", |
| /* dc */ "INST_DC", "INST_DD", "INST_DE", "INST_DF", |
| /* e0 */ "MIRP00000", "MIRP00001", "MIRP00010", "MIRP00011", |
| /* e4 */ "MIRP00100", "MIRP00101", "MIRP00110", "MIRP00111", |
| /* e8 */ "MIRP01000", "MIRP01001", "MIRP01010", "MIRP01011", |
| /* ec */ "MIRP01100", "MIRP01101", "MIRP01110", "MIRP01111", |
| /* f0 */ "MIRP10000", "MIRP10001", "MIRP10010", "MIRP10011", |
| /* f4 */ "MIRP10100", "MIRP10101", "MIRP10110", "MIRP10111", |
| /* f8 */ "MIRP11000", "MIRP11001", "MIRP11010", "MIRP11011", |
| /* fc */ "MIRP11100", "MIRP11101", "MIRP11110", "MIRP11111" |
| }; |
| } |