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

org.jpedal.fonts.tt.conversion.FontWriter 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


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

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.Deflater;

import org.jpedal.fonts.tt.FontFile2;
import org.jpedal.utils.Sorts;
import org.jpedal.utils.StringUtils;

public class FontWriter extends FontFile2 {

	private static final long serialVersionUID = -5399219427547126730L;

	String name;

	int glyphCount;

	int headCheckSumPos = -1;

	Map IDtoTable = new HashMap();

	private static boolean compressWoff = true;
	// remove the commas in generated font names in order to support html display
	// private final static boolean removeCommas = true;

	static {
		if (System.getProperty("org.jpedal.pdf2html.compressWoff") != null
				&& System.getProperty("org.jpedal.pdf2html.compressWoff").toLowerCase().equals("false")) compressWoff = false;
	}

	// list responsible for holding TTF Table Header information.
	ArrayList ttfList = new ArrayList();

	// old table order source kept in case of revert
	// /**
	// * the tsbles need to be in 1 order and
	// * the tags in the header in another
	// */
	// static final String[] TTFTableOrder=new String[]{"OS/2",
	// "cmap",
	// "glyf",
	// "head",
	// "hhea",
	// "hmtx",
	// "loca",
	// "maxp",
	// "name",
	// "post",
	// };

	/**
	 * the tsbles need to be in 1 order and the tags in the header in another
	 */
	static final String[] TTFTableOrder = new String[] { "OS/2", "cmap", "cvt ", "fpgm", "glyf", "head", "hhea", "hmtx", "loca", "maxp", "name",
			"post", "prep" };

	private HashMap tableStore = new HashMap();

	public FontWriter(byte[] data) {
		super(data);
	}

	public FontWriter() {}

	/**
	 * Retrieves a positive int from a specified number of bytes in supplied array
	 * 
	 * @param d
	 *            Byte array to fetch from
	 * @param startPoint
	 *            Location of start of number
	 * @param offsetSize
	 *            Number of bytes occupied
	 * @return Number found
	 */
	static int getUintFromByteArray(byte[] d, int startPoint, int offsetSize) {
		int shift = (offsetSize - 1) * 8;
		int result = 0;
		int offset = 0;
		while (shift >= 0) {
			int part = d[startPoint + offset];
			if (part < 0) part += 256;
			result = result | part << shift;
			offset++;
			shift = shift - 8;
		}

		return result;
	}

	/**
	 * Retrieves a binary number of arbitrary length from the supplied int array, treating it as bytes
	 * 
	 * @param d
	 *            int array to fetch from
	 * @param startPoint
	 *            Location of start of number
	 * @param offsetSize
	 *            Number of bytes occupied
	 * @return Number found
	 */
	static int getUintFromIntArray(int[] d, int startPoint, int offsetSize) {
		int shift = (offsetSize - 1) * 8;
		int result = 0;
		int offset = 0;
		while (shift >= 0) {
			result = result | (d[startPoint + offset] & 0xFF) << shift;
			offset++;
			shift = shift - 8;
		}

		return result;
	}

	/**
	 * Creates a Type 1c number as a byte array.
	 * 
	 * @param num
	 *            Number to encode
	 * @return byte array of number in type 1c format
	 */
	static byte[] set1cNumber(int num) {
		byte[] result;

		if (num >= -107 && num <= 107) {
			result = new byte[] { (byte) (num + 139) };

		}
		else
			if (num >= 108 && num <= 1131) {
				num -= 108;
				result = new byte[] { (byte) (247 + (num / 256)), (byte) (num & 0xFF) };

			}
			else
				if (num >= -1131 && num <= -108) {
					num += 108;
					result = new byte[] { (byte) (251 + (num / -256)), (byte) (-num & 0xFF) };

				}
				else
					if (num >= -32768 && num <= 32767) {
						result = new byte[] { 28, (byte) ((num / 256) & 0xFF), (byte) (num & 0xFF) };

					}
					else {
						result = new byte[] { 29, (byte) ((((num / 256) / 256) / 256) & 0xFF), (byte) (((num / 256) / 256) & 0xFF),
								(byte) ((num / 256) & 0xFF), (byte) (num & 0xFF) };
					}

		return result;
	}

