Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.fontbox.cff;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fontbox.util.Charsets;
/**
* This class represents a parser for a CFF font.
* @author Villu Ruusmann
*/
public class CFFParser
{
/**
* Log instance.
*/
private static final Log LOG = LogFactory.getLog(CFFParser.class);
private static final String TAG_OTTO = "OTTO";
private static final String TAG_TTCF = "ttcf";
private static final String TAG_TTFONLY = "\u0000\u0001\u0000\u0000";
private String[] stringIndex = null;
private ByteSource source;
// for debugging only
private String debugFontName;
/**
* Source from which bytes may be read in the future.
*/
public interface ByteSource
{
/**
* Returns the source bytes. May be called more than once.
*/
byte[] getBytes() throws IOException;
}
/**
* Parse CFF font using byte array, also passing in a byte source for future use.
*
* @param bytes source bytes
* @param source source to re-read bytes from in the future
* @return the parsed CFF fonts
* @throws IOException If there is an error reading from the stream
*/
public List parse(byte[] bytes, ByteSource source) throws IOException
{
this.source = source;
return parse(bytes);
}
/**
* Parse CFF font using a byte array as input.
*
* @param bytes the given byte array
* @return the parsed CFF fonts
* @throws IOException If there is an error reading from the stream
*/
public List parse(byte[] bytes) throws IOException
{
CFFDataInput input = new CFFDataInput(bytes);
String firstTag = readTagName(input);
// try to determine which kind of font we have
if (TAG_OTTO.equals(firstTag))
{
input = createTaggedCFFDataInput(input, bytes);
}
else if (TAG_TTCF.equals(firstTag))
{
throw new IOException("True Type Collection fonts are not supported.");
}
else if (TAG_TTFONLY.equals(firstTag))
{
throw new IOException("OpenType fonts containing a true type font are not supported.");
}
else
{
input.setPosition(0);
}
@SuppressWarnings("unused")
Header header = readHeader(input);
String[] nameIndex = readStringIndexData(input);
if (nameIndex == null)
{
throw new IOException("Name index missing in CFF font");
}
byte[][] topDictIndex = readIndexData(input);
stringIndex = readStringIndexData(input);
byte[][] globalSubrIndex = readIndexData(input);
List fonts = new ArrayList(nameIndex.length);
for (int i = 0; i < nameIndex.length; i++)
{
CFFFont font = parseFont(input, nameIndex[i], topDictIndex[i]);
font.setGlobalSubrIndex(globalSubrIndex);
font.setData(source);
fonts.add(font);
}
return fonts;
}
private CFFDataInput createTaggedCFFDataInput(CFFDataInput input, byte[] bytes) throws IOException
{
// this is OpenType font containing CFF data
// so find CFF tag
short numTables = input.readShort();
@SuppressWarnings("unused")
short searchRange = input.readShort();
@SuppressWarnings("unused")
short entrySelector = input.readShort();
@SuppressWarnings("unused")
short rangeShift = input.readShort();
for (int q = 0; q < numTables; q++)
{
String tagName = readTagName(input);
@SuppressWarnings("unused")
long checksum = readLong(input);
long offset = readLong(input);
long length = readLong(input);
if ("CFF ".equals(tagName))
{
byte[] bytes2 = Arrays.copyOfRange(bytes, (int) offset, (int) (offset + length));
return new CFFDataInput(bytes2);
}
}
throw new IOException("CFF tag not found in this OpenType font.");
}
private static String readTagName(CFFDataInput input) throws IOException
{
byte[] b = input.readBytes(4);
return new String(b, Charsets.ISO_8859_1);
}
private static long readLong(CFFDataInput input) throws IOException
{
return (input.readCard16() << 16) | input.readCard16();
}
private static Header readHeader(CFFDataInput input) throws IOException
{
Header cffHeader = new Header();
cffHeader.major = input.readCard8();
cffHeader.minor = input.readCard8();
cffHeader.hdrSize = input.readCard8();
cffHeader.offSize = input.readOffSize();
return cffHeader;
}
private static int[] readIndexDataOffsets(CFFDataInput input) throws IOException
{
int count = input.readCard16();
if (count == 0)
{
return null;
}
int offSize = input.readOffSize();
int[] offsets = new int[count+1];
for (int i = 0; i <= count; i++)
{
int offset = input.readOffset(offSize);
if (offset > input.length())
{
throw new IOException("illegal offset value " + offset + " in CFF font");
}
offsets[i] = offset;
}
return offsets;
}
private static byte[][] readIndexData(CFFDataInput input) throws IOException
{
int[] offsets = readIndexDataOffsets(input);
if (offsets == null)
{
return null;
}
int count = offsets.length-1;
byte[][] indexDataValues = new byte[count][];
for (int i = 0; i < count; i++)
{
int length = offsets[i + 1] - offsets[i];
indexDataValues[i] = input.readBytes(length);
}
return indexDataValues;
}
private static String[] readStringIndexData(CFFDataInput input) throws IOException
{
int[] offsets = readIndexDataOffsets(input);
if (offsets == null)
{
return null;
}
int count = offsets.length-1;
String[] indexDataValues = new String[count];
for (int i = 0; i < count; i++)
{
int length = offsets[i + 1] - offsets[i];
if (length < 0)
{
throw new IOException("Negative index data length + " + length + " at " +
i + ": offsets[" + (i + 1) + "]=" + offsets[i + 1] +
", offsets[" + i + "]=" + offsets[i]);
}
indexDataValues[i] = new String(input.readBytes(length), Charsets.ISO_8859_1);
}
return indexDataValues;
}
private static DictData readDictData(CFFDataInput input) throws IOException
{
DictData dict = new DictData();
while (input.hasRemaining())
{
dict.add(readEntry(input));
}
return dict;
}
private static DictData readDictData(CFFDataInput input, int dictSize) throws IOException
{
DictData dict = new DictData();
int endPosition = input.getPosition() + dictSize;
while (input.getPosition() < endPosition)
{
dict.add(readEntry(input));
}
return dict;
}
private static DictData.Entry readEntry(CFFDataInput input) throws IOException
{
DictData.Entry entry = new DictData.Entry();
while (true)
{
int b0 = input.readUnsignedByte();
if (b0 >= 0 && b0 <= 21)
{
entry.operator = readOperator(input, b0);
break;
}
else if (b0 == 28 || b0 == 29)
{
entry.operands.add(readIntegerNumber(input, b0));
}
else if (b0 == 30)
{
entry.operands.add(readRealNumber(input));
}
else if (b0 >= 32 && b0 <= 254)
{
entry.operands.add(readIntegerNumber(input, b0));
}
else
{
throw new IOException("invalid DICT data b0 byte: " + b0);
}
}
return entry;
}
private static CFFOperator readOperator(CFFDataInput input, int b0) throws IOException
{
CFFOperator.Key key = readOperatorKey(input, b0);
return CFFOperator.getOperator(key);
}
private static CFFOperator.Key readOperatorKey(CFFDataInput input, int b0) throws IOException
{
if (b0 == 12)
{
int b1 = input.readUnsignedByte();
return new CFFOperator.Key(b0, b1);
}
return new CFFOperator.Key(b0);
}
private static Integer readIntegerNumber(CFFDataInput input, int b0) throws IOException
{
if (b0 == 28)
{
return (int) input.readShort();
}
else if (b0 == 29)
{
return input.readInt();
}
else if (b0 >= 32 && b0 <= 246)
{
return b0 - 139;
}
else if (b0 >= 247 && b0 <= 250)
{
int b1 = input.readUnsignedByte();
return (b0 - 247) * 256 + b1 + 108;
}
else if (b0 >= 251 && b0 <= 254)
{
int b1 = input.readUnsignedByte();
return -(b0 - 251) * 256 - b1 - 108;
}
else
{
throw new IllegalArgumentException();
}
}
private static Double readRealNumber(CFFDataInput input) throws IOException
{
StringBuilder sb = new StringBuilder();
boolean done = false;
boolean exponentMissing = false;
boolean hasExponent = false;
int[] nibbles = new int[2];
while (!done)
{
int b = input.readUnsignedByte();
nibbles[0] = b / 16;
nibbles[1] = b % 16;
for (int nibble : nibbles)
{
switch (nibble)
{
case 0x0:
case 0x1:
case 0x2:
case 0x3:
case 0x4:
case 0x5:
case 0x6:
case 0x7:
case 0x8:
case 0x9:
sb.append(nibble);
exponentMissing = false;
break;
case 0xa:
sb.append(".");
break;
case 0xb:
if (hasExponent)
{
LOG.warn("duplicate 'E' ignored after " + sb);
break;
}
sb.append("E");
exponentMissing = true;
hasExponent = true;
break;
case 0xc:
if (hasExponent)
{
LOG.warn("duplicate 'E-' ignored after " + sb);
break;
}
sb.append("E-");
exponentMissing = true;
hasExponent = true;
break;
case 0xd:
break;
case 0xe:
sb.append("-");
break;
case 0xf:
done = true;
break;
default:
// can only be a programming error because a nibble is between 0 and F
throw new IllegalArgumentException("illegal nibble " + nibble);
}
}
}
if (exponentMissing)
{
// the exponent is missing, just append "0" to avoid an exception
// not sure if 0 is the correct value, but it seems to fit
// see PDFBOX-1522
sb.append("0");
}
if (sb.length() == 0)
{
return 0d;
}
try
{
return Double.valueOf(sb.toString());
}
catch (NumberFormatException ex)
{
throw new IOException(ex);
}
}
private CFFFont parseFont(CFFDataInput input, String name, byte[] topDictIndex) throws IOException
{
// top dict
CFFDataInput topDictInput = new CFFDataInput(topDictIndex);
DictData topDict = readDictData(topDictInput);
// we don't support synthetic fonts
DictData.Entry syntheticBaseEntry = topDict.getEntry("SyntheticBase");
if (syntheticBaseEntry != null)
{
throw new IOException("Synthetic Fonts are not supported");
}
// determine if this is a Type 1-equivalent font or a CIDFont
CFFFont font;
boolean isCIDFont = topDict.getEntry("ROS") != null;
if (isCIDFont)
{
CFFCIDFont cffCIDFont = new CFFCIDFont();
DictData.Entry rosEntry = topDict.getEntry("ROS");
cffCIDFont.setRegistry(readString(rosEntry.getNumber(0).intValue()));
cffCIDFont.setOrdering(readString(rosEntry.getNumber(1).intValue()));
cffCIDFont.setSupplement(rosEntry.getNumber(2).intValue());
font = cffCIDFont;
}
else
{
font = new CFFType1Font();
}
// name
debugFontName = name;
font.setName(name);
// top dict
font.addValueToTopDict("version", getString(topDict, "version"));
font.addValueToTopDict("Notice", getString(topDict, "Notice"));
font.addValueToTopDict("Copyright", getString(topDict, "Copyright"));
font.addValueToTopDict("FullName", getString(topDict, "FullName"));
font.addValueToTopDict("FamilyName", getString(topDict, "FamilyName"));
font.addValueToTopDict("Weight", getString(topDict, "Weight"));
font.addValueToTopDict("isFixedPitch", topDict.getBoolean("isFixedPitch", false));
font.addValueToTopDict("ItalicAngle", topDict.getNumber("ItalicAngle", 0));
font.addValueToTopDict("UnderlinePosition", topDict.getNumber("UnderlinePosition", -100));
font.addValueToTopDict("UnderlineThickness", topDict.getNumber("UnderlineThickness", 50));
font.addValueToTopDict("PaintType", topDict.getNumber("PaintType", 0));
font.addValueToTopDict("CharstringType", topDict.getNumber("CharstringType", 2));
font.addValueToTopDict("FontMatrix", topDict.getArray("FontMatrix", Arrays.asList(
0.001, (double) 0, (double) 0, 0.001,
(double) 0, (double) 0)));
font.addValueToTopDict("UniqueID", topDict.getNumber("UniqueID", null));
font.addValueToTopDict("FontBBox", topDict.getArray("FontBBox",
Arrays. asList(0, 0, 0, 0)));
font.addValueToTopDict("StrokeWidth", topDict.getNumber("StrokeWidth", 0));
font.addValueToTopDict("XUID", topDict.getArray("XUID", null));
// charstrings index
DictData.Entry charStringsEntry = topDict.getEntry("CharStrings");
int charStringsOffset = charStringsEntry.getNumber(0).intValue();
input.setPosition(charStringsOffset);
byte[][] charStringsIndex = readIndexData(input);
// charset
DictData.Entry charsetEntry = topDict.getEntry("charset");
CFFCharset charset;
if (charsetEntry != null)
{
int charsetId = charsetEntry.getNumber(0).intValue();
if (!isCIDFont && charsetId == 0)
{
charset = CFFISOAdobeCharset.getInstance();
}
else if (!isCIDFont && charsetId == 1)
{
charset = CFFExpertCharset.getInstance();
}
else if (!isCIDFont && charsetId == 2)
{
charset = CFFExpertSubsetCharset.getInstance();
}
else
{
input.setPosition(charsetId);
charset = readCharset(input, charStringsIndex.length, isCIDFont);
}
}
else
{
if (isCIDFont)
{
// a CID font with no charset does not default to any predefined charset
charset = new EmptyCharset(charStringsIndex.length);
}
else
{
charset = CFFISOAdobeCharset.getInstance();
}
}
font.setCharset(charset);
// charstrings dict
font.charStrings = charStringsIndex;
// format-specific dictionaries
if (isCIDFont)
{
parseCIDFontDicts(input, topDict, (CFFCIDFont) font, charStringsIndex.length);
List privMatrix = null;
List