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

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


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

public class CFFFixer {

	private byte[] data, original;
	private int charstringOffset = -1;

	private int privateOffset = -1;
	private int privateOffsetLocation = -1;
	private int privateOffsetLength = -1;

	private int fdArrayOffset = -1;
	private int fdArrayOffsetLocation = -1;
	private int fdArrayOffsetLength = -1;

	public CFFFixer(byte[] cff) {
		super();

		this.data = cff;
		this.original = new byte[cff.length];
		System.arraycopy(cff, 0, this.original, 0, cff.length);

		// Check for any sequence of 12 0 and fix if found
		if (scanForDotsection()) fixData();
	}

	/**
	 * Tests for a sequence of 12 0, which might signify dotsection is being used.
	 * 
	 * @return Whether dotsection might be being used.
	 */
	private boolean scanForDotsection() {
		boolean result = false;
		for (int i = 0; i < this.data.length - 1; i++) {
			if (this.data[i] == 12 && this.data[i + 1] == 0) result = true;
		}

		return result;
	}

	/**
	 * Go through the data and remove bad commands.
	 */
	private void fixData() {
		try {

			// Find top dict index
			int nameIndexStart = this.data[2];
			int nameIndexCount = FontWriter.getUintFromByteArray(this.data, nameIndexStart, 2);
			int nameIndexOffsize = this.data[nameIndexStart + 2];
			int nameIndexEndOffsetLocation = nameIndexStart + 3 + (nameIndexOffsize * nameIndexCount);
			int nameIndexEndOffset = FontWriter.getUintFromByteArray(this.data, nameIndexEndOffsetLocation, nameIndexOffsize);

			// Find top dict
			int dictIndexStart = nameIndexEndOffsetLocation + nameIndexEndOffset + (nameIndexOffsize - 1);
			int topDictIndexCount = FontWriter.getUintFromByteArray(this.data, dictIndexStart, 2);
			if (topDictIndexCount != 1) {
				this.data = this.original;
				return;
			}
			int topDictIndexOffsize = this.data[dictIndexStart + 2];
			int topDictIndexEndOffsetLocation = dictIndexStart + 3 + (topDictIndexOffsize * topDictIndexCount);
			int topDictIndexEndOffset = FontWriter.getUintFromByteArray(this.data, topDictIndexEndOffsetLocation, topDictIndexOffsize);

			// Decode top dict to find some key values
			int topDictDataStart = topDictIndexEndOffsetLocation + topDictIndexOffsize;
			byte[] topDict = new byte[topDictIndexEndOffset - 1];
			System.arraycopy(this.data, topDictDataStart, topDict, 0, topDictIndexEndOffset - 1);
			decodeTopDict(topDict);

			// Check charstrings found
			if (this.charstringOffset == -1) {
				this.data = this.original;
				return;
			}

			// Get data about charstrings
			int charstringsCount = FontWriter.getUintFromByteArray(this.data, this.charstringOffset, 2);
			int charstringsOffsize = this.data[this.charstringOffset + 2];
			int charstringsOffsetsStart = this.charstringOffset + 3;
			int[] charstringOffsets = new int[charstringsCount + 1];
			for (int i = 0; i < charstringsCount + 1; i++)
				charstringOffsets[i] = FontWriter.getUintFromByteArray(this.data, charstringsOffsetsStart + (charstringsOffsize * i),
						charstringsOffsize);
			int charstringDataStart = charstringsOffsetsStart + ((charstringsCount + 1) * charstringsOffsize);

			// Go through charstrings removing bad commands and tracking what changes need to be made to offsets
			int bytesRemoved = 0;
			for (int i = 0; i < charstringsCount; i++) {

				// Cast Charstring to int array so highest byte isn't negative
				int start = charstringDataStart + charstringOffsets[i] - 1;
				int end = charstringDataStart + charstringOffsets[i + 1] - 1;
				int[] c = new int[end - start];
				for (int j = 0; j < c.length; j++) {
					c[j] = this.data[start + j];

					if (c[j] < 0) c[j] += 256;
				}

				// Store pointer and lengths for moving onto next command/number
				int pointer = 0;
				int lastNumLength;
				int lastOpLength;

				while (pointer < c.length) {
					int chunk = c[pointer];

					if (chunk >= 32 && chunk <= 246) { // Number
						lastNumLength = 1;
						lastOpLength = 0;
					}
					else
						if (chunk >= 247 && chunk <= 254) { // Number
							lastNumLength = 2;
							lastOpLength = 0;
						}
						else
							if (chunk == 255) {
								lastNumLength = 5;
								lastOpLength = 0;
							}
							else
								if (chunk == 28) { // Number
									lastNumLength = 3;
									lastOpLength = 0;
								}
								else
									if (chunk == 12) { // Two byte ID's
										if (c[pointer + 1] == 0) { // dotsection command- remove
											System.arraycopy(this.data, start + pointer + 2, this.data, start + pointer, this.data.length
													- (start + pointer + 2));
											System.arraycopy(c, pointer + 2, c, pointer, c.length - (pointer + 2));
											for (int j = i + 1; j < charstringOffsets.length; j++)
												charstringOffsets[j] -= 2;
											bytesRemoved += 2;
											lastOpLength = 0;
										}
										else {
											lastOpLength = 2;
										}
										lastNumLength = 0;
									}
									else {
										lastOpLength = 1; // One byte ID's - we're not interested in these
										lastNumLength = 0;
									}

					pointer += lastOpLength + lastNumLength;
				}

			}

			// Check if anything was fixed and return if not
			if (bytesRemoved == 0) {
				this.data = this.original;
				return;
			}

			// Update the charstring offsets
			for (int i = 0; i < charstringOffsets.length; i++) {
				byte[] newNumber = FontWriter.setUintAsBytes(charstringOffsets[i], charstringsOffsize);
				System.arraycopy(newNumber, 0, this.data, charstringsOffsetsStart + (i * charstringsOffsize), charstringsOffsize);
			}

			// Update the Font DICT offset (if there is one)
			if (this.fdArrayOffset != -1 && this.fdArrayOffsetLength != -1 && this.fdArrayOffsetLocation != -1) {
				byte[] newFdArrayOffset = FontWriter.set1cNumber(this.fdArrayOffset - bytesRemoved);
				newFdArrayOffset = pad1cNumber(this.fdArrayOffsetLength, newFdArrayOffset, this.fdArrayOffset - bytesRemoved);
				System.arraycopy(newFdArrayOffset, 0, this.data, topDictDataStart + this.fdArrayOffsetLocation, this.fdArrayOffsetLength);
			}

			// Update the Private DICT offset (if there is one)
			if (this.privateOffset != -1 && this.privateOffsetLength != -1 && this.privateOffsetLocation != -1) {
				byte[] newPrivateOffset = FontWriter.set1cNumber(this.privateOffset - bytesRemoved);
				newPrivateOffset = pad1cNumber(this.privateOffsetLength, newPrivateOffset, this.privateOffset - bytesRemoved);
				System.arraycopy(newPrivateOffset, 0, this.data, topDictDataStart + this.privateOffsetLocation, this.privateOffsetLength);
			}

			// Create new array and copy data in
			byte[] oldData = this.data;
			this.data = new byte[this.data.length - bytesRemoved];
			System.arraycopy(oldData, 0, this.data, 0, this.data.length);

		}
		catch (Exception e) {
			this.data = this.original;
		}
	}

