blob: bde365071252c3524fbd61911712c14e8b9f8828 [file] [log] [blame]
/**
*
*/
package com.google.typography.font.sfntly.table.cff;
import com.google.typography.font.sfntly.data.ReadableFontData;
import com.google.typography.font.sfntly.data.WritableFontData;
import com.google.typography.font.sfntly.table.Header;
import com.google.typography.font.sfntly.table.SubTable;
import com.google.typography.font.sfntly.table.Table;
import com.google.typography.font.sfntly.table.cff.DictTokenizer.DictEntry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author stuartg
*
*/
public class CffTable extends Table {
/*
* ID tags used to access the CFF table pieces in the Map.
*/
private static Integer CFF_HEADER_ID = new Integer(1);
private static Integer CFF_NAME_INDEX_ID = new Integer(2);
private static Integer CFF_TOP_DICT_INDEX_ID = new Integer(3);
private static Integer CFF_STRING_INDEX_ID = new Integer(4);
private static Integer CFF_GLOBAL_SUBR_INDEX_ID = new Integer(5);
private static Integer CFF_ENCODINGS_ID = new Integer(6);
private static Integer CFF_CHARSETS_ID = new Integer(7);
private static Integer CFF_FDSELECT_ID = new Integer(8);
private static Integer CFF_CHARSTRINGS_INDEX_ID = new Integer(9);
private static Integer CFF_FONT_DICT_INDEX_ID = new Integer(10);
private static Integer CFF_PRIVATE_DICT_ID = new Integer(11);
private static Integer CFF_LOCAL_SUBR_INDEX_ID = new Integer(12);
private static Integer CFF_NOTICES_ID = new Integer(13);
private Map<Integer, CffSubTable> cffPieces;
/**
* @param header
* @param data
*/
private CffTable(Header header, ReadableFontData data) {
super(header, data);
}
private Map<Integer, CffSubTable> getCffPieces() {
if (this.cffPieces == null) {
Map<Integer, CffSubTable.Builder<? extends CffSubTable>> cffPiecesBuilders =
Builder.parse(this.data);
this.cffPieces = new HashMap<Integer, CffSubTable>();
for (Map.Entry<Integer, CffSubTable.Builder<? extends CffSubTable>> entry :
cffPiecesBuilders.entrySet()) {
//System.out.println(entry);
CffSubTable subTable = entry.getValue().build();
this.cffPieces.put(entry.getKey(), subTable);
}
}
return this.cffPieces;
}
public GenericIndex nameIndex() {
return (GenericIndex) this.getCffPieces().get(CFF_NAME_INDEX_ID);
}
public TopDictIndex topDict() {
return (TopDictIndex) this.getCffPieces().get(CFF_TOP_DICT_INDEX_ID);
}
public GenericIndex stringIndex() {
return (GenericIndex) this.getCffPieces().get(CFF_STRING_INDEX_ID);
}
public GenericIndex globalSubrIndex() {
return (GenericIndex) this.getCffPieces().get(CFF_GLOBAL_SUBR_INDEX_ID);
}
public CharStringsIndex charStringsIndex() {
return (CharStringsIndex) this.getCffPieces().get(CFF_CHARSTRINGS_INDEX_ID);
}
static boolean validateOffSize(int offSize) {
if (offSize >= 1 && offSize <=4) {
return true;
}
return false;
}
static void validateOffSizeException(int offSize) {
if (offSize >= 1 && offSize <=4) {
return;
}
throw new IllegalArgumentException("Offset Size Must be between 1 and 4 inclusive.");
}
// public int majorVersion() {
//
// }
//
// public int minorVersion() {
//
// }
@Override
public String toString() {
StringBuilder sb = new StringBuilder(super.toString());
sb.append("\r");
for (CffSubTable sub : this.getCffPieces().values()) {
sb.append("\t");
sb.append(sub);
sb.append("\r");
}
return sb.toString();
}
public static class Builder extends Table.Builder<CffTable> {
private Map<Integer, CffSubTable.Builder<? extends CffSubTable>> cffPieces;
public static Builder createBuilder(Header header, ReadableFontData data) {
return new Builder(header, data);
}
public Builder(Header header, ReadableFontData data) {
super(header, data);
}
public Builder(Header header, WritableFontData data) {
super(header, data);
}
protected Builder(Header header) {
super(header);
}
private Map<Integer, ? extends CffSubTable.Builder<? extends CffSubTable>> getCffPieces() {
if (this.cffPieces == null) {
this.cffPieces = parse(this.internalReadData());
this.setModelChanged();
}
return this.cffPieces;
}
// TODO(stuartg): for testing - make private
private static Map<Integer, CffSubTable.Builder<? extends CffSubTable>>
parse(ReadableFontData data) {
Map<Integer, CffSubTable.Builder<? extends CffSubTable>> cffPieces =
new HashMap<Integer, CffSubTable.Builder<? extends CffSubTable>>();
int walkingSize = 0;
// temporary cff pieces
TopDictIndex.Builder topDictIndex;
// Start of Packed Ordered Data
//System.out.println("1 walkingSize = " + walkingSize);
// Header
{
ReadableFontData subData = data.slice(0);
int subSize = CffHeader.hdrSize(subData);
subData.bound(0, subSize);
CffHeader.Builder cffHeader = CffHeader.Builder.createBuilder(subData);
cffPieces.put(CFF_HEADER_ID, cffHeader);
walkingSize += subSize;
}
//System.out.println("2 walkingSize = " + walkingSize);
// Name Index
{
ReadableFontData subData = data.slice(walkingSize);
int subSize = IndexDataSubTable.indexTableSize(subData);
subData.bound(0, subSize);
GenericIndex.Builder nameIndex = GenericIndex.Builder.createBuilder(subData);
cffPieces.put(CFF_NAME_INDEX_ID, nameIndex);
walkingSize += subSize;
}
//System.out.println("3 walkingSize = " + walkingSize);
// Top Dict Index
{
ReadableFontData subData = data.slice(walkingSize);
int subSize = IndexDataSubTable.indexTableSize(subData);
subData.bound(0, subSize);
topDictIndex = TopDictIndex.Builder.createBuilder(subData);
cffPieces.put(CFF_TOP_DICT_INDEX_ID, topDictIndex);
walkingSize += subSize;
//System.out.println(topDictIndex);
}
//System.out.println("4 walkingSize = " + walkingSize);
// String Index
{
ReadableFontData subData = data.slice(walkingSize);
int subSize = IndexDataSubTable.indexTableSize(subData);
subData.bound(0, subSize);
GenericIndex.Builder stringIndex = GenericIndex.Builder.createBuilder(subData);
cffPieces.put(CFF_STRING_INDEX_ID, stringIndex);
walkingSize += subSize;
}
//System.out.println("5 walkingSize = " + walkingSize);
// Global Subr Index
{
ReadableFontData subData = data.slice(walkingSize);
int subSize = IndexDataSubTable.indexTableSize(subData);
subData.bound(0, subSize);
GenericIndex.Builder globalSubrIndex = GenericIndex.Builder.createBuilder(subData);
cffPieces.put(CFF_GLOBAL_SUBR_INDEX_ID, globalSubrIndex);
walkingSize += subSize;
}
//System.out.println("6 walkingSize = " + walkingSize);
// End of packed ordered data
// TODO(stuartg): validate and flag for missing pieces
// Encodings
{
Number rawSubOffset = topDictIndex.encoding();
if (rawSubOffset != null) {
int subOffset = rawSubOffset.intValue();
if (subOffset != 0) {
ReadableFontData subData = data.slice(subOffset);
int subSize = Encoding.encodingLength(subData);
subData.bound(0, subSize);
Encoding.Builder encoding = Encoding.Builder.createBuilder(subData);
cffPieces.put(CFF_ENCODINGS_ID, encoding);
}
}
}
// Charsets
// TODO(stuartg): decide if needed or not
// - difficult to calculate length
// - must parse all the data
// {
// Number rawSubOffset = this.topDictIndex.charset();
// if (rawSubOffset == null) {
// // TODO(stuartg): handle error condition
// }
// int subOffset = rawSubOffset.intValue();
// ReadableFontData subData = data.slice(subOffset);
// int subSize = Charset.charsetLength(subData);
// subData.bound(0, subSize);
// this.charset = Charset.Builder.createBuilder(subData);
// }
// FDSelect - only CIDFont
// CharStrings Index
{
Number rawSubOffset = topDictIndex.charStrings();
if (rawSubOffset != null) {
int subOffset = rawSubOffset.intValue();
if (subOffset != 0) {
ReadableFontData subData = data.slice(subOffset);
int subSize = IndexDataSubTable.indexTableSize(subData);
subData.bound(0, subSize);
CharStringsIndex.Builder charStrings = CharStringsIndex.Builder.createBuilder(subData);
cffPieces.put(CFF_CHARSTRINGS_INDEX_ID, charStrings);
}
}
}
// Font Dict Index
// Local Subr Index
// Copyright and Trademark Notices
return cffPieces;
}
public CffHeader.Builder cffHeader() {
return (CffHeader.Builder) this.getCffPieces().get(CFF_HEADER_ID);
}
public GenericIndex.Builder nameIndex() {
return (GenericIndex.Builder) this.getCffPieces().get(CFF_NAME_INDEX_ID);
}
public TopDictIndex.Builder topDict() {
return (TopDictIndex.Builder) this.getCffPieces().get(CFF_TOP_DICT_INDEX_ID);
}
public GenericIndex.Builder stringIndex() {
return (GenericIndex.Builder) this.getCffPieces().get(CFF_STRING_INDEX_ID);
}
public GenericIndex.Builder globalSubrIndex() {
return (GenericIndex.Builder) this.getCffPieces().get(CFF_GLOBAL_SUBR_INDEX_ID);
}
public CharStringsIndex.Builder charStringsIndex() {
return (CharStringsIndex.Builder) this.getCffPieces().get(CFF_CHARSTRINGS_INDEX_ID);
}
@Override
protected CffTable subBuildTable(ReadableFontData data) {
CffTable cff = new CffTable(this.header(), data);
return cff;
}
@Override
protected int subSerialize(WritableFontData newData) {
int size = 0;
int topDictOffset = -1;
int topDictLength = -1;
int charsetsOffset = -1;
int encodingsOffset = -1;
int charstringsOffset = -1;
int privateDictOffset = -1;
Set<Integer> piecesToSerialize = new HashSet<Integer>(this.getCffPieces().keySet());
System.out.printf("1 subSerialize(): size = 0x%x - %d%n", size, size);
{
CffSubTable.Builder<? extends CffSubTable> table =
this.getCffPieces().get(CFF_HEADER_ID);
if (table != null) {
// TODO(stuartg): what if it is null - build one?
piecesToSerialize.remove(CFF_HEADER_ID);
WritableFontData subData = newData.slice(size);
size += table.subSerialize(subData);
}
}
System.out.printf("2 - header subSerialize(): size = 0x%x - %d%n", size, size);
{
CffSubTable.Builder<? extends CffSubTable> table =
this.getCffPieces().get(CFF_NAME_INDEX_ID);
if (table != null) {
piecesToSerialize.remove(CFF_NAME_INDEX_ID);
WritableFontData subData = newData.slice(size);
size += table.subSerialize(subData);
}
}
System.out.printf("3 - name index subSerialize(): size = 0x%x - %d%n", size, size);
{
// first write to know the size and to reserve the space
CffSubTable.Builder<? extends CffSubTable> table =
this.getCffPieces().get(CFF_TOP_DICT_INDEX_ID);
if (table != null) {
topDictOffset = size;
piecesToSerialize.remove(CFF_TOP_DICT_INDEX_ID);
WritableFontData subData = newData.slice(size);
topDictLength = table.subSerialize(subData);
size += topDictLength;
}
}
System.out.printf("4 - top dict subSerialize(): size = 0x%x - %d%n", size, size);
{
CffSubTable.Builder<? extends CffSubTable> table =
this.getCffPieces().get(CFF_STRING_INDEX_ID);
if (table != null) {
piecesToSerialize.remove(CFF_STRING_INDEX_ID);
WritableFontData subData = newData.slice(size);
size += table.subSerialize(subData);
}
}
System.out.printf("5 - string index subSerialize(): size = 0x%x - %d%n", size, size);
{
CffSubTable.Builder<? extends CffSubTable> table =
this.getCffPieces().get(CFF_GLOBAL_SUBR_INDEX_ID);
if (table != null) {
piecesToSerialize.remove(CFF_GLOBAL_SUBR_INDEX_ID);
WritableFontData subData = newData.slice(size);
size += table.subSerialize(subData);
}
}
System.out.printf("6 - global subr subSerialize(): size = 0x%x - %d%n", size, size);
for (Integer pieceId : piecesToSerialize)
{
CffSubTable.Builder<? extends CffSubTable> table =
this.getCffPieces().get(pieceId);
if (table != null) {
if (pieceId == CFF_CHARSETS_ID) {
charsetsOffset = size;
} else if (pieceId == CFF_ENCODINGS_ID) {
encodingsOffset = size;
} else if (pieceId == CFF_CHARSTRINGS_INDEX_ID) {
charstringsOffset = size;
} else if (pieceId == CFF_PRIVATE_DICT_ID) {
privateDictOffset = size;
}
WritableFontData subData = newData.slice(size);
size += table.subSerialize(subData);
System.out.printf("pieceId=%d, subSerialize(): size = 0x%x - %d%n", pieceId, size, size);
}
}
{
// second write to hold the correct offsets
CffSubTable.Builder<? extends CffSubTable> table =
this.getCffPieces().get(CFF_TOP_DICT_INDEX_ID);
if (table != null) {
TopDictIndex.Builder tdb = (TopDictIndex.Builder) table;
if (charsetsOffset != -1) {
setAndForceFullSizeTopDictEntry(tdb, null, TopDictIndex.DICT_charset, charsetsOffset);
}
if (encodingsOffset != -1) {
setAndForceFullSizeTopDictEntry(tdb, null, TopDictIndex.DICT_Encoding, encodingsOffset);
}
if (charstringsOffset != -1) {
setAndForceFullSizeTopDictEntry(tdb, null, TopDictIndex.DICT_CharStrings, charstringsOffset);
}
if (privateDictOffset != -1) {
setAndForceFullSizeTopDictEntry(tdb, null, TopDictIndex.DICT_Private, privateDictOffset);
}
WritableFontData subData = newData.slice(topDictOffset, topDictLength);
tdb.subSerialize(subData);
}
}
return size;
}
@Override
protected boolean subReadyToSerialize() {
if (!this.changed()) {
return true;
}
// TODO(stuartg): check for existence of required tables
for (SubTable.Builder<? extends SubTable> b : this.getCffPieces().values()) {
if (!b.readyToBuild()) {
return false;
}
}
return true;
}
private boolean setAndForceFullSizeTopDictEntry(
TopDictIndex.Builder topDictBuilder, Integer pieceTag, Integer operator, int value) {
if (pieceTag == null || this.getCffPieces().containsKey(pieceTag)) {
DictEntry.Builder deb = DictEntry.Builder.createBuilder(operator, value);
deb.forceFullSizeIntegerEncoding();
topDictBuilder.setDictEntry(deb.build());
return true;
}
return false;
}
@Override
protected int subDataSizeToSerialize() {
if (!this.changed()) {
return this.internalReadData().length();
}
int size = 0;
boolean variable = false;
System.out.printf("**** subDataSizeToSerialize()%n");
//for (CffSubTable.Builder<? extends CffSubTable> b : this.getCffPieces().values()) {
for (Integer id : this.getCffPieces().keySet()) {
CffSubTable.Builder<? extends CffSubTable> b = this.getCffPieces().get(id);
if (b instanceof TopDictIndex.Builder) {
TopDictIndex.Builder tdb = (TopDictIndex.Builder) b;
// fluff up the top dict to include space for the offsets to other tables
setAndForceFullSizeTopDictEntry(
tdb, CFF_CHARSETS_ID, TopDictIndex.DICT_charset, 0);
setAndForceFullSizeTopDictEntry(
tdb, CFF_ENCODINGS_ID, TopDictIndex.DICT_Encoding, 0);
setAndForceFullSizeTopDictEntry(
tdb, CFF_CHARSTRINGS_INDEX_ID, TopDictIndex.DICT_CharStrings, 0);
setAndForceFullSizeTopDictEntry(
tdb, CFF_PRIVATE_DICT_ID, TopDictIndex.DICT_Private, 0);
// remove any pieces we don't want
// TODO(stuartg): temporary only - need to handle CID fonts
// tdb.removedDictEntry(TopDictIndex.DICT_ROS);
// tdb.removedDictEntry(TopDictIndex.DICT_CIDFontVersion);
// tdb.removedDictEntry(TopDictIndex.DICT_CIDFontRevision);
// tdb.removedDictEntry(TopDictIndex.DICT_CIDFontType);
// tdb.removedDictEntry(TopDictIndex.DICT_CIDCount);
// tdb.removedDictEntry(TopDictIndex.DICT_UIDBase);
// tdb.removedDictEntry(TopDictIndex.DICT_FDArray);
// tdb.removedDictEntry(TopDictIndex.DICT_FDSelect);
// tdb.removedDictEntry(TopDictIndex.DICT_FontName);
}
int pieceSize = b.subDataSizeToSerialize();
variable |= pieceSize <= 0;
size += Math.abs(pieceSize);
System.out.printf("id=%d, pieceSize=0x%x, variable=%b, size=%x%n", id, pieceSize, variable, size);
}
return variable ? -size : size;
}
@Override
protected void subDataSet() {
this.cffPieces = null;
}
// TODO(stuartg): remove this method - only for testing
public void makeDirty() {
this.setModelChanged();
}
}
}