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/17 19:32:26 UTC

svn commit: r1824629 - in /pdfbox/branches/2.0/pdfbox/src: main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java test/java/org/apache/pdfbox/pdmodel/font/TestFontEmbedding.java

Author: tilman
Date: Sat Feb 17 19:32:26 2018
New Revision: 1824629

URL: http://svn.apache.org/viewvc?rev=1824629&view=rev
Log:
PDFBOX-4106: Correct vmtx embedding for proportional fonts and add test for it, by Aaron Madlon-Kay

Modified:
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java
    pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/font/TestFontEmbedding.java

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java?rev=1824629&r1=1824628&r2=1824629&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java Sat Feb 17 19:32:26 2018
@@ -30,8 +30,12 @@ import java.util.TreeSet;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.ttf.GlyphData;
+import org.apache.fontbox.ttf.GlyphTable;
+import org.apache.fontbox.ttf.HorizontalMetricsTable;
 import org.apache.fontbox.ttf.TrueTypeFont;
 import org.apache.fontbox.ttf.VerticalHeaderTable;
+import org.apache.fontbox.ttf.VerticalMetricsTable;
 import org.apache.pdfbox.cos.COSArray;
 import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSInteger;
@@ -349,7 +353,13 @@ final class PDCIDFontType2Embedder exten
 
         float scaling = 1000f / ttf.getHeader().getUnitsPerEm();
 
-        long w1 = Math.round(-ttf.getVerticalHeader().getAdvanceHeightMax() * scaling);
+        VerticalHeaderTable vhea = ttf.getVerticalHeader();
+        VerticalMetricsTable vmtx = ttf.getVerticalMetrics();
+        GlyphTable glyf = ttf.getGlyph();
+        HorizontalMetricsTable hmtx = ttf.getHorizontalMetrics();
+
+        long v_y = Math.round(vhea.getAscender() * scaling);
+        long w1 = Math.round(-vhea.getAdvanceHeightMax() * scaling);
 
         COSArray heights = new COSArray();
         COSArray w2 = new COSArray();
@@ -360,10 +370,16 @@ final class PDCIDFontType2Embedder exten
         {
             // 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)
+            GlyphData glyph = glyf.getGlyph(cid);
+            if (glyph == null)
+            {
+                continue;
+            }
+            long height = Math.round((glyph.getYMaximum() + vmtx.getTopSideBearing(cid)) * scaling);
+            long advance = Math.round(-vmtx.getAdvanceHeight(cid) * scaling);
+            if (height == v_y && advance == w1)
             {
-                // skip default height
+                // skip default metrics
                 continue;
             }
             // c [w1_1y v_1x v_1y w1_2y v_2x v_2y ... w1_ny v_nx v_ny]
@@ -373,10 +389,10 @@ final class PDCIDFontType2Embedder exten
                 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(advance)); // w1_iy
+            long width = Math.round(hmtx.getAdvanceWidth(cid) * scaling);
             w2.add(COSInteger.get(width / 2)); // v_ix
-            w2.add(COSInteger.get(w1)); // v_iy
+            w2.add(COSInteger.get(height)); // v_iy
             prev = cid;
         }
         cidFont.setItem(COSName.W2, heights);
@@ -510,12 +526,18 @@ final class PDCIDFontType2Embedder exten
         }
 
         int cidMax = ttf.getNumberOfGlyphs();
-        int[] gidMetrics = new int[cidMax * 3];
+        int[] gidMetrics = new int[cidMax * 4];
         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);
+            GlyphData glyph = ttf.getGlyph().getGlyph(cid);
+            if (glyph == null) {
+                gidMetrics[cid * 4] = Integer.MIN_VALUE;
+            } else {
+                gidMetrics[cid * 4] = cid;
+                gidMetrics[cid * 4 + 1] = ttf.getVerticalMetrics().getAdvanceHeight(cid);
+                gidMetrics[cid * 4 + 2] = ttf.getHorizontalMetrics().getAdvanceWidth(cid);
+                gidMetrics[cid * 4 + 3] = glyph.getYMaximum() + ttf.getVerticalMetrics().getTopSideBearing(cid);
+            }
         }
 
         cidFont.setItem(COSName.W2, getVerticalMetrics(gidMetrics));
