You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ja...@apache.org on 2014/12/16 00:54:56 UTC
svn commit: r1645796 [1/2] - in /pdfbox/trunk:
examples/src/main/java/org/apache/pdfbox/examples/pdmodel/
fontbox/src/main/java/org/apache/fontbox/ttf/
pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/
pdfbox/src/main/java/org/apache/pdfbox/pdmodel/...
Author: jahewson
Date: Mon Dec 15 23:54:55 2014
New Revision: 1645796
URL: http://svn.apache.org/r1645796
Log:
PDFBOX-2565: Subsetting of embedded Type0 TrueType fonts
Added:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/Subsetter.java (with props)
Modified:
pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/CmapTable.java
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFParser.java
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDSimpleFont.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFontEmbedder.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType0Font.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/TrueTypeEmbedder.java
pdfbox/trunk/tools/src/main/java/org/apache/pdfbox/tools/TextToPDF.java
Modified: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java (original)
+++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java Mon Dec 15 23:54:55 2014
@@ -25,6 +25,7 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont;
+import org.apache.pdfbox.pdmodel.font.PDType0Font;
/**
* This is an example that creates a simple document
@@ -56,7 +57,7 @@ public class HelloWorldTTF
PDPage page = new PDPage();
doc.addPage(page);
- PDFont font = PDTrueTypeFont.loadTTF(doc, new File(fontfile));
+ PDFont font = PDType0Font.load(doc, new File(fontfile));
PDPageContentStream contentStream = new PDPageContentStream(doc,
page);
Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/CmapTable.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/CmapTable.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/CmapTable.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/CmapTable.java Mon Dec 15 23:54:55 2014
@@ -40,12 +40,13 @@ public class CmapTable extends TTFTable
// Windows encodings
public static final int ENCODING_WIN_SYMBOL = 0; // Unicode, non-standard character set
- public static final int ENCODING_WIN_UNICODE = 1;
+ public static final int ENCODING_WIN_UNICODE_BMP = 1; // Unicode BMP (UCS-2)
public static final int ENCODING_WIN_SHIFT_JIS = 2;
public static final int ENCODING_WIN_BIG5 = 3;
public static final int ENCODING_WIN_PRC = 4;
public static final int ENCODING_WIN_WANSUNG = 5;
public static final int ENCODING_WIN_JOHAB = 6;
+ public static final int ENCODING_WIN_UNICODE_FULL = 10; // Unicode Full (UCS-4)
// Unicode encodings
public static final int ENCODING_UNICODE_1_0 = 0;
Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java Mon Dec 15 23:54:55 2014
@@ -225,7 +225,7 @@ public class PostScriptTable extends TTF
/**
* @return Returns the mimMemType1.
*/
- public long getMimMemType1()
+ public long getMinMemType1()
{
return mimMemType1;
}
@@ -307,7 +307,7 @@ public class PostScriptTable extends TTF
*/
public String getName(int gid)
{
- if (gid < 0 || gid > glyphNames.length)
+ if (gid < 0 || glyphNames == null || gid > glyphNames.length)
{
return null;
}
Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFParser.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFParser.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFParser.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFParser.java Mon Dec 15 23:54:55 2014
@@ -99,6 +99,19 @@ public class TTFParser
/**
* Parse a file and get a true type font.
*
+ * @param ttfData The TTF data to parse.
+ * @return A true type font.
+ * @throws IOException If there is an error parsing the true type font.
+ */
+ public TrueTypeFont parseEmbedded(InputStream ttfData) throws IOException
+ {
+ this.isEmbedded = true;
+ return parse(new MemoryTTFDataStream(ttfData));
+ }
+
+ /**
+ * Parse a file and get a true type font.
+ *
* @param raf The TTF file.
* @return A true type font.
* @throws IOException If there is an error parsing the true type font.
Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java Mon Dec 15 23:54:55 2014
@@ -22,7 +22,7 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
+import java.nio.charset.Charset;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
@@ -36,1008 +36,904 @@ import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
/**
- * A font, which is comprised of a subset of characters of a TrueType font.
- * Based on code developed by Wolfgang Glas
- * http://svn.clazzes.org/svn/sketch/trunk/pdf/pdf-entities/src/main/java/org/clazzes/sketch/pdf/entities/impl/TTFSubFont.java
+ * Subsetter for TrueType (TTF) fonts.
+ *
+ * <p>Originally developed by Wolfgang Glas for
+ * <a href="https://clazzes.org/display/SKETCH/Clazzes.org+Sketch+Home">Sketch</a>.
+ *
+ * @author Wolfgang Glas
*/
-public class TTFSubsetter
+public final class TTFSubsetter
{
- private static final Log LOG = LogFactory.getLog(TTFSubsetter.class);
- private static final byte[] PAD_BUF = new byte[] {0,0,0};
-
- private final TrueTypeFont baseTTF;
- private final String nameSuffix;
- private final CmapSubtable baseCmap;
-
- // A map of unicode char codes to glyph IDs of the original font.
- private final SortedMap<Integer,Integer> characters;
- // A sorted version of this set will comprise the generated glyph IDs
- // for the written truetype font.
- private final SortedSet<Integer> glyphIds;
-
+ private static final byte[] PAD_BUF = new byte[] { 0, 0, 0 };
+
+ private final TrueTypeFont ttf;
+ private final CmapSubtable unicodeCmap;
+ private final SortedMap<Integer, Integer> uniToGID;
+
+ private final List<String> keepTables;
+ private final SortedSet<Integer> glyphIds; // new glyph ids
+ private String prefix;
+ private boolean hasAddedCompoundReferences;
+
/**
- * Constructs a subfont based on the given font using the given suffix.
- *
- * @param baseFont the base font of the subfont
- * @param suffix suffix used for the naming
- *
+ * Creates a subsetter for the given font.
+ *
+ * @param ttf the font to be subset
*/
- public TTFSubsetter(TrueTypeFont baseFont, String suffix) throws IOException
+ public TTFSubsetter(TrueTypeFont ttf) throws IOException
{
- baseTTF = baseFont;
- nameSuffix = suffix;
- characters = new TreeMap<Integer, Integer>();
- glyphIds = new TreeSet<Integer>();
-
- CmapSubtable[] cmaps = this.baseTTF.getCmap().getCmaps();
- CmapSubtable unicodeCmap = null;
-
- for (CmapSubtable cmap : cmaps)
- {
- // take first unicode map.
- if (cmap.getPlatformId() == 0 || (cmap.getPlatformId() == 3 && cmap.getPlatformEncodingId() == 1))
- {
- unicodeCmap = cmap;
- break;
- }
- }
- baseCmap = unicodeCmap;
- // add notdef character.
- addCharCode(0);
+ this(ttf, null);
}
-
+
/**
- * Add the given charcode to the subfpont.
- *
- * @param charCode the charCode to be added
+ * Creates a subsetter for the given font.
*
+ * @param ttf the font to be subset
+ * @param tables optional tables to keep if present
*/
- public void addCharCode(int charCode)
+ public TTFSubsetter(TrueTypeFont ttf, List<String> tables) throws IOException
{
- Integer gid = baseCmap.getGlyphId(charCode);
- if (charCode == 0 || gid != 0)
- {
- characters.put(charCode,gid);
- glyphIds.add(gid);
- }
+ this.ttf = ttf;
+ this.keepTables = tables;
+
+ uniToGID = new TreeMap<Integer, Integer>();
+ glyphIds = new TreeSet<Integer>();
+
+ // find the best Unicode cmap
+ this.unicodeCmap = getUnicodeCmap(ttf.getCmap());
+
+ // always copy GID 0
+ glyphIds.add(0);
}
- private static int log2i(int i)
+ /**
+ * Returns the best Unicode from the font (the most general).
+ */
+ private CmapSubtable getUnicodeCmap(CmapTable cmapTable) throws IOException
{
- int ret = -1;
- if ((i & 0xffff0000) != 0)
+ CmapSubtable cmap = cmapTable.getSubtable(CmapTable.PLATFORM_UNICODE,
+ CmapTable.ENCODING_UNICODE_2_0_FULL);
+ if (cmap == null)
{
- i >>>= 16;
- ret += 16;
+ cmap = cmapTable.getSubtable(CmapTable.PLATFORM_UNICODE,
+ CmapTable.ENCODING_UNICODE_2_0_BMP);
}
- if ((i & 0xff00) != 0)
+ if (cmap == null)
{
- i >>>= 8;
- ret += 8;
+ cmap = cmapTable.getSubtable(CmapTable.PLATFORM_WINDOWS,
+ CmapTable.ENCODING_WIN_UNICODE_BMP);
}
- if ((i & 0xf0) != 0)
+ if (cmap == null)
{
- i >>>= 4;
- ret += 4;
+ // Microsoft's "Recommendations for OpenType Fonts" says that "Symbol" encoding
+ // actually means "Unicode, non-standard character set"
+ cmap = cmapTable.getSubtable(CmapTable.PLATFORM_WINDOWS,
+ CmapTable.ENCODING_WIN_SYMBOL);
}
- if ((i & 0xc) != 0)
+ if (cmap == null)
{
- i >>>= 2;
- ret += 2;
+ throw new IOException("The TrueType font does not contain a Unicode cmap");
}
- if ((i & 0x2) != 0)
- {
- i >>>= 1;
- ++ret;
- }
- if (i != 0)
+ return cmap;
+ }
+
+ /**
+ * Sets the prefix to add to the font's PostScript name.
+ */
+ public void setPrefix(String prefix)
+ {
+ this.prefix = prefix;
+ }
+
+ /**
+ * Add the given character code to the subset.
+ *
+ * @param unicode character code
+ */
+ public void add(int unicode)
+ {
+ int gid = unicodeCmap.getGlyphId(unicode);
+ if (gid != 0)
{
- ++ret;
+ uniToGID.put(unicode, gid);
+ glyphIds.add(gid);
}
- return ret;
}
-
- private static long buildUint32(int high, int low)
+
+ /**
+ * Add the given character codes to the subset.
+ *
+ * @param unicodeSet character code set
+ */
+ public void addAll(Set<Integer> unicodeSet)
{
- return ((((long)high)&0xffffL) << 16) | (((long)low)&0xffffL);
+ for (int unicode : unicodeSet)
+ {
+ add(unicode);
+ }
}
-
- private static long buildUint32(byte[] bytes)
+
+ /**
+ * Returns the map of new -> old GIDs.
+ */
+ public Map<Integer, Integer> getGIDMap() throws IOException
{
- return ((((long)bytes[0])&0xffL) << 24) |
- ((((long)bytes[1])&0xffL) << 16) |
- ((((long)bytes[2])&0xffL) << 8) |
- (((long)bytes[3])&0xffL);
+ addCompoundReferences();
+
+ Map<Integer, Integer> newToOld = new HashMap<Integer, Integer>();
+ int newGID = 0;
+ for (int oldGID : glyphIds)
+ {
+ newToOld.put(newGID, oldGID);
+ newGID++;
+ }
+ return newToOld;
}
/**
- * @param dos The data output stream.
+ * @param out The data output stream.
* @param nTables The number of table.
* @return The file offset of the first TTF table to write.
* @throws IOException Upon errors.
*/
- private static long writeFileHeader(DataOutputStream dos, int nTables) throws IOException
+ private long writeFileHeader(DataOutputStream out, int nTables) throws IOException
{
- dos.writeInt(0x00010000);
- dos.writeShort(nTables);
+ out.writeInt(0x00010000);
+ out.writeShort(nTables);
int mask = Integer.highestOneBit(nTables);
int searchRange = mask * 16;
- dos.writeShort(searchRange);
+ out.writeShort(searchRange);
- int entrySelector=log2i(mask);
+ int entrySelector = log2(mask);
- dos.writeShort(entrySelector);
+ out.writeShort(entrySelector);
// numTables * 16 - searchRange
int last = 16 * nTables - searchRange;
- dos.writeShort(last);
+ out.writeShort(last);
- return 0x00010000L + buildUint32(nTables,searchRange) + buildUint32(entrySelector,last);
+ return 0x00010000L + toUInt32(nTables, searchRange) + toUInt32(entrySelector, last);
}
- private static long writeTableHeader(DataOutputStream dos, String tag, long offset, byte[] bytes)
+ private long writeTableHeader(DataOutputStream out, String tag, long offset, byte[] bytes)
throws IOException
{
-
- int n = bytes.length;
- int nup;
- long checksum = 0L;
-
- for (nup=0;nup<n;++nup)
+ long checksum = 0;
+ for (int nup = 0, n = bytes.length; nup < n; nup++)
{
- checksum += (((long)bytes[nup]) & 0xffL)<<(24-(nup%4)*8);
+ checksum += ((long)bytes[nup] & 0xffL) << 24- nup % 4 *8;
}
-
checksum &= 0xffffffffL;
-
- LOG.debug(String.format("Writing table header [%s,%08x,%08x,%08x]",tag,checksum,offset,bytes.length));
-
+
byte[] tagbytes = tag.getBytes("US-ASCII");
-
- dos.write(tagbytes,0,4);
- dos.writeInt((int)checksum);
- dos.writeInt((int)offset);
- dos.writeInt(bytes.length);
-
- // account for the checksum twice, one time for the header field, on time for the content itself.
- return buildUint32(tagbytes) + checksum + checksum + offset + bytes.length;
+
+ out.write(tagbytes, 0, 4);
+ out.writeInt((int)checksum);
+ out.writeInt((int)offset);
+ out.writeInt(bytes.length);
+
+ // account for the checksum twice, once for the header field, once for the content itself
+ return toUInt32(tagbytes) + checksum + checksum + offset + bytes.length;
}
-
- private static void writeTableBody(OutputStream os, byte[] bytes) throws IOException
+
+ private void writeTableBody(OutputStream os, byte[] bytes) throws IOException
{
int n = bytes.length;
os.write(bytes);
- if ((n%4)!=0)
+ if (n % 4 != 0)
{
- os.write(PAD_BUF,0,4-n%4);
+ os.write(PAD_BUF, 0, 4 - n % 4);
}
}
- private static void writeFixed(DataOutputStream dos, double f) throws IOException
- {
- double ip = Math.floor(f);
- double fp = (f-ip) * 65536.0;
- dos.writeShort((int)ip);
- dos.writeShort((int)fp);
- }
-
- private static void writeUint32(DataOutputStream dos, long l) throws IOException
- {
- dos.writeInt((int)l);
- }
-
- private static void writeUint16(DataOutputStream dos, int i) throws IOException
- {
- dos.writeShort(i);
- }
-
- private static void writeSint16(DataOutputStream dos, short i) throws IOException
- {
- dos.writeShort(i);
- }
-
- private static void writeUint8(DataOutputStream dos, int i) throws IOException
- {
- dos.writeByte(i);
- }
-
- private static void writeLongDateTime(DataOutputStream dos, Calendar calendar) throws IOException
- {
- // inverse operation of TTFDataStream.readInternationalDate()
- GregorianCalendar cal = new GregorianCalendar( 1904, 0, 1 );
- long millisFor1904 = cal.getTimeInMillis();
- long secondsSince1904 = (calendar.getTimeInMillis() - millisFor1904) / 1000L;
- dos.writeLong(secondsSince1904);
- }
-
- private byte[] buildHeadTable() throws IOException
+ private byte[] buildHeadTable() throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
-
- LOG.debug("Building table [head]...");
-
- HeaderTable h = this.baseTTF.getHeader();
-
- writeFixed(dos,h.getVersion());
- writeFixed(dos,h.getFontRevision());
- writeUint32(dos,0 /* h.getCheckSumAdjustment() */);
- writeUint32(dos,h.getMagicNumber());
- writeUint16(dos,h.getFlags());
- writeUint16(dos,h.getUnitsPerEm());
- writeLongDateTime(dos,h.getCreated());
- writeLongDateTime(dos,h.getModified());
- writeSint16(dos,h.getXMin());
- writeSint16(dos,h.getYMin());
- writeSint16(dos,h.getXMax());
- writeSint16(dos,h.getYMax());
- writeUint16(dos,h.getMacStyle());
- writeUint16(dos,h.getLowestRecPPEM());
- writeSint16(dos,h.getFontDirectionHint());
- // force long format of 'loca' table.
- writeSint16(dos,(short)1 /* h.getIndexToLocFormat() */);
- writeSint16(dos,h.getGlyphDataFormat());
- dos.flush();
-
- LOG.debug("Finished table [head].");
+ DataOutputStream out = new DataOutputStream(bos);
+
+ HeaderTable h = ttf.getHeader();
+ writeFixed(out, h.getVersion());
+ writeFixed(out, h.getFontRevision());
+ writeUint32(out, 0); // h.getCheckSumAdjustment()
+ writeUint32(out, h.getMagicNumber());
+ writeUint16(out, h.getFlags());
+ writeUint16(out, h.getUnitsPerEm());
+ writeLongDateTime(out, h.getCreated());
+ writeLongDateTime(out, h.getModified());
+ writeSInt16(out, h.getXMin());
+ writeSInt16(out, h.getYMin());
+ writeSInt16(out, h.getXMax());
+ writeSInt16(out, h.getYMax());
+ writeUint16(out, h.getMacStyle());
+ writeUint16(out, h.getLowestRecPPEM());
+ writeSInt16(out, h.getFontDirectionHint());
+ // force long format of 'loca' table
+ writeSInt16(out, (short)1); // h.getIndexToLocFormat()
+ writeSInt16(out, h.getGlyphDataFormat());
+ out.flush();
return bos.toByteArray();
}
-
- private byte[] buildHheaTable() throws IOException
+
+ private byte[] buildHheaTable() throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
+ DataOutputStream out = new DataOutputStream(bos);
- LOG.debug("Building table [hhea]...");
+ HorizontalHeaderTable h = ttf.getHorizontalHeader();
+ writeFixed(out, h.getVersion());
+ writeSInt16(out, h.getAscender());
+ writeSInt16(out, h.getDescender());
+ writeSInt16(out, h.getLineGap());
+ writeUint16(out, h.getAdvanceWidthMax());
+ writeSInt16(out, h.getMinLeftSideBearing());
+ writeSInt16(out, h.getMinRightSideBearing());
+ writeSInt16(out, h.getXMaxExtent());
+ writeSInt16(out, h.getCaretSlopeRise());
+ writeSInt16(out, h.getCaretSlopeRun());
+ writeSInt16(out, h.getReserved1()); // caretOffset
+ writeSInt16(out, h.getReserved2());
+ writeSInt16(out, h.getReserved3());
+ writeSInt16(out, h.getReserved4());
+ writeSInt16(out, h.getReserved5());
+ writeSInt16(out, h.getMetricDataFormat());
+ writeUint16(out, glyphIds.subSet(0, h.getNumberOfHMetrics()).size());
- HorizontalHeaderTable h = this.baseTTF.getHorizontalHeader();
-
- writeFixed(dos,h.getVersion());
- writeSint16(dos,h.getAscender());
- writeSint16(dos,h.getDescender());
- writeSint16(dos,h.getLineGap());
- writeUint16(dos,h.getAdvanceWidthMax());
- writeSint16(dos,h.getMinLeftSideBearing());
- writeSint16(dos,h.getMinRightSideBearing());
- writeSint16(dos,h.getXMaxExtent());
- writeSint16(dos,h.getCaretSlopeRise());
- writeSint16(dos,h.getCaretSlopeRun());
- writeSint16(dos,h.getReserved1()); // caretOffset
- writeSint16(dos,h.getReserved2());
- writeSint16(dos,h.getReserved3());
- writeSint16(dos,h.getReserved4());
- writeSint16(dos,h.getReserved5());
- writeSint16(dos,h.getMetricDataFormat());
- writeUint16(dos,glyphIds.subSet(0,h.getNumberOfHMetrics()).size());
-
- dos.flush();
- LOG.debug("Finished table [hhea].");
+ out.flush();
return bos.toByteArray();
}
- private static boolean replicateNameRecord(NameRecord nr)
+ private boolean shouldCopyNameRecord(NameRecord nr)
{
- return nr.getPlatformId() == NameRecord.PLATFORM_WINDOWS
+ return nr.getPlatformId() == NameRecord.PLATFORM_WINDOWS
&& nr.getPlatformEncodingId() == NameRecord.ENCODING_WINDOWS_UNICODE_BMP
- && nr.getLanguageId() == 0
+ && nr.getLanguageId() == NameRecord.LANGUGAE_WINDOWS_EN_US
&& nr.getNameId() >= 0 && nr.getNameId() < 7;
}
-
- private byte[] buildNameTable() throws IOException
+
+ private byte[] buildNameTable() throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
-
- LOG.debug("Building table [name]...");
+ DataOutputStream out = new DataOutputStream(bos);
+
+ NamingTable name = ttf.getNaming();
+ if (name == null || keepTables != null && !keepTables.contains("name"))
+ {
+ return null;
+ }
- NamingTable n = this.baseTTF.getNaming();
- List<NameRecord> nameRecords = null;
- if ( n != null)
- {
- nameRecords = n.getNameRecords();
- }
- else
- {
- // sometimes there is no naming table in an embedded subfonts
- // create some dummies
- nameRecords = new ArrayList<NameRecord>();
- NameRecord nr = new NameRecord();
- nr.setPlatformId(NameRecord.PLATFORM_WINDOWS);
- nr.setPlatformEncodingId(NameRecord.ENCODING_WINDOWS_UNICODE_BMP);
- nr.setLanguageId(0);
- nr.setNameId(NameRecord.NAME_FONT_FAMILY_NAME);
- nr.setString("PDFBox-Dummy-Familyname");
- nameRecords.add(nr);
- nr = new NameRecord();
- nr.setPlatformId(NameRecord.PLATFORM_WINDOWS);
- nr.setPlatformEncodingId(NameRecord.ENCODING_WINDOWS_UNICODE_BMP);
- nr.setLanguageId(0);
- nr.setNameId(NameRecord.NAME_FULL_FONT_NAME);
- nr.setString("PDFBox-Dummy-Fullname");
- nameRecords.add(nr);
- }
- int numberOfRecords = nameRecords.size();
- int nrep = 0;
- for (int i=0;i<numberOfRecords;++i)
+ List<NameRecord> nameRecords = name.getNameRecords();
+ int numRecords = 0;
+ for (NameRecord record : nameRecords)
{
- NameRecord nr = nameRecords.get(i);
- if (replicateNameRecord(nr))
+ if (shouldCopyNameRecord(record))
{
- LOG.debug("Writing name record ["+nr.getNameId()+"], ["+nr.getString()+"],");
- ++nrep;
+ numRecords++;
}
}
- writeUint16(dos,0);
- writeUint16(dos,nrep);
- writeUint16(dos,2*3 + (2*6) * nrep);
-
- byte[][] names = new byte[nrep][];
- int j=0;
- for (int i=0;i<numberOfRecords;++i)
+ writeUint16(out, 0);
+ writeUint16(out, numRecords);
+ writeUint16(out, 2*3 + 2*6 * numRecords);
+
+ if (numRecords == 0)
{
- NameRecord nr = nameRecords.get(i);
- if (replicateNameRecord(nr))
+ return null;
+ }
+
+ byte[][] names = new byte[numRecords][];
+ int j = 0;
+ for (NameRecord record : nameRecords)
+ {
+ if (shouldCopyNameRecord(record))
{
- int platform = nr.getPlatformId();
- int encoding = nr.getPlatformEncodingId();
+ int platform = record.getPlatformId();
+ int encoding = record.getPlatformEncodingId();
String charset = "ISO-8859-1";
- if( platform == 3 && encoding == 1 )
+
+ if (platform == CmapTable.PLATFORM_WINDOWS &&
+ encoding == CmapTable.ENCODING_WIN_UNICODE_BMP)
{
charset = "UTF-16BE";
}
- else if( platform == 2 )
+ else if (platform == 2) // ISO [deprecated]=
{
- if( encoding == 0 )
+ if (encoding == 0) // 7-bit ASCII
{
charset = "US-ASCII";
}
- else if( encoding == 1 )
+ else if (encoding == 1) // ISO 10646=
{
//not sure is this is correct??
charset = "UTF16-BE";
}
- else if( encoding == 2 )
+ else if (encoding == 2) // ISO 8859-1
{
charset = "ISO-8859-1";
}
}
- String value = nr.getString();
- if (nr.getNameId() == 6 && this.nameSuffix != null)
+ String value = record.getString();
+ if (record.getNameId() == 6 && prefix != null)
{
- value += this.nameSuffix;
+ value = prefix + value;
}
names[j] = value.getBytes(charset);
- ++j;
+ j++;
}
}
-
+
int offset = 0;
j = 0;
- for (int i=0;i<numberOfRecords;++i)
+ for (NameRecord nr : nameRecords)
{
- NameRecord nr = nameRecords.get(i);
- if (replicateNameRecord(nr))
+ if (shouldCopyNameRecord(nr))
{
- writeUint16(dos,nr.getPlatformId());
- writeUint16(dos,nr.getPlatformEncodingId());
- writeUint16(dos,nr.getLanguageId());
- writeUint16(dos,nr.getNameId());
- writeUint16(dos,names[j].length);
- writeUint16(dos,offset);
+ writeUint16(out, nr.getPlatformId());
+ writeUint16(out, nr.getPlatformEncodingId());
+ writeUint16(out, nr.getLanguageId());
+ writeUint16(out, nr.getNameId());
+ writeUint16(out, names[j].length);
+ writeUint16(out, offset);
offset += names[j].length;
- ++j;
+ j++;
}
}
-
- for (int i=0;i<nrep;++i)
+
+ for (int i = 0; i < numRecords; i++)
{
- dos.write(names[i]);
+ out.write(names[i]);
}
- dos.flush();
- LOG.debug("Finished table [name].");
+
+ out.flush();
return bos.toByteArray();
}
-
- private byte[] buildMaxpTable() throws IOException
+
+ private byte[] buildMaxpTable() throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
-
- LOG.debug("Building table [maxp]...");
+ DataOutputStream out = new DataOutputStream(bos);
- MaximumProfileTable p = this.baseTTF.getMaximumProfile();
+ MaximumProfileTable p = ttf.getMaximumProfile();
+ writeFixed(out, 1.0);
+ writeUint16(out, glyphIds.size());
+ writeUint16(out, p.getMaxPoints());
+ writeUint16(out, p.getMaxContours());
+ writeUint16(out, p.getMaxCompositePoints());
+ writeUint16(out, p.getMaxCompositeContours());
+ writeUint16(out, p.getMaxZones());
+ writeUint16(out, p.getMaxTwilightPoints());
+ writeUint16(out, p.getMaxStorage());
+ writeUint16(out, p.getMaxFunctionDefs());
+ writeUint16(out, p.getMaxInstructionDefs());
+ writeUint16(out, p.getMaxStackElements());
+ writeUint16(out, p.getMaxSizeOfInstructions());
+ writeUint16(out, p.getMaxComponentElements());
+ writeUint16(out, p.getMaxComponentDepth());
- writeFixed(dos,1.0);
- writeUint16(dos,glyphIds.size());
- writeUint16(dos,p.getMaxPoints());
- writeUint16(dos,p.getMaxContours());
- writeUint16(dos,p.getMaxCompositePoints());
- writeUint16(dos,p.getMaxCompositeContours());
- writeUint16(dos,p.getMaxZones());
- writeUint16(dos,p.getMaxTwilightPoints());
- writeUint16(dos,p.getMaxStorage());
- writeUint16(dos,p.getMaxFunctionDefs());
- writeUint16(dos,0);
- writeUint16(dos,p.getMaxStackElements());
- writeUint16(dos,0);
- writeUint16(dos,p.getMaxComponentElements());
- writeUint16(dos,p.getMaxComponentDepth());
-
- dos.flush();
- LOG.debug("Finished table [maxp].");
+ out.flush();
return bos.toByteArray();
}
-
- private byte[] buildOS2Table() throws IOException
+
+ private byte[] buildOS2Table() throws IOException
{
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- OS2WindowsMetricsTable os2 = this.baseTTF.getOS2Windows();
- if (os2 == null)
- {
- // sometimes there is no OS2 table in an embedded subfonts
- // create a dummy
- os2 = new OS2WindowsMetricsTable();
+ OS2WindowsMetricsTable os2 = ttf.getOS2Windows();
+ if (os2 == null || keepTables != null && !keepTables.contains("OS/2"))
+ {
+ return null;
}
-
- LOG.debug("Building table [OS/2]...");
- writeUint16(dos,0);
- writeSint16(dos,os2.getAverageCharWidth());
- writeUint16(dos,os2.getWeightClass());
- writeUint16(dos,os2.getWidthClass());
-
- writeSint16(dos,os2.getFsType());
-
- writeSint16(dos,os2.getSubscriptXSize());
- writeSint16(dos,os2.getSubscriptYSize());
- writeSint16(dos,os2.getSubscriptXOffset());
- writeSint16(dos,os2.getSubscriptYOffset());
-
- writeSint16(dos,os2.getSuperscriptXSize());
- writeSint16(dos,os2.getSuperscriptYSize());
- writeSint16(dos,os2.getSuperscriptXOffset());
- writeSint16(dos,os2.getSuperscriptYOffset());
-
- writeSint16(dos,os2.getStrikeoutSize());
- writeSint16(dos,os2.getStrikeoutPosition());
- writeUint8(dos,os2.getFamilyClass());
- writeUint8(dos,os2.getFamilySubClass());
- dos.write(os2.getPanose());
-
- writeUint32(dos,0);
- writeUint32(dos,0);
- writeUint32(dos,0);
- writeUint32(dos,0);
-
- dos.write(os2.getAchVendId().getBytes("ISO-8859-1"));
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(bos);
- Iterator<Entry<Integer, Integer>> it = characters.entrySet().iterator();
+ writeUint16(out, os2.getVersion());
+ writeSInt16(out, os2.getAverageCharWidth());
+ writeUint16(out, os2.getWeightClass());
+ writeUint16(out, os2.getWidthClass());
+
+ writeSInt16(out, os2.getFsType());
+
+ writeSInt16(out, os2.getSubscriptXSize());
+ writeSInt16(out, os2.getSubscriptYSize());
+ writeSInt16(out, os2.getSubscriptXOffset());
+ writeSInt16(out, os2.getSubscriptYOffset());
+
+ writeSInt16(out, os2.getSuperscriptXSize());
+ writeSInt16(out, os2.getSuperscriptYSize());
+ writeSInt16(out, os2.getSuperscriptXOffset());
+ writeSInt16(out, os2.getSuperscriptYOffset());
+
+ writeSInt16(out, os2.getStrikeoutSize());
+ writeSInt16(out, os2.getStrikeoutPosition());
+ writeUint8(out, os2.getFamilyClass());
+ writeUint8(out, os2.getFamilySubClass());
+ out.write(os2.getPanose());
+
+ writeUint32(out, 0);
+ writeUint32(out, 0);
+ writeUint32(out, 0);
+ writeUint32(out, 0);
+
+ out.write(os2.getAchVendId().getBytes("US-ASCII"));
+
+ Iterator<Entry<Integer, Integer>> it = uniToGID.entrySet().iterator();
it.next();
Entry<Integer, Integer> first = it.next();
-
- writeUint16(dos,os2.getFsSelection());
- writeUint16(dos,first.getKey());
- writeUint16(dos,characters.lastKey());
- /*
- * The mysterious Microsoft additions.
- *
- * SHORT sTypoAscender
- * SHORT sTypoDescender
- * SHORT sTypoLineGap
- * USHORT usWinAscent
- * USHORT usWinDescent
- */
- writeUint16(dos,os2.getTypoAscender());
- writeUint16(dos,os2.getTypoDescender());
- writeUint16(dos,os2.getTypoLineGap());
- writeUint16(dos,os2.getWinAscent());
- writeUint16(dos,os2.getWinDescent());
-
- dos.flush();
- LOG.debug("Finished table [OS/2].");
+
+ writeUint16(out, os2.getFsSelection());
+ writeUint16(out, first.getKey());
+ writeUint16(out, uniToGID.lastKey());
+ writeUint16(out, os2.getTypoAscender());
+ writeUint16(out, os2.getTypoDescender());
+ writeUint16(out, os2.getTypoLineGap());
+ writeUint16(out, os2.getWinAscent());
+ writeUint16(out, os2.getWinDescent());
+
+ out.flush();
return bos.toByteArray();
}
-
- private byte[] buildLocaTable(long[] newOffsets) throws IOException
+
+ private byte[] buildLocaTable(long[] newOffsets) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
+ DataOutputStream out = new DataOutputStream(bos);
- LOG.debug("Building table [loca]...");
-
- for (long newOff : newOffsets)
- {
- writeUint32(dos,newOff);
+ for (long offset : newOffsets)
+ {
+ writeUint32(out, offset);
}
- dos.flush();
- LOG.debug("Finished table [loca].");
+
+ out.flush();
return bos.toByteArray();
}
- private boolean addCompoundReferences() throws IOException
+ /**
+ * Resolve compound glyph references.
+ */
+ private void addCompoundReferences() throws IOException
{
- GlyphTable g = this.baseTTF.getGlyph();
- long[] offsets = this.baseTTF.getIndexToLocation().getOffsets();
- InputStream is = this.baseTTF.getOriginalData();
- Set<Integer> glyphIdsToAdd = null;
- try
+ if (hasAddedCompoundReferences)
{
- is.skip(g.getOffset());
- long lastOff = 0L;
- for (Integer glyphId : this.glyphIds)
+ return;
+ }
+ hasAddedCompoundReferences = true;
+
+ boolean hasNested;
+ do
+ {
+ GlyphTable g = ttf.getGlyph();
+ long[] offsets = ttf.getIndexToLocation().getOffsets();
+ InputStream is = ttf.getOriginalData();
+ Set<Integer> glyphIdsToAdd = null;
+ try
{
- long offset = offsets[glyphId];
- long len = offsets[glyphId+1] - offset;
- is.skip(offset-lastOff);
- byte[] buf= new byte[(int)len];
- is.read(buf);
- // rewrite glyphIds for compound glyphs
- if (buf.length >= 2 && buf[0] == -1 && buf[1] == -1)
+ is.skip(g.getOffset());
+ long lastOff = 0L;
+ for (Integer glyphId : glyphIds)
{
- int off = 2*5;
- int flags = 0;
- do
+ long offset = offsets[glyphId];
+ long len = offsets[glyphId + 1] - offset;
+ is.skip(offset - lastOff);
+ byte[] buf = new byte[(int)len];
+ is.read(buf);
+ // rewrite glyphIds for compound glyphs
+ if (buf.length >= 2 && buf[0] == -1 && buf[1] == -1)
{
- flags = ((((int)buf[off]) & 0xff) << 8) | (buf[off+1] & 0xff);
- off +=2;
- int ogid = ((((int)buf[off]) & 0xff) << 8) | (buf[off+1] & 0xff);
- if (!this.glyphIds.contains(ogid))
+ int off = 2*5;
+ int flags;
+ do
{
- LOG.debug("Adding referenced glyph "+ogid+" of compound glyph "+glyphId);
- if (glyphIdsToAdd == null)
+ flags = ((int)buf[off] & 0xff) << 8 | buf[off + 1] & 0xff;
+ off +=2;
+ int ogid = ((int)buf[off] & 0xff) << 8 | buf[off + 1] & 0xff;
+ if (!glyphIds.contains(ogid))
{
- glyphIdsToAdd = new TreeSet<Integer>();
+ if (glyphIdsToAdd == null)
+ {
+ glyphIdsToAdd = new TreeSet<Integer>();
+ }
+ glyphIdsToAdd.add(ogid);
}
- glyphIdsToAdd.add(ogid);
- }
- off += 2;
- // ARG_1_AND_2_ARE_WORDS
- if ((flags & (1 << 0)) != 0)
- {
- off += 2 * 2;
- }
- else
- {
- off += 2;
- }
- // WE_HAVE_A_TWO_BY_TWO
- if ((flags & (1 << 7)) != 0)
- {
- off += 2 * 4;
- }
- // WE_HAVE_AN_X_AND_Y_SCALE
- else if ((flags & (1 << 6)) != 0)
- {
- off += 2 * 2;
- }
- // WE_HAVE_A_SCALE
- else if ((flags & (1 << 3)) != 0)
- {
off += 2;
+ // ARG_1_AND_2_ARE_WORDS
+ if ((flags & 1 << 0) != 0)
+ {
+ off += 2 * 2;
+ }
+ else
+ {
+ off += 2;
+ }
+ // WE_HAVE_A_TWO_BY_TWO
+ if ((flags & 1 << 7) != 0)
+ {
+ off += 2 * 4;
+ }
+ // WE_HAVE_AN_X_AND_Y_SCALE
+ else if ((flags & 1 << 6) != 0)
+ {
+ off += 2 * 2;
+ }
+ // WE_HAVE_A_SCALE
+ else if ((flags & 1 << 3) != 0)
+ {
+ off += 2;
+ }
}
-
- // MORE_COMPONENTS
- }
- while ((flags & (1 << 5)) != 0);
-
+ while ((flags & 1 << 5) != 0); // MORE_COMPONENTS
+
+ }
+ lastOff = offsets[glyphId + 1];
}
- lastOff = offsets[glyphId+1];
}
- }
- finally
- {
- is.close();
- }
- if (glyphIdsToAdd != null)
- {
- this.glyphIds.addAll(glyphIdsToAdd);
- }
- return glyphIdsToAdd == null;
+ finally
+ {
+ is.close();
+ }
+ if (glyphIdsToAdd != null)
+ {
+ glyphIds.addAll(glyphIdsToAdd);
+ }
+ hasNested = glyphIdsToAdd != null;
+ } while (hasNested);
}
-
- private byte[] buildGlyfTable(long[] newOffsets) throws IOException
+
+ private byte[] buildGlyfTable(long[] newOffsets) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
- LOG.debug("Building table [glyf]...");
- GlyphTable g = this.baseTTF.getGlyph();
- long[] offsets = this.baseTTF.getIndexToLocation().getOffsets();
- InputStream is = this.baseTTF.getOriginalData();
- try
+
+ GlyphTable g = ttf.getGlyph();
+ long[] offsets = ttf.getIndexToLocation().getOffsets();
+ InputStream is = ttf.getOriginalData();
+ try
{
is.skip(g.getOffset());
- long lastOff = 0L;
- long newOff = 0L;
- int ioff = 0;
- for (Integer glyphId : this.glyphIds)
+
+ long prevEnd = 0; // previously read glyph offset
+ long newOffset = 0; // new offset for the glyph in the subset font
+ int newGid = 0; // new GID in subset font
+
+ // for each glyph in the subset
+ for (Integer gid : glyphIds)
{
- long offset = offsets[glyphId];
- long len = offsets[glyphId+1] - offset;
- newOffsets[ioff++] = newOff;
- is.skip(offset-lastOff);
- byte[] buf= new byte[(int)len];
+ long offset = offsets[gid];
+ long length = offsets[gid + 1] - offset;
+
+ newOffsets[newGid++] = newOffset;
+ is.skip(offset - prevEnd);
+
+ byte[] buf = new byte[(int)length];
is.read(buf);
- // rewrite glyphIds for compound glyphs
- if (buf.length >= 2 && buf[0] == -1 && buf[1] == -1)
+
+ // detect glyph type
+ if (buf.length >= 2 && buf[0] == -1 && buf[1] == -1)
{
- LOG.debug("Compound glyph "+glyphId);
+ // compound glyph
int off = 2*5;
- int flags = 0;
- do
+ int flags;
+ do
{
- // clear the WE_HAVE_INSTRUCTIONS bit. (bit 8 is lsb of the first byte)
- buf[off] &= 0xfe;
- flags = ((((int)buf[off]) & 0xff) << 8) | ((int)buf[off+1] & 0xff);
- off +=2;
- int ogid = ((((int)buf[off]) & 0xff) << 8) | ((int)buf[off+1] & 0xff);
- if (!this.glyphIds.contains(ogid))
- {
- this.glyphIds.add(ogid);
- }
- int ngid = this.getNewGlyphId(ogid);
- if (LOG.isDebugEnabled())
+ // flags
+ flags = ((int)buf[off] & 0xff) << 8 | (int)buf[off + 1] & 0xff;
+ off += 2;
+
+ // glyphIndex
+ int componentGid = ((int)buf[off] & 0xff) << 8 | (int)buf[off + 1] & 0xff;
+ if (!glyphIds.contains(componentGid))
{
- LOG.debug(String.format("mapped glyph %d to %d in compound reference (flags=%04x)",
- ogid,ngid,flags));
+ glyphIds.add(componentGid);
}
- buf[off] = (byte)(ngid >>> 8);
- buf[off+1] = (byte)ngid;
+
+ int newComponentGid = getNewGlyphId(componentGid);
+ buf[off] = (byte)(newComponentGid >>> 8);
+ buf[off + 1] = (byte)newComponentGid;
off += 2;
+
// ARG_1_AND_2_ARE_WORDS
- if ((flags & (1 << 0)) != 0)
+ if ((flags & 1 << 0) != 0)
{
off += 2 * 2;
}
- else
+ else
{
off += 2;
}
// WE_HAVE_A_TWO_BY_TWO
- if ((flags & (1 << 7)) != 0)
+ if ((flags & 1 << 7) != 0)
{
off += 2 * 4;
}
// WE_HAVE_AN_X_AND_Y_SCALE
- else if ((flags & (1 << 6)) != 0)
+ else if ((flags & 1 << 6) != 0)
{
off += 2 * 2;
}
// WE_HAVE_A_SCALE
- else if ((flags & (1 << 3)) != 0)
+ else if ((flags & 1 << 3) != 0)
{
off += 2;
}
- // MORE_COMPONENTS
- }
- while ((flags & (1 << 5)) != 0);
+ }
+ while ((flags & 1 << 5) != 0); // MORE_COMPONENTS
+
+ // WE_HAVE_INSTRUCTIONS
+ if ((flags & 0x0100) == 0x0100)
+ {
+ // USHORT numInstr
+ int numInstr = ((int)buf[off] & 0xff) << 8 | (int)buf[off + 1] & 0xff;
+ off += 2;
+
+ // BYTE instr[numInstr]
+ off += numInstr;
+ }
+
// write the compound glyph
- bos.write(buf,0,off);
- newOff += off;
+ bos.write(buf, 0, off);
+
+ // offset to start next glyph
+ newOffset += off;
}
else if (buf.length > 0)
{
- /*
- * bail out instructions for simple glyphs, an excerpt from the specs is given below:
- *
- * int16 numberOfContours If the number of contours is positive or zero, it is a single glyph;
- * If the number of contours is -1, the glyph is compound
- * FWord xMin Minimum x for coordinate data
- * FWord yMin Minimum y for coordinate data
- * FWord xMax Maximum x for coordinate data
- * FWord yMax Maximum y for coordinate data
- * (here follow the data for the simple or compound glyph)
- *
- * Table 15: Simple glyph definition
- * Type Name Description
- * uint16 endPtsOfContours[n] Array of last points of each contour; n is the number of contours;
- * array entries are point indices
- * uint16 instructionLength Total number of bytes needed for instructions
- * uint8 instructions[instructionLength] Array of instructions for this glyph
- * uint8 flags[variable] Array of flags
- * uint8 or int16 xCoordinates[] Array of x-coordinates; the first is relative to (0,0),
- * others are relative to previous point
- * uint8 or int16 yCoordinates[] Array of y-coordinates; the first is relative to (0,0),
- * others are relative to previous point
- */
-
- int numberOfContours = (((int)buf[0]& 0xff) << 8) | ((int)buf[1] & 0xff);
-
- // offset of instructionLength
- int off = 2*5 + numberOfContours * 2;
-
- // write numberOfContours, xMin, yMin, xMax, yMax, endPtsOfContours[n]
- bos.write(buf,0,off);
- newOff += off;
-
- int instructionLength = ((((int)buf[off]) & 0xff) << 8) | ((int)buf[off+1] & 0xff);
-
- // zarro instructions.
- bos.write(0);
- bos.write(0);
- newOff += 2;
-
- off += 2 + instructionLength;
-
- // flags and coordinates
- bos.write(buf,off,buf.length - off);
- newOff += buf.length - off;
+ // copy the entire glyph
+ bos.write(buf, 0, buf.length);
+
+ // offset to start next glyph
+ newOffset += buf.length;
}
-
-
- if ((newOff % 4L) != 0L)
+
+ // 4-byte alignment
+ if (newOffset % 4 != 0)
{
- int np = (int)(4-newOff%4L);
- bos.write(PAD_BUF,0,np);
- newOff += np;
+ int len = 4 - (int)(newOffset % 4);
+ bos.write(PAD_BUF, 0, len);
+ newOffset += len;
}
-
- lastOff = offsets[glyphId+1];
+
+ prevEnd = offset + length;
}
- newOffsets[ioff++] = newOff;
+ newOffsets[newGid++] = newOffset;
}
- finally
+ finally
{
is.close();
}
- LOG.debug("Finished table [glyf].");
+
return bos.toByteArray();
}
- private int getNewGlyphId(Integer oldGid)
+ private int getNewGlyphId(Integer oldGid)
{
- return this.glyphIds.headSet(oldGid).size();
+ return glyphIds.headSet(oldGid).size();
}
-
- private byte[] buildCmapTable() throws IOException
+
+ private byte[] buildCmapTable() throws IOException
{
+ if (ttf.getCmap() == null || keepTables != null && !keepTables.contains("cmap"))
+ {
+ return null;
+ }
+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- LOG.debug("Building table [cmap]...");
- /*
- * UInt16 version Version number (Set to zero)
- * UInt16 numberSubtables Number of encoding subtables
- */
- writeUint16(dos,0);
- writeUint16(dos,1);
- /*
- * UInt16 platformID Platform identifier
- * UInt16 platformSpecificID Platform-specific encoding identifier
- * UInt32 offset Offset of the mapping table
- */
- writeUint16(dos,3); // unicode
- writeUint16(dos,1); // Default Semantics
- writeUint32(dos, 4 * 2 + 4);
- // mapping of type 4.
- Iterator<Entry<Integer, Integer>> it = this.characters.entrySet().iterator();
+ DataOutputStream out = new DataOutputStream(bos);
+
+ // cmap header
+ writeUint16(out, 0); // version
+ writeUint16(out, 1); // numberSubtables
+
+ // encoding record
+ writeUint16(out, CmapTable.PLATFORM_WINDOWS); // platformID
+ writeUint16(out, CmapTable.ENCODING_WIN_UNICODE_BMP); // platformSpecificID
+ writeUint32(out, 4 * 2 + 4); // offset
+
+ // build Format 4 subtable (Unicode BMP)
+ Iterator<Entry<Integer, Integer>> it = uniToGID.entrySet().iterator();
it.next();
Entry<Integer, Integer> lastChar = it.next();
Entry<Integer, Integer> prevChar = lastChar;
- int lastGid = this.getNewGlyphId(lastChar.getValue());
+ int lastGid = getNewGlyphId(lastChar.getValue());
+
+ int[] startCode = new int[uniToGID.size()];
+ int[] endCode = new int[uniToGID.size()];
+ int[] idDelta = new int[uniToGID.size()];
+ int segCount = 0;
+ while(it.hasNext())
+ {
+ Entry<Integer, Integer> curChar2Gid = it.next();
+ int curGid = getNewGlyphId(curChar2Gid.getValue());
+
+ // todo: need format Format 12 for non-BMP
+ if (curChar2Gid.getKey() > 0xFFFF)
+ {
+ throw new UnsupportedOperationException("non-BMP Unicode character");
+ }
- int[] startCode = new int[this.characters.size()];
- int[] endCode = new int[this.characters.size()];
- int[] idDelta = new int[this.characters.size()];
- int nseg = 0;
- while(it.hasNext())
- {
- Entry<Integer, Integer> curChar = it.next();
- int curGid = this.getNewGlyphId(curChar.getValue());
-
- if (curChar.getKey() != prevChar.getKey()+1 ||
- curGid - lastGid != curChar.getKey() - lastChar.getKey())
+ if (curChar2Gid.getKey() != prevChar.getKey()+1 ||
+ curGid - lastGid != curChar2Gid.getKey() - lastChar.getKey())
{
- // Don't emit ranges, which map to the undef glyph, the
- // undef glyph is emitted a the very last segment.
- if (lastGid != 0)
+ if (lastGid != 0)
{
- startCode[nseg] = lastChar.getKey();
- endCode[nseg] = prevChar.getKey();
- idDelta[nseg] = lastGid - lastChar.getKey();
- ++nseg;
+ // don't emit ranges, which map to GID 0, the
+ // undef glyph is emitted a the very last segment
+ startCode[segCount] = lastChar.getKey();
+ endCode[segCount] = prevChar.getKey();
+ idDelta[segCount] = lastGid - lastChar.getKey();
+ segCount++;
}
- // shorten ranges which start with undef by one.
- else if (!lastChar.getKey().equals(prevChar.getKey()))
+ else if (!lastChar.getKey().equals(prevChar.getKey()))
{
- startCode[nseg] = lastChar.getKey()+1;
- endCode[nseg] = prevChar.getKey();
- idDelta[nseg] = lastGid - lastChar.getKey();
- ++nseg;
+ // shorten ranges which start with GID 0 by one
+ startCode[segCount] = lastChar.getKey() + 1;
+ endCode[segCount] = prevChar.getKey();
+ idDelta[segCount] = lastGid - lastChar.getKey();
+ segCount++;
}
lastGid = curGid;
- lastChar = curChar;
+ lastChar = curChar2Gid;
}
- prevChar = curChar;
+ prevChar = curChar2Gid;
}
+
// trailing segment
- startCode[nseg] = lastChar.getKey();
- endCode[nseg] = prevChar.getKey();
- idDelta[nseg] = lastGid -lastChar.getKey();
- ++nseg;
- // notdef character.
- startCode[nseg] = 0xffff;
- endCode[nseg] = 0xffff;
- idDelta[nseg] = 1;
- ++nseg;
-
- /*
- * UInt16 format Format number is set to 4
- * UInt16 length Length of subtable in bytes
- * UInt16 language Language code for this encoding subtable, or zero if language-independent
- * UInt16 segCountX2 2 * segCount
- * UInt16 searchRange 2 * (2**FLOOR(log2(segCount)))
- * UInt16 entrySelector log2(searchRange/2)
- * UInt16 rangeShift (2 * segCount) - searchRange
- * UInt16 endCode[segCount] Ending character code for each segment, last = 0xFFFF.
- * UInt16 reservedPad This value should be zero
- * UInt16 startCode[segCount] Starting character code for each segment
- * UInt16 idDelta[segCount] Delta for all character codes in segment
- * UInt16 idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0
- * UInt16 glyphIndexArray[variable] Glyph index array
- */
-
- writeUint16(dos,4);
- writeUint16(dos, 8*2 + nseg * (4*2));
- writeUint16(dos,0);
- writeUint16(dos,nseg*2);
- int nsegHigh = Integer.highestOneBit(nseg);
- writeUint16(dos,nsegHigh*2);
- writeUint16(dos,log2i(nsegHigh));
- writeUint16(dos,2*(nseg-nsegHigh));
-
- for (int i=0;i<nseg;++i)
- {
- writeUint16(dos,endCode[i]);
- }
- writeUint16(dos,0);
- for (int i=0;i<nseg;++i)
- {
- writeUint16(dos,startCode[i]);
- }
- for (int i=0;i<nseg;++i)
- {
- writeUint16(dos,idDelta[i]);
- }
- for (int i=0;i<nseg;++i)
- {
- writeUint16(dos,0);
+ startCode[segCount] = lastChar.getKey();
+ endCode[segCount] = prevChar.getKey();
+ idDelta[segCount] = lastGid -lastChar.getKey();
+ segCount++;
+
+ // GID 0
+ startCode[segCount] = 0xffff;
+ endCode[segCount] = 0xffff;
+ idDelta[segCount] = 1;
+ segCount++;
+
+ // write format 4 subtable
+ int searchRange = 2 * (int)Math.pow(2, Math.floor(log2(segCount)));
+ writeUint16(out, 4); // format
+ writeUint16(out, 8 * 2 + segCount * 4*2); // length
+ writeUint16(out, 0); // language
+ writeUint16(out, segCount * 2); // segCountX2
+ writeUint16(out, searchRange); // searchRange
+ writeUint16(out, log2(searchRange / 2)); // entrySelector
+ writeUint16(out, 2 * segCount - searchRange); // rangeShift
+
+ // endCode[segCount]
+ for (int i = 0; i < segCount; i++)
+ {
+ writeUint16(out, endCode[i]);
}
- LOG.debug("Finished table [cmap].");
+
+ // reservedPad
+ writeUint16(out, 0);
+
+ // startCode[segCount]
+ for (int i = 0; i < segCount; i++)
+ {
+ writeUint16(out, startCode[i]);
+ }
+
+ // idDelta[segCount]
+ for (int i = 0; i < segCount; i++)
+ {
+ writeUint16(out, idDelta[i]);
+ }
+
+ for (int i = 0; i < segCount; i++)
+ {
+ writeUint16(out, 0);
+ }
+
return bos.toByteArray();
}
- private byte[] buildPostTable() throws IOException
+ private byte[] buildPostTable() throws IOException
{
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- LOG.debug("Building table [post]...");
- PostScriptTable p = this.baseTTF.getPostScript();
- if (p == null)
- {
- // sometimes there is no post table in an embedded subfonts
- // create a dummy
- p = new PostScriptTable();
- }
- String[] glyphNames = p.getGlyphNames();
- /*
- Fixed format Format of this table
- Fixed italicAngle Italic angle in degrees
- FWord underlinePosition Underline position
- FWord underlineThickness Underline thickness
- uint32 isFixedPitch Font is monospaced; set to 1 if the font is monospaced and 0 otherwise
- (N.B., to maintain compatibility with older versions of the TrueType spec, accept any non-zero value
- as meaning that the font is monospaced)
- uint32 minMemType42 Minimum memory usage when a TrueType font is downloaded as a Type 42 font
- uint32 maxMemType42 Maximum memory usage when a TrueType font is downloaded as a Type 42 font
- uint32 minMemType1 Minimum memory usage when a TrueType font is downloaded as a Type 1 font
- uint32 maxMemType1 Maximum memory usage when a TrueType font is downloaded as a Type 1 font
- uint16 numberOfGlyphs number of glyphs
- uint16 glyphNameIndex[numberOfGlyphs] Ordinal number of this glyph in 'post' string tables.
- This is not an offset.
- Pascal string names[numberNewGlyphs] glyph names with length bytes [variable] (a Pascal string)
- */
- writeFixed(dos,2.0);
- writeFixed(dos,p.getItalicAngle());
- writeSint16(dos,p.getUnderlinePosition());
- writeSint16(dos,p.getUnderlineThickness());
- writeUint32(dos,p.getIsFixedPitch());
- writeUint32(dos,p.getMinMemType42());
- writeUint32(dos,p.getMaxMemType42());
- writeUint32(dos,p.getMimMemType1());
- writeUint32(dos,p.getMaxMemType1());
- writeUint16(dos,baseTTF.getHorizontalHeader().getNumberOfHMetrics());
-
- List<String> additionalNames = new ArrayList<String>();
- Map<String,Integer> additionalNamesIndices = new HashMap<String,Integer>();
-
- if (glyphNames != null)
+ PostScriptTable post = ttf.getPostScript();
+ if (post == null || keepTables != null && !keepTables.contains("post"))
{
- for (Integer glyphId : this.glyphIds)
- {
- String name = glyphNames[glyphId];
+ return null;
+ }
- Integer macId = WGL4Names.MAC_GLYPH_NAMES_INDICES.get(name);
- if (macId == null)
- {
- Integer idx = additionalNamesIndices.get(name);
- if (idx == null)
- {
- idx = additionalNames.size();
- additionalNames.add(name);
- additionalNamesIndices.put(name,idx);
- }
- writeUint16(dos,idx+258);
- }
- else
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(bos);
+
+ writeFixed(out, 2.0); // version
+ writeFixed(out, post.getItalicAngle());
+ writeSInt16(out, post.getUnderlinePosition());
+ writeSInt16(out, post.getUnderlineThickness());
+ writeUint32(out, post.getIsFixedPitch());
+ writeUint32(out, post.getMinMemType42());
+ writeUint32(out, post.getMaxMemType42());
+ writeUint32(out, post.getMinMemType1());
+ writeUint32(out, post.getMaxMemType1());
+
+ // version 2.0
+
+ // numberOfGlyphs
+ writeUint16(out, glyphIds.size());
+
+ // glyphNameIndex[numGlyphs]
+ Map<String, Integer> names = new TreeMap<String, Integer>();
+ for (int gid : glyphIds)
+ {
+ String name = post.getName(gid);
+ Integer macId = WGL4Names.MAC_GLYPH_NAMES_INDICES.get(name);
+ if (macId != null)
+ {
+ // the name is implicit, as it's from MacRoman
+ writeUint16(out, macId);
+ }
+ else
+ {
+ // the name will be written explicitly
+ Integer ordinal = names.get(name);
+ if (ordinal == null)
{
- writeUint16(dos, macId);
+ ordinal = names.size();
+ names.put(name, ordinal);
}
+ writeUint16(out, 258 + ordinal);
}
}
-
- for (String name : additionalNames)
+
+ // names[numberNewGlyphs]
+ for (String name : names.keySet())
{
- LOG.debug("additionalName=["+name+"].");
- byte[] buf = name.getBytes("US-ASCII");
- writeUint8(dos,buf.length);
- dos.write(buf);
+ byte[] buf = name.getBytes(Charset.forName("US-ASCII"));
+ writeUint8(out, buf.length);
+ out.write(buf);
}
- dos.flush();
- LOG.debug("Finished table [post].");
+
+ out.flush();
return bos.toByteArray();
}
- private byte[] buildHmtxTable() throws IOException
+ private byte[] buildHmtxTable() throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
- LOG.debug("Building table [hmtx]...");
- HorizontalHeaderTable h = this.baseTTF.getHorizontalHeader();
- HorizontalMetricsTable hm = this.baseTTF.getHorizontalMetrics();
+
+ HorizontalHeaderTable h = ttf.getHorizontalHeader();
+ HorizontalMetricsTable hm = ttf.getHorizontalMetrics();
byte [] buf = new byte[4];
- InputStream is = this.baseTTF.getOriginalData();
- try
+ InputStream is = ttf.getOriginalData();
+ try
{
is.skip(hm.getOffset());
long lastOff = 0;
- for (Integer glyphId : this.glyphIds)
+ for (Integer glyphId : glyphIds)
{
- // offset in original file.
+ // offset in original file
long off;
- if (glyphId < h.getNumberOfHMetrics())
+ if (glyphId < h.getNumberOfHMetrics())
{
off = glyphId * 4;
}
- else
+ else
{
off = h.getNumberOfHMetrics() * 4 + (glyphId - h.getNumberOfHMetrics()) * 2;
}
- // skip over from last original offset.
- if (off != lastOff)
+ // skip over from last original offset
+ if (off != lastOff)
{
long nskip = off-lastOff;
- if (nskip != is.skip(nskip))
+ if (nskip != is.skip(nskip))
{
throw new EOFException("Unexpected EOF exception parsing glyphId of hmtx table.");
}
}
- // read left side bearings only, if we are beyond numOfHMetrics.
+ // read left side bearings only, if we are beyond numOfHMetrics
int n = glyphId < h.getNumberOfHMetrics() ? 4 : 2;
- if (n != is.read(buf,0,n))
+ if (n != is.read(buf, 0, n))
{
throw new EOFException("Unexpected EOF exception parsing glyphId of hmtx table.");
}
- bos.write(buf,0,n);
+ bos.write(buf, 0, n);
lastOff = off + n;
}
- LOG.debug("Finished table [hmtx].");
+
return bos.toByteArray();
}
- finally
+ finally
{
is.close();
}
@@ -1045,66 +941,134 @@ public class TTFSubsetter
/**
* Write the subfont to the given output stream.
- *
+ *
* @param os the stream used for writing
* @throws IOException if something went wrong.
*/
- public void writeToStream(OutputStream os) throws IOException
+ public void writeToStream(OutputStream os) throws IOException
{
- LOG.debug("glyphIds=[" + glyphIds + "]");
- LOG.debug("numGlyphs=[" + glyphIds.size() + "]");
- while (!addCompoundReferences())
- {
- }
- DataOutputStream dos = new DataOutputStream(os);
+ addCompoundReferences();
+
+ DataOutputStream out = new DataOutputStream(os);
try
{
- /*
- 'cmap' character to glyph mapping
- 'glyf' glyph data
- 'head' font header
- 'hhea' horizontal header
- 'OS/2' OS/2 compatibility table.
- 'hmtx' horizontal metrics
- 'loca' index to location
- 'maxp' maximum profile
- 'name' naming
- 'post' PostScript
- */
- String[] tableNames = {"OS/2","cmap","glyf","head","hhea","hmtx","loca","maxp","name","post"};
- byte [][] tables = new byte[tableNames.length][];
- long[] newOffsets = new long[this.glyphIds.size()+1];
- tables[3] = this.buildHeadTable();
- tables[4] = this.buildHheaTable();
- tables[7] = this.buildMaxpTable();
- tables[8] = this.buildNameTable();
- tables[0] = this.buildOS2Table();
- tables[2] = this.buildGlyfTable(newOffsets);
- tables[6] = this.buildLocaTable(newOffsets);
- tables[1] = this.buildCmapTable();
- tables[5] = this.buildHmtxTable();
- tables[9] = this.buildPostTable();
- long checksum = writeFileHeader(dos,tableNames.length);
- long offset = 12L + 16L * tableNames.length;
- for (int i=0;i<tableNames.length;++i)
+ long[] newLoca = new long[glyphIds.size() + 1];
+
+ // generate tables in dependency order
+ byte[] head = buildHeadTable();
+ byte[] hhea = buildHheaTable();
+ byte[] maxp = buildMaxpTable();
+ byte[] name = buildNameTable();
+ byte[] os2 = buildOS2Table();
+ byte[] glyf = buildGlyfTable(newLoca);
+ byte[] loca = buildLocaTable(newLoca);
+ byte[] cmap = buildCmapTable();
+ byte[] hmtx = buildHmtxTable();
+ byte[] post = buildPostTable();
+
+ // save to TTF in optimized order
+ Map<String, byte[]> tables = new TreeMap<String, byte[]>();
+ if (os2 != null) { tables.put("OS/2", os2); }
+ if (cmap != null) { tables.put("cmap", cmap); }
+ if (glyf != null) { tables.put("glyf", glyf); }
+ tables.put("head", head);
+ tables.put("hhea", hhea);
+ tables.put("hmtx", hmtx);
+ if (loca != null) { tables.put("loca", loca); }
+ tables.put("maxp", maxp);
+ if (name != null) { tables.put("name", name); }
+ if (post != null) { tables.put("post", post); }
+
+ // copy all other tables
+ for (Map.Entry<String, TTFTable> entry : ttf.getTableMap().entrySet())
{
- checksum += writeTableHeader(dos,tableNames[i],offset,tables[i]);
- offset += ((tables[i].length + 3) / 4) * 4;
+ String tag = entry.getKey();
+ TTFTable table = entry.getValue();
+
+ if (!tables.containsKey(tag) && (keepTables == null || keepTables.contains(tag)))
+ {
+ tables.put(tag, ttf.getTableBytes(table));
+ }
+ }
+
+ // calculate checksum
+ long checksum = writeFileHeader(out, tables.size());
+ long offset = 12L + 16L * tables.size();
+ for (Map.Entry<String, byte[]> entry : tables.entrySet())
+ {
+ checksum += writeTableHeader(out, entry.getKey(), offset, entry.getValue());
+ offset += (entry.getValue().length + 3) / 4 * 4;
}
checksum = 0xB1B0AFBAL - (checksum & 0xffffffffL);
- // correct checksumAdjustment of 'head' table.
- tables[3][8] = (byte)(checksum >>> 24);
- tables[3][9] = (byte)(checksum >>> 16);
- tables[3][10] = (byte)(checksum >>> 8);
- tables[3][11] = (byte)(checksum);
- for (int i=0;i<tableNames.length;++i)
+
+ // update checksumAdjustment in 'head' table
+ head[8] = (byte)(checksum >>> 24);
+ head[9] = (byte)(checksum >>> 16);
+ head[10] = (byte)(checksum >>> 8);
+ head[11] = (byte)checksum;
+ for (byte[] bytes : tables.values())
{
- writeTableBody(dos,tables[i]);
+ writeTableBody(out, bytes);
}
}
finally
{
- dos.close();
+ out.close();
}
}
+
+ private void writeFixed(DataOutputStream out, double f) throws IOException
+ {
+ double ip = Math.floor(f);
+ double fp = (f-ip) * 65536.0;
+ out.writeShort((int)ip);
+ out.writeShort((int)fp);
+ }
+
+ private void writeUint32(DataOutputStream out, long l) throws IOException
+ {
+ out.writeInt((int)l);
+ }
+
+ private void writeUint16(DataOutputStream out, int i) throws IOException
+ {
+ out.writeShort(i);
+ }
+
+ private void writeSInt16(DataOutputStream out, short i) throws IOException
+ {
+ out.writeShort(i);
+ }
+
+ private void writeUint8(DataOutputStream out, int i) throws IOException
+ {
+ out.writeByte(i);
+ }
+
+ private void writeLongDateTime(DataOutputStream out, Calendar calendar) throws IOException
+ {
+ // inverse operation of TTFDataStream.readInternationalDate()
+ GregorianCalendar cal = new GregorianCalendar( 1904, 0, 1 );
+ long millisFor1904 = cal.getTimeInMillis();
+ long secondsSince1904 = (calendar.getTimeInMillis() - millisFor1904) / 1000L;
+ out.writeLong(secondsSince1904);
+ }
+
+ private long toUInt32(int high, int low)
+ {
+ return ((long)high & 0xffffL) << 16 | (long)low & 0xffffL;
+ }
+
+ private long toUInt32(byte[] bytes)
+ {
+ return ((long)bytes[0] & 0xffL) << 24 |
+ ((long)bytes[1] & 0xffL) << 16 |
+ ((long)bytes[2] & 0xffL) << 8 |
+ (long)bytes[3] & 0xffL;
+ }
+
+ private int log2(int num)
+ {
+ return (int)Math.round(Math.log(num) / Math.log(2));
+ }
}
Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java Mon Dec 15 23:54:55 2014
@@ -107,6 +107,23 @@ public class TrueTypeFont implements Typ
{
return tables;
}
+
+ /**
+ * Returns the war bytes of the given table.
+ */
+ public synchronized byte[] getTableBytes(TTFTable table) throws IOException
+ {
+ // save current position
+ long currentPosition = data.getCurrentPosition();
+ data.seek(table.getOffset());
+
+ // read all data
+ byte[] bytes = data.read((int)table.getLength());
+
+ // restore current position
+ data.seek(currentPosition);
+ return bytes;
+ }
/**
* This will get the naming table for the true type font.
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java Mon Dec 15 23:54:55 2014
@@ -25,9 +25,13 @@ import java.io.IOException;
import java.io.OutputStream;
import java.text.NumberFormat;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -42,7 +46,9 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.COSStreamArray;
import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.font.PDCIDFontType2;
import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
@@ -129,8 +135,11 @@ public class PDPageContentStream impleme
// instance variables
private OutputStream output;
private PDResources resources;
+
private boolean inTextMode = false;
private final Stack<PDFont> fontStack = new Stack<PDFont>();
+ private final List<PDFont> fontsToSubset = new ArrayList<PDFont>();
+ private final Map<PDFont, Set<Integer>> subsetCodePoints = new HashMap<PDFont, Set<Integer>>();
private PDColorSpace currentStrokingColorSpace = PDDeviceGray.INSTANCE;
private PDColorSpace currentNonStrokingColorSpace = PDDeviceGray.INSTANCE;
@@ -312,6 +321,19 @@ public class PDPageContentStream impleme
*/
public void setFont(PDFont font, float fontSize) throws IOException
{
+ setFont(font, fontSize, true);
+ }
+
+ /**
+ * Set the font to draw text with.
+ *
+ * @param font The font to use.
+ * @param fontSize The font size to draw the text.
+ * @param embedSubset True to subset this font when embedding it. Affects all uses of this font.
+ * @throws IOException If there is an error writing the font information.
+ */
+ public void setFont(PDFont font, float fontSize, boolean embedSubset) throws IOException
+ {
if (fontStack.isEmpty())
{
fontStack.add(font);
@@ -321,6 +343,16 @@ public class PDPageContentStream impleme
fontStack.setElementAt(font, fontStack.size() - 1);
}
+ if (embedSubset)
+ {
+ fontsToSubset.add(font);
+ subsetCodePoints.put(font, new HashSet<Integer>());
+ }
+ else
+ {
+ fontsToSubset.remove(font);
+ }
+
appendCOSName(resources.add(font));
appendRawCommands(SPACE);
appendRawCommands(fontSize);
@@ -639,6 +671,20 @@ public class PDPageContentStream impleme
}
PDFont font = fontStack.peek();
+
+ // Unicode code points to keep when subsetting
+ Set<Integer> codePoints = subsetCodePoints.get(font);
+ if (codePoints != null)
+ {
+ for (int offset = 0; offset < text.length(); )
+ {
+ int codePoint = text.codePointAt(offset);
+ codePoints.add(codePoint);
+ offset += Character.charCount(codePoint);
+ }
+
+ }
+
COSWriter.writeString(font.encode(text), output);
appendRawCommands(SPACE);
appendRawCommands(SHOW_TEXT);
@@ -1618,6 +1664,17 @@ public class PDPageContentStream impleme
*/
public void close() throws IOException
{
+ for (PDFont font : fontsToSubset)
+ {
+ // currently we only support subsetting Type0/CIDFontType2 fonts
+ if (font instanceof PDType0Font)
+ {
+ if (((PDType0Font)font).getDescendantFont() instanceof PDCIDFontType2)
+ {
+ font.subset(subsetCodePoints.get(font));
+ }
+ }
+ }
output.close();
currentNonStrokingColorSpace = null;
currentStrokingColorSpace = null;
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2.java?rev=1645796&r1=1645795&r2=1645796&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2.java Mon Dec 15 23:54:55 2014
@@ -351,7 +351,7 @@ public class PDCIDFontType2 extends PDCI
if (cmap == null)
{
cmap = cmapTable.getSubtable(CmapTable.PLATFORM_WINDOWS,
- CmapTable.ENCODING_WIN_UNICODE);
+ CmapTable.ENCODING_WIN_UNICODE_BMP);
}
if (cmap == null)
{