package org.apache.fop.fonts.truetype;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
public class TTFSubSetFile extends TTFFile {
protected byte[] output;
protected int realSize;
protected int currentPos;
protected Map<OFTableName, Integer> offsets = new HashMap<OFTableName, Integer>();
private int checkSumAdjustmentOffset;
protected int locaOffset;
protected int[] glyphOffsets;
public TTFSubSetFile() {
}
public TTFSubSetFile(boolean useKerning, boolean useAdvanced) {
super(useKerning, useAdvanced);
}
protected Map<OFTableName, OFDirTabEntry> newDirTabs
= new HashMap<OFTableName, OFDirTabEntry>();
private int determineTableCount() {
int numTables = 4;
if (isCFF()) {
throw new UnsupportedOperationException(
"OpenType fonts with CFF glyphs are not supported");
} else {
numTables += 5;
if (hasCvt()) {
numTables++;
}
if (hasFpgm()) {
numTables++;
}
if (hasPrep()) {
numTables++;
}
if (!cid) {
numTables++;
}
}
return numTables;
}
protected void createDirectory() {
int numTables = determineTableCount();
writeByte((byte)0);
writeByte((byte)1);
writeByte((byte)0);
writeByte((byte)0);
realSize += 4;
writeUShort(numTables);
realSize += 2;
int maxPow = maxPow2(numTables);
int searchRange = (int) Math.pow(2, maxPow) * 16;
writeUShort(searchRange);
realSize += 2;
writeUShort(maxPow);
realSize += 2;
writeUShort((numTables * 16) - searchRange);
realSize += 2;
writeTableName(OFTableName.OS2);
if (!cid) {
writeTableName(OFTableName.CMAP);
}
if (hasCvt()) {
writeTableName(OFTableName.CVT);
}
if (hasFpgm()) {
writeTableName(OFTableName.FPGM);
}
writeTableName(OFTableName.GLYF);
writeTableName(OFTableName.HEAD);
writeTableName(OFTableName.HHEA);
writeTableName(OFTableName.HMTX);
writeTableName(OFTableName.LOCA);
writeTableName(OFTableName.MAXP);
writeTableName(OFTableName.NAME);
writeTableName(OFTableName.POST);
if (hasPrep()) {
writeTableName(OFTableName.PREP);
}
newDirTabs.put(OFTableName.TABLE_DIRECTORY, new OFDirTabEntry(0, currentPos));
}
private void writeTableName(OFTableName tableName) {
writeString(tableName.getName());
offsets.put(tableName, currentPos);
currentPos += 12;
realSize += 16;
}
private boolean hasCvt() {
return dirTabs.containsKey(OFTableName.CVT);
}
private boolean hasFpgm() {
return dirTabs.containsKey(OFTableName.FPGM);
}
private boolean hasPrep() {
return dirTabs.containsKey(OFTableName.PREP);
}
protected void createLoca(int size) throws IOException {
pad4();
locaOffset = currentPos;
int dirTableOffset = offsets.get(OFTableName.LOCA);
writeULong(dirTableOffset + 4, currentPos);
writeULong(dirTableOffset + 8, size * 4 + 4);
currentPos += size * 4 + 4;
realSize += size * 4 + 4;
}
private boolean copyTable(FontFileReader in, OFTableName tableName) throws IOException {
OFDirTabEntry entry = dirTabs.get(tableName);
if (entry != null) {
pad4();
seekTab(in, tableName, 0);
writeBytes(in.getBytes((int) entry.getOffset(), (int) entry.getLength()));
updateCheckSum(currentPos, (int) entry.getLength(), tableName);
currentPos += (int) entry.getLength();
realSize += (int) entry.getLength();
return true;
} else {
return false;
}
}
protected boolean createCvt(FontFileReader in) throws IOException {
return copyTable(in, OFTableName.CVT);
}
protected boolean createFpgm(FontFileReader in) throws IOException {
return copyTable(in, OFTableName.FPGM);
}
protected boolean createName(FontFileReader in) throws IOException {
return copyTable(in, OFTableName.NAME);
}
protected boolean createOS2(FontFileReader in) throws IOException {
return copyTable(in, OFTableName.OS2);
}
protected void createMaxp(FontFileReader in, int size) throws IOException {
OFTableName maxp = OFTableName.MAXP;
OFDirTabEntry entry = dirTabs.get(maxp);
if (entry != null) {
pad4();
seekTab(in, maxp, 0);
writeBytes(in.getBytes((int) entry.getOffset(), (int) entry.getLength()));
writeUShort(currentPos + 4, size);
updateCheckSum(currentPos, (int)entry.getLength(), maxp);
currentPos += (int)entry.getLength();
realSize += (int)entry.getLength();
} else {
throw new IOException("Can't find maxp table");
}
}
protected void createPost(FontFileReader in) throws IOException {
OFTableName post = OFTableName.POST;
OFDirTabEntry entry = dirTabs.get(post);
if (entry != null) {
pad4();
seekTab(in, post, 0);
int newTableSize = 32;
byte[] newPostTable = new byte[newTableSize];
System.arraycopy(in.getBytes((int) entry.getOffset(), newTableSize),
0, newPostTable, 0, newTableSize);
newPostTable[1] = 0x03;
writeBytes(newPostTable);
updateCheckSum(currentPos, newTableSize, post);
currentPos += newTableSize;
realSize += newTableSize;
} else {
}
}
protected boolean createPrep(FontFileReader in) throws IOException {
return copyTable(in, OFTableName.PREP);
}
protected void createHhea(FontFileReader in, int size) throws IOException {
OFDirTabEntry entry = dirTabs.get(OFTableName.HHEA);
if (entry != null) {
pad4();
seekTab(in, OFTableName.HHEA, 0);
writeBytes(in.getBytes((int) entry.getOffset(), (int) entry.getLength()));
writeUShort((int) entry.getLength() + currentPos - 2, size);
updateCheckSum(currentPos, (int) entry.getLength(), OFTableName.HHEA);
currentPos += (int) entry.getLength();
realSize += (int) entry.getLength();
} else {
throw new IOException("Can't find hhea table");
}
}
protected void createHead(FontFileReader in) throws IOException {
OFTableName head = OFTableName.HEAD;
OFDirTabEntry entry = dirTabs.get(head);
if (entry != null) {
pad4();
seekTab(in, head, 0);
writeBytes(in.getBytes((int) entry.getOffset(), (int) entry.getLength()));
checkSumAdjustmentOffset = currentPos + 8;
output[currentPos + 8] = 0;
output[currentPos + 9] = 0;
output[currentPos + 10] = 0;
output[currentPos + 11] = 0;
output[currentPos + 50] = 0;
if (cid) {
output[currentPos + 51] = 1;
}
updateCheckSum(currentPos, (int)entry.getLength(), head);
currentPos += (int)entry.getLength();
realSize += (int)entry.getLength();
} else {
throw new IOException("Can't find head table");
}
}
private void createGlyf(FontFileReader in,
Map<Integer, Integer> glyphs) throws IOException {
OFTableName glyf = OFTableName.GLYF;
OFDirTabEntry entry = dirTabs.get(glyf);
int size = 0;
int startPos = 0;
int endOffset = 0;
if (entry != null) {
pad4();
startPos = currentPos;
int[] origIndexes = buildSubsetIndexToOrigIndexMap(glyphs);
glyphOffsets = new int[origIndexes.length];
for (int i = 0; i < origIndexes.length; i++) {
int nextOffset = 0;
int origGlyphIndex = origIndexes[i];
if (origGlyphIndex >= (mtxTab.length - 1)) {
nextOffset = (int)lastLoca;
} else {
nextOffset = (int)mtxTab[origGlyphIndex + 1].getOffset();
}
int glyphOffset = (int)mtxTab[origGlyphIndex].getOffset();
int glyphLength = nextOffset - glyphOffset;
byte[] glyphData = in.getBytes(
(int)entry.getOffset() + glyphOffset,
glyphLength);
int endOffset1 = endOffset;
writeBytes(glyphData);
writeULong(locaOffset + i * 4, currentPos - startPos);
if ((currentPos - startPos + glyphLength) > endOffset1) {
endOffset1 = (currentPos - startPos + glyphLength);
}
glyphOffsets[i] = currentPos;
currentPos += glyphLength;
realSize += glyphLength;
endOffset = endOffset1;
}
size = currentPos - startPos;
currentPos += 12;
realSize += 12;
updateCheckSum(startPos, size + 12, glyf);
writeULong(locaOffset + glyphs.size() * 4, endOffset);
int locaSize = glyphs.size() * 4 + 4;
int checksum = getCheckSum(output, locaOffset, locaSize);
writeULong(offsets.get(OFTableName.LOCA), checksum);
int padSize = (locaOffset + locaSize) % 4;
newDirTabs.put(OFTableName.LOCA,
new OFDirTabEntry(locaOffset, locaSize + padSize));
} else {
throw new IOException("Can't find glyf table");
}
}
protected int[] buildSubsetIndexToOrigIndexMap(Map<Integer, Integer> glyphs) {
int[] origIndexes = new int[glyphs.size()];
for (Map.Entry<Integer, Integer> glyph : glyphs.entrySet()) {
int origIndex = glyph.getKey();
int subsetIndex = glyph.getValue();
if (origIndexes.length > subsetIndex) {
origIndexes[subsetIndex] = origIndex;
}
}
return origIndexes;
}
protected void createHmtx(FontFileReader in,
Map<Integer, Integer> glyphs) throws IOException {
OFTableName hmtx = OFTableName.HMTX;
OFDirTabEntry entry = dirTabs.get(hmtx);
int longHorMetricSize = glyphs.size() * 2;
int leftSideBearingSize = glyphs.size() * 2;
int hmtxSize = longHorMetricSize + leftSideBearingSize;
if (entry != null) {
pad4();
for (Map.Entry<Integer, Integer> glyph : glyphs.entrySet()) {
Integer origIndex = glyph.getKey();
Integer subsetIndex = glyph.getValue();
writeUShort(currentPos + subsetIndex * 4,
mtxTab[origIndex].getWx());
writeUShort(currentPos + subsetIndex * 4 + 2,
mtxTab[origIndex].getLsb());
}
updateCheckSum(currentPos, hmtxSize, hmtx);
currentPos += hmtxSize;
realSize += hmtxSize;
} else {
throw new IOException("Can't find hmtx table");
}
}
public void readFont(FontFileReader in, String name, String header,
Map<Integer, Integer> glyphs) throws IOException {
fontFile = in;
if (!checkTTC(header, name)) {
throw new IOException("Failed to read font");
}
Map<Integer, Integer> subsetGlyphs = new HashMap<Integer, Integer>(glyphs);
output = new byte[in.getFileSize()];
readDirTabs();
readFontHeader();
getNumGlyphs();
readHorizontalHeader();
readHorizontalMetrics();
readIndexToLocation();
scanGlyphs(in, subsetGlyphs);
createDirectory();
boolean optionalTableFound;
optionalTableFound = createCvt(in);
if (!optionalTableFound) {
log.debug("TrueType: ctv table not present. Skipped.");
}
optionalTableFound = createFpgm(in);
if (!optionalTableFound) {
log.debug("TrueType: fpgm table not present. Skipped.");
}
createLoca(subsetGlyphs.size());
createGlyf(in, subsetGlyphs);
createOS2(in);
createHead(in);
createHhea(in, subsetGlyphs.size());
createHmtx(in, subsetGlyphs);
createMaxp(in, subsetGlyphs.size());
createName(in);
createPost(in);
optionalTableFound = createPrep(in);
if (!optionalTableFound) {
log.debug("TrueType: prep table not present. Skipped.");
}
pad4();
createCheckSumAdjustment();
}
public byte[] getFontSubset() {
byte[] ret = new byte[realSize];
System.arraycopy(output, 0, ret, 0, realSize);
return ret;
}
private void handleGlyphSubset(TTFGlyphOutputStream glyphOut) throws IOException {
glyphOut.startGlyphStream();
for (int i = 0; i < glyphOffsets.length - 1; i++) {
glyphOut.streamGlyph(output, glyphOffsets[i],
glyphOffsets[i + 1] - glyphOffsets[i]);
}
OFDirTabEntry glyf = newDirTabs.get(OFTableName.GLYF);
long lastGlyphLength = glyf.getLength()
- (glyphOffsets[glyphOffsets.length - 1] - glyf.getOffset());
glyphOut.streamGlyph(output, glyphOffsets[glyphOffsets.length - 1],
(int) lastGlyphLength);
glyphOut.endGlyphStream();
}
@Override
public void stream(TTFOutputStream ttfOut) throws IOException {
SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortedDirTabs
= sortDirTabMap(newDirTabs);
TTFTableOutputStream tableOut = ttfOut.getTableOutputStream();
TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream();
ttfOut.startFontStream();
for (Map.Entry<OFTableName, OFDirTabEntry> entry : sortedDirTabs) {
if (entry.getKey().equals(OFTableName.GLYF)) {
handleGlyphSubset(glyphOut);
} else {
tableOut.streamTable(output, (int) entry.getValue().getOffset(),
(int) entry.getValue().getLength());
}
}
ttfOut.endFontStream();
}
protected void scanGlyphs(FontFileReader in, Map<Integer, Integer> subsetGlyphs)
throws IOException {
OFDirTabEntry glyfTableInfo = dirTabs.get(OFTableName.GLYF);
if (glyfTableInfo == null) {
throw new IOException("Glyf table could not be found");
}
GlyfTable glyfTable = new GlyfTable(in, mtxTab, glyfTableInfo, subsetGlyphs);
glyfTable.populateGlyphsWithComposites();
}
private int writeString(String str) {
int length = 0;
try {
byte[] buf = str.getBytes("ISO-8859-1");
writeBytes(buf);
length = buf.length;
currentPos += length;
} catch (java.io.UnsupportedEncodingException e) {
}
return length;
}
private void writeByte(byte b) {
output[currentPos++] = b;
}
protected void writeBytes(byte[] b) {
if (b.length + currentPos > output.length) {
byte[] newoutput = new byte[output.length * 2];
System.arraycopy(output, 0, newoutput, 0, output.length);
output = newoutput;
}
System.arraycopy(b, 0, output, currentPos, b.length);
}
protected void writeUShort(int s) {
byte b1 = (byte)((s >> 8) & 0xff);
byte b2 = (byte)(s & 0xff);
writeByte(b1);
writeByte(b2);
}
protected void writeUShort(int pos, int s) {
byte b1 = (byte)((s >> 8) & 0xff);
byte b2 = (byte)(s & 0xff);
output[pos] = b1;
output[pos + 1] = b2;
}
protected void writeULong(int pos, int s) {
byte b1 = (byte)((s >> 24) & 0xff);
byte b2 = (byte)((s >> 16) & 0xff);
byte b3 = (byte)((s >> 8) & 0xff);
byte b4 = (byte)(s & 0xff);
output[pos] = b1;
output[pos + 1] = b2;
output[pos + 2] = b3;
output[pos + 3] = b4;
}
protected void pad4() {
int padSize = getPadSize(currentPos);
if (padSize < 4) {
for (int i = 0; i < padSize; i++) {
output[currentPos++] = 0;
realSize++;
}
}
}
private int maxPow2(int max) {
int i = 0;
while (Math.pow(2, i) <= max) {
i++;
}
return (i - 1);
}
protected void updateCheckSum(int tableStart, int tableSize, OFTableName tableName) {
int checksum = getCheckSum(output, tableStart, tableSize);
int offset = offsets.get(tableName);
int padSize = getPadSize(tableStart + tableSize);
newDirTabs.put(tableName, new OFDirTabEntry(tableStart, tableSize + padSize));
writeULong(offset, checksum);
writeULong(offset + 4, tableStart);
writeULong(offset + 8, tableSize);
}
protected static int getCheckSum(byte[] data, int start, int size) {
int remainder = size % 4;
if (remainder != 0) {
size += remainder;
}
long sum = 0;
for (int i = 0; i < size; i += 4) {
long l = 0;
for (int j = 0; j < 4; j++) {
l <<= 8;
if (data.length > (start + i + j)) {
l |= data[start + i + j] & 0xff;
}
}
sum += l;
}
return (int) sum;
}
protected void createCheckSumAdjustment() {
long sum = getCheckSum(output, 0, realSize);
int checksum = (int)(0xb1b0afba - sum);
writeULong(checkSumAdjustmentOffset, checksum);
}
}