com.adobe.fontengine.font.cff.CFFSubrize Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
/*
T2 subroutinizer.
The idea of the subroutinizer is to reduce the size of a font by replacing all
repeated charstring code in a font by references to a single instance of that
code. This idea may be extended to a number of fonts in a FontSet where
repeated charstring code in different fonts is replaced. The single instance of
charstring code is stored as a subroutine and is assigned a unique subroutine
number that may be used to indentify it.
In CFF technology charstring repeats that occur within a single font or across
multiple fonts are stored in 2 separate data structures and are known as the
"local" and "global" subroutines, repectively. Local and global subroutines
are referenced by calling them with the callsubr or callgsubr operators,
respectively. These operators take a subroutine number, which serves to
identify the subroutine, as an argument. The local and global subroutine number
spaces identify different sets of subroutines, therefore local subroutine
number 1 is distinct from global subroutine number 1.
...
The most challenging part of the process of subroutinization is finding and
counting the repeated charstrings. This is achieved by first building a suffix
CDAWG using the concatenation of all the charstrings from all the fonts as
input. Subseqeuntly the completed suffix CDAWG is traversed in order to count
The suffix CDAWG is built as a compact CDAWG (CDAWG) using an algorithm described in paper
"On-Line Construction of Compact Directed Acyclic Word Graphs" (200), S. Inenaga, et. al.
which is an extension
to the compact suffix tree construction algorithm "On-line construction of suffix trees"
(1995) by Esko Ukkonen
The original code used a DAWG algorithm described in "Text Algorithms, Maxime Crochemore
and Wojciech Ryter, OUP" p.113. CDAWG is expected to use less memory than DAWG.
...
regular.saved = count * (length - call - num) - (off + length + ret);
tail.saved = count * (length - call - num) - (off + length);
Bytes saved for various counts and lengths.
Following table calculated with the following parameters:
callsubr 1
subr# 1
offset 2
return 1
| length
| 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| +------------------------------------------------------------------------
| 2| -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13
| 3| -3 -1 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31
| c 4| -2 1 4 7 10 13 16 19 22 25 28 31 34 37 40 43 46 49
| o 5| -1 3 7 11 15 19 23 27 31 35 39 43 47 51 55 59 63 67
| u 6| 0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85
| n 7| 1 7 13 19 25 31 37 43 49 55 61 67 73 79 85 91 97 103
| t 8| 2 9 16 23 30 37 44 51 58 65 72 79 86 93 100 107 114 121
| 9| 3 11 19 27 35 43 51 59 67 75 83 91 99 107 115 123 131 139
| 10| 4 13 22 31 40 49 58 67 76 85 94 103 112 121 130 139 148 157
| +------------------------------------------------------------------------
Following table calculated with the following parameters:
callsubr 1
subr# 2
offset 2
return 1
| length
| 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| +------------------------------------------------------------------------
| 2| -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10 11
| 3| -6 -4 -2 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28
| c 4| -6 -3 0 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45
| o 5| -6 -2 2 6 10 14 18 22 26 30 34 38 42 46 50 54 58 62
| u 6| -6 -1 4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79
| n 7| -6 0 6 12 18 24 30 36 42 48 54 60 66 72 78 84 90 96
| t 8| -6 1 8 15 22 29 36 43 50 57 64 71 78 85 92 99 106 113
| 9| -6 2 10 18 26 34 42 50 58 66 74 82 90 98 106 114 122 130
| 10| -6 3 12 21 30 39 48 57 66 75 84 93 102 111 120 129 138 147
| +------------------------------------------------------------------------
*/
package com.adobe.fontengine.font.cff;
import java.util.ArrayList;
import java.util.Collections;
import com.adobe.fontengine.font.cff.CFFByteArray.CFFByteArrayBuilder;
public class CFFSubrize
{
/* Constants */
private static final int MAX_CALL_LIST_COUNT = 3; /* Maximum number of call list candidates buildCallList creates */
private static final int NODE_GLOBAL = 0x7FFF; /* Identifies global subr */
private static final int EDGE_TABLE_SMALLEST_SPARSE_SIZE = 128; /* Double the hash table size even before it becomes full beyond this size */
private static final int EDGE_TABLE_SIZE_USE_SIMPLE_HASH = 16; /* Use a better hash function for edges if the table size is larger than this */
private static final int SUBR_TABLE_SIZE_USE_SIMPLE_HASH = 1 << 18; /* Use a better hash function for subrs if the table size is larger than this */
private static final int SUBR_HASH_SAMPLE_COUNT = 8; /* Sample this many bytes from a subr in the better hash function */
private static final int SUBR_PREFIX_MAP_SIZE = (1 << (2 * 8)) / 8;
private static final int CALL_OP_SIZE = 1; /* Size of call(g)subr (bytes) */
private static final int LISTSIZE = 4000;
private static final int TX_MAX_CALL_STACK = 10; /* Max callsubr stack depth */
private static final int T2_SEPARATOR = 9;
private static final int TX_CALLSUBR = 10;
private static final int TX_RETURN = 11;
private static final int TX_ESCAPE = 12;
private static final int TX_ENDCHAR = 14;
private static final int T2_HINTMASK = 19;
private static final int T2_CNTRMASK = 20;
private static final int T2_SHORTINT = 28;
private static final int T2_CALLGSUBR = 29;
private static final int CFF_LONGINT = 29; /* Context distinguishes this from T2_CALLGSUBR */
private static final int SORT_MODE_LOCAL = 0;
private static final int SORT_MODE_GLOBAL = 1;
private static final int SORT_MODE_FITNESS = 2;
/* ------------------------------- CDAWG data ------------------------------- */
static private final class Edge
{
int label; /* Index to the beginning of the edge string in byte array */
Node son; /* Son node. red/black color is encoded as the LSB in this pointer */
int length; /* Length of the edge string */
};
// static private int mNodeID;
static private final class Node
{
Node suffix; /* Suffix link */
Edge edgeTable[]; /* Pointer to the edge table */
int misc; /* Initially longest path from root, then subr index */
int edgeCount; /* Number of edges from this node */
int edgeTableSize; /* Number of entries allocated in the edge table */
short paths; /* Paths through node */
short id; /* Font id */
boolean counted;
boolean tested;
boolean tail;
// int nodeSN;
private Node(int length, int fid)
{
// System.out.println("Node: " + length);
misc = length;
id = (short)fid;
// nodeSN = ++mNodeID;
}
};
/* ------------------------------- Subr data ------------------------------- */
private final class Subr implements Comparable
{
Node node; /* Associated node */
Link sups; /* Superior subrs */
Link infs; /* Inferior subrs */
Subr next; /* Next member of social group */
int cstr; /* Charstring index */
short length; /* Subr length (original bytes spanned) */
short count; /* Occurance count */
short deltalen; /* Delta length */
short subrnum; /* Biased subr number */
short numsize; /* Size of subr number (1, 2, or 3 bytes) */
short maskcnt; /* hint/cntrmask count */
short misc; /* subrSaved value/call depth (transient) */
boolean select;
boolean reject;
boolean member;
/**
* Note: this class has a natural ordering that is inconsistent with equals.
* The hashCode and equals declarations below are to keep FindBugs happy. It doesn't like having
* these default to the Object definitions because, in fact, we do NOT guarantee here that objects
* for which compareTo returns 0 will return true for equals. But we only ever sort ArrayList
* objects in this module.
*/
public int hashCode()
{
return super.hashCode();
}
public boolean equals(Object obj)
{
return super.equals(obj);
}
public int compareTo(Object obj)
{
if (mSubrSortMode == SORT_MODE_LOCAL)
return cmpLocalSetSubrs(this, (Subr)obj);
else if (mSubrSortMode == SORT_MODE_GLOBAL)
return cmpGlobalSetSubrs(this, (Subr)obj);
return cmpSubrFitness(this, (Subr)obj);
}
};
static private final class Link /* Social group link */
{
Subr subr; /* Superior/inferior subr */
Link next; /* Next record */
int offset; /* Offset within superior/inferior */
private Link(Subr s, int o, Link n)
{
subr = s;
offset = o;
next = n;
}
};
static private final class Call /* Subr call within charstring */
{
Subr subr; /* Inferior subr */
int offset; /* Offset within charstring */
private Call(Subr s, int o) {
subr = s;
offset = o;
}
};
static private final class SubrCSData
{
int nStrings;
int offset[]; /* Offset (index) array */
byte data[]; /* Charstring data buffer */
};
static private final class SubrFDInfo
{
SubrCSData subrs = new SubrCSData();
SubrCSData chars = new SubrCSData(); /* Temporary subroutinized charstrings for DICT */
int iChar; /* Index of next char to be copied */
};
static private final class SubrFont
{
boolean subrFontCID; /* Identifies CID fonts */
byte fdIndex[]; /* CID: Map each glyph into fdInfo */
int fdCount; /* CID: Number of font & private dicts */
SubrFDInfo fdInfo[]; /* CID: [fdCount] */
SubrCSData subrs = new SubrCSData(); /* Subr data (non-CID) */
SubrCSData chars = new SubrCSData(); /* Char data */
};
static private final class RefPair /* Reference pair return information */
{
Node node; /* The updated node */
int index; /* The updated character index */
};
/* Class variable declarations */
private byte mCharStr[]; /* Current font's char string */
private Node mRoot; /* CDAWG root */
private Node mBase; /* CDAWG base */
private ArrayList mSinks; /* Of "Node". CDAWG sinks (one for each font id) */
private Edge mBaseEdge; /* dummy edge from base to root */
private ArrayList mSubrs; /* Of "Subr". Subr list (all) */
private ArrayList mTmp; /* Of "Subr". Temporary subr list */
private Subr mReorder[]; /* Reordered subrs */
private ArrayList mCalls; /* Of "Call". Temporary subr call accumulator */
private ArrayList mMembers; /* Of "Subr". Temporary social group member accumulator */
private ArrayList mLeaders; /* Of "Subr". Social group leaders */
private boolean mSingleton; /* Single font in font set */
private int mOffSize; /* Subr INDEX offset size */
private boolean mSubrStackOvl; /* Subr stack overflow */
private SubrCSData mGSubrs; /* Global subrs */
private int mNumFonts; /* Font count */
private SubrFont mFonts[]; /* Font list */
private byte mOpLenCache[]; /* Cached values of t2oplen. If 0, the second byte in the charstring indicates the length */
private Subr mSubrHash[]; /* Subr hash table */
private byte mSubrPrefixMap[]; /* bit table where a bit is set when its corresponding subr 2-byte prefix selected */
private short mSubrLenMap[]; /* boolean table where a value is set when any subr with the corresponding length is selected */
private short mPrefixLen[]; /* Prefix byte length for each byte in a charstring */
private int mMaxSubrLen; /* Maximum subr length */
private int mMinSubrLen; /* Minimum subr lenth */
private int mMaxNumSubrs; /* Maximum number of subroutines (0 means default 65536) */
private int mSubrSortMode; /* Subr sort method to use */
/* Public methods */
public CFFSubrize()
{
mOffSize = 2;
mSubrPrefixMap = new byte[SUBR_PREFIX_MAP_SIZE];
}
public CharStrings subrize(CharStrings csi)
{
return subrize(csi, null);
}
public CharStrings subrize(CharStrings csi, byte fdIndex[])
{
int nStrings = csi.getCount();
int offsets[] = new int[nStrings];
int cBase = csi.offsetOf(0);
int cEnd = csi.size();
int cLen = cEnd - cBase;
for (int i = 0; i < nStrings; i++) {
offsets[i] = csi.offsetOf(i + 1) - cBase;
}
byte cstrs[] = csi.data.getBytes(cBase, cLen);
byte newCstrs[] = new byte[cstrs.length + 4*nStrings];
int src = 0;
int dst = 0;
for (int i = 0; i < nStrings; i++) {
int length = offsets[i] - src;
System.arraycopy(cstrs, src, newCstrs, dst, length);
src += length;
dst += length;
newCstrs[dst++] = T2_SEPARATOR;
newCstrs[dst++] = (byte)(i >> 16);
newCstrs[dst++] = (byte)(i >> 8);
newCstrs[dst++] = (byte)i;
offsets[i] = dst;
}
cstrs = newCstrs;
nStrings = subrizeRaw(nStrings, offsets, cstrs, fdIndex);
offsets = getMainOffsets();
cstrs = getMainData();
return buildIndex(nStrings, offsets, cstrs);
}
public CharStrings getLSubrs()
{
int nStrings = getLSubrCount();
if (nStrings == 0)
return null;
return buildIndex(nStrings, getLSubrOffsets(), getLSubrData());
}
public CharStrings getLSubrs(int fdIndex)
{
if (mFonts[0].fdInfo == null || fdIndex >= mFonts[0].fdInfo.length)
return null;
SubrCSData csData = mFonts[0].fdInfo[fdIndex].subrs;
if (csData.nStrings == 0)
return null;
return buildIndex(csData.nStrings, csData.offset, csData.data);
}
public CharStrings getGSubrs()
{
int nStrings = getGSubrCount();
if (nStrings == 0)
return null;
return buildIndex(nStrings, getGSubrOffsets(), getGSubrData());
}
public int subrizeRaw(int nStrings, int offsets[], byte cstrs[], byte fdIndex[])
{
SubrFont fonts[] = new SubrFont[1];
SubrFont font = new SubrFont();
font.chars.nStrings = nStrings;
font.chars.offset = offsets;
font.chars.data = cstrs;
if (fdIndex != null) {
font.subrFontCID = true;
font.fdIndex = fdIndex;
int fdMax = 0;
for (int i = 0; i < fdIndex.length; i++) {
if (fdIndex[i] > fdMax) {
fdMax = fdIndex[i];
}
}
font.fdCount = fdMax + 1;
font.fdInfo = new SubrFDInfo[fdMax + 1];
for (int i = 0; i <= fdMax; i++) {
font.fdInfo[i] = new SubrFDInfo();
}
}
fonts[0] = font;
cfwSubrSubrize(1, fonts);
return font.chars.nStrings;
}
public int[] getMainOffsets()
{
return mFonts[0].chars.offset;
}
public byte[] getMainData()
{
return mFonts[0].chars.data;
}
public int getGSubrCount()
{
return (mGSubrs == null) ? 0 : mGSubrs.nStrings;
}
public int[] getGSubrOffsets()
{
return (mGSubrs == null) ? null : mGSubrs.offset;
}
public byte[] getGSubrData()
{
return (mGSubrs == null) ? null : mGSubrs.data;
}
public int getLSubrCount()
{
return mFonts[0].subrs.nStrings;
}
public int[] getLSubrOffsets()
{
return mFonts[0].subrs.offset;
}
public byte[] getLSubrData()
{
return mFonts[0].subrs.data;
}
private CharStrings buildIndex(int nStrings, int offsets[], byte cstrs[])
{
int offsetSize;
int totalSize = cstrs.length + 1;
CFFByteArrayBuilder builder = CFFByteArray.getCFFByteArrayBuilderInstance();
builder.addCard16(nStrings);
if (totalSize > 0x00ffffff) {
offsetSize = 4;
} else if (totalSize > 0x0000ffff) {
offsetSize = 3;
} else if (totalSize > 0x000000ff) {
offsetSize = 2;
} else {
offsetSize = 1;
}
builder.addCard8(offsetSize);
builder.addOffset(offsetSize, 1);
for (int i = 0; i < offsets.length; i++) {
builder.addOffset(offsetSize, offsets[i] + 1);
}
builder.addBytes(cstrs, 0, totalSize - 1);
try {
return new CharStrings(builder.toCFFByteArray(), 0);
} catch (Exception e) {
return null;
}
}
/* --------------------------- Edge Table -------------------------- */
/* --------------------------- Type 2 charstring functions --------------------------- */
/* Return operator length from opcode */
private int t2oplen(byte cstr[])
{
switch (cstr[0] & 0xFF) {
default:
return 1;
case TX_ESCAPE:
case 247: case 248: case 249: case 250:
case 251: case 252: case 253: case 254:
return 2;
case T2_SHORTINT:
return 3;
case T2_SEPARATOR:
return 4;
case 255:
return 5;
case T2_HINTMASK:
case T2_CNTRMASK:
return cstr[1] & 0xFF;
}
}
/* Copy and edit cstr by removing length bytes from mask operators. Return advanced destination buffer pointer */
private int t2cstrcpy(byte dst[], int dstX, int srcX, int length)
{
int left;
int srcEnd = srcX + length;
while (srcX < srcEnd) {
switch (mCharStr[srcX] & 0xFF) {
case T2_HINTMASK:
case T2_CNTRMASK: /* Mask ops; remove length byte */
dst[dstX++] = mCharStr[srcX++];
left = mCharStr[srcX++] - 2;
while (left-- != 0) {
dst[dstX++] = mCharStr[srcX++];
}
length--;
break;
case 255: /* 5-byte number */
dst[dstX++] = mCharStr[srcX++];
dst[dstX++] = mCharStr[srcX++];
dst[dstX++] = mCharStr[srcX++];
dst[dstX++] = mCharStr[srcX++];
dst[dstX++] = mCharStr[srcX++];
break;
case T2_SHORTINT: /* 3-byte number */
dst[dstX++] = mCharStr[srcX++];
dst[dstX++] = mCharStr[srcX++];
dst[dstX++] = mCharStr[srcX++];
break;
case TX_ESCAPE: /* 2-byte number/esc operator */
case 247: case 248: case 249: case 250:
case 251: case 252: case 253: case 254:
dst[dstX++] = mCharStr[srcX++];
dst[dstX++] = mCharStr[srcX++];
break;
default: /* 1-byte number/operator */
dst[dstX++] = mCharStr[srcX++];
break;
}
}
return dstX;
}
/* Allocate the initial edge table for a given node */
private void newEdgeTable(Node node, int size)
{
node.edgeTableSize = size;
node.edgeCount = 0;
node.edgeTable = new Edge[size];
}
/* Initialize new CDAWG edge */
private void initEdge(Edge edge, int label, int edgeLength, Node son)
{
edge.label = label;
edge.length = edgeLength;
edge.son = son;
}
/* --------------------------- CDAWG Construction --------------------------- */
/* Compare two edge labels */
private int labelcmp(int length1, int label1, int label2)
{
int cmp = mCharStr[label1++] - mCharStr[label2];
if (cmp != 0) {
return cmp; /* First byte differs */
} else {
int length2 = opLen(label2);
int length = (length1 < length2) ? length1 : length2;
label2++;
while (--length != 0) {
cmp = mCharStr[label1++] - mCharStr[label2++];
if (cmp != 0) {
return cmp; /* Subsequent byte differs */
}
}
/* Equal up to shortest label */
return length1 - length2;
}
}
/* Calculate a hash value for a given edge; this simple formula appears good enough */
private int hashLabel(int label, int length)
{
int hash = mCharStr[label] & 0xFF;
hash += (hash << 5);
while (--length > 0) {
int temp = mCharStr[++label] & 0xFF;
hash += temp;
hash <<= 5;
hash += temp;
}
return hash;
}
/* Look up the edge table as a hash table for a given edge label. Returns a pointer to an edge entry which may be empty if not found */
private Edge lookupEdgeTable(Node node, int length, int label)
{
int tableSize = node.edgeTableSize;
int tableSizeMinus1 = tableSize - 1;
int hashValue = (tableSize <= EDGE_TABLE_SIZE_USE_SIMPLE_HASH) ? ((mCharStr[label] & 0xFF) + length) : hashLabel(label, length);
int hashIncrement = 0;
int count = 0;
while (count < tableSize) {
Edge edge = node.edgeTable[hashValue & tableSizeMinus1]; /* (hashValue % node.edgeTableSize) */
if (edge == null) {
edge = node.edgeTable[hashValue & tableSizeMinus1] = new Edge();
}
if (edge.length == 0) {
return edge;
}
if (labelcmp(length, label, edge.label) == 0) {
return edge;
}
/* Collided with a wrong entry.
Update the hash value using the quadratic probing algorithm and try again */
hashValue += ++hashIncrement;
count++;
}
/* couldn't find an entry (and the table was full) */
return null;
}
/* Enter a new edge into the edge table represented as a hash table */
private void addEdgeToHashTable(Node node, Node son, int length, int label, int edgeLength)
{
boolean doubleIt = false;
Edge edge;
if (node.edgeTable == null) {
/* The initial edge table starts out with only one entry */
newEdgeTable(node, 1);
edge = node.edgeTable[0] = new Edge();
} else {
/* Double the hash table if the large table is almost full or no empty slot available */
if (node.edgeCount >= node.edgeTableSize) {
doubleIt = true;
} else if (node.edgeTableSize >= EDGE_TABLE_SMALLEST_SPARSE_SIZE) {
if (node.edgeCount >= (node.edgeTableSize - (node.edgeTableSize >> 3))) {
/* When the hash table is >= 87.5% full, double its size */
doubleIt = true;
}
}
if (doubleIt) {
doubleEdgeTable(node);
}
edge = lookupEdgeTable(node, length, label);
}
initEdge(edge, label, edgeLength, son);
node.edgeCount++;
}
/* Double the size of the edge table for a given node */
private void doubleEdgeTable(Node node)
{
Edge oldTable[] = node.edgeTable;
int oldTableSize = node.edgeTableSize;
int newTableSize = node.edgeTableSize * 2;
Edge newTable[] = new Edge[newTableSize];
Edge edge;
int i;
/* Replace the old hash table with a new blank hash table */
node.edgeTable = newTable;
node.edgeTableSize = newTableSize;
node.edgeCount = 0;
/* Copy all edges from the old hash table to the new hash table */
for (i = 0; i < oldTableSize; i++) {
edge = oldTable[i];
if (edge != null && edge.length != 0) {
addEdgeToHashTable(node, edge.son, opLen(edge.label), edge.label, edge.length);
}
}
}
/* Add edge to between father and son nodes */
private void addEdge(Node father, Node son, int length, int label, int edgeLength)
{
addEdgeToHashTable(father, son, length, label, edgeLength);
}
/* Find linking edge from node with label */
private Edge findEdge(Node node, int length, int label)
{
Edge edge;
if (node.misc == -1)
return mBaseEdge;
edge = lookupEdgeTable(node, length, label);
if (edge == null || edge.length != 0) {
return edge;
} else {
return null;
}
}
/* Copy the edge table from the source node to the destination node */
private void copyEdgeTable(Node destNode, Node srcNode)
{
newEdgeTable(destNode, srcNode.edgeTableSize);
destNode.edgeCount = srcNode.edgeCount;
for (int i = 0; i < srcNode.edgeTableSize; i++) {
Edge srcEdge = srcNode.edgeTable[i];
if (srcEdge != null) {
Edge destEdge = new Edge();
initEdge(destEdge, srcEdge.label, srcEdge.length, srcEdge.son);
destNode.edgeTable[i] = destEdge;
}
}
}
private void walkEdgeTable(Node node, int param1, int param2)
{
for (int i = 0; i < node.edgeTableSize; i++) {
Edge edge = node.edgeTable[i];
if (edge != null && edge.length != 0) {
findCandSubrs(edge, param1, param2);
}
}
}
/* Determine whether the state with the canonical reference pair (s,(k,p)) is the end point */
private boolean checkEndPoint(Node s, int k, int p, int length, int id)
{
// System.out.println("CheckEndPoint: " + k + ", " + p + ", " + length);
/* c == *p */
if (s.misc == -1) {
return true; /* base node is always an end point */
}
if (k < p) {
/* implicit node */
/* let s (k',p'). s' be the text[k]-edge from s; return (c == text[k' + p - k]) */
Edge edge = findEdge(s, opLen(k), k);
return labelcmp(length, p, edge.label + p - k) == 0;
} else {
/* explicit node */
/* is there 'c'-edge from s? */
return (findEdge(s, length, p) != null);
}
}
/* Return either an explicit/implicit node corresponding to the canonical reference pair (s,(k,p)) */
private Node extension(Node s, int k, int p)
{
// System.out.println("Extension: " + k + ", " + p);
if (k >= p) {
/* explicit node */
return s;
} else {
/* implicit node */
/* let s (k',p'). s' be the text[k]-edge from s */
return findEdge(s, opLen(k), k).son;
}
}
/* Redirect the edge (s,(k,p)) to r */
private void redirect(Node s, int k, int p, Node r)
{
/* let s (k',p'). s' be the text[k]-edge from s */
Edge edge = findEdge(s, opLen(k), k);
// System.out.println("Redirect: " + k + ", " + p);
/* replace this edge by edge s (k',k'+p-k). r;
note that the edge has the same label so it can be replaced in place
within the edge tree */
edge.son = r;
edge.length = p - k;
}
/* Canonize (normalize) a reference pair (s,(k,p)) of an implicit node and return it as (s',k') */
private void canonize(Node s, int k, int p, RefPair refPair)
{
// System.out.println("Canonize: " + k + ", " + p);
if (k < p) {
Edge edge;
int length = opLen(k);
if (s.misc == -1) {
/* base node has a one-length edge to root for every token */
s = mRoot;
k += length;
if (k >= p) {
/* explicit node */
refPair.node = s;
refPair.index = k;
return;
}
length = opLen(k);
}
/* (s,(k,p)) is an implicit node */
/* find the text[k]-edge s (k',p') . s' from s */
edge = findEdge(s, length, k);
while (edge.length <= p - k) {
k += edge.length;
s = edge.son;
if (k < p) {
/* find the text[k]-edge s (k',p'). s' from s */
edge = findEdge(s, opLen(k), k);
}
}
}
refPair.node = s;
refPair.index = k;
}
/* Split an edge at the canonical reference point (s,(k,p)) and returns the split point as an explicit node */
private Node splitEdge(Node s, int k, int p, int id)
{
Node r;
int newLabel;
// System.out.println("Split: " + k + ", " + p);
/* let s (k',p') . s' be the text[k]-edge from s */
Edge edge = findEdge(s, opLen(k), k);
/* replace this edge by edges s (k',k'+p-k) . r and r (k'+p-k+1,p') . s', where r is a new node */
r = new Node(s.misc + p - k, (edge.son.id != id) ? NODE_GLOBAL : id);
newLabel = edge.label + p - k;
addEdge(r, edge.son, opLen(newLabel), newLabel, edge.label + edge.length - newLabel);
edge.length = p - k;
edge.son = r;
return r;
}
/* If a node at the canonical reference point (s,(k,p)) is a non-solid, explicit node,
then duplicate the node and return a new active point */
private void separateNode(Node s, int k, int p, RefPair refPair, int id)
{
Node ss;
Node rr;
int kk;
int pminus1;
Node cs;
int ck;
// System.out.println("Separate: " + k + ", " + p);
canonize(s, k, p, refPair);
ss = refPair.node;
kk = refPair.index;
if (kk < p) {
/* implicit node */
refPair.node = ss;
refPair.index = kk;
return;
}
/* (s',(k',p)) is an explicit node */
if (s.misc == -1) {
/* base node */
refPair.node = ss;
refPair.index = kk;
return;
}
if (ss.misc == s.misc + p - k) {
/* solid edge */
Node suffix;
for (suffix = ss; suffix != null; suffix = suffix.suffix) {
if (suffix.id != id) {
suffix.id = NODE_GLOBAL;
}
}
refPair.node = ss;
refPair.index = kk;
return;
}
/* non-solid case */
/* create a new node r' as a duplication of s' */
rr = new Node(s.misc + p - k, (ss.id != id) ? NODE_GLOBAL : id);
/* Copy the edge table */
copyEdgeTable(rr, ss);
/* set up suffix link */
rr.suffix = ss.suffix;
ss.suffix = rr;
do {
/* replace the text[k]-edge from s to s' by edge s (k,p). r' */
Node son;
Edge edge = findEdge(s, opLen(k), k);
ss = edge.son;
initEdge(edge, k, p - k, rr);
/* calculate a canonical reference for Suf(s) with edge (k,p-1);
(s, k) := canonize(Suf(s),(k,p-1))
that is, we need to back up from (k,p) by one token. Since we can't
parse a charstring backwards, we parse from k forward to find p-1.
this code may need optimization since it is O(n^2) */
pminus1 = k;
for (;;) {
int length = opLen(pminus1);
if (pminus1 + length >= p) {
break;
}
pminus1 += length;
}
canonize(s.suffix, k, pminus1, refPair);
s = refPair.node;
k = refPair.index;
if (k < p) {
/* implicit node */
son = findEdge(s, opLen(k), k).son;
} else {
son = s;
}
if (son.id != id) {
son.id = NODE_GLOBAL;
}
/* do {..} until (s',k') != canonize(s,(k,p)) */
canonize(s, k, p, refPair);
cs = refPair.node;
ck = refPair.index;
} while ((ss == cs) && (kk == ck));
/* return (r',p) */
refPair.node = rr;
refPair.index = p;
}
/* Append font's charstring data to CDAWG. This construction algorithm closely
follows the one presented in "On-Line Construction of Compact Directed Acyclic
Word Graphs" although this one also adds code the identify
nodes in the CDAWG with a particular font or as global nodes if they
terminate charstrings that appear in multiple fonts.
A notational difference is that an edge is represented as (k,p) where
p points at the next token after the end of the string as opposed to
p pointing at the last token in the string.
*/
private void addFont(SubrFont font, int iFont, boolean multiFonts)
{
int p;
int pend;
byte pfd[] = null;
int pfdx;
int id;
Node s; /* active point */
int k; /* beginning of the current reference point */
Node e; /* extention node */
Node r, oldr;
RefPair refPair = new RefPair();
if (font.chars.nStrings == 0) {
return; /* Synthetic font */
}
p = 0;
pfdx = 0;
pend = font.chars.offset[font.chars.nStrings - 1];
mCharStr = font.chars.data;
if (font.subrFontCID) {
pfd = font.fdIndex;
id = (short)(iFont + pfd[pfdx++]);
} else {
id = iFont;
}
if (mBase == null) {
mBase = new Node(-1, id);
mBase.misc = -1; /* length from source */
mBase.suffix = null;
}
if (mRoot == null) {
mRoot = new Node(0, id);
mRoot.suffix = mBase;
}
/* set up base edge */
mBaseEdge = new Edge();
mBaseEdge.son = mRoot;
s = mRoot;
k = p;
/* Extend path (token by token) */
while (p < pend) {
int length = opLen(p);
r = null;
oldr = null;
e = null;
/* Update at (s,(k,p)) which is the canonical reference pair for the active point. */
while (!checkEndPoint(s, k, p, length, id)) {
Node sink;
if (k < p) {
/* implicit */
Node newe = extension(s, k, p);
if (newe == e) {
redirect(s, k, p, r);
canonize(s.suffix, k, p, refPair);
s = refPair.node;
k = refPair.index;
continue;
} else {
e = newe;
r = splitEdge(s, k, p, id);
}
} else {
/* explicit */
r = s;
}
/* create p new edge r (p,infinity) . sink */
/* we furnish one sink for each font */
if (id >= mSinks.size()) {
ensureSize(mSinks, id + 1);
}
sink = (Node)mSinks.get(id);
if (sink == null) {
sink = new Node(0, id);
sink.counted = true;
sink.paths = 1;
mSinks.set(id, sink);
}
addEdge(r, sink, length, p, pend - p);
if (oldr != null) {
oldr.suffix = r;
}
oldr = r;
canonize(s.suffix, k, p, refPair);
s = refPair.node;
k = refPair.index;
}
if (oldr != null) {
oldr.suffix = s;
}
/* Even though we reached an end point, we need to follow the suffix link
in order to mark shared nodes as NODE_GLOBAL in the case of multi-fonts. */
if (multiFonts) {
Node ss = s;
int kk = k;
int pp = p + length;
while (ss.misc != -1) {
canonize(ss.suffix, kk, pp, refPair);
ss = refPair.node;
kk = refPair.index;
if (kk >= pp) {
/* explicit */
if (ss.id != id) {
ss.id = NODE_GLOBAL;
}
}
}
}
separateNode(s, k, p + length, refPair, id);
s = refPair.node;
k = refPair.index;
if (font.subrFontCID && mCharStr[p] == T2_SEPARATOR && p + length < pend) {
/* Change id for CID font on charstring boundary */
id = iFont + pfd[pfdx++];
}
p += length;
}
}
/* ----------------------- Subr Hash Table ------------------------ */
private int hashSubr(int cstr, int length)
{
int hash = length;
int limit;
int delta;
int i;
if (length >= ((SUBR_HASH_SAMPLE_COUNT - 1) * 2)) {
delta = length / ((SUBR_HASH_SAMPLE_COUNT - 1));
} else {
delta = 1;
}
limit = length - delta;
for (i = 0; i < limit; i += delta) {
hash += mCharStr[cstr + i] & 0xFF;
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += mCharStr[cstr + length - 1] & 0xFF;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += (hash << 3);
hash ^= (hash >> 11);
return hash;
}
private int lookupSubrHash(int cstr, int length)
{
int tableSize = mSubrHash.length;
int tableSizeMinus1 = tableSize - 1;
int hashValue;
int hashIncrement = 0;
int count = 0;
/* switch to a better hashing function when the table size gets large */
if (tableSize <= SUBR_TABLE_SIZE_USE_SIMPLE_HASH) {
int c1 = mCharStr[cstr] & 0xFF;
int c2 = mCharStr[cstr + (length / 2)] & 0xFF;
int c3 = mCharStr[cstr + length - 1] & 0xFF;
hashValue = (c1 + c2 + c3 + length) + (length << 5) + (c2 << 9) + (c3 << 14);
} else {
hashValue = hashSubr(cstr, length);
}
while (count < tableSize) {
int i = hashValue & tableSizeMinus1;
Subr subr = mSubrHash[i];
if (subr == null) {
return i;
}
if ((subr.length == length) && ((cstr == subr.cstr) || (bytecmp(mCharStr, cstr, mCharStr, subr.cstr, length) == 0))) {
return i;
}
hashValue += ++hashIncrement;
count++;
}
/* shouldn't happen */
return -1;
}
private void updateSubrQuickTestTables()
{
int i;
int prevSubrLen;
/* Determine the subr max and min lengths and build the prefix map
with the current selected subrs */
mMaxSubrLen = 0;
mMinSubrLen = Integer.MAX_VALUE;
for (i = 0; i < mSubrs.size(); i++) {
Subr subr = (Subr)mSubrs.get(i);
if (!subr.reject) {
int length = subr.length;
setSubrPrefixMap(subr.cstr);
/* Record the max and min lengths of subrs for use by buildCallList */
if (length > mMaxSubrLen) {
mMaxSubrLen = length;
}
if (length < mMinSubrLen) {
mMinSubrLen = length;
}
}
}
/* Set flags in the subr len map for all current selected subrs */
mSubrLenMap = new short[mMaxSubrLen + 1];
for (i = 0; i < mSubrs.size(); i++) {
Subr subr = (Subr)mSubrs.get(i);
if (!subr.reject) {
mSubrLenMap[subr.length] = 1;
}
}
/* Modify the subr len map so that every entry contains a length of a selected subr which is shorter or equal to the length for the entry */
prevSubrLen = 0; /* 0 means no subr */
for (i = 0; i < mSubrLenMap.length; i++) {
if (mSubrLenMap[i] != 0) {
prevSubrLen = i;
}
mSubrLenMap[i] = (short)prevSubrLen;
}
}
/* Enter all subrs in the subr hash table. Also update subrPrefixMap and subrLenMap */
private void createSubrHash()
{
int n = mSubrs.size();
int hashTableSize = 1;
int i;
/* Make the table size a power of 2 and at least 50% free */
n *= 2;
while (n > hashTableSize) {
hashTableSize <<= 1;
}
mSubrHash = new Subr[hashTableSize];
for (i = 0; i < mSubrs.size(); i++) {
Subr subr = (Subr)mSubrs.get(i);
mSubrHash[lookupSubrHash(subr.cstr, subr.length)] = subr;
}
updateSubrQuickTestTables();
}
/* ----------------------- Candidate Subr Selection ------------------------ */
/* Count paths running through each node */
private int countPaths(Edge edge)
{
Node node;
if (edge == null) {
return 0;
}
node = edge.son;
if (!node.counted) {
/* Count descendent paths */
int count = node.paths;
/* Recursively descend complex node */
count += countPathsForNode(node);
/* Update node */
node.paths = (short)((count > Short.MAX_VALUE) ? Short.MAX_VALUE : count);
node.counted = true;
}
return node.paths;
}
private int countPathsForNode(Node node)
{
int count = 0;
int i, tableSize;
tableSize = node.edgeTableSize;
for (i = 0; i < tableSize; i++) {
Edge edge = node.edgeTable[i];
if (edge != null && edge.length != 0) {
count += countPaths(edge);
}
}
return count;
}
/* Test candidate subr and save if it meets candidate requirements.
This function selects all candidate subrs using a subr number size of 1 and
Subr INDEX offset element size of 2. The actual sizes will be used later
when accepting or rejecting subrs from the initial candidate list. The
savings available are as follows:
call = 1 callsubr op size
num = 1 subr number size
off = 2 subr INDEX offset element size
ret = 1 return op size
Regular Tail
length count saved count saved
------ ----- ----- ----- -----
3 7 1 6 1
4 4 1 4 2
5 3 1 3 2
6 3 3 3 4
7 3 5 2 1
8 2 1 2 2
9- 2 >0 2 >0
*/
private void saveSubr(int edgeEnd, Node node, int maskcnt, boolean tail, int subrLen)
{
int count = node.paths;
/* Test for candidacy */
if (subrLen > Short.MAX_VALUE)
return;
switch (subrLen - maskcnt) {
case 1:
case 2:
return;
case 3:
if (count < (7 - (tail ? 1 : 0))) {
return;
}
break;
case 4:
if (count < 4) {
return;
}
break;
case 5:
case 6:
if (count < 3) {
return;
}
break;
case 7:
if (count < (3 - (tail ? 1 : 0))) {
return;
}
break;
default:
if (count < 2) {
return;
}
break;
}
/* Select as candidate subr */
Subr subr = new Subr();
subr.node = node;
subr.sups = null;
subr.infs = null;
subr.next = null;
subr.cstr = edgeEnd - subrLen;
subr.length = (short)subrLen;
subr.count = (short)count;
subr.deltalen = 0;
subr.numsize = 1;
subr.maskcnt = (short)maskcnt;
mSubrs.add(subr);
// System.out.println(subr.cstr + ", " + subr.length);
/* Update subr node */
node.misc = mSubrs.size() - 1;
if (tail) {
node.tail = true;
}
}
/* Find candidate subrs in CDAWG */
private void findCandSubrs(Edge edge, int maskcnt, int misc)
{
if (misc + edge.length == edge.son.misc) {
/* Descend solid edge */
Node node;
int edgeEnd;
for (;;) {
int pstr;
node = edge.son;
if (node.tested || node.paths == 1) {
return;
}
node.tested = true;
/* scan the edge string for masks and endchar */
pstr = edge.label;
edgeEnd = pstr + edge.length;
while (pstr < edgeEnd) {
int oplen = opLen(pstr);
int b0 = mCharStr[pstr] & 0xFF;
if (b0 == TX_ENDCHAR) {
if (node.paths > 1) {
pstr += oplen;
saveSubr(pstr, node, maskcnt, true, node.misc - (edge.label + edge.length - pstr));
}
return;
} else if (b0 == T2_HINTMASK || b0 == T2_CNTRMASK) {
maskcnt++;
}
pstr += oplen;
}
misc = node.misc;
if (node.edgeCount > 1) {
break;
} else if (node.paths > node.edgeTable[0].son.paths) {
saveSubr(edgeEnd, node, maskcnt, false, misc);
}
}
saveSubr(edgeEnd, node, maskcnt, false, misc);
walkEdgeTable(node, maskcnt, misc);
}
}
/* Calculate byte savings for this subr. See candSubr() for details */
private int subrSaved(Subr subr)
{
int length = subr.length - subr.maskcnt;
return subr.count * (length - CALL_OP_SIZE - subr.numsize) - (mOffSize + length + (subr.node.tail ? 0 : 1));
}
/* Calculate byte savings by one instance of call to this subr */
private int subrSavedByOneCall(Subr subr)
{
int length = subr.length - subr.maskcnt;
return (length - CALL_OP_SIZE - subr.numsize);
}
/* Scan charstring and build call list of subrs */
/* The same function is called with buildPhase = 0 for setting subr count during
overlap handling phase and called with buildPhase = 1 for building call list.
The code looks for subrs with the longest length first, then try to cover
a given charstring. During the following iterations shorter subrs are tried
to fill gaps (if we apply the same logic to each gap recursively, we can get
the most optimal result). Lists are created and are tried to fill their gaps in
the order of the subr size. All subrs including those inferior to others are tried.
If a subr can't fit in any gap in lists, then a new list is created for it.
At the end, the most space saving list of subrs is selected as the call list.
The call list may not contain the longest inferior subr for the given charstring
as the result.
TODO: If this approach works well the overlap handling phase should be rewritten
using the same logic in order to resolve the logic disparity between the two phases.
*/
private void buildCallList(int buildPhase, int length, int pstart, boolean selfMatch, int id)
{
int pstr;
int pend = pstart + length;
int subend;
int p;
int substrlen;
int maxSaved;
int bestListIndex;
ArrayList list;
ArrayList callListArray = new ArrayList();
/* Initialize the prefix length array for the given charstring it is used to shorten a charstring at its end */
mPrefixLen = new short[length];
p = pstart;
for (int i = 0; i < length;) {
int oplen = opLen(p);
for (int j = 0; j < oplen; j++)
mPrefixLen[i++] = (short)j;
p += oplen;
}
substrlen = pend - pstart;
if (substrlen > mMaxSubrLen) {
substrlen = mMaxSubrLen;
}
for (; substrlen >= mMinSubrLen; substrlen--) {
/* Shorten substrlen to a nearest shorter or equal subr length */
substrlen = mSubrLenMap[substrlen];
for (pstr = pstart; (subend = pstr + substrlen) <= pend; pstr += opLen(pstr)) {
int subrHash;
Subr subr;
/* Check the subr prefix map to see if there is any subrs starting with this prefix */
if (!testSubrPrefixMap(pstr)) {
continue;
}
subend = pstr + substrlen;
if (subend < pend) {
if (mPrefixLen[subend - pstart] != 0) {
continue; /* invalid op at the end */
}
}
/* Look up this substring in the subr hash table */
subrHash = lookupSubrHash(pstr, subend - pstr);
subr = (subrHash >= 0) ? mSubrHash[subrHash] : null;
if (subr != null && (buildPhase == 0 || subr.select)
&& ((subend != pend) || (pstr != pstart) || selfMatch)
&& (buildPhase == 0 || (id == subr.node.id) || (subr.node.id == NODE_GLOBAL))) {
boolean foundGap = false;
int subrStartOffset = pstr - pstart;
int subrEndOffset = subrStartOffset + subr.length;
/* Try to fill a gap in any list in callListArray */
for (int i = 0; i < callListArray.size(); i++) {
int j;
int cnt;
boolean overlap = false;
list = (ArrayList)callListArray.get(i);
cnt = list.size();
for (j = 0; j < cnt; j++) {
Call call = (Call)list.get(j);
if (subrEndOffset <= call.offset) {
break; /* found gap before at j'th call */
}
if (subrEndOffset > call.offset && subrStartOffset < call.offset + call.subr.length) {
/* overlap */
overlap = true;
break;
}
if (subrStartOffset < call.offset + call.subr.length && subrEndOffset > call.offset) {
/* overlap */
overlap = true;
break;
}
}
/* insert the subr into this gap */
if (!overlap) {
Call call = new Call(subr, subrStartOffset);
list.add(j, call);
foundGap = true;
break;
}
}
if (!foundGap && (callListArray.size() < MAX_CALL_LIST_COUNT)) {
/* create a new list for this subr */
Call call = new Call(subr, subrStartOffset);
list = new ArrayList();
list.add(call);
callListArray.add(list);
}
}
}
}
/* Select the most space saving call list from the call list array */
maxSaved = 0;
bestListIndex = 0;
for (int i = 0; i < callListArray.size(); i++) {
int saved = 0;
list = (ArrayList)callListArray.get(i);
for (int j = 0; j < list.size(); j++) {
saved += subrSavedByOneCall(((Call)list.get(j)).subr);
}
if (saved > maxSaved) {
maxSaved = saved;
bestListIndex = i;
}
}
mCalls = (callListArray.isEmpty()) ? new ArrayList() : (ArrayList)callListArray.get(bestListIndex);
if (buildPhase == 0) {
for (int i = 0; i < mCalls.size(); i++) {
Call call = (Call)mCalls.get(i);
call.subr.count++;
}
}
}
private void setSubrActCount()
{
int i, j;
Subr subr;
Link infs;
/* Reset subr count */
for (i = 0; i < mSubrs.size(); i++) {
subr = (Subr)mSubrs.get(i);
subr.count = 0;
}
/* Make call list for each subr and set actual call count in each */
for (i = 0; i < mSubrs.size(); i++) {
subr = (Subr)mSubrs.get(i);
buildCallList(0, subr.length, subr.cstr, false, 0);
infs = null;
for (j = mCalls.size() - 1; j >= 0; j--) {
Call call = (Call)mCalls.get(j);
Subr inf = call.subr;
/* Add superior subr to inferior subrs */
inf.sups = new Link(subr, call.offset, inf.sups);
/* Add inferior subr to list */
infs = new Link(inf, call.offset, infs);
}
subr.infs = infs;
}
}
private void sortInfSubrs()
{
int i;
Subr subr;
/* Cache the subrSaved value for use during sort below */
for (i = 0; i < mSubrs.size(); i++) {
subr = (Subr)mSubrs.get(i);
subr.misc = (short)subrSaved(subr);
}
/* Reorder inferiors by savings (largest first) */
for (i = 0; i < mSubrs.size(); i++) {
subr = (Subr)mSubrs.get(i);
/* Sort list in-place */
for (Link head = subr.infs; head != null; head = head.next) {
Link probe = head.next;
if (probe == null) {
break; /* End of list */
} else {
Link best = null;
int max = head.subr.misc;
/* Find best inferior subr in remainder of list */
do {
int saved = probe.subr.misc;
if (saved > max) {
best = probe;
max = saved;
}
probe = probe.next;
} while (probe != null);
if (best != null) {
/* Swap "head" and "best" nodes */
Call tmp = new Call(head.subr, head.offset);
head.subr = best.subr;
head.offset = best.offset;
best.subr = tmp.subr;
best.offset = tmp.offset;
}
}
}
}
}
/* Select candidate subrs */
private void selectCandSubrs()
{
/* Count paths */
countPathsForNode(mRoot);
/* Find candidate subrs */
mSubrs = new ArrayList();
walkEdgeTable(mRoot, 0, 0);
/* Create a hash table of subrs */
createSubrHash();
}
/* ----------------------- Associate Subrs With Font ----------------------- */
/* Associate subrs with a font in the FontSet */
private void assocSubrs()
{
for (int i = 0; i < mNumFonts; i++) {
int offset = 0;
SubrFont font = mFonts[i];
for (int j = 0; j < font.chars.nStrings; j++) {
int nextoff = font.chars.offset[j];
buildCallList(0, nextoff - offset, offset, true, 0);
offset = nextoff;
}
}
}
/* --------------------------- Select Final Subrs -------------------------- */
/* Add social group member subr */
private void addMember(Subr subr)
{
int listlength = 0;
Subr list[] = new Subr[LISTSIZE]; /*Used to reverse the order of the Subr's*/
ArrayList subrStack = new ArrayList();
subrStack.add(subr);
while (subrStack.size() > 0) {
subr = (Subr)subrStack.remove(subrStack.size() - 1);
if (!subr.member) {
/* Add member and mark subr to avoid reselection */
mMembers.add(subr);
subr.member = true;
for (Link link = subr.sups; link != null; link = link.next) {
/* Add superior member to temporary list*/
list[listlength++] = link.subr;
if (listlength >= LISTSIZE) {
return;
}
}
for (Link link = subr.infs; link != null; link = link.next) {
/* Add inferior member to temporary list*/
list[listlength++] = link.subr;
if (listlength >= LISTSIZE) {
return;
}
}
/* Transfer from temporary list into stack*/
for (int i = listlength - 1; i >= 0 ; i--) {
subrStack.add(list[i]);
}
listlength = 0;
}
}
}
private int tieBreaker(Subr a, Subr b)
{
if (a.cstr > b.cstr)
return -1;
if (a.cstr < b.cstr)
return 1;
if (a.length > b.length)
return -1;
if (a.length < b.length)
return 1;
return 0;
}
/* Compare social group member subrs for a global subr set. Global subrs are sorted before local subrs, largest saving first. */
private int cmpGlobalSetSubrs(Subr a, Subr b)
{
if (a.node.id == NODE_GLOBAL) {
if (b.node.id == NODE_GLOBAL) {
/* global global */
int asaved = subrSaved(a);
int bsaved = subrSaved(b);
if (asaved > bsaved) {
return -1;
} else if (asaved < bsaved) {
return 1;
} else {
return tieBreaker(a, b);
}
} else {
/* global local */
return -1;
}
} else if (b.node.id == NODE_GLOBAL) {
/* local global */
return 1;
} else {
/* local local */
return tieBreaker(a, b);
}
}
/* Compare social group member subrs for a local subr set. Selected global
subrs are sorted before local subrs which are sorted before rejected global
subrs. Selected global and unselected local subrs are further sorted by
largest savings first. */
private int cmpLocalSetSubrs(Subr a, Subr b)
{
int state = 0;
if (a.reject)
state |= 8;
if (a.select)
state |= 4;
if (b.reject)
state |= 2;
if (b.select)
state |= 1;
switch (state) {
/* a-subr, b-subr */
case 0: /* local, local */
case 5: /* global.select, global.select */ {
int asaved = subrSaved(a);
int bsaved = subrSaved(b);
if (asaved > bsaved) {
return -1;
} else if (asaved < bsaved) {
return 1;
} else {
return tieBreaker(a, b);
}
}
case 1: /* local, global.select */
case 8: /* global.reject, local */
case 9: /* global.reject, global.select */
return 1;
case 2: /* local, global.reject */
case 4: /* global.select, local */
case 6: /* global.select, global.reject */
return -1;
case 10:/* global.reject, global.reject */
return tieBreaker(a, b);
default:
System.out.println("cmpLocalSetSubrs() can't happen!\n");
}
return 0; /* Suppress compiler warning */
}
/* Find social groups for global or local subr set. */
private void findGroups(int id)
{
mLeaders = new ArrayList();
mSubrSortMode = (id == NODE_GLOBAL) ? SORT_MODE_GLOBAL : SORT_MODE_LOCAL;
for (int i = 0; i < mTmp.size(); i++) {
Subr subr = (Subr)mTmp.get(i);
if (subr.member) {
continue;
}
/* Add new social group */
mMembers = new ArrayList();
addMember(subr);
/* Sort members by largest saving first */
Collections.sort(mMembers);
/* Link members */
for (int j = 0; j < mMembers.size() - 1; j++) {
((Subr)mMembers.get(j)).next = (Subr)mMembers.get(j + 1);
}
((Subr)mMembers.get(mMembers.size() - 1)).next = null;
/* Add leader */
mLeaders.add(mMembers.get(0));
}
}
/* Update superior lengths */
private void updateSups(Subr subr, int deltalen, int id)
{
Link link;
for (link = subr.sups; link != null; link = link.next) {
Subr sup = link.subr;
if (!(sup.select || sup.reject) && sup.node.id == id) {
updateSups(sup, deltalen, id);
sup.deltalen += deltalen;
}
}
}
/* Select subr */
private void selectSubr(Subr subr)
{
int count = subr.count;
int length = subr.length - subr.maskcnt + subr.deltalen;
int saved = count * (length - CALL_OP_SIZE - subr.numsize) - (mOffSize + length + (subr.node.tail ? 0 : 1));
if (saved > 0) {
/* Select subr */
int id = subr.node.id;
int deltalen = subr.numsize + CALL_OP_SIZE - subr.length - subr.deltalen;
subr.select = true;
updateSups(subr, deltalen, id);
} else {
/* Reject subr */
subr.reject = true;
}
}
/* Select global subrs from social group. */
private void selectGlobalSubrs(Subr subr, int id)
{
for (; subr != null; subr = subr.next) {
if (subr.node.id != NODE_GLOBAL) {
return; /* Quit on first local subr */
} else if (!(subr.select || subr.reject)) {
selectSubr(subr);
}
}
}
/* Select local subrs from social group. */
private void selectLocalSubrs(Subr subr, int id)
{
for (; subr != null; subr = subr.next) {
if (subr.node.id == NODE_GLOBAL) {
if (subr.select) {
updateSups(subr, subr.numsize + CALL_OP_SIZE - subr.length, id);
} else {
return; /* Quit on first rejected global subr */
}
} else if (!(subr.select || subr.reject)) {
selectSubr(subr);
}
}
}
/* Compare subr calls by selection/saved/length/frequency */
private int cmpSubrFitness(Subr a, Subr b)
{
boolean aselect = a.select;
boolean bselect = b.select;
if (aselect == bselect) {
int asaved = subrSaved(a);
int bsaved = subrSaved(b);
if (asaved > bsaved) { /* Compare savings */
return -1;
} else if (asaved < bsaved) {
return 1;
} else if (a.length > b.length) { /* Compare length */
return -1;
} else if (a.length < b.length) {
return 1;
} else if (a.count > b.count) { /* Compare count */
return -1;
} else if (a.count < b.count) {
return 1;
} else {
return tieBreaker(a, b);
}
} else if (!aselect && bselect) {
return 1;
} else {
return -1;
}
}
/* Check for subr stack depth overflow */
private void checkSubrStackOvl(Subr subr, int depth)
{
Link sup;
/* No need to check this subr if it has been checked with this or deeper stack */
if (depth <= subr.misc) {
return;
}
subr.misc = (short)depth;
depth++;
if (subr.select) {
if (depth >= TX_MAX_CALL_STACK) {
/* Stack depth exceeded; reject subr */
subr.select = false;
subr.reject = true;
depth--;
mSubrStackOvl = true;
}
}
/* Recursively ascend superior subrs */
for (sup = subr.sups; sup != null; sup = sup.next) {
checkSubrStackOvl(sup.subr, depth);
}
}
/* Select subr set to be saved in font. Subr list is in the temporary array */
private void selectFinalSubrSet(int id)
{
int i;
int nSelected = 0; /* Suppress optimizer warning */
int multiplier = mSingleton ? 2 : 1; /* Range multiplier */
int pass = 0;
findGroups(id); /* Find social groups (related subrs) */
for (;;) {
for (i = 0; i < mLeaders.size(); i++) {
Subr subr = (Subr)mLeaders.get(i);
if (subr.next == null) {
/* Select/reject hermit subr */
if (subrSaved(subr) > 0) {
subr.select = true;
} else {
subr.reject = true;
}
} else {
if (id == NODE_GLOBAL) {
selectGlobalSubrs(subr, id);
} else {
selectLocalSubrs(subr, id);
}
}
}
/* Reset misc field for storing subr call depth by checkSubrStackOvl */
for (i = 0; i < mTmp.size(); i++) {
((Subr)mTmp.get(i)).misc = -1;
}
/* Remove membership so globals may be selected in other local sets and check for and handle subr call stack overflow */
for (i = 0; i < mLeaders.size(); i++) {
Subr subr;
for (subr = (Subr)mLeaders.get(i); subr != null; subr = subr.next) {
if (subr.infs == null) {
checkSubrStackOvl(subr, 0);
}
subr.member = false;
}
}
/* Sort selected subrs by fitness */
mSubrSortMode = SORT_MODE_FITNESS;
Collections.sort(mTmp);
/* Find last selected subr */
for (i = mTmp.size() - 1; i >= 0; i--) {
if (((Subr)mTmp.get(i)).select) {
nSelected = i + 1;
break;
}
}
if (++pass == 2) {
/* Bias parity hack */
if (mSingleton && (nSelected == 2479 || nSelected == 67799)) {
Subr subr = new Subr();
subr.node = new Node(0, 0);
mTmp.add(nSelected++, subr);
}
/* Discard extra subrs if exceeds capacity */
int limit = mMaxNumSubrs;
if (limit == 0) {
limit = Short.MAX_VALUE;
}
limit = limit * multiplier;
if (nSelected > limit) {
while (limit < mTmp.size()) {
mTmp.remove(mTmp.size() - 1);
}
} else {
while (nSelected < mTmp.size()) {
mTmp.remove(mTmp.size() - 1);
}
}
return;
} else if (nSelected < 215 * multiplier) {
/* All subrs fit into first 1-byte range. Since we don't have to increase the index number size,
for any subroutines, their calculated savings won't change on a second pass. */
while (nSelected < mTmp.size()) {
mTmp.remove(mTmp.size() - 1);
}
return;
}
/* Removed subr marking before reselection */
/* We are going to have to increase the index number size for some subroutines.
This means that their savings will get smaller, and we may need to reject them. */
for (i = 0; i < mTmp.size(); i++) {
((Subr)mTmp.get(i)).select = false;
((Subr)mTmp.get(i)).reject = false;
}
/* Assign new subr number sizes */
for (i = mTmp.size() - 1; i >= 2263 * multiplier; i--)
((Subr)mTmp.get(i)).numsize = 3;
for (; i >= 215 * multiplier; i--)
((Subr)mTmp.get(i)).numsize = 2;
for (; i >= 0; i--)
((Subr)mTmp.get(i)).numsize = 1;
}
}
private void stackOverflow()
{
// System.out.println("subr stack depth exceeded (reduced)");
}
/* --------------------------- Add Subrs to INDEX -------------------------- */
/* Subroutinize charstring */
private int subrizeCstr(byte dst[], int dstX, int srcX, int length)
{
int i;
int offset;
/* Insert calls in charstring */
offset = 0;
for (i = 0; i < mCalls.size(); i++) {
Call call = (Call)mCalls.get(i);
Subr subr = call.subr;
if (subr != null) {
/* Copy bytes preceeding subr */
dstX = t2cstrcpy(dst, dstX, srcX, call.offset - offset);
/* Add subr call */
dstX += cfwEncInt(subr.subrnum, dst, dstX);
dst[dstX++] = (subr.node.id == NODE_GLOBAL) ? (byte)T2_CALLGSUBR : (byte)TX_CALLSUBR;
/* Skip over subr's bytes */
srcX += call.offset - offset + subr.length;
offset = call.offset + subr.length;
}
}
/* Copy remainder of charstring and return destination buffer pointer */
return t2cstrcpy(dst, dstX, srcX, length - offset);
}
/* Subroutinize charstrings */
private void subrizeChars(SubrCSData chars, int id)
{
int srcX = 0;
int dstX = 0;
byte dst[] = new byte[chars.data.length];
for (int i = 0; i < chars.nStrings; i++) {
int nextoff = chars.offset[i];
int length = nextoff - srcX - 4; /* T2_SEPARATOR */
/* Build subr call list */
buildCallList(1, length, srcX, true, id);
/* Subroutinize charstring */
dstX = subrizeCstr(dst, dstX, srcX, length);
/* Save offset */
chars.offset[i] = dstX;
srcX = nextoff;
}
/* Copy charstring data without loosing original data pointer */
chars.data = new byte[dstX];
System.arraycopy(dst, 0, chars.data, 0, dstX);
}
/* Create biased reordering for either global or local subrs for a singleton
font using combined subr INDEXes. The global subrs are selected from even
temporary array indexes and local subrs are chosen from odd indexes */
private void reorderCombined(boolean local)
{
int bias;
int count; /* Element in reorder array */
int i; /* Reorder array index */
int j; /* Temporary array index */
if (local) {
/* Reorder local (odd) indexes */
count = mTmp.size() / 2;
j = (mTmp.size() & ~1) + 1;
} else {
/* Reorder global (even) indexes */
count = (mTmp.size() + 1) / 2;
j = (mTmp.size() + 1) & ~1;
}
/* Set the reorder array size */
mReorder = new Subr[count];
i = count - 1;
if (count < 1240) {
/* Bias-107 reordering */
for (; i >= 0; i--) {
mReorder[i + 0] = (Subr)mTmp.get(j -= 2);
}
bias = 107;
} else if (count < 33900) {
/* Bias-1131 reordering */
for (; i >= 1239; i--) {
mReorder[i + 0] = (Subr)mTmp.get(j -= 2);
}
for (; i >= 215; i--) {
mReorder[i - 215] = (Subr)mTmp.get(j -= 2);
}
for (; i >= 0; i--) {
mReorder[i + 1024] = (Subr)mTmp.get(j -= 2);
}
bias = 1131;
} else {
/* Bias-32768 reordering */
for (; i >= 33900; i--) {
mReorder[i + 0] = (Subr)mTmp.get(j -= 2);
}
for (; i >= 2263; i--) {
mReorder[i - 2263] = (Subr)mTmp.get(j -= 2);
}
for (; i >= 1239; i--) {
mReorder[i + 31637] = (Subr)mTmp.get(j -= 2);
}
for (; i >= 215; i--) {
mReorder[i + 31422] = (Subr)mTmp.get(j -= 2);
}
for (; i >= 0; i--) {
mReorder[i + 32661] = (Subr)mTmp.get(j -= 2);
}
bias = 32768;
}
if (!local) {
/* Add biased subr numbers and mark global subrs */
for (i = 0; i < count; i++) {
Subr subr = mReorder[i];
subr.subrnum = (short)(i - bias);
subr.node.id = NODE_GLOBAL;
}
/* Copy global subr numbers to local subrs so that when the global subr
INDEX is constructed the local subr references will be valid */
for (i = 0; i < mTmp.size() - 1; i += 2) {
((Subr)mTmp.get(i + 1)).subrnum = ((Subr)mTmp.get(i)).subrnum;
}
}
}
/* Create biased reordering for a non-singleton font */
private void reorderSubrs()
{
int i;
int bias;
/* Assign reording index */
mReorder = new Subr[mTmp.size()];
i = mTmp.size() - 1;
if (mTmp.size() < 1240) {
/* Bias-107 reordering */
for (; i >= 0; i--) {
mReorder[i + 0] = (Subr)mTmp.get(i);
}
bias = 107;
} else if (mTmp.size() < 33900) {
/* Bias-1131 reordering */
for (; i >= 1239; i--) {
mReorder[i + 0] = (Subr)mTmp.get(i);
}
for (; i >= 215; i--) {
mReorder[i - 215] = (Subr)mTmp.get(i);
}
for (; i >= 0; i--) {
mReorder[i + 1024] = (Subr)mTmp.get(i);
}
bias = 1131;
} else {
/* Bias-32768 reordering */
for (; i >= 33900; i--) {
mReorder[i + 0] = (Subr)mTmp.get(i);
}
for (; i >= 2263; i--) {
mReorder[i - 2263] = (Subr)mTmp.get(i);
}
for (; i >= 1239; i--) {
mReorder[i + 31637] = (Subr)mTmp.get(i);
}
for (; i >= 215; i--) {
mReorder[i + 31422] = (Subr)mTmp.get(i);
}
for (; i >= 0; i--) {
mReorder[i + 32661] = (Subr)mTmp.get(i);
}
bias = 32768;
}
/* Add biased subr numbers */
for (i = 0; i < mReorder.length; i++) {
mReorder[i].subrnum = (short)(i - bias);
}
}
/* Add reorder subrs from reorder array */
private void addSubrs(SubrCSData subrs, int id)
{
if (mReorder == null || mReorder.length == 0) {
return; /* No subrs */
}
/* Allocate subr offset array */
subrs.nStrings = (short)mReorder.length;
subrs.offset = new int[mReorder.length];
int maxSize = 0;
/* Initially allocate space for entire charstring + last op */
for (int i = 0; i < mReorder.length; i++) {
maxSize += mReorder[i].length + 1;
}
int dstX = 0;
byte dst[] = new byte[maxSize];
for (int i = 0; i < mReorder.length; i++) {
Subr subr = mReorder[i];
/* Build subr call list */
buildCallList(1, subr.length, subr.cstr, false, id);
/* Subroutinize charstring */
dstX = subrizeCstr(dst, dstX, subr.cstr, subr.length);
/* Terminate subr */
if (!subr.node.tail) {
dst[dstX++] = TX_RETURN;
}
/* Adjust initial length estimate and save offset */
subrs.offset[i] = dstX;
}
/* Allocate and copy charstring data */
subrs.data = new byte[dstX];
System.arraycopy(dst, 0, subrs.data, 0, dstX);
}
/* Subroutinize FD charstrings */
private void subrizeFDChars(SubrCSData dataOut, SubrFont font, int id, int iFD)
{
int dstX = 0;
SubrCSData dataIn = font.chars;
mCharStr = font.chars.data;
byte dst[] = new byte[font.chars.data.length];
/* Count chars in this this FD */
dataOut.nStrings = 0;
for (int i = 0; i < dataIn.nStrings; i++) {
if (font.fdIndex[i] == iFD) {
dataOut.nStrings++;
}
}
/* Allocate offset array */
dataOut.offset = new int[dataOut.nStrings];
int iDst = 0;
for (int i = 0; i < dataIn.nStrings; i++) {
if (font.fdIndex[i] == iFD) {
int srcX = (i == 0) ? 0 : dataIn.offset[i - 1];
int length = dataIn.offset[i] - srcX - 4; /* T2_SEPARATOR */
/* Build subr call list */
buildCallList(1, length, srcX, true, id + iFD);
/* Subroutinize charstring */
dstX = subrizeCstr(dst, dstX, srcX, length);
/* Adjust initial length estimate and save offset */
dataOut.offset[iDst++] = dstX;
}
}
/* Copy charstring data */
dataOut.data = new byte[dstX];
System.arraycopy(dst, 0, dataOut.data, 0, dstX);
}
/* Reassemble cstrs for CID-keyed font */
private void joinFDChars(SubrFont font)
{
/* Determine total size of subroutinized charstring data */
int size = 0;
for (int i = 0; i < font.fdCount; i++) {
SubrFDInfo info = font.fdInfo[i];
if (info.chars.nStrings != 0)
size += info.chars.offset[info.chars.nStrings - 1];
info.iChar = 0;
}
/* Allocate new charstring data without loosing original data pointer */
font.chars.data = new byte[size];
int dstX = 0;
for (int i = 0; i < font.chars.nStrings; i++) {
SubrFDInfo info = font.fdInfo[font.fdIndex[i]];
int srcX = (info.iChar == 0) ? 0 : info.chars.offset[info.iChar - 1];
int length = info.chars.offset[info.iChar++] - srcX;
System.arraycopy(info.chars.data, srcX, font.chars.data, dstX, length);
dstX += length;
font.chars.offset[i] = dstX;
}
/* Free temporary chars index for each FD */
for (int i = 0; i < font.fdCount; i++) {
SubrCSData chars = font.fdInfo[i].chars;
chars.offset = null;
chars.data = null;
}
}
/* -------------------------- Subroutinize FontSet ------------------------- */
/* Build subrs with specific id */
private void buildSubrs(SubrCSData subrs, int id)
{
int i;
/* Build temporary array of subrs with matching id */
mTmp = new ArrayList();
for (i = 0; i < mSubrs.size(); i++) {
Subr subr = (Subr)mSubrs.get(i);
if (subr.node.id == id) {
mTmp.add(subr);
}
}
selectFinalSubrSet(id);
updateSubrQuickTestTables();
reorderSubrs();
addSubrs(subrs, id);
}
/* Subroutinize all fonts in FontSet */
private void cfwSubrSubrize(int nFonts, SubrFont fonts[])
{
int iFont;
int i;
byte dummycstr[] = new byte[2];
mNumFonts = nFonts;
mFonts = fonts;
/* Initialize opLenCache */
mOpLenCache = new byte[256];
for (i = 0; i < 256; i++) {
dummycstr[0] = (byte)i;
mOpLenCache[i] = (byte)t2oplen(dummycstr);
}
/* Determine type of FontSet */
mSingleton = mNumFonts == 1 && !(mFonts[0].subrFontCID);
/* Add fonts' charstring data to CDAWG */
iFont = 0;
mSinks = new ArrayList();
for (i = 0; i < mNumFonts; i++) {
addFont(mFonts[i], iFont, mNumFonts > 1 || mFonts[i].subrFontCID);
iFont += (mFonts[i].subrFontCID) ? mFonts[i].fdCount : 1;
}
selectCandSubrs(); /* Select candidate subrs */
setSubrActCount(); /* Set subr actual call counts */
assocSubrs(); /* Associate subrs with a font */
sortInfSubrs(); /* Sort inferior subrs by saving */
if (mSingleton) {
/* Single non-CID font */
/* Build temporary array from full subr list */
mTmp = new ArrayList();
mTmp.addAll(mSubrs);
mSubrStackOvl = false;
selectFinalSubrSet(0);
updateSubrQuickTestTables();
if (mSubrStackOvl) {
stackOverflow();
}
if (mTmp.size() >= 215) {
/* Make global subrs from even indexes */
reorderCombined(false);
mGSubrs = new SubrCSData();
addSubrs(mGSubrs, NODE_GLOBAL);
/* Make local subrs from odd indexes */
reorderCombined(true);
addSubrs(mFonts[0].subrs, 0);
} else {
reorderSubrs();
addSubrs(mFonts[0].subrs, 0);
}
subrizeChars(mFonts[0].chars, 0);
} else {
/* Multiple fonts or single CID font */
/* Build global subrs */
mGSubrs = new SubrCSData();
buildSubrs(mGSubrs, NODE_GLOBAL);
/* Find and add local subrs to each font */
iFont = 0;
for (i = 0; i < mNumFonts; i++) {
SubrFont font = mFonts[i];
mSubrStackOvl = false;
if (font.subrFontCID) {
/* Subrotinize CID-keyed font */
int j;
for (j = 0; j < mFonts[i].fdCount; j++) {
/* Subroutinize component DICT */
SubrFDInfo info = font.fdInfo[j];
buildSubrs(info.subrs, iFont + j);
subrizeFDChars(info.chars, font, iFont, j);
}
joinFDChars(font);
iFont += mFonts[i].fdCount;
} else {
if (font.chars.nStrings != 0) {
/* Subroutinize non-synthetic font */
buildSubrs(font.subrs, iFont);
subrizeChars(font.chars, iFont);
}
iFont++;
}
if (mSubrStackOvl) {
stackOverflow();
}
}
}
}
/* Macros converted to methods */
private int opLen(int idx)
{
int len = mOpLenCache[mCharStr[idx] & 0xFF] & 0xFF;
if (len == 0)
len = mCharStr[idx + 1] & 0xFF;
return len;
}
private void setSubrPrefixMap(int idx)
{
int b0 = mCharStr[idx] & 0xFF;
int b1 = mCharStr[idx + 1] & 0xFF;
int index = (b0 << 5) | (b1 >> 3);
int bit = 1 << (b1 & 7);
mSubrPrefixMap[index] |= bit;
}
private boolean testSubrPrefixMap(int idx)
{
int b0 = mCharStr[idx] & 0xFF;
int b1 = mCharStr[idx + 1] & 0xFF;
int index = (b0 << 5) | (b1 >> 3);
int bit = 1 << (b1 & 7);
return ((mSubrPrefixMap[index] & bit) != 0);
}
private void ensureSize(ArrayList list, int newSize)
{
list.ensureCapacity(newSize);
for (int i = list.size(); i < newSize; i++) {
list.add(null);
}
}
private int bytecmp(byte item1[], int offset1, byte item2[], int offset2, int length)
{
while (length-- != 0) {
if (item1[offset1++] != item2[offset2++])
return (item1[--offset1] & 0xFF) - (item2[--offset2] & 0xFF);
}
return 0;
}
private int cfwEncInt(long i, byte t[], int idx)
{
if (-107 <= i && i <= 107) {
/* Single byte number */
t[idx] = (byte)(i + 139);
return 1;
} else if (108 <= i && i <= 1131) {
/* Positive 2-byte number */
i -= 108;
t[idx] = (byte)((i>>8) + 247);
t[idx + 1] = (byte)i;
return 2;
} else if (-1131 <= i && i <= -108) {
/* Negative 2-byte number */
i += 108;
t[idx] = (byte)((-i>>8) + 251);
t[idx + 1] = (byte)-i;
return 2;
} else if (-32768 <= i && i <= 32767) {
/* Positive/negative 3-byte number (shared with dict ops) */
t[idx] = (byte)T2_SHORTINT;
t[idx + 1] = (byte)(i>>8);
t[idx + 2] = (byte)i;
return 3;
} else {
/* Positive/negative 5-byte number (dict ops only) */
t[idx] = (byte)CFF_LONGINT;
t[idx + 1] = (byte)(i>>24);
t[idx + 2] = (byte)(i>>16);
t[idx + 3] = (byte)(i>>8);
t[idx + 4] = (byte)i;
return 5;
}
}
}