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

org.jpedal.fonts.tt.conversion.CFFWriter Maven / Gradle / Ivy

The newest version!
/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.idrsolutions.com
 * Help section for developers at http://www.idrsolutions.com/java-pdf-library-support/
 *
 * (C) Copyright 1997-2013, IDRsolutions and Contributors.
 *
 * 	This file is part of JPedal
 *
     This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

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


 *
 * ---------------
 * CFFWriter.java
 * ---------------
 */
package org.jpedal.fonts.tt.conversion;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.jpedal.fonts.StandardFonts;
import org.jpedal.fonts.Type1;
import org.jpedal.fonts.Type1C;
import org.jpedal.fonts.glyph.PdfJavaGlyphs;
import org.jpedal.utils.LogWriter;

public class CFFWriter extends Type1 implements FontTableWriter {

	private static final long serialVersionUID = -4172447980016197351L;

	final private static boolean debugTopDictOffsets = false;

	private String name;
	private byte[][] subrs;
	final private String[] glyphNames;
	private byte[][] charstrings;
	private int[] charstringXDisplacement, charstringYDisplacement;
	private byte[] header, nameIndex, topDictIndex, globalSubrIndex, encodings, charsets, charStringsIndex, privateDict, localSubrIndex, stringIndex;
	final private ArrayList strings = new ArrayList();
	private int[] widthX, widthY, lsbX, lsbY;
	private int defaultWidthX, nominalWidthX;
	private ArrayList currentCharString;
	private int currentCharStringID;
	private float[] bbox = new float[4];

	// Values for processing flex sections
	private boolean inFlex = false;
	private CharstringElement currentFlexCommand;
	private boolean firstArgsAdded = false;

	// Values for dealing with incorrect em square
	private double emSquareSize = 1000;
	private double scale = 1;
	private boolean inSeac = false;

	public CFFWriter(PdfJavaGlyphs glyphs, String name) {

		this.glyphs = glyphs;
		this.name = name;

		// Fetch charstrings and subrs
		Map charStringSegments = glyphs.getCharStrings();

		// Count subrs and chars
		Object[] keys = charStringSegments.keySet().toArray();
		Arrays.sort(keys);
		int maxSubrNum = 0, maxSubrLen = 0, charCount = 0;
		for (int i = 0; i < charStringSegments.size(); i++) {
			String key = (String) keys[i];
			if (key.startsWith("subrs")) {
				int num = Integer.parseInt(key.replaceAll("[^0-9]", ""));
				if (num > maxSubrNum) {
					maxSubrNum = num;
				}
				int len = ((byte[]) charStringSegments.get(key)).length;
				if (len > maxSubrLen) {
					maxSubrLen = len;
				}
			}
			else {
				charCount++;
			}
		}

		// Move to array
		this.subrs = new byte[maxSubrNum + 1][];
		this.glyphNames = new String[charCount];
		this.charstrings = new byte[charCount][];
		this.charstringXDisplacement = new int[charCount];
		this.charstringYDisplacement = new int[charCount];
		charCount = 0;
		for (int i = 0; i < charStringSegments.size(); i++) {
			String key = (String) keys[i];
			Object obj = charStringSegments.get(key);
			byte[] cs = ((byte[]) obj);
			if (key.startsWith("subrs")) {
				int num = Integer.parseInt(key.replaceAll("[^0-9]", ""));
				this.subrs[num] = cs;
			}
			else {
				this.glyphNames[charCount] = key;
				this.charstrings[charCount] = cs;
				charCount++;
			}
		}

		convertCharstrings();
	}