	/**
	 * Decode the Top DICT for some required values
	 * 
	 * @param dict
	 *            the dictionary
	 */
	private void decodeTopDict(byte[] dict) {

		// Cast to int array so highest byte isn't negative
		int[] d = new int[dict.length];
		for (int i = 0; i < dict.length; i++)
			d[i] = dict[i] < 0 ? dict[i] + 256 : dict[i];

		int pointer = 0;
		int lastNumLength = 0;
		int lastNum = 0;
		int lastOpLength;

		// Run through array picking out only the bits of data we need
		while (pointer < d.length) {
			int chunk = d[pointer];

			if (chunk >= 32 && chunk <= 246) { // Number
				lastNum = chunk - 139;
				lastNumLength = 1;
				lastOpLength = 0;
			}
			else
				if (chunk >= 247 && chunk <= 250) { // Number
					lastNum = ((chunk - 247) * 256) + d[pointer + 1] + 108;
					lastNumLength = 2;
					lastOpLength = 0;
				}
				else
					if (chunk >= 251 && chunk <= 254) { // Number
						lastNum = -((chunk - 251) * 256) - d[pointer + 1] - 108;
						lastNumLength = 2;
						lastOpLength = 0;
					}
					else
						if (chunk == 28) { // Number
							lastNum = FontWriter.getUintFromIntArray(d, pointer + 1, 2);
							lastNumLength = 3;
							lastOpLength = 0;
						}
						else
							if (chunk == 29) { // Number
								lastNum = FontWriter.getUintFromIntArray(d, pointer + 1, 4);
								lastNumLength = 5;
								lastOpLength = 0;
							}
							else
								if (chunk == 30) { // Number
									lastNumLength = 1;
									while (!(((d[pointer + lastNumLength] & 0xF) == 0xF) || ((d[pointer + lastNumLength] & 0x0F) == 0x0F)))
										lastNumLength++;
									lastNumLength++;
									lastOpLength = 0;
								}
								else
									if (chunk == 12) { // Two byte ID's - we're usually not interested in these
										if (d[pointer + 1] == 36) { // Font DICT - we'll probably need to change this offset so store location and
																	// length and value
											this.fdArrayOffsetLocation = pointer - lastNumLength;
											this.fdArrayOffsetLength = lastNumLength;
											this.fdArrayOffset = lastNum;
										}
										lastOpLength = 2;
										lastNumLength = 0;
									}
									else
										if (chunk == 17) { // Charstrings - we need this so we can change the strings
											this.charstringOffset = lastNum;
											lastOpLength = 1;
											lastNumLength = 0;
										}
										else
											if (chunk == 18) { // Private dict - we'll probably need to change this offset so store location and
																// length and value
												this.privateOffsetLocation = pointer - lastNumLength;
												this.privateOffsetLength = lastNumLength;
												this.privateOffset = lastNum;
												lastOpLength = 1;
												lastNumLength = 0;
											}
											else {
												lastOpLength = 1; // Other one byte command - not interested
												lastNumLength = 0;
											}

			pointer += lastOpLength + lastNumLength;
		}
	}

