You are viewing a plain text version of this content. The canonical link for it is here.
Posted to fop-commits@xmlgraphics.apache.org by fo...@apache.org on 2001/03/23 09:41:34 UTC
cvs commit: xml-fop/src/org/apache/fop/fonts TTFSubSetFile.java
fotis 01/03/23 00:41:34
Added: src/org/apache/fop/fonts TTFSubSetFile.java
Log:
subset fonts embedding [submitted by Tore Engvig]
Revision Changes Path
1.1 xml-fop/src/org/apache/fop/fonts/TTFSubSetFile.java
Index: TTFSubSetFile.java
===================================================================
/* -- $Id: TTFSubSetFile.java,v 1.1 2001/03/23 08:41:34 fotis Exp $
*
* Copyright (C) 2001 The Apache Software Foundation. All rights reserved.
* For details on use and redistribution please refer to the
* LICENSE file included with these sources."
*/
package org.apache.fop.fonts;
import java.io.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
/**
* Reads a TrueType file and generates a subset
* That can be used to embed a TrueType CID font
* TrueType tables needed for embedded CID fonts are:
* "head", "hhea", "loca", "maxp", "cvt ", "prep", "glyf", "hmtx" and "fpgm"
* The TrueType spec can be found at the Microsoft
* Typography site: http://www.microsoft.com/truetype/
*/
public class TTFSubSetFile extends TTFFile {
byte[] output = null;
int realSize = 0;
int currentPos = 0;
/* Offsets in name table to be filled out by table.
The offsets are to the checkSum field */
int cvtDirOffset = 0;
int fpgmDirOffset = 0;
int glyfDirOffset = 0;
int headDirOffset = 0;
int hheaDirOffset = 0;
int hmtxDirOffset = 0;
int locaDirOffset = 0;
int maxpDirOffset = 0;
int prepDirOffset = 0;
int checkSumAdjustmentOffset = 0;
int locaOffset = 0;
/**
* Initalize the output array
*/
private void init(int size) {
output = new byte[size];
realSize = 0;
currentPos = 0;
// createDirectory()
}
/**
Create the directory table
*/
private void createDirectory() {
int numTables = 9;
// Create the TrueType header
writeByte((byte)0);
writeByte((byte)1);
writeByte((byte)0);
writeByte((byte)0);
realSize+=4;
writeUShort(numTables);
realSize += 2;
// Create searchRange, entrySelector and rangeShift
int maxPow = maxPow2(numTables);
int searchRange = maxPow*16;
writeUShort(searchRange);
realSize += 2;
writeUShort(maxPow);
realSize += 2;
writeUShort((numTables*16) - searchRange);
realSize += 2;
// Create space for the table entries
writeString("cvt ");
cvtDirOffset = currentPos;
currentPos+=12;
realSize+=16;
writeString("fpgm");
fpgmDirOffset = currentPos;
currentPos+=12;
realSize+=16;
writeString("glyf");
glyfDirOffset = currentPos;
currentPos+=12;
realSize+=16;
writeString("head");
headDirOffset = currentPos;
currentPos+=12;
realSize+=16;
writeString("hhea");
hheaDirOffset = currentPos;
currentPos+=12;
realSize+=16;
writeString("hmtx");
hmtxDirOffset = currentPos;
currentPos+=12;
realSize+=16;
writeString("loca");
locaDirOffset = currentPos;
currentPos+=12;
realSize+=16;
writeString("maxp");
maxpDirOffset = currentPos;
currentPos+=12;
realSize+=16;
writeString("prep");
prepDirOffset = currentPos;
currentPos+=12;
realSize+=16;
}
/**
* Copy the cvt table as is from original font to subset font
*/
private void createCvt(FontFileReader in) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("cvt ");
if (entry != null) {
pad4();
seek_tab(in, "cvt ", 0);
System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
0, output, currentPos, (int)entry.length);
int checksum = getCheckSum(currentPos, (int)entry.length);
writeULong(cvtDirOffset, checksum);
writeULong(cvtDirOffset+4, currentPos);
writeULong(cvtDirOffset+8, (int)entry.length);
currentPos+=(int)entry.length;
realSize+=(int)entry.length;
} else {
throw new IOException ("Can't find cvt table");
}
}
/**
* Copy the fpgm table as is from original font to subset font
*/
private void createFpgm(FontFileReader in) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("fpgm");
if (entry != null) {
pad4();
seek_tab(in, "fpgm", 0);
System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
0, output, currentPos, (int)entry.length);
int checksum = getCheckSum(currentPos, (int)entry.length);
writeULong(fpgmDirOffset, checksum);
writeULong(fpgmDirOffset+4, currentPos);
writeULong(fpgmDirOffset+8, (int)entry.length);
currentPos+=(int)entry.length;
realSize+=(int)entry.length;
} else {
throw new IOException ("Can't find fpgm table");
}
}
/**
* Create an empty loca table without updating checksum
*/
private void createLoca(int size) throws IOException {
pad4();
locaOffset = currentPos;
writeULong(locaDirOffset+4, currentPos);
writeULong(locaDirOffset+8, size*4+4);
currentPos+=size*4+4;
realSize+=size*4+4;
}
/**
* Copy the maxp table as is from original font to subset font
* and set num glyphs to size
*/
private void createMaxp(FontFileReader in, int size) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("maxp");
if (entry != null) {
pad4();
seek_tab(in, "maxp", 0);
System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
0, output, currentPos, (int)entry.length);
writeUShort(currentPos+4, size);
int checksum = getCheckSum(currentPos, (int)entry.length);
writeULong(maxpDirOffset, checksum);
writeULong(maxpDirOffset+4, currentPos);
writeULong(maxpDirOffset+8, (int)entry.length);
currentPos+=(int)entry.length;
realSize+=(int)entry.length;
} else {
throw new IOException ("Can't find maxp table");
}
}
/**
* Copy the prep table as is from original font to subset font
*/
private void createPrep(FontFileReader in) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("prep");
if (entry != null) {
pad4();
seek_tab(in, "prep", 0);
System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
0, output, currentPos, (int)entry.length);
int checksum = getCheckSum(currentPos, (int)entry.length);
writeULong(prepDirOffset, checksum);
writeULong(prepDirOffset+4, currentPos);
writeULong(prepDirOffset+8, (int)entry.length);
currentPos+=(int)entry.length;
realSize+=(int)entry.length;
} else {
throw new IOException ("Can't find prep table");
}
}
/**
* Copy the hhea table as is from original font to subset font
* and fill in size of hmtx table
*/
private void createHhea(FontFileReader in, int size) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hhea");
if (entry != null) {
pad4();
seek_tab(in, "hhea", 0);
System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
0, output, currentPos, (int)entry.length);
writeUShort((int)entry.length + currentPos-2, size);
int checksum = getCheckSum(currentPos, (int)entry.length);
writeULong(hheaDirOffset, checksum);
writeULong(hheaDirOffset+4, currentPos);
writeULong(hheaDirOffset+8, (int)entry.length);
currentPos+=(int)entry.length;
realSize+=(int)entry.length;
} else {
throw new IOException ("Can't find hhea table");
}
}
/**
* Copy the head table as is from original font to subset font
* and set indexToLocaFormat to long and set
* checkSumAdjustment to 0, store offset to checkSumAdjustment
* in checkSumAdjustmentOffset
*/
private void createHead(FontFileReader in) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("head");
if (entry != null) {
pad4();
seek_tab(in, "head", 0);
System.arraycopy(in.getBytes((int)entry.offset, (int)entry.length),
0, output, currentPos, (int)entry.length);
checkSumAdjustmentOffset = currentPos + 8;
output[currentPos+8] = 0; // Set checkSumAdjustment to 0
output[currentPos+9] = 0;
output[currentPos+10] = 0;
output[currentPos+11] = 0;
output[currentPos+(int)entry.length-2] = 0; // long locaformat
int checksum = getCheckSum(currentPos, (int)entry.length);
writeULong(headDirOffset, checksum);
writeULong(headDirOffset+4, currentPos);
writeULong(headDirOffset+8, (int)entry.length);
currentPos+=(int)entry.length;
realSize+=(int)entry.length;
} else {
throw new IOException ("Can't find head table");
}
}
/**
* Create the glyf table and fill in loca table
*/
private void createGlyf(FontFileReader in, Hashtable glyphs)
throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf");
int size = 0;
int start = 0;
int endOffset = 0; // Store this as the last loca
if (entry != null) {
pad4();
start = currentPos;
for (Enumeration e = glyphs.keys(); e.hasMoreElements();) {
int glyphLength = 0;
Integer origIndex = (Integer)e.nextElement();
Integer subsetIndex = (Integer)glyphs.get(origIndex);
int nextOffset = 0;
if (origIndex.intValue() >= (mtx_tab.length-1))
nextOffset = (int)lastLoca;
else
nextOffset = (int)mtx_tab[origIndex.intValue()+1].offset;
glyphLength = nextOffset -
(int)mtx_tab[origIndex.intValue()].offset;
// Copy glyph
System.arraycopy(in.getBytes((int)entry.offset +
(int)mtx_tab[origIndex.intValue()].offset,
glyphLength),
0, output,
currentPos,
glyphLength);
// Update loca table
writeULong(locaOffset + subsetIndex.intValue()*4,
currentPos-start);
if ((currentPos - start + glyphLength) > endOffset)
endOffset = (currentPos - start + glyphLength);
currentPos+=glyphLength;
realSize+=glyphLength;
}
size = currentPos - start;
int checksum = getCheckSum(currentPos, size);
writeULong(glyfDirOffset, checksum);
writeULong(glyfDirOffset+4, start);
writeULong(glyfDirOffset+8, size);
currentPos+=12;
realSize+=12;
// Update loca checksum and last loca index
writeULong(locaOffset + glyphs.size()*4,
endOffset);
checksum = getCheckSum(locaOffset, glyphs.size()*4+4);
writeULong(locaDirOffset, checksum);
} else {
throw new IOException ("Can't find glyf table");
}
}
/**
* Create the hmtx table by copying metrics from original
* font to subset font. The glyphs hashtable contains an
* Integer key and Integer value that maps the original
* metric (key) to the subset metric (value)
*/
private void createHmtx(FontFileReader in, Hashtable glyphs)
throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hmtx");
int longHorMetricSize = glyphs.size()*2;
int leftSideBearingSize = glyphs.size()*2;
int hmtxSize = longHorMetricSize + leftSideBearingSize;
if (entry != null) {
pad4();
int offset = (int)entry.offset;
for (Enumeration e = glyphs.keys(); e.hasMoreElements();) {
Integer origIndex = (Integer)e.nextElement();
Integer subsetIndex = (Integer)glyphs.get(origIndex);
writeUShort(currentPos+subsetIndex.intValue()*4,
mtx_tab[origIndex.intValue()].wx);
writeUShort(currentPos+subsetIndex.intValue()*4+2,
mtx_tab[origIndex.intValue()].lsb);
}
int checksum = getCheckSum(currentPos, hmtxSize);
writeULong(hmtxDirOffset, checksum);
writeULong(hmtxDirOffset+4, currentPos);
writeULong(hmtxDirOffset+8, hmtxSize);
currentPos+=hmtxSize;
realSize+=hmtxSize;
} else {
throw new IOException ("Can't find hmtx table");
}
}
/**
* Returns a Vector containing the glyph itself plus all glyphs
* that this composite glyph uses
*/
private Vector getIncludedGlyphs(FontFileReader in,
int glyphOffset, Integer glyphIdx)
throws IOException {
Vector ret = new Vector();
ret.addElement(glyphIdx);
int offset = glyphOffset +
(int)mtx_tab[glyphIdx.intValue()].offset + 10;
Integer compositeIdx = null;
int flags = 0;
boolean moreComposites = true;
while (moreComposites) {
flags = in.readTTFUShort(offset);
compositeIdx = new Integer(in.readTTFUShort(offset+2));
ret.addElement(compositeIdx);
offset+=4;
if ((flags & 1) > 0) {
// ARG_1_AND_ARG_2_ARE_WORDS
offset+=4;
} else {
offset+=2;
}
if ((flags & 8) > 0)
offset+=2; // WE_HAVE_A_SCALE
else if ((flags & 64) > 0)
offset+=4; // WE_HAVE_AN_X_AND_Y_SCALE
else if ((flags & 128) > 0)
offset+=8; // WE_HAVE_A_TWO_BY_TWO
if ((flags & 32) > 0)
moreComposites = true;
else
moreComposites = false;
}
return ret;
}
/**
* Rewrite all compositepointers in glyphindex glyphIdx
*
*/
private void remapComposite(FontFileReader in, Hashtable glyphs,
int glyphOffset, Integer glyphIdx)
throws IOException {
int offset = glyphOffset +
(int)mtx_tab[glyphIdx.intValue()].offset + 10;
Integer compositeIdx = null;
int flags = 0;
boolean moreComposites = true;
while (moreComposites) {
flags = in.readTTFUShort(offset);
compositeIdx = new Integer(in.readTTFUShort(offset+2));
Integer newIdx = (Integer)glyphs.get(compositeIdx);
in.writeTTFUShort(offset+2, newIdx.intValue());
offset+=4;
if ((flags & 1) > 0) {
// ARG_1_AND_ARG_2_ARE_WORDS
offset+=4;
} else {
offset+=2;
}
if ((flags & 8) > 0 ) {
offset+=2; // WE_HAVE_A_SCALE
} else if ((flags & 64) > 0) {
offset+=4; // WE_HAVE_AN_X_AND_Y_SCALE
} else if ((flags & 128) > 0) {
offset+=8; // WE_HAVE_A_TWO_BY_TWO
}
if ((flags & 32) > 0)
moreComposites = true;
else
moreComposites = false;
}
}
/**
* Scan all the original glyphs for composite glyphs and add those glyphs
* to the glyphmapping also rewrite the composite glyph pointers to the new
* mapping
*/
private void scanGlyphs(FontFileReader in, Hashtable glyphs) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf");
Hashtable newComposites = null;
Hashtable allComposites = new Hashtable();
int newIndex = glyphs.size();
if (entry != null) {
while (newComposites == null || newComposites.size() > 0) {
// Inefficient to iterate through all glyphs
newComposites = new Hashtable();
for (Enumeration e = glyphs.keys(); e.hasMoreElements();) {
Integer origIndex = (Integer)e.nextElement();
if (in.readTTFShort(entry.offset +
mtx_tab[origIndex.intValue()].offset) < 0) {
// origIndex is a composite glyph
allComposites.put(origIndex, glyphs.get(origIndex));
Vector composites = getIncludedGlyphs(in, (int)entry.offset,
origIndex);
// Iterate through all composites pointed to
// by this composite and check if they exists
// in the glyphs map, add them if not.
for (Enumeration cps=composites.elements();
cps.hasMoreElements();) {
Integer cIdx =(Integer)cps.nextElement();
if (glyphs.get(cIdx) == null &&
newComposites.get(cIdx) == null) {
newComposites.put(cIdx, new Integer(newIndex));
newIndex++;
}
}
}
}
// Add composites to glyphs
for (Enumeration m = newComposites.keys(); m.hasMoreElements();) {
Integer im = (Integer)m.nextElement();
glyphs.put(im, newComposites.get(im));
}
}
// Iterate through all composites to remap their composite index
for (Enumeration ce = allComposites.keys(); ce.hasMoreElements();) {
remapComposite(in, glyphs, (int)entry.offset,
(Integer)ce.nextElement());
}
} else {
throw new IOException ("Can't find glyf table");
}
}
/**
* glyphs has old index as (Integer) key and new index
* as (Integer) value
*/
public byte[] readFont(FontFileReader in, String name,
Hashtable glyphs) throws IOException {
/* Check if TrueType collection, and that the name
exists in the collection
*/
if (!checkTTC(in, name, false))
throw new IOException("Failed to read font");
output = new byte[in.getFileSize()];
readDirTabs(in);
readFontHeader(in);
getNumGlyphs(in);
readHorizontalHeader(in);
readHorizontalMetrics(in);
readIndexToLocation(in);
scanGlyphs(in, glyphs);
createDirectory(); // Create the TrueType header and directory
createCvt(in); // copy the cvt table
createFpgm(in); // copy fpgm table
createHead(in);
createHhea(in, glyphs.size()); // Create the hhea table
createHmtx(in, glyphs); // Create hmtx table
createMaxp(in, glyphs.size()); // copy the maxp table
createPrep(in); // copy prep table
createLoca(glyphs.size()); // create empty loca table
createGlyf(in, glyphs);
pad4();
createCheckSumAdjustment();
byte[] ret = new byte[realSize];
System.arraycopy(output, 0, ret, 0, realSize);
return ret;
}
/**
* writes a ISO-8859-1 string at the currentPosition
* updates currentPosition but not realSize
* @return number of bytes written
*/
private int writeString(String str) {
int length = 0;
try {
byte[] buf = str.getBytes("ISO-8859-1");
System.arraycopy(buf, 0, output, currentPos, buf.length);
length = buf.length;
currentPos += length;
} catch (Exception e) {
// This should never happen!
}
return length;
}
/**
Appends a byte to the output array,
updates currentPost but not realSize
*/
private void writeByte(byte b) {
output[currentPos++] = b;
}
/**
Appends a USHORT to the output array,
updates currentPost but not realSize
*/
private void writeUShort(int s) {
byte b1 = (byte)((s >> 8) & 0xff);
byte b2 = (byte) (s & 0xff);
writeByte(b1);
writeByte(b2);
}
/**
Appends a USHORT to the output array,
at the given position without changing currentPos
*/
private void writeUShort(int pos, int s) {
byte b1 = (byte)((s >> 8) & 0xff);
byte b2 = (byte) (s & 0xff);
output[pos] = b1;
output[pos+1] = b2;
}
/**
Appends a ULONG to the output array,
updates currentPos but not realSize
*/
private void writeULong(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);
writeByte(b1);
writeByte(b2);
writeByte(b3);
writeByte(b4);
}
/**
Appends a ULONG to the output array,
at the given position without changing currentPos
*/
private 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;
}
/** Read a signed short value at given position
*/
private short readShort(int pos) {
int ret = readUShort(pos);
return (short)ret;
}
/** Read a unsigned short value at given position
*/
private int readUShort(int pos) {
int ret = (int)output[pos];
if (ret < 0)
ret+=256;
ret = ret << 8;
if ((int)output[pos+1] < 0) {
ret |= (int)output[pos+1]+256;
} else
ret |= (int)output[pos+1];
return ret;
}
/**
* Create a padding in the fontfile to align
* on a 4-byte boundary
*/
private void pad4() {
int padSize = currentPos % 4;
for (int i = 0; i < padSize; i++) {
output[currentPos++] = 0;
realSize++;
}
}
/**
* Returns the maximum power of 2 <= max
*/
private int maxPow2(int max) {
int i=0;
while (Math.pow(2, (double)i) < max)
i++;
return (i-1);
}
private int log2(int num) {
return (int)(Math.log((double)num)/Math.log(2));
}
private int getCheckSum(int start, int size) {
return (int)getLongCheckSum(start, size);
}
private long getLongCheckSum(int start, int size) {
// All the tables here are aligned on four byte boundaries
// Add remainder to size if it's not a multiple of 4
int remainder = size % 4;
if (remainder != 0)
size += remainder;
long sum = 0;
for (int i = 0; i < size; i+=4) {
int l = (int)(output[start+i] << 24);
l += (int)(output[start+i+1] << 16);
l += (int)(output[start+i+2] << 16);
l += (int)(output[start+i+3] << 16);
sum += l;
if (sum > 0xffffffff)
sum = sum - 0xffffffff;
}
return sum;
}
private void createCheckSumAdjustment() {
long sum = getLongCheckSum(0, realSize);
int checksum = (int)(0xb1b0afba - sum);
writeULong(checkSumAdjustmentOffset, checksum);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: fop-cvs-unsubscribe@xml.apache.org
For additional commands, e-mail: fop-cvs-help@xml.apache.org