	/**
	 * Convert the charstrings from type 1 to type 2.
	 */
	private void convertCharstrings() {

		/**
		 * Convert instructions
		 */
		try {

			this.widthX = new int[this.charstrings.length];
			this.widthY = new int[this.charstrings.length];
			this.lsbX = new int[this.charstrings.length];
			this.lsbY = new int[this.charstrings.length];

			// Perform initial conversion
			byte[][] newCharstrings = new byte[this.charstrings.length][];
			for (int charstringID = 0; charstringID < this.charstrings.length; charstringID++) {
				newCharstrings[charstringID] = convertCharstring(this.charstrings[charstringID], charstringID);
			}

			// Check em square size and reconvert while scaling if necessary
			if (this.bbox[2] - this.bbox[0] > 1100) {

				// Calculate em size and scaling to apply
				this.emSquareSize = (this.bbox[2] - this.bbox[0]);
				this.scale = 1d / (this.emSquareSize / 1000d);

				// Reset displacements & bbox
				this.charstringXDisplacement = new int[this.charstringXDisplacement.length];
				this.charstringYDisplacement = new int[this.charstringYDisplacement.length];
				this.bbox = new float[4];

				// Re-convert charstrings now scale is set
				for (int charstringID = 0; charstringID < this.charstrings.length; charstringID++) {
					newCharstrings[charstringID] = convertCharstring(this.charstrings[charstringID], charstringID);
				}
				this.charstrings = newCharstrings;

			}
			else {
				this.charstrings = newCharstrings;
			}

		}
		catch (Exception e) {
			// tell user and log
			if (LogWriter.isOutput()) LogWriter.writeLog("Exception: " + e.getMessage());
		}

		/**
		 * Calculate values for defaultWidthX and nominalWidthX & add widths to start of charstrings
		 */

		// Find counts for each value
		HashMap valueCount = new HashMap();
		for (int i = 0; i < this.charstrings.length; i++) {
			Integer count = valueCount.get(Integer.valueOf(this.widthX[i]));
			if (count == null) {
				count = 1;
			}
			else {
				count = count.intValue() + 1;
			}
			valueCount.put(this.widthX[i], count);
		}

		// Find most common value to use as defaultWidthX
		Object[] values = valueCount.keySet().toArray();
		int maxCount = 0;
		this.defaultWidthX = 0;
		for (Object value : values) {
			int count = valueCount.get(value);
			if (count > maxCount) {
				maxCount = count;
				this.defaultWidthX = (Integer) value;
			}
		}

		// Find average for nominalWidthX
		int total = 0;
		int count = 0;
		for (Object value : values) {
			if ((Integer) value != this.defaultWidthX) {
				count++;
				total += (Integer) value;
			}
		}
		if (count != 0) {
			this.nominalWidthX = total / count;
		}
		else {
			this.nominalWidthX = 0;
		}

		// Blank default widths and update other widths
		for (int i = 0; i < this.widthX.length; i++) {
			if (this.widthX[i] == this.defaultWidthX) {
				this.widthX[i] = Integer.MIN_VALUE;
			}
			else {
				this.widthX[i] = this.widthX[i] - this.nominalWidthX;
			}
		}

		// Append widths to start of charstrings (but not if it's 0 as this signifies default)
		for (int i = 0; i < this.widthX.length; i++) {
			if (this.widthX[i] != Integer.MIN_VALUE) {
				byte[] width = FontWriter.setCharstringType2Number(this.widthX[i]);
				byte[] newCharstring = new byte[width.length + this.charstrings[i].length];
				System.arraycopy(width, 0, newCharstring, 0, width.length);
				System.arraycopy(this.charstrings[i], 0, newCharstring, width.length, this.charstrings[i].length);
				this.charstrings[i] = newCharstring;
			}
		}

		// Check all charstrings end in endchar and append if not
		for (int i = 0; i < this.charstrings.length; i++) {
			if (this.charstrings[i][this.charstrings[i].length - 1] != 14) {
				byte[] newCharstring = new byte[this.charstrings[i].length + 1];
				System.arraycopy(this.charstrings[i], 0, newCharstring, 0, this.charstrings[i].length);
				newCharstring[newCharstring.length - 1] = 14;
				this.charstrings[i] = newCharstring;
			}
		}
	}

	/**
	 * Convert a charstring from type 1 to type 2.
	 * 
	 * @param charstring
	 *            The charstring to convert
	 * @param charstringID
	 *            The number of the charstring to convert
	 * @return The converted charstring
	 */
	private byte[] convertCharstring(byte[] charstring, int charstringID) {

		int[] cs = new int[charstring.length];
		for (int i = 0; i < charstring.length; i++) {
			cs[i] = charstring[i];
			if (cs[i] < 0) {
				cs[i] += 256;
			}
		}

		ByteArrayOutputStream bos = new ByteArrayOutputStream();

		this.currentCharString = new ArrayList();
		this.currentCharStringID = charstringID;

		// Convert to CharstringElements
		CharstringElement element;
		for (int i = 0; i < cs.length; i += element.getLength()) {
			element = new CharstringElement(cs, i);
		}

		// Rescale commands if necessary
		if (this.emSquareSize != 1000 && !this.inSeac) {
			for (CharstringElement e : this.currentCharString) {
				e.scale();
			}
			this.widthX[charstringID] = (int) (this.scale * this.widthX[charstringID]);
			this.widthY[charstringID] = (int) (this.scale * this.widthY[charstringID]);
			this.lsbX[charstringID] = (int) (this.scale * this.lsbX[charstringID]);
			this.lsbY[charstringID] = (int) (this.scale * this.lsbY[charstringID]);
		}

		// Calculate and store displacement and bbox
		for (CharstringElement e : this.currentCharString) {
			int[] d = e.getDisplacement();
			this.charstringXDisplacement[charstringID] += d[0];
			this.charstringYDisplacement[charstringID] += d[1];
			this.bbox[0] = this.charstringXDisplacement[charstringID] < this.bbox[0] ? this.charstringXDisplacement[charstringID] : this.bbox[0];
			this.bbox[1] = this.charstringYDisplacement[charstringID] < this.bbox[1] ? this.charstringYDisplacement[charstringID] : this.bbox[1];
			this.bbox[2] = this.charstringXDisplacement[charstringID] > this.bbox[2] ? this.charstringXDisplacement[charstringID] : this.bbox[2];
			this.bbox[3] = this.charstringYDisplacement[charstringID] > this.bbox[3] ? this.charstringYDisplacement[charstringID] : this.bbox[3];
		}

		// Print for debug
		// System.out.println("Charstring "+charstringID);
		// for (CharstringElement currentElement : currentCharString) {
		// byte[] e = currentElement.getType2Bytes();
		// for (byte b : e) {
		// String bin = Integer.toBinaryString(b);
		// int addZeros = 8 - bin.length();
		// for (int k=0; k 256) {
			result++;
			i = i / 256;
		}