@@ -530,11 +552,10 @@ final class PDCIDFontType2Embedder exten
 
         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);
+        long lastVxValue = Math.round(values[2] * scaling / 2f);
+        long lastVyValue = Math.round(values[3] * scaling);
 
         COSArray inner = new COSArray();
         COSArray outer = new COSArray();
@@ -542,16 +563,22 @@ final class PDCIDFontType2Embedder exten
 
         State state = State.FIRST;
 
-        for (int i = 3; i < values.length; i += 3)
+        for (int i = 4; i < values.length; i += 4)
         {
             long cid = values[i];
+            if (cid == Integer.MIN_VALUE)
+            {
+                // no glyph for this cid
+                continue;
+            }
             long w1Value = Math.round(-values[i + 1] * scaling);
             long vxValue = Math.round(values[i + 2] * scaling / 2);
+            long vyValue = Math.round(values[i + 3] * scaling);
 
             switch (state)
             {
             case FIRST:
-                if (cid == lastCid + 1 && w1Value == lastW1Value && vxValue == lastVxValue)
+                if (cid == lastCid + 1 && w1Value == lastW1Value && vxValue == lastVxValue && vyValue == lastVyValue)
                 {
                     state = State.SERIAL;
                 }
@@ -561,20 +588,20 @@ final class PDCIDFontType2Embedder exten
                     inner = new COSArray();
                     inner.add(COSInteger.get(lastW1Value));
                     inner.add(COSInteger.get(lastVxValue));
-                    inner.add(COSInteger.get(v_y));
+                    inner.add(COSInteger.get(lastVyValue));
                 }
                 else
                 {
                     inner = new COSArray();
                     inner.add(COSInteger.get(lastW1Value));
                     inner.add(COSInteger.get(lastVxValue));
-                    inner.add(COSInteger.get(v_y));
+                    inner.add(COSInteger.get(lastVyValue));
                     outer.add(inner);
                     outer.add(COSInteger.get(cid));
                 }
                 break;
             case BRACKET:
-                if (cid == lastCid + 1 && w1Value == lastW1Value && vxValue == lastVxValue)
+                if (cid == lastCid + 1 && w1Value == lastW1Value && vxValue == lastVxValue && vyValue == lastVyValue)
                 {
                     state = State.SERIAL;
                     outer.add(inner);
@@ -584,25 +611,25 @@ final class PDCIDFontType2Embedder exten
                 {
                     inner.add(COSInteger.get(lastW1Value));
                     inner.add(COSInteger.get(lastVxValue));
-                    inner.add(COSInteger.get(v_y));
+                    inner.add(COSInteger.get(lastVyValue));
                 }
                 else
                 {
                     state = State.FIRST;
                     inner.add(COSInteger.get(lastW1Value));
                     inner.add(COSInteger.get(lastVxValue));
-                    inner.add(COSInteger.get(v_y));
+                    inner.add(COSInteger.get(lastVyValue));
                     outer.add(inner);
                     outer.add(COSInteger.get(cid));
                 }
                 break;
             case SERIAL:
-                if (cid != lastCid + 1 || w1Value != lastW1Value || vxValue != lastVxValue)
+                if (cid != lastCid + 1 || w1Value != lastW1Value || vxValue != lastVxValue || vyValue != lastVyValue)
                 {
                     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(lastVyValue));
                     outer.add(COSInteger.get(cid));
                     state = State.FIRST;
                 }
@@ -610,6 +637,7 @@ final class PDCIDFontType2Embedder exten
             }
             lastW1Value = w1Value;
             lastVxValue = vxValue;
