diff options
author | Luis Bernardo <lbernardo@apache.org> | 2013-08-14 22:35:37 +0000 |
---|---|---|
committer | Luis Bernardo <lbernardo@apache.org> | 2013-08-14 22:35:37 +0000 |
commit | 21682bdca71d422e03b4452fa841b6b542d0c22f (patch) | |
tree | a1a2d93f459d1141f25a91387c06b453c79e9d39 /src/java/org | |
parent | cb742f91f7809f8a15157ceb2ed79f0473946c56 (diff) | |
download | xmlgraphics-fop-21682bdca71d422e03b4452fa841b6b542d0c22f.tar.gz xmlgraphics-fop-21682bdca71d422e03b4452fa841b6b542d0c22f.zip |
FOP-2252: OpenType CFF support for FOP; patch submitted by Robert Meyer
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1514076 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org')
22 files changed, 3867 insertions, 2205 deletions
diff --git a/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java b/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java index b5457e4e4..4fa6c3b62 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java +++ b/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java @@ -29,9 +29,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.fonts.truetype.FontFileReader; -import org.apache.fop.fonts.truetype.TTFDirTabEntry; -import org.apache.fop.fonts.truetype.TTFFile; -import org.apache.fop.fonts.truetype.TTFTableName; +import org.apache.fop.fonts.truetype.OFDirTabEntry; +import org.apache.fop.fonts.truetype.OFTableName; +import org.apache.fop.fonts.truetype.OpenFont; // CSOFF: AvoidNestedBlocksCheck // CSOFF: NoWhitespaceAfterCheck @@ -50,7 +50,7 @@ public final class OTFAdvancedTypographicTableReader { // logging state private static Log log = LogFactory.getLog(OTFAdvancedTypographicTableReader.class); // instance state - private TTFFile ttf; // parent font file reader + private OpenFont otf; // parent font file reader private FontFileReader in; // input reader private GlyphDefinitionTable gdef; // glyph definition table private GlyphSubstitutionTable gsub; // glyph substitution table @@ -68,10 +68,10 @@ public final class OTFAdvancedTypographicTableReader { * @param ttf parent font file reader (must be non-null) * @param in font file reader (must be non-null) */ - public OTFAdvancedTypographicTableReader(TTFFile ttf, FontFileReader in) { - assert ttf != null; + public OTFAdvancedTypographicTableReader(OpenFont otf, FontFileReader in) { + assert otf != null; assert in != null; - this.ttf = ttf; + this.otf = otf; this.in = in; } @@ -127,7 +127,8 @@ public final class OTFAdvancedTypographicTableReader { return gpos; } - private void readLangSysTable(TTFTableName tableTag, long langSysTable, String langSysTag) throws IOException { + private void readLangSysTable(OFTableName tableTag, long langSysTable, String langSysTag) + throws IOException { in.seekSet(langSysTable); if (log.isDebugEnabled()) { log.debug(tableTag + " lang sys table: " + langSysTag); @@ -169,7 +170,7 @@ public final class OTFAdvancedTypographicTableReader { private static String defaultTag = "dflt"; - private void readScriptTable(TTFTableName tableTag, long scriptTable, String scriptTag) throws IOException { + private void readScriptTable(OFTableName tableTag, long scriptTable, String scriptTag) throws IOException { in.seekSet(scriptTable); if (log.isDebugEnabled()) { log.debug(tableTag + " script table: " + scriptTag); @@ -222,7 +223,7 @@ public final class OTFAdvancedTypographicTableReader { seLanguages = null; } - private void readScriptList(TTFTableName tableTag, long scriptList) throws IOException { + private void readScriptList(OFTableName tableTag, long scriptList) throws IOException { in.seekSet(scriptList); // read script record count int ns = in.readTTFUShort(); @@ -251,7 +252,7 @@ public final class OTFAdvancedTypographicTableReader { } } - private void readFeatureTable(TTFTableName tableTag, long featureTable, String featureTag, int featureIndex) throws IOException { + private void readFeatureTable(OFTableName tableTag, long featureTable, String featureTag, int featureIndex) throws IOException { in.seekSet(featureTable); if (log.isDebugEnabled()) { log.debug(tableTag + " feature table: " + featureTag); @@ -279,7 +280,7 @@ public final class OTFAdvancedTypographicTableReader { seFeatures.put("f" + featureIndex, new Object[] { featureTag, lul }); } - private void readFeatureList(TTFTableName tableTag, long featureList) throws IOException { + private void readFeatureList(OFTableName tableTag, long featureList) throws IOException { in.seekSet(featureList); // read feature record count int nf = in.readTTFUShort(); @@ -1736,28 +1737,28 @@ public final class OTFAdvancedTypographicTableReader { // XPlacement int xp; if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT) != 0) { - xp = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + xp = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { xp = 0; } // YPlacement int yp; if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT) != 0) { - yp = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + yp = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { yp = 0; } // XAdvance int xa; if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE) != 0) { - xa = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + xa = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { xa = 0; } // YAdvance int ya; if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE) != 0) { - ya = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + ya = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { ya = 0; } @@ -2029,23 +2030,23 @@ public final class OTFAdvancedTypographicTableReader { int af = in.readTTFUShort(); if (af == 1) { // read x coordinate - int x = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read y coordinate - int y = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); a = new GlyphPositioningTable.Anchor(x, y); } else if (af == 2) { // read x coordinate - int x = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read y coordinate - int y = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read anchor point index int ap = in.readTTFUShort(); a = new GlyphPositioningTable.Anchor(x, y, ap); } else if (af == 3) { // read x coordinate - int x = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read y coordinate - int y = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read x device table offset int xdo = in.readTTFUShort(); // read y device table offset @@ -3145,9 +3146,9 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readLookupTable(TTFTableName tableTag, int lookupSequence, long lookupTable) throws IOException { - boolean isGSUB = tableTag.equals(TTFTableName.GSUB); - boolean isGPOS = tableTag.equals(TTFTableName.GPOS); + private void readLookupTable(OFTableName tableTag, int lookupSequence, long lookupTable) throws IOException { + boolean isGSUB = tableTag.equals(OFTableName.GSUB); + boolean isGPOS = tableTag.equals(OFTableName.GPOS); in.seekSet(lookupTable); // read lookup type int lt = in.readTTFUShort(); @@ -3198,7 +3199,7 @@ public final class OTFAdvancedTypographicTableReader { } } - private void readLookupList(TTFTableName tableTag, long lookupList) throws IOException { + private void readLookupList(OFTableName tableTag, long lookupList) throws IOException { in.seekSet(lookupList); // read lookup record count int nl = in.readTTFUShort(); @@ -3233,7 +3234,7 @@ public final class OTFAdvancedTypographicTableReader { * @param lookupList offset to lookup list from beginning of font file * @throws IOException In case of a I/O problem */ - private void readCommonLayoutTables(TTFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException { + private void readCommonLayoutTables(OFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException { if (scriptList > 0) { readScriptList(tableTag, scriptList); } @@ -3245,7 +3246,7 @@ public final class OTFAdvancedTypographicTableReader { } } - private void readGDEFClassDefTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { + private void readGDEFClassDefTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { initATSubState(); in.seekSet(subtableOffset); // subtable is a bare class definition table @@ -3257,7 +3258,7 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readGDEFAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { + private void readGDEFAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { initATSubState(); in.seekSet(subtableOffset); // read coverage offset @@ -3275,7 +3276,7 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readGDEFLigatureCaretTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { + private void readGDEFLigatureCaretTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { initATSubState(); in.seekSet(subtableOffset); // read coverage offset @@ -3305,7 +3306,7 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readGDEFMarkAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { + private void readGDEFMarkAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { initATSubState(); in.seekSet(subtableOffset); // subtable is a bare class definition table @@ -3317,7 +3318,7 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readGDEFMarkGlyphsTableFormat1(TTFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException { + private void readGDEFMarkGlyphsTableFormat1(OFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException { initATSubState(); in.seekSet(subtableOffset); // skip over format (already known) @@ -3351,7 +3352,7 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readGDEFMarkGlyphsTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { + private void readGDEFMarkGlyphsTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read mark set subtable format int sf = in.readTTFUShort(); @@ -3367,17 +3368,17 @@ public final class OTFAdvancedTypographicTableReader { * @throws IOException In case of a I/O problem */ private void readGDEF() throws IOException { - TTFTableName tableTag = TTFTableName.GDEF; + OFTableName tableTag = OFTableName.GDEF; // Initialize temporary state initATState(); // Read glyph definition (GDEF) table - TTFDirTabEntry dirTab = ttf.getDirectoryEntry(tableTag); + OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); if (gdef != null) { if (log.isDebugEnabled()) { log.debug(tableTag + ": ignoring duplicate table"); } } else if (dirTab != null) { - ttf.seekTab(in, tableTag, 0); + otf.seekTab(in, tableTag, 0); long version = in.readTTFULong(); if (log.isDebugEnabled()) { log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); @@ -3440,17 +3441,17 @@ public final class OTFAdvancedTypographicTableReader { * @throws IOException In case of a I/O problem */ private void readGSUB() throws IOException { - TTFTableName tableTag = TTFTableName.GSUB; + OFTableName tableTag = OFTableName.GSUB; // Initialize temporary state initATState(); // Read glyph substitution (GSUB) table - TTFDirTabEntry dirTab = ttf.getDirectoryEntry(tableTag); + OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); if (gpos != null) { if (log.isDebugEnabled()) { log.debug(tableTag + ": ignoring duplicate table"); } } else if (dirTab != null) { - ttf.seekTab(in, tableTag, 0); + otf.seekTab(in, tableTag, 0); int version = in.readTTFLong(); if (log.isDebugEnabled()) { log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); @@ -3477,17 +3478,17 @@ public final class OTFAdvancedTypographicTableReader { * @throws IOException In case of a I/O problem */ private void readGPOS() throws IOException { - TTFTableName tableTag = TTFTableName.GPOS; + OFTableName tableTag = OFTableName.GPOS; // Initialize temporary state initATState(); // Read glyph positioning (GPOS) table - TTFDirTabEntry dirTab = ttf.getDirectoryEntry(tableTag); + OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); if (gpos != null) { if (log.isDebugEnabled()) { log.debug(tableTag + ": ignoring duplicate table"); } } else if (dirTab != null) { - ttf.seekTab(in, tableTag, 0); + otf.seekTab(in, tableTag, 0); int version = in.readTTFLong(); if (log.isDebugEnabled()) { log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); diff --git a/src/java/org/apache/fop/fonts/FontLoader.java b/src/java/org/apache/fop/fonts/FontLoader.java index f7ee24cbc..09e38260e 100644 --- a/src/java/org/apache/fop/fonts/FontLoader.java +++ b/src/java/org/apache/fop/fonts/FontLoader.java @@ -26,7 +26,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.apps.io.InternalResourceResolver; -import org.apache.fop.fonts.truetype.TTFFontLoader; +import org.apache.fop.fonts.truetype.OFFontLoader; import org.apache.fop.fonts.type1.Type1FontLoader; /** @@ -105,7 +105,7 @@ public abstract class FontLoader { } loader = new Type1FontLoader(fontFileURI, embedded, useKerning, resourceResolver); } else { - loader = new TTFFontLoader(fontFileURI, subFontName, embedded, embeddingMode, + loader = new OFFontLoader(fontFileURI, subFontName, embedded, embeddingMode, encodingMode, useKerning, useAdvanced, resourceResolver); } return loader.getFont(); diff --git a/src/java/org/apache/fop/fonts/MultiByteFont.java b/src/java/org/apache/fop/fonts/MultiByteFont.java index 83f6491f3..68be7bed8 100644 --- a/src/java/org/apache/fop/fonts/MultiByteFont.java +++ b/src/java/org/apache/fop/fonts/MultiByteFont.java @@ -22,6 +22,7 @@ package org.apache.fop.fonts; import java.nio.CharBuffer; import java.nio.IntBuffer; import java.util.BitSet; +import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.logging.Log; @@ -67,6 +68,15 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl private int firstUnmapped; private int lastUnmapped; + private boolean isOTFFile = false; + + // since for most users the most likely glyphs are in the first cmap segments we store their mapping. + private static final int NUM_MOST_LIKELY_GLYPHS = 256; + private int[] mostLikelyGlyphs = new int[NUM_MOST_LIKELY_GLYPHS]; + + //A map to store each used glyph from the CID set against the glyph name. + private LinkedHashMap<Integer, String> usedGlyphNames = new LinkedHashMap<Integer, String>(); + /** * Default constructor */ @@ -111,6 +121,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl return cidType; } + public void setIsOTFFile(boolean isOTFFile) { + this.isOTFFile = isOTFFile; + } + + public boolean isOTFFile() { + return this.isOTFFile; + } + /** * Sets the CIDType. * @param cidType The cidType to set @@ -147,6 +165,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl return this.cidSet; } + public void mapUsedGlyphName(int gid, String value) { + usedGlyphNames.put(gid, value); + } + + public LinkedHashMap<Integer, String> getUsedGlyphNames() { + return usedGlyphNames; + } + /** {@inheritDoc} */ @Override public String getEncodingName() { @@ -177,10 +203,15 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl * @return the glyph index (or 0 if the glyph is not available) */ // [TBD] - needs optimization, i.e., change from linear search to binary search - private int findGlyphIndex(int c) { + public int findGlyphIndex(int c) { int idx = c; int retIdx = SingleByteEncoding.NOT_FOUND_CODE_POINT; + // for most users the most likely glyphs are in the first cmap segments (meaning the one with + // the lowest unicode start values) + if (idx < NUM_MOST_LIKELY_GLYPHS && mostLikelyGlyphs[idx] != 0) { + return mostLikelyGlyphs[idx]; + } for (int i = 0; (i < cmap.length) && retIdx == 0; i++) { if (cmap[i].getUnicodeStart() <= idx && cmap[i].getUnicodeEnd() >= idx) { @@ -188,6 +219,9 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl retIdx = cmap[i].getGlyphStartIndex() + idx - cmap[i].getUnicodeStart(); + if (idx < NUM_MOST_LIKELY_GLYPHS) { + mostLikelyGlyphs[idx] = retIdx; + } } } return retIdx; @@ -281,22 +315,6 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl return findCharacterFromGlyphIndex(gi, true); } - - /** {@inheritDoc} */ - @Override - public char mapChar(char c) { - notifyMapOperation(); - int glyphIndex = findGlyphIndex(c); - if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) { - warnMissingGlyph(c); - glyphIndex = findGlyphIndex(Typeface.NOT_FOUND); - } - if (isEmbeddable()) { - glyphIndex = cidSet.mapChar(glyphIndex, c); - } - return (char) glyphIndex; - } - protected BitSet getGlyphIndices() { BitSet bitset = new BitSet(); bitset.set(0); @@ -329,6 +347,23 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl /** {@inheritDoc} */ @Override + public char mapChar(char c) { + notifyMapOperation(); + int glyphIndex = findGlyphIndex(c); + if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) { + warnMissingGlyph(c); + if (!isOTFFile) { + glyphIndex = findGlyphIndex(Typeface.NOT_FOUND); + } + } + if (isEmbeddable()) { + glyphIndex = cidSet.mapChar(glyphIndex, c); + } + return (char) glyphIndex; + } + + /** {@inheritDoc} */ + @Override public boolean hasChar(char c) { return (findGlyphIndex(c) != SingleByteEncoding.NOT_FOUND_CODE_POINT); } diff --git a/src/java/org/apache/fop/fonts/SingleByteFont.java b/src/java/org/apache/fop/fonts/SingleByteFont.java index cd11c7849..a61d846e6 100644 --- a/src/java/org/apache/fop/fonts/SingleByteFont.java +++ b/src/java/org/apache/fop/fonts/SingleByteFont.java @@ -32,7 +32,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.fonts.Glyphs; import org.apache.fop.apps.io.InternalResourceResolver; -import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion; +import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion; /** * Generic SingleByte font diff --git a/src/java/org/apache/fop/fonts/apps/TTFReader.java b/src/java/org/apache/fop/fonts/apps/TTFReader.java index db858e285..fc003d201 100644 --- a/src/java/org/apache/fop/fonts/apps/TTFReader.java +++ b/src/java/org/apache/fop/fonts/apps/TTFReader.java @@ -38,6 +38,7 @@ import org.apache.fop.Version; import org.apache.fop.fonts.CMapSegment; import org.apache.fop.fonts.FontUtil; import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OFFontLoader; import org.apache.fop.fonts.truetype.TTFFile; // CSOFF: InnerAssignmentCheck @@ -216,7 +217,8 @@ public class TTFReader extends AbstractFontReader { InputStream stream = new FileInputStream(fileName); try { FontFileReader reader = new FontFileReader(stream); - boolean supported = ttfFile.readFont(reader, fontName); + String header = OFFontLoader.readHeader(reader); + boolean supported = ttfFile.readFont(reader, header, fontName); if (!supported) { return null; } diff --git a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java index e115264bb..21ebd4937 100644 --- a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java +++ b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java @@ -43,8 +43,8 @@ import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.FontUtil; import org.apache.fop.fonts.MultiByteFont; import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OFFontLoader; import org.apache.fop.fonts.truetype.TTFFile; -import org.apache.fop.fonts.truetype.TTFFontLoader; /** * Attempts to determine correct FontInfo @@ -220,7 +220,7 @@ public class FontInfoFinder { log.debug("Loading " + fontName); } try { - TTFFontLoader ttfLoader = new TTFFontLoader(fontURI, fontName, true, + OFFontLoader ttfLoader = new OFFontLoader(fontURI, fontName, true, EmbeddingMode.AUTO, EncodingMode.AUTO, useKerning, useAdvanced, resourceResolver); customFont = ttfLoader.getFont(); diff --git a/src/java/org/apache/fop/fonts/truetype/GlyfTable.java b/src/java/org/apache/fop/fonts/truetype/GlyfTable.java index f26ac2ffd..90abccaf2 100644 --- a/src/java/org/apache/fop/fonts/truetype/GlyfTable.java +++ b/src/java/org/apache/fop/fonts/truetype/GlyfTable.java @@ -31,7 +31,7 @@ import java.util.TreeSet; */ public class GlyfTable { - private final TTFMtxEntry[] mtxTab; + private final OFMtxEntry[] mtxTab; private final long tableOffset; @@ -47,7 +47,7 @@ public class GlyfTable { /** All the glyphs that are composed, but do not appear in the subset. */ private Set<Integer> composedGlyphs = new TreeSet<Integer>(); - GlyfTable(FontFileReader in, TTFMtxEntry[] metrics, TTFDirTabEntry dirTableEntry, + GlyfTable(FontFileReader in, OFMtxEntry[] metrics, OFDirTabEntry dirTableEntry, Map<Integer, Integer> glyphs) throws IOException { mtxTab = metrics; tableOffset = dirTableEntry.getOffset(); diff --git a/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java b/src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java index c273d4471..a9c471d5e 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java +++ b/src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java @@ -26,17 +26,17 @@ import java.io.UnsupportedEncodingException; /** * This class represents an entry to a TrueType font's Dir Tab. */ -public class TTFDirTabEntry { +public class OFDirTabEntry { private byte[] tag = new byte[4]; private int checksum; private long offset; private long length; - public TTFDirTabEntry() { + public OFDirTabEntry() { } - public TTFDirTabEntry(long offset, long length) { + public OFDirTabEntry(long offset, long length) { this.offset = offset; this.length = length; } diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java b/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java index b7adbd4c9..f15837bb8 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java +++ b/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java @@ -37,13 +37,13 @@ import org.apache.fop.fonts.FontType; import org.apache.fop.fonts.MultiByteFont; import org.apache.fop.fonts.NamedCharacter; import org.apache.fop.fonts.SingleByteFont; -import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion; +import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion; import org.apache.fop.util.HexEncoder; /** * Loads a TrueType font into memory directly from the original font file. */ -public class TTFFontLoader extends FontLoader { +public class OFFontLoader extends FontLoader { private MultiByteFont multiFont; private SingleByteFont singleFont; @@ -56,7 +56,7 @@ public class TTFFontLoader extends FontLoader { * @param fontFileURI the URI representing the font file * @param resourceResolver the resource resolver for font URI resolution */ - public TTFFontLoader(URI fontFileURI, InternalResourceResolver resourceResolver) { + public OFFontLoader(URI fontFileURI, InternalResourceResolver resourceResolver) { this(fontFileURI, null, true, EmbeddingMode.AUTO, EncodingMode.AUTO, true, true, resourceResolver); } @@ -72,7 +72,7 @@ public class TTFFontLoader extends FontLoader { * @param useAdvanced true to enable loading advanced info if available, false to disable * @param resolver the FontResolver for font URI resolution */ - public TTFFontLoader(URI fontFileURI, String subFontName, boolean embedded, + public OFFontLoader(URI fontFileURI, String subFontName, boolean embedded, EmbeddingMode embeddingMode, EncodingMode encodingMode, boolean useKerning, boolean useAdvanced, InternalResourceResolver resolver) { super(fontFileURI, embedded, useKerning, useAdvanced, resolver); @@ -101,26 +101,30 @@ public class TTFFontLoader extends FontLoader { private void read(String ttcFontName) throws IOException { InputStream in = resourceResolver.getResource(this.fontFileURI); try { - TTFFile ttf = new TTFFile(useKerning, useAdvanced); FontFileReader reader = new FontFileReader(in); - boolean supported = ttf.readFont(reader, ttcFontName); + String header = readHeader(reader); + boolean isCFF = header.equals("OTTO"); + OpenFont otf = (isCFF) ? new OTFFile() : new TTFFile(useKerning, useAdvanced); + boolean supported = otf.readFont(reader, header, ttcFontName); if (!supported) { - throw new IOException("TrueType font is not supported: " + fontFileURI); + throw new IOException("The font does not have a Unicode cmap table: " + fontFileURI); } - buildFont(ttf, ttcFontName); + buildFont(otf, ttcFontName); loaded = true; } finally { IOUtils.closeQuietly(in); } } - - private void buildFont(TTFFile ttf, String ttcFontName) { - if (ttf.isCFF()) { - throw new UnsupportedOperationException( - "OpenType fonts with CFF data are not supported, yet"); + public static String readHeader(FontFileReader fontFile) throws IOException { + if (fontFile != null) { + fontFile.seekSet(0); + return fontFile.readTTFString(4); // TTF_FIXED_SIZE (4 bytes) } + return null; + } + private void buildFont(OpenFont otf, String ttcFontName) { boolean isCid = this.embedded; if (this.encodingMode == EncodingMode.SINGLE_BYTE) { isCid = false; @@ -128,6 +132,7 @@ public class TTFFontLoader extends FontLoader { if (isCid) { multiFont = new MultiByteFont(resourceResolver, embeddingMode); + multiFont.setIsOTFFile(otf instanceof OTFFile); returnFont = multiFont; multiFont.setTTCName(ttcFontName); } else { @@ -135,43 +140,47 @@ public class TTFFontLoader extends FontLoader { returnFont = singleFont; } - returnFont.setFontName(ttf.getPostScriptName()); - returnFont.setFullName(ttf.getFullName()); - returnFont.setFamilyNames(ttf.getFamilyNames()); - returnFont.setFontSubFamilyName(ttf.getSubFamilyName()); - returnFont.setCapHeight(ttf.getCapHeight()); - returnFont.setXHeight(ttf.getXHeight()); - returnFont.setAscender(ttf.getLowerCaseAscent()); - returnFont.setDescender(ttf.getLowerCaseDescent()); - returnFont.setFontBBox(ttf.getFontBBox()); - returnFont.setFlags(ttf.getFlags()); - returnFont.setStemV(Integer.parseInt(ttf.getStemV())); //not used for TTF - returnFont.setItalicAngle(Integer.parseInt(ttf.getItalicAngle())); + returnFont.setFontName(otf.getPostScriptName()); + returnFont.setFullName(otf.getFullName()); + returnFont.setFamilyNames(otf.getFamilyNames()); + returnFont.setFontSubFamilyName(otf.getSubFamilyName()); + returnFont.setCapHeight(otf.getCapHeight()); + returnFont.setXHeight(otf.getXHeight()); + returnFont.setAscender(otf.getLowerCaseAscent()); + returnFont.setDescender(otf.getLowerCaseDescent()); + returnFont.setFontBBox(otf.getFontBBox()); + returnFont.setFlags(otf.getFlags()); + returnFont.setStemV(Integer.parseInt(otf.getStemV())); //not used for TTF + returnFont.setItalicAngle(Integer.parseInt(otf.getItalicAngle())); returnFont.setMissingWidth(0); - returnFont.setWeight(ttf.getWeightClass()); + returnFont.setWeight(otf.getWeightClass()); returnFont.setEmbeddingMode(this.embeddingMode); if (isCid) { - multiFont.setCIDType(CIDFontType.CIDTYPE2); - int[] wx = ttf.getWidths(); + if (otf instanceof OTFFile) { + multiFont.setCIDType(CIDFontType.CIDTYPE0); + } else { + multiFont.setCIDType(CIDFontType.CIDTYPE2); + } + int[] wx = otf.getWidths(); multiFont.setWidthArray(wx); } else { singleFont.setFontType(FontType.TRUETYPE); - singleFont.setEncoding(ttf.getCharSetName()); - returnFont.setFirstChar(ttf.getFirstChar()); - returnFont.setLastChar(ttf.getLastChar()); - singleFont.setTrueTypePostScriptVersion(ttf.getPostScriptVersion()); - copyWidthsSingleByte(ttf); + singleFont.setEncoding(otf.getCharSetName()); + returnFont.setFirstChar(otf.getFirstChar()); + returnFont.setLastChar(otf.getLastChar()); + singleFont.setTrueTypePostScriptVersion(otf.getPostScriptVersion()); + copyWidthsSingleByte(otf); } - returnFont.setCMap(getCMap(ttf)); + returnFont.setCMap(getCMap(otf)); - if (useKerning) { - copyKerning(ttf, isCid); + if (otf.getKerning() != null && useKerning) { + copyKerning(otf, isCid); } if (useAdvanced) { - copyAdvanced(ttf); + copyAdvanced(otf); } if (this.embedded) { - if (ttf.isEmbeddable()) { + if (otf.isEmbeddable()) { returnFont.setEmbedURI(this.fontFileURI); } else { String msg = "The font " + this.fontFileURI + " is not embeddable due to a" @@ -181,25 +190,25 @@ public class TTFFontLoader extends FontLoader { } } - private CMapSegment[] getCMap(TTFFile ttf) { - CMapSegment[] array = new CMapSegment[ttf.getCMaps().size()]; - return ttf.getCMaps().toArray(array); + private CMapSegment[] getCMap(OpenFont otf) { + CMapSegment[] array = new CMapSegment[otf.getCMaps().size()]; + return otf.getCMaps().toArray(array); } - private void copyWidthsSingleByte(TTFFile ttf) { - int[] wx = ttf.getWidths(); + private void copyWidthsSingleByte(OpenFont otf) { + int[] wx = otf.getWidths(); for (int i = singleFont.getFirstChar(); i <= singleFont.getLastChar(); i++) { - singleFont.setWidth(i, ttf.getCharWidth(i)); + singleFont.setWidth(i, otf.getCharWidth(i)); } - for (CMapSegment segment : ttf.getCMaps()) { + for (CMapSegment segment : otf.getCMaps()) { if (segment.getUnicodeStart() < 0xFFFE) { for (char u = (char)segment.getUnicodeStart(); u <= segment.getUnicodeEnd(); u++) { int codePoint = singleFont.getEncoding().mapChar(u); if (codePoint <= 0) { int glyphIndex = segment.getGlyphStartIndex() + u - segment.getUnicodeStart(); - String glyphName = ttf.getGlyphName(glyphIndex); - if (glyphName.length() == 0 && ttf.getPostScriptVersion() != PostScriptVersion.V2) { + String glyphName = otf.getGlyphName(glyphIndex); + if (glyphName.length() == 0 && otf.getPostScriptVersion() != PostScriptVersion.V2) { glyphName = "u" + HexEncoder.encode(u); } if (glyphName.length() > 0) { @@ -216,22 +225,22 @@ public class TTFFontLoader extends FontLoader { /** * Copy kerning information. */ - private void copyKerning(TTFFile ttf, boolean isCid) { + private void copyKerning(OpenFont otf, boolean isCid) { // Get kerning Set<Integer> kerningSet; if (isCid) { - kerningSet = ttf.getKerning().keySet(); + kerningSet = otf.getKerning().keySet(); } else { - kerningSet = ttf.getAnsiKerning().keySet(); + kerningSet = otf.getAnsiKerning().keySet(); } for (Integer kpx1 : kerningSet) { Map<Integer, Integer> h2; if (isCid) { - h2 = ttf.getKerning().get(kpx1); + h2 = otf.getKerning().get(kpx1); } else { - h2 = ttf.getAnsiKerning().get(kpx1); + h2 = otf.getAnsiKerning().get(kpx1); } returnFont.putKerningEntry(kpx1, h2); } @@ -240,12 +249,12 @@ public class TTFFontLoader extends FontLoader { /** * Copy advanced typographic information. */ - private void copyAdvanced(TTFFile ttf) { + private void copyAdvanced(OpenFont otf) { if (returnFont instanceof MultiByteFont) { MultiByteFont mbf = (MultiByteFont) returnFont; - mbf.setGDEF(ttf.getGDEF()); - mbf.setGSUB(ttf.getGSUB()); - mbf.setGPOS(ttf.getGPOS()); + mbf.setGDEF(otf.getGDEF()); + mbf.setGSUB(otf.getGSUB()); + mbf.setGPOS(otf.getGPOS()); } } diff --git a/src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java b/src/java/org/apache/fop/fonts/truetype/OFMtxEntry.java index 6884a633d..89af2296f 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java +++ b/src/java/org/apache/fop/fonts/truetype/OFMtxEntry.java @@ -24,7 +24,7 @@ import java.util.List; /** * This class represents a TrueType Mtx Entry. */ -class TTFMtxEntry { +class OFMtxEntry { private int wx; private int lsb; diff --git a/src/java/org/apache/fop/fonts/truetype/TTFTableName.java b/src/java/org/apache/fop/fonts/truetype/OFTableName.java index e5ad63128..f6264129a 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFTableName.java +++ b/src/java/org/apache/fop/fonts/truetype/OFTableName.java @@ -24,98 +24,104 @@ package org.apache.fop.fonts.truetype; * Represents table names as found in a TrueType font's Table Directory. * TrueType fonts may have custom tables so we cannot use an enum. */ -public final class TTFTableName { +public final class OFTableName { /** The first table in a TrueType font file containing metadata about other tables. */ - public static final TTFTableName TABLE_DIRECTORY = new TTFTableName("tableDirectory"); + public static final OFTableName TABLE_DIRECTORY = new OFTableName("tableDirectory"); + + /** Baseline data */ + public static final OFTableName BASE = new OFTableName("BASE"); + + /** CFF data/ */ + public static final OFTableName CFF = new OFTableName("CFF "); /** Embedded bitmap data. */ - public static final TTFTableName EBDT = new TTFTableName("EBDT"); + public static final OFTableName EBDT = new OFTableName("EBDT"); /** Embedded bitmap location data. */ - public static final TTFTableName EBLC = new TTFTableName("EBLC"); + public static final OFTableName EBLC = new OFTableName("EBLC"); /** Embedded bitmap scaling data. */ - public static final TTFTableName EBSC = new TTFTableName("EBSC"); + public static final OFTableName EBSC = new OFTableName("EBSC"); /** A FontForge specific table. */ - public static final TTFTableName FFTM = new TTFTableName("FFTM"); + public static final OFTableName FFTM = new OFTableName("FFTM"); /** Divides glyphs into various classes that make using the GPOS/GSUB tables easier. */ - public static final TTFTableName GDEF = new TTFTableName("GDEF"); + public static final OFTableName GDEF = new OFTableName("GDEF"); /** Provides kerning information, mark-to-base, etc. for opentype fonts. */ - public static final TTFTableName GPOS = new TTFTableName("GPOS"); + public static final OFTableName GPOS = new OFTableName("GPOS"); /** Provides ligature information, swash, etc. for opentype fonts. */ - public static final TTFTableName GSUB = new TTFTableName("GSUB"); + public static final OFTableName GSUB = new OFTableName("GSUB"); /** Linear threshold table. */ - public static final TTFTableName LTSH = new TTFTableName("LTSH"); + public static final OFTableName LTSH = new OFTableName("LTSH"); /** OS/2 and Windows specific metrics. */ - public static final TTFTableName OS2 = new TTFTableName("OS/2"); + public static final OFTableName OS2 = new OFTableName("OS/2"); /** PCL 5 data. */ - public static final TTFTableName PCLT = new TTFTableName("PCLT"); + public static final OFTableName PCLT = new OFTableName("PCLT"); /** Vertical Device Metrics table. */ - public static final TTFTableName VDMX = new TTFTableName("VDMX"); + public static final OFTableName VDMX = new OFTableName("VDMX"); /** Character to glyph mapping. */ - public static final TTFTableName CMAP = new TTFTableName("cmap"); + public static final OFTableName CMAP = new OFTableName("cmap"); /** Control Value Table. */ - public static final TTFTableName CVT = new TTFTableName("cvt "); + public static final OFTableName CVT = new OFTableName("cvt "); /** Font program. */ - public static final TTFTableName FPGM = new TTFTableName("fpgm"); + public static final OFTableName FPGM = new OFTableName("fpgm"); /** Grid-fitting and scan conversion procedure (grayscale). */ - public static final TTFTableName GASP = new TTFTableName("gasp"); + public static final OFTableName GASP = new OFTableName("gasp"); /** Glyph data. */ - public static final TTFTableName GLYF = new TTFTableName("glyf"); + public static final OFTableName GLYF = new OFTableName("glyf"); /** Horizontal device metrics. */ - public static final TTFTableName HDMX = new TTFTableName("hdmx"); + public static final OFTableName HDMX = new OFTableName("hdmx"); /** Font header. */ - public static final TTFTableName HEAD = new TTFTableName("head"); + public static final OFTableName HEAD = new OFTableName("head"); /** Horizontal header. */ - public static final TTFTableName HHEA = new TTFTableName("hhea"); + public static final OFTableName HHEA = new OFTableName("hhea"); /** Horizontal metrics. */ - public static final TTFTableName HMTX = new TTFTableName("hmtx"); + public static final OFTableName HMTX = new OFTableName("hmtx"); /** Kerning. */ - public static final TTFTableName KERN = new TTFTableName("kern"); + public static final OFTableName KERN = new OFTableName("kern"); /** Index to location. */ - public static final TTFTableName LOCA = new TTFTableName("loca"); + public static final OFTableName LOCA = new OFTableName("loca"); /** Maximum profile. */ - public static final TTFTableName MAXP = new TTFTableName("maxp"); + public static final OFTableName MAXP = new OFTableName("maxp"); /** Naming table. */ - public static final TTFTableName NAME = new TTFTableName("name"); + public static final OFTableName NAME = new OFTableName("name"); /** PostScript information. */ - public static final TTFTableName POST = new TTFTableName("post"); + public static final OFTableName POST = new OFTableName("post"); /** CVT Program. */ - public static final TTFTableName PREP = new TTFTableName("prep"); + public static final OFTableName PREP = new OFTableName("prep"); /** Vertical Metrics header. */ - public static final TTFTableName VHEA = new TTFTableName("vhea"); + public static final OFTableName VHEA = new OFTableName("vhea"); /** Vertical Metrics. */ - public static final TTFTableName VMTX = new TTFTableName("vmtx"); + public static final OFTableName VMTX = new OFTableName("vmtx"); private final String name; - private TTFTableName(String name) { + private OFTableName(String name) { this.name = name; } @@ -131,9 +137,9 @@ public final class TTFTableName { * @param tableName table name as in the Table Directory * @return TTFTableName */ - public static TTFTableName getValue(String tableName) { + public static OFTableName getValue(String tableName) { if (tableName != null) { - return new TTFTableName(tableName); + return new OFTableName(tableName); } throw new IllegalArgumentException("A TrueType font table name must not be null"); } @@ -148,10 +154,10 @@ public final class TTFTableName { if (o == this) { return true; } - if (!(o instanceof TTFTableName)) { + if (!(o instanceof OFTableName)) { return false; } - TTFTableName to = (TTFTableName) o; + OFTableName to = (OFTableName) o; return this.name.equals(to.getName()); } diff --git a/src/java/org/apache/fop/fonts/truetype/OTFFile.java b/src/java/org/apache/fop/fonts/truetype/OTFFile.java new file mode 100644 index 000000000..3976b5994 --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/OTFFile.java @@ -0,0 +1,109 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.truetype; + +import java.io.IOException; + +import org.apache.fontbox.cff.CFFDataInput; +import org.apache.fontbox.cff.CFFFont; +import org.apache.fontbox.cff.CFFFont.Mapping; +import org.apache.fontbox.cff.CFFParser; + +public class OTFFile extends OpenFont { + + protected CFFFont fileFont; + + public OTFFile() throws IOException { + checkForFontbox(); + } + + private void checkForFontbox() throws IOException { + try { + Class.forName("org.apache.fontbox.cff.CFFFont"); + } catch (ClassNotFoundException ex) { + throw new IOException("The Fontbox jar was not found in the classpath. This is " + + "required for OTF CFF ssupport."); + } + } + + @Override + protected void updateBBoxAndOffset() throws IOException { + UnicodeMapping[] mappings = unicodeMappings.toArray(new UnicodeMapping[0]); + for (int i = 0; i < mappings.length; i++) { + int glyphIdx = mappings[i].getGlyphIndex(); + Mapping m = fileFont.getGIDMappings().get(glyphIdx); + int[] bbox = fileFont.getBoundingBox(m.getSID()); + String name = fileFont.getNameOfCharFromCode(m.getSID()); + mtxTab[glyphIdx].setBoundingBox(bbox); + mtxTab[glyphIdx].setName(name); + } + } + + @Override + protected void initializeFont(FontFileReader in) throws IOException { + fontFile = in; + fontFile.seekSet(0); + CFFParser parser = new CFFParser(); + fileFont = parser.parse(in.getAllBytes()).get(0); + } + + protected void readName() throws IOException { + Object familyName = fileFont.getProperty("FamilyName"); + if (familyName != null && !familyName.equals("")) { + familyNames.add(familyName.toString()); + fullName = familyName.toString(); + } else { + fullName = fileFont.getName(); + familyNames.add(fullName); + } + } + + /** + * Reads the CFFData from a given font file + * @param fontFile The font file being read + * @return The byte data found in the CFF table + */ + public static byte[] getCFFData(FontFileReader fontFile) throws IOException { + byte[] cff = new byte[0]; + CFFDataInput input = new CFFDataInput(fontFile.getAllBytes()); + input.readBytes(4); //OTTO + short numTables = input.readShort(); + input.readShort(); //searchRange + input.readShort(); //entrySelector + input.readShort(); //rangeShift + + for (int q = 0; q < numTables; q++) { + String tagName = new String(input.readBytes(4)); + readLong(input); //Checksum + long offset = readLong(input); + long length = readLong(input); + if (tagName.equals("CFF ")) { + cff = new byte[(int)length]; + System.arraycopy(fontFile.getAllBytes(), (int)offset, cff, 0, cff.length); + break; + } + } + return cff; + } + + private static long readLong(CFFDataInput input) throws IOException { + return (input.readCard16() << 16) | input.readCard16(); + } +} diff --git a/src/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java b/src/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java new file mode 100644 index 000000000..dbea48216 --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java @@ -0,0 +1,1092 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.truetype; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.fontbox.cff.CFFStandardString; +import org.apache.fontbox.cff.encoding.CFFEncoding; + +import org.apache.fop.fonts.MultiByteFont; +import org.apache.fop.fonts.cff.CFFDataReader; +import org.apache.fop.fonts.cff.CFFDataReader.CFFIndexData; +import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry; +import org.apache.fop.fonts.cff.CFFDataReader.FDSelect; +import org.apache.fop.fonts.cff.CFFDataReader.FontDict; +import org.apache.fop.fonts.cff.CFFDataReader.Format0FDSelect; +import org.apache.fop.fonts.cff.CFFDataReader.Format3FDSelect; + +/** + * Reads an OpenType CFF file and generates a subset + * The OpenType specification can be found at the Microsoft + * Typography site: http://www.microsoft.com/typography/otspec/ + */ +public class OTFSubSetFile extends OTFFile { + + private byte[] output; + private int currentPos = 0; + private int realSize = 0; + + /** A map containing each glyph to be included in the subset + * with their existing and new GID's **/ + private LinkedHashMap<Integer, Integer> subsetGlyphs; + + /** A map of the new GID to SID used to construct the charset table **/ + private LinkedHashMap<Integer, Integer> gidToSID; + + private CFFIndexData localIndexSubr; + private CFFIndexData globalIndexSubr; + + /** List of subroutines to write to the local / global indexes in the subset font **/ + private List<byte[]> subsetLocalIndexSubr; + private List<byte[]> subsetGlobalIndexSubr; + + /** For fonts which have an FDSelect or ROS flag in Top Dict, this is used to store the + * local subroutine indexes for each group as opposed to the above subsetLocalIndexSubr */ + private ArrayList<List<byte[]>> fdSubrs; + + /** The subset FD Select table used to store the mappings between glyphs and their + * associated FDFont object which point to a private dict and local subroutines. */ + private LinkedHashMap<Integer, FDIndexReference> subsetFDSelect; + + /** A list of unique subroutines from the global / local subroutine indexes */ + private List<Integer> localUniques; + private List<Integer> globalUniques; + + /** A store of the number of subroutines each global / local subroutine will store **/ + private int subsetLocalSubrCount; + private int subsetGlobalSubrCount; + + /** A list of char string data for each glyph to be stored in the subset font **/ + private List<byte[]> subsetCharStringsIndex; + + /** The embedded name to change in the name table **/ + private String embeddedName; + + /** An array used to hold the string index data for the subset font **/ + private List<byte[]> stringIndexData = new ArrayList<byte[]>(); + + /** The CFF reader object used to read data and offsets from the original font file */ + private CFFDataReader cffReader = null; + + /** The class used to represent this font **/ + private MultiByteFont mbFont; + + /** The number of standard strings in CFF **/ + private static final int NUM_STANDARD_STRINGS = 391; + /** The operator used to identify a local subroutine reference */ + private static final int LOCAL_SUBROUTINE = 10; + /** The operator used to identify a global subroutine reference */ + private static final int GLOBAL_SUBROUTINE = 29; + + public OTFSubSetFile() throws IOException { + super(); + } + + public void readFont(FontFileReader in, String embeddedName, String header, + MultiByteFont mbFont) throws IOException { + this.mbFont = mbFont; + readFont(in, embeddedName, header, mbFont.getUsedGlyphs()); + } + + /** + * Reads and creates a subset of the font. + * + * @param in FontFileReader to read from + * @param name Name to be checked for in the font file + * @param header The header of the font file + * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and + * new index as (Integer) value) + * @throws IOException in case of an I/O problem + */ + void readFont(FontFileReader in, String embeddedName, String header, + Map<Integer, Integer> usedGlyphs) throws IOException { + fontFile = in; + + currentPos = 0; + realSize = 0; + + this.embeddedName = embeddedName; + + //Sort by the new GID and store in a LinkedHashMap + subsetGlyphs = sortByValue(usedGlyphs); + + output = new byte[in.getFileSize()]; + + initializeFont(in); + + cffReader = new CFFDataReader(fontFile); + + //Create the CIDFontType0C data + createCFF(); + } + + private LinkedHashMap<Integer, Integer> sortByValue(Map<Integer, Integer> map) { + List<Entry<Integer, Integer>> list = new ArrayList<Entry<Integer, Integer>>(map.entrySet()); + Collections.sort(list, new Comparator<Entry<Integer, Integer>>() { + public int compare(Entry<Integer, Integer> o1, Entry<Integer, Integer> o2) { + return ((Comparable<Integer>) o1.getValue()).compareTo(o2.getValue()); + } + }); + + LinkedHashMap<Integer, Integer> result = new LinkedHashMap<Integer, Integer>(); + for (Entry<Integer, Integer> entry : list) { + result.put(entry.getKey(), entry.getValue()); + } + return result; + } + + private void createCFF() throws IOException { + //Header + writeBytes(cffReader.getHeader()); + + //Name Index + writeIndex(Arrays.asList(embeddedName.getBytes())); + + //Keep offset of the topDICT so it can be updated once all data has been written + int topDictOffset = currentPos; + //Top DICT Index and Data + byte[] topDictIndex = cffReader.getTopDictIndex().getByteData(); + int offSize = topDictIndex[2]; + writeBytes(topDictIndex, 0, 3 + (offSize * 2)); + int topDictDataOffset = currentPos; + writeTopDICT(); + + //Create the char string index data and related local / global subroutines + if (cffReader.getFDSelect() == null) { + createCharStringData(); + } else { + createCharStringDataCID(); + } + + //If it is a CID-Keyed font, store each FD font and add each SID + List<Integer> fontNameSIDs = null; + List<Integer> subsetFDFonts = null; + if (cffReader.getFDSelect() != null) { + subsetFDFonts = getUsedFDFonts(); + fontNameSIDs = storeFDStrings(subsetFDFonts); + } + + //String index + writeStringIndex(); + + //Global subroutine index + writeIndex(subsetGlobalIndexSubr); + + //Encoding + int encodingOffset = currentPos; + writeEncoding(fileFont.getEncoding()); + + //Charset table + int charsetOffset = currentPos; + writeCharsetTable(cffReader.getFDSelect() != null); + + //FDSelect table + int fdSelectOffset = currentPos; + if (cffReader.getFDSelect() != null) { + writeFDSelect(); + } + + //Char Strings Index + int charStringOffset = currentPos; + writeIndex(subsetCharStringsIndex); + + if (cffReader.getFDSelect() == null) { + //Keep offset to modify later with the local subroutine index offset + int privateDictOffset = currentPos; + writePrivateDict(); + + //Local subroutine index + int localIndexOffset = currentPos; + writeIndex(subsetLocalIndexSubr); + + //Update the offsets + updateOffsets(topDictOffset, charsetOffset, charStringOffset, privateDictOffset, + localIndexOffset, encodingOffset); + } else { + List<Integer> privateDictOffsets = writeCIDDictsAndSubrs(subsetFDFonts); + int fdArrayOffset = writeFDArray(subsetFDFonts, privateDictOffsets, fontNameSIDs); + + updateCIDOffsets(topDictDataOffset, fdArrayOffset, fdSelectOffset, charsetOffset, + charStringOffset, encodingOffset); + } + } + + private List<Integer> storeFDStrings(List<Integer> uniqueNewRefs) throws IOException { + ArrayList<Integer> fontNameSIDs = new ArrayList<Integer>(); + List<FontDict> fdFonts = cffReader.getFDFonts(); + for (int i = 0; i < uniqueNewRefs.size(); i++) { + FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i)); + byte[] fdFontByteData = fdFont.getByteData(); + Map<String, DICTEntry> fdFontDict = cffReader.parseDictData(fdFontByteData); + fontNameSIDs.add(stringIndexData.size() + NUM_STANDARD_STRINGS); + stringIndexData.add(cffReader.getStringIndex().getValue(fdFontDict.get("FontName") + .getOperands().get(0).intValue() - NUM_STANDARD_STRINGS)); + } + return fontNameSIDs; + } + + private void writeBytes(byte[] out) { + for (int i = 0; i < out.length; i++) { + output[currentPos++] = out[i]; + realSize++; + } + } + + private void writeBytes(byte[] out, int offset, int length) { + for (int i = offset; i < offset + length; i++) { + output[currentPos++] = out[i]; + realSize++; + } + } + + private void writeEncoding(CFFEncoding encoding) throws IOException { + LinkedHashMap<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + DICTEntry encodingEntry = topDICT.get("Encoding"); + if (encodingEntry != null && encodingEntry.getOperands().get(0).intValue() != 0 + && encodingEntry.getOperands().get(0).intValue() != 1) { + writeByte(0); + writeByte(gidToSID.size()); + for (int gid : gidToSID.keySet()) { + int code = encoding.getCode(gidToSID.get(gid)); + writeByte(code); + } + } + } + + private void writeTopDICT() throws IOException { + LinkedHashMap<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + List<String> topDictStringEntries = Arrays.asList("version", "Notice", "Copyright", + "FullName", "FamilyName", "Weight", "PostScript"); + for (Map.Entry<String, DICTEntry> dictEntry : topDICT.entrySet()) { + String dictKey = dictEntry.getKey(); + DICTEntry entry = dictEntry.getValue(); + //If the value is an SID, update the reference but keep the size the same + if (dictKey.equals("ROS")) { + writeROSEntry(entry); + } else if (dictKey.equals("CIDCount")) { + writeCIDCount(entry); + } else if (topDictStringEntries.contains(dictKey)) { + writeTopDictStringEntry(entry); + } else { + writeBytes(entry.getByteData()); + } + } + } + + private void writeROSEntry(DICTEntry dictEntry) throws IOException { + int sidA = dictEntry.getOperands().get(0).intValue(); + if (sidA > 390) { + stringIndexData.add(cffReader.getStringIndex().getValue(sidA - NUM_STANDARD_STRINGS)); + } + int sidAStringIndex = stringIndexData.size() + 390; + int sidB = dictEntry.getOperands().get(1).intValue(); + if (sidB > 390) { + stringIndexData.add("Identity".getBytes()); + } + int sidBStringIndex = stringIndexData.size() + 390; + byte[] cidEntryByteData = dictEntry.getByteData(); + cidEntryByteData = updateOffset(cidEntryByteData, 0, dictEntry.getOperandLengths().get(0), + sidAStringIndex); + cidEntryByteData = updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0), + dictEntry.getOperandLengths().get(1), sidBStringIndex); + cidEntryByteData = updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0) + + dictEntry.getOperandLengths().get(1), dictEntry.getOperandLengths().get(2), 139); + writeBytes(cidEntryByteData); + } + + private void writeCIDCount(DICTEntry dictEntry) throws IOException { + byte[] cidCountByteData = dictEntry.getByteData(); + cidCountByteData = updateOffset(cidCountByteData, 0, dictEntry.getOperandLengths().get(0), + subsetGlyphs.size()); + writeBytes(cidCountByteData); + } + + private void writeTopDictStringEntry(DICTEntry dictEntry) throws IOException { + int sid = dictEntry.getOperands().get(0).intValue(); + if (sid > 391) { + stringIndexData.add(cffReader.getStringIndex().getValue(sid - 391)); + } + + byte[] newDictEntry = createNewRef(stringIndexData.size() + 390, dictEntry.getOperator(), + dictEntry.getOperandLength()); + writeBytes(newDictEntry); + } + + private void writeStringIndex() throws IOException { + Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + int charsetOffset = topDICT.get("charset").getOperands().get(0).intValue(); + + gidToSID = new LinkedHashMap<Integer, Integer>(); + + for (int gid : subsetGlyphs.keySet()) { + int sid = cffReader.getSIDFromGID(charsetOffset, gid); + //Check whether the SID falls into the standard string set + if (sid < NUM_STANDARD_STRINGS) { + gidToSID.put(subsetGlyphs.get(gid), sid); + if (mbFont != null) { + mbFont.mapUsedGlyphName(subsetGlyphs.get(gid), + CFFStandardString.getName(sid)); + } + } else { + int index = sid - NUM_STANDARD_STRINGS; + if (index <= cffReader.getStringIndex().getNumObjects()) { + if (mbFont != null) { + mbFont.mapUsedGlyphName(subsetGlyphs.get(gid), + new String(cffReader.getStringIndex().getValue(index))); + } + gidToSID.put(subsetGlyphs.get(gid), stringIndexData.size() + 391); + stringIndexData.add(cffReader.getStringIndex().getValue(index)); + } else { + if (mbFont != null) { + mbFont.mapUsedGlyphName(subsetGlyphs.get(gid), ".notdef"); + } + gidToSID.put(subsetGlyphs.get(gid), index); + } + } + } + //Write the String Index + writeIndex(stringIndexData); + } + + private void createCharStringDataCID() throws IOException { + CFFIndexData charStringsIndex = cffReader.getCharStringIndex(); + + FDSelect fontDictionary = cffReader.getFDSelect(); + if (fontDictionary instanceof Format0FDSelect) { + throw new UnsupportedOperationException("OTF CFF CID Format0 currently not implemented"); + } else if (fontDictionary instanceof Format3FDSelect) { + Format3FDSelect fdSelect = (Format3FDSelect)fontDictionary; + Map<Integer, Integer> subsetGroups = new HashMap<Integer, Integer>(); + + List<Integer> uniqueGroups = new ArrayList<Integer>(); + for (int gid : subsetGlyphs.keySet()) { + Integer[] ranges = fdSelect.getRanges().keySet().toArray(new Integer[0]); + for (int i = 0; i < ranges.length; i++) { + int nextRange = -1; + if (i < ranges.length - 1) { + nextRange = ranges[i + 1]; + } else { + nextRange = fdSelect.getSentinelGID(); + } + if (gid >= ranges[i] && gid < nextRange) { + subsetGroups.put(gid, fdSelect.getRanges().get(ranges[i])); + if (!uniqueGroups.contains(fdSelect.getRanges().get(ranges[i]))) { + uniqueGroups.add(fdSelect.getRanges().get(ranges[i])); + } + } + } + } + + //Prepare resources + globalIndexSubr = cffReader.getGlobalIndexSubr(); + + //Create the new char string index + subsetCharStringsIndex = new ArrayList<byte[]>(); + + globalUniques = new ArrayList<Integer>(); + + subsetFDSelect = new LinkedHashMap<Integer, FDIndexReference>(); + + List<List<Integer>> foundLocalUniques = new ArrayList<List<Integer>>(); + for (int i = 0; i < uniqueGroups.size(); i++) { + foundLocalUniques.add(new ArrayList<Integer>()); + } + for (int gid : subsetGlyphs.keySet()) { + int group = subsetGroups.get(gid); + localIndexSubr = cffReader.getFDFonts().get(group).getLocalSubrData(); + localUniques = foundLocalUniques.get(uniqueGroups.indexOf(subsetGroups.get(gid))); + + FDIndexReference newFDReference = new FDIndexReference( + uniqueGroups.indexOf(subsetGroups.get(gid)), subsetGroups.get(gid)); + subsetFDSelect.put(subsetGlyphs.get(gid), newFDReference); + byte[] data = charStringsIndex.getValue(gid); + preScanForSubsetIndexSize(data); + } + + //Create the two lists which are to store the local and global subroutines + subsetGlobalIndexSubr = new ArrayList<byte[]>(); + + fdSubrs = new ArrayList<List<byte[]>>(); + subsetGlobalSubrCount = globalUniques.size(); + globalUniques.clear(); + localUniques = null; + + for (int l = 0; l < foundLocalUniques.size(); l++) { + fdSubrs.add(new ArrayList<byte[]>()); + } + List<List<Integer>> foundLocalUniquesB = new ArrayList<List<Integer>>(); + for (int k = 0; k < uniqueGroups.size(); k++) { + foundLocalUniquesB.add(new ArrayList<Integer>()); + } + for (Integer gid : subsetGlyphs.keySet()) { + int group = subsetGroups.get(gid); + localIndexSubr = cffReader.getFDFonts().get(group).getLocalSubrData(); + localUniques = foundLocalUniquesB.get(subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex()); + byte[] data = charStringsIndex.getValue(gid); + subsetLocalIndexSubr = fdSubrs.get(subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex()); + subsetLocalSubrCount = foundLocalUniques.get(subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex()).size(); + data = readCharStringData(data, subsetLocalSubrCount); + subsetCharStringsIndex.add(data); + } + } + } + + private void writeFDSelect() { + writeByte(0); //Format + for (Integer gid : subsetFDSelect.keySet()) { + writeByte(subsetFDSelect.get(gid).getNewFDIndex()); + } + } + + private List<Integer> getUsedFDFonts() { + List<Integer> uniqueNewRefs = new ArrayList<Integer>(); + for (int gid : subsetFDSelect.keySet()) { + int fdIndex = subsetFDSelect.get(gid).getOldFDIndex(); + if (!uniqueNewRefs.contains(fdIndex)) { + uniqueNewRefs.add(fdIndex); + } + } + return uniqueNewRefs; + } + + private List<Integer> writeCIDDictsAndSubrs(List<Integer> uniqueNewRefs) + throws IOException { + List<Integer> privateDictOffsets = new ArrayList<Integer>(); + List<FontDict> fdFonts = cffReader.getFDFonts(); + for (int i = 0; i < uniqueNewRefs.size(); i++) { + FontDict curFDFont = fdFonts.get(uniqueNewRefs.get(i)); + HashMap<String, DICTEntry> fdPrivateDict = cffReader.parseDictData( + curFDFont.getPrivateDictData()); + int privateDictOffset = currentPos; + privateDictOffsets.add(privateDictOffset); + byte[] fdPrivateDictByteData = curFDFont.getPrivateDictData(); + if (fdPrivateDict.get("Subrs") != null) { + fdPrivateDictByteData = updateOffset(fdPrivateDictByteData, fdPrivateDict.get("Subrs").getOffset(), + fdPrivateDict.get("Subrs").getOperandLength(), + fdPrivateDictByteData.length); + } + writeBytes(fdPrivateDictByteData); + writeIndex(fdSubrs.get(i)); + } + return privateDictOffsets; + } + + private int writeFDArray(List<Integer> uniqueNewRefs, List<Integer> privateDictOffsets, + List<Integer> fontNameSIDs) + throws IOException { + int offset = currentPos; + List<FontDict> fdFonts = cffReader.getFDFonts(); + + writeCard16(uniqueNewRefs.size()); + writeByte(1); //Offset size + writeByte(1); //First offset + + int count = 1; + for (int i = 0; i < uniqueNewRefs.size(); i++) { + FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i)); + count += fdFont.getByteData().length; + writeByte(count); + } + + for (int i = 0; i < uniqueNewRefs.size(); i++) { + FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i)); + byte[] fdFontByteData = fdFont.getByteData(); + Map<String, DICTEntry> fdFontDict = cffReader.parseDictData(fdFontByteData); + //Update the SID to the FontName + fdFontByteData = updateOffset(fdFontByteData, fdFontDict.get("FontName").getOffset() - 1, + fdFontDict.get("FontName").getOperandLengths().get(0), + fontNameSIDs.get(i)); + //Update the Private dict reference + fdFontByteData = updateOffset(fdFontByteData, fdFontDict.get("Private").getOffset() + + fdFontDict.get("Private").getOperandLengths().get(0), + fdFontDict.get("Private").getOperandLengths().get(1), + privateDictOffsets.get(i)); + writeBytes(fdFontByteData); + } + return offset; + } + + private class FDIndexReference { + private int newFDIndex; + private int oldFDIndex; + + public FDIndexReference(int newFDIndex, int oldFDIndex) { + this.newFDIndex = newFDIndex; + this.oldFDIndex = oldFDIndex; + } + + public int getNewFDIndex() { + return newFDIndex; + } + + public int getOldFDIndex() { + return oldFDIndex; + } + } + + private void createCharStringData() throws IOException { + Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + + CFFIndexData charStringsIndex = cffReader.getCharStringIndex(); + + DICTEntry privateEntry = topDICT.get("Private"); + if (privateEntry != null) { + int privateOffset = privateEntry.getOperands().get(1).intValue(); + Map<String, DICTEntry> privateDICT = cffReader.getPrivateDict(privateEntry); + + int localSubrOffset = privateOffset + privateDICT.get("Subrs").getOperands().get(0).intValue(); + localIndexSubr = cffReader.readIndex(localSubrOffset); + } + + globalIndexSubr = cffReader.getGlobalIndexSubr(); + + //Create the two lists which are to store the local and global subroutines + subsetLocalIndexSubr = new ArrayList<byte[]>(); + subsetGlobalIndexSubr = new ArrayList<byte[]>(); + + //Create the new char string index + subsetCharStringsIndex = new ArrayList<byte[]>(); + + localUniques = new ArrayList<Integer>(); + globalUniques = new ArrayList<Integer>(); + + for (int gid : subsetGlyphs.keySet()) { + byte[] data = charStringsIndex.getValue(gid); + preScanForSubsetIndexSize(data); + } + + //Store the size of each subset index and clear the unique arrays + subsetLocalSubrCount = localUniques.size(); + subsetGlobalSubrCount = globalUniques.size(); + localUniques.clear(); + globalUniques.clear(); + + for (int gid : subsetGlyphs.keySet()) { + byte[] data = charStringsIndex.getValue(gid); + //Retrieve modified char string data and fill local / global subroutine arrays + data = readCharStringData(data, subsetLocalSubrCount); + subsetCharStringsIndex.add(data); + } + } + + private void preScanForSubsetIndexSize(byte[] data) throws IOException { + boolean hasLocalSubroutines = localIndexSubr != null && localIndexSubr.getNumObjects() > 0; + boolean hasGlobalSubroutines = globalIndexSubr != null && globalIndexSubr.getNumObjects() > 0; + BytesNumber operand = new BytesNumber(-1, -1); + for (int dataPos = 0; dataPos < data.length; dataPos++) { + int b0 = data[dataPos] & 0xff; + if (b0 == LOCAL_SUBROUTINE && hasLocalSubroutines) { + int subrNumber = getSubrNumber(localIndexSubr.getNumObjects(), operand.getNumber()); + + if (!localUniques.contains(subrNumber) && subrNumber < localIndexSubr.getNumObjects()) { + localUniques.add(subrNumber); + byte[] subr = localIndexSubr.getValue(subrNumber); + preScanForSubsetIndexSize(subr); + } + operand.clearNumber(); + } else if (b0 == GLOBAL_SUBROUTINE && hasGlobalSubroutines) { + int subrNumber = getSubrNumber(globalIndexSubr.getNumObjects(), operand.getNumber()); + + if (!globalUniques.contains(subrNumber) && subrNumber < globalIndexSubr.getNumObjects()) { + globalUniques.add(subrNumber); + byte[] subr = globalIndexSubr.getValue(subrNumber); + preScanForSubsetIndexSize(subr); + } + operand.clearNumber(); + } else if ((b0 >= 0 && b0 <= 27) || (b0 >= 29 && b0 <= 31)) { + operand.clearNumber(); + if (b0 == 19 || b0 == 20) { + dataPos += 1; + } + } else if (b0 == 28 || (b0 >= 32 && b0 <= 255)) { + operand = readNumber(b0, data, dataPos); + dataPos += operand.getNumBytes() - 1; + } + } + } + + private int getSubrNumber(int numSubroutines, int operand) { + int bias = getBias(numSubroutines); + return bias + operand; + } + + private byte[] readCharStringData(byte[] data, int subsetLocalSubrCount) throws IOException { + boolean hasLocalSubroutines = localIndexSubr != null && localIndexSubr.getNumObjects() > 0; + boolean hasGlobalSubroutines = globalIndexSubr != null && globalIndexSubr.getNumObjects() > 0; + BytesNumber operand = new BytesNumber(-1, -1); + for (int dataPos = 0; dataPos < data.length; dataPos++) { + int b0 = data[dataPos] & 0xff; + if (b0 == 10 && hasLocalSubroutines) { + int subrNumber = getSubrNumber(localIndexSubr.getNumObjects(), operand.getNumber()); + + int newRef = getNewRefForReference(subrNumber, localUniques, localIndexSubr, subsetLocalIndexSubr, + subsetLocalSubrCount); + + if (newRef != -1) { + byte[] newData = constructNewRefData(dataPos, data, operand, subsetLocalSubrCount, + newRef, new int[] {10}); + dataPos -= data.length - newData.length; + data = newData; + } + + operand.clearNumber(); + } else if (b0 == 29 && hasGlobalSubroutines) { + int subrNumber = getSubrNumber(globalIndexSubr.getNumObjects(), operand.getNumber()); + + int newRef = getNewRefForReference(subrNumber, globalUniques, globalIndexSubr, subsetGlobalIndexSubr, + subsetGlobalSubrCount); + + if (newRef != -1) { + byte[] newData = constructNewRefData(dataPos, data, operand, subsetGlobalSubrCount, + newRef, new int[] {29}); + dataPos -= (data.length - newData.length); + data = newData; + } + + operand.clearNumber(); + } else if ((b0 >= 0 && b0 <= 27) || (b0 >= 29 && b0 <= 31)) { + operand.clearNumber(); + if (b0 == 19 || b0 == 20) { + dataPos += 1; + } + } else if (b0 == 28 || (b0 >= 32 && b0 <= 255)) { + operand = readNumber(b0, data, dataPos); + dataPos += operand.getNumBytes() - 1; + } + } + + //Return the data with the modified references to our arrays + return data; + } + + private int getNewRefForReference(int subrNumber, List<Integer> uniquesArray, + CFFIndexData indexSubr, List<byte[]> subsetIndexSubr, int subrCount) throws IOException { + int newRef = -1; + if (!uniquesArray.contains(subrNumber)) { + if (subrNumber < indexSubr.getNumObjects()) { + byte[] subr = indexSubr.getValue(subrNumber); + subr = readCharStringData(subr, subrCount); + if (!uniquesArray.contains(subrNumber)) { + uniquesArray.add(subrNumber); + subsetIndexSubr.add(subr); + newRef = subsetIndexSubr.size() - 1; + } else { + newRef = uniquesArray.indexOf(subrNumber); + } + } + } else { + newRef = uniquesArray.indexOf(subrNumber); + } + return newRef; + } + + private int getBias(int subrCount) { + if (subrCount < 1240) { + return 107; + } else if (subrCount < 33900) { + return 1131; + } else { + return 32768; + } + } + + private byte[] constructNewRefData(int curDataPos, byte[] currentData, BytesNumber operand, + int fullSubsetIndexSize, int curSubsetIndexSize, int[] operatorCode) { + //Create the new array with the modified reference + byte[] newData; + int startRef = curDataPos - operand.getNumBytes(); + int length = operand.getNumBytes() + 1; + byte[] preBytes = new byte[startRef]; + System.arraycopy(currentData, 0, preBytes, 0, startRef); + int newBias = getBias(fullSubsetIndexSize); + int newRef = curSubsetIndexSize - newBias; + byte[] newRefBytes = createNewRef(newRef, operatorCode, -1); + newData = concatArray(preBytes, newRefBytes); + byte[] postBytes = new byte[currentData.length - (startRef + length)]; + System.arraycopy(currentData, startRef + length, postBytes, 0, + currentData.length - (startRef + length)); + return concatArray(newData, postBytes); + } + + public static byte[] createNewRef(int newRef, int[] operatorCode, int forceLength) { + byte[] newRefBytes; + int sizeOfOperator = operatorCode.length; + if ((forceLength == -1 && newRef <= 107) || forceLength == 1) { + newRefBytes = new byte[1 + sizeOfOperator]; + //The index values are 0 indexed + newRefBytes[0] = (byte)(newRef + 139); + for (int i = 0; i < operatorCode.length; i++) { + newRefBytes[1 + i] = (byte)operatorCode[i]; + } + } else if ((forceLength == -1 && newRef <= 1131) || forceLength == 2) { + newRefBytes = new byte[2 + sizeOfOperator]; + if (newRef <= 363) { + newRefBytes[0] = (byte)247; + } else if (newRef <= 619) { + newRefBytes[0] = (byte)248; + } else if (newRef <= 875) { + newRefBytes[0] = (byte)249; + } else { + newRefBytes[0] = (byte)250; + } + newRefBytes[1] = (byte)(newRef - 108); + for (int i = 0; i < operatorCode.length; i++) { + newRefBytes[2 + i] = (byte)operatorCode[i]; + } + } else if ((forceLength == -1 && newRef <= 32767) || forceLength == 3) { + newRefBytes = new byte[3 + sizeOfOperator]; + newRefBytes[0] = 28; + newRefBytes[1] = (byte)(newRef >> 8); + newRefBytes[2] = (byte)newRef; + for (int i = 0; i < operatorCode.length; i++) { + newRefBytes[3 + i] = (byte)operatorCode[i]; + } + } else { + newRefBytes = new byte[5 + sizeOfOperator]; + newRefBytes[0] = 29; + newRefBytes[1] = (byte)(newRef >> 24); + newRefBytes[2] = (byte)(newRef >> 16); + newRefBytes[3] = (byte)(newRef >> 8); + newRefBytes[4] = (byte)newRef; + for (int i = 0; i < operatorCode.length; i++) { + newRefBytes[5 + i] = (byte)operatorCode[i]; + } + } + return newRefBytes; + } + + public static byte[] concatArray(byte[] a, byte[] b) { + int aLen = a.length; + int bLen = b.length; + byte[] c = new byte[aLen + bLen]; + System.arraycopy(a, 0, c, 0, aLen); + System.arraycopy(b, 0, c, aLen, bLen); + return c; + } + + private int writeIndex(List<byte[]> dataArray) { + int hdrTotal = 3; + //2 byte number of items + this.writeCard16(dataArray.size()); + //Offset Size: 1 byte = 256, 2 bytes = 65536 etc. + int totLength = 0; + for (int i = 0; i < dataArray.size(); i++) { + totLength += dataArray.get(i).length; + } + int offSize = 1; + if (totLength <= (1 << 8)) { + offSize = 1; + } else if (totLength <= (1 << 16)) { + offSize = 2; + } else if (totLength <= (1 << 24)) { + offSize = 3; + } else { + offSize = 4; + } + this.writeByte(offSize); + //Count the first offset 1 + hdrTotal += offSize; + int total = 0; + for (int i = 0; i < dataArray.size(); i++) { + hdrTotal += offSize; + int length = dataArray.get(i).length; + switch (offSize) { + case 1: + if (i == 0) { + writeByte(1); + } + total += length; + writeByte(total + 1); + break; + case 2: + if (i == 0) { + writeCard16(1); + } + total += length; + writeCard16(total + 1); + break; + case 3: + if (i == 0) { + writeThreeByteNumber(1); + } + total += length; + writeThreeByteNumber(total + 1); + break; + case 4: + if (i == 0) { + writeULong(1); + } + total += length; + writeULong(total + 1); + break; + default: + throw new AssertionError("Offset Size was not an expected value."); + } + } + for (int i = 0; i < dataArray.size(); i++) { + writeBytes(dataArray.get(i)); + } + return hdrTotal + total; + } + + + private BytesNumber readNumber(int b0, byte[] input, int curPos) throws IOException { + if (b0 == 28) { + int b1 = input[curPos + 1] & 0xff; + int b2 = input[curPos + 2] & 0xff; + return new BytesNumber(Integer.valueOf((short) (b1 << 8 | b2)), 3); + } else if (b0 >= 32 && b0 <= 246) { + return new BytesNumber(Integer.valueOf(b0 - 139), 1); + } else if (b0 >= 247 && b0 <= 250) { + int b1 = input[curPos + 1] & 0xff; + return new BytesNumber(Integer.valueOf((b0 - 247) * 256 + b1 + 108), 2); + } else if (b0 >= 251 && b0 <= 254) { + int b1 = input[curPos + 1] & 0xff; + return new BytesNumber(Integer.valueOf(-(b0 - 251) * 256 - b1 - 108), 2); + } else if (b0 == 255) { + int b1 = input[curPos + 1] & 0xff; + int b2 = input[curPos + 2] & 0xff; + return new BytesNumber(Integer.valueOf((short)(b1 << 8 | b2)), 5); + } else { + throw new IllegalArgumentException(); + } + } + + /** + * A class used to store the last number operand and also it's size in bytes + */ + private static final class BytesNumber { + private int number; + private int numBytes; + + public BytesNumber(int number, int numBytes) { + this.number = number; + this.numBytes = numBytes; + } + + public int getNumber() { + return this.number; + } + + public int getNumBytes() { + return this.numBytes; + } + + public void clearNumber() { + this.number = -1; + this.numBytes = -1; + } + } + + private void writeCharsetTable(boolean cidFont) throws IOException { + writeByte(0); + for (int gid : gidToSID.keySet()) { + if (cidFont && gid == 0) { + continue; + } + writeCard16((cidFont) ? gid : gidToSID.get(gid)); + } + } + + private void writePrivateDict() throws IOException { + Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + + DICTEntry privateEntry = topDICT.get("Private"); + if (privateEntry != null) { + writeBytes(cffReader.getPrivateDictBytes(privateEntry)); + } + } + + private void updateOffsets(int topDictOffset, int charsetOffset, int charStringOffset, + int privateDictOffset, int localIndexOffset, int encodingOffset) + throws IOException { + Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + Map<String, DICTEntry> privateDICT = null; + + DICTEntry privateEntry = topDICT.get("Private"); + if (privateEntry != null) { + privateDICT = cffReader.getPrivateDict(privateEntry); + } + + int dataPos = 3 + (cffReader.getTopDictIndex().getOffSize() + * cffReader.getTopDictIndex().getOffsets().length); + int dataTopDictOffset = topDictOffset + dataPos; + + updateFixedOffsets(topDICT, dataTopDictOffset, charsetOffset, charStringOffset, encodingOffset); + + if (privateDICT != null) { + //Private index offset in the top dict + int oldPrivateOffset = dataTopDictOffset + privateEntry.getOffset(); + output = updateOffset(output, oldPrivateOffset + privateEntry.getOperandLengths().get(0), + privateEntry.getOperandLengths().get(1), privateDictOffset); + + //Update the local subroutine index offset in the private dict + DICTEntry subroutines = privateDICT.get("Subrs"); + int oldLocalSubrOffset = privateDictOffset + subroutines.getOffset(); + //Value needs to be converted to -139 etc. + int encodeValue = 0; + if (subroutines.getOperandLength() == 1) { + encodeValue = 139; + } + output = updateOffset(output, oldLocalSubrOffset, subroutines.getOperandLength(), + (localIndexOffset - privateDictOffset) + encodeValue); + } + } + + private void updateFixedOffsets(Map<String, DICTEntry> topDICT, int dataTopDictOffset, + int charsetOffset, int charStringOffset, int encodingOffset) { + //Charset offset in the top dict + DICTEntry charset = topDICT.get("charset"); + int oldCharsetOffset = dataTopDictOffset + charset.getOffset(); + output = updateOffset(output, oldCharsetOffset, charset.getOperandLength(), charsetOffset); + + //Char string index offset in the private dict + DICTEntry charString = topDICT.get("CharStrings"); + int oldCharStringOffset = dataTopDictOffset + charString.getOffset(); + output = updateOffset(output, oldCharStringOffset, charString.getOperandLength(), charStringOffset); + + DICTEntry encodingEntry = topDICT.get("Encoding"); + if (encodingEntry != null && encodingEntry.getOperands().get(0).intValue() != 0 + && encodingEntry.getOperands().get(0).intValue() != 1) { + int oldEncodingOffset = dataTopDictOffset + encodingEntry.getOffset(); + output = updateOffset(output, oldEncodingOffset, encodingEntry.getOperandLength(), encodingOffset); + } + } + + private void updateCIDOffsets(int topDictDataOffset, int fdArrayOffset, int fdSelectOffset, + int charsetOffset, int charStringOffset, int encodingOffset) { + LinkedHashMap<String, DICTEntry> topDict = cffReader.getTopDictEntries(); + + DICTEntry fdArrayEntry = topDict.get("FDArray"); + if (fdArrayEntry != null) { + output = updateOffset(output, topDictDataOffset + fdArrayEntry.getOffset() - 1, + fdArrayEntry.getOperandLength(), fdArrayOffset); + } + + DICTEntry fdSelect = topDict.get("FDSelect"); + if (fdSelect != null) { + output = updateOffset(output, topDictDataOffset + fdSelect.getOffset() - 1, + fdSelect.getOperandLength(), fdSelectOffset); + } + + updateFixedOffsets(topDict, topDictDataOffset, charsetOffset, charStringOffset, encodingOffset); + } + + private byte[] updateOffset(byte[] out, int position, int length, int replacement) { + switch (length) { + case 1: + out[position] = (byte)(replacement & 0xFF); + break; + case 2: + if (replacement <= 363) { + out[position] = (byte)247; + } else if (replacement <= 619) { + out[position] = (byte)248; + } else if (replacement <= 875) { + out[position] = (byte)249; + } else { + out[position] = (byte)250; + } + out[position + 1] = (byte)(replacement - 108); + break; + case 3: + out[position] = (byte)28; + out[position + 1] = (byte)((replacement >> 8) & 0xFF); + out[position + 2] = (byte)(replacement & 0xFF); + break; + case 5: + out[position] = (byte)29; + out[position + 1] = (byte)((replacement >> 24) & 0xFF); + out[position + 2] = (byte)((replacement >> 16) & 0xFF); + out[position + 3] = (byte)((replacement >> 8) & 0xFF); + out[position + 4] = (byte)(replacement & 0xFF); + break; + default: + } + return out; + } + + /** + * Appends a byte to the output array, + * updates currentPost but not realSize + */ + private void writeByte(int b) { + output[currentPos++] = (byte)b; + realSize++; + } + + /** + * Appends a USHORT to the output array, + * updates currentPost but not realSize + */ + private void writeCard16(int s) { + byte b1 = (byte)((s >> 8) & 0xff); + byte b2 = (byte)(s & 0xff); + writeByte(b1); + writeByte(b2); + } + + private void writeThreeByteNumber(int s) { + byte b1 = (byte)((s >> 16) & 0xFF); + byte b2 = (byte)((s >> 8) & 0xFF); + byte b3 = (byte)(s & 0xFF); + output[currentPos++] = b1; + output[currentPos++] = b2; + output[currentPos++] = b3; + realSize += 3; + } + + /** + * Appends a ULONG to the output array, + * at the given position + */ + 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); + output[currentPos++] = b1; + output[currentPos++] = b2; + output[currentPos++] = b3; + output[currentPos++] = b4; + realSize += 4; + } + + /** + * Returns a subset of the fonts (readFont() MUST be called first in order to create the + * subset). + * @return byte array + */ + public byte[] getFontSubset() { + byte[] ret = new byte[realSize]; + System.arraycopy(output, 0, ret, 0, realSize); + return ret; + } +} diff --git a/src/java/org/apache/fop/fonts/truetype/OpenFont.java b/src/java/org/apache/fop/fonts/truetype/OpenFont.java new file mode 100644 index 000000000..ce9a2b388 --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/OpenFont.java @@ -0,0 +1,1937 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.truetype; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.fonts.Glyphs; + +import org.apache.fop.complexscripts.fonts.AdvancedTypographicTableFormatException; +import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable; +import org.apache.fop.complexscripts.fonts.GlyphPositioningTable; +import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable; +import org.apache.fop.complexscripts.fonts.OTFAdvancedTypographicTableReader; +import org.apache.fop.fonts.CMapSegment; +import org.apache.fop.fonts.FontUtil; +import org.apache.fop.fonts.MultiByteFont; + +public abstract class OpenFont { + + static final byte NTABS = 24; + static final int MAX_CHAR_CODE = 255; + static final int ENC_BUF_SIZE = 1024; + + private static final String[] MAC_GLYPH_ORDERING = { + /* 0x000 */ + ".notdef", ".null", "nonmarkingreturn", "space", + "exclam", "quotedbl", "numbersign", "dollar", + "percent", "ampersand", "quotesingle", "parenleft", + "parenright", "asterisk", "plus", "comma", + /* 0x010 */ + "hyphen", "period", "slash", "zero", + "one", "two", "three", "four", + "five", "six", "seven", "eight", + "nine", "colon", "semicolon", "less", + /* 0x020 */ + "equal", "greater", "question", "at", + "A", "B", "C", "D", + "E", "F", "G", "H", + "I", "J", "K", "L", + /* 0x030 */ + "M", "N", "O", "P", + "Q", "R", "S", "T", + "U", "V", "W", "X", + "Y", "Z", "bracketleft", "backslash", + /* 0x040 */ + "bracketright", "asciicircum", "underscore", "grave", + "a", "b", "c", "d", + "e", "f", "g", "h", + "i", "j", "k", "l", + /* 0x050 */ + "m", "n", "o", "p", + "q", "r", "s", "t", + "u", "v", "w", "x", + "y", "z", "braceleft", "bar", + /* 0x060 */ + "braceright", "asciitilde", "Adieresis", "Aring", + "Ccedilla", "Eacute", "Ntilde", "Odieresis", + "Udieresis", "aacute", "agrave", "acircumflex", + "adieresis", "atilde", "aring", "ccedilla", + /* 0x070 */ + "eacute", "egrave", "ecircumflex", "edieresis", + "iacute", "igrave", "icircumflex", "idieresis", + "ntilde", "oacute", "ograve", "ocircumflex", + "odieresis", "otilde", "uacute", "ugrave", + /* 0x080 */ + "ucircumflex", "udieresis", "dagger", "degree", + "cent", "sterling", "section", "bullet", + "paragraph", "germandbls", "registered", "copyright", + "trademark", "acute", "dieresis", "notequal", + /* 0x090 */ + "AE", "Oslash", "infinity", "plusminus", + "lessequal", "greaterequal", "yen", "mu", + "partialdiff", "summation", "product", "pi", + "integral", "ordfeminine", "ordmasculine", "Omega", + /* 0x0A0 */ + "ae", "oslash", "questiondown", "exclamdown", + "logicalnot", "radical", "florin", "approxequal", + "Delta", "guillemotleft", "guillemotright", "ellipsis", + "nonbreakingspace", "Agrave", "Atilde", "Otilde", + /* 0x0B0 */ + "OE", "oe", "endash", "emdash", + "quotedblleft", "quotedblright", "quoteleft", "quoteright", + "divide", "lozenge", "ydieresis", "Ydieresis", + "fraction", "currency", "guilsinglleft", "guilsinglright", + /* 0x0C0 */ + "fi", "fl", "daggerdbl", "periodcentered", + "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", + "Ecircumflex", "Aacute", "Edieresis", "Egrave", + "Iacute", "Icircumflex", "Idieresis", "Igrave", + /* 0x0D0 */ + "Oacute", "Ocircumflex", "apple", "Ograve", + "Uacute", "Ucircumflex", "Ugrave", "dotlessi", + "circumflex", "tilde", "macron", "breve", + "dotaccent", "ring", "cedilla", "hungarumlaut", + /* 0x0E0 */ + "ogonek", "caron", "Lslash", "lslash", + "Scaron", "scaron", "Zcaron", "zcaron", + "brokenbar", "Eth", "eth", "Yacute", + "yacute", "Thorn", "thorn", "minus", + /* 0x0F0 */ + "multiply", "onesuperior", "twosuperior", "threesuperior", + "onehalf", "onequarter", "threequarters", "franc", + "Gbreve", "gbreve", "Idotaccent", "Scedilla", + "scedilla", "Cacute", "cacute", "Ccaron", + /* 0x100 */ + "ccaron", "dcroat" + }; + + /** The FontFileReader used to read this TrueType font. */ + protected FontFileReader fontFile; + + /** Set to true to get even more debug output than with level DEBUG */ + public static final boolean TRACE_ENABLED = false; + + private static final String ENCODING = "WinAnsiEncoding"; // Default encoding + + private static final short FIRST_CHAR = 0; + + protected boolean useKerning = false; + private boolean isEmbeddable = true; + private boolean hasSerifs = true; + /** + * Table directory + */ + protected Map<OFTableName, OFDirTabEntry> dirTabs; + + private Map<Integer, Map<Integer, Integer>> kerningTab; // for CIDs + private Map<Integer, Map<Integer, Integer>> ansiKerningTab; // For winAnsiEncoding + private List<CMapSegment> cmaps; + protected List<UnicodeMapping> unicodeMappings; + + private int upem; // unitsPerEm from "head" table + private int nhmtx; // Number of horizontal metrics + private PostScriptVersion postScriptVersion; + protected int locaFormat; + /** + * Offset to last loca + */ + protected long lastLoca = 0; + protected int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table) + + /** + * Contains glyph data + */ + protected OFMtxEntry[] mtxTab; // Contains glyph data + + protected String postScriptName = ""; + protected String fullName = ""; + protected String notice = ""; + protected final Set<String> familyNames = new HashSet<String>(); + protected String subFamilyName = ""; + + private long italicAngle = 0; + private long isFixedPitch = 0; + private int fontBBox1 = 0; + private int fontBBox2 = 0; + private int fontBBox3 = 0; + private int fontBBox4 = 0; + private int capHeight = 0; + private int os2CapHeight = 0; + private int xHeight = 0; + private int os2xHeight = 0; + //Effective ascender/descender + private int ascender = 0; + private int descender = 0; + //Ascender/descender from hhea table + private int hheaAscender = 0; + private int hheaDescender = 0; + //Ascender/descender from OS/2 table + private int os2Ascender = 0; + private int os2Descender = 0; + private int usWeightClass = 0; + + private short lastChar = 0; + + private int[] ansiWidth; + private Map<Integer, List<Integer>> ansiIndex; + + // internal mapping of glyph indexes to unicode indexes + // used for quick mappings in this class + private final Map<Integer, Integer> glyphToUnicodeMap = new HashMap<Integer, Integer>(); + private final Map<Integer, Integer> unicodeToGlyphMap = new HashMap<Integer, Integer>(); + + private boolean isCFF; + + // advanced typographic table support + protected boolean useAdvanced = false; + protected OTFAdvancedTypographicTableReader advancedTableReader; + + /** + * Version of the PostScript table (<q>post</q>) contained in this font. + */ + public static enum PostScriptVersion { + /** PostScript table version 1.0. */ + V1, + /** PostScript table version 2.0. */ + V2, + /** PostScript table version 3.0. */ + V3, + /** Unknown version of the PostScript table. */ + UNKNOWN; + } + + /** + * logging instance + */ + protected Log log = LogFactory.getLog(TTFFile.class); + + public OpenFont() { + this(true, false); + } + + /** + * Constructor + * @param useKerning true if kerning data should be loaded + * @param useAdvanced true if advanced typographic tables should be loaded + */ + public OpenFont(boolean useKerning, boolean useAdvanced) { + this.useKerning = useKerning; + this.useAdvanced = useAdvanced; + } + + /** + * Key-value helper class. + */ + final class UnicodeMapping implements Comparable { + + private final int unicodeIndex; + private final int glyphIndex; + + UnicodeMapping(int glyphIndex, int unicodeIndex) { + this.unicodeIndex = unicodeIndex; + this.glyphIndex = glyphIndex; + glyphToUnicodeMap.put(new Integer(glyphIndex), new Integer(unicodeIndex)); + unicodeToGlyphMap.put(new Integer(unicodeIndex), new Integer(glyphIndex)); + } + + /** + * Returns the glyphIndex. + * @return the glyph index + */ + public int getGlyphIndex() { + return glyphIndex; + } + + /** + * Returns the unicodeIndex. + * @return the Unicode index + */ + public int getUnicodeIndex() { + return unicodeIndex; + } + + + /** {@inheritDoc} */ + public int hashCode() { + int hc = unicodeIndex; + hc = 19 * hc + (hc ^ glyphIndex); + return hc; + } + + /** {@inheritDoc} */ + public boolean equals(Object o) { + if (o instanceof UnicodeMapping) { + UnicodeMapping m = (UnicodeMapping) o; + if (unicodeIndex != m.unicodeIndex) { + return false; + } else { + return (glyphIndex == m.glyphIndex); + } + } else { + return false; + } + } + + /** {@inheritDoc} */ + public int compareTo(Object o) { + if (o instanceof UnicodeMapping) { + UnicodeMapping m = (UnicodeMapping) o; + if (unicodeIndex > m.unicodeIndex) { + return 1; + } else if (unicodeIndex < m.unicodeIndex) { + return -1; + } else { + return 0; + } + } else { + return -1; + } + } + } + + /** + * Obtain directory table entry. + * @param name (tag) of entry + * @return a directory table entry or null if none found + */ + public OFDirTabEntry getDirectoryEntry(OFTableName name) { + return dirTabs.get(name); + } + + /** + * Position inputstream to position indicated + * in the dirtab offset + offset + * @param in font file reader + * @param tableName (tag) of table + * @param offset from start of table + * @return true if seek succeeded + * @throws IOException if I/O exception occurs during seek + */ + public boolean seekTab(FontFileReader in, OFTableName tableName, + long offset) throws IOException { + OFDirTabEntry dt = dirTabs.get(tableName); + if (dt == null) { + log.error("Dirtab " + tableName.getName() + " not found."); + return false; + } else { + in.seekSet(dt.getOffset() + offset); + } + return true; + } + + /** + * Convert from truetype unit to pdf unit based on the + * unitsPerEm field in the "head" table + * @param n truetype unit + * @return pdf unit + */ + public int convertTTFUnit2PDFUnit(int n) { + int ret; + if (n < 0) { + long rest1 = n % upem; + long storrest = 1000 * rest1; + long ledd2 = (storrest != 0 ? rest1 / storrest : 0); + ret = -((-1000 * n) / upem - (int)ledd2); + } else { + ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem; + } + + return ret; + } + + /** + * Read the cmap table, + * return false if the table is not present or only unsupported + * tables are present. Currently only unicode cmaps are supported. + * Set the unicodeIndex in the TTFMtxEntries and fills in the + * cmaps vector. + */ + protected boolean readCMAP() throws IOException { + + unicodeMappings = new ArrayList<OpenFont.UnicodeMapping>(); + + seekTab(fontFile, OFTableName.CMAP, 2); + int numCMap = fontFile.readTTFUShort(); // Number of cmap subtables + long cmapUniOffset = 0; + long symbolMapOffset = 0; + + if (log.isDebugEnabled()) { + log.debug(numCMap + " cmap tables"); + } + + //Read offset for all tables. We are only interested in the unicode table + for (int i = 0; i < numCMap; i++) { + int cmapPID = fontFile.readTTFUShort(); + int cmapEID = fontFile.readTTFUShort(); + long cmapOffset = fontFile.readTTFLong(); + + if (log.isDebugEnabled()) { + log.debug("Platform ID: " + cmapPID + " Encoding: " + cmapEID); + } + + if (cmapPID == 3 && cmapEID == 1) { + cmapUniOffset = cmapOffset; + } + if (cmapPID == 3 && cmapEID == 0) { + symbolMapOffset = cmapOffset; + } + } + + if (cmapUniOffset > 0) { + return readUnicodeCmap(cmapUniOffset, 1); + } else if (symbolMapOffset > 0) { + return readUnicodeCmap(symbolMapOffset, 0); + } else { + log.fatal("Unsupported TrueType font: No Unicode or Symbol cmap table" + + " not present. Aborting"); + return false; + } + } + + private boolean readUnicodeCmap(long cmapUniOffset, int encodingID) + throws IOException { + //Read CMAP table and correct mtxTab.index + int mtxPtr = 0; + + // Read unicode cmap + seekTab(fontFile, OFTableName.CMAP, cmapUniOffset); + int cmapFormat = fontFile.readTTFUShort(); + /*int cmap_length =*/ fontFile.readTTFUShort(); //skip cmap length + + if (log.isDebugEnabled()) { + log.debug("CMAP format: " + cmapFormat); + } + + if (cmapFormat == 4) { + fontFile.skip(2); // Skip version number + int cmapSegCountX2 = fontFile.readTTFUShort(); + int cmapSearchRange = fontFile.readTTFUShort(); + int cmapEntrySelector = fontFile.readTTFUShort(); + int cmapRangeShift = fontFile.readTTFUShort(); + + if (log.isDebugEnabled()) { + log.debug("segCountX2 : " + cmapSegCountX2); + log.debug("searchRange : " + cmapSearchRange); + log.debug("entrySelector: " + cmapEntrySelector); + log.debug("rangeShift : " + cmapRangeShift); + } + + + int[] cmapEndCounts = new int[cmapSegCountX2 / 2]; + int[] cmapStartCounts = new int[cmapSegCountX2 / 2]; + int[] cmapDeltas = new int[cmapSegCountX2 / 2]; + int[] cmapRangeOffsets = new int[cmapSegCountX2 / 2]; + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapEndCounts[i] = fontFile.readTTFUShort(); + } + + fontFile.skip(2); // Skip reservedPad + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapStartCounts[i] = fontFile.readTTFUShort(); + } + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapDeltas[i] = fontFile.readTTFShort(); + } + + //int startRangeOffset = in.getCurrentPos(); + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapRangeOffsets[i] = fontFile.readTTFUShort(); + } + + int glyphIdArrayOffset = fontFile.getCurrentPos(); + + BitSet eightBitGlyphs = new BitSet(256); + + // Insert the unicode id for the glyphs in mtxTab + // and fill in the cmaps ArrayList + for (int i = 0; i < cmapStartCounts.length; i++) { + + if (log.isTraceEnabled()) { + log.trace(i + ": " + cmapStartCounts[i] + + " - " + cmapEndCounts[i]); + } + if (log.isDebugEnabled()) { + if (isInPrivateUseArea(cmapStartCounts[i], cmapEndCounts[i])) { + log.debug("Font contains glyphs in the Unicode private use area: " + + Integer.toHexString(cmapStartCounts[i]) + " - " + + Integer.toHexString(cmapEndCounts[i])); + } + } + + for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) { + + // Update lastChar + if (j < 256 && j > lastChar) { + lastChar = (short)j; + } + + if (j < 256) { + eightBitGlyphs.set(j); + } + + if (mtxPtr < mtxTab.length) { + int glyphIdx; + // the last character 65535 = .notdef + // may have a range offset + if (cmapRangeOffsets[i] != 0 && j != 65535) { + int glyphOffset = glyphIdArrayOffset + + ((cmapRangeOffsets[i] / 2) + + (j - cmapStartCounts[i]) + + (i) + - cmapSegCountX2 / 2) * 2; + fontFile.seekSet(glyphOffset); + glyphIdx = (fontFile.readTTFUShort() + cmapDeltas[i]) + & 0xffff; + //mtxTab[glyphIdx].setName(mtxTab[glyphIdx].getName() + " - "+(char)j); + unicodeMappings.add(new UnicodeMapping(glyphIdx, j)); + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + + if (encodingID == 0 && j >= 0xF020 && j <= 0xF0FF) { + //Experimental: Mapping 0xF020-0xF0FF to 0x0020-0x00FF + //Tested with Wingdings and Symbol TTF fonts which map their + //glyphs in the region 0xF020-0xF0FF. + int mapped = j - 0xF000; + if (!eightBitGlyphs.get(mapped)) { + //Only map if Unicode code point hasn't been mapped before + unicodeMappings.add(new UnicodeMapping(glyphIdx, mapped)); + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(mapped)); + } + } + + // Also add winAnsiWidth + List<Integer> v = ansiIndex.get(new Integer(j)); + if (v != null) { + for (Integer aIdx : v) { + ansiWidth[aIdx.intValue()] + = mtxTab[glyphIdx].getWx(); + + if (log.isTraceEnabled()) { + log.trace("Added width " + + mtxTab[glyphIdx].getWx() + + " uni: " + j + + " ansi: " + aIdx.intValue()); + } + } + } + + if (log.isTraceEnabled()) { + log.trace("Idx: " + + glyphIdx + + " Delta: " + cmapDeltas[i] + + " Unicode: " + j + + " name: " + mtxTab[glyphIdx].getName()); + } + } else { + glyphIdx = (j + cmapDeltas[i]) & 0xffff; + + if (glyphIdx < mtxTab.length) { + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + } else { + log.debug("Glyph " + glyphIdx + + " out of range: " + + mtxTab.length); + } + + unicodeMappings.add(new UnicodeMapping(glyphIdx, j)); + if (glyphIdx < mtxTab.length) { + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + } else { + log.debug("Glyph " + glyphIdx + + " out of range: " + + mtxTab.length); + } + + // Also add winAnsiWidth + List<Integer> v = ansiIndex.get(new Integer(j)); + if (v != null) { + for (Integer aIdx : v) { + ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx(); + } + } + + //getLogger().debug("IIdx: " + + // mtxPtr + + // " Delta: " + cmap_deltas[i] + + // " Unicode: " + j + + // " name: " + + // mtxTab[(j+cmap_deltas[i]) & 0xffff].name); + + } + if (glyphIdx < mtxTab.length) { + if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) { + mtxPtr++; + } + } + } + } + } + } else { + log.error("Cmap format not supported: " + cmapFormat); + return false; + } + return true; + } + + private boolean isInPrivateUseArea(int start, int end) { + return (isInPrivateUseArea(start) || isInPrivateUseArea(end)); + } + + private boolean isInPrivateUseArea(int unicode) { + return (unicode >= 0xE000 && unicode <= 0xF8FF); + } + + /** + * + * @return mmtx data + */ + public List<OFMtxEntry> getMtx() { + return Collections.unmodifiableList(Arrays.asList(mtxTab)); + } + + /** + * Print first char/last char + */ + private void printMaxMin() { + int min = 255; + int max = 0; + for (int i = 0; i < mtxTab.length; i++) { + if (mtxTab[i].getIndex() < min) { + min = mtxTab[i].getIndex(); + } + if (mtxTab[i].getIndex() > max) { + max = mtxTab[i].getIndex(); + } + } + log.info("Min: " + min); + log.info("Max: " + max); + } + + + /** + * Reads the font using a FontFileReader. + * + * @param in The FontFileReader to use + * @throws IOException In case of an I/O problem + */ + public void readFont(FontFileReader in, String header) throws IOException { + readFont(in, header, (String)null); + } + + /** + * initialize the ansiWidths array (for winAnsiEncoding) + * and fill with the missingwidth + */ + protected void initAnsiWidths() { + ansiWidth = new int[256]; + for (int i = 0; i < 256; i++) { + ansiWidth[i] = mtxTab[0].getWx(); + } + + // Create an index hash to the ansiWidth + // Can't just index the winAnsiEncoding when inserting widths + // same char (eg bullet) is repeated more than one place + ansiIndex = new HashMap<Integer, List<Integer>>(); + for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { + Integer ansi = new Integer(i); + Integer uni = new Integer(Glyphs.WINANSI_ENCODING[i]); + + List<Integer> v = ansiIndex.get(uni); + if (v == null) { + v = new ArrayList<Integer>(); + ansiIndex.put(uni, v); + } + v.add(ansi); + } + } + + /** + * Read the font data. + * If the fontfile is a TrueType Collection (.ttc file) + * the name of the font to read data for must be supplied, + * else the name is ignored. + * + * @param in The FontFileReader to use + * @param name The name of the font + * @return boolean Returns true if the font is valid + * @throws IOException In case of an I/O problem + */ + public boolean readFont(FontFileReader in, String header, String name) throws IOException { + initializeFont(in); + /* + * Check if TrueType collection, and that the name + * exists in the collection + */ + if (!checkTTC(header, name)) { + if (name == null) { + throw new IllegalArgumentException( + "For TrueType collection you must specify which font " + + "to select (-ttcname)"); + } else { + throw new IOException( + "Name does not exist in the TrueType collection: " + name); + } + } + + readDirTabs(); + readFontHeader(); + getNumGlyphs(); + if (log.isDebugEnabled()) { + log.debug("Number of glyphs in font: " + numberOfGlyphs); + } + readHorizontalHeader(); + readHorizontalMetrics(); + initAnsiWidths(); + readPostScript(); + readOS2(); + determineAscDesc(); + + readName(); + boolean pcltFound = readPCLT(); + // Read cmap table and fill in ansiwidths + boolean valid = readCMAP(); + if (!valid) { + return false; + } + + // Create cmaps for bfentries + createCMaps(); + updateBBoxAndOffset(); + + if (useKerning) { + readKerning(); + } + handleCharacterSpacing(in); + + guessVerticalMetricsFromGlyphBBox(); + return true; + } + + /** + * Reads a font. + * + * @param in FontFileReader to read from + * @param name Name to be checked for in the font file + * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and + * new index as (Integer) value) + * @throws IOException in case of an I/O problem + */ + public void readFont(FontFileReader in, String header, MultiByteFont mbfont) throws IOException { + readFont(in, header, mbfont.getTTCName()); + } + + protected abstract void updateBBoxAndOffset() throws IOException; + + protected abstract void readName() throws IOException; + + protected abstract void initializeFont(FontFileReader in) throws IOException; + + protected void handleCharacterSpacing(FontFileReader in) throws IOException { + // Read advanced typographic tables. + if (useAdvanced) { + try { + OTFAdvancedTypographicTableReader atr + = new OTFAdvancedTypographicTableReader(this, in); + atr.readAll(); + this.advancedTableReader = atr; + } catch (AdvancedTypographicTableFormatException e) { + log.warn( + "Encountered format constraint violation in advanced (typographic) table (AT) " + + "in font '" + getFullName() + "', ignoring AT data: " + + e.getMessage() + ); + } + } + + } + + protected void createCMaps() { + cmaps = new ArrayList<CMapSegment>(); + int unicodeStart; + int glyphStart; + int unicodeEnd; + + Iterator<UnicodeMapping> e = unicodeMappings.iterator(); + UnicodeMapping um = e.next(); + UnicodeMapping lastMapping = um; + + unicodeStart = um.getUnicodeIndex(); + glyphStart = um.getGlyphIndex(); + + while (e.hasNext()) { + um = e.next(); + if (((lastMapping.getUnicodeIndex() + 1) != um.getUnicodeIndex()) + || ((lastMapping.getGlyphIndex() + 1) != um.getGlyphIndex())) { + unicodeEnd = lastMapping.getUnicodeIndex(); + cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart)); + unicodeStart = um.getUnicodeIndex(); + glyphStart = um.getGlyphIndex(); + } + lastMapping = um; + } + + unicodeEnd = lastMapping.getUnicodeIndex(); + cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart)); + } + + /** + * Returns the PostScript name of the font. + * @return String The PostScript name + */ + public String getPostScriptName() { + if (postScriptName.length() == 0) { + return FontUtil.stripWhiteSpace(getFullName()); + } else { + return postScriptName; + } + } + + PostScriptVersion getPostScriptVersion() { + return postScriptVersion; + } + + /** + * Returns the font family names of the font. + * @return Set The family names (a Set of Strings) + */ + public Set<String> getFamilyNames() { + return familyNames; + } + + /** + * Returns the font sub family name of the font. + * @return String The sub family name + */ + public String getSubFamilyName() { + return subFamilyName; + } + + /** + * Returns the full name of the font. + * @return String The full name + */ + public String getFullName() { + return fullName; + } + + /** + * Returns the name of the character set used. + * @return String The caracter set + */ + public String getCharSetName() { + return ENCODING; + } + + /** + * Returns the CapHeight attribute of the font. + * @return int The CapHeight + */ + public int getCapHeight() { + return convertTTFUnit2PDFUnit(capHeight); + } + + /** + * Returns the XHeight attribute of the font. + * @return int The XHeight + */ + public int getXHeight() { + return convertTTFUnit2PDFUnit(xHeight); + } + + /** + * Returns the number of bytes necessary to pad the currentPosition so that a table begins + * on a 4-byte boundary. + * @param currentPosition the position to pad. + * @return int the number of bytes to pad. + */ + protected int getPadSize(int currentPosition) { + int padSize = 4 - (currentPosition % 4); + return padSize < 4 ? padSize : 0; + } + + /** + * Returns the Flags attribute of the font. + * @return int The Flags + */ + public int getFlags() { + int flags = 32; // Use Adobe Standard charset + if (italicAngle != 0) { + flags |= 64; + } + if (isFixedPitch != 0) { + flags |= 2; + } + if (hasSerifs) { + flags |= 1; + } + return flags; + } + + /** + * Returns the weight class of this font. Valid values are 100, 200....,800, 900. + * @return the weight class value (or 0 if there was no OS/2 table in the font) + */ + public int getWeightClass() { + return this.usWeightClass; + } + + /** + * Returns the StemV attribute of the font. + * @return String The StemV + */ + public String getStemV() { + return "0"; + } + + /** + * Returns the ItalicAngle attribute of the font. + * @return String The ItalicAngle + */ + public String getItalicAngle() { + String ia = Short.toString((short)(italicAngle / 0x10000)); + + // This is the correct italic angle, however only int italic + // angles are supported at the moment so this is commented out. + /* + * if ((italicAngle % 0x10000) > 0 ) + * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000))); + */ + return ia; + } + + /** + * @return int[] The font bbox + */ + public int[] getFontBBox() { + final int[] fbb = new int[4]; + fbb[0] = convertTTFUnit2PDFUnit(fontBBox1); + fbb[1] = convertTTFUnit2PDFUnit(fontBBox2); + fbb[2] = convertTTFUnit2PDFUnit(fontBBox3); + fbb[3] = convertTTFUnit2PDFUnit(fontBBox4); + + return fbb; + } + + /** + * Returns the LowerCaseAscent attribute of the font. + * @return int The LowerCaseAscent + */ + public int getLowerCaseAscent() { + return convertTTFUnit2PDFUnit(ascender); + } + + /** + * Returns the LowerCaseDescent attribute of the font. + * @return int The LowerCaseDescent + */ + public int getLowerCaseDescent() { + return convertTTFUnit2PDFUnit(descender); + } + + /** + * Returns the index of the last character, but this is for WinAnsiEncoding + * only, so the last char is < 256. + * @return short Index of the last character (<256) + */ + public short getLastChar() { + return lastChar; + } + + /** + * Returns the index of the first character. + * @return short Index of the first character + */ + public short getFirstChar() { + return FIRST_CHAR; + } + + /** + * Returns an array of character widths. + * @return int[] The character widths + */ + public int[] getWidths() { + int[] wx = new int[mtxTab.length]; + for (int i = 0; i < wx.length; i++) { + wx[i] = convertTTFUnit2PDFUnit(mtxTab[i].getWx()); + } + + return wx; + } + + /** + * Returns an array (xMin, yMin, xMax, yMax) for a glyph. + * + * @param glyphIndex the index of the glyph + * @return int[] Array defining bounding box. + */ + public int[] getBBox(int glyphIndex) { + int[] bboxInTTFUnits = mtxTab[glyphIndex].getBoundingBox(); + int[] bbox = new int[4]; + for (int i = 0; i < 4; i++) { + bbox[i] = convertTTFUnit2PDFUnit(bboxInTTFUnits[i]); + } + return bbox; + } + + /** + * Returns the width of a given character. + * @param idx Index of the character + * @return int Standard width + */ + public int getCharWidth(int idx) { + return convertTTFUnit2PDFUnit(ansiWidth[idx]); + } + + /** + * Returns the kerning table. + * @return Map The kerning table + */ + public Map<Integer, Map<Integer, Integer>> getKerning() { + return kerningTab; + } + + /** + * Returns the ANSI kerning table. + * @return Map The ANSI kerning table + */ + public Map<Integer, Map<Integer, Integer>> getAnsiKerning() { + return ansiKerningTab; + } + + /** + * Indicates if the font may be embedded. + * @return boolean True if it may be embedded + */ + public boolean isEmbeddable() { + return isEmbeddable; + } + + /** + * Indicates whether or not the font is an OpenType + * CFF font (rather than a TrueType font). + * @return true if the font is in OpenType CFF format. + */ + public boolean isCFF() { + return this.isCFF; + } + + /** + * Read Table Directory from the current position in the + * FontFileReader and fill the global HashMap dirTabs + * with the table name (String) as key and a TTFDirTabEntry + * as value. + * @throws IOException in case of an I/O problem + */ + protected void readDirTabs() throws IOException { + int sfntVersion = fontFile.readTTFLong(); // TTF_FIXED_SIZE (4 bytes) + switch (sfntVersion) { + case 0x10000: + log.debug("sfnt version: OpenType 1.0"); + break; + case 0x4F54544F: //"OTTO" + this.isCFF = true; + log.debug("sfnt version: OpenType with CFF data"); + break; + case 0x74727565: //"true" + log.debug("sfnt version: Apple TrueType"); + break; + case 0x74797031: //"typ1" + log.debug("sfnt version: Apple Type 1 housed in sfnt wrapper"); + break; + default: + log.debug("Unknown sfnt version: " + Integer.toHexString(sfntVersion)); + break; + } + int ntabs = fontFile.readTTFUShort(); + fontFile.skip(6); // 3xTTF_USHORT_SIZE + + dirTabs = new HashMap<OFTableName, OFDirTabEntry>(); + OFDirTabEntry[] pd = new OFDirTabEntry[ntabs]; + log.debug("Reading " + ntabs + " dir tables"); + + for (int i = 0; i < ntabs; i++) { + pd[i] = new OFDirTabEntry(); + String tableName = pd[i].read(fontFile); + dirTabs.put(OFTableName.getValue(tableName), pd[i]); + } + dirTabs.put(OFTableName.TABLE_DIRECTORY, + new OFDirTabEntry(0L, fontFile.getCurrentPos())); + log.debug("dir tables: " + dirTabs.keySet()); + } + + /** + * Read the "head" table, this reads the bounding box and + * sets the upem (unitsPerEM) variable + * @throws IOException in case of an I/O problem + */ + protected void readFontHeader() throws IOException { + seekTab(fontFile, OFTableName.HEAD, 2 * 4 + 2 * 4); + int flags = fontFile.readTTFUShort(); + if (log.isDebugEnabled()) { + log.debug("flags: " + flags + " - " + Integer.toString(flags, 2)); + } + upem = fontFile.readTTFUShort(); + if (log.isDebugEnabled()) { + log.debug("unit per em: " + upem); + } + + fontFile.skip(16); + + fontBBox1 = fontFile.readTTFShort(); + fontBBox2 = fontFile.readTTFShort(); + fontBBox3 = fontFile.readTTFShort(); + fontBBox4 = fontFile.readTTFShort(); + if (log.isDebugEnabled()) { + log.debug("font bbox: xMin=" + fontBBox1 + + " yMin=" + fontBBox2 + + " xMax=" + fontBBox3 + + " yMax=" + fontBBox4); + } + + fontFile.skip(2 + 2 + 2); + + locaFormat = fontFile.readTTFShort(); + } + + /** + * Read the number of glyphs from the "maxp" table + * @throws IOException in case of an I/O problem + */ + protected void getNumGlyphs() throws IOException { + seekTab(fontFile, OFTableName.MAXP, 4); + numberOfGlyphs = fontFile.readTTFUShort(); + } + + + /** + * Read the "hhea" table to find the ascender and descender and + * size of "hmtx" table, as a fixed size font might have only + * one width. + * @throws IOException in case of an I/O problem + */ + protected void readHorizontalHeader() + throws IOException { + seekTab(fontFile, OFTableName.HHEA, 4); + hheaAscender = fontFile.readTTFShort(); + hheaDescender = fontFile.readTTFShort(); + + fontFile.skip(2 + 2 + 3 * 2 + 8 * 2); + nhmtx = fontFile.readTTFUShort(); + + if (log.isDebugEnabled()) { + log.debug("hhea.Ascender: " + formatUnitsForDebug(hheaAscender)); + log.debug("hhea.Descender: " + formatUnitsForDebug(hheaDescender)); + log.debug("Number of horizontal metrics: " + nhmtx); + } + } + + /** + * Read "hmtx" table and put the horizontal metrics + * in the mtxTab array. If the number of metrics is less + * than the number of glyphs (eg fixed size fonts), extend + * the mtxTab array and fill in the missing widths + * @throws IOException in case of an I/O problem + */ + protected void readHorizontalMetrics() + throws IOException { + seekTab(fontFile, OFTableName.HMTX, 0); + + int mtxSize = Math.max(numberOfGlyphs, nhmtx); + mtxTab = new OFMtxEntry[mtxSize]; + + if (log.isTraceEnabled()) { + log.trace("*** Widths array: \n"); + } + for (int i = 0; i < mtxSize; i++) { + mtxTab[i] = new OFMtxEntry(); + } + for (int i = 0; i < nhmtx; i++) { + mtxTab[i].setWx(fontFile.readTTFUShort()); + mtxTab[i].setLsb(fontFile.readTTFUShort()); + + if (log.isTraceEnabled()) { + log.trace(" width[" + i + "] = " + + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";"); + } + } + + if (nhmtx < mtxSize) { + // Fill in the missing widths + int lastWidth = mtxTab[nhmtx - 1].getWx(); + for (int i = nhmtx; i < mtxSize; i++) { + mtxTab[i].setWx(lastWidth); + mtxTab[i].setLsb(fontFile.readTTFUShort()); + } + } + } + + + /** + * Read the "post" table + * containing the PostScript names of the glyphs. + */ + protected void readPostScript() throws IOException { + seekTab(fontFile, OFTableName.POST, 0); + int postFormat = fontFile.readTTFLong(); + italicAngle = fontFile.readTTFULong(); + //underlinePosition + fontFile.readTTFShort(); + //underlineThickness + fontFile.readTTFShort(); + isFixedPitch = fontFile.readTTFULong(); + + //Skip memory usage values + fontFile.skip(4 * 4); + + log.debug("PostScript format: 0x" + Integer.toHexString(postFormat)); + switch (postFormat) { + case 0x00010000: + log.debug("PostScript format 1"); + postScriptVersion = PostScriptVersion.V1; + for (int i = 0; i < MAC_GLYPH_ORDERING.length; i++) { + mtxTab[i].setName(MAC_GLYPH_ORDERING[i]); + } + break; + case 0x00020000: + log.debug("PostScript format 2"); + postScriptVersion = PostScriptVersion.V2; + int numGlyphStrings = 0; + + // Read Number of Glyphs + int l = fontFile.readTTFUShort(); + + // Read indexes + for (int i = 0; i < l; i++) { + mtxTab[i].setIndex(fontFile.readTTFUShort()); + + if (mtxTab[i].getIndex() > 257) { + //Index is not in the Macintosh standard set + numGlyphStrings++; + } + + if (log.isTraceEnabled()) { + log.trace("PostScript index: " + mtxTab[i].getIndexAsString()); + } + } + + // firstChar=minIndex; + String[] psGlyphsBuffer = new String[numGlyphStrings]; + if (log.isDebugEnabled()) { + log.debug("Reading " + numGlyphStrings + + " glyphnames, that are not in the standard Macintosh" + + " set. Total number of glyphs=" + l); + } + for (int i = 0; i < psGlyphsBuffer.length; i++) { + psGlyphsBuffer[i] = fontFile.readTTFString(fontFile.readTTFUByte()); + } + + //Set glyph names + for (int i = 0; i < l; i++) { + if (mtxTab[i].getIndex() < MAC_GLYPH_ORDERING.length) { + mtxTab[i].setName(MAC_GLYPH_ORDERING[mtxTab[i].getIndex()]); + } else { + if (!mtxTab[i].isIndexReserved()) { + int k = mtxTab[i].getIndex() - MAC_GLYPH_ORDERING.length; + + if (log.isTraceEnabled()) { + log.trace(k + " i=" + i + " mtx=" + mtxTab.length + + " ps=" + psGlyphsBuffer.length); + } + + mtxTab[i].setName(psGlyphsBuffer[k]); + } + } + } + + break; + case 0x00030000: + // PostScript format 3 contains no glyph names + log.debug("PostScript format 3"); + postScriptVersion = PostScriptVersion.V3; + break; + default: + log.error("Unknown PostScript format: " + postFormat); + postScriptVersion = PostScriptVersion.UNKNOWN; + } + } + + + /** + * Read the "OS/2" table + */ + protected void readOS2() throws IOException { + // Check if font is embeddable + OFDirTabEntry os2Entry = dirTabs.get(OFTableName.OS2); + if (os2Entry != null) { + seekTab(fontFile, OFTableName.OS2, 0); + int version = fontFile.readTTFUShort(); + if (log.isDebugEnabled()) { + log.debug("OS/2 table: version=" + version + + ", offset=" + os2Entry.getOffset() + ", len=" + os2Entry.getLength()); + } + fontFile.skip(2); //xAvgCharWidth + this.usWeightClass = fontFile.readTTFUShort(); + + // usWidthClass + fontFile.skip(2); + + int fsType = fontFile.readTTFUShort(); + if (fsType == 2) { + isEmbeddable = false; + } else { + isEmbeddable = true; + } + fontFile.skip(11 * 2); + fontFile.skip(10); //panose array + fontFile.skip(4 * 4); //unicode ranges + fontFile.skip(4); + fontFile.skip(3 * 2); + int v; + os2Ascender = fontFile.readTTFShort(); //sTypoAscender + os2Descender = fontFile.readTTFShort(); //sTypoDescender + if (log.isDebugEnabled()) { + log.debug("sTypoAscender: " + os2Ascender + + " -> internal " + convertTTFUnit2PDFUnit(os2Ascender)); + log.debug("sTypoDescender: " + os2Descender + + " -> internal " + convertTTFUnit2PDFUnit(os2Descender)); + } + v = fontFile.readTTFShort(); //sTypoLineGap + if (log.isDebugEnabled()) { + log.debug("sTypoLineGap: " + v); + } + v = fontFile.readTTFUShort(); //usWinAscent + if (log.isDebugEnabled()) { + log.debug("usWinAscent: " + formatUnitsForDebug(v)); + } + v = fontFile.readTTFUShort(); //usWinDescent + if (log.isDebugEnabled()) { + log.debug("usWinDescent: " + formatUnitsForDebug(v)); + } + + //version 1 OS/2 table might end here + if (os2Entry.getLength() >= 78 + (2 * 4) + (2 * 2)) { + fontFile.skip(2 * 4); + this.os2xHeight = fontFile.readTTFShort(); //sxHeight + this.os2CapHeight = fontFile.readTTFShort(); //sCapHeight + if (log.isDebugEnabled()) { + log.debug("sxHeight: " + this.os2xHeight); + log.debug("sCapHeight: " + this.os2CapHeight); + } + } + + } else { + isEmbeddable = true; + } + } + + /** + * Read the "PCLT" table to find xHeight and capHeight. + * @throws IOException In case of a I/O problem + */ + protected boolean readPCLT() throws IOException { + OFDirTabEntry dirTab = dirTabs.get(OFTableName.PCLT); + if (dirTab != null) { + fontFile.seekSet(dirTab.getOffset() + 4 + 4 + 2); + xHeight = fontFile.readTTFUShort(); + log.debug("xHeight from PCLT: " + formatUnitsForDebug(xHeight)); + fontFile.skip(2 * 2); + capHeight = fontFile.readTTFUShort(); + log.debug("capHeight from PCLT: " + formatUnitsForDebug(capHeight)); + fontFile.skip(2 + 16 + 8 + 6 + 1 + 1); + + int serifStyle = fontFile.readTTFUByte(); + serifStyle = serifStyle >> 6; + serifStyle = serifStyle & 3; + if (serifStyle == 1) { + hasSerifs = false; + } else { + hasSerifs = true; + } + return true; + } else { + return false; + } + } + + /** + * Determines the right source for the ascender and descender values. The problem here is + * that the interpretation of these values is not the same for every font. There doesn't seem + * to be a uniform definition of an ascender and a descender. In some fonts + * the hhea values are defined after the Apple interpretation, but not in every font. The + * same problem is in the OS/2 table. FOP needs the ascender and descender to determine the + * baseline so we need values which add up more or less to the "em box". However, due to + * accent modifiers a character can grow beyond the em box. + */ + protected void determineAscDesc() { + int hheaBoxHeight = hheaAscender - hheaDescender; + int os2BoxHeight = os2Ascender - os2Descender; + if (os2Ascender > 0 && os2BoxHeight <= upem) { + ascender = os2Ascender; + descender = os2Descender; + } else if (hheaAscender > 0 && hheaBoxHeight <= upem) { + ascender = hheaAscender; + descender = hheaDescender; + } else { + if (os2Ascender > 0) { + //Fall back to info from OS/2 if possible + ascender = os2Ascender; + descender = os2Descender; + } else { + ascender = hheaAscender; + descender = hheaDescender; + } + } + + if (log.isDebugEnabled()) { + log.debug("Font box height: " + (ascender - descender)); + if (ascender - descender > upem) { + log.debug("Ascender and descender together are larger than the em box."); + } + } + } + + protected void guessVerticalMetricsFromGlyphBBox() { + // Approximate capHeight from height of "H" + // It's most unlikely that a font misses the PCLT table + // This also assumes that postscriptnames exists ("H") + // Should look it up in the cmap (that wouldn't help + // for charsets without H anyway...) + // Same for xHeight with the letter "x" + int localCapHeight = 0; + int localXHeight = 0; + int localAscender = 0; + int localDescender = 0; + for (int i = 0; i < mtxTab.length; i++) { + if ("H".equals(mtxTab[i].getName())) { + localCapHeight = mtxTab[i].getBoundingBox()[3]; + } else if ("x".equals(mtxTab[i].getName())) { + localXHeight = mtxTab[i].getBoundingBox()[3]; + } else if ("d".equals(mtxTab[i].getName())) { + localAscender = mtxTab[i].getBoundingBox()[3]; + } else if ("p".equals(mtxTab[i].getName())) { + localDescender = mtxTab[i].getBoundingBox()[1]; + } else { + // OpenType Fonts with a version 3.0 "post" table don't have glyph names. + // Use Unicode indices instead. + List unicodeIndex = mtxTab[i].getUnicodeIndex(); + if (unicodeIndex.size() > 0) { + //Only the first index is used + char ch = (char)((Integer)unicodeIndex.get(0)).intValue(); + if (ch == 'H') { + localCapHeight = mtxTab[i].getBoundingBox()[3]; + } else if (ch == 'x') { + localXHeight = mtxTab[i].getBoundingBox()[3]; + } else if (ch == 'd') { + localAscender = mtxTab[i].getBoundingBox()[3]; + } else if (ch == 'p') { + localDescender = mtxTab[i].getBoundingBox()[1]; + } + } + } + } + if (log.isDebugEnabled()) { + log.debug("Ascender from glyph 'd': " + formatUnitsForDebug(localAscender)); + log.debug("Descender from glyph 'p': " + formatUnitsForDebug(localDescender)); + } + if (ascender - descender > upem) { + log.debug("Replacing specified ascender/descender with derived values to get values" + + " which fit in the em box."); + ascender = localAscender; + descender = localDescender; + } + + if (log.isDebugEnabled()) { + log.debug("xHeight from glyph 'x': " + formatUnitsForDebug(localXHeight)); + log.debug("CapHeight from glyph 'H': " + formatUnitsForDebug(localCapHeight)); + } + if (capHeight == 0) { + capHeight = localCapHeight; + if (capHeight == 0) { + capHeight = os2CapHeight; + } + if (capHeight == 0) { + log.debug("capHeight value could not be determined." + + " The font may not work as expected."); + } + } + if (xHeight == 0) { + xHeight = localXHeight; + if (xHeight == 0) { + xHeight = os2xHeight; + } + if (xHeight == 0) { + log.debug("xHeight value could not be determined." + + " The font may not work as expected."); + } + } + } + + /** + * Read the kerning table, create a table for both CIDs and + * winAnsiEncoding. + * @throws IOException In case of a I/O problem + */ + protected void readKerning() throws IOException { + // Read kerning + kerningTab = new HashMap<Integer, Map<Integer, Integer>>(); + ansiKerningTab = new HashMap<Integer, Map<Integer, Integer>>(); + OFDirTabEntry dirTab = dirTabs.get(OFTableName.KERN); + if (dirTab != null) { + seekTab(fontFile, OFTableName.KERN, 2); + for (int n = fontFile.readTTFUShort(); n > 0; n--) { + fontFile.skip(2 * 2); + int k = fontFile.readTTFUShort(); + if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) { + return; + } + if ((k >> 8) != 0) { + continue; + } + + k = fontFile.readTTFUShort(); + fontFile.skip(3 * 2); + while (k-- > 0) { + int i = fontFile.readTTFUShort(); + int j = fontFile.readTTFUShort(); + int kpx = fontFile.readTTFShort(); + if (kpx != 0) { + // CID kerning table entry, using unicode indexes + final Integer iObj = glyphToUnicode(i); + final Integer u2 = glyphToUnicode(j); + if (iObj == null) { + // happens for many fonts (Ubuntu font set), + // stray entries in the kerning table?? + log.debug("Ignoring kerning pair because no Unicode index was" + + " found for the first glyph " + i); + } else if (u2 == null) { + log.debug("Ignoring kerning pair because Unicode index was" + + " found for the second glyph " + i); + } else { + Map<Integer, Integer> adjTab = kerningTab.get(iObj); + if (adjTab == null) { + adjTab = new HashMap<Integer, Integer>(); + } + adjTab.put(u2, new Integer(convertTTFUnit2PDFUnit(kpx))); + kerningTab.put(iObj, adjTab); + } + } + } + } + + // Create winAnsiEncoded kerning table from kerningTab + // (could probably be simplified, for now we remap back to CID indexes and + // then to winAnsi) + for (Integer unicodeKey1 : kerningTab.keySet()) { + Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue()); + Map<Integer, Integer> akpx = new HashMap<Integer, Integer>(); + Map<Integer, Integer> ckpx = kerningTab.get(unicodeKey1); + + for (Integer unicodeKey2 : ckpx.keySet()) { + Integer cidKey2 = unicodeToGlyph(unicodeKey2.intValue()); + Integer kern = ckpx.get(unicodeKey2); + + Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator(); + while (uniMap.hasNext()) { + Integer unicodeKey = (Integer)uniMap.next(); + Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); + for (int u = 0; u < ansiKeys.length; u++) { + akpx.put(ansiKeys[u], kern); + } + } + } + + if (akpx.size() > 0) { + Iterator uniMap = mtxTab[cidKey1.intValue()].getUnicodeIndex().listIterator(); + while (uniMap.hasNext()) { + Integer unicodeKey = (Integer)uniMap.next(); + Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); + for (int u = 0; u < ansiKeys.length; u++) { + ansiKerningTab.put(ansiKeys[u], akpx); + } + } + } + } + } + } + + /** + * Streams a font. + * @param ttfOut The interface for streaming TrueType tables. + * @exception IOException file write error + */ + public void stream(TTFOutputStream ttfOut) throws IOException { + SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortedDirTabs = sortDirTabMap(dirTabs); + byte[] file = fontFile.getAllBytes(); + TTFTableOutputStream tableOut = ttfOut.getTableOutputStream(); + TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream(); + ttfOut.startFontStream(); + for (Map.Entry<OFTableName, OFDirTabEntry> entry : sortedDirTabs) { + int offset = (int) entry.getValue().getOffset(); + int paddedLength = (int) entry.getValue().getLength(); + paddedLength += getPadSize(offset + paddedLength); + if (entry.getKey().equals(OFTableName.GLYF)) { + streamGlyf(glyphOut, file, offset, paddedLength); + } else { + tableOut.streamTable(file, offset, paddedLength); + } + } + ttfOut.endFontStream(); + } + + private void streamGlyf(TTFGlyphOutputStream glyphOut, byte[] fontFile, int tableOffset, + int tableLength) throws IOException { + //Stream all but the last glyph + int glyphStart = 0; + int glyphEnd = 0; + glyphOut.startGlyphStream(); + for (int i = 0; i < mtxTab.length - 1; i++) { + glyphStart = (int) mtxTab[i].getOffset() + tableOffset; + glyphEnd = (int) mtxTab[i + 1].getOffset() + tableOffset; + glyphOut.streamGlyph(fontFile, glyphStart, glyphEnd - glyphStart); + } + glyphOut.streamGlyph(fontFile, glyphEnd, (tableOffset + tableLength) - glyphEnd); + glyphOut.endGlyphStream(); + } + + /** + * Returns the order in which the tables in a TrueType font should be written to file. + * @param directoryTabs the map that is to be sorted. + * @return TTFTablesNames[] an array of table names sorted in the order they should appear in + * the TTF file. + */ + SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> + sortDirTabMap(Map<OFTableName, OFDirTabEntry> directoryTabs) { + SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortedSet + = new TreeSet<Map.Entry<OFTableName, OFDirTabEntry>>( + new Comparator<Map.Entry<OFTableName, OFDirTabEntry>>() { + + public int compare(Entry<OFTableName, OFDirTabEntry> o1, + Entry<OFTableName, OFDirTabEntry> o2) { + return (int) (o1.getValue().getOffset() - o2.getValue().getOffset()); + } + }); + sortedSet.addAll(directoryTabs.entrySet()); + return sortedSet; + } + + /** + * Returns this font's character to glyph mapping. + * + * @return the font's cmap + */ + public List<CMapSegment> getCMaps() { + return cmaps; + } + + /** + * Check if this is a TrueType collection and that the given + * name exists in the collection. + * If it does, set offset in fontfile to the beginning of + * the Table Directory for that font. + * @param name The name to check + * @return True if not collection or font name present, false otherwise + * @throws IOException In case of an I/O problem + */ + protected final boolean checkTTC(String tag, String name) throws IOException { + if ("ttcf".equals(tag)) { + // This is a TrueType Collection + fontFile.skip(4); + + // Read directory offsets + int numDirectories = (int)fontFile.readTTFULong(); + // int numDirectories=in.readTTFUShort(); + long[] dirOffsets = new long[numDirectories]; + for (int i = 0; i < numDirectories; i++) { + dirOffsets[i] = fontFile.readTTFULong(); + } + + log.info("This is a TrueType collection file with " + + numDirectories + " fonts"); + log.info("Containing the following fonts: "); + // Read all the directories and name tables to check + // If the font exists - this is a bit ugly, but... + boolean found = false; + + // Iterate through all name tables even if font + // Is found, just to show all the names + long dirTabOffset = 0; + for (int i = 0; (i < numDirectories); i++) { + fontFile.seekSet(dirOffsets[i]); + readDirTabs(); + + readName(); + + if (fullName.equals(name)) { + found = true; + dirTabOffset = dirOffsets[i]; + log.info(fullName + " <-- selected"); + } else { + log.info(fullName); + } + + // Reset names + notice = ""; + fullName = ""; + familyNames.clear(); + postScriptName = ""; + subFamilyName = ""; + } + + fontFile.seekSet(dirTabOffset); + return found; + } else { + fontFile.seekSet(0); + return true; + } + } + + /** + * Return TTC font names + * @param in FontFileReader to read from + * @return True if not collection or font name present, false otherwise + * @throws IOException In case of an I/O problem + */ + public final List<String> getTTCnames(FontFileReader in) throws IOException { + this.fontFile = in; + + List<String> fontNames = new ArrayList<String>(); + String tag = in.readTTFString(4); + + if ("ttcf".equals(tag)) { + // This is a TrueType Collection + in.skip(4); + + // Read directory offsets + int numDirectories = (int)in.readTTFULong(); + long[] dirOffsets = new long[numDirectories]; + for (int i = 0; i < numDirectories; i++) { + dirOffsets[i] = in.readTTFULong(); + } + + log.info("This is a TrueType collection file with " + + numDirectories + " fonts"); + log.info("Containing the following fonts: "); + + for (int i = 0; (i < numDirectories); i++) { + in.seekSet(dirOffsets[i]); + readDirTabs(); + + readName(); + + log.info(fullName); + fontNames.add(fullName); + + // Reset names + notice = ""; + fullName = ""; + familyNames.clear(); + postScriptName = ""; + subFamilyName = ""; + } + + in.seekSet(0); + return fontNames; + } else { + log.error("Not a TTC!"); + return null; + } + } + + /* + * Helper classes, they are not very efficient, but that really + * doesn't matter... + */ + private Integer[] unicodeToWinAnsi(int unicode) { + List<Integer> ret = new ArrayList<Integer>(); + for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { + if (unicode == Glyphs.WINANSI_ENCODING[i]) { + ret.add(new Integer(i)); + } + } + return ret.toArray(new Integer[0]); + } + + /** + * Dumps a few informational values to System.out. + */ + public void printStuff() { + System.out.println("Font name: " + postScriptName); + System.out.println("Full name: " + fullName); + System.out.println("Family name: " + familyNames); + System.out.println("Subfamily name: " + subFamilyName); + System.out.println("Notice: " + notice); + System.out.println("xHeight: " + convertTTFUnit2PDFUnit(xHeight)); + System.out.println("capheight: " + convertTTFUnit2PDFUnit(capHeight)); + + int italic = (int)(italicAngle >> 16); + System.out.println("Italic: " + italic); + System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000)); + if ((italicAngle % 0x10000) > 0) { + System.out.print("." + + (short)((italicAngle % 0x10000) * 1000) + / 0x10000); + } + System.out.println(); + System.out.println("Ascender: " + convertTTFUnit2PDFUnit(ascender)); + System.out.println("Descender: " + convertTTFUnit2PDFUnit(descender)); + System.out.println("FontBBox: [" + convertTTFUnit2PDFUnit(fontBBox1) + + " " + convertTTFUnit2PDFUnit(fontBBox2) + " " + + convertTTFUnit2PDFUnit(fontBBox3) + " " + + convertTTFUnit2PDFUnit(fontBBox4) + "]"); + } + + private String formatUnitsForDebug(int units) { + return units + " -> " + convertTTFUnit2PDFUnit(units) + " internal units"; + } + + /** + * Map a glyph index to the corresponding unicode code point + * + * @param glyphIndex + * @return unicode code point + */ + private Integer glyphToUnicode(int glyphIndex) { + return glyphToUnicodeMap.get(new Integer(glyphIndex)); + } + + /** + * Map a unicode code point to the corresponding glyph index + * + * @param unicodeIndex unicode code point + * @return glyph index + */ + private Integer unicodeToGlyph(int unicodeIndex) throws IOException { + final Integer result + = unicodeToGlyphMap.get(new Integer(unicodeIndex)); + if (result == null) { + throw new IOException( + "Glyph index not found for unicode value " + unicodeIndex); + } + return result; + } + + String getGlyphName(int glyphIndex) { + return mtxTab[glyphIndex].getName(); + } + + /** + * Determine if advanced (typographic) table is present. + * @return true if advanced (typographic) table is present + */ + public boolean hasAdvancedTable() { + if (advancedTableReader != null) { + return advancedTableReader.hasAdvancedTable(); + } else { + return false; + } + } + + /** + * Returns the GDEF table or null if none present. + * @return the GDEF table + */ + public GlyphDefinitionTable getGDEF() { + if (advancedTableReader != null) { + return advancedTableReader.getGDEF(); + } else { + return null; + } + } + + /** + * Returns the GSUB table or null if none present. + * @return the GSUB table + */ + public GlyphSubstitutionTable getGSUB() { + if (advancedTableReader != null) { + return advancedTableReader.getGSUB(); + } else { + return null; + } + } + + /** + * Returns the GPOS table or null if none present. + * @return the GPOS table + */ + public GlyphPositioningTable getGPOS() { + if (advancedTableReader != null) { + return advancedTableReader.getGPOS(); + } else { + return null; + } + } + + /** + * Static main method to get info about a TrueType font. + * @param args The command line arguments + */ + public static void main(String[] args) { + InputStream stream = null; + try { + boolean useKerning = true; + boolean useAdvanced = true; + + stream = new FileInputStream(args[0]); + FontFileReader reader = new FontFileReader(stream); + + String name = null; + if (args.length >= 2) { + name = args[1]; + } + + String header = OFFontLoader.readHeader(reader); + boolean isCFF = header.equals("OTTO"); + OpenFont otfFile = (isCFF) ? new OTFFile() : new TTFFile(useKerning, useAdvanced); + otfFile.readFont(reader, header, name); + otfFile.printStuff(); + + } catch (IOException ioe) { + System.err.println("Problem reading font: " + ioe.toString()); + ioe.printStackTrace(System.err); + } finally { + IOUtils.closeQuietly(stream); + } + } +} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFile.java b/src/java/org/apache/fop/fonts/truetype/TTFFile.java index 2e78ef7f0..52df45ffb 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFFile.java +++ b/src/java/org/apache/fop/fonts/truetype/TTFFile.java @@ -19,221 +19,14 @@ package org.apache.fop.fonts.truetype; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.apache.xmlgraphics.fonts.Glyphs; - -import org.apache.fop.complexscripts.fonts.AdvancedTypographicTableFormatException; -import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable; -import org.apache.fop.complexscripts.fonts.GlyphPositioningTable; -import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable; -import org.apache.fop.complexscripts.fonts.OTFAdvancedTypographicTableReader; -import org.apache.fop.fonts.CMapSegment; -import org.apache.fop.fonts.FontUtil; /** * Reads a TrueType file or a TrueType Collection. * The TrueType spec can be found at the Microsoft. * Typography site: http://www.microsoft.com/truetype/ */ -public class TTFFile { - - static final byte NTABS = 24; - static final int MAX_CHAR_CODE = 255; - static final int ENC_BUF_SIZE = 1024; - - private static final String[] MAC_GLYPH_ORDERING = { - /* 0x000 */ - ".notdef", ".null", "nonmarkingreturn", "space", - "exclam", "quotedbl", "numbersign", "dollar", - "percent", "ampersand", "quotesingle", "parenleft", - "parenright", "asterisk", "plus", "comma", - /* 0x010 */ - "hyphen", "period", "slash", "zero", - "one", "two", "three", "four", - "five", "six", "seven", "eight", - "nine", "colon", "semicolon", "less", - /* 0x020 */ - "equal", "greater", "question", "at", - "A", "B", "C", "D", - "E", "F", "G", "H", - "I", "J", "K", "L", - /* 0x030 */ - "M", "N", "O", "P", - "Q", "R", "S", "T", - "U", "V", "W", "X", - "Y", "Z", "bracketleft", "backslash", - /* 0x040 */ - "bracketright", "asciicircum", "underscore", "grave", - "a", "b", "c", "d", - "e", "f", "g", "h", - "i", "j", "k", "l", - /* 0x050 */ - "m", "n", "o", "p", - "q", "r", "s", "t", - "u", "v", "w", "x", - "y", "z", "braceleft", "bar", - /* 0x060 */ - "braceright", "asciitilde", "Adieresis", "Aring", - "Ccedilla", "Eacute", "Ntilde", "Odieresis", - "Udieresis", "aacute", "agrave", "acircumflex", - "adieresis", "atilde", "aring", "ccedilla", - /* 0x070 */ - "eacute", "egrave", "ecircumflex", "edieresis", - "iacute", "igrave", "icircumflex", "idieresis", - "ntilde", "oacute", "ograve", "ocircumflex", - "odieresis", "otilde", "uacute", "ugrave", - /* 0x080 */ - "ucircumflex", "udieresis", "dagger", "degree", - "cent", "sterling", "section", "bullet", - "paragraph", "germandbls", "registered", "copyright", - "trademark", "acute", "dieresis", "notequal", - /* 0x090 */ - "AE", "Oslash", "infinity", "plusminus", - "lessequal", "greaterequal", "yen", "mu", - "partialdiff", "summation", "product", "pi", - "integral", "ordfeminine", "ordmasculine", "Omega", - /* 0x0A0 */ - "ae", "oslash", "questiondown", "exclamdown", - "logicalnot", "radical", "florin", "approxequal", - "Delta", "guillemotleft", "guillemotright", "ellipsis", - "nonbreakingspace", "Agrave", "Atilde", "Otilde", - /* 0x0B0 */ - "OE", "oe", "endash", "emdash", - "quotedblleft", "quotedblright", "quoteleft", "quoteright", - "divide", "lozenge", "ydieresis", "Ydieresis", - "fraction", "currency", "guilsinglleft", "guilsinglright", - /* 0x0C0 */ - "fi", "fl", "daggerdbl", "periodcentered", - "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", - "Ecircumflex", "Aacute", "Edieresis", "Egrave", - "Iacute", "Icircumflex", "Idieresis", "Igrave", - /* 0x0D0 */ - "Oacute", "Ocircumflex", "apple", "Ograve", - "Uacute", "Ucircumflex", "Ugrave", "dotlessi", - "circumflex", "tilde", "macron", "breve", - "dotaccent", "ring", "cedilla", "hungarumlaut", - /* 0x0E0 */ - "ogonek", "caron", "Lslash", "lslash", - "Scaron", "scaron", "Zcaron", "zcaron", - "brokenbar", "Eth", "eth", "Yacute", - "yacute", "Thorn", "thorn", "minus", - /* 0x0F0 */ - "multiply", "onesuperior", "twosuperior", "threesuperior", - "onehalf", "onequarter", "threequarters", "franc", - "Gbreve", "gbreve", "Idotaccent", "Scedilla", - "scedilla", "Cacute", "cacute", "Ccaron", - /* 0x100 */ - "ccaron", "dcroat" - }; - - /** The FontFileReader used to read this TrueType font. */ - protected FontFileReader fontFile; - - /** Set to true to get even more debug output than with level DEBUG */ - public static final boolean TRACE_ENABLED = false; - - private final String encoding = "WinAnsiEncoding"; // Default encoding - - private final short firstChar = 0; - - private boolean useKerning = false; - - private boolean isEmbeddable = true; - private boolean hasSerifs = true; - /** - * Table directory - */ - protected Map<TTFTableName, TTFDirTabEntry> dirTabs; - private Map<Integer, Map<Integer, Integer>> kerningTab; // for CIDs - private Map<Integer, Map<Integer, Integer>> ansiKerningTab; // For winAnsiEncoding - private List<CMapSegment> cmaps; - private Set<UnicodeMapping> unicodeMappings; - - private int upem; // unitsPerEm from "head" table - private int nhmtx; // Number of horizontal metrics - private PostScriptVersion postScriptVersion; - private int locaFormat; - /** - * Offset to last loca - */ - protected long lastLoca = 0; - private int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table) - - /** - * Contains glyph data - */ - protected TTFMtxEntry[] mtxTab; // Contains glyph data - - private String postScriptName = ""; - private String fullName = ""; - private String notice = ""; - private Set<String> familyNames = new HashSet<String>(); - private String subFamilyName = ""; - - private long italicAngle = 0; - private long isFixedPitch = 0; - private int fontBBox1 = 0; - private int fontBBox2 = 0; - private int fontBBox3 = 0; - private int fontBBox4 = 0; - private int capHeight = 0; - private int os2CapHeight = 0; - private int underlinePosition = 0; - private int underlineThickness = 0; - private int xHeight = 0; - private int os2xHeight = 0; - //Effective ascender/descender - private int ascender = 0; - private int descender = 0; - //Ascender/descender from hhea table - private int hheaAscender = 0; - private int hheaDescender = 0; - //Ascender/descender from OS/2 table - private int os2Ascender = 0; - private int os2Descender = 0; - private int usWeightClass = 0; - - private short lastChar = 0; - - private int[] ansiWidth; - private Map<Integer, List<Integer>> ansiIndex; - - // internal mapping of glyph indexes to unicode indexes - // used for quick mappings in this class - private final Map<Integer, Integer> glyphToUnicodeMap = new HashMap<Integer, Integer>(); - private final Map<Integer, Integer> unicodeToGlyphMap = new HashMap<Integer, Integer>(); - - private TTFDirTabEntry currentDirTab; - - private boolean isCFF; - - // advanced typographic table support - private boolean useAdvanced = false; - private OTFAdvancedTypographicTableReader advancedTableReader; - - /** - * logging instance - */ - protected Log log = LogFactory.getLog(TTFFile.class); +public class TTFFile extends OpenFont { public TTFFile() { this(true, false); @@ -245,1183 +38,15 @@ public class TTFFile { * @param useAdvanced true if advanced typographic tables should be loaded */ public TTFFile(boolean useKerning, boolean useAdvanced) { - this.useKerning = useKerning; - this.useAdvanced = useAdvanced; - } - - /** - * Key-value helper class. - */ - final class UnicodeMapping implements Comparable { - - private final int unicodeIndex; - private final int glyphIndex; - - UnicodeMapping(int glyphIndex, int unicodeIndex) { - this.unicodeIndex = unicodeIndex; - this.glyphIndex = glyphIndex; - glyphToUnicodeMap.put(new Integer(glyphIndex), new Integer(unicodeIndex)); - unicodeToGlyphMap.put(new Integer(unicodeIndex), new Integer(glyphIndex)); - } - - /** - * Returns the glyphIndex. - * @return the glyph index - */ - public int getGlyphIndex() { - return glyphIndex; - } - - /** - * Returns the unicodeIndex. - * @return the Unicode index - */ - public int getUnicodeIndex() { - return unicodeIndex; - } - - - /** {@inheritDoc} */ - public int hashCode() { - int hc = unicodeIndex; - hc = 19 * hc + (hc ^ glyphIndex); - return hc; - } - - /** {@inheritDoc} */ - public boolean equals(Object o) { - if (o instanceof UnicodeMapping) { - UnicodeMapping m = (UnicodeMapping) o; - if (unicodeIndex != m.unicodeIndex) { - return false; - } else { - return (glyphIndex == m.glyphIndex); - } - } else { - return false; - } - } - - /** {@inheritDoc} */ - public int compareTo(Object o) { - if (o instanceof UnicodeMapping) { - UnicodeMapping m = (UnicodeMapping) o; - if (unicodeIndex > m.unicodeIndex) { - return 1; - } else if (unicodeIndex < m.unicodeIndex) { - return -1; - } else { - return 0; - } - } else { - return -1; - } - } - } - - /** - * Version of the PostScript table (<q>post</q>) contained in this font. - */ - public static enum PostScriptVersion { - /** PostScript table version 1.0. */ - V1, - /** PostScript table version 2.0. */ - V2, - /** PostScript table version 3.0. */ - V3, - /** Unknown version of the PostScript table. */ - UNKNOWN; - } - - /** - * Obtain directory table entry. - * @param name (tag) of entry - * @return a directory table entry or null if none found - */ - public TTFDirTabEntry getDirectoryEntry(TTFTableName name) { - return dirTabs.get(name); - } - - /** - * Position inputstream to position indicated - * in the dirtab offset + offset - * @param in font file reader - * @param tableName (tag) of table - * @param offset from start of table - * @return true if seek succeeded - * @throws IOException if I/O exception occurs during seek - */ - public boolean seekTab(FontFileReader in, TTFTableName tableName, - long offset) throws IOException { - TTFDirTabEntry dt = dirTabs.get(tableName); - if (dt == null) { - log.error("Dirtab " + tableName.getName() + " not found."); - return false; - } else { - in.seekSet(dt.getOffset() + offset); - this.currentDirTab = dt; - } - return true; - } - - /** - * Convert from truetype unit to pdf unit based on the - * unitsPerEm field in the "head" table - * @param n truetype unit - * @return pdf unit - */ - public int convertTTFUnit2PDFUnit(int n) { - int ret; - if (n < 0) { - long rest1 = n % upem; - long storrest = 1000 * rest1; - long ledd2 = (storrest != 0 ? rest1 / storrest : 0); - ret = -((-1000 * n) / upem - (int)ledd2); - } else { - ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem; - } - - return ret; - } - - /** - * Read the cmap table, - * return false if the table is not present or only unsupported - * tables are present. Currently only unicode cmaps are supported. - * Set the unicodeIndex in the TTFMtxEntries and fills in the - * cmaps vector. - */ - private boolean readCMAP() throws IOException { - - unicodeMappings = new java.util.TreeSet(); - - seekTab(fontFile, TTFTableName.CMAP, 2); - int numCMap = fontFile.readTTFUShort(); // Number of cmap subtables - long cmapUniOffset = 0; - long symbolMapOffset = 0; - - if (log.isDebugEnabled()) { - log.debug(numCMap + " cmap tables"); - } - - //Read offset for all tables. We are only interested in the unicode table - for (int i = 0; i < numCMap; i++) { - int cmapPID = fontFile.readTTFUShort(); - int cmapEID = fontFile.readTTFUShort(); - long cmapOffset = fontFile.readTTFLong(); - - if (log.isDebugEnabled()) { - log.debug("Platform ID: " + cmapPID + " Encoding: " + cmapEID); - } - - if (cmapPID == 3 && cmapEID == 1) { - cmapUniOffset = cmapOffset; - } - if (cmapPID == 3 && cmapEID == 0) { - symbolMapOffset = cmapOffset; - } - } - - if (cmapUniOffset > 0) { - return readUnicodeCmap(cmapUniOffset, 1); - } else if (symbolMapOffset > 0) { - return readUnicodeCmap(symbolMapOffset, 0); - } else { - log.fatal("Unsupported TrueType font: No Unicode or Symbol cmap table" - + " not present. Aborting"); - return false; - } - } - - private boolean readUnicodeCmap( - long cmapUniOffset, int encodingID) - throws IOException { - //Read CMAP table and correct mtxTab.index - int mtxPtr = 0; - - // Read unicode cmap - seekTab(fontFile, TTFTableName.CMAP, cmapUniOffset); - int cmapFormat = fontFile.readTTFUShort(); - /*int cmap_length =*/ fontFile.readTTFUShort(); //skip cmap length - - if (log.isDebugEnabled()) { - log.debug("CMAP format: " + cmapFormat); - } - - if (cmapFormat == 4) { - fontFile.skip(2); // Skip version number - int cmapSegCountX2 = fontFile.readTTFUShort(); - int cmapSearchRange = fontFile.readTTFUShort(); - int cmapEntrySelector = fontFile.readTTFUShort(); - int cmapRangeShift = fontFile.readTTFUShort(); - - if (log.isDebugEnabled()) { - log.debug("segCountX2 : " + cmapSegCountX2); - log.debug("searchRange : " + cmapSearchRange); - log.debug("entrySelector: " + cmapEntrySelector); - log.debug("rangeShift : " + cmapRangeShift); - } - - - int[] cmapEndCounts = new int[cmapSegCountX2 / 2]; - int[] cmapStartCounts = new int[cmapSegCountX2 / 2]; - int[] cmapDeltas = new int[cmapSegCountX2 / 2]; - int[] cmapRangeOffsets = new int[cmapSegCountX2 / 2]; - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapEndCounts[i] = fontFile.readTTFUShort(); - } - - fontFile.skip(2); // Skip reservedPad - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapStartCounts[i] = fontFile.readTTFUShort(); - } - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapDeltas[i] = fontFile.readTTFShort(); - } - - //int startRangeOffset = in.getCurrentPos(); - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapRangeOffsets[i] = fontFile.readTTFUShort(); - } - - int glyphIdArrayOffset = fontFile.getCurrentPos(); - - BitSet eightBitGlyphs = new BitSet(256); - - // Insert the unicode id for the glyphs in mtxTab - // and fill in the cmaps ArrayList - - for (int i = 0; i < cmapStartCounts.length; i++) { - - if (log.isTraceEnabled()) { - log.trace(i + ": " + cmapStartCounts[i] - + " - " + cmapEndCounts[i]); - } - if (log.isDebugEnabled()) { - if (isInPrivateUseArea(cmapStartCounts[i], cmapEndCounts[i])) { - log.debug("Font contains glyphs in the Unicode private use area: " - + Integer.toHexString(cmapStartCounts[i]) + " - " - + Integer.toHexString(cmapEndCounts[i])); - } - } - - for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) { - - // Update lastChar - if (j < 256 && j > lastChar) { - lastChar = (short)j; - } - - if (j < 256) { - eightBitGlyphs.set(j); - } - - if (mtxPtr < mtxTab.length) { - int glyphIdx; - // the last character 65535 = .notdef - // may have a range offset - if (cmapRangeOffsets[i] != 0 && j != 65535) { - int glyphOffset = glyphIdArrayOffset - + ((cmapRangeOffsets[i] / 2) - + (j - cmapStartCounts[i]) - + (i) - - cmapSegCountX2 / 2) * 2; - fontFile.seekSet(glyphOffset); - glyphIdx = (fontFile.readTTFUShort() + cmapDeltas[i]) - & 0xffff; - - unicodeMappings.add(new UnicodeMapping(glyphIdx, j)); - mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); - - // Also add winAnsiWidth - List<Integer> v = ansiIndex.get(new Integer(j)); - if (v != null) { - for (Integer aIdx : v) { - ansiWidth[aIdx.intValue()] - = mtxTab[glyphIdx].getWx(); - - if (log.isTraceEnabled()) { - log.trace("Added width " - + mtxTab[glyphIdx].getWx() - + " uni: " + j - + " ansi: " + aIdx.intValue()); - } - } - } - - if (log.isTraceEnabled()) { - log.trace("Idx: " - + glyphIdx - + " Delta: " + cmapDeltas[i] - + " Unicode: " + j - + " name: " + mtxTab[glyphIdx].getName()); - } - } else { - glyphIdx = (j + cmapDeltas[i]) & 0xffff; - - if (glyphIdx < mtxTab.length) { - mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); - } else { - log.debug("Glyph " + glyphIdx - + " out of range: " - + mtxTab.length); - } - - unicodeMappings.add(new UnicodeMapping(glyphIdx, j)); - if (glyphIdx < mtxTab.length) { - mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); - } else { - log.debug("Glyph " + glyphIdx - + " out of range: " - + mtxTab.length); - } - - // Also add winAnsiWidth - List<Integer> v = ansiIndex.get(new Integer(j)); - if (v != null) { - for (Integer aIdx : v) { - ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx(); - } - } - - //getLogger().debug("IIdx: " + - // mtxPtr + - // " Delta: " + cmap_deltas[i] + - // " Unicode: " + j + - // " name: " + - // mtxTab[(j+cmap_deltas[i]) & 0xffff].name); - - } - if (glyphIdx < mtxTab.length) { - if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) { - mtxPtr++; - } - } - } - } - } - } else { - log.error("Cmap format not supported: " + cmapFormat); - return false; - } - return true; - } - - private boolean isInPrivateUseArea(int start, int end) { - return (isInPrivateUseArea(start) || isInPrivateUseArea(end)); - } - - private boolean isInPrivateUseArea(int unicode) { - return (unicode >= 0xE000 && unicode <= 0xF8FF); - } - - /** - * Print first char/last char - */ - private void printMaxMin() { - int min = 255; - int max = 0; - for (int i = 0; i < mtxTab.length; i++) { - if (mtxTab[i].getIndex() < min) { - min = mtxTab[i].getIndex(); - } - if (mtxTab[i].getIndex() > max) { - max = mtxTab[i].getIndex(); - } - } - log.info("Min: " + min); - log.info("Max: " + max); - } - - - /** - * Reads the font using a FontFileReader. - * - * @param in The FontFileReader to use - * @throws IOException In case of an I/O problem - */ - public void readFont(FontFileReader in) throws IOException { - readFont(in, (String)null); - } - - /** - * initialize the ansiWidths array (for winAnsiEncoding) - * and fill with the missingwidth - */ - private void initAnsiWidths() { - ansiWidth = new int[256]; - for (int i = 0; i < 256; i++) { - ansiWidth[i] = mtxTab[0].getWx(); - } - - // Create an index hash to the ansiWidth - // Can't just index the winAnsiEncoding when inserting widths - // same char (eg bullet) is repeated more than one place - ansiIndex = new HashMap<Integer, List<Integer>>(); - for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { - Integer ansi = new Integer(i); - Integer uni = new Integer(Glyphs.WINANSI_ENCODING[i]); - - List<Integer> v = ansiIndex.get(uni); - if (v == null) { - v = new ArrayList<Integer>(); - ansiIndex.put(uni, v); - } - v.add(ansi); - } - } - - /** - * Read the font data. - * If the fontfile is a TrueType Collection (.ttc file) - * the name of the font to read data for must be supplied, - * else the name is ignored. - * - * @param in The FontFileReader to use - * @param name The name of the font - * @return boolean Returns true if the font is valid - * @throws IOException In case of an I/O problem - */ - public boolean readFont(FontFileReader in, String name) throws IOException { - fontFile = in; - /* - * Check if TrueType collection, and that the name - * exists in the collection - */ - if (!checkTTC(name)) { - if (name == null) { - throw new IllegalArgumentException( - "For TrueType collection you must specify which font " - + "to select (-ttcname)"); - } else { - throw new IOException( - "Name does not exist in the TrueType collection: " + name); - } - } - - readDirTabs(); - readFontHeader(); - getNumGlyphs(); - if (log.isDebugEnabled()) { - log.debug("Number of glyphs in font: " + numberOfGlyphs); - } - readHorizontalHeader(); - readHorizontalMetrics(); - initAnsiWidths(); - readPostScript(); - readOS2(); - determineAscDesc(); - if (!isCFF) { - readIndexToLocation(); - readGlyf(); - } - readName(); - boolean pcltFound = readPCLT(); - // Read cmap table and fill in ansiwidths - boolean valid = readCMAP(); - if (!valid) { - return false; - } - // Create cmaps for bfentries - createCMaps(); - - if (useKerning) { - readKerning(); - } - - // Read advanced typographic tables. - if (useAdvanced) { - try { - OTFAdvancedTypographicTableReader atr - = new OTFAdvancedTypographicTableReader(this, in); - atr.readAll(); - this.advancedTableReader = atr; - } catch (AdvancedTypographicTableFormatException e) { - log.warn( - "Encountered format constraint violation in advanced (typographic) table (AT) " - + "in font '" + getFullName() + "', ignoring AT data: " - + e.getMessage() - ); - } - } - - guessVerticalMetricsFromGlyphBBox(); - return true; - } - - /** - * Reads a font. - * - * @param in FontFileReader to read from - * @param name Name to be checked for in the font file - * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and - * new index as (Integer) value) - * @throws IOException in case of an I/O problem - */ - public void readFont(FontFileReader in, String name, - Map<Integer, Integer> glyphs) throws IOException { - readFont(in, name); - } - - private void createCMaps() { - cmaps = new ArrayList<CMapSegment>(); - int unicodeStart; - int glyphStart; - int unicodeEnd; - - Iterator<UnicodeMapping> e = unicodeMappings.iterator(); - UnicodeMapping um = e.next(); - UnicodeMapping lastMapping = um; - - unicodeStart = um.getUnicodeIndex(); - glyphStart = um.getGlyphIndex(); - - while (e.hasNext()) { - um = e.next(); - if (((lastMapping.getUnicodeIndex() + 1) != um.getUnicodeIndex()) - || ((lastMapping.getGlyphIndex() + 1) != um.getGlyphIndex())) { - unicodeEnd = lastMapping.getUnicodeIndex(); - cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart)); - unicodeStart = um.getUnicodeIndex(); - glyphStart = um.getGlyphIndex(); - } - lastMapping = um; - } - - unicodeEnd = lastMapping.getUnicodeIndex(); - cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart)); - } - - /** - * Returns the PostScript name of the font. - * @return String The PostScript name - */ - public String getPostScriptName() { - if (postScriptName.length() == 0) { - return FontUtil.stripWhiteSpace(getFullName()); - } else { - return postScriptName; - } - } - - PostScriptVersion getPostScriptVersion() { - return postScriptVersion; - } - - /** - * Returns the font family names of the font. - * @return Set The family names (a Set of Strings) - */ - public Set<String> getFamilyNames() { - return familyNames; - } - - /** - * Returns the font sub family name of the font. - * @return String The sub family name - */ - public String getSubFamilyName() { - return subFamilyName; - } - - /** - * Returns the full name of the font. - * @return String The full name - */ - public String getFullName() { - return fullName; - } - - /** - * Returns the name of the character set used. - * @return String The caracter set - */ - public String getCharSetName() { - return encoding; - } - - /** - * Returns the CapHeight attribute of the font. - * @return int The CapHeight - */ - public int getCapHeight() { - return convertTTFUnit2PDFUnit(capHeight); - } - - /** - * Returns the XHeight attribute of the font. - * @return int The XHeight - */ - public int getXHeight() { - return convertTTFUnit2PDFUnit(xHeight); - } - - /** - * Returns the number of bytes necessary to pad the currentPosition so that a table begins - * on a 4-byte boundary. - * @param currentPosition the position to pad. - * @return int the number of bytes to pad. - */ - protected int getPadSize(int currentPosition) { - int padSize = 4 - (currentPosition % 4); - return padSize < 4 ? padSize : 0; - } - - /** - * Returns the Flags attribute of the font. - * @return int The Flags - */ - public int getFlags() { - int flags = 32; // Use Adobe Standard charset - if (italicAngle != 0) { - flags |= 64; - } - if (isFixedPitch != 0) { - flags |= 2; - } - if (hasSerifs) { - flags |= 1; - } - return flags; - } - - /** - * Returns the weight class of this font. Valid values are 100, 200....,800, 900. - * @return the weight class value (or 0 if there was no OS/2 table in the font) - */ - public int getWeightClass() { - return this.usWeightClass; - } - - /** - * Returns the StemV attribute of the font. - * @return String The StemV - */ - public String getStemV() { - return "0"; - } - - /** - * Returns the ItalicAngle attribute of the font. - * @return String The ItalicAngle - */ - public String getItalicAngle() { - String ia = Short.toString((short)(italicAngle / 0x10000)); - - // This is the correct italic angle, however only int italic - // angles are supported at the moment so this is commented out. - /* - * if ((italicAngle % 0x10000) > 0 ) - * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000))); - */ - return ia; - } - - /** - * @return int[] The font bbox - */ - public int[] getFontBBox() { - final int[] fbb = new int[4]; - fbb[0] = convertTTFUnit2PDFUnit(fontBBox1); - fbb[1] = convertTTFUnit2PDFUnit(fontBBox2); - fbb[2] = convertTTFUnit2PDFUnit(fontBBox3); - fbb[3] = convertTTFUnit2PDFUnit(fontBBox4); - - return fbb; - } - - /** - * Returns the LowerCaseAscent attribute of the font. - * @return int The LowerCaseAscent - */ - public int getLowerCaseAscent() { - return convertTTFUnit2PDFUnit(ascender); - } - - /** - * Returns the LowerCaseDescent attribute of the font. - * @return int The LowerCaseDescent - */ - public int getLowerCaseDescent() { - return convertTTFUnit2PDFUnit(descender); - } - - /** - * Returns the index of the last character, but this is for WinAnsiEncoding - * only, so the last char is < 256. - * @return short Index of the last character (<256) - */ - public short getLastChar() { - return lastChar; - } - - /** - * Returns the index of the first character. - * @return short Index of the first character - */ - public short getFirstChar() { - return firstChar; - } - - /** - * Returns an array of character widths. - * @return int[] The character widths - */ - public int[] getWidths() { - int[] wx = new int[mtxTab.length]; - for (int i = 0; i < wx.length; i++) { - wx[i] = convertTTFUnit2PDFUnit(mtxTab[i].getWx()); - } - - return wx; - } - - /** - * Returns an array (xMin, yMin, xMax, yMax) for a glyph. - * - * @param glyphIndex the index of the glyph - * @return int[] Array defining bounding box. - */ - public int[] getBBox(int glyphIndex) { - int[] bboxInTTFUnits = mtxTab[glyphIndex].getBoundingBox(); - int[] bbox = new int[4]; - for (int i = 0; i < 4; i++) { - bbox[i] = convertTTFUnit2PDFUnit(bboxInTTFUnits[i]); - } - return bbox; - } - - /** - * Returns the width of a given character. - * @param idx Index of the character - * @return int Standard width - */ - public int getCharWidth(int idx) { - return convertTTFUnit2PDFUnit(ansiWidth[idx]); - } - - /** - * Returns the kerning table. - * @return Map The kerning table - */ - public Map<Integer, Map<Integer, Integer>> getKerning() { - return kerningTab; - } - - /** - * Returns the ANSI kerning table. - * @return Map The ANSI kerning table - */ - public Map<Integer, Map<Integer, Integer>> getAnsiKerning() { - return ansiKerningTab; - } - - /** - * Indicates if the font may be embedded. - * @return boolean True if it may be embedded - */ - public boolean isEmbeddable() { - return isEmbeddable; - } - - /** - * Indicates whether or not the font is an OpenType - * CFF font (rather than a TrueType font). - * @return true if the font is in OpenType CFF format. - */ - public boolean isCFF() { - return this.isCFF; - } - - /** - * Read Table Directory from the current position in the - * FontFileReader and fill the global HashMap dirTabs - * with the table name (String) as key and a TTFDirTabEntry - * as value. - * @throws IOException in case of an I/O problem - */ - protected void readDirTabs() throws IOException { - int sfntVersion = fontFile.readTTFLong(); // TTF_FIXED_SIZE (4 bytes) - switch (sfntVersion) { - case 0x10000: - log.debug("sfnt version: OpenType 1.0"); - break; - case 0x4F54544F: //"OTTO" - this.isCFF = true; - log.debug("sfnt version: OpenType with CFF data"); - break; - case 0x74727565: //"true" - log.debug("sfnt version: Apple TrueType"); - break; - case 0x74797031: //"typ1" - log.debug("sfnt version: Apple Type 1 housed in sfnt wrapper"); - break; - default: - log.debug("Unknown sfnt version: " + Integer.toHexString(sfntVersion)); - break; - } - int ntabs = fontFile.readTTFUShort(); - fontFile.skip(6); // 3xTTF_USHORT_SIZE - - dirTabs = new HashMap<TTFTableName, TTFDirTabEntry>(); - TTFDirTabEntry[] pd = new TTFDirTabEntry[ntabs]; - log.debug("Reading " + ntabs + " dir tables"); - - for (int i = 0; i < ntabs; i++) { - pd[i] = new TTFDirTabEntry(); - String tableName = pd[i].read(fontFile); - dirTabs.put(TTFTableName.getValue(tableName), pd[i]); - } - dirTabs.put(TTFTableName.TABLE_DIRECTORY, - new TTFDirTabEntry(0L, fontFile.getCurrentPos())); - log.debug("dir tables: " + dirTabs.keySet()); - } - - /** - * Read the "head" table, this reads the bounding box and - * sets the upem (unitsPerEM) variable - * @throws IOException in case of an I/O problem - */ - protected void readFontHeader() throws IOException { - seekTab(fontFile, TTFTableName.HEAD, 2 * 4 + 2 * 4); - int flags = fontFile.readTTFUShort(); - if (log.isDebugEnabled()) { - log.debug("flags: " + flags + " - " + Integer.toString(flags, 2)); - } - upem = fontFile.readTTFUShort(); - if (log.isDebugEnabled()) { - log.debug("unit per em: " + upem); - } - - fontFile.skip(16); - - fontBBox1 = fontFile.readTTFShort(); - fontBBox2 = fontFile.readTTFShort(); - fontBBox3 = fontFile.readTTFShort(); - fontBBox4 = fontFile.readTTFShort(); - if (log.isDebugEnabled()) { - log.debug("font bbox: xMin=" + fontBBox1 - + " yMin=" + fontBBox2 - + " xMax=" + fontBBox3 - + " yMax=" + fontBBox4); - } - - fontFile.skip(2 + 2 + 2); - - locaFormat = fontFile.readTTFShort(); - } - - /** - * Read the number of glyphs from the "maxp" table - * @throws IOException in case of an I/O problem - */ - protected void getNumGlyphs() throws IOException { - seekTab(fontFile, TTFTableName.MAXP, 4); - numberOfGlyphs = fontFile.readTTFUShort(); - } - - - /** - * Read the "hhea" table to find the ascender and descender and - * size of "hmtx" table, as a fixed size font might have only - * one width. - * @throws IOException in case of an I/O problem - */ - protected void readHorizontalHeader() - throws IOException { - seekTab(fontFile, TTFTableName.HHEA, 4); - hheaAscender = fontFile.readTTFShort(); - hheaDescender = fontFile.readTTFShort(); - - fontFile.skip(2 + 2 + 3 * 2 + 8 * 2); - nhmtx = fontFile.readTTFUShort(); - - if (log.isDebugEnabled()) { - log.debug("hhea.Ascender: " + formatUnitsForDebug(hheaAscender)); - log.debug("hhea.Descender: " + formatUnitsForDebug(hheaDescender)); - log.debug("Number of horizontal metrics: " + nhmtx); - } - } - - /** - * Read "hmtx" table and put the horizontal metrics - * in the mtxTab array. If the number of metrics is less - * than the number of glyphs (eg fixed size fonts), extend - * the mtxTab array and fill in the missing widths - * @throws IOException in case of an I/O problem - */ - protected void readHorizontalMetrics() - throws IOException { - seekTab(fontFile, TTFTableName.HMTX, 0); - - int mtxSize = Math.max(numberOfGlyphs, nhmtx); - mtxTab = new TTFMtxEntry[mtxSize]; - - if (log.isTraceEnabled()) { - log.trace("*** Widths array: \n"); - } - for (int i = 0; i < mtxSize; i++) { - mtxTab[i] = new TTFMtxEntry(); - } - for (int i = 0; i < nhmtx; i++) { - mtxTab[i].setWx(fontFile.readTTFUShort()); - mtxTab[i].setLsb(fontFile.readTTFUShort()); - - if (log.isTraceEnabled()) { - log.trace(" width[" + i + "] = " - + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";"); - } - } - - if (nhmtx < mtxSize) { - // Fill in the missing widths - int lastWidth = mtxTab[nhmtx - 1].getWx(); - for (int i = nhmtx; i < mtxSize; i++) { - mtxTab[i].setWx(lastWidth); - mtxTab[i].setLsb(fontFile.readTTFUShort()); - } - } - } - - - /** - * Read the "post" table - * containing the PostScript names of the glyphs. - */ - private void readPostScript() throws IOException { - seekTab(fontFile, TTFTableName.POST, 0); - int postFormat = fontFile.readTTFLong(); - italicAngle = fontFile.readTTFULong(); - underlinePosition = fontFile.readTTFShort(); - underlineThickness = fontFile.readTTFShort(); - isFixedPitch = fontFile.readTTFULong(); - - //Skip memory usage values - fontFile.skip(4 * 4); - - log.debug("PostScript format: 0x" + Integer.toHexString(postFormat)); - switch (postFormat) { - case 0x00010000: - log.debug("PostScript format 1"); - postScriptVersion = PostScriptVersion.V1; - for (int i = 0; i < MAC_GLYPH_ORDERING.length; i++) { - mtxTab[i].setName(MAC_GLYPH_ORDERING[i]); - } - break; - case 0x00020000: - log.debug("PostScript format 2"); - postScriptVersion = PostScriptVersion.V2; - int numGlyphStrings = 0; - - // Read Number of Glyphs - int l = fontFile.readTTFUShort(); - - // Read indexes - for (int i = 0; i < l; i++) { - mtxTab[i].setIndex(fontFile.readTTFUShort()); - - if (mtxTab[i].getIndex() > 257) { - //Index is not in the Macintosh standard set - numGlyphStrings++; - } - - if (log.isTraceEnabled()) { - log.trace("PostScript index: " + mtxTab[i].getIndexAsString()); - } - } - - // firstChar=minIndex; - String[] psGlyphsBuffer = new String[numGlyphStrings]; - if (log.isDebugEnabled()) { - log.debug("Reading " + numGlyphStrings - + " glyphnames, that are not in the standard Macintosh" - + " set. Total number of glyphs=" + l); - } - for (int i = 0; i < psGlyphsBuffer.length; i++) { - psGlyphsBuffer[i] = fontFile.readTTFString(fontFile.readTTFUByte()); - } - - //Set glyph names - for (int i = 0; i < l; i++) { - if (mtxTab[i].getIndex() < MAC_GLYPH_ORDERING.length) { - mtxTab[i].setName(MAC_GLYPH_ORDERING[mtxTab[i].getIndex()]); - } else { - if (!mtxTab[i].isIndexReserved()) { - int k = mtxTab[i].getIndex() - MAC_GLYPH_ORDERING.length; - - if (log.isTraceEnabled()) { - log.trace(k + " i=" + i + " mtx=" + mtxTab.length - + " ps=" + psGlyphsBuffer.length); - } - - mtxTab[i].setName(psGlyphsBuffer[k]); - } - } - } - - break; - case 0x00030000: - // PostScript format 3 contains no glyph names - log.debug("PostScript format 3"); - postScriptVersion = PostScriptVersion.V3; - break; - default: - log.error("Unknown PostScript format: " + postFormat); - postScriptVersion = PostScriptVersion.UNKNOWN; - } - } - - - /** - * Read the "OS/2" table - */ - private void readOS2() throws IOException { - // Check if font is embeddable - TTFDirTabEntry os2Entry = dirTabs.get(TTFTableName.OS2); - if (os2Entry != null) { - seekTab(fontFile, TTFTableName.OS2, 0); - int version = fontFile.readTTFUShort(); - if (log.isDebugEnabled()) { - log.debug("OS/2 table: version=" + version - + ", offset=" + os2Entry.getOffset() + ", len=" + os2Entry.getLength()); - } - fontFile.skip(2); //xAvgCharWidth - this.usWeightClass = fontFile.readTTFUShort(); - - // usWidthClass - fontFile.skip(2); - - int fsType = fontFile.readTTFUShort(); - if (fsType == 2) { - isEmbeddable = false; - } else { - isEmbeddable = true; - } - fontFile.skip(11 * 2); - fontFile.skip(10); //panose array - fontFile.skip(4 * 4); //unicode ranges - fontFile.skip(4); - fontFile.skip(3 * 2); - int v; - os2Ascender = fontFile.readTTFShort(); //sTypoAscender - os2Descender = fontFile.readTTFShort(); //sTypoDescender - if (log.isDebugEnabled()) { - log.debug("sTypoAscender: " + os2Ascender - + " -> internal " + convertTTFUnit2PDFUnit(os2Ascender)); - log.debug("sTypoDescender: " + os2Descender - + " -> internal " + convertTTFUnit2PDFUnit(os2Descender)); - } - v = fontFile.readTTFShort(); //sTypoLineGap - if (log.isDebugEnabled()) { - log.debug("sTypoLineGap: " + v); - } - v = fontFile.readTTFUShort(); //usWinAscent - if (log.isDebugEnabled()) { - log.debug("usWinAscent: " + formatUnitsForDebug(v)); - } - v = fontFile.readTTFUShort(); //usWinDescent - if (log.isDebugEnabled()) { - log.debug("usWinDescent: " + formatUnitsForDebug(v)); - } - - //version 1 OS/2 table might end here - if (os2Entry.getLength() >= 78 + (2 * 4) + (2 * 2)) { - fontFile.skip(2 * 4); - this.os2xHeight = fontFile.readTTFShort(); //sxHeight - this.os2CapHeight = fontFile.readTTFShort(); //sCapHeight - if (log.isDebugEnabled()) { - log.debug("sxHeight: " + this.os2xHeight); - log.debug("sCapHeight: " + this.os2CapHeight); - } - } - - } else { - isEmbeddable = true; - } - } - - /** - * Read the "loca" table. - * @throws IOException In case of a I/O problem - */ - protected final void readIndexToLocation() - throws IOException { - if (!seekTab(fontFile, TTFTableName.LOCA, 0)) { - throw new IOException("'loca' table not found, happens when the font file doesn't" - + " contain TrueType outlines (trying to read an OpenType CFF font maybe?)"); - } - for (int i = 0; i < numberOfGlyphs; i++) { - mtxTab[i].setOffset(locaFormat == 1 ? fontFile.readTTFULong() - : (fontFile.readTTFUShort() << 1)); - } - lastLoca = (locaFormat == 1 ? fontFile.readTTFULong() - : (fontFile.readTTFUShort() << 1)); - } - - /** - * Read the "glyf" table to find the bounding boxes. - * @throws IOException In case of a I/O problem - */ - private void readGlyf() throws IOException { - TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.GLYF); - if (dirTab == null) { - throw new IOException("glyf table not found, cannot continue"); - } - for (int i = 0; i < (numberOfGlyphs - 1); i++) { - if (mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { - fontFile.seekSet(dirTab.getOffset() + mtxTab[i].getOffset()); - fontFile.skip(2); - final int[] bbox = { - fontFile.readTTFShort(), - fontFile.readTTFShort(), - fontFile.readTTFShort(), - fontFile.readTTFShort()}; - mtxTab[i].setBoundingBox(bbox); - } else { - mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox()); - } - } - - - long n = (dirTabs.get(TTFTableName.GLYF)).getOffset(); - for (int i = 0; i < numberOfGlyphs; i++) { - if ((i + 1) >= mtxTab.length - || mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { - fontFile.seekSet(n + mtxTab[i].getOffset()); - fontFile.skip(2); - final int[] bbox = { - fontFile.readTTFShort(), - fontFile.readTTFShort(), - fontFile.readTTFShort(), - fontFile.readTTFShort()}; - mtxTab[i].setBoundingBox(bbox); - } else { - /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/ - final int bbox0 = mtxTab[0].getBoundingBox()[0]; - final int[] bbox = {bbox0, bbox0, bbox0, bbox0}; - mtxTab[i].setBoundingBox(bbox); - /* Original code - mtxTab[i].bbox[0] = mtxTab[0].bbox[0]; - mtxTab[i].bbox[1] = mtxTab[0].bbox[0]; - mtxTab[i].bbox[2] = mtxTab[0].bbox[0]; - mtxTab[i].bbox[3] = mtxTab[0].bbox[0]; */ - } - if (log.isTraceEnabled()) { - log.trace(mtxTab[i].toString(this)); - } - } + super(useKerning, useAdvanced); } /** * Read the "name" table. * @throws IOException In case of a I/O problem */ - private void readName() throws IOException { - seekTab(fontFile, TTFTableName.NAME, 2); + protected void readName() throws IOException { + seekTab(fontFile, OFTableName.NAME, 2); int i = fontFile.getCurrentPos(); int n = fontFile.readTTFUShort(); int j = fontFile.readTTFUShort() + i - 2; @@ -1487,572 +112,84 @@ public class TTFFile { } /** - * Read the "PCLT" table to find xHeight and capHeight. + * Read the "glyf" table to find the bounding boxes. * @throws IOException In case of a I/O problem */ - private boolean readPCLT() throws IOException { - TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.PCLT); - if (dirTab != null) { - fontFile.seekSet(dirTab.getOffset() + 4 + 4 + 2); - xHeight = fontFile.readTTFUShort(); - log.debug("xHeight from PCLT: " + formatUnitsForDebug(xHeight)); - fontFile.skip(2 * 2); - capHeight = fontFile.readTTFUShort(); - log.debug("capHeight from PCLT: " + formatUnitsForDebug(capHeight)); - fontFile.skip(2 + 16 + 8 + 6 + 1 + 1); - - int serifStyle = fontFile.readTTFUByte(); - serifStyle = serifStyle >> 6; - serifStyle = serifStyle & 3; - if (serifStyle == 1) { - hasSerifs = false; - } else { - hasSerifs = true; - } - return true; - } else { - return false; - } - } - - /** - * Determines the right source for the ascender and descender values. The problem here is - * that the interpretation of these values is not the same for every font. There doesn't seem - * to be a uniform definition of an ascender and a descender. In some fonts - * the hhea values are defined after the Apple interpretation, but not in every font. The - * same problem is in the OS/2 table. FOP needs the ascender and descender to determine the - * baseline so we need values which add up more or less to the "em box". However, due to - * accent modifiers a character can grow beyond the em box. - */ - private void determineAscDesc() { - int hheaBoxHeight = hheaAscender - hheaDescender; - int os2BoxHeight = os2Ascender - os2Descender; - if (os2Ascender > 0 && os2BoxHeight <= upem) { - ascender = os2Ascender; - descender = os2Descender; - } else if (hheaAscender > 0 && hheaBoxHeight <= upem) { - ascender = hheaAscender; - descender = hheaDescender; - } else { - if (os2Ascender > 0) { - //Fall back to info from OS/2 if possible - ascender = os2Ascender; - descender = os2Descender; - } else { - ascender = hheaAscender; - descender = hheaDescender; - } - } - - if (log.isDebugEnabled()) { - log.debug("Font box height: " + (ascender - descender)); - if (ascender - descender > upem) { - log.debug("Ascender and descender together are larger than the em box."); - } + private void readGlyf() throws IOException { + OFDirTabEntry dirTab = dirTabs.get(OFTableName.GLYF); + if (dirTab == null) { + throw new IOException("glyf table not found, cannot continue"); } - } - - private void guessVerticalMetricsFromGlyphBBox() { - // Approximate capHeight from height of "H" - // It's most unlikely that a font misses the PCLT table - // This also assumes that postscriptnames exists ("H") - // Should look it up in the cmap (that wouldn't help - // for charsets without H anyway...) - // Same for xHeight with the letter "x" - int localCapHeight = 0; - int localXHeight = 0; - int localAscender = 0; - int localDescender = 0; - for (int i = 0; i < mtxTab.length; i++) { - if ("H".equals(mtxTab[i].getName())) { - localCapHeight = mtxTab[i].getBoundingBox()[3]; - } else if ("x".equals(mtxTab[i].getName())) { - localXHeight = mtxTab[i].getBoundingBox()[3]; - } else if ("d".equals(mtxTab[i].getName())) { - localAscender = mtxTab[i].getBoundingBox()[3]; - } else if ("p".equals(mtxTab[i].getName())) { - localDescender = mtxTab[i].getBoundingBox()[1]; + for (int i = 0; i < (numberOfGlyphs - 1); i++) { + if (mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { + fontFile.seekSet(dirTab.getOffset() + mtxTab[i].getOffset()); + fontFile.skip(2); + final int[] bbox = { + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort()}; + mtxTab[i].setBoundingBox(bbox); } else { - // OpenType Fonts with a version 3.0 "post" table don't have glyph names. - // Use Unicode indices instead. - List unicodeIndex = mtxTab[i].getUnicodeIndex(); - if (unicodeIndex.size() > 0) { - //Only the first index is used - char ch = (char)((Integer)unicodeIndex.get(0)).intValue(); - if (ch == 'H') { - localCapHeight = mtxTab[i].getBoundingBox()[3]; - } else if (ch == 'x') { - localXHeight = mtxTab[i].getBoundingBox()[3]; - } else if (ch == 'd') { - localAscender = mtxTab[i].getBoundingBox()[3]; - } else if (ch == 'p') { - localDescender = mtxTab[i].getBoundingBox()[1]; - } - } - } - } - if (log.isDebugEnabled()) { - log.debug("Ascender from glyph 'd': " + formatUnitsForDebug(localAscender)); - log.debug("Descender from glyph 'p': " + formatUnitsForDebug(localDescender)); - } - if (ascender - descender > upem) { - log.debug("Replacing specified ascender/descender with derived values to get values" - + " which fit in the em box."); - ascender = localAscender; - descender = localDescender; - } - - if (log.isDebugEnabled()) { - log.debug("xHeight from glyph 'x': " + formatUnitsForDebug(localXHeight)); - log.debug("CapHeight from glyph 'H': " + formatUnitsForDebug(localCapHeight)); - } - if (capHeight == 0) { - capHeight = localCapHeight; - if (capHeight == 0) { - capHeight = os2CapHeight; - } - if (capHeight == 0) { - log.debug("capHeight value could not be determined." - + " The font may not work as expected."); - } - } - if (xHeight == 0) { - xHeight = localXHeight; - if (xHeight == 0) { - xHeight = os2xHeight; - } - if (xHeight == 0) { - log.debug("xHeight value could not be determined." - + " The font may not work as expected."); - } - } - } - - /** - * Read the kerning table, create a table for both CIDs and - * winAnsiEncoding. - * @throws IOException In case of a I/O problem - */ - private void readKerning() throws IOException { - // Read kerning - kerningTab = new HashMap<Integer, Map<Integer, Integer>>(); - ansiKerningTab = new HashMap<Integer, Map<Integer, Integer>>(); - TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.KERN); - if (dirTab != null) { - seekTab(fontFile, TTFTableName.KERN, 2); - for (int n = fontFile.readTTFUShort(); n > 0; n--) { - fontFile.skip(2 * 2); - int k = fontFile.readTTFUShort(); - if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) { - return; - } - if ((k >> 8) != 0) { - continue; - } - - k = fontFile.readTTFUShort(); - fontFile.skip(3 * 2); - while (k-- > 0) { - int i = fontFile.readTTFUShort(); - int j = fontFile.readTTFUShort(); - int kpx = fontFile.readTTFShort(); - if (kpx != 0) { - // CID kerning table entry, using unicode indexes - final Integer iObj = glyphToUnicode(i); - final Integer u2 = glyphToUnicode(j); - if (iObj == null) { - // happens for many fonts (Ubuntu font set), - // stray entries in the kerning table?? - log.debug("Ignoring kerning pair because no Unicode index was" - + " found for the first glyph " + i); - } else if (u2 == null) { - log.debug("Ignoring kerning pair because Unicode index was" - + " found for the second glyph " + i); - } else { - Map<Integer, Integer> adjTab = kerningTab.get(iObj); - if (adjTab == null) { - adjTab = new HashMap<Integer, Integer>(); - } - adjTab.put(u2, new Integer(convertTTFUnit2PDFUnit(kpx))); - kerningTab.put(iObj, adjTab); - } - } - } - } - - // Create winAnsiEncoded kerning table from kerningTab - // (could probably be simplified, for now we remap back to CID indexes and - // then to winAnsi) - for (Integer unicodeKey1 : kerningTab.keySet()) { - Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue()); - Map<Integer, Integer> akpx = new HashMap<Integer, Integer>(); - Map<Integer, Integer> ckpx = kerningTab.get(unicodeKey1); - - for (Integer unicodeKey2 : ckpx.keySet()) { - Integer cidKey2 = unicodeToGlyph(unicodeKey2.intValue()); - Integer kern = ckpx.get(unicodeKey2); - - Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator(); - while (uniMap.hasNext()) { - Integer unicodeKey = (Integer)uniMap.next(); - Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); - for (int u = 0; u < ansiKeys.length; u++) { - akpx.put(ansiKeys[u], kern); - } - } - } - - if (akpx.size() > 0) { - Iterator uniMap = mtxTab[cidKey1.intValue()].getUnicodeIndex().listIterator(); - while (uniMap.hasNext()) { - Integer unicodeKey = (Integer)uniMap.next(); - Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); - for (int u = 0; u < ansiKeys.length; u++) { - ansiKerningTab.put(ansiKeys[u], akpx); - } - } - } + mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox()); } } - } - /** - * Streams a font. - * @param ttfOut The interface for streaming TrueType tables. - * @exception IOException file write error - */ - public void stream(TTFOutputStream ttfOut) throws IOException { - SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> sortedDirTabs = sortDirTabMap(dirTabs); - byte[] file = fontFile.getAllBytes(); - TTFTableOutputStream tableOut = ttfOut.getTableOutputStream(); - TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream(); - ttfOut.startFontStream(); - for (Map.Entry<TTFTableName, TTFDirTabEntry> entry : sortedDirTabs) { - int offset = (int) entry.getValue().getOffset(); - int paddedLength = (int) entry.getValue().getLength(); - paddedLength += getPadSize(offset + paddedLength); - if (entry.getKey().equals(TTFTableName.GLYF)) { - streamGlyf(glyphOut, file, offset, paddedLength); + long n = (dirTabs.get(OFTableName.GLYF)).getOffset(); + for (int i = 0; i < numberOfGlyphs; i++) { + if ((i + 1) >= mtxTab.length + || mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { + fontFile.seekSet(n + mtxTab[i].getOffset()); + fontFile.skip(2); + final int[] bbox = { + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort()}; + mtxTab[i].setBoundingBox(bbox); } else { - tableOut.streamTable(file, offset, paddedLength); - } - } - ttfOut.endFontStream(); - } - - private void streamGlyf(TTFGlyphOutputStream glyphOut, byte[] fontFile, int tableOffset, - int tableLength) throws IOException { - //Stream all but the last glyph - int glyphStart = 0; - int glyphEnd = 0; - glyphOut.startGlyphStream(); - for (int i = 0; i < mtxTab.length - 1; i++) { - glyphStart = (int) mtxTab[i].getOffset() + tableOffset; - glyphEnd = (int) mtxTab[i + 1].getOffset() + tableOffset; - glyphOut.streamGlyph(fontFile, glyphStart, glyphEnd - glyphStart); - } - glyphOut.streamGlyph(fontFile, glyphEnd, (tableOffset + tableLength) - glyphEnd); - glyphOut.endGlyphStream(); - } - - /** - * Returns the order in which the tables in a TrueType font should be written to file. - * @param directoryTabs the map that is to be sorted. - * @return TTFTablesNames[] an array of table names sorted in the order they should appear in - * the TTF file. - */ - SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> - sortDirTabMap(Map<TTFTableName, TTFDirTabEntry> directoryTabs) { - SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> sortedSet - = new TreeSet<Map.Entry<TTFTableName, TTFDirTabEntry>>( - new Comparator<Map.Entry<TTFTableName, TTFDirTabEntry>>() { - - public int compare(Entry<TTFTableName, TTFDirTabEntry> o1, - Entry<TTFTableName, TTFDirTabEntry> o2) { - return (int) (o1.getValue().getOffset() - o2.getValue().getOffset()); - } - }); - sortedSet.addAll(directoryTabs.entrySet()); - return sortedSet; - } - - /** - * Returns this font's character to glyph mapping. - * - * @return the font's cmap - */ - public List<CMapSegment> getCMaps() { - return cmaps; - } - - /** - * Check if this is a TrueType collection and that the given - * name exists in the collection. - * If it does, set offset in fontfile to the beginning of - * the Table Directory for that font. - * @param name The name to check - * @return True if not collection or font name present, false otherwise - * @throws IOException In case of an I/O problem - */ - protected final boolean checkTTC(String name) throws IOException { - String tag = fontFile.readTTFString(4); - - if ("ttcf".equals(tag)) { - // This is a TrueType Collection - fontFile.skip(4); - - // Read directory offsets - int numDirectories = (int)fontFile.readTTFULong(); - // int numDirectories=in.readTTFUShort(); - long[] dirOffsets = new long[numDirectories]; - for (int i = 0; i < numDirectories; i++) { - dirOffsets[i] = fontFile.readTTFULong(); - } - - log.info("This is a TrueType collection file with " - + numDirectories + " fonts"); - log.info("Containing the following fonts: "); - // Read all the directories and name tables to check - // If the font exists - this is a bit ugly, but... - boolean found = false; - - // Iterate through all name tables even if font - // Is found, just to show all the names - long dirTabOffset = 0; - for (int i = 0; (i < numDirectories); i++) { - fontFile.seekSet(dirOffsets[i]); - readDirTabs(); - - readName(); - - if (fullName.equals(name)) { - found = true; - dirTabOffset = dirOffsets[i]; - log.info(fullName + " <-- selected"); - } else { - log.info(fullName); - } - - // Reset names - notice = ""; - fullName = ""; - familyNames.clear(); - postScriptName = ""; - subFamilyName = ""; - } - - fontFile.seekSet(dirTabOffset); - return found; - } else { - fontFile.seekSet(0); - return true; - } - } - - /** - * Return TTC font names - * @param in FontFileReader to read from - * @return True if not collection or font name present, false otherwise - * @throws IOException In case of an I/O problem - */ - public final List<String> getTTCnames(FontFileReader in) throws IOException { - this.fontFile = in; - - List<String> fontNames = new ArrayList<String>(); - String tag = in.readTTFString(4); - - if ("ttcf".equals(tag)) { - // This is a TrueType Collection - in.skip(4); - - // Read directory offsets - int numDirectories = (int)in.readTTFULong(); - long[] dirOffsets = new long[numDirectories]; - for (int i = 0; i < numDirectories; i++) { - dirOffsets[i] = in.readTTFULong(); - } - - log.info("This is a TrueType collection file with " - + numDirectories + " fonts"); - log.info("Containing the following fonts: "); - - for (int i = 0; (i < numDirectories); i++) { - in.seekSet(dirOffsets[i]); - readDirTabs(); - - readName(); - - log.info(fullName); - fontNames.add(fullName); - - // Reset names - notice = ""; - fullName = ""; - familyNames.clear(); - postScriptName = ""; - subFamilyName = ""; + /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/ + final int bbox0 = mtxTab[0].getBoundingBox()[0]; + final int[] bbox = {bbox0, bbox0, bbox0, bbox0}; + mtxTab[i].setBoundingBox(bbox); + /* Original code + mtxTab[i].bbox[0] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[1] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[2] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[3] = mtxTab[0].bbox[0]; */ } - - in.seekSet(0); - return fontNames; - } else { - log.error("Not a TTC!"); - return null; - } - } - - /* - * Helper classes, they are not very efficient, but that really - * doesn't matter... - */ - private Integer[] unicodeToWinAnsi(int unicode) { - List<Integer> ret = new ArrayList<Integer>(); - for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { - if (unicode == Glyphs.WINANSI_ENCODING[i]) { - ret.add(new Integer(i)); + if (log.isTraceEnabled()) { + log.trace(mtxTab[i].toString(this)); } } - return ret.toArray(new Integer[0]); - } - - /** - * Dumps a few informational values to System.out. - */ - public void printStuff() { - System.out.println("Font name: " + postScriptName); - System.out.println("Full name: " + fullName); - System.out.println("Family name: " + familyNames); - System.out.println("Subfamily name: " + subFamilyName); - System.out.println("Notice: " + notice); - System.out.println("xHeight: " + convertTTFUnit2PDFUnit(xHeight)); - System.out.println("capheight: " + convertTTFUnit2PDFUnit(capHeight)); - - int italic = (int)(italicAngle >> 16); - System.out.println("Italic: " + italic); - System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000)); - if ((italicAngle % 0x10000) > 0) { - System.out.print("." - + (short)((italicAngle % 0x10000) * 1000) - / 0x10000); - } - System.out.println(); - System.out.println("Ascender: " + convertTTFUnit2PDFUnit(ascender)); - System.out.println("Descender: " + convertTTFUnit2PDFUnit(descender)); - System.out.println("FontBBox: [" + convertTTFUnit2PDFUnit(fontBBox1) - + " " + convertTTFUnit2PDFUnit(fontBBox2) + " " - + convertTTFUnit2PDFUnit(fontBBox3) + " " - + convertTTFUnit2PDFUnit(fontBBox4) + "]"); - } - - private String formatUnitsForDebug(int units) { - return units + " -> " + convertTTFUnit2PDFUnit(units) + " internal units"; - } - - /** - * Map a glyph index to the corresponding unicode code point - * - * @param glyphIndex - * @return unicode code point - */ - private Integer glyphToUnicode(int glyphIndex) { - return glyphToUnicodeMap.get(new Integer(glyphIndex)); - } - - /** - * Map a unicode code point to the corresponding glyph index - * - * @param unicodeIndex unicode code point - * @return glyph index - */ - private Integer unicodeToGlyph(int unicodeIndex) throws IOException { - final Integer result - = unicodeToGlyphMap.get(new Integer(unicodeIndex)); - if (result == null) { - throw new IOException( - "Glyph index not found for unicode value " + unicodeIndex); - } - return result; } - String getGlyphName(int glyphIndex) { - return mtxTab[glyphIndex].getName(); + @Override + protected void updateBBoxAndOffset() throws IOException { + readIndexToLocation(); + readGlyf(); } /** - * Determine if advanced (typographic) table is present. - * @return true if advanced (typographic) table is present - */ - public boolean hasAdvancedTable() { - if (advancedTableReader != null) { - return advancedTableReader.hasAdvancedTable(); - } else { - return false; - } - } - - /** - * Returns the GDEF table or null if none present. - * @return the GDEF table - */ - public GlyphDefinitionTable getGDEF() { - if (advancedTableReader != null) { - return advancedTableReader.getGDEF(); - } else { - return null; - } - } - - /** - * Returns the GSUB table or null if none present. - * @return the GSUB table + * Read the "loca" table. + * @throws IOException In case of a I/O problem */ - public GlyphSubstitutionTable getGSUB() { - if (advancedTableReader != null) { - return advancedTableReader.getGSUB(); - } else { - return null; + protected final void readIndexToLocation() + throws IOException { + if (!seekTab(fontFile, OFTableName.LOCA, 0)) { + throw new IOException("'loca' table not found, happens when the font file doesn't" + + " contain TrueType outlines (trying to read an OpenType CFF font maybe?)"); } - } - - /** - * Returns the GPOS table or null if none present. - * @return the GPOS table - */ - public GlyphPositioningTable getGPOS() { - if (advancedTableReader != null) { - return advancedTableReader.getGPOS(); - } else { - return null; + for (int i = 0; i < numberOfGlyphs; i++) { + mtxTab[i].setOffset(locaFormat == 1 ? fontFile.readTTFULong() + : (fontFile.readTTFUShort() << 1)); } + lastLoca = (locaFormat == 1 ? fontFile.readTTFULong() + : (fontFile.readTTFUShort() << 1)); } - /** - * Static main method to get info about a TrueType font. - * @param args The command line arguments - */ - public static void main(String[] args) { - InputStream stream = null; - try { - boolean useKerning = true; - boolean useAdvanced = true; - TTFFile ttfFile = new TTFFile(useKerning, useAdvanced); - - stream = new FileInputStream(args[0]); - FontFileReader reader = new FontFileReader(stream); - - String name = null; - if (args.length >= 2) { - name = args[1]; - } - - ttfFile.readFont(reader, name); - ttfFile.printStuff(); - - } catch (IOException ioe) { - System.err.println("Problem reading font: " + ioe.toString()); - ioe.printStackTrace(System.err); - } finally { - IOUtils.closeQuietly(stream); - } + @Override + protected void initializeFont(FontFileReader in) throws IOException { + fontFile = in; } } diff --git a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java index c9a0e6c8e..ff46af1c7 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java +++ b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java @@ -43,7 +43,7 @@ public class TTFSubSetFile extends TTFFile { * Offsets in name table to be filled out by table. * The offsets are to the checkSum field */ - private Map<TTFTableName, Integer> offsets = new HashMap<TTFTableName, Integer>(); + private Map<OFTableName, Integer> offsets = new HashMap<OFTableName, Integer>(); private int checkSumAdjustmentOffset = 0; private int locaOffset = 0; @@ -67,8 +67,8 @@ public class TTFSubSetFile extends TTFFile { } /** The dir tab entries in the new subset font. */ - private Map<TTFTableName, TTFDirTabEntry> newDirTabs - = new HashMap<TTFTableName, TTFDirTabEntry>(); + private Map<OFTableName, OFDirTabEntry> newDirTabs + = new HashMap<OFTableName, OFDirTabEntry>(); private int determineTableCount() { int numTables = 4; //4 req'd tables: head,hhea,hmtx,maxp @@ -117,29 +117,29 @@ public class TTFSubSetFile extends TTFFile { writeUShort((numTables * 16) - searchRange); realSize += 2; // Create space for the table entries (these must be in ASCII alphabetical order[A-Z] then[a-z]) - writeTableName(TTFTableName.OS2); + writeTableName(OFTableName.OS2); if (hasCvt()) { - writeTableName(TTFTableName.CVT); + writeTableName(OFTableName.CVT); } if (hasFpgm()) { - writeTableName(TTFTableName.FPGM); + writeTableName(OFTableName.FPGM); } - writeTableName(TTFTableName.GLYF); - writeTableName(TTFTableName.HEAD); - writeTableName(TTFTableName.HHEA); - writeTableName(TTFTableName.HMTX); - writeTableName(TTFTableName.LOCA); - writeTableName(TTFTableName.MAXP); - writeTableName(TTFTableName.NAME); - writeTableName(TTFTableName.POST); + 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(TTFTableName.PREP); + writeTableName(OFTableName.PREP); } - newDirTabs.put(TTFTableName.TABLE_DIRECTORY, new TTFDirTabEntry(0, currentPos)); + newDirTabs.put(OFTableName.TABLE_DIRECTORY, new OFDirTabEntry(0, currentPos)); } - private void writeTableName(TTFTableName tableName) { + private void writeTableName(OFTableName tableName) { writeString(tableName.getName()); offsets.put(tableName, currentPos); currentPos += 12; @@ -148,15 +148,15 @@ public class TTFSubSetFile extends TTFFile { private boolean hasCvt() { - return dirTabs.containsKey(TTFTableName.CVT); + return dirTabs.containsKey(OFTableName.CVT); } private boolean hasFpgm() { - return dirTabs.containsKey(TTFTableName.FPGM); + return dirTabs.containsKey(OFTableName.FPGM); } private boolean hasPrep() { - return dirTabs.containsKey(TTFTableName.PREP); + return dirTabs.containsKey(OFTableName.PREP); } /** @@ -165,15 +165,15 @@ public class TTFSubSetFile extends TTFFile { private void createLoca(int size) throws IOException { pad4(); locaOffset = currentPos; - int dirTableOffset = offsets.get(TTFTableName.LOCA); + 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, TTFTableName tableName) throws IOException { - TTFDirTabEntry entry = dirTabs.get(tableName); + private boolean copyTable(FontFileReader in, OFTableName tableName) throws IOException { + OFDirTabEntry entry = dirTabs.get(tableName); if (entry != null) { pad4(); seekTab(in, tableName, 0); @@ -193,28 +193,28 @@ public class TTFSubSetFile extends TTFFile { * Copy the cvt table as is from original font to subset font */ private boolean createCvt(FontFileReader in) throws IOException { - return copyTable(in, TTFTableName.CVT); + return copyTable(in, OFTableName.CVT); } /** * Copy the fpgm table as is from original font to subset font */ private boolean createFpgm(FontFileReader in) throws IOException { - return copyTable(in, TTFTableName.FPGM); + return copyTable(in, OFTableName.FPGM); } /** * Copy the name table as is from the original. */ private boolean createName(FontFileReader in) throws IOException { - return copyTable(in, TTFTableName.NAME); + return copyTable(in, OFTableName.NAME); } /** * Copy the OS/2 table as is from the original. */ private boolean createOS2(FontFileReader in) throws IOException { - return copyTable(in, TTFTableName.OS2); + return copyTable(in, OFTableName.OS2); } /** @@ -222,8 +222,8 @@ public class TTFSubSetFile extends TTFFile { * and set num glyphs to size */ private void createMaxp(FontFileReader in, int size) throws IOException { - TTFTableName maxp = TTFTableName.MAXP; - TTFDirTabEntry entry = dirTabs.get(maxp); + OFTableName maxp = OFTableName.MAXP; + OFDirTabEntry entry = dirTabs.get(maxp); if (entry != null) { pad4(); seekTab(in, maxp, 0); @@ -240,8 +240,8 @@ public class TTFSubSetFile extends TTFFile { } private void createPost(FontFileReader in) throws IOException { - TTFTableName post = TTFTableName.POST; - TTFDirTabEntry entry = dirTabs.get(post); + OFTableName post = OFTableName.POST; + OFDirTabEntry entry = dirTabs.get(post); if (entry != null) { pad4(); seekTab(in, post, 0); @@ -266,7 +266,7 @@ public class TTFSubSetFile extends TTFFile { * Copy the prep table as is from original font to subset font */ private boolean createPrep(FontFileReader in) throws IOException { - return copyTable(in, TTFTableName.PREP); + return copyTable(in, OFTableName.PREP); } @@ -275,15 +275,15 @@ public class TTFSubSetFile extends TTFFile { * and fill in size of hmtx table */ private void createHhea(FontFileReader in, int size) throws IOException { - TTFDirTabEntry entry = dirTabs.get(TTFTableName.HHEA); + OFDirTabEntry entry = dirTabs.get(OFTableName.HHEA); if (entry != null) { pad4(); - seekTab(in, TTFTableName.HHEA, 0); + seekTab(in, OFTableName.HHEA, 0); System.arraycopy(in.getBytes((int) entry.getOffset(), (int) entry.getLength()), 0, output, currentPos, (int) entry.getLength()); writeUShort((int) entry.getLength() + currentPos - 2, size); - updateCheckSum(currentPos, (int) entry.getLength(), TTFTableName.HHEA); + updateCheckSum(currentPos, (int) entry.getLength(), OFTableName.HHEA); currentPos += (int) entry.getLength(); realSize += (int) entry.getLength(); } else { @@ -299,8 +299,8 @@ public class TTFSubSetFile extends TTFFile { * in checkSumAdjustmentOffset */ private void createHead(FontFileReader in) throws IOException { - TTFTableName head = TTFTableName.HEAD; - TTFDirTabEntry entry = dirTabs.get(head); + OFTableName head = OFTableName.HEAD; + OFDirTabEntry entry = dirTabs.get(head); if (entry != null) { pad4(); seekTab(in, head, 0); @@ -329,8 +329,8 @@ public class TTFSubSetFile extends TTFFile { */ private void createGlyf(FontFileReader in, Map<Integer, Integer> glyphs) throws IOException { - TTFTableName glyf = TTFTableName.GLYF; - TTFDirTabEntry entry = dirTabs.get(glyf); + OFTableName glyf = OFTableName.GLYF; + OFDirTabEntry entry = dirTabs.get(glyf); int size = 0; int startPos = 0; int endOffset = 0; // Store this as the last loca @@ -393,10 +393,10 @@ public class TTFSubSetFile extends TTFFile { writeULong(locaOffset + glyphs.size() * 4, endOffset); int locaSize = glyphs.size() * 4 + 4; int checksum = getCheckSum(output, locaOffset, locaSize); - writeULong(offsets.get(TTFTableName.LOCA), checksum); + writeULong(offsets.get(OFTableName.LOCA), checksum); int padSize = (locaOffset + locaSize) % 4; - newDirTabs.put(TTFTableName.LOCA, - new TTFDirTabEntry(locaOffset, locaSize + padSize)); + newDirTabs.put(OFTableName.LOCA, + new OFDirTabEntry(locaOffset, locaSize + padSize)); } else { throw new IOException("Can't find glyf table"); } @@ -420,8 +420,8 @@ public class TTFSubSetFile extends TTFFile { */ private void createHmtx(FontFileReader in, Map<Integer, Integer> glyphs) throws IOException { - TTFTableName hmtx = TTFTableName.HMTX; - TTFDirTabEntry entry = dirTabs.get(hmtx); + OFTableName hmtx = OFTableName.HMTX; + OFDirTabEntry entry = dirTabs.get(hmtx); int longHorMetricSize = glyphs.size() * 2; int leftSideBearingSize = glyphs.size() * 2; @@ -457,11 +457,11 @@ public class TTFSubSetFile extends TTFFile { * new index as (Integer) value) * @throws IOException in case of an I/O problem */ - public void readFont(FontFileReader in, String name, + public void readFont(FontFileReader in, String name, String header, Map<Integer, Integer> glyphs) throws IOException { fontFile = in; //Check if TrueType collection, and that the name exists in the collection - if (!checkTTC(name)) { + if (!checkTTC(header, name)) { throw new IOException("Failed to read font"); } @@ -533,7 +533,7 @@ public class TTFSubSetFile extends TTFFile { glyphOffsets[i + 1] - glyphOffsets[i]); } // Stream the last glyph - TTFDirTabEntry glyf = newDirTabs.get(TTFTableName.GLYF); + OFDirTabEntry glyf = newDirTabs.get(OFTableName.GLYF); long lastGlyphLength = glyf.getLength() - (glyphOffsets[glyphOffsets.length - 1] - glyf.getOffset()); glyphOut.streamGlyph(output, glyphOffsets[glyphOffsets.length - 1], @@ -543,14 +543,14 @@ public class TTFSubSetFile extends TTFFile { @Override public void stream(TTFOutputStream ttfOut) throws IOException { - SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> sortedDirTabs + SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortedDirTabs = sortDirTabMap(newDirTabs); TTFTableOutputStream tableOut = ttfOut.getTableOutputStream(); TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream(); ttfOut.startFontStream(); - for (Map.Entry<TTFTableName, TTFDirTabEntry> entry : sortedDirTabs) { - if (entry.getKey().equals(TTFTableName.GLYF)) { + for (Map.Entry<OFTableName, OFDirTabEntry> entry : sortedDirTabs) { + if (entry.getKey().equals(OFTableName.GLYF)) { handleGlyphSubset(glyphOut); } else { tableOut.streamTable(output, (int) entry.getValue().getOffset(), @@ -562,7 +562,7 @@ public class TTFSubSetFile extends TTFFile { private void scanGlyphs(FontFileReader in, Map<Integer, Integer> subsetGlyphs) throws IOException { - TTFDirTabEntry glyfTableInfo = dirTabs.get(TTFTableName.GLYF); + OFDirTabEntry glyfTableInfo = dirTabs.get(OFTableName.GLYF); if (glyfTableInfo == null) { throw new IOException("Glyf table could not be found"); } @@ -663,11 +663,11 @@ public class TTFSubSetFile extends TTFFile { } - private void updateCheckSum(int tableStart, int tableSize, TTFTableName tableName) { + private 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 TTFDirTabEntry(tableStart, tableSize + padSize)); + newDirTabs.put(tableName, new OFDirTabEntry(tableStart, tableSize + padSize)); writeULong(offset, checksum); writeULong(offset + 4, tableStart); writeULong(offset + 8, tableSize); diff --git a/src/java/org/apache/fop/pdf/PDFCFFStreamType0C.java b/src/java/org/apache/fop/pdf/PDFCFFStreamType0C.java new file mode 100644 index 000000000..53f0b36b4 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFCFFStreamType0C.java @@ -0,0 +1,74 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * PDFStream for embeddable OpenType CFF fonts. + */ +public class PDFCFFStreamType0C extends AbstractPDFFontStream { + + private byte[] cffData; + private boolean fullEmbed; + + /** + * Main constructor + * @param fullEmbed Determines whether the font is fully embedded + */ + public PDFCFFStreamType0C(boolean fullEmbed) { + super(); + this.fullEmbed = fullEmbed; + } + + protected int getSizeHint() throws IOException { + if (this.cffData != null) { + return cffData.length; + } else { + return 0; //no hint available + } + } + + /** {@inheritDoc} */ + protected void outputRawStreamData(OutputStream out) throws IOException { + out.write(this.cffData); + } + + /** {@inheritDoc} */ + protected void populateStreamDict(Object lengthEntry) { + String type = (fullEmbed) ? "OpenType" : "CIDFontType0C"; + put("Subtype", new PDFName(type)); + super.populateStreamDict(lengthEntry); + } + + /** + * Sets the CFF font data. + * @param data the font payload + * @param size size of the payload + * @throws IOException in case of an I/O problem + */ + public void setData(byte[] data, int size) throws IOException { + this.cffData = new byte[size]; + System.arraycopy(data, 0, this.cffData, 0, size); + } + +} + diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java index 2fe9f29e0..8fd8d3444 100644 --- a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java +++ b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java @@ -449,14 +449,13 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { private class Rev5Engine extends InitializationEngine { - // private SecureRandom random = new SecureRandom(); - private byte[] userValidationSalt = new byte[8]; - private byte[] userKeySalt = new byte[8]; - private byte[] ownerValidationSalt = new byte[8]; - private byte[] ownerKeySalt = new byte[8]; - private byte[] ueValue; - private byte[] oeValue; - private final boolean encryptMetadata; + protected byte[] userValidationSalt = new byte[8]; + protected byte[] userKeySalt = new byte[8]; + protected byte[] ownerValidationSalt = new byte[8]; + protected byte[] ownerKeySalt = new byte[8]; + protected byte[] ueValue; + protected byte[] oeValue; + protected final boolean encryptMetadata; Rev5Engine(EncryptionSettings encryptionSettings) { super(encryptionSettings); @@ -563,7 +562,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { /** * Algorithm 3.8-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3) */ - private void computeUEValue() { + protected void computeUEValue() { digest.reset(); byte[] prepared = preparedUserPassword; byte[] concatenated = new byte[prepared.length + 8]; @@ -577,7 +576,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { /** * Algorithm 3.9-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3) */ - private void computeOEValue() { + protected void computeOEValue() { digest.reset(); byte[] prepared = preparedOwnerPassword; byte[] concatenated = new byte[prepared.length + 56]; @@ -615,6 +614,86 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { } } + private class Rev6Engine extends Rev5Engine { + + private MessageDigest digest384; + private MessageDigest digest512; + + Rev6Engine(EncryptionSettings encryptionSettings) { + super(encryptionSettings); + try { + digest384 = MessageDigest.getInstance("SHA-384"); + digest512 = MessageDigest.getInstance("SHA-512"); + } catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException(e.getMessage()); + } + } + + @Override + protected void computeUValue() { + byte[] userBytes = new byte[16]; + random.nextBytes(userBytes); + System.arraycopy(userBytes, 0, userValidationSalt, 0, 8); + System.arraycopy(userBytes, 8, userKeySalt, 0, 8); + digest.reset(); + byte[] prepared = preparedUserPassword; + byte[] concatenated = new byte[prepared.length + 8]; + System.arraycopy(prepared, 0, concatenated, 0, prepared.length); + System.arraycopy(userValidationSalt, 0, concatenated, prepared.length, 8); + digest.update(concatenated); + byte[] block = digest.digest(); + int blockSize = 32; + byte[] key = new byte[16]; + byte[] iv = new byte[16]; + int length = prepared.length + blockSize; + byte[] data = new byte[length * 64]; + for (int i = 0; i < 64 || i < data[length * 64 - 1] + 32; i++) { + System.arraycopy(block, 0, key, 0, 16); + System.arraycopy(block, 16, iv, 0, 16); + for (int j = 0; j < 64; j++) { + System.arraycopy(prepared, 0, data, j * length, prepared.length); + System.arraycopy(block, 0, data, j * length + prepared.length, blockSize); + } + try { + final Cipher cipher = PDFEncryptionJCE.initCipher(key, false, iv); + data = cipher.doFinal(data); + } catch (IllegalBlockSizeException e) { + throw new IllegalStateException(e.getMessage()); + } catch (BadPaddingException e) { + throw new IllegalStateException(e.getMessage()); + } + int sum = 0; + for (int k = 0; k < 16; k++) { + sum += data[k]; + } + blockSize = 32 + (sum % 3) * 16; + switch (blockSize) { + case 32: + digest.reset(); + digest.update(data); + block = digest.digest(); + break; + case 48: + digest384.reset(); + digest384.update(data); + block = digest384.digest(); + break; + case 64: + digest512.reset(); + digest512.update(data); + block = digest512.digest(); + break; + default: + // not possible + break; + } + length = prepared.length + blockSize; + data = new byte[length * 64]; + } + } + + } + private class EncryptionFilter extends PDFFilter { private int streamNumber; diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index 1756f1d56..631499af1 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -56,6 +56,8 @@ import org.apache.fop.fonts.SingleByteEncoding; import org.apache.fop.fonts.SingleByteFont; import org.apache.fop.fonts.Typeface; import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OFFontLoader; +import org.apache.fop.fonts.truetype.OTFSubSetFile; import org.apache.fop.fonts.truetype.TTFSubSetFile; import org.apache.fop.fonts.type1.PFBData; import org.apache.fop.fonts.type1.PFBParser; @@ -1387,15 +1389,15 @@ public class PDFFactory { int firstChar = singleByteFont.getFirstChar(); int lastChar = singleByteFont.getLastChar(); nonBase14.setWidthMetrics(firstChar, - lastChar, - new PDFArray(null, metrics.getWidths())); + lastChar, + new PDFArray(null, metrics.getWidths())); //Handle encoding SingleByteEncoding mapping = singleByteFont.getEncoding(); if (singleByteFont.isSymbolicFont()) { //no encoding, use the font's encoding if (forceToUnicode) { - generateToUnicodeCmap(nonBase14, mapping); + generateToUnicodeCmap(nonBase14, mapping); } } else if (PDFEncoding.isPredefinedEncoding(mapping.getName())) { font.setEncoding(mapping.getName()); @@ -1403,7 +1405,7 @@ public class PDFFactory { //believed. } else { Object pdfEncoding = createPDFEncoding(mapping, - singleByteFont.getFontName()); + singleByteFont.getFontName()); if (pdfEncoding instanceof PDFEncoding) { font.setEncoding((PDFEncoding)pdfEncoding); } else { @@ -1518,7 +1520,8 @@ public class PDFFactory { // Check if the font is embeddable if (desc.isEmbeddable()) { - AbstractPDFStream stream = makeFontFile(desc); + AbstractPDFStream stream = makeFontFile(desc, fontPrefix); + if (stream != null) { descriptor.setFontFile(desc.getFontType(), stream); getDocument().registerObject(stream); @@ -1564,7 +1567,7 @@ public class PDFFactory { * @param desc FontDescriptor of the font. * @return PDFStream The embedded font file */ - public AbstractPDFStream makeFontFile(FontDescriptor desc) { + public AbstractPDFStream makeFontFile(FontDescriptor desc, String fontPrefix) { if (desc.getFontType() == FontType.OTHER) { throw new IllegalArgumentException("Trying to embed unsupported font type: " + desc.getFontType()); @@ -1578,20 +1581,24 @@ public class PDFFactory { if (in == null) { return null; } else { - AbstractPDFStream embeddedFont; + AbstractPDFStream embeddedFont = null; if (desc.getFontType() == FontType.TYPE0) { MultiByteFont mbfont = (MultiByteFont) font; FontFileReader reader = new FontFileReader(in); byte[] fontBytes; + String header = OFFontLoader.readHeader(reader); + boolean isCFF = mbfont.isOTFFile(); if (font.getEmbeddingMode() == EmbeddingMode.FULL) { fontBytes = reader.getAllBytes(); + if (isCFF) { + //Ensure version 1.6 for full OTF CFF embedding + document.setPDFVersion(Version.V1_6); + } } else { - TTFSubSetFile ttfFile = new TTFSubSetFile(); - ttfFile.readFont(reader, mbfont.getTTCName(), mbfont.getUsedGlyphs()); - fontBytes = ttfFile.getFontSubset(); + fontBytes = getFontSubsetBytes(reader, mbfont, header, fontPrefix, desc, + isCFF); } - embeddedFont = new PDFTTFStream(fontBytes.length); - ((PDFTTFStream) embeddedFont).setData(fontBytes, fontBytes.length); + embeddedFont = getFontStream(font, fontBytes, isCFF); } else if (desc.getFontType() == FontType.TYPE1) { PFBParser parser = new PFBParser(); PFBData pfb = parser.parsePFB(in); @@ -1621,6 +1628,32 @@ public class PDFFactory { } } + private byte[] getFontSubsetBytes(FontFileReader reader, MultiByteFont mbfont, String header, + String fontPrefix, FontDescriptor desc, boolean isCFF) throws IOException { + if (isCFF) { + OTFSubSetFile otfFile = new OTFSubSetFile(); + otfFile.readFont(reader, fontPrefix + desc.getEmbedFontName(), header, mbfont); + return otfFile.getFontSubset(); + } else { + TTFSubSetFile otfFile = new TTFSubSetFile(); + otfFile.readFont(reader, mbfont.getTTCName(), header, mbfont.getUsedGlyphs()); + return otfFile.getFontSubset(); + } + } + + private AbstractPDFStream getFontStream(CustomFont font, byte[] fontBytes, boolean isCFF) + throws IOException { + AbstractPDFStream embeddedFont; + if (isCFF) { + embeddedFont = new PDFCFFStreamType0C(font.getEmbeddingMode() == EmbeddingMode.FULL); + ((PDFCFFStreamType0C) embeddedFont).setData(fontBytes, fontBytes.length); + } else { + embeddedFont = new PDFTTFStream(fontBytes.length); + ((PDFTTFStream) embeddedFont).setData(fontBytes, fontBytes.length); + } + return embeddedFont; + } + private CustomFont getCustomFont(FontDescriptor desc) { Typeface tempFont; if (desc instanceof LazyFont) { diff --git a/src/java/org/apache/fop/pdf/PDFFontDescriptor.java b/src/java/org/apache/fop/pdf/PDFFontDescriptor.java index ec4e99101..73dbebc3f 100644 --- a/src/java/org/apache/fop/pdf/PDFFontDescriptor.java +++ b/src/java/org/apache/fop/pdf/PDFFontDescriptor.java @@ -102,6 +102,8 @@ public class PDFFontDescriptor extends PDFDictionary { public void setFontFile(FontType subtype, AbstractPDFStream fontfile) { if (subtype == FontType.TYPE1) { put("FontFile", fontfile); + } else if (fontfile instanceof PDFCFFStreamType0C) { + put("FontFile3", fontfile); } else { put("FontFile2", fontfile); } diff --git a/src/java/org/apache/fop/render/ps/PSFontUtils.java b/src/java/org/apache/fop/render/ps/PSFontUtils.java index e57567b88..c22a3ba28 100644 --- a/src/java/org/apache/fop/render/ps/PSFontUtils.java +++ b/src/java/org/apache/fop/render/ps/PSFontUtils.java @@ -29,6 +29,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fontbox.cff.CFFStandardString; import org.apache.xmlgraphics.fonts.Glyphs; import org.apache.xmlgraphics.ps.DSCConstants; @@ -50,9 +51,14 @@ import org.apache.fop.fonts.MultiByteFont; import org.apache.fop.fonts.SingleByteEncoding; import org.apache.fop.fonts.SingleByteFont; import org.apache.fop.fonts.Typeface; +import org.apache.fop.fonts.cff.CFFDataReader; +import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry; import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OFFontLoader; +import org.apache.fop.fonts.truetype.OTFFile; +import org.apache.fop.fonts.truetype.OTFSubSetFile; +import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion; import org.apache.fop.fonts.truetype.TTFFile; -import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion; import org.apache.fop.fonts.truetype.TTFOutputStream; import org.apache.fop.fonts.truetype.TTFSubSetFile; import org.apache.fop.render.ps.fonts.PSTTFOutputStream; @@ -221,6 +227,7 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { private static PSFontResource embedFont(PSGenerator gen, Typeface tf, PSResource fontRes, PSEventProducer eventProducer) throws IOException { + boolean embeddedFont = false; FontType fontType = tf.getFontType(); PSFontResource fontResource = null; if (!(fontType == FontType.TYPE1 || fontType == FontType.TRUETYPE @@ -232,52 +239,63 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { CustomFont cf = (CustomFont)tf; if (isEmbeddable(cf)) { InputStream in = getInputStreamOnFont(gen, cf); - if (in == null) { + if (in != null) { + if (fontType == FontType.TYPE0) { + if (((MultiByteFont)tf).isOTFFile()) { + checkPostScriptLevel3(gen, eventProducer, "OpenType CFF"); + embedType2CFF(gen, (MultiByteFont) tf, in); + } else { + if (gen.embedIdentityH()) { + checkPostScriptLevel3(gen, eventProducer, "TrueType"); + /* + * First CID-keyed font to be embedded; add + * %%IncludeResource: comment for ProcSet CIDInit. + */ + gen.includeProcsetCIDInitResource(); + } + PSResource cidFontResource; + cidFontResource = embedType2CIDFont(gen, + (MultiByteFont) tf, in); + fontResource = PSFontResource.createFontResource(fontRes, + gen.getProcsetCIDInitResource(), gen.getIdentityHCMapResource(), + cidFontResource); + } + } + gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, fontRes); + if (fontType == FontType.TYPE1) { + embedType1Font(gen, in); + fontResource = PSFontResource.createFontResource(fontRes); + } else if (fontType == FontType.TRUETYPE) { + embedTrueTypeFont(gen, (SingleByteFont) tf, in); + fontResource = PSFontResource.createFontResource(fontRes); + } else { + composeType0Font(gen, (MultiByteFont) tf, in); + } + gen.writeDSCComment(DSCConstants.END_RESOURCE); + gen.getResourceTracker().registerSuppliedResource(fontRes); + embeddedFont = true; + } else { gen.commentln("%WARNING: Could not embed font: " + cf.getEmbedFontName()); log.warn("Font " + cf.getEmbedFontName() + " is marked as supplied in the" + " PostScript file but could not be embedded!"); - gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); - fontResource = PSFontResource.createFontResource(fontRes); - return fontResource; - } - if (fontType == FontType.TYPE0) { - if (gen.embedIdentityH()) { - checkPostScriptLevel3(gen, eventProducer); - /* - * First CID-keyed font to be embedded; add - * %%IncludeResource: comment for ProcSet CIDInit. - */ - gen.includeProcsetCIDInitResource(); - } - PSResource cidFontResource = embedType2CIDFont(gen, - (MultiByteFont) tf, in); - fontResource = PSFontResource.createFontResource(fontRes, - gen.getProcsetCIDInitResource(), gen.getIdentityHCMapResource(), - cidFontResource); } - gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, fontRes); - if (fontType == FontType.TYPE1) { - embedType1Font(gen, in); - fontResource = PSFontResource.createFontResource(fontRes); - } else if (fontType == FontType.TRUETYPE) { - embedTrueTypeFont(gen, (SingleByteFont) tf, in); - fontResource = PSFontResource.createFontResource(fontRes); - } else { - composeType0Font(gen, (MultiByteFont) tf, in); - } - gen.writeDSCComment(DSCConstants.END_RESOURCE); - gen.getResourceTracker().registerSuppliedResource(fontRes); + } + if (!embeddedFont) { + gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); + fontResource = PSFontResource.createFontResource(fontRes); + return fontResource; } return fontResource; } - private static void checkPostScriptLevel3(PSGenerator gen, PSEventProducer eventProducer) { + private static void checkPostScriptLevel3(PSGenerator gen, PSEventProducer eventProducer, + String fontType) { if (gen.getPSLevel() < 3) { if (eventProducer != null) { eventProducer.postscriptLevel3Needed(gen); } else { throw new IllegalStateException("PostScript Level 3 is" - + " required to use TrueType fonts," + + " required to use " + fontType + " fonts," + " configured level is " + gen.getPSLevel()); } @@ -415,6 +433,96 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { gen.writeln("] composefont pop"); } + private static void embedType2CFF(PSGenerator gen, + MultiByteFont font, InputStream fontStream) throws IOException { + FontFileReader reader = new FontFileReader(fontStream); + String header = OFFontLoader.readHeader(reader); + String psName; + CFFDataReader cffReader = new CFFDataReader(reader); + if (cffReader.getFDSelect() != null) { + throw new UnsupportedOperationException("CID-Keyed OTF CFF fonts are not supported" + + " for PostScript output."); + } + + byte[] bytes; + if (font.getEmbeddingMode() == EmbeddingMode.FULL) { + font.setFontName(new String(cffReader.getNameIndex().getValue(0))); + psName = font.getEmbedFontName(); + Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + int charsetOffset = topDICT.get("charset").getOperands().get(0).intValue(); + for (int gid = 0; gid < cffReader.getCharStringIndex().getNumObjects(); gid++) { + int sid = cffReader.getSIDFromGID(charsetOffset, gid); + + //Check whether the SID falls into the standard string set + if (sid < 391) { + font.mapUsedGlyphName(gid, + CFFStandardString.getName(sid)); + } else { + int index = sid - 391; + if (index < cffReader.getStringIndex().getNumObjects()) { + font.mapUsedGlyphName(gid, + new String(cffReader.getStringIndex().getValue(index))); + } else { + font.mapUsedGlyphName(gid, ".notdef"); + } + } + } + bytes = OTFFile.getCFFData(reader); + } else { + psName = font.getEmbedFontName(); + OTFSubSetFile otfFile = new OTFSubSetFile(); + otfFile.readFont(reader, psName, header, font); + bytes = otfFile.getFontSubset(); + } + + gen.writeln("%!PS-Adobe-3.0 Resource-FontSet"); + gen.writeln("%%DocumentNeedResources:ProcSet(FontSetInit)"); + gen.writeln("%%Title:(FontSet/" + psName + ")"); + gen.writeln("%%Version: 1.000"); + gen.writeln("%%EndComments"); + gen.writeln("%%IncludeResource:ProcSet(FontSetInit)"); + gen.writeln("%%BeginResource: FontSet (" + psName + ")"); + gen.writeln("/FontSetInit /ProcSet findresource begin"); + //Next line + 1 + String fontDeclaration = "/" + psName + " " + bytes.length + " StartData"; + gen.writeln("%%BeginData: " + (fontDeclaration.length() + 1 + bytes.length) + " Binary Bytes"); + gen.writeln(fontDeclaration); + gen.writeByteArr(bytes); + gen.writeln("%%EndData"); + gen.writeln("%%EndResource"); + + gen.writeln("/" + psName + ".0.enc [ "); + int lengthCount = 0; + int charCount = 1; + int encodingCount = 0; + String line = ""; + for (int gid : font.getUsedGlyphNames().keySet()) { + line += "/" + font.getUsedGlyphNames().get(gid) + " "; + lengthCount++; + charCount++; + if (lengthCount == 8) { + gen.writeln(line); + line = ""; + lengthCount = 0; + } + if (charCount > 256) { + encodingCount++; + charCount = 1; + gen.writeln(line); + line = ""; + lengthCount = 0; + gen.writeln("] def"); + gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName, + encodingCount - 1, psName, encodingCount - 1, psName)); + gen.writeln("/" + psName + "." + encodingCount + ".enc [ "); + } + } + gen.writeln(line); + gen.writeln("] def"); + gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName, encodingCount, + psName, encodingCount, psName)); + } + private static PSResource embedType2CIDFont(PSGenerator gen, MultiByteFont font, InputStream fontStream) throws IOException { assert font.getCIDType() == CIDFontType.CIDTYPE2; @@ -502,17 +610,18 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { } gen.writeln(">] def"); FontFileReader reader = new FontFileReader(fontStream); + String header = OFFontLoader.readHeader(reader); TTFFile ttfFile; if (font.getEmbeddingMode() != EmbeddingMode.FULL) { ttfFile = new TTFSubSetFile(); - ttfFile.readFont(reader, font.getTTCName(), font.getUsedGlyphs()); + //Change the TTFFile to have the abstract method for TTFSubSetFile + ((TTFSubSetFile)ttfFile).readFont(reader, font.getTTCName(), header, font.getUsedGlyphs()); } else { ttfFile = new TTFFile(); ttfFile.readFont(reader, font.getTTCName()); } - createType42DictionaryEntries(gen, font, new CMapSegment[0], ttfFile); gen.writeln("CIDFontName currentdict end /CIDFont defineresource pop"); gen.writeln("end"); @@ -670,7 +779,7 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { private static PSResource defineDerivedTrueTypeFont(PSGenerator gen, PSEventProducer eventProducer, String baseFontName, String fontName, SingleByteEncoding encoding, CMapSegment[] cmap) throws IOException { - checkPostScriptLevel3(gen, eventProducer); + checkPostScriptLevel3(gen, eventProducer, "TrueType"); PSResource res = new PSResource(PSResource.TYPE_FONT, fontName); gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, res); gen.commentln("%XGCDependencies: font " + baseFontName); diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index b0b370c79..3d1887f2d 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -40,6 +40,7 @@ import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.ps.PSGenerator; import org.apache.xmlgraphics.ps.PSResource; +import org.apache.fop.fonts.EmbeddingMode; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.LazyFont; @@ -375,7 +376,13 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { } Font font = getFontInfo().getFontInstance(triplet, sizeMillipoints); - useFont(fontKey, sizeMillipoints); + PSFontResource res = getDocumentHandler().getPSResourceForFontKey(fontKey); + if (tf instanceof MultiByteFont && ((MultiByteFont)tf).isOTFFile()) { + generator.writeln("/" + res.getName() + ".0 " + + generator.formatDouble(sizeMillipoints / 1000f) + " F"); + } else { + useFont(fontKey, sizeMillipoints); + } if (dp != null && dp[0] != null) { x += dp[0][0]; @@ -408,7 +415,30 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { } } } else { - useFont(fontKey, sizeMillipoints); + if (tf instanceof MultiByteFont && ((MultiByteFont)tf).isOTFFile()) { + //Analyze string and split up in order to paint in different sub-fonts/encodings + int curEncoding = 0; + for (int i = start; i < textLen; i++) { + char orgChar = text.charAt(i); + + MultiByteFont mbFont = (MultiByteFont)tf; + int origGlyphIdx = mbFont.findGlyphIndex(orgChar); + int newGlyphIdx = mbFont.getUsedGlyphs().get(origGlyphIdx); + int encoding = newGlyphIdx / 256; + if (encoding != curEncoding) { + if (i != 0) { + writeText(text, start, i - start, letterSpacing, wordSpacing, dp, font, tf, + true); + start = i; + } + generator.writeln("/" + res.getName() + "." + encoding + " " + + generator.formatDouble(sizeMillipoints / 1000f) + " F"); + curEncoding = encoding; + } + } + } else { + useFont(fontKey, sizeMillipoints); + } } writeText(text, start, textLen - start, letterSpacing, wordSpacing, dp, font, tf, tf instanceof MultiByteFont); @@ -431,12 +461,14 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { int lineStart = 0; StringBuffer accText = new StringBuffer(initialSize); StringBuffer sb = new StringBuffer(initialSize); + boolean isOTF = multiByte && ((MultiByteFont)tf).isOTFFile(); for (int i = start; i < end; i++) { char orgChar = text.charAt(i); char ch; int cw; int xGlyphAdjust = 0; int yGlyphAdjust = 0; + if (CharUtilities.isFixedWidthSpace(orgChar)) { //Fixed width space are rendered as spaces so copy/paste works in a reader ch = font.mapChar(CharUtilities.SPACE); @@ -460,11 +492,16 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { xGlyphAdjust -= dp[i + 1][0]; yGlyphAdjust += dp[i + 1][1]; } - if (multiByte) { - accText.append(HexEncoder.encode(ch)); - } else { + if (!multiByte || isOTF) { char codepoint = (char)(ch % 256); - PSGenerator.escapeChar(codepoint, accText); //add character to accumulated text + if (isOTF) { + codepoint -= (((MultiByteFont)tf).getEmbeddingMode() == EmbeddingMode.FULL) ? 0 : 1; + accText.append(HexEncoder.encode(codepoint, 2)); + } else { + PSGenerator.escapeChar(codepoint, accText); //add character to accumulated text + } + } else { + accText.append(HexEncoder.encode(ch)); } if (xGlyphAdjust != 0 || yGlyphAdjust != 0) { needTJ = true; |