		return result;
	}

	@Override
	public int getIntValue(int i) {
		return -1;
	}

	/**
	 * Return a list of the names of the glyphs in this font.
	 * 
	 * @return List of glyph names
	 */
	public String[] getGlyphList() {
		return this.glyphNames;
	}

	/**
	 * Return the widths of all of the glyphs of this font.
	 * 
	 * @return List of glyph widths
	 */
	public int[] getWidths() {
		int[] widths = new int[this.widthX.length];

		for (int i = 0; i < this.widthX.length; i++) {
			if (this.widthX[i] == Integer.MIN_VALUE) {
				widths[i] = this.defaultWidthX;
			}
			else {
				widths[i] = this.widthX[i] + this.nominalWidthX;
			}
		}

		return widths;
	}

	/**
	 * Return the left side bearings of all of the glyphs of this font.
	 * 
	 * @return List of left side bearings.
	 */
	public int[] getBearings() {
		return this.lsbX;
	}

	/**
	 * Return a bounding box calculated from the outlines.
	 * 
	 * @return the calculated bbox
	 */
	public float[] getBBox() {
		return this.bbox;
	}

	/**
	 * Return the size of the em square calculated from the outlines.
	 * 
	 * @return the calculated em square size
	 */
	public double getEmSquareSize() {
		return this.emSquareSize;
	}

	private class CharstringElement {
		private boolean isCommand = true;
		private String commandName;
		private int numberValue;
		private int length = 1;
		private ArrayList args = new ArrayList();
		final private boolean isResult;
		private CharstringElement parent;

		/**
		 * Constructor used for generating an integer parameter.
		 * 
		 * @param number
		 *            The number this element should represent
		 */
		public CharstringElement(int number) {
			this.isResult = false;
			this.isCommand = false;
			this.numberValue = number;
		}

		/**
		 * Constructor used for generating placeholder result elements.
		 * 
		 * @param parent
		 *            The element this is a result of
		 */
		public CharstringElement(CharstringElement parent) {
			this.isResult = true;
			this.isCommand = false;
			this.parent = parent;
			CFFWriter.this.currentCharString.add(this);
		}

		/**
		 * Normal constructor used when converting from Type 1 stream.
		 * 
		 * @param charstring
		 *            byte array to copy from
		 * @param pos
		 *            starting position for this element
		 */
		public CharstringElement(int[] charstring, int pos) {
			this.isResult = false;
			CFFWriter.this.currentCharString.add(this);

			int b = charstring[pos];

			if (b >= 32 && b <= 246) { // Single byte number

				this.numberValue = b - 139;
				this.isCommand = false;

			}
			else
				if ((b >= 247 && b <= 250) || (b >= 251 && b <= 254)) { // Two byte number

					if (b < 251) {
						this.numberValue = ((b - 247) * 256) + charstring[pos + 1] + 108;
					}
					else {
						this.numberValue = -((b - 251) * 256) - charstring[pos + 1] - 108;
					}

					this.isCommand = false;
					this.length = 2;

				}
				else {

					boolean mergePrevious = false;

					switch (b) {
						case 1: // hstem
							this.commandName = "hstem";
							claimArguments(2, true, true);
							break;
						case 3: // vstem
							this.commandName = "vstem";
							claimArguments(2, true, true);
							break;
						case 4: // vmoveto
							this.commandName = "vmoveto";
							claimArguments(1, true, true);

							// If in a flex section channel arg and 0 into current flex command
							if (CFFWriter.this.inFlex) {
								// If second pair found add to the first and store back
								if (CFFWriter.this.currentFlexCommand.args.size() == 2 && !CFFWriter.this.firstArgsAdded) {
									int arg0 = CFFWriter.this.currentFlexCommand.args.get(0).numberValue;
									int arg1 = this.args.get(0).numberValue + CFFWriter.this.currentFlexCommand.args.get(1).numberValue;
									CFFWriter.this.currentFlexCommand.args.clear();
									CFFWriter.this.currentFlexCommand.args.add(new CharstringElement(arg0));
									CFFWriter.this.currentFlexCommand.args.add(new CharstringElement(arg1));
									CFFWriter.this.firstArgsAdded = true;
								}
								else {
									CFFWriter.this.currentFlexCommand.args.add(new CharstringElement(0));
									CFFWriter.this.currentFlexCommand.args.add(this.args.get(0));
								}
								this.commandName = "";
							}
							break;
						case 5: // rlineto
							this.commandName = "rlineto";
							claimArguments(2, true, true);
							mergePrevious = true;
							break;
						case 6: // hlineto
							this.commandName = "hlineto";
							claimArguments(1, true, true);
							break;
						case 7: // vlineto
							this.commandName = "vlineto";
							claimArguments(1, true, true);
							break;
						case 8: // rrcurveto
							this.commandName = "rrcurveto";
							claimArguments(6, true, true);
							mergePrevious = true;
							break;
						case 9: // closepath
							this.commandName = "closepath";
							claimArguments(0, false, true);
							break;
						case 10: // callsubr
							this.commandName = "callsubr";
							claimArguments(1, false, false);

							int subrNumber = this.args.get(0).numberValue;

							// Handle starting a section of flex code
							if (!CFFWriter.this.inFlex && subrNumber == 1) {
								// Repurpose this as flex command and set flag for processing following commands
								this.args.clear();
								this.commandName = "flex";
								CFFWriter.this.currentFlexCommand = this;
								CFFWriter.this.inFlex = true;
							}

							// Handle subr calls during flex sections
							if (CFFWriter.this.inFlex && subrNumber >= 0 && subrNumber <= 2) {

								// Handle endind flex section
								if (subrNumber == 0) {
									claimArguments(3, false, false);
									if (this.args.size() >= 4) {
										CFFWriter.this.currentFlexCommand.args.add(this.args.get(3));
									}
									else {
										CFFWriter.this.currentFlexCommand.args.add(new CharstringElement(0));
									}
									CFFWriter.this.inFlex = false;
									CFFWriter.this.firstArgsAdded = false;
								}

								// Handle other cases
							}
							else {

								byte[] rawSubr = CFFWriter.this.subrs[subrNumber];

								// Deal with top byte being negative
								int[] subr = new int[rawSubr.length];
								for (int i = 0; i < rawSubr.length; i++) {
									subr[i] = rawSubr[i];
									if (subr[i] < 0) {
										subr[i] += 256;
									}
								}

								// Convert to CharstringElements
								CharstringElement element;
								for (int i = 0; i < subr.length; i += element.length) {
									element = new CharstringElement(subr, i);
								}
							}
							break;
						case 11: // return
							this.commandName = "return";
							break;
						case 12: // 2 byte command
							this.length = 2;
							switch (charstring[pos + 1]) {
								case 0: // dotsection
									this.commandName = "dotsection";
									claimArguments(0, false, true);
									break;
								case 1: // vstem3
									this.commandName = "vstem3";
									claimArguments(6, true, true);
									break;
								case 2: // hstem3
									this.commandName = "hstem3";
									claimArguments(6, true, true);
									break;
								case 6: // seac
									this.commandName = "seac";
									claimArguments(5, true, true);
									break;
								case 7: // sbw
									this.commandName = "sbw";
									claimArguments(4, true, true);
									CFFWriter.this.lsbX[CFFWriter.this.currentCharStringID] = this.args.get(0).evaluate();
									CFFWriter.this.lsbY[CFFWriter.this.currentCharStringID] = this.args.get(1).evaluate();
									CFFWriter.this.widthX[CFFWriter.this.currentCharStringID] = this.args.get(2).evaluate();
									CFFWriter.this.widthY[CFFWriter.this.currentCharStringID] = this.args.get(3).evaluate();

									// repurpose as rmoveto
									if (CFFWriter.this.lsbX[CFFWriter.this.currentCharStringID] != 0) {
										this.commandName = "rmoveto";
										this.args.clear();
										this.args.add(new CharstringElement(CFFWriter.this.lsbX[CFFWriter.this.currentCharStringID]));
										this.args.add(new CharstringElement(CFFWriter.this.lsbY[CFFWriter.this.currentCharStringID]));
									}
									break;
								case 12: // div
									this.commandName = "div";
									claimArguments(2, false, false);
									new CharstringElement(this);
									break;
								case 16: // callothersubr
									this.commandName = "callothersubr";
									claimArguments(2, false, false);
									if (this.args.size() > 1) {
										int count = this.args.get(1).numberValue;
										boolean foundEnough = claimArguments(count, false, false);

										if (!foundEnough) {
											CFFWriter.this.currentCharString.remove(this);
											return;
										}

										// Place arguments back on stack
										for (int i = 0; i < count; i++) {
											new CharstringElement(this.args.get((1 + count) - i).numberValue);
										}
									}
									break;
								case 17: // pop
									this.commandName = "pop";
									new CharstringElement(this);
									break;
								case 33: // setcurrentpoint
									this.commandName = "setcurrentpoint";
									claimArguments(2, true, true);
									break;
								default:
							}
							break;
						case 13: // hsbw
							this.commandName = "hsbw";
							claimArguments(2, true, true);
							CFFWriter.this.lsbX[CFFWriter.this.currentCharStringID] = this.args.get(0).evaluate();
							CFFWriter.this.widthX[CFFWriter.this.currentCharStringID] = this.args.get(1).evaluate();

							// repurpose as rmoveto
							if (CFFWriter.this.lsbX[CFFWriter.this.currentCharStringID] != 0) {
								this.commandName = "rmoveto";
								this.args.set(1, new CharstringElement(0));
							}
							break;
						case 14: // endchar
							this.commandName = "endchar";
							claimArguments(0, false, true);
							break;
						case 21: // rmoveto
							this.commandName = "rmoveto";
							claimArguments(2, true, true);

							// If in a flex section channel args into current flex command
							if (CFFWriter.this.inFlex) {
								// If second pair found add to the first and store back
								if (CFFWriter.this.currentFlexCommand.args.size() == 2 && !CFFWriter.this.firstArgsAdded) {
									int arg0 = this.args.get(0).numberValue + CFFWriter.this.currentFlexCommand.args.get(0).numberValue;
									int arg1 = this.args.get(1).numberValue + CFFWriter.this.currentFlexCommand.args.get(1).numberValue;
									CFFWriter.this.currentFlexCommand.args.clear();
									CFFWriter.this.currentFlexCommand.args.add(new CharstringElement(arg0));
									CFFWriter.this.currentFlexCommand.args.add(new CharstringElement(arg1));
									CFFWriter.this.firstArgsAdded = true;
								}
								else {
									CFFWriter.this.currentFlexCommand.args.add(this.args.get(0));
									CFFWriter.this.currentFlexCommand.args.add(this.args.get(1));
								}
								this.commandName = "";
							}
							break;
						case 22: // hmoveto
							this.commandName = "hmoveto";
							claimArguments(1, true, true);

							// If in a flex section channel arg and 0 into current flex command
							if (CFFWriter.this.inFlex) {
								// If second pair found add to the first and store back
								if (CFFWriter.this.currentFlexCommand.args.size() == 2 && !CFFWriter.this.firstArgsAdded) {
									int arg0 = this.args.get(0).numberValue + CFFWriter.this.currentFlexCommand.args.get(0).numberValue;
									int arg1 = CFFWriter.this.currentFlexCommand.args.get(1).numberValue;
									CFFWriter.this.currentFlexCommand.args.clear();
									CFFWriter.this.currentFlexCommand.args.add(new CharstringElement(arg0));
									CFFWriter.this.currentFlexCommand.args.add(new CharstringElement(arg1));
									CFFWriter.this.firstArgsAdded = true;
								}
								else {
									CFFWriter.this.currentFlexCommand.args.add(this.args.get(0));
									CFFWriter.this.currentFlexCommand.args.add(new CharstringElement(0));
								}
								this.commandName = "";
							}
							break;
						case 30: // vhcurveto
							this.commandName = "vhcurveto";
							claimArguments(4, true, true);
							break;
						case 31: // hvcurveto
							this.commandName = "hvcurveto";
							claimArguments(4, true, true);
							break;
						case 255: // 5 byte number
							this.length = 5;
							this.isCommand = false;
							this.numberValue = (charstring[pos + 4] & 0xFF) + ((charstring[pos + 3] & 0xFF) << 8)
									+ ((charstring[pos + 2] & 0xFF) << 16) + ((charstring[pos + 1] & 0xFF) << 24);

							break;
						default:
					}

					if (mergePrevious) {
						CharstringElement previous = CFFWriter.this.currentCharString.get(CFFWriter.this.currentCharString.indexOf(this) - 1);
						if (this.commandName.equals(previous.commandName) && previous.args.size() <= (39 - this.args.size())) {
							CFFWriter.this.currentCharString.remove(previous);
							for (CharstringElement e : this.args) {
								previous.args.add(e);
							}
							this.args = previous.args;
						}
					}
				}
		}

		/**
		 * Evaluate the numerical value of this element. This is used for hsbw and sbw where the value is being funneled into a data structure rather
		 * than remaining in the converted charstring.
		 * 
		 * @return The numerical value of the element.
		 */
		private int evaluate() {
			if (this.isResult) {
				return this.parent.evaluate();
			}
			else
				if (this.isCommand) {
					if ("div".equals(this.commandName)) {
						return (this.args.get(1).evaluate() / this.args.get(0).evaluate());
					}
					else {}
				}

			return this.numberValue;
		}

		/**
		 * @return The number of bytes used in the original stream for just this element (not it's arguments).
		 */
		public int getLength() {
			return this.length;
		}

		/**
		 * Get the displacement created by this CharstringElement.
		 * 
		 * @return An int array pair of values for the horizontal and vertical displacement values.
		 */
		public int[] getDisplacement() {

			if (!this.isCommand) {
				return new int[] { 0, 0 };
			}

			if ("hstem".equals(this.commandName)) {}
			else
				if ("vstem".equals(this.commandName)) {}
				else
					if ("vmoveto".equals(this.commandName)) {
						return new int[] { 0, this.args.get(0).evaluate() };
					}
					else
						if ("rlineto".equals(this.commandName)) {
							int dx = 0;
							int dy = 0;
							for (int i = 0; i < this.args.size() / 2; i++) {
								dx += this.args.get(i * 2).evaluate();
								dy += this.args.get(1 + (i * 2)).evaluate();
							}
							return new int[] { dx, dy };
						}
						else
							if ("hlineto".equals(this.commandName)) {
								return new int[] { this.args.get(0).evaluate(), 0 };
							}
							else
								if ("vlineto".equals(this.commandName)) {
									return new int[] { 0, this.args.get(0).evaluate() };
								}
								else
									if ("rrcurveto".equals(this.commandName)) {
										int dx = 0;
										int dy = 0;
										for (int i = 0; i < this.args.size() / 2; i++) {
											dx += this.args.get(i * 2).evaluate();
											dy += this.args.get(1 + (i * 2)).evaluate();
										}
										return new int[] { dx, dy };
									}
									else
										if ("closepath".equals(this.commandName)) {}
										else
											if ("callsubr".equals(this.commandName)) {}
											else
												if ("return".equals(this.commandName)) {}
												else
													if ("dotsection".equals(this.commandName)) {}
													else
														if ("vstem3".equals(this.commandName)) {}
														else
															if ("hstem3".equals(this.commandName)) {}
															else
																if ("seac".equals(this.commandName)) {
																	// Hopefully won't have to implement this...
																}
																else
																	if ("sbw".equals(this.commandName)) {}
																	else
																		if ("div".equals(this.commandName)) {}
																		else
																			if ("callothersubr".equals(this.commandName)) {}
																			else
																				if ("pop".equals(this.commandName)) {}
																				else
																					if ("setcurrentpoint".equals(this.commandName)) {}
																					else
																						if ("hsbw".equals(this.commandName)) {}
																						else
																							if ("endchar".equals(this.commandName)) {}
																							else
																								if ("rmoveto".equals(this.commandName)) {
																									return new int[] { this.args.get(0).evaluate(),
																											this.args.get(1).evaluate() };
																								}
																								else
																									if ("hmoveto".equals(this.commandName)) {
																										return new int[] {
																												this.args.get(0).evaluate(), 0 };
																									}
																									else
																										if ("vhcurveto".equals(this.commandName)) {
																											return new int[] {
																													this.args.get(1).evaluate()
																															+ this.args.get(3)
																																	.evaluate(),
																													this.args.get(0).evaluate()
																															+ this.args.get(2)
																																	.evaluate() };
																										}
																										else
																											if ("hvcurveto".equals(this.commandName)) {
																												return new int[] {
																														this.args.get(0).evaluate()
																																+ this.args.get(1)
																																		.evaluate(),
																														this.args.get(2).evaluate()
																																+ this.args.get(3)
																																		.evaluate() };
																											}
																											else
																												if ("flex".equals(this.commandName)) {
																													int dx = 0;
																													int dy = 0;
																													for (int i = 0; i < 6; i++) {
																														dx += this.args.get(i * 2)
																																.evaluate();
																														dy += this.args.get(
																																1 + (i * 2))
																																.evaluate();
																													}
																													return new int[] { dx, dy };
																												}
																												else
																													if (this.commandName.length() == 0) {
																														return new int[] { 0, 0 };
																													}
																													else {}

			return new int[] { 0, 0 };
		}

		/**
		 * Scale this element according to a precalculated scale value. Works recursively.
		 */
		public void scale() {

			// If result, ignore
			if (this.isResult) {
				return;
			}

			// If number, scale it
			if (!this.isCommand) {
				this.numberValue = (int) (this.numberValue * CFFWriter.this.scale);
				return;
			}

			// Check how to handle args if command
			boolean scaleAll = false;
			if ("hstem".equals(this.commandName)) {
				scaleAll = true;
			}
			else
				if ("vstem".equals(this.commandName)) {
					scaleAll = true;
				}
				else
					if ("vmoveto".equals(this.commandName)) {
						scaleAll = true;
					}
					else
						if ("rlineto".equals(this.commandName)) {
							scaleAll = true;
						}
						else
							if ("hlineto".equals(this.commandName)) {
								scaleAll = true;
							}
							else
								if ("vlineto".equals(this.commandName)) {
									scaleAll = true;
								}
								else
									if ("rrcurveto".equals(this.commandName)) {
										scaleAll = true;
									}
									else
										if ("closepath".equals(this.commandName)) {}
										else
											if ("callsubr".equals(this.commandName)) {}
											else
												if ("return".equals(this.commandName)) {}
												else
													if ("dotsection".equals(this.commandName)) {}
													else
														if ("vstem3".equals(this.commandName)) {
															scaleAll = true;
														}
														else
															if ("hstem3".equals(this.commandName)) {
																scaleAll = true;
															}
															else
																if ("seac".equals(this.commandName)) {
																	for (int i = 0; i < 3; i++) {
																		this.args.get(i).scale();
																	}
																}
																else
																	if ("sbw".equals(this.commandName)) {}
																	else
																		if ("div".equals(this.commandName)) {
																			scaleAll = true;
																		}
																		else
																			if ("callothersubr".equals(this.commandName)) {}
																			else
																				if ("pop".equals(this.commandName)) {}
																				else
																					if ("setcurrentpoint".equals(this.commandName)) {
																						scaleAll = true;
																					}
																					else
																						if ("hsbw".equals(this.commandName)) {}
																						else
																							if ("endchar".equals(this.commandName)) {}
																							else
																								if ("rmoveto".equals(this.commandName)) {
																									scaleAll = true;
																								}
																								else
																									if ("hmoveto".equals(this.commandName)) {
																										scaleAll = true;
																									}
																									else
																										if ("vhcurveto".equals(this.commandName)) {
																											scaleAll = true;
																										}
																										else
																											if ("hvcurveto".equals(this.commandName)) {
																												scaleAll = true;
																											}
																											else
																												if ("flex".equals(this.commandName)) {
																													scaleAll = true;
																												}
																												else
																													if (this.commandName.length() == 0) {}
																													else {}

			if (scaleAll) {
				for (CharstringElement e : this.args) {
					e.scale();
				}
			}
		}

		/**
		 * Return the type 2 bytes required to match the effect of the instruction and it's arguments.
		 * 
		 * @return the type 2 bytes required to match the effect of the instruction and it's arguments
		 */
		public byte[] getType2Bytes() {

			if (!this.isCommand) {

				if (this.isResult) {
					return new byte[] {};
				}

				return FontWriter.setCharstringType2Number(this.numberValue);
			}

			boolean noChange = false;
			byte[] commandNumber = new byte[] {};

			if ("hstem".equals(this.commandName)) {
				noChange = true;
				commandNumber = new byte[] { 1 };
			}
			else
				if ("vstem".equals(this.commandName)) {
					noChange = true;
					commandNumber = new byte[] { 3 };
				}
				else
					if ("vmoveto".equals(this.commandName)) {
						noChange = true;
						commandNumber = new byte[] { 4 };
					}
					else
						if ("rlineto".equals(this.commandName)) {
							noChange = true;
							commandNumber = new byte[] { 5 };
						}
						else
							if ("hlineto".equals(this.commandName)) {
								noChange = true;
								commandNumber = new byte[] { 6 };
							}
							else
								if ("vlineto".equals(this.commandName)) {
									noChange = true;
									commandNumber = new byte[] { 7 };
								}
								else
									if ("rrcurveto".equals(this.commandName)) {
										noChange = true;
										commandNumber = new byte[] { 8 };
									}
									else
										if ("closepath".equals(this.commandName)) {
											// Remove moveto automatically closes paths in Type 2
											return new byte[] {};
										}
										else
											if ("callsubr".equals(this.commandName)) {
												return new byte[] {};

											}
											else
												if ("return".equals(this.commandName)) {
													// noChange=true;
													// commandNumber=new byte[]{11};
													// Unsupported othersubrs
													return new byte[] {};
												}
												else
													if ("dotsection".equals(this.commandName)) {
														// Deprecated - remove
														return new byte[] {};
													}
													else
														if ("vstem3".equals(this.commandName)) {

														}
														else
															if ("hstem3".equals(this.commandName)) {

															}
															else
																if ("seac".equals(this.commandName)) { // Create accented character by merging
																										// specified charstrings

																	// Get args
																	// int asb = args.get(0).numberValue;
																	int adx = this.args.get(1).numberValue;
																	int ady = this.args.get(2).numberValue;
																	int bchar = this.args.get(3).numberValue;
																	int achar = this.args.get(4).numberValue;

																	// Look up character code for specified location in standard encoding
																	int aCharUnicode = StandardFonts.getEncodedChar(StandardFonts.STD, achar).charAt(
																			0);
																	int bCharUnicode = StandardFonts.getEncodedChar(StandardFonts.STD, bchar).charAt(
																			0);
																	int accentIndex = -1;
																	int baseIndex = -1;

																	// Run through glyph names comparing character codes to those for the accent and
																	// base to find glyph indices
																	for (int i = 0; i < CFFWriter.this.glyphNames.length; i++) {
																		int adobePos = StandardFonts.getAdobeMap(CFFWriter.this.glyphNames[i]);
																		if (adobePos >= 0 && adobePos < 512) {

																			if (adobePos == aCharUnicode) {
																				accentIndex = i;
																			}
																			if (adobePos == bCharUnicode) {
																				baseIndex = i;
																			}
																		}
																	}

																	// Check both glyphs found
																	if (accentIndex == -1 || baseIndex == -1) {
																		return new byte[] {};
																	}

																	// Merge glyphs
																	try {
																		ByteArrayOutputStream bos = new ByteArrayOutputStream();

																		int charstringStore = CFFWriter.this.currentCharStringID;

																		// Fetch base charstring, convert, and remove endchar command
																		CFFWriter.this.charstringXDisplacement[baseIndex] = 0;
																		CFFWriter.this.charstringYDisplacement[baseIndex] = 0;
																		CFFWriter.this.inSeac = true;
																		byte[] rawBaseCharstring = convertCharstring(
																				CFFWriter.this.charstrings[baseIndex], baseIndex);
																		CFFWriter.this.inSeac = false;
																		CFFWriter.this.currentCharStringID = charstringStore;
																		byte[] baseCharstring = new byte[rawBaseCharstring.length - 1];
																		System.arraycopy(rawBaseCharstring, 0, baseCharstring, 0,
																				baseCharstring.length);
																		bos.write(baseCharstring);

																		// Move to the origin plus the offset
																		bos.write(FontWriter
																				.setCharstringType2Number(-(CFFWriter.this.charstringXDisplacement[baseIndex])
																						+ adx));
																		bos.write(FontWriter
																				.setCharstringType2Number(-(CFFWriter.this.charstringYDisplacement[baseIndex])
																						+ ady));
																		bos.write((byte) 21);

																		// Fetch accent charstring and convert
																		CFFWriter.this.charstringXDisplacement[accentIndex] = 0;
																		CFFWriter.this.charstringYDisplacement[accentIndex] = 0;
																		byte[] accentCharstring = convertCharstring(
																				CFFWriter.this.charstrings[accentIndex], accentIndex);
																		CFFWriter.this.currentCharStringID = charstringStore;
																		bos.write(accentCharstring);

																		return bos.toByteArray();
																	}
																	catch (IOException e) {
																		// tell user and log
																		if (LogWriter.isOutput()) LogWriter.writeLog("Exception: " + e.getMessage());
																	}

																}
																else
																	if ("sbw".equals(this.commandName)) {
																		// Might need to moveto arg coordinates?
																		return new byte[] {};

																	}
																	else
																		if ("div".equals(this.commandName)) {
																			noChange = true;
																			commandNumber = new byte[] { 12, 12 };

																		}
																		else
																			if ("callothersubr".equals(this.commandName)) {

																			}
																			else
																				if ("pop".equals(this.commandName)) {

																				}
																				else
																					if ("setcurrentpoint".equals(this.commandName)) {

																					}
																					else
																						if ("hsbw".equals(this.commandName)) {
																							// Might need to moveto arg coordinates?
																							return new byte[] {};

																						}
																						else
																							if ("endchar".equals(this.commandName)) {
																								noChange = true;
																								commandNumber = new byte[] { 14 };
																							}
																							else
																								if ("rmoveto".equals(this.commandName)) {
																									noChange = true;
																									commandNumber = new byte[] { 21 };
																								}
																								else
																									if ("hmoveto".equals(this.commandName)) {
																										noChange = true;
																										commandNumber = new byte[] { 22 };
																									}
																									else
																										if ("vhcurveto".equals(this.commandName)) {
																											noChange = true;
																											commandNumber = new byte[] { 30 };
																										}
																										else
																											if ("hvcurveto".equals(this.commandName)) {
																												noChange = true;
																												commandNumber = new byte[] { 31 };
																											}
																											else
																												if ("flex".equals(this.commandName)) {
																													noChange = true;
																													commandNumber = new byte[] { 12,
																															35 };
																												}
																												else
																													if (this.commandName.length() == 0) {
																														return new byte[] {};
																													}
																													else {}

			if (noChange) {
				// No change - return args and command
				ByteArrayOutputStream bos = getStreamWithArgs();
				try {
					bos.write(commandNumber);
				}
				catch (IOException e) {
					// tell user and log
					if (LogWriter.isOutput()) LogWriter.writeLog("Exception: " + e.getMessage());
				}
				return bos.toByteArray();
			}

			return new byte[] {};
		}

		private ByteArrayOutputStream getStreamWithArgs() {
			ByteArrayOutputStream result = new ByteArrayOutputStream();

			try {
				for (CharstringElement arg : this.args) {
					result.write(arg.getType2Bytes());
				}
			}
			catch (IOException e) {
				// tell user and log
				if (LogWriter.isOutput()) LogWriter.writeLog("Exception: " + e.getMessage());
			}

			return result;
		}

		/**
		 * Return a representation of the element as a string.
		 * 
		 * @return Element as string
		 */
		@Override
		public String toString() {
			if (this.isCommand) {
				return this.commandName + this.args.toString();
			}

			if (this.isResult) {
				return "result of " + this.parent;
			}

			return String.valueOf(this.numberValue);
		}

		private void printStack() {
			System.out.println("Stack bottom");
			for (CharstringElement e : CFFWriter.this.currentCharString) {
				if (!e.isCommand) {
					System.out.println(e);
				}
			}
			System.out.println("Stack top");
		}

		/**
		 * Removes arguments from the stack (in other words, numbers and results from the instruction stream) and places them in this element's
		 * argument list.
		 * 
		 * @param count
		 *            The number of arguments to take
		 * @param takeFromBottom
		 *            Where to take the arguments from
		 * @param clearStack
		 *            Whether to clear the stack after
		 * @return whether enough arguments were found
		 */
		private boolean claimArguments(int count, boolean takeFromBottom, boolean clearStack) {

			if (count > 0) {
				int currentIndex = CFFWriter.this.currentCharString.indexOf(this);
				if (currentIndex == -1) {
					throw new RuntimeException("Not in list!");
				}

				int argsFound = 0;
				boolean failed = false;
				while (argsFound < count && !failed) {

					boolean found = false;
					if (takeFromBottom) {
						int pos = 0;
						while (!found && pos <= currentIndex) {
							CharstringElement e = CFFWriter.this.currentCharString.get(pos);
							if (!e.isCommand) {
								argsFound++;
								this.args.add(e);
								CFFWriter.this.currentCharString.remove(e);
								found = true;
							}
							pos++;
						}
					}
					else {
						int pos = currentIndex;
						while (!found && pos >= 0) {
							CharstringElement e = CFFWriter.this.currentCharString.get(pos);
							if (!e.isCommand) {
								argsFound++;
								this.args.add(e);
								CFFWriter.this.currentCharString.remove(e);
								found = true;
								currentIndex--;
							}
							pos--;
						}
					}
					if (!found) {
						failed = true;
					}
				}

				if (argsFound < count) {
					// System.out.println("Not enough arguments! ("+argsFound+" of "+count+") "+ (currentCharStringID > charstrings.length ?
					// "subr "+(currentCharStringID-charstrings.length) : "charstring "+currentCharStringID));
					// throw new RuntimeException("Not enough arguments!");
					return false;
				}
			}

			if (clearStack) {
				for (int i = 0; i < CFFWriter.this.currentCharString.size(); i++) {
					CharstringElement e = CFFWriter.this.currentCharString.get(i);
					if (!e.isCommand) {
						CFFWriter.this.currentCharString.remove(e);
					}
				}
			}

			return true;

		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy