blob: 9bb870822c1ebfa28f3146e034ee1336396d55ea [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.fop.render.pdf.pdfbox;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.GlyfTable;
import org.apache.fop.fonts.truetype.OFDirTabEntry;
import org.apache.fop.fonts.truetype.OFMtxEntry;
import org.apache.fop.fonts.truetype.OFTableName;
import org.apache.fop.fonts.truetype.TTFSubSetFile;
public class MergeTTFonts extends TTFSubSetFile implements MergeFonts {
private Map<Integer, Glyph> added = new TreeMap<Integer, Glyph>();
private int origIndexesLen;
private int size;
protected MaximumProfileTable maxp = new MaximumProfileTable();
private Integer nhmtxDiff = null;
private List<Cmap> cmap;
static class Glyph {
final byte[] data;
final OFMtxEntry mtx;
Glyph(byte[] d, OFMtxEntry m) {
data = d;
mtx = m;
}
}
public MergeTTFonts(List<Cmap> cmap) {
this.cmap = cmap;
}
/**
* Create the glyf table and fill in loca table
* @param glyphs map of glyphs
* @param in fontfile
* @throws IOException on error
*/
private void readGlyf(Map<Integer, Integer> glyphs, FontFileReader in) throws IOException {
OFDirTabEntry entry = dirTabs.get(OFTableName.GLYF);
if (entry != null) {
int[] origIndexes = buildSubsetIndexToOrigIndexMap(glyphs);
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;
if (glyphLength < 0) {
continue;
}
byte[] glyphData = in.getBytes(
(int)entry.getOffset() + glyphOffset,
glyphLength);
Glyph g = new Glyph(glyphData, mtxTab[origGlyphIndex]);
if (!cid && (origIndexesLen == 0 || (glyphLength > 0 && i > 0))) {
added.put(i, g);
} else if (cid) {
added.put(i + origIndexesLen, g);
}
}
if (!cid) {
origIndexesLen = origIndexes.length;
} else {
origIndexesLen += origIndexes.length;
}
} else {
throw new IOException("Can't find glyf table");
}
}
private void createGlyf() throws IOException {
OFDirTabEntry entry = dirTabs.get(OFTableName.GLYF);
int size = 0;
int startPos = 0;
int endOffset = 0; // Store this as the last loca
if (entry != null) {
pad4();
startPos = currentPos;
/* Loca table must be in order by glyph index, so build
* an array first and then write the glyph info and
* location offset.
*/
glyphOffsets = new int[origIndexesLen];
for (Map.Entry<Integer, Glyph> gly : added.entrySet()) {
byte[] glyphData = gly.getValue().data;
int glyphLength = glyphData.length;
int i = gly.getKey();
if (i >= origIndexesLen) {
continue;
}
int endOffset1 = endOffset;
// Copy glyph
writeBytes(glyphData);
// Update loca table
if (cid || locaFormat == 1) {
writeULong(locaOffset + i * 4, currentPos - startPos);
}
if ((currentPos - startPos + glyphLength) > endOffset1) {
endOffset1 = currentPos - startPos + glyphLength;
}
// Store the glyph boundary positions relative to the start of the font
glyphOffsets[i] = currentPos;
currentPos += glyphLength;
realSize += glyphLength;
endOffset = endOffset1;
}
size = currentPos - startPos;
currentPos += 12;
realSize += 12;
updateCheckSum(startPos, size + 12, OFTableName.GLYF);
// Update loca checksum and last loca index
if (cid || locaFormat == 1) {
writeULong(locaOffset + added.size() * 4, endOffset);
}
int locaSize = added.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));
if (!cid && locaFormat == 0) {
int i = 0;
int offset = 0;
for (Glyph e : added.values()) {
writeUShort(locaOffset + i * 2, offset / 2);
offset += e.data.length;
i++;
}
writeUShort(locaOffset + i * 2, offset / 2);
}
} else {
throw new IOException("Can't find glyf table");
}
}
/**
* Create the hmtx table by copying metrics from original
* font to subset font. The glyphs Map contains an
* Integer key and Integer value that maps the original
* metric (key) to the subset metric (value)
* @throws IOException on error
*/
protected void createHmtx() throws IOException {
OFTableName hmtx = OFTableName.HMTX;
OFDirTabEntry entry = dirTabs.get(hmtx);
if (entry != null) {
pad4();
// int offset = (int)entry.offset;
int longHorMetricSize = added.size() * 2;
int leftSideBearingSize = added.size() * 2;
int hmtxSize = longHorMetricSize + leftSideBearingSize;
for (Map.Entry<Integer, Glyph> e : added.entrySet()) {
Integer subsetIndex = e.getKey();
OFMtxEntry mtx = e.getValue().mtx;
writeUShort(currentPos + subsetIndex * 4,
mtx.getWx());
writeUShort(currentPos + subsetIndex * 4 + 2,
mtx.getLsb());
}
updateCheckSum(currentPos, hmtxSize, hmtx);
currentPos += hmtxSize;
realSize += hmtxSize;
} else {
throw new IOException("Can't find hmtx table");
}
}
/**
* Returns a subset of the original font.
*
* @param is font file
* @param name name
* @param fontContainer fontContainer
* @param subsetGlyphs Map of glyphs (glyphs has old index as (Integer) key and
* new index as (Integer) value)
* @param cid is cid
* @throws IOException in case of an I/O problem
*/
public void readFont(InputStream is, String name, FontContainer fontContainer,
Map<Integer, Integer> subsetGlyphs, boolean cid) throws IOException {
this.cid = cid;
if (subsetGlyphs.isEmpty()) {
return;
}
this.fontFile = new FontFileReader(is);
size += fontFile.getAllBytes().length;
readDirTabs();
readFontHeader();
getNumGlyphs();
readHorizontalHeader();
readHorizontalMetrics();
readIndexToLocation();
int sgsize = subsetGlyphs.size();
if (!cid && subsetGlyphs.size() <= 1) {
for (int i = 0; i < mtxTab.length; i++) {
subsetGlyphs.put(i, i);
}
}
scanGlyphs(fontFile, subsetGlyphs);
readGlyf(subsetGlyphs, fontFile);
if (nhmtxDiff == null) {
nhmtxDiff = sgsize - nhmtx;
if (nhmtxDiff < 0) {
nhmtxDiff = 0;
}
}
}
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");
}
new MergeGlyfTable(in, mtxTab, glyfTableInfo, subsetGlyphs);
}
static class MergeGlyfTable extends GlyfTable {
public MergeGlyfTable(FontFileReader in, OFMtxEntry[] metrics, OFDirTabEntry dirTableEntry,
Map<Integer, Integer> glyphs) throws IOException {
super(in, metrics, dirTableEntry, glyphs);
populateGlyphsWithComposites();
}
@Override
protected void addAllComposedGlyphsToSubset() {
int newIndex = -1;
for (int v : subset.values()) {
if (v > newIndex) {
newIndex = v;
}
}
for (int composedGlyph : composedGlyphs) {
subset.put(composedGlyph, ++newIndex);
}
}
}
public byte[] getMergedFontSubset() throws IOException {
output = new byte[size * 2];
createDirectory(); // Create the TrueType header and directory
int sgsize = added.size();
if (!cid) {
writeCMAP(cmap);
// copyTable(fontFile, OFTableName.CMAP);
}
createHmtx(); // Create hmtx table
createLoca(sgsize); // create empty loca table
createHead(fontFile);
createOS2(fontFile); // copy the OS/2 table
if (!cid) {
createHhea(fontFile, sgsize - nhmtxDiff); // Create the hhea table
} else {
createHhea(fontFile, sgsize); // Create the hhea table
}
if (maxp.getVersion() == 0) {
createMaxp(fontFile, sgsize); // copy the maxp table
} else {
writeMaxp();
}
createCvt(fontFile); // copy the cvt table
createFpgm(fontFile); // copy fpgm table
createPost(fontFile); // copy the post table
createPrep(fontFile); // copy prep table
createName(fontFile); // copy the name table
createGlyf(); //create glyf table and update loca table
pad4();
createCheckSumAdjustment();
return getFontSubset();
}
private void writeMaxp() {
int checksum = currentPos;
pad4();
int startPos = currentPos;
writeUShort((int) maxp.getVersion()); //version
writeUShort(0);
writeUShort(added.size()); //numGlyphs
writeUShort(maxp.getMaxPoints()); //maxPoints
writeUShort(maxp.getMaxContours()); //maxContours
writeUShort(maxp.getMaxCompositePoints()); //maxCompositePoints
writeUShort(maxp.getMaxCompositeContours()); //maxCompositeContours
writeUShort(maxp.getMaxZones()); //maxZones
writeUShort(maxp.getMaxTwilightPoints()); //maxTwilightPoints
writeUShort(maxp.getMaxStorage()); //maxStorage
writeUShort(maxp.getMaxFunctionDefs()); //maxFunctionDefs
writeUShort(maxp.getMaxInstructionDefs()); //maxInstructionDefs
writeUShort(maxp.getMaxStackElements()); //maxStackElements
writeUShort(maxp.getMaxSizeOfInstructions()); //maxSizeOfInstructions
writeUShort(maxp.getMaxComponentElements()); //maxComponentElements
writeUShort(maxp.getMaxComponentDepth()); //maxComponentDepth
updateCheckSum(checksum, currentPos - startPos, OFTableName.MAXP);
realSize += currentPos - startPos;
}
private void writeCMAP(List<Cmap> cmaps) {
mergeUniCmap(cmaps);
int checksum = currentPos;
pad4();
int cmapPos = currentPos;
writeUShort(0); //version
writeUShort(cmaps.size()); //number of tables
int tablesSize = 8 * cmaps.size();
for (int i = 0; i < cmaps.size(); i++) {
Cmap cmap = cmaps.get(i);
writeUShort(cmap.platformId); //platformid
writeUShort(cmap.platformEncodingId); //platformEncodingId
writeULong(currentPos, 4 + tablesSize + getCmapOffset(cmaps, i)); //subTableOffset
currentPos += 4;
}
for (Cmap cmap : cmaps) {
writeUShort(12); //subtableFormat
writeUShort(0);
writeULong(currentPos, (cmap.glyphIdToCharacterCode.size() * 12) + 16);
currentPos += 4;
writeULong(currentPos, 0);
currentPos += 4;
writeULong(currentPos, cmap.glyphIdToCharacterCode.size());
currentPos += 4;
for (Map.Entry<Integer, Integer> g : cmap.glyphIdToCharacterCode.entrySet()) {
writeULong(currentPos, g.getKey());
currentPos += 4;
writeULong(currentPos, g.getKey());
currentPos += 4;
writeULong(currentPos, g.getValue());
currentPos += 4;
}
}
// writeUShort(6); //subtableFormat
// int firstCode = -1;
// int lastCode = -1;
// List<Integer> codes = new ArrayList<Integer>();
// for (Map.Entry<Integer, Integer> g : glyphIdToCharacterCode.entrySet()) {
// if (firstCode < 0) {
// firstCode = g.getKey();
// }
// while (lastCode > 0 && lastCode + 1 < g.getKey()) {
// codes.add(0);
// lastCode++;
// }
// codes.add(g.getValue());
//
// lastCode = g.getKey();
// }
//
// writeUShort((codes.size() * 2) + 6); //length
// writeUShort(0); //version
//
// writeUShort(firstCode); //firstCode
// writeUShort(codes.size()); //entryCount
//
// for (int i : codes) {
// writeUShort(i);
// }
updateCheckSum(checksum, currentPos - cmapPos, OFTableName.CMAP);
realSize += currentPos - cmapPos;
}
private void mergeUniCmap(List<Cmap> cmaps) {
Cmap uniCmap = null;
for (Cmap cmap : cmaps) {
if (cmap.platformId == 3 && cmap.platformEncodingId == 1) {
uniCmap = cmap;
}
}
if (uniCmap != null) {
for (Cmap cmap : cmaps) {
uniCmap.glyphIdToCharacterCode.putAll(cmap.glyphIdToCharacterCode);
}
}
}
private int getCmapOffset(List<Cmap> cmaps, int index) {
int result = 0;
for (int i = 0; i < index; i++) {
Cmap curCmap = cmaps.get(i);
result += (curCmap.glyphIdToCharacterCode.size() * 12) + 16;
}
return result;
}
/**
* Get index from starting at lowest glyph position
* @param glyphs map
* @return index map
*/
protected int[] buildSubsetIndexToOrigIndexMap(Map<Integer, Integer> glyphs) {
int[] origIndexes = new int[glyphs.size()];
int minIndex = Integer.MAX_VALUE;
for (int glyph : glyphs.values()) {
if (minIndex > glyph) {
minIndex = glyph;
}
}
for (Map.Entry<Integer, Integer> glyph : glyphs.entrySet()) {
int origIndex = glyph.getKey();
int subsetIndex = glyph.getValue() - minIndex;
origIndexes[subsetIndex] = origIndex;
}
return origIndexes;
}
public static class Cmap {
int platformId;
int platformEncodingId;
Map<Integer, Integer> glyphIdToCharacterCode = new TreeMap<Integer, Integer>();
public Cmap(int platformID, int platformEncodingID) {
this.platformId = platformID;
this.platformEncodingId = platformEncodingID;
}
}
}