	/**
	 * Creates a real type 1c as a byte array.
	 * 
	 * @param num
	 *            Number to encode
	 * @return byte array of number in type 1c real format
	 */
	static byte[] set1cRealNumber(double num) {
		String n = Double.toString(num);

		// Reduce length of number
		final int maxLength = 10;
		if (n.length() > maxLength) {
			if (n.contains("E")) {
				String[] parts = n.split("E");
				n = n.substring(0, maxLength - (parts[1].length() + 1)) + "E" + parts[1];
			}
			else {
				n = n.substring(0, maxLength);
			}
		}

		// Append f to end
		n = n + "f";

		// Find length in bytes
		int len = n.length();
		if ((len % 2) == 1) {
			len++;
		}
		len /= 2;
		byte[] result = new byte[1 + len];

		result[0] = 30;

		// Add nibbles
		for (int i = 0; i < len; i++) {
			int charLoc = i * 2;
			char a = n.charAt(charLoc);
			char b;
			if (charLoc + 1 < n.length()) {
				b = n.charAt(charLoc + 1);
			}
			else {
				b = a;
			}

			byte aByte = getNibble(a);
			byte bByte = getNibble(b);

			result[i + 1] = (byte) (((aByte & 0xF) << 4) | (bByte & 0xF));
		}

		return result;
	}

	/**
	 * Get the nibble which represents a character in a type 1c real number
	 * 
	 * @param c
	 *            Character to represent
	 * @return Representation as nibble in bottom 4 bits
	 */
	private static byte getNibble(char c) {
		switch (c) {
			case '.':
				return 0xa;
			case 'E':
				return 0xb;
			case '-':
				return 0xe;
			case 'f':
				return 0xf;
			default:
				return (byte) Integer.parseInt("" + c);
		}
	}

	/**
	 * Creates a Charstring Type 2 number as a byte array.
	 * 
	 * @param num
	 *            Number to encode
	 * @return byte array of number in charstring type 2 format
	 */
	static byte[] setCharstringType2Number(int num) {
		byte[] result;

		if (num >= -107 && num <= 107) {
			result = new byte[] { (byte) (num + 139) };

		}
		else
			if (num >= 108 && num <= 1131) {
				num -= 108;
				result = new byte[] { (byte) (247 + (num / 256)), (byte) (num & 0xFF) };

			}
			else
				if (num >= -1131 && num <= -108) {
					num += 108;
					result = new byte[] { (byte) (251 + (num / -256)), (byte) (-num & 0xFF) };

				}
				else {
					if (num >= 0) {
						result = new byte[] { (byte) 255, (byte) ((num / 256) & 0xFF), (byte) (num & 0xFF), 0, 0 };
					}
					else {
						int add = num + 32768;
						result = new byte[] { (byte) 255, (byte) (0x80 | ((add / 256) & 0x7F)), (byte) (add & 0xFF), 0, 0 };
					}
				}

		return result;
	}

	/**
	 * Encode a number as a byte array of arbitrary length.
	 * 
	 * @param num
	 *            The number to encode
	 * @param byteCount
	 *            The number of bytes to use
	 * @return The number expressed in the required number of bytes
	 */
	public static byte[] setUintAsBytes(int num, int byteCount) {
		byte[] result = new byte[byteCount];
		for (int i = byteCount; i > 0; i--) {
			int part = num;
			for (int j = 1; j < i; j++) {
				part = (part >> 8);
			}
			result[byteCount - i] = (byte) part;
		}

		return result;
	}

	static int createChecksum(byte[] table) {

		int checksumValue = 0;

		FontFile2 checksum = new FontFile2(table, true);

		int longCount = ((table.length + 3) >> 2);

		for (int j = 0; j < longCount; j++) {
			checksumValue = checksumValue + checksum.getNextUint32();
		}

		return checksumValue;
	}