+            lastVyValue = vyValue;
             lastCid = cid;
         }
 
@@ -619,20 +647,20 @@ final class PDCIDFontType2Embedder exten
             inner = new COSArray();
             inner.add(COSInteger.get(lastW1Value));
             inner.add(COSInteger.get(lastVxValue));
-            inner.add(COSInteger.get(v_y));
+            inner.add(COSInteger.get(lastVyValue));
             outer.add(inner);
             break;
         case BRACKET:
             inner.add(COSInteger.get(lastW1Value));
             inner.add(COSInteger.get(lastVxValue));
-            inner.add(COSInteger.get(v_y));
+            inner.add(COSInteger.get(lastVyValue));
             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));
+            outer.add(COSInteger.get(lastVyValue));
             break;
         }
         return outer;

Modified: pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/font/TestFontEmbedding.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/font/TestFontEmbedding.java?rev=1824629&r1=1824628&r2=1824629&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/font/TestFontEmbedding.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/font/TestFontEmbedding.java Sat Feb 17 19:32:26 2018
@@ -113,6 +113,63 @@ public class TestFontEmbedding extends T
         assertEquals(expectedExtractedtext, extracted.replaceAll("\r", "").trim());
     }
 
+    /**
+     * Embed a proportional TTF as vertical CIDFontType2 with subsetting.
+     *
+     * @throws IOException
+     */
+    public void testCIDFontType2VerticalSubsetProportional() throws IOException
+    {
+        String text = "「ABC」";
+        String expectedExtractedtext = "「\nA\nB\nC\n」";
+        File pdf = new File(OUT_DIR, "CIDFontType2VP.pdf");
+
+        PDDocument document = new PDDocument();
+        
+        PDPage page = new PDPage(PDRectangle.A4);
+        document.addPage(page);
+        File ipafont = new File("target/fonts/ipagp00303", "ipagp.ttf");
+        PDType0Font vfont = PDType0Font.loadVertical(document, ipafont);
+        PDPageContentStream contentStream = new PDPageContentStream(document, page);
+
+        contentStream.beginText();
+        contentStream.setFont(vfont, 20);
+        contentStream.newLineAtOffset(50, 700);
+        contentStream.showText(text);
+        contentStream.endText();
+        contentStream.close();
+
+        // Check the font substitution
+        byte[] encode = vfont.encode(text);
+        int cid = ((encode[0] & 0xFF) << 8) + (encode[1] & 0xFF);
+        assertEquals(12607, cid); // it's 12461 without substitution
+        // Check the dictionaries
+        COSDictionary fontDict = vfont.getCOSObject();
+        assertEquals(COSName.IDENTITY_V, fontDict.getDictionaryObject(COSName.ENCODING));
+
+        document.save(pdf);
+
+        // Vertical metrics are fixed during subsetting, so do this after calling save()
+        COSDictionary descFontDict = vfont.getDescendantFont().getCOSObject();
+        COSArray dw2 = (COSArray) descFontDict.getDictionaryObject(COSName.DW2);
+        assertNull(dw2); // This font uses default values for DW2
+        // c [ w1_1y v_1x v_1y ... w1_ny v_nx v_ny ]
+        COSArray w2 = (COSArray) descFontDict.getDictionaryObject(COSName.W2);
+        assertEquals(2, w2.size());
+        assertEquals(12607, w2.getInt(0)); // Start CID
+        COSArray metrics = (COSArray) w2.getObject(1);
+        int i = 0;
+        for (int n : new int[] {-570, 500, 450, -570, 500, 880})
+        {
+            assertEquals(n, metrics.getInt(i++));
+        }
+        document.close();
+
+        // Check text extraction
+        String extracted = getUnicodeText(pdf);
+        assertEquals(expectedExtractedtext, extracted.replaceAll("\r", "").trim());
+    }
+
     private void validateCIDFontType2(boolean useSubset) throws Exception
     {
         PDDocument document = new PDDocument();