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/08/31 04:24:19 UTC
svn commit: r1621554 - in /pdfbox/trunk:
fontbox/src/main/java/org/apache/fontbox/ttf/
pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/
pdfbox/src/main/java/org/apache/pdfbox/rendering/font/
pdfbox/src/test/java/org/apache/pdfbox/rendering/ preflig...
Author: jahewson
Date: Sun Aug 31 02:24:19 2014
New Revision: 1621554
URL: http://svn.apache.org/r1621554
Log:
PDFBOX-2303: Lazy loading of glyphs in TrueType fonts
Added:
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/rendering/TestRegressions.java
Modified:
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeDescript.java
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphTable.java
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/rendering/font/TTFGlyph2D.java
pdfbox/trunk/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/CIDType2Container.java
Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeDescript.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeDescript.java?rev=1621554&r1=1621553&r2=1621554&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeDescript.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeDescript.java Sun Aug 31 02:24:19 2014
@@ -41,7 +41,7 @@ public class GlyfCompositeDescript exten
private static final Log LOG = LogFactory.getLog(GlyfCompositeDescript.class);
private final List<GlyfCompositeComp> components = new ArrayList<GlyfCompositeComp>();
- private GlyphData[] glyphs = null;
+ private GlyphTable glyphTable = null;
private boolean beingResolved = false;
private boolean resolved = false;
@@ -56,7 +56,7 @@ public class GlyfCompositeDescript exten
{
super((short) -1, bais);
- glyphs = glyphTable.getGlyphs();
+ this.glyphTable = glyphTable;
// Get all of the composite components
GlyfCompositeComp comp;
@@ -265,14 +265,19 @@ public class GlyfCompositeDescript exten
private GlyphDescription getGlypDescription(int index)
{
- if (glyphs != null && index < glyphs.length)
+ try
{
- GlyphData glyph = glyphs[index];
+ GlyphData glyph = glyphTable.getGlyph(index);
if (glyph != null)
{
return glyph.getDescription();
}
+ return null;
+ }
+ catch (IOException e)
+ {
+ LOG.error(e);
+ return null;
}
- return null;
}
}
Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphTable.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphTable.java?rev=1621554&r1=1621553&r2=1621554&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphTable.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphTable.java Sun Aug 31 02:24:19 2014
@@ -17,12 +17,13 @@
package org.apache.fontbox.ttf;
import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
/**
* A table in a true type font.
*
- * @author Ben Litchfield (ben@benlitchfield.com)
- *
+ * @author Ben Litchfield
*/
public class GlyphTable extends TTFTable
{
@@ -33,6 +34,12 @@ public class GlyphTable extends TTFTable
private GlyphData[] glyphs;
+ // lazy table reading
+ private TTFDataStream data;
+ private IndexToLocationTable loca;
+ private int numGlyphs;
+ protected Map<Integer, GlyphData> cache = new ConcurrentHashMap<Integer, GlyphData>();
+
/**
* This will read the required data from the stream.
*
@@ -42,23 +49,34 @@ public class GlyphTable extends TTFTable
*/
public void read(TrueTypeFont ttf, TTFDataStream data) throws IOException
{
- IndexToLocationTable loc = ttf.getIndexToLocation();
+ loca = ttf.getIndexToLocation();
+ numGlyphs = ttf.getNumberOfGlyphs();
+
+ // we don't actually read the table yet because it can contain tens of thousands of glyphs
+ this.data = data;
+ initialized = true;
+ }
+
+ /**
+ * Reads all glyphs from the font. Can be very slow.
+ */
+ private void readAll() throws IOException
+ {
// the glyph offsets
- long[] offsets = loc.getOffsets();
- // number of glyphs
- int numGlyphs = ttf.getNumberOfGlyphs();
+ long[] offsets = loca.getOffsets();
+
// the end of the glyph table
// should not be 0, but sometimes is, see PDFBOX-2044
// structure of this table: see
// https://developer.apple.com/fonts/TTRefMan/RM06/Chap6loca.html
- long endOfGlyphs = offsets[numGlyphs];
+ long endOfGlyphs = offsets[numGlyphs];
long offset = getOffset();
glyphs = new GlyphData[numGlyphs];
for (int i = 0; i < numGlyphs; i++)
{
// end of glyphs reached?
if (endOfGlyphs != 0 &&
- endOfGlyphs == offsets[i])
+ endOfGlyphs == offsets[i])
{
break;
}
@@ -85,10 +103,14 @@ public class GlyphTable extends TTFTable
}
/**
- * @return Returns the glyphs.
+ * Returns all glyphs. This method can be very slow.
*/
- public GlyphData[] getGlyphs()
+ public synchronized GlyphData[] getGlyphs() throws IOException
{
+ if (glyphs == null)
+ {
+ readAll();
+ }
return glyphs;
}
@@ -99,4 +121,55 @@ public class GlyphTable extends TTFTable
{
glyphs = glyphsValue;
}
+
+ /**
+ * Returns the data for the glyph with the given GID.
+ *
+ * @param gid GID
+ * @throws IOException if the font cannot be read
+ */
+ public GlyphData getGlyph(int gid) throws IOException
+ {
+ if (gid < 0 || gid >= numGlyphs)
+ {
+ return null;
+ }
+
+ if (cache.containsKey(gid))
+ {
+ return cache.get(gid);
+ }
+
+ synchronized (this)
+ {
+ // save
+ long currentPosition = data.getCurrentPosition();
+
+ // read a single glyph
+ long[] offsets = loca.getOffsets();
+
+ GlyphData glyph;
+ if (offsets[gid] == offsets[gid + 1])
+ {
+ // no outline
+ glyph = null;
+ }
+ else
+ {
+ data.seek(getOffset() + offsets[gid]);
+ glyph = new GlyphData();
+ glyph.initData(this, data);
+
+ // resolve composite glyph
+ if (glyph.getDescription().isComposite())
+ {
+ glyph.getDescription().resolve();
+ }
+ }
+
+ // restore
+ data.seek(currentPosition);
+ return glyph;
+ }
+ }
}
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=1621554&r1=1621553&r2=1621554&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 Sun Aug 31 02:24:19 2014
@@ -406,9 +406,8 @@ public class TrueTypeFont implements Typ
{
readPostScriptNames();
- GlyphData[] glyphs = getGlyph().getGlyphs();
Integer gid = postScriptNames.get(name);
- if (gid == null || gid < 0 || gid >= glyphs.length)
+ if (gid == null || gid < 0 || gid >= getMaximumProfile().getNumGlyphs())
{
return 0;
}
@@ -420,21 +419,21 @@ public class TrueTypeFont implements Typ
{
readPostScriptNames();
- GlyphData[] glyphs = getGlyph().getGlyphs();
Integer gid = postScriptNames.get(name);
- if (gid == null || gid < 0 || gid >= glyphs.length)
+ if (gid == null || gid < 0 || gid >= getMaximumProfile().getNumGlyphs())
{
gid = 0;
}
// some glyphs have no outlines (e.g. space, table, newline)
- if (glyphs[gid] == null)
+ GlyphData glyph = getGlyph().getGlyph(gid);
+ if (glyph == null)
{
return new GeneralPath();
}
else
{
- GeneralPath path = glyphs[gid].getPath();
+ GeneralPath path = glyph.getPath();
// scale to 1000upem, per PostScript convention
float scale = 1000f / getUnitsPerEm();
@@ -466,8 +465,7 @@ public class TrueTypeFont implements Typ
readPostScriptNames();
Integer gid = postScriptNames.get(name);
- GlyphData[] glyphs = getGlyph().getGlyphs();
- return !(gid == null || gid < 0 || gid >= glyphs.length);
+ return !(gid == null || gid < 0 || gid >= getMaximumProfile().getNumGlyphs());
}
@Override
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java?rev=1621554&r1=1621553&r2=1621554&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java Sun Aug 31 02:24:19 2014
@@ -190,7 +190,7 @@ public class PDTrueTypeFont extends PDSi
public float getHeight(int code) throws IOException
{
int gid = codeToGID(code);
- GlyphData glyph = ttf.getGlyph().getGlyphs()[gid];
+ GlyphData glyph = ttf.getGlyph().getGlyph(gid);
if (glyph != null)
{
return glyph.getBoundingBox().getHeight();
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/rendering/font/TTFGlyph2D.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/rendering/font/TTFGlyph2D.java?rev=1621554&r1=1621553&r2=1621554&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/rendering/font/TTFGlyph2D.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/rendering/font/TTFGlyph2D.java Sun Aug 31 02:24:19 2014
@@ -139,14 +139,14 @@ public class TTFGlyph2D implements Glyph
}
else
{
- GlyphData[] glyphData = ttf.getGlyph().getGlyphs();
- if (gid >= glyphData.length)
+ GlyphData glyph = ttf.getGlyph().getGlyph(gid);
+ if (gid >= ttf.getMaximumProfile().getNumGlyphs())
{
LOG.warn(font.getName() + ": Glyph not found: " + gid);
glyphPath = new GeneralPath();
glyphs.put(gid, glyphPath);
}
- else if (glyphData[gid] == null)
+ else if (glyph == null)
{
// empty glyph (e.g. space, newline)
glyphPath = new GeneralPath();
@@ -154,7 +154,6 @@ public class TTFGlyph2D implements Glyph
}
else
{
- GlyphData glyph = glyphData[gid];
glyphPath = glyph.getPath();
if (hasScaling)
{
Added: pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/rendering/TestRegressions.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/rendering/TestRegressions.java?rev=1621554&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/rendering/TestRegressions.java (added)
+++ pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/rendering/TestRegressions.java Sun Aug 31 02:24:19 2014
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.rendering;
+
+import org.apache.pdfbox.ParallelParameterized;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameters;
+
+import javax.imageio.ImageIO;
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.color.ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorConvertOp;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * todo: JavaDoc
+ *
+ * @author John Hewson
+ */
+@RunWith(ParallelParameterized.class) // todo: disable this test by default? (for now?)
+//@Ignore
+public class TestRegressions
+{
+ private static final String PDF_DIR = "../../regression/pdf";
+ private static final String PNG_DIR = "../../regression/png";
+ private static final String OUT_DIR = "../../regression/out";
+ private static final String DIFF_DIR = "../../regression/diff";
+
+ @Parameters(name = "{0}")
+ public static Collection<Object[]> data()
+ {
+ // todo: sanity check for compatible JDK?
+
+ // populate the input parameters
+ File[] files = new File(PDF_DIR).listFiles();
+ List<Object[]> params = new ArrayList<Object[]>();
+ if (files != null)
+ {
+ for (File file : files)
+ {
+ if (file.getName().endsWith(".pdf"))
+ {
+ params.add(new Object[] { file.getName() });
+ }
+ }
+ }
+ return params;
+ }
+
+ private final String filename;
+ private File inDir, outDir, diffDir;
+
+ public TestRegressions(String fileName)
+ {
+ this.filename = fileName;
+ }
+
+ @Before
+ public void init()
+ {
+ File file = new File(PDF_DIR, filename);
+
+ // create output dir
+ String dirName = file.getName().substring(0, file.getName().lastIndexOf('.'));
+ outDir = new File(OUT_DIR, dirName);
+ outDir.mkdirs();
+
+ // input png dirs
+ inDir = new File(PNG_DIR, dirName);
+
+ // diff dir (if any)
+ diffDir = new File(DIFF_DIR, dirName);
+ if (diffDir.exists())
+ {
+ // clean the diff dir
+ for(File png: diffDir.listFiles())
+ {
+ png.delete();
+ }
+ diffDir.delete();
+ }
+
+ // clean the output dir
+ for(File png: outDir.listFiles())
+ {
+ png.delete();
+ }
+ }
+
+ @Test
+ public void render() throws IOException
+ {
+ File file = new File(PDF_DIR, filename);
+ PDDocument document = PDDocument.load(file);
+ try
+ {
+ boolean noPNG = false;
+ boolean isDifferent = false;
+
+ PDFRenderer renderer = new PDFRenderer(document);
+ for (int i = 0, size = document.getNumberOfPages(); i < size; i++)
+ {
+ BufferedImage imageTest = renderer.renderImageWithDPI(i, 72);
+
+ // write to output file
+ File outFile = new File(outDir, (i+1) + ".png");
+ ImageIO.write(imageTest, "PNG", outFile);
+
+ // load expected png
+ File inFile = new File(inDir, (i+1) + ".png");
+ if (inFile.exists())
+ {
+ BufferedImage imageGood = ImageIO.read(inFile);
+
+ // compare
+ compare: for (int y = 0; y < imageGood.getHeight(); y++)
+ {
+ for (int x = 0; x < imageGood.getWidth(); x++)
+ {
+ if (imageGood.getRGB(x, y) != imageTest.getRGB(x, y))
+ {
+ // save diff to file
+ BufferedImage imageDiff = diff(imageGood, imageTest);
+ diffDir.mkdirs();
+ File diffFile = new File(diffDir, (i+1) + ".png");
+ ImageIO.write(imageDiff, "PNG", diffFile);
+
+ // keep rendering all pages
+ isDifferent = true;
+ break compare;
+ }
+ }
+ }
+ }
+ else
+ {
+ // no expected png, probably the first time this file has been used, so we
+ // continue to render all pages, but we'll fail the test at the end
+ noPNG = true;
+ }
+ }
+
+ if (noPNG)
+ {
+ // if this is the first time that this PDF file has been rendered then the test
+ // will fail, but the "out" directory will contain the result. If it is good then
+ // it can be manually added to the "png" directory and committed to SVN
+ Assert.fail("No PNG found for '" + file.getName() +
+ "', perhaps this is a new file?");
+ }
+ else if (isDifferent)
+ {
+ // fail after all pages have been rendered
+ Assert.fail("Rendering differs in '" + file.getName() + "'");
+ }
+ }
+ finally
+ {
+ document.close();
+ }
+ }
+
+ private BufferedImage diff(BufferedImage imageGood, BufferedImage imageTest)
+ {
+ BufferedImage diff = new BufferedImage(imageGood.getWidth(), imageGood.getHeight(),
+ BufferedImage.TYPE_INT_RGB);
+
+ // convert good image to grayscale
+ ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
+ ColorConvertOp op = new ColorConvertOp(cs, null);
+ BufferedImage imageGray = op.filter(imageGood, null);
+
+ // draw good image as grayscale background
+ Graphics2D graphics = diff.createGraphics();
+ graphics.setBackground(Color.WHITE);
+ graphics.clearRect(0, 0, imageGood.getWidth(), imageGood.getHeight());
+ graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.25f));
+ graphics.drawImage(imageGray, null, null);
+ graphics.dispose();
+
+ // draw differing pixels in red
+ for (int y = 0; y < imageGood.getHeight(); y++)
+ {
+ for (int x = 0; x < imageGood.getWidth(); x++)
+ {
+ if (imageGood.getRGB(x, y) != imageTest.getRGB(x, y))
+ {
+ int rgb = new Color(255, 0, 0).getRGB();
+ diff.setRGB(x, y, rgb);
+ }
+ }
+ }
+ return diff;
+ }
+}
Modified: pdfbox/trunk/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/CIDType2Container.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/CIDType2Container.java?rev=1621554&r1=1621553&r2=1621554&view=diff
==============================================================================
--- pdfbox/trunk/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/CIDType2Container.java (original)
+++ pdfbox/trunk/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/CIDType2Container.java Sun Aug 31 02:24:19 2014
@@ -49,7 +49,7 @@ public class CIDType2Container extends F
try
{
// if glyph exists we can check the width
- if (this.ttf != null && this.ttf.getGlyph().getGlyphs().length > glyphIndex)
+ if (this.ttf != null && this.ttf.getMaximumProfile().getNumGlyphs() > glyphIndex)
{
/*
* In a Mono space font program, the length of the AdvanceWidth array must be one. According to the TrueType