	/**
	 * return a short
	 */
	final static public byte[] setUFWord(int rawValue) {

		short value = (short) rawValue;

		byte[] returnValue = new byte[2];

		for (int i = 0; i < 2; i++) {
			returnValue[i] = (byte) ((value >> (8 * (1 - i))) & 255);
		}

		return returnValue;
	}

	/**
	 * return a short
	 */
	final static public byte[] setFWord(int rawValue) {

		short value = (short) rawValue;

		byte[] returnValue = new byte[2];

		for (int i = 0; i < 2; i++) {
			returnValue[i] = (byte) ((value >> (8 * (1 - i))) & 255);
		}

		return returnValue;
	}

	/**
	 * turn int back into byte[2]
	 **/
	final static public byte[] setNextUint16(int value) {

		byte[] returnValue = new byte[2];

		for (int i = 0; i < 2; i++) {
			returnValue[i] = (byte) ((value >> (8 * (1 - i))) & 255);
		}
		return returnValue;
	}

	/**
	 * turn int back into byte[2]
	 **/
	final static public byte[] setNextInt16(int value) {

		byte[] returnValue = new byte[2];

		for (int i = 0; i < 2; i++) {
			returnValue[i] = (byte) ((value >> (8 * (1 - i))) & 255);
		}
		return returnValue;
	}

	/**
	 * turn int back into byte[2]
	 **/
	final static public byte[] setNextSignedInt16(short value) {

		byte[] returnValue = new byte[2];

		for (int i = 0; i < 2; i++) {
			returnValue[i] = (byte) ((value >> (8 * (1 - i))) & 255);
		}
		return returnValue;
	}

	/**
	 * turn int back into byte
	 **/
	final static public byte setNextUint8(int value) {
		return (byte) (value & 255);
	}

	/**
	 * turn int back into byte[4]
	 **/
	final static public byte[] setNextUint32(int value) {

		byte[] returnValue = new byte[4];

		for (int i = 0; i < 4; i++) {
			returnValue[i] = (byte) ((value >> (8 * (3 - i))) & 255);
		}

		return returnValue;
	}

	/**
	 * turn int back into byte[8]
	 **/
	final static public byte[] setNextUint64(int value) {

		byte[] returnValue = new byte[8];

		for (int i = 0; i < 8; i++) {
			returnValue[i] = (byte) ((value >> (8 * (7 - i))) & 255);
		}

		return returnValue;
	}

	final static public byte[] setNextUint64(long value) {

		byte[] returnValue = new byte[8];

		for (int i = 0; i < 8; i++) {
			returnValue[i] = (byte) ((value >> (8 * (7 - i))) & 255);
		}

		return returnValue;
	}