	/**
	 * Pads a type 1c number to take up the required number of bytes.
	 * 
	 * @param byteCount
	 *            The number of bytes required.
	 * @param currentRepresentation
	 *            The current representation of the number.
	 * @param number
	 *            The number to encode.
	 * @return The representation using the required number of bytes.
	 */
	private static byte[] pad1cNumber(int byteCount, byte[] currentRepresentation, int number) {

		// Check if already the same
		if (byteCount == currentRepresentation.length) return currentRepresentation;

		if (byteCount < currentRepresentation.length) throw new IllegalArgumentException("Trying to pad a number to a smaller size.");

		if (byteCount != 2 && byteCount != 3 && byteCount != 5) throw new IllegalArgumentException("Padding to an incorect number of bytes.");

		byte[] result;

		if (byteCount == 2) // must be padding from 1 so just append a zero beforehand
		result = new byte[] { (byte) 139, currentRepresentation[0] };

		else
			if (byteCount == 3) // Generate as 2 byte number
			result = new byte[] { 28, (byte) ((number >> 8) & 0xFF), (byte) (number & 0xFF) };

			else // byteCount must be 5 - Generate as 4 byte number
			result = new byte[] { 29, (byte) ((number >> 24) & 0xFF), (byte) ((number >> 16) & 0xFF), (byte) ((number >> 8) & 0xFF),
					(byte) (number & 0xFF) };

		return result;
	}

	/**
	 * @return the fixed font
	 */
	public byte[] getBytes() {
		return this.data;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy