You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2018/02/15 21:57:34 UTC
svn commit: r1824376 -
/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java
Author: tilman
Date: Thu Feb 15 21:57:34 2018
New Revision: 1824376
URL: http://svn.apache.org/viewvc?rev=1824376&view=rev
Log:
PDFBOX-4106: implement vertical metrics support when embedding/subsetting, by Aaron Madlon-Kay
Modified:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java?rev=1824376&r1=1824375&r2=1824376&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java Thu Feb 15 21:57:34 2018
@@ -28,7 +28,10 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.apache.fontbox.ttf.TrueTypeFont;
+import org.apache.fontbox.ttf.VerticalHeaderTable;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSInteger;
@@ -45,10 +48,14 @@ import org.apache.pdfbox.pdmodel.common.
*/
final class PDCIDFontType2Embedder extends TrueTypeEmbedder
{
+
+ private static final Log LOG = LogFactory.getLog(PDCIDFontType2Embedder.class);
+
private final PDDocument document;
private final PDType0Font parent;
private final COSDictionary dict;
private final COSDictionary cidFont;
+ private final boolean vertical;
/**
* Creates a new TrueType font embedder for the given TTF as a PDCIDFontType2.
@@ -66,6 +73,7 @@ final class PDCIDFontType2Embedder exten
this.document = document;
this.dict = dict;
this.parent = parent;
+ this.vertical = vertical;
// parent Type 0 font
dict.setItem(COSName.SUBTYPE, COSName.TYPE0);
@@ -102,6 +110,11 @@ final class PDCIDFontType2Embedder exten
}
// build unicode mapping before subsetting as the subsetted font won't have a cmap
buildToUnicodeCMap(gidToCid);
+ // build vertical metrics before subsetting as the subsetted font won't have vhea, vmtx
+ if (vertical)
+ {
+ buildVerticalMetrics(cidToGid);
+ }
// rebuild the relevant part of the font
buildFontFile2(ttfSubset);
addNameTag(tag);
@@ -197,6 +210,12 @@ final class PDCIDFontType2Embedder exten
// W - widths
buildWidths(cidFont);
+ // Vertical metrics
+ if (vertical)
+ {
+ buildVerticalMetrics(cidFont);
+ }
+
// CIDToGIDMap
cidFont.setItem(COSName.CID_TO_GID_MAP, COSName.IDENTITY);
@@ -291,6 +310,78 @@ final class PDCIDFontType2Embedder exten
cidFont.setItem(COSName.W, widths);
}
+ private boolean buildVerticalHeader(COSDictionary cidFont) throws IOException
+ {
+ VerticalHeaderTable vhea = ttf.getVerticalHeader();
+ if (vhea == null)
+ {
+ LOG.warn("Font to be subset is set to vertical, but has no 'vhea' table");
+ return false;
+ }
+
+ float scaling = 1000f / ttf.getHeader().getUnitsPerEm();
+
+ long v = Math.round(vhea.getAscender() * scaling);
+ long w1 = Math.round(-vhea.getAdvanceHeightMax() * scaling);
+ if (v != 880 || w1 != -1000)
+ {
+ COSArray cosDw2 = new COSArray();
+ cosDw2.add(COSInteger.get(v));
+ cosDw2.add(COSInteger.get(w1));
+ cidFont.setItem(COSName.DW2, cosDw2);
+ }
+ return true;
+ }
+
+ /**
+ * Builds vertical metrics with a custom CIDToGIDMap (for embedding font subset).
+ */
+ private void buildVerticalMetrics(Map<Integer, Integer> cidToGid) throws IOException
+ {
+ // The "vhea" and "vmtx" tables that specify vertical metrics shall never be used by a conforming
+ // reader. The only way to specify vertical metrics in PDF shall be by means of the DW2 and W2
+ // entries in a CIDFont dictionary.
+
+ if (!buildVerticalHeader(cidFont))
+ {
+ return;
+ }
+
+ float scaling = 1000f / ttf.getHeader().getUnitsPerEm();
+
+ long w1 = Math.round(-ttf.getVerticalHeader().getAdvanceHeightMax() * scaling);
+
+ COSArray heights = new COSArray();
+ COSArray w2 = new COSArray();
+ int prev = Integer.MIN_VALUE;
+ // Use a sorted list to get an optimal width array
+ Set<Integer> keys = new TreeSet<>(cidToGid.keySet());
+ for (int cid : keys)
+ {
+ // Unlike buildWidths, we look up with cid (not gid) here because this is
+ // the original TTF, not the rebuilt one.
+ long height = Math.round(-ttf.getVerticalMetrics().getAdvanceHeight(cid) * scaling);
+ if (height == w1)
+ {
+ // skip default height
+ continue;
+ }
+ // c [w1_1y v_1x v_1y w1_2y v_2x v_2y ... w1_ny v_nx v_ny]
+ if (prev != cid - 1)
+ {
+ w2 = new COSArray();
+ heights.add(COSInteger.get(cid)); // c
+ heights.add(w2);
+ }
+ w2.add(COSInteger.get(height)); // w1_iy
+ long width = Math.round(ttf.getHorizontalMetrics().getAdvanceWidth(cid) * scaling);
+ w2.add(COSInteger.get(width / 2)); // v_ix
+ w2.add(COSInteger.get(w1)); // v_iy
+ prev = cid;
+ }
+ cidFont.setItem(COSName.W2, heights);
+ }
+
/**
* Build widths with Identity CIDToGIDMap (for embedding full font).
*/
@@ -407,6 +498,157 @@ final class PDCIDFontType2Embedder exten
}
return outer;
}
+
+ /**
+ * Build vertical metrics with Identity CIDToGIDMap (for embedding full font).
+ */
+ private void buildVerticalMetrics(COSDictionary cidFont) throws IOException
+ {
+ if (!buildVerticalHeader(cidFont))
+ {
+ return;
+ }
+
+ float scaling = 1000f / ttf.getHeader().getUnitsPerEm();
+
+ long v = Math.round(ttf.getVerticalHeader().getAscender() * scaling);
+ long w1 = Math.round(-ttf.getVerticalHeader().getAdvanceHeightMax() * scaling);
+ if (v != 880 || w1 != -1000)
+ {
+ COSArray cosDw2 = new COSArray();
+ cosDw2.add(COSInteger.get(v));
+ cosDw2.add(COSInteger.get(w1));
+ cidFont.setItem(COSName.DW2, cosDw2);
+ }
+
+ int cidMax = ttf.getNumberOfGlyphs();
+ int[] gidMetrics = new int[cidMax * 3];
+ for (int cid = 0; cid < cidMax; cid++)
+ {
+ gidMetrics[cid * 3] = cid;
+ gidMetrics[cid * 3 + 1] = ttf.getVerticalMetrics().getAdvanceHeight(cid);
+ gidMetrics[cid * 3 + 2] = ttf.getHorizontalMetrics().getAdvanceWidth(cid);
+ }
+
+ cidFont.setItem(COSName.W2, getVerticalMetrics(gidMetrics));
+ }
+
+ private COSArray getVerticalMetrics(int[] values) throws IOException
+ {
+ if (values.length == 0)
+ {
+ throw new IllegalArgumentException("length of values must be > 0");
+ }
+
+ float scaling = 1000f / ttf.getHeader().getUnitsPerEm();
+
+ long v_y = Math.round(ttf.getVerticalHeader().getAscender() * scaling);
+
+ long lastCid = values[0];
+ long lastW1Value = Math.round(-values[1] * scaling);
+ long lastVxValue = Math.round(values[2] * scaling / 2);
+
+ COSArray inner = null;
+ COSArray outer = new COSArray();
+ outer.add(COSInteger.get(lastCid));
+
+ State state = State.FIRST;
+
+ for (int i = 3; i < values.length; i += 3)
+ {
+ long cid = values[i];
+ long w1Value = Math.round(-values[i + 1] * scaling);
+ long vxValue = Math.round(values[i + 2] * scaling / 2);
+
+ switch (state)
+ {
+ case FIRST:
+ if (cid == lastCid + 1 && w1Value == lastW1Value && vxValue == lastVxValue)
+ {
+ state = State.SERIAL;
+ }
+ else if (cid == lastCid + 1)
+ {
+ state = State.BRACKET;
+ inner = new COSArray();
+ inner.add(COSInteger.get(lastW1Value));
+ inner.add(COSInteger.get(lastVxValue));
+ inner.add(COSInteger.get(v_y));
+ }
+ else
+ {
+ inner = new COSArray();
+ inner.add(COSInteger.get(lastW1Value));
+ inner.add(COSInteger.get(lastVxValue));
+ inner.add(COSInteger.get(v_y));
+ outer.add(inner);
+ outer.add(COSInteger.get(cid));
+ }
+ break;
+ case BRACKET:
+ if (cid == lastCid + 1 && w1Value == lastW1Value && vxValue == lastVxValue)
+ {
+ state = State.SERIAL;
+ outer.add(inner);
+ outer.add(COSInteger.get(lastCid));
+ }
+ else if (cid == lastCid + 1)
+ {
+ inner.add(COSInteger.get(lastW1Value));
+ inner.add(COSInteger.get(lastVxValue));
+ inner.add(COSInteger.get(v_y));
+ }
+ else
+ {
+ state = State.FIRST;
+ inner.add(COSInteger.get(lastW1Value));
+ inner.add(COSInteger.get(lastVxValue));
+ inner.add(COSInteger.get(v_y));
+ outer.add(inner);
+ outer.add(COSInteger.get(cid));
+ }
+ break;
+ case SERIAL:
+ if (cid != lastCid + 1 || w1Value != lastW1Value || vxValue != lastVxValue)
+ {
+ outer.add(COSInteger.get(lastCid));
+ outer.add(COSInteger.get(lastW1Value));
+ outer.add(COSInteger.get(lastVxValue));
+ outer.add(COSInteger.get(v_y));
+ outer.add(COSInteger.get(cid));
+ state = State.FIRST;
+ }
+ break;
+ }
+ lastW1Value = w1Value;
+ lastVxValue = vxValue;
+ lastCid = cid;
+ }
+
+ switch (state)
+ {
+ case FIRST:
+ inner = new COSArray();
+ inner.add(COSInteger.get(lastW1Value));
+ inner.add(COSInteger.get(lastVxValue));
+ inner.add(COSInteger.get(v_y));
+ outer.add(inner);
+ break;
+ case BRACKET:
+ inner.add(COSInteger.get(lastW1Value));
+ inner.add(COSInteger.get(lastVxValue));
+ inner.add(COSInteger.get(v_y));
+ outer.add(inner);
+ break;
+ case SERIAL:
+ outer.add(COSInteger.get(lastCid));
+ outer.add(COSInteger.get(lastW1Value));
+ outer.add(COSInteger.get(lastVxValue));
+ outer.add(COSInteger.get(v_y));
+ break;
+ }
+ return outer;
+ }
/**
* Returns the descendant CIDFont.