	/** read the table tableLength */
	final public byte[] writeFontToStream() throws IOException {

		readTables();

		ByteArrayOutputStream bos = new ByteArrayOutputStream();

		// version
		if (this.type == OPENTYPE) { // OTF with glyf data start OTTO otherwise 1.0
			if (this.subType == PS) bos.write(setNextUint32(1330926671));
			else bos.write(setNextUint32(65536));
		}
		else
			if (this.type == TTC) bos.write(setNextUint32(1953784678));
			else bos.write(setNextUint32(65536));

		if (this.type == TTC) {

			System.out.println("TTC write not implemented");

			// getNextUint32(); //version
			// fontCount=getNextUint32();
			//
			// //location of tables
			// tables=new int[tableCount][fontCount];
			// tableLength=new int[tableCount][fontCount];
			//
			// int[] fontOffsets=new int[fontCount];
			//
			// for(int currentFont=0;currentFont 0 ? 4 - (compLength % 4) : 0;
			if (padding > 0) {
				byte[] n = new byte[padding];
				dbos.write(n);
			}
			endPos = endPos + d.getCompressLength() + padding;
			// System.out.println("printing\t"+d.getTag()+" off "+d.getOffset()+" checksum "+d.getChecksum()+" length "+d.getLength()+" compressLen "+d.getCompressLength()+" --pdding-- "+
			// padding +" --- end-- "+endPos);

		}

		ByteArrayOutputStream wbos = new ByteArrayOutputStream();
		wbos.write(setNextUint32(0x774f4646)); // signature

		// below lines write sfnt version into woff data
		if (this.type == OPENTYPE) { // OTF with glyf data start OTTO otherwise 1.0
			if (this.subType == PS) wbos.write(setNextUint32(1330926671));
			else wbos.write(setNextUint32(65536));
		}
		else
			if (this.type == TTC) {
				wbos.write(setNextUint32(1953784678));
			}
			else {
				wbos.write(setNextUint32(65536));
			}
		// end of sfnt version

		wbos.write(setNextUint32(woffSuffix + endPos));// length
		wbos.write(setNextUint16(this.numTables));// number of tables
		wbos.write(setNextUint16(0)); // reserved

		int endPadding = (originalSfntData.length % 4) > 0 ? 4 - (originalSfntData.length % 4) : 0;

		wbos.write(setNextUint32(endPadding > 0 ? originalSfntData.length + endPadding : originalSfntData.length));
		wbos.write(setNextUint16(1)); // major version
		wbos.write(setNextUint16(1)); // minor version
		wbos.write(setNextUint32(0)); // metaOffet
		wbos.write(setNextUint32(0)); // metaLength
		wbos.write(setNextUint32(0)); // metaOrigLength
		wbos.write(setNextUint32(0)); // privOffset
		wbos.write(setNextUint32(0)); // privLength

		for (TTFDirectory t : this.ttfList) {
			wbos.write(t.getTagBytes());
			wbos.write(setNextUint32(t.getOffset()));
			wbos.write(setNextUint32(t.getCompressLength()));
			wbos.write(setNextUint32(t.getLength()));
			wbos.write(setNextUint32(t.getChecksum()));
		}
		wbos.write(dbos.toByteArray());
		wbos.flush();
		wbos.close();
		return wbos.toByteArray();
	}

	/*
	 * Method adjust the header table checksum and checkSumAdjustment field in head
	 * @return calculated head checksum
	 */
	private int AdjustWoffChecksum(byte[] tableBytes, int headOffset, int headLength) {

		if (tableBytes.length > headOffset && headLength >= 4) {
			ByteBuffer data = ByteBuffer.wrap(tableBytes);

			byte[] a = new byte[4];
			for (int z = 0; z < 4; z++) {
				a[z] = 0;
			}

			System.arraycopy(a, 0, tableBytes, (headOffset + 8), 4);
			int totalChecksum = (0xB1B0AFBA - createChecksum(a));

			byte[] b = new byte[headLength];
			System.arraycopy(data.array(), headOffset, b, 0, headLength);

			int headChecksum = createChecksum(b);

			ByteBuffer bb = ByteBuffer.allocate(4);
			bb.putInt(totalChecksum);
			byte[] c = bb.array();

			System.arraycopy(c, 0, tableBytes, (headOffset + 8), 4);
			return headChecksum;

		}
		else {
			return 0;
		}
	}

	/*
	 * This class added in order to access the variable amount in woff Font Conversion
	 */
	private class TTFDirectory {
		private int offset2, checksum2, length2, compressLength;

		private String tag2;

		public TTFDirectory(String tag, int offset, int checksum, int length) {
			this.tag2 = tag;
			this.offset2 = offset;
			this.checksum2 = checksum;
			this.length2 = length;
		}

		public String getTag() {
			return this.tag2;
		}

		public byte[] getTagBytes() {
			byte[] b = new byte[4];
			for (int z = 0; z < 4; z++) {
				b[z] = (byte) this.getTag().charAt(z);
			}
			return b;
		}

		public int getOffset() {
			return this.offset2;
		}

		public int getChecksum() {
			return this.checksum2;
		}

		public int getLength() {
			return this.length2;
		}

		public int getCompressLength() {
			return this.compressLength;
		}

		public void setOffset(int offset) {
			this.offset2 = offset;
		}

		public void setChecksum(int checksum) {
			this.checksum2 = checksum;
		}

		public void setLength(int length) {
			this.length2 = length;
		}

		public void setTag(String tag) {
			this.tag2 = tag;
		}

		public void setCompressLength(int compressLength) {
			this.compressLength = compressLength;
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy