/*
* 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.Collections;
import java.util.LinkedList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.fontbox.cff.CFFFont.Mapping;
import org.apache.fontbox.cff.CFFOperator;
import org.apache.fontbox.cff.charset.CFFCharset;
import org.apache.fontbox.cff.charset.CFFExpertCharset;
import org.apache.fontbox.cff.charset.CFFExpertSubsetCharset;
import org.apache.fontbox.cff.charset.CFFISOAdobeCharset;
import org.apache.fontbox.cff.encoding.CFFEncoding;
import org.apache.fontbox.cff.encoding.CFFExpertEncoding;
import org.apache.fontbox.cff.encoding.CFFStandardEncoding;
/**
* This class represents a parser for a CFF font.
* @author Villu Ruusmann
* @version $Revision: 1.0 $
*/
public class CFFParser
{
private CFFDataInput input = null;
private Header header = null;
private IndexData nameIndex = null;
private IndexData topDictIndex = null;
private IndexData stringIndex = null;
/**
* Parsing 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<CFFFont> parse(byte[] bytes) throws IOException
{
input = new CFFDataInput(bytes);
header = readHeader(input);
nameIndex = readIndexData(input);
topDictIndex = readIndexData(input);
stringIndex = readIndexData(input);
IndexData globalSubrIndex = readIndexData(input);
List<CFFFont> fonts = new ArrayList<CFFFont>();
for (int i = 0; i < nameIndex.getCount(); i++)
{
CFFFont font = parseFont(i);
font.setGlobalSubrIndex(globalSubrIndex);
fonts.add(font);
}
return fonts;
}
private static Header readHeader(CFFDataInput input) throws IOException
{
Header header = new Header();
header.major = input.readCard8();
header.minor = input.readCard8();
header.hdrSize = input.readCard8();
header.offSize = input.readOffSize();
return header;
}
private static IndexData readIndexData(CFFDataInput input)
throws IOException
{
int count = input.readCard16();
IndexData index = new IndexData(count);
if (count == 0)
{
return index;
}
int offSize = input.readOffSize();
for (int i = 0; i <= count; i++)
{
index.setOffset(i, input.readOffset(offSize));
}
int dataSize = index.getOffset(count) - index.getOffset(0);
index.initData(dataSize);
for (int i = 0; i < dataSize; i++)
{
index.setData(i, input.readCard8());
}
return index;
}
private static DictData readDictData(CFFDataInput input) throws IOException
{
DictData dict = new DictData();
dict.entries = new ArrayList<DictData.Entry>();
while (input.hasRemaining())
{
DictData.Entry entry = readEntry(input);
dict.entries.add(entry);
}
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, b0));
}
else if (b0 >= 32 && b0 <= 254)
{
entry.operands.add(readIntegerNumber(input, b0));
}
else
{
throw new IllegalArgumentException();
}
}
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)
{
int b1 = input.readUnsignedByte();
int b2 = input.readUnsignedByte();
return Integer.valueOf((short) (b1 << 8 | b2));
}
else if (b0 == 29)
{
int b1 = input.readUnsignedByte();
int b2 = input.readUnsignedByte();
int b3 = input.readUnsignedByte();
int b4 = input.readUnsignedByte();
return Integer.valueOf(b1 << 24 | b2 << 16 | b3 << 8 | b4);
}
else if (b0 >= 32 && b0 <= 246)
{
return Integer.valueOf(b0 - 139);
}
else if (b0 >= 247 && b0 <= 250)
{
int b1 = input.readUnsignedByte();
return Integer.valueOf((b0 - 247) * 256 + b1 + 108);
}
else if (b0 >= 251 && b0 <= 254)
{
int b1 = input.readUnsignedByte();
return Integer.valueOf(-(b0 - 251) * 256 - b1 - 108);
}
else
{
throw new IllegalArgumentException();
}
}
private static Double readRealNumber(CFFDataInput input, int b0)
throws IOException
{
StringBuffer sb = new StringBuffer();
boolean done = false;
while (!done)
{
int b = input.readUnsignedByte();
int[] nibbles = { b / 16, 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);
break;
case 0xa:
sb.append(".");
break;
case 0xb:
sb.append("E");
break;
case 0xc:
sb.append("E-");
break;
case 0xd:
break;
case 0xe:
sb.append("-");
break;
case 0xf:
done = true;
break;
default:
throw new IllegalArgumentException();
}
}
}
return Double.valueOf(sb.toString());
}
private CFFFont parseFont(int index) throws IOException
{
CFFFont font = null;
DataInput nameInput = new DataInput(nameIndex.getBytes(index));
String name = nameInput.getString();
CFFDataInput topDictInput = new CFFDataInput(topDictIndex.getBytes(index));
DictData topDict = readDictData(topDictInput);
DictData.Entry syntheticBaseEntry = topDict.getEntry("SyntheticBase");
if (syntheticBaseEntry != null)
{
throw new IOException("Synthetic Fonts are not supported");
}
DictData.Entry rosEntry = topDict.getEntry("ROS");
if (rosEntry != null) {
font = new CFFFontROS();
((CFFFontROS)font).setRegistry(readString(rosEntry.getNumber(0).intValue()));
((CFFFontROS)font).setOrdering(readString(rosEntry.getNumber(1).intValue()));
((CFFFontROS)font).setSupplement(rosEntry.getNumber(2).intValue());
}
if (font == null) {
// -- No specific behavior for this font
font = new CFFFont();
}
font.setName(name);
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", getBoolean(topDict, "isFixedPitch", false));
font.addValueToTopDict("ItalicAngle", getNumber(topDict, "ItalicAngle", 0));
font.addValueToTopDict("UnderlinePosition", getNumber(topDict, "UnderlinePosition", -100));
font.addValueToTopDict("UnderlineThickness", getNumber(topDict, "UnderlineThickness", 50));
font.addValueToTopDict("PaintType", getNumber(topDict, "PaintType", 0));
font.addValueToTopDict("CharstringType", getNumber(topDict, "CharstringType", 2));
font.addValueToTopDict("FontMatrix", getArray(topDict, "FontMatrix", Arrays
.<Number> asList(Double.valueOf(0.001), Double.valueOf(0),
Double.valueOf(0), Double.valueOf(0.001), Double
.valueOf(0), Double.valueOf(0))));
font.addValueToTopDict("UniqueID", getNumber(topDict, "UniqueID", null));
font.addValueToTopDict("FontBBox", getArray(topDict, "FontBBox", Arrays
.<Number> asList(Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0), Integer.valueOf(0))));
font.addValueToTopDict("StrokeWidth", getNumber(topDict, "StrokeWidth", 0));
font.addValueToTopDict("XUID", getArray(topDict, "XUID", null));
DictData.Entry charStringsEntry = topDict.getEntry("CharStrings");
int charStringsOffset = charStringsEntry.getNumber(0).intValue();
input.setPosition(charStringsOffset);
IndexData charStringsIndex = readIndexData(input);
DictData.Entry charsetEntry = topDict.getEntry("charset");
CFFCharset charset;
int charsetId = charsetEntry != null ? charsetEntry.getNumber(0)
.intValue() : 0;
if (charsetId == 0)
{
charset = CFFISOAdobeCharset.getInstance();
}
else if (charsetId == 1)
{
charset = CFFExpertCharset.getInstance();
}
else if (charsetId == 2)
{
charset = CFFExpertSubsetCharset.getInstance();
}
else
{
input.setPosition(charsetId);
charset = readCharset(input, charStringsIndex.getCount());
}
font.setCharset(charset);
font.getCharStringsDict().put(".notdef", charStringsIndex.getBytes(0));
int[] gids = new int[charStringsIndex.getCount()];
List<CFFCharset.Entry> glyphEntries = charset.getEntries();
for (int i = 1; i < charStringsIndex.getCount(); i++)
{
CFFCharset.Entry glyphEntry = glyphEntries.get(i - 1);
gids[i - 1] = glyphEntry.getSID();
font.getCharStringsDict().put(glyphEntry.getName(), charStringsIndex.getBytes(i));
}
DictData.Entry encodingEntry = topDict.getEntry("Encoding");
CFFEncoding encoding;
int encodingId = encodingEntry != null ? encodingEntry.getNumber(0).intValue() : 0;
if (encodingId == 0 || rosEntry != null) // --- ROS uses StandardEncoding
{
encoding = CFFStandardEncoding.getInstance();
}
else if (encodingId == 1)
{
encoding = CFFExpertEncoding.getInstance();
}
else
{
input.setPosition(encodingId);
encoding = readEncoding(input, gids);
}
font.setEncoding(encoding);
if (rosEntry != null) {
// ---- It is a CIDKeyed Font, The Private Dictionary isn't in the Top Dict But in the Font Dict
// ---- Font Dict can be accessed by the FDArray and FDSelect
DictData.Entry fdArrayEntry = topDict.getEntry("FDArray");
if (fdArrayEntry == null) {
throw new IOException("FDArray is missing for a CIDKeyed Font.");
}
int fontDictOffset = fdArrayEntry.getNumber(0).intValue();
input.setPosition(fontDictOffset);
IndexData fdIndex = readIndexData(input);
List<Map<String, Object>> privateDictionaries = new LinkedList<Map<String, Object>>();
List<Map<String, Object>> fontDictionaries = new LinkedList<Map<String, Object>>();
CFFFontROS fontRos = (CFFFontROS)font;
for (int i = 0; i < fdIndex.getCount(); ++i) {
byte[] b = fdIndex.getBytes(i);
CFFDataInput fontDictInput = new CFFDataInput(b);
DictData fontDictData = readDictData(fontDictInput);
Map<String, Object> fontDictMap = new LinkedHashMap<String, Object>();
fontDictMap.put("FontName", getString(fontDictData, "FontName"));
fontDictMap.put("FontType", getNumber(fontDictData, "FontType", 0));
fontDictMap.put("FontBBox", getDelta(fontDictData, "FontBBox", null));
fontDictMap.put("FontMatrix", getDelta(fontDictData, "FontMatrix", null));
// TODO OD-4 : Add here other keys
fontDictionaries.add(fontDictMap);
DictData.Entry privateEntry = fontDictData.getEntry("Private");
// --- Font DICT is invalid without "Private" entry
if ( privateEntry == null) {
throw new IOException("Missing Private Dictionary");
}
int privateOffset = privateEntry.getNumber(1).intValue();
input.setPosition(privateOffset);
int privateSize = privateEntry.getNumber(0).intValue();
CFFDataInput privateDictData = new CFFDataInput(input.readBytes(privateSize));
DictData privateDict = readDictData(privateDictData);
Map<String, Object> privDict = new LinkedHashMap<String, Object>();
privDict.put("BlueValues", getDelta(privateDict, "BlueValues", null));
privDict.put("OtherBlues", getDelta(privateDict, "OtherBlues", null));
privDict.put("FamilyBlues", getDelta(privateDict, "FamilyBlues", null));
privDict.put("FamilyOtherBlues", getDelta(privateDict, "FamilyOtherBlues", null));
privDict.put("BlueScale", getNumber(privateDict, "BlueScale", Double.valueOf(0.039625)));
privDict.put("BlueShift", getNumber(privateDict, "BlueShift", Integer.valueOf(7)));
privDict.put("BlueFuzz", getNumber(privateDict, "BlueFuzz", Integer.valueOf(1)));
privDict.put("StdHW", getNumber(privateDict, "StdHW", null));
privDict.put("StdVW", getNumber(privateDict, "StdVW", null));
privDict.put("StemSnapH", getDelta(privateDict, "StemSnapH", null));
privDict.put("StemSnapV", getDelta(privateDict, "StemSnapV", null));
privDict.put("ForceBold", getBoolean(privateDict, "ForceBold", false));
privDict.put("LanguageGroup", getNumber(privateDict, "LanguageGroup", Integer.valueOf(0)));
privDict.put("ExpansionFactor", getNumber(privateDict, "ExpansionFactor", Double.valueOf(0.06)));
privDict.put("initialRandomSeed", getNumber(privateDict, "initialRandomSeed", Integer.valueOf(0)));
privDict.put("defaultWidthX", getNumber(privateDict, "defaultWidthX", Integer.valueOf(0)));
privDict.put("nominalWidthX", getNumber(privateDict, "nominalWidthX", Integer.valueOf(0)));
int localSubrOffset = (Integer)getNumber(privateDict, "Subrs", Integer.valueOf(0));
if (localSubrOffset == 0)
{
font.setLocalSubrIndex(new IndexData(0));
}
else
{
input.setPosition(privateOffset + localSubrOffset);
font.setLocalSubrIndex(readIndexData(input));
}
privateDictionaries.add(privDict);
}
fontRos.setFontDict(fontDictionaries);
fontRos.setPrivDict(privateDictionaries);
DictData.Entry fdSelectEntry = topDict.getEntry("FDSelect");
int fdSelectPos = fdSelectEntry.getNumber(0).intValue();
input.setPosition(fdSelectPos);
CIDKeyedFDSelect fdSelect = readFDSelect(input, charStringsIndex.getCount(), fontRos);
font.addValueToPrivateDict("defaultWidthX", Integer.valueOf(1000));
font.addValueToPrivateDict("nominalWidthX", Integer.valueOf(0));
fontRos.setFdSelect(fdSelect);
} else {
DictData.Entry privateEntry = topDict.getEntry("Private");
int privateOffset = privateEntry.getNumber(1).intValue();
input.setPosition(privateOffset);
int privateSize = privateEntry.getNumber(0).intValue();
CFFDataInput privateDictData = new CFFDataInput(input.readBytes(privateSize));
DictData privateDict = readDictData(privateDictData);
font.addValueToPrivateDict("BlueValues", getDelta(privateDict, "BlueValues", null));
font.addValueToPrivateDict("OtherBlues", getDelta(privateDict, "OtherBlues", null));
font.addValueToPrivateDict("FamilyBlues", getDelta(privateDict, "FamilyBlues", null));
font.addValueToPrivateDict("FamilyOtherBlues", getDelta(privateDict, "FamilyOtherBlues", null));
font.addValueToPrivateDict("BlueScale", getNumber(privateDict, "BlueScale", Double.valueOf(0.039625)));
font.addValueToPrivateDict("BlueShift", getNumber(privateDict, "BlueShift", Integer.valueOf(7)));
font.addValueToPrivateDict("BlueFuzz", getNumber(privateDict, "BlueFuzz", Integer.valueOf(1)));
font.addValueToPrivateDict("StdHW", getNumber(privateDict, "StdHW", null));
font.addValueToPrivateDict("StdVW", getNumber(privateDict, "StdVW", null));
font.addValueToPrivateDict("StemSnapH", getDelta(privateDict, "StemSnapH", null));
font.addValueToPrivateDict("StemSnapV", getDelta(privateDict, "StemSnapV", null));
font.addValueToPrivateDict("ForceBold", getBoolean(privateDict, "ForceBold", false));
font.addValueToPrivateDict("LanguageGroup", getNumber(privateDict, "LanguageGroup", Integer.valueOf(0)));
font.addValueToPrivateDict("ExpansionFactor", getNumber(privateDict, "ExpansionFactor", Double.valueOf(0.06)));
font.addValueToPrivateDict("initialRandomSeed", getNumber(privateDict, "initialRandomSeed", Integer.valueOf(0)));
font.addValueToPrivateDict("defaultWidthX", getNumber(privateDict, "defaultWidthX", Integer.valueOf(0)));
font.addValueToPrivateDict("nominalWidthX", getNumber(privateDict, "nominalWidthX", Integer.valueOf(0)));
int localSubrOffset = (Integer)getNumber(privateDict, "Subrs", Integer.valueOf(0));
if (localSubrOffset == 0)
{
font.setLocalSubrIndex(new IndexData(0));
}
else
{
input.setPosition(privateOffset + localSubrOffset);
font.setLocalSubrIndex(readIndexData(input));
}
}
return font;
}
private String readString(int index) throws IOException
{
if (index >= 0 && index <= 390)
{
return CFFStandardString.getName(index);
}
if (index - 391 <= stringIndex.getCount()) {
DataInput dataInput = new DataInput(stringIndex.getBytes(index - 391));
return dataInput.getString();
}
else {
return CFFStandardString.getName(0);
}
}
private String getString(DictData dict, String name) throws IOException
{
DictData.Entry entry = dict.getEntry(name);
return (entry != null ? readString(entry.getNumber(0).intValue()) : null);
}
private Boolean getBoolean(DictData dict, String name, boolean defaultValue) throws IOException
{
DictData.Entry entry = dict.getEntry(name);
return entry != null ? entry.getBoolean(0) : defaultValue;
}
private Number getNumber(DictData dict, String name, Number defaultValue) throws IOException
{
DictData.Entry entry = dict.getEntry(name);
return entry != null ? entry.getNumber(0) : defaultValue;
}
// TODO Where is the difference to getDelta??
private List<Number> getArray(DictData dict, String name, List<Number> defaultValue) throws IOException
{
DictData.Entry entry = dict.getEntry(name);
return entry != null ? entry.getArray() : defaultValue;
}
// TODO Where is the difference to getArray??
private List<Number> getDelta(DictData dict, String name, List<Number> defaultValue) throws IOException
{
DictData.Entry entry = dict.getEntry(name);
return entry != null ? entry.getArray() : defaultValue;
}
private CFFEncoding readEncoding(CFFDataInput dataInput, int[] gids)
throws IOException
{
int format = dataInput.readCard8();
int baseFormat = format & 0x7f;
if (baseFormat == 0)
{
return readFormat0Encoding(dataInput, format, gids);
}
else if (baseFormat == 1)
{
return readFormat1Encoding(dataInput, format, gids);
}
else
{
throw new IllegalArgumentException();
}
}
private Format0Encoding readFormat0Encoding(CFFDataInput dataInput, int format,
int[] gids) throws IOException
{
Format0Encoding encoding = new Format0Encoding();
encoding.format = format;
encoding.nCodes = dataInput.readCard8();
encoding.code = new int[encoding.nCodes];
for (int i = 0; i < encoding.code.length; i++)
{
encoding.code[i] = dataInput.readCard8();
encoding.register(encoding.code[i], gids[i]);
}
if ((format & 0x80) != 0)
{
readSupplement(dataInput, encoding);
}
return encoding;
}
private Format1Encoding readFormat1Encoding(CFFDataInput dataInput, int format,
int[] gids) throws IOException
{
Format1Encoding encoding = new Format1Encoding();
encoding.format = format;
encoding.nRanges = dataInput.readCard8();
int count = 0;
encoding.range = new Format1Encoding.Range1[encoding.nRanges];
for (int i = 0; i < encoding.range.length; i++)
{
Format1Encoding.Range1 range = new Format1Encoding.Range1();
range.first = dataInput.readCard8();
range.nLeft = dataInput.readCard8();
encoding.range[i] = range;
for (int j = 0; j < 1 + range.nLeft; j++)
{
encoding.register(range.first + j, gids[count + j]);
}
count += 1 + range.nLeft;
}
if ((format & 0x80) != 0)
{
readSupplement(dataInput, encoding);
}
return encoding;
}
private void readSupplement(CFFDataInput dataInput, EmbeddedEncoding encoding)
throws IOException
{
encoding.nSups = dataInput.readCard8();
encoding.supplement = new EmbeddedEncoding.Supplement[encoding.nSups];
for (int i = 0; i < encoding.supplement.length; i++)
{
EmbeddedEncoding.Supplement supplement = new EmbeddedEncoding.Supplement();
supplement.code = dataInput.readCard8();
supplement.glyph = dataInput.readSID();
encoding.supplement[i] = supplement;
}
}
/**
* Read the FDSelect Data according to the format.
* @param dataInput
* @param nGlyphs
* @param ros
* @return
* @throws IOException
*/
private CIDKeyedFDSelect readFDSelect(CFFDataInput dataInput, int nGlyphs, CFFFontROS ros)
throws IOException
{
int format = dataInput.readCard8();
if (format == 0)
{
return readFormat0FDSelect(dataInput, format, nGlyphs, ros);
}
else if (format == 3)
{
return readFormat3FDSelect(dataInput, format, nGlyphs, ros);
}
else
{
throw new IllegalArgumentException();
}
}
/**
* Read the Format 0 of the FDSelect data structure.
* @param dataInput
* @param format
* @param nGlyphs
* @param ros
* @return
* @throws IOException
*/
private Format0FDSelect readFormat0FDSelect(CFFDataInput dataInput, int format, int nGlyphs, CFFFontROS ros) throws IOException
{
Format0FDSelect fdselect = new Format0FDSelect(ros);
fdselect.format = format;
fdselect.fds = new int[nGlyphs];
for (int i = 0; i < fdselect.fds.length; i++)
{
fdselect.fds[i] = dataInput.readCard8();
}
return fdselect;
}
/**
* Read the Format 3 of the FDSelect data structure.
*
* @param dataInput
* @param format
* @param nGlyphs
* @param ros
* @return
* @throws IOException
*/
private Format3FDSelect readFormat3FDSelect(CFFDataInput dataInput, int format, int nGlyphs, CFFFontROS ros) throws IOException
{
Format3FDSelect fdselect = new Format3FDSelect(ros);
fdselect.format = format;
fdselect.nbRanges = dataInput.readCard16();
fdselect.range3 = new Range3[fdselect.nbRanges];
for (int i = 0; i < fdselect.nbRanges; i++)
{
Range3 r3 = new Range3();
r3.first = dataInput.readCard16();
r3.fd = dataInput.readCard8();
fdselect.range3[i] = r3;
}
fdselect.sentinel = dataInput.readCard16();
return fdselect;
}
/**
* Container of a Format 3 FDSelect data (see "The Compact Font Format Specification" chapter "FDSelect" )
*/
private static class Format3FDSelect extends CIDKeyedFDSelect {
private int format;
private int nbRanges;
private Range3[] range3;
private int sentinel;
private Format3FDSelect(CFFFontROS _owner) {
super(_owner);
}
/* (non-Javadoc)
* @see org.apache.fontbox.cff.CIDKeyedFDSelect#getFd(int)
*/
@Override
public int getFd(int glyph) {
for (int i = 0 ; i < nbRanges; ++i) {
if (range3[i].first >= glyph) {
if (i + 1 < nbRanges ) {
if (range3[i+1].first > glyph ) {
return range3[i].fd;
} else {
// go to next range
}
} else {
// last range reach, the sentinel must be greater than glyph
if (sentinel > glyph) {
return range3[i].fd;
} else {
return -1;
}
}
}
}
return 0;
}
@Override
public String toString()
{
return getClass().getName() + "[format=" + format + " nbRanges=" + nbRanges + ", range3="
+ Arrays.toString(range3) + " sentinel=" + sentinel + "]";
}
}
/**
* Structure of a Range3 element
*/
private static class Range3 {
private int first;
private int fd;
@Override
public String toString() {
return getClass().getName() + "[first=" + first + ", fd=" + fd + "]";
}
}
/**
* Container of a Format 0 FDSelect data (see "The Compact Font Format Specification" chapter "FDSelect" )
*/
private static class Format0FDSelect extends CIDKeyedFDSelect {
private int format;
private int[] fds;
private Format0FDSelect(CFFFontROS _owner) {
super(_owner);
}
/* (non-Javadoc)
* @see org.apache.fontbox.cff.CIDKeyedFDSelect#getFd(int)
*/
@Override
public int getFd(int glyph) {
// ---- search the position of the given glyph
for (Mapping mapping: this.owner.getMappings()) {
if (mapping.getSID() == glyph) {
int index = 0;
Map<String, byte[]> charString = this.owner.getCharStringsDict();
Set<String> keys = charString.keySet();
for (String str : keys) {
if (mapping.getName().equals(str)) {
return fds[index];
}
++index;
}
}
}
return -1;
}
@Override
public String toString()
{
return getClass().getName() + "[format=" + format + ", fds="
+ Arrays.toString(fds) + "]";
}
}
private CFFCharset readCharset(CFFDataInput dataInput, int nGlyphs)
throws IOException
{
int format = dataInput.readCard8();
if (format == 0)
{
return readFormat0Charset(dataInput, format, nGlyphs);
}
else if (format == 1)
{
return readFormat1Charset(dataInput, format, nGlyphs);
}
else if (format == 2)
{
return readFormat2Charset(dataInput, format, nGlyphs);
}
else
{
throw new IllegalArgumentException();
}
}
private Format0Charset readFormat0Charset(CFFDataInput dataInput, int format,
int nGlyphs) throws IOException
{
Format0Charset charset = new Format0Charset();
charset.format = format;
charset.glyph = new int[nGlyphs - 1];
for (int i = 0; i < charset.glyph.length; i++)
{
charset.glyph[i] = dataInput.readSID();
charset.register(charset.glyph[i], readString(charset.glyph[i]));
}
return charset;
}
private Format1Charset readFormat1Charset(CFFDataInput dataInput, int format,
int nGlyphs) throws IOException
{
Format1Charset charset = new Format1Charset();
charset.format = format;
List<Format1Charset.Range1> ranges = new ArrayList<Format1Charset.Range1>();
for (int i = 0; i < nGlyphs - 1;)
{
Format1Charset.Range1 range = new Format1Charset.Range1();
range.first = dataInput.readSID();
range.nLeft = dataInput.readCard8();
ranges.add(range);
for (int j = 0; j < 1 + range.nLeft; j++)
{
charset.register(range.first + j, readString(range.first + j));
}
i += 1 + range.nLeft;
}
charset.range = ranges.toArray(new Format1Charset.Range1[0]);
return charset;
}
private Format2Charset readFormat2Charset(CFFDataInput dataInput, int format,
int nGlyphs) throws IOException
{
Format2Charset charset = new Format2Charset();
charset.format = format;
charset.range = new Format2Charset.Range2[0];
for (int i = 0; i < nGlyphs - 1;)
{
Format2Charset.Range2[] newRange = new Format2Charset.Range2[charset.range.length + 1];
System.arraycopy(charset.range, 0, newRange, 0,
charset.range.length);
charset.range = newRange;
Format2Charset.Range2 range = new Format2Charset.Range2();
range.first = dataInput.readSID();
range.nLeft = dataInput.readCard16();
charset.range[charset.range.length - 1] = range;
for (int j = 0; j < 1 + range.nLeft; j++)
{
charset.register(range.first + j, readString(range.first + j));
}
i += 1 + range.nLeft;
}
return charset;
}
/**
* Inner class holding the header of a CFF font.
*/
private static class Header
{
private int major;
private int minor;
private int hdrSize;
private int offSize;
@Override
public String toString()
{
return getClass().getName() + "[major=" + major + ", minor="
+ minor + ", hdrSize=" + hdrSize + ", offSize=" + offSize
+ "]";
}
}
/**
* Inner class holding the DictData of a CFF font.
*/
private static class DictData
{
private List<Entry> entries = null;
public Entry getEntry(CFFOperator.Key key)
{
return getEntry(CFFOperator.getOperator(key));
}
public Entry getEntry(String name)
{
return getEntry(CFFOperator.getOperator(name));
}
private Entry getEntry(CFFOperator operator)
{
for (Entry entry : entries)
{
// Check for null entry before comparing the Font
if (entry != null && entry.operator != null &&
entry.operator.equals(operator))
{
return entry;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public String toString()
{
return getClass().getName() + "[entries=" + entries + "]";
}
/**
* Inner class holding an operand of a CFF font.
*/
private static class Entry
{
private List<Number> operands = new ArrayList<Number>();
private CFFOperator operator = null;
public Number getNumber(int index)
{
return operands.get(index);
}
public Boolean getBoolean(int index)
{
Number operand = operands.get(index);
if (operand instanceof Integer)
{
switch (operand.intValue())
{
case 0:
return Boolean.FALSE;
case 1:
return Boolean.TRUE;
default:
break;
}
}
throw new IllegalArgumentException();
}
// TODO unused??
public Integer getSID(int index)
{
Number operand = operands.get(index);
if (operand instanceof Integer)
{
return (Integer) operand;
}
throw new IllegalArgumentException();
}
// TODO Where is the difference to getDelta??
public List<Number> getArray()
{
return operands;
}
// TODO Where is the difference to getArray??
public List<Number> getDelta()
{
return operands;
}
@Override
public String toString()
{
return getClass().getName() + "[operands=" + operands
+ ", operator=" + operator + "]";
}
}
}
/**
* Inner class representing an embedded CFF encoding.
*/
abstract static class EmbeddedEncoding extends CFFEncoding
{
private int nSups;
private Supplement[] supplement;
@Override
public boolean isFontSpecific()
{
return true;
}
List<Supplement> getSupplements()
{
if(supplement == null){
return Collections.<Supplement>emptyList();
}
return Arrays.asList(supplement);
}
/**
* Inner class representing a supplement for an encoding.
*/
static class Supplement
{
private int code;
private int glyph;
int getCode(){
return code;
}
int getGlyph(){
return glyph;
}
@Override
public String toString()
{
return getClass().getName() + "[code=" + code + ", glyph="
+ glyph + "]";
}
}
}
/**
* Inner class representing a Format0 encoding.
*/
private static class Format0Encoding extends EmbeddedEncoding
{
private int format;
private int nCodes;
private int[] code;
@Override
public String toString()
{
return getClass().getName() + "[format=" + format + ", nCodes="
+ nCodes + ", code=" + Arrays.toString(code)
+ ", supplement=" + Arrays.toString(super.supplement) + "]";
}
}
/**
* Inner class representing a Format1 encoding.
*/
private static class Format1Encoding extends EmbeddedEncoding
{
private int format;
private int nRanges;
private Range1[] range;
@Override
public String toString()
{
return getClass().getName() + "[format=" + format + ", nRanges="
+ nRanges + ", range=" + Arrays.toString(range)
+ ", supplement=" + Arrays.toString(super.supplement) + "]";
}
/**
* Inner class representing a range of an encoding.
*/
private static class Range1
{
private int first;
private int nLeft;
@Override
public String toString()
{
return getClass().getName() + "[first=" + first + ", nLeft="
+ nLeft + "]";
}
}
}
/**
* Inner class representing an embedded CFF charset.
*/
abstract static class EmbeddedCharset extends CFFCharset
{
@Override
public boolean isFontSpecific()
{
return true;
}
}
/**
* Inner class representing a Format0 charset.
*/
private static class Format0Charset extends EmbeddedCharset
{
private int format;
private int[] glyph;
@Override
public String toString()
{
return getClass().getName() + "[format=" + format + ", glyph="
+ Arrays.toString(glyph) + "]";
}
}
/**
* Inner class representing a Format1 charset.
*/
private static class Format1Charset extends EmbeddedCharset
{
private int format;
private Range1[] range;
@Override
public String toString()
{
return getClass().getName() + "[format=" + format + ", range="
+ Arrays.toString(range) + "]";
}
/**
* Inner class representing a range of a charset.
*/
private static class Range1
{
private int first;
private int nLeft;
@Override
public String toString()
{
return getClass().getName() + "[first=" + first + ", nLeft="
+ nLeft + "]";
}
}
}
/**
* Inner class representing a Format2 charset.
*/
private static class Format2Charset extends EmbeddedCharset
{
private int format;
private Range2[] range;
@Override
public String toString()
{
return getClass().getName() + "[format=" + format + ", range="
+ Arrays.toString(range) + "]";
}
/**
* Inner class representing a range of a charset.
*/
private static class Range2
{
private int first;
private int nLeft;
@Override
public String toString()
{
return getClass().getName() + "[first=" + first + ", nLeft="
+ nLeft + "]";
}
}
}
}