From 77e0508bb36a6c5e547857f2bea17e1def2a2abc Mon Sep 17 00:00:00 2001 From: Simon Steiner Date: Tue, 24 Feb 2015 11:23:44 +0000 Subject: [PATCH] FOP-2445: Merge PDF Linearization branch git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1661887 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/fop/pdf/AbstractPDFStream.java | 25 +- src/java/org/apache/fop/pdf/BitmapImage.java | 4 +- .../org/apache/fop/pdf/CompressedObject.java | 2 +- src/java/org/apache/fop/pdf/PDFAnnotList.java | 6 + src/java/org/apache/fop/pdf/PDFArray.java | 12 + src/java/org/apache/fop/pdf/PDFCIDFont.java | 16 + .../org/apache/fop/pdf/PDFDictionary.java | 30 +- src/java/org/apache/fop/pdf/PDFDocument.java | 101 ++++- .../org/apache/fop/pdf/PDFEncryptionJCE.java | 12 +- .../apache/fop/pdf/PDFEncryptionManager.java | 9 +- src/java/org/apache/fop/pdf/PDFFactory.java | 21 +- src/java/org/apache/fop/pdf/PDFGState.java | 6 +- src/java/org/apache/fop/pdf/PDFGoTo.java | 8 +- .../apache/fop/pdf/PDFICCBasedColorSpace.java | 7 + .../org/apache/fop/pdf/PDFImageXObject.java | 11 + src/java/org/apache/fop/pdf/PDFInfo.java | 4 +- .../org/apache/fop/pdf/PDFLinearization.java | 422 ++++++++++++++++++ src/java/org/apache/fop/pdf/PDFLink.java | 10 + src/java/org/apache/fop/pdf/PDFObject.java | 36 +- .../org/apache/fop/pdf/PDFObjectNumber.java | 46 ++ src/java/org/apache/fop/pdf/PDFOutline.java | 20 +- src/java/org/apache/fop/pdf/PDFPages.java | 10 +- src/java/org/apache/fop/pdf/PDFReference.java | 8 +- src/java/org/apache/fop/pdf/PDFResources.java | 67 ++- src/java/org/apache/fop/pdf/PDFRoot.java | 9 +- src/java/org/apache/fop/pdf/PDFStream.java | 2 +- .../pdf/xref/CompressedObjectReference.java | 14 +- .../fop/pdf/xref/CrossReferenceStream.java | 8 +- .../fop/pdf/xref/CrossReferenceTable.java | 26 +- .../fop/pdf/xref/TrailerDictionary.java | 2 +- .../fop/render/pdf/PDFContentGenerator.java | 4 + .../fop/render/pdf/PDFDocumentHandler.java | 24 +- .../pdf/PDFDocumentNavigationHandler.java | 5 +- .../render/pdf/PDFImageHandlerGraphics2D.java | 2 +- .../fop/render/pdf/PDFImageHandlerSVG.java | 2 +- .../org/apache/fop/render/pdf/PDFPainter.java | 8 +- .../fop/render/pdf/PDFRendererConfig.java | 2 + .../fop/render/pdf/PDFRendererOption.java | 6 + .../render/pdf/PDFRendererOptionsConfig.java | 5 + .../fop/render/pdf/PDFRenderingUtil.java | 1 + .../apache/fop/svg/PDFDocumentGraphics2D.java | 2 +- .../org/apache/fop/svg/PDFGraphics2D.java | 15 +- .../fop/pdf/AbstractPDFStreamTestCase.java | 2 +- .../fop/pdf/ObjectStreamManagerTestCase.java | 8 +- .../apache/fop/pdf/ObjectStreamTestCase.java | 3 +- .../fop/pdf/PDFEncryptionJCETestCase.java | 4 +- .../fop/pdf/PDFLinearizationTestCase.java | 311 +++++++++++++ .../org/apache/fop/pdf/PDFObjectTestCase.java | 8 +- .../CompressedObjectReferenceTestCase.java | 4 +- .../pdf/xref/CrossReferenceObjectTest.java | 2 +- .../xref/CrossReferenceStreamTestCase.java | 10 +- .../pdf/xref/CrossReferenceTableTestCase.java | 2 +- 52 files changed, 1239 insertions(+), 145 deletions(-) create mode 100644 src/java/org/apache/fop/pdf/PDFLinearization.java create mode 100644 src/java/org/apache/fop/pdf/PDFObjectNumber.java create mode 100644 test/java/org/apache/fop/pdf/PDFLinearizationTestCase.java diff --git a/src/java/org/apache/fop/pdf/AbstractPDFStream.java b/src/java/org/apache/fop/pdf/AbstractPDFStream.java index 331d4f7a5..e550f7e24 100644 --- a/src/java/org/apache/fop/pdf/AbstractPDFStream.java +++ b/src/java/org/apache/fop/pdf/AbstractPDFStream.java @@ -21,6 +21,7 @@ package org.apache.fop.pdf; import java.io.IOException; import java.io.OutputStream; +import java.util.Set; import org.apache.commons.io.output.CountingOutputStream; @@ -36,7 +37,9 @@ public abstract class AbstractPDFStream extends PDFObject { /** The filters that should be applied */ private PDFFilterList filters; - private final boolean encodeOnTheFly; + private boolean encodeOnTheFly; + + private PDFNumber refLength = new PDFNumber(); protected AbstractPDFStream() { this(true); @@ -220,11 +223,11 @@ public abstract class AbstractPDFStream extends PDFObject { StringBuilder textBuffer = new StringBuilder(64); StreamCache encodedStream = null; - PDFNumber refLength = null; final Object lengthEntry; if (encodeOnTheFly) { - refLength = new PDFNumber(); - getDocumentSafely().registerObject(refLength); + if (!refLength.hasObjectNumber()) { + registerChildren(); + } lengthEntry = refLength; } else { encodedStream = encodeStream(); @@ -281,4 +284,18 @@ public abstract class AbstractPDFStream extends PDFObject { protected boolean multipleFiltersAllowed() { return true; } + + @Override + public void getChildren(Set children) { + dictionary.getChildren(children); + if (encodeOnTheFly) { + children.add(refLength); + } + } + + public void registerChildren() { + if (encodeOnTheFly) { + getDocument().registerObject(refLength); + } + } } diff --git a/src/java/org/apache/fop/pdf/BitmapImage.java b/src/java/org/apache/fop/pdf/BitmapImage.java index 2f7be57e6..de6997f95 100644 --- a/src/java/org/apache/fop/pdf/BitmapImage.java +++ b/src/java/org/apache/fop/pdf/BitmapImage.java @@ -51,7 +51,7 @@ public class BitmapImage implements PDFImage { * @param mask the transparency mask reference if any */ public BitmapImage(String k, int width, int height, byte[] data, - String mask) { + PDFReference mask) { this.key = k; this.height = height; this.width = width; @@ -59,7 +59,7 @@ public class BitmapImage implements PDFImage { this.colorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); this.bitmaps = data; if (mask != null) { - maskRef = new PDFReference(mask); + maskRef = mask; } } diff --git a/src/java/org/apache/fop/pdf/CompressedObject.java b/src/java/org/apache/fop/pdf/CompressedObject.java index 55d9c2953..270e672c1 100644 --- a/src/java/org/apache/fop/pdf/CompressedObject.java +++ b/src/java/org/apache/fop/pdf/CompressedObject.java @@ -35,7 +35,7 @@ interface CompressedObject { * * @return the object number. */ - int getObjectNumber(); + PDFObjectNumber getObjectNumber(); /** * Outputs this object's content into the given stream. diff --git a/src/java/org/apache/fop/pdf/PDFAnnotList.java b/src/java/org/apache/fop/pdf/PDFAnnotList.java index 65b327e31..4259d0e67 100644 --- a/src/java/org/apache/fop/pdf/PDFAnnotList.java +++ b/src/java/org/apache/fop/pdf/PDFAnnotList.java @@ -21,6 +21,7 @@ package org.apache.fop.pdf; // Java import java.util.List; +import java.util.Set; /** * class representing an object which is a list of annotations. @@ -73,4 +74,9 @@ public class PDFAnnotList extends PDFObject { * 19 0 R * ] */ + + @Override + public void getChildren(Set children) { + PDFDictionary.getChildren(links, children); + } } diff --git a/src/java/org/apache/fop/pdf/PDFArray.java b/src/java/org/apache/fop/pdf/PDFArray.java index 78f5d080b..36ff57799 100644 --- a/src/java/org/apache/fop/pdf/PDFArray.java +++ b/src/java/org/apache/fop/pdf/PDFArray.java @@ -21,7 +21,9 @@ package org.apache.fop.pdf; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.apache.commons.io.output.CountingOutputStream; @@ -213,4 +215,14 @@ public class PDFArray extends PDFObject { return cout.getCount(); } + @Override + public void getChildren(Set children) { + List contents = new ArrayList(); + for (Object c : values) { + if (!(c instanceof PDFReference)) { + contents.add(c); + } + } + PDFDictionary.getChildren(contents, children); + } } diff --git a/src/java/org/apache/fop/pdf/PDFCIDFont.java b/src/java/org/apache/fop/pdf/PDFCIDFont.java index 907cab0df..28908c6c3 100644 --- a/src/java/org/apache/fop/pdf/PDFCIDFont.java +++ b/src/java/org/apache/fop/pdf/PDFCIDFont.java @@ -21,6 +21,7 @@ package org.apache.fop.pdf; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.Set; import org.apache.fop.fonts.CIDFontType; @@ -301,5 +302,20 @@ public class PDFCIDFont extends PDFObject { return bout.toByteArray(); } + @Override + public void getChildren(Set children) { + super.getChildren(children); + if (cidMap != null) { + children.add(cidMap); + cidMap.getChildren(children); + } + children.add(descriptor); + descriptor.getChildren(children); + if (cmap != null) { + children.add(cmap); + cmap.getChildren(children); + } + } + } diff --git a/src/java/org/apache/fop/pdf/PDFDictionary.java b/src/java/org/apache/fop/pdf/PDFDictionary.java index ae0b950fd..d3e2e57d1 100644 --- a/src/java/org/apache/fop/pdf/PDFDictionary.java +++ b/src/java/org/apache/fop/pdf/PDFDictionary.java @@ -21,8 +21,12 @@ package org.apache.fop.pdf; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.io.output.CountingOutputStream; @@ -30,7 +34,7 @@ import org.apache.commons.io.output.CountingOutputStream; * Class representing a PDF dictionary object */ public class PDFDictionary extends PDFObject { - + private boolean visited; /** * the entry map */ @@ -134,4 +138,28 @@ public class PDFDictionary extends PDFObject { textBuffer.append(">>"); } + @Override + public void getChildren(Set children) { + if (!visited) { + visited = true; + Map childrenMap = new HashMap(entries); + childrenMap.remove("Parent"); + getChildren(childrenMap.values(), children); + visited = false; + } + } + + public static void getChildren(Collection values, Set children) { + for (Object x : values) { + if (x instanceof PDFReference) { + x = ((PDFReference) x).getObject(); + } + if (x instanceof PDFObject) { + if (((PDFObject) x).hasObjectNumber()) { + children.add((PDFObject) x); + } + ((PDFObject) x).getChildren(children); + } + } + } } diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index ccdd69a7a..59ff9c988 100644 --- a/src/java/org/apache/fop/pdf/PDFDocument.java +++ b/src/java/org/apache/fop/pdf/PDFDocument.java @@ -78,18 +78,18 @@ public class PDFDocument { private Log log = LogFactory.getLog("org.apache.fop.pdf"); /** the current character position */ - private long position; + protected long position; /** the character position of each object */ - private List indirectObjectOffsets = new ArrayList(); + protected List indirectObjectOffsets = new ArrayList(); - private Collection structureTreeElements; + protected List structureTreeElements; /** List of objects to write in the trailer */ - private List trailerObjects = new ArrayList(); + protected List trailerObjects = new ArrayList(); /** the objects themselves */ - private List objects = new LinkedList(); + protected List objects = new LinkedList(); /** Controls the PDF version of this document */ private VersionController versionController; @@ -128,6 +128,8 @@ public class PDFDocument { /** the counter for XObject numbering */ private int xObjectCount; + protected int gStateObjectCount; + /* TODO: Should be modified (works only for image subtype) */ private Map xObjectsMap = new HashMap(); @@ -155,6 +157,8 @@ public class PDFDocument { private List launches = new ArrayList(); + protected List pageObjs = new ArrayList(); + private List layers; private List navigators; @@ -169,6 +173,10 @@ public class PDFDocument { private boolean mergeFontsEnabled; + private boolean linearizationEnabled; + + protected boolean outputStarted; + /** * Creates an empty PDF document. * @@ -347,6 +355,14 @@ public class PDFDocument { return this.root; } + /** + * Get the Structural Tree Collection for this document + * @return + */ + public List getStructureTreeElements() { + return structureTreeElements; + } + /** * Creates and returns a StructTreeRoot object. * @@ -398,6 +414,9 @@ public class PDFDocument { public PDFObject registerObject(PDFObject obj) { assignObjectNumber(obj); addObject(obj); + if (obj instanceof AbstractPDFStream) { + ((AbstractPDFStream) obj).registerChildren(); + } return obj; } @@ -421,6 +440,9 @@ public class PDFDocument { * @param obj {@link PDFObject} to assign a number to */ public void assignObjectNumber(PDFObject obj) { + if (outputStarted && isLinearizationEnabled()) { + throw new IllegalStateException("Can't assign number after start of output"); + } if (obj == null) { throw new NullPointerException("obj must not be null"); } @@ -436,7 +458,7 @@ public class PDFDocument { + "PDFObject already has a parent PDFDocument"); } - obj.setObjectNumber(++this.objectcount); + obj.setObjectNumber(this); if (currentParent == null) { obj.setDocument(this); @@ -485,6 +507,7 @@ public class PDFDocument { } if (obj instanceof PDFPage) { this.pages.notifyKidRegistered((PDFPage)obj); + pageObjs.add((PDFPage) obj); } if (obj instanceof PDFLaunch) { this.launches.add((PDFLaunch) obj); @@ -551,7 +574,7 @@ public class PDFDocument { public void setEncryption(PDFEncryptionParams params) { getProfile().verifyEncryptionAllowed(); fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator(); - this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params, this); + this.encryption = PDFEncryptionManager.newInstance(params, this); if (this.encryption != null) { PDFObject pdfObject = (PDFObject)this.encryption; addTrailerObject(pdfObject); @@ -965,6 +988,7 @@ public class PDFDocument { * @throws IOException if there is an exception writing to the output stream */ public void output(OutputStream stream) throws IOException { + outputStarted = true; //Write out objects until the list is empty. This approach (used with a //LinkedList) allows for output() methods to create and register objects //on the fly even during serialization. @@ -974,9 +998,29 @@ public class PDFDocument { } } - private void streamIndirectObject(PDFObject o, OutputStream stream) throws IOException { + protected void writeTrailer(OutputStream stream, int first, int last, int size, long mainOffset, long startxref) + throws IOException { + TrailerOutputHelper trailerOutputHelper = mayCompressStructureTreeElements() + ? new CompressedTrailerOutputHelper() + : new UncompressedTrailerOutputHelper(); + if (structureTreeElements != null) { + trailerOutputHelper.outputStructureTreeElements(stream); + } + TrailerDictionary trailerDictionary = createTrailerDictionary(mainOffset != 0); + if (mainOffset != 0) { + trailerDictionary.getDictionary().put("Prev", mainOffset); + } + trailerOutputHelper.outputCrossReferenceObject(stream, trailerDictionary, first, last, size); + String trailer = "\nstartxref\n" + startxref + "\n%%EOF\n"; + stream.write(encode(trailer)); + } + + protected int streamIndirectObject(PDFObject o, OutputStream stream) throws IOException { + outputStarted = true; recordObjectOffset(o); - this.position += outputIndirectObject(o, stream); + int len = outputIndirectObject(o, stream); + this.position += len; + return len; } private void streamIndirectObjects(Collection objects, OutputStream stream) @@ -987,7 +1031,7 @@ public class PDFDocument { } private void recordObjectOffset(PDFObject object) { - int index = object.getObjectNumber() - 1; + int index = object.getObjectNumber().getNumber() - 1; while (indirectObjectOffsets.size() <= index) { indirectObjectOffsets.add(null); } @@ -1076,23 +1120,26 @@ public class PDFDocument { trailerOutputHelper.outputStructureTreeElements(stream); } streamIndirectObjects(trailerObjects, stream); - TrailerDictionary trailerDictionary = createTrailerDictionary(); - long startxref = trailerOutputHelper.outputCrossReferenceObject(stream, trailerDictionary); + TrailerDictionary trailerDictionary = createTrailerDictionary(true); + long startxref = trailerOutputHelper.outputCrossReferenceObject(stream, trailerDictionary, 0, + indirectObjectOffsets.size(), indirectObjectOffsets.size()); String trailer = "\nstartxref\n" + startxref + "\n%%EOF\n"; stream.write(encode(trailer)); } private boolean mayCompressStructureTreeElements() { return accessibilityEnabled - && versionController.getPDFVersion().compareTo(Version.V1_5) >= 0; + && versionController.getPDFVersion().compareTo(Version.V1_5) >= 0 + && !isLinearizationEnabled(); } - private TrailerDictionary createTrailerDictionary() { + private TrailerDictionary createTrailerDictionary(boolean addRoot) { FileIDGenerator gen = getFileIDGenerator(); - TrailerDictionary trailerDictionary = new TrailerDictionary(this) - .setRoot(root) - .setInfo(info) - .setFileID(gen.getOriginalFileID(), gen.getUpdatedFileID()); + TrailerDictionary trailerDictionary = new TrailerDictionary(this); + if (addRoot) { + trailerDictionary.setRoot(root).setInfo(info); + } + trailerDictionary.setFileID(gen.getOriginalFileID(), gen.getUpdatedFileID()); if (isEncryptionActive()) { trailerDictionary.setEncryption(encryption); } @@ -1117,7 +1164,8 @@ public class PDFDocument { /** * @return the offset of the cross-reference object (the value of startxref) */ - long outputCrossReferenceObject(OutputStream stream, TrailerDictionary trailerDictionary) + long outputCrossReferenceObject(OutputStream stream, TrailerDictionary trailerDictionary, + int first, int last, int size) throws IOException; } @@ -1129,9 +1177,9 @@ public class PDFDocument { } public long outputCrossReferenceObject(OutputStream stream, - TrailerDictionary trailerDictionary) throws IOException { + TrailerDictionary trailerDictionary, int first, int last, int size) throws IOException { new CrossReferenceTable(trailerDictionary, position, - indirectObjectOffsets).output(stream); + indirectObjectOffsets, first, last, size).output(stream); return position; } } @@ -1150,7 +1198,7 @@ public class PDFDocument { } public long outputCrossReferenceObject(OutputStream stream, - TrailerDictionary trailerDictionary) throws IOException { + TrailerDictionary trailerDictionary, int first, int last, int size) throws IOException { // Outputting the object streams should not have created new indirect objects assert objects.isEmpty(); new CrossReferenceStream(PDFDocument.this, ++objectcount, trailerDictionary, position, @@ -1175,4 +1223,13 @@ public class PDFDocument { } return fileIDGenerator; } + + public boolean isLinearizationEnabled() { + return linearizationEnabled; + } + + public void setLinearizationEnabled(boolean b) { + linearizationEnabled = b; + } + } diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java index 63b31d9e0..ff2aac68f 100644 --- a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java +++ b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java @@ -621,11 +621,11 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { private class EncryptionFilter extends PDFFilter { - private int streamNumber; + private PDFObjectNumber streamNumber; private int streamGeneration; - EncryptionFilter(int streamNumber, int streamGeneration) { + EncryptionFilter(PDFObjectNumber streamNumber, int streamGeneration) { this.streamNumber = streamNumber; this.streamGeneration = streamGeneration; } @@ -658,7 +658,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { out.flush(); return new CipherOutputStream(out, cipher); } else { - byte[] key = createEncryptionKey(streamNumber, streamGeneration); + byte[] key = createEncryptionKey(streamNumber.getNumber(), streamGeneration); Cipher cipher = initCipher(key); return new CipherOutputStream(out, cipher); } @@ -666,7 +666,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { } - private PDFEncryptionJCE(int objectNumber, PDFEncryptionParams params, PDFDocument pdf) { + private PDFEncryptionJCE(PDFObjectNumber objectNumber, PDFEncryptionParams params, PDFDocument pdf) { setObjectNumber(objectNumber); try { if (params.getEncryptionLengthInBits() == 256) { @@ -692,7 +692,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { * @return the newly created encryption object */ public static PDFEncryption make( - int objectNumber, PDFEncryptionParams params, PDFDocument pdf) { + PDFObjectNumber objectNumber, PDFEncryptionParams params, PDFDocument pdf) { return new PDFEncryptionJCE(objectNumber, params, pdf); } @@ -714,7 +714,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { System.arraycopy(encryptedData, 0, storedData, 16, encryptedData.length); return storedData; } else { - byte[] key = createEncryptionKey(o.getObjectNumber(), o.getGeneration()); + byte[] key = createEncryptionKey(o.getObjectNumber().getNumber(), o.getGeneration()); return encryptWithKey(key, data); } } diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionManager.java b/src/java/org/apache/fop/pdf/PDFEncryptionManager.java index 6e57b1518..0bf2ca2e2 100644 --- a/src/java/org/apache/fop/pdf/PDFEncryptionManager.java +++ b/src/java/org/apache/fop/pdf/PDFEncryptionManager.java @@ -107,20 +107,21 @@ public final class PDFEncryptionManager { /** * Creates a new PDFEncryption instance if PDF encryption is available. - * @param objnum PDF object number * @param params PDF encryption parameters * @param pdf the PDF document to encrypt * @return PDFEncryption the newly created instance, null if PDF encryption * is unavailable. */ - public static PDFEncryption newInstance(int objnum, PDFEncryptionParams params, + public static PDFEncryption newInstance(PDFEncryptionParams params, PDFDocument pdf) { try { + PDFObjectNumber pdfObjectNumber = new PDFObjectNumber(); + pdfObjectNumber.setDocument(pdf); Class clazz = Class.forName("org.apache.fop.pdf.PDFEncryptionJCE"); Method makeMethod = clazz.getMethod("make", - new Class[] {int.class, PDFEncryptionParams.class, PDFDocument.class}); + new Class[] {PDFObjectNumber.class, PDFEncryptionParams.class, PDFDocument.class}); Object obj = makeMethod.invoke(null, - new Object[] {new Integer(objnum), params, pdf}); + new Object[] {pdfObjectNumber, params, pdf}); return (PDFEncryption)obj; } catch (ClassNotFoundException e) { if (checkAvailableAlgorithms()) { diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index 5ab483f60..017761754 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -105,7 +105,7 @@ public class PDFFactory { */ public PDFRoot makeRoot(PDFPages pages) { //Make a /Pages object. This object is written in the trailer. - PDFRoot pdfRoot = new PDFRoot(++this.document.objectcount, pages); + PDFRoot pdfRoot = new PDFRoot(document, pages); pdfRoot.setDocument(getDocument()); getDocument().addTrailerObject(pdfRoot); return pdfRoot; @@ -117,7 +117,7 @@ public class PDFFactory { * @return a new PDF Pages object for adding pages to */ public PDFPages makePages() { - PDFPages pdfPages = new PDFPages(++(this.document.objectcount)); + PDFPages pdfPages = new PDFPages(getDocument()); pdfPages.setDocument(getDocument()); getDocument().addTrailerObject(pdfPages); return pdfPages; @@ -129,7 +129,7 @@ public class PDFFactory { * @return a new PDF resources object */ public PDFResources makeResources() { - PDFResources pdfResources = new PDFResources(++this.document.objectcount); + PDFResources pdfResources = new PDFResources(getDocument()); pdfResources.setDocument(getDocument()); getDocument().addTrailerObject(pdfResources); return pdfResources; @@ -197,7 +197,14 @@ public class PDFFactory { * create a PDFPage with the next object number, the given * resources, contents and dimensions */ - PDFPage page = new PDFPage(resources, pageIndex, mediaBox, cropBox, bleedBox, trimBox); + PDFResources res = getDocument().getFactory().makeResources(); + if (getDocument().isLinearizationEnabled()) { + getDocument().trailerObjects.remove(resources); + } + res.setParentResources(resources); + + PDFPage page = new PDFPage(res, pageIndex, mediaBox, cropBox, bleedBox, trimBox); + res.setParent(page); getDocument().assignObjectNumber(page); getDocument().getPages().addPage(page); @@ -763,7 +770,7 @@ public class PDFFactory { * @return the new PDF outline object */ public PDFOutline makeOutline(PDFOutline parent, String label, - String actionRef, boolean showSubItems) { + PDFReference actionRef, boolean showSubItems) { PDFOutline pdfOutline = new PDFOutline(label, actionRef, showSubItems); if (parent != null) { parent.addOutline(pdfOutline); @@ -785,7 +792,7 @@ public class PDFFactory { PDFAction pdfAction, boolean showSubItems) { return pdfAction == null ? null - : makeOutline(parent, label, pdfAction.getAction(), showSubItems); + : makeOutline(parent, label, new PDFReference(pdfAction.getAction()), showSubItems); } // This one is obsolete now, at least it isn't called from anywhere inside FOP @@ -804,7 +811,7 @@ public class PDFFactory { boolean showSubItems) { String goToRef = getGoToReference(destination, yoffset); - return makeOutline(parent, label, goToRef, showSubItems); + return makeOutline(parent, label, new PDFReference(goToRef), showSubItems); } diff --git a/src/java/org/apache/fop/pdf/PDFGState.java b/src/java/org/apache/fop/pdf/PDFGState.java index 540917b60..094b7c246 100644 --- a/src/java/org/apache/fop/pdf/PDFGState.java +++ b/src/java/org/apache/fop/pdf/PDFGState.java @@ -105,13 +105,17 @@ public class PDFGState extends PDFObject { } private Map values = new java.util.HashMap(); + private int objNum; /** * Returns the name of this object * @return the name */ public String getName() { - return "GS" + getObjectNumber(); + if (objNum == 0) { + objNum = ++getDocument().gStateObjectCount; + } + return "GS" + objNum; } /** diff --git a/src/java/org/apache/fop/pdf/PDFGoTo.java b/src/java/org/apache/fop/pdf/PDFGoTo.java index 89beac236..695a5b73c 100644 --- a/src/java/org/apache/fop/pdf/PDFGoTo.java +++ b/src/java/org/apache/fop/pdf/PDFGoTo.java @@ -31,7 +31,7 @@ public class PDFGoTo extends PDFAction { /** * the pageReference */ - private String pageReference; + private PDFReference pageReference; private String destination; private float xPosition; private float yPosition; @@ -43,7 +43,9 @@ public class PDFGoTo extends PDFAction { */ public PDFGoTo(String pageReference) { super(); - setPageReference(pageReference); + if (pageReference != null) { + setPageReference(new PDFReference(pageReference)); + } } /** @@ -63,7 +65,7 @@ public class PDFGoTo extends PDFAction { * * @param pageReference the new page reference to use */ - public void setPageReference(String pageReference) { + public void setPageReference(PDFReference pageReference) { this.pageReference = pageReference; } diff --git a/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java b/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java index b86ba29f5..9635917ce 100644 --- a/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java +++ b/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java @@ -23,6 +23,7 @@ import java.awt.color.ColorSpace; import java.awt.color.ICC_Profile; import java.io.IOException; import java.io.InputStream; +import java.util.Set; import org.apache.commons.io.IOUtils; @@ -155,4 +156,10 @@ public class PDFICCBasedColorSpace extends PDFObject implements PDFColorSpace { return sRGBProfile; } + @Override + public void getChildren(Set children) { + super.getChildren(children); + children.add(iccStream); + iccStream.getChildren(children); + } } diff --git a/src/java/org/apache/fop/pdf/PDFImageXObject.java b/src/java/org/apache/fop/pdf/PDFImageXObject.java index 1c28cb2a7..e472efbea 100644 --- a/src/java/org/apache/fop/pdf/PDFImageXObject.java +++ b/src/java/org/apache/fop/pdf/PDFImageXObject.java @@ -22,6 +22,7 @@ package org.apache.fop.pdf; // Java import java.io.IOException; import java.io.OutputStream; +import java.util.Set; /* modified by JKT to integrate with 0.12.0 */ /* modified by Eric SCHAEFFER to integrate with 0.13.0 */ @@ -171,4 +172,14 @@ public class PDFImageXObject extends PDFXObject { return pdfimage.multipleFiltersAllowed(); } + @Override + public void getChildren(Set children) { + super.getChildren(children); + PDFICCStream pdfICCStream = pdfimage.getICCStream(); + if (pdfICCStream != null) { + children.add(pdfICCStream); + pdfICCStream.getChildren(children); + } + } + } diff --git a/src/java/org/apache/fop/pdf/PDFInfo.java b/src/java/org/apache/fop/pdf/PDFInfo.java index dc7c3bdca..97bca9038 100644 --- a/src/java/org/apache/fop/pdf/PDFInfo.java +++ b/src/java/org/apache/fop/pdf/PDFInfo.java @@ -22,7 +22,7 @@ package org.apache.fop.pdf; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Date; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.TimeZone; @@ -317,7 +317,7 @@ public class PDFInfo extends PDFObject { throw new IllegalArgumentException(key + " is a reserved keyword"); } if (customProperties == null) { - customProperties = new HashMap(); + customProperties = new LinkedHashMap(); } customProperties.put(new PDFName(key), value); } diff --git a/src/java/org/apache/fop/pdf/PDFLinearization.java b/src/java/org/apache/fop/pdf/PDFLinearization.java new file mode 100644 index 000000000..fb12d2793 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFLinearization.java @@ -0,0 +1,422 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ +package org.apache.fop.pdf; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.io.output.CountingOutputStream; + +public class PDFLinearization { + private PDFDocument doc; + private Map> pageObjsMap = new HashMap>(); + private PDFDictionary linearDict; + private HintTable hintTable; + + public PDFLinearization(PDFDocument doc) { + this.doc = doc; + } + + static class HintTable extends PDFStream { + private List pages; + int pageStartPos; + List sharedLengths = new ArrayList(); + List pageLengths = new ArrayList(); + List contentStreamLengths = new ArrayList(); + List objCount = new ArrayList(); + Map hintGroups = new HashMap(); + + public HintTable(PDFDocument doc) { + super(false); + doc.assignObjectNumber(this); + doc.addObject(this); + pages = doc.pageObjs; + for (int i = 0; i < pages.size(); i++) { + pageLengths.add(0); + contentStreamLengths.add(0); + objCount.add(0); + } + hintGroups.put("/C", new int[4]); + hintGroups.put("/L", new int[4]); + hintGroups.put("/I", new int[4]); + hintGroups.put("/E", new int[4]); + hintGroups.put("/O", new int[4]); + hintGroups.put("/V", new int[4]); + } + + @Override + public PDFFilterList getFilterList() { + return new PDFFilterList(getDocument().isEncryptionActive()); + } + + @Override + protected void outputRawStreamData(OutputStream os) throws IOException { + CountingOutputStream bos = new CountingOutputStream(os); + + //start header + writeULong(1, bos); //1 + writeULong(pageStartPos, bos); //2 + writeCard16(32, bos); //3 + writeULong(0, bos); //4 + writeCard16(32, bos); //5 + writeULong(0, bos); //6 + writeCard16(0, bos); //7 + writeULong(0, bos); //8 + writeCard16(32, bos); //9 + writeCard16(0, bos); //10 + writeCard16(0, bos); //11 + writeCard16(0, bos); //12 + writeCard16(4, bos); //13 + //end header + + for (PDFPage page : pages) { + writeULong(objCount.get(page.pageIndex) - 1, bos); + } + for (PDFPage page : pages) { + writeULong(pageLengths.get(page.pageIndex), bos); + } + for (PDFPage page : pages) { + writeULong(contentStreamLengths.get(page.pageIndex), bos); + } + + writeSharedTable(bos); + + for (Map.Entry group : hintGroups.entrySet()) { + put(group.getKey(), bos.getCount()); + for (int i : group.getValue()) { + writeULong(i, bos); + } + if (group.getKey().equals("/C")) { + writeULong(0, bos); + writeCard16(0, bos); + } + } + } + + private void writeSharedTable(CountingOutputStream bos) throws IOException { + put("/S", bos.getCount()); + + //Shared object hint table, header section + writeULong(0, bos); //1 + writeULong(0, bos); //2 + writeULong(sharedLengths.size(), bos); //3 + writeULong(sharedLengths.size(), bos); //4 + writeCard16(0, bos); //5 + writeULong(0, bos); //6 + writeCard16(32, bos); //7 + + for (int i : sharedLengths) { + writeULong(i, bos); + } + writeULong(0, bos); + } + + private void writeCard16(int s, OutputStream bos) throws IOException { + byte b1 = (byte)((s >> 8) & 0xff); + byte b2 = (byte)(s & 0xff); + bos.write(b1); + bos.write(b2); + } + + private void writeULong(int s, OutputStream bos) throws IOException { + byte b1 = (byte)((s >> 24) & 0xff); + byte b2 = (byte)((s >> 16) & 0xff); + byte b3 = (byte)((s >> 8) & 0xff); + byte b4 = (byte)(s & 0xff); + bos.write(b1); + bos.write(b2); + bos.write(b3); + bos.write(b4); + } + } + + static class LinearPDFDictionary extends PDFDictionary { + private int lastsize = -1; + + public LinearPDFDictionary(PDFDocument doc) { + put("Linearized", 1); + put("/L", 0); + PDFArray larray = new PDFArray(); + larray.add(0); + larray.add(0); + put("/H", larray); + doc.assignObjectNumber(this); + getObjectNumber().getNumber(); + put("/O", getObjectNumber().getNumber() + 3); + put("/E", 0); + put("/N", doc.pageObjs.size()); + put("/T", 0); + } + + public int output(OutputStream stream) throws IOException { + int size = super.output(stream); + int padding = lastsize - size + 32; + if (lastsize == -1) { + padding = 32; + lastsize = size; + } + writePadding(padding, stream); + return size + padding; + } + } + + + private Set assignNumbers() throws IOException { + Set page1Children = getPage1Children(); + if (!doc.pageObjs.isEmpty()) { + for (int i = 1; i < doc.pageObjs.size(); i++) { + PDFPage page = doc.pageObjs.get(i); + Set children = pageObjsMap.get(page); + for (PDFObject c : children) { + if (!page1Children.contains(c) && c.hasObjectNumber()) { + c.getObjectNumber().getNumber(); + } + } + } + for (PDFObject o : doc.objects) { + if (o instanceof PDFDests || o instanceof PDFOutline) { + for (PDFObject c : getChildren(o)) { + c.getObjectNumber().getNumber(); + } + } + if (o instanceof PDFInfo || o instanceof PDFPageLabels) { + o.getObjectNumber().getNumber(); + } + } + for (PDFObject o : doc.objects) { + if (!page1Children.contains(o)) { + o.getObjectNumber().getNumber(); + } + } + } + linearDict = new LinearPDFDictionary(doc); + for (PDFObject o : page1Children) { + o.getObjectNumber().getNumber(); + } + sort(doc.objects); + return page1Children; + } + + private void sort(List objects) { + Collections.sort(objects, new Comparator() { + public int compare(PDFObject o1, PDFObject o2) { + return ((Integer) o1.getObjectNumber().getNumber()).compareTo(o2.getObjectNumber().getNumber()); + } + }); + } + + private Set getChildren(PDFObject o) { + Set children = new LinkedHashSet(); + children.add(o); + o.getChildren(children); + return children; + } + + public void outputPages(OutputStream stream) throws IOException { + Collections.sort(doc.pageObjs, new Comparator() { + public int compare(PDFPage o1, PDFPage o2) { + return ((Integer) o1.pageIndex).compareTo(o2.pageIndex); + } + }); + doc.objects.addAll(doc.trailerObjects); + doc.trailerObjects = null; + if (doc.getStructureTreeElements() != null) { + doc.objects.addAll(doc.getStructureTreeElements()); + doc.structureTreeElements = null; + } + for (int i = 0; i < doc.objects.size() * 2; i++) { + doc.indirectObjectOffsets.add(0L); + } + Set page1Children = assignNumbers(); + doc.streamIndirectObject(linearDict, new ByteArrayOutputStream()); + for (PDFObject o : page1Children) { + doc.objects.remove(o); + } + int sizeOfRest = doc.objects.size(); + + ByteArrayOutputStream fakeHeaderTrailerStream = new ByteArrayOutputStream(); + long topTrailer = doc.position; + doc.writeTrailer(fakeHeaderTrailerStream, sizeOfRest, page1Children.size() + 1, + page1Children.size() + sizeOfRest + 1, Long.MAX_VALUE, 0); + doc.position += fakeHeaderTrailerStream.size(); + + ByteArrayOutputStream pageStream = new ByteArrayOutputStream(); + writeObjects(page1Children, pageStream, sizeOfRest + 1); + long trailerOffset = doc.position; + ByteArrayOutputStream footerTrailerStream = new ByteArrayOutputStream(); + doc.writeTrailer(footerTrailerStream, 0, sizeOfRest, sizeOfRest, 0, topTrailer); + doc.position += footerTrailerStream.size(); + + linearDict.put("/L", doc.position); + + PDFDocument.outputIndirectObject(linearDict, stream); + CountingOutputStream realTrailer = new CountingOutputStream(stream); + doc.writeTrailer(realTrailer, sizeOfRest, page1Children.size() + 1, + page1Children.size() + sizeOfRest + 1, trailerOffset, 0); + writePadding(fakeHeaderTrailerStream.size() - realTrailer.getCount(), stream); + for (PDFObject o : page1Children) { + PDFDocument.outputIndirectObject(o, stream); + if (o instanceof HintTable) { + break; + } + } + stream.write(pageStream.toByteArray()); + stream.write(footerTrailerStream.toByteArray()); + } + + private Set getPage1Children() throws IOException { + Set page1Children = new LinkedHashSet(); + if (!doc.pageObjs.isEmpty()) { + PDFPage page1 = doc.pageObjs.get(0); + page1Children.add(doc.getRoot()); + hintTable = new HintTable(doc); + page1Children.add(hintTable); + page1Children.add(page1); + page1.getChildren(page1Children); + doc.objects.remove(doc.getPages()); + doc.objects.add(0, doc.getPages()); + pageObjsMap.put(page1, page1Children); + + for (int i = 1; i < doc.pageObjs.size(); i++) { + PDFPage page = doc.pageObjs.get(i); + pageObjsMap.put(page, getChildren(page)); + } + } + return page1Children; + } + + private static void writePadding(int padding, OutputStream stream) throws IOException { + for (int i = 0; i < padding; i++) { + stream.write(" ".getBytes("UTF-8")); + } + } + + private void writeObjects(Set children1, OutputStream pageStream, int sizeOfRest) throws IOException { + writePage1(children1, pageStream); + linearDict.put("/E", doc.position); + for (PDFPage page : doc.pageObjs) { + if (page.pageIndex != 0) { + writePage(page, pageStream); + } + } + while (!doc.objects.isEmpty()) { + PDFObject o = doc.objects.remove(0); + if (o instanceof PDFOutline) { + writeObjectGroup("/O", getChildren(o), pageStream); + } else if (o instanceof PDFDests) { + writeObjectGroup("/E", getChildren(o), pageStream); + } else if (o instanceof PDFInfo) { + writeObjectGroup("/I", getChildren(o), pageStream); + } else if (o instanceof PDFPageLabels) { + writeObjectGroup("/L", getChildren(o), pageStream); + } else if (o instanceof PDFStructTreeRoot) { + writeObjectGroup("/C", getChildren(o), pageStream); + } else { + doc.streamIndirectObject(o, pageStream); + } + } + linearDict.put("/T", doc.position + 8 + String.valueOf(sizeOfRest).length()); + } + + private void writeObjectGroup(String name, Set objects, OutputStream pageStream) + throws IOException { + List children = new ArrayList(objects); + sort(children); + + int[] values = hintTable.hintGroups.get(name); + values[0] = children.iterator().next().getObjectNumber().getNumber(); + values[1] = (int) doc.position; + values[2] = children.size(); + for (PDFObject o : children) { + values[3] += doc.streamIndirectObject(o, pageStream); + doc.objects.remove(o); + } + } + + private void writePage1(Set children1, OutputStream pageStream) throws IOException { + hintTable.pageStartPos = (int) doc.position; + OutputStream stream = new ByteArrayOutputStream(); + + Set sharedChildren = getSharedObjects(); + + int page1Len = 0; + int objCount = 0; + int sharedCount = 0; + for (PDFObject o : children1) { + if (o instanceof HintTable) { + PDFArray a = (PDFArray) linearDict.get("/H"); + a.set(0, doc.position); + doc.streamIndirectObject(o, stream); + a.set(1, doc.position - (Double)a.get(0)); + stream = pageStream; + } else { + int len = doc.streamIndirectObject(o, stream); + if (o instanceof PDFStream && hintTable.contentStreamLengths.get(0) == 0) { + hintTable.contentStreamLengths.set(0, len); + } + if (!(o instanceof PDFRoot)) { + page1Len += len; + objCount++; + } + if (sharedChildren.contains(o)) { + hintTable.sharedLengths.set(sharedCount, len); + sharedCount++; + } + } + } + hintTable.pageLengths.set(0, page1Len); + hintTable.objCount.set(0, objCount); + } + + private Set getSharedObjects() { + Set pageSharedChildren = getChildren(doc.pageObjs.get(0)); + for (int i = 0; i < pageSharedChildren.size(); i++) { + hintTable.sharedLengths.add(0); + } + return pageSharedChildren; + } + + private void writePage(PDFPage page, OutputStream pageStream) throws IOException { + Set children = pageObjsMap.get(page); + int pageLen = 0; + int objCount = 0; + for (PDFObject c : children) { + if (doc.objects.contains(c)) { + int len = doc.streamIndirectObject(c, pageStream); + if (c instanceof PDFStream) { + hintTable.contentStreamLengths.set(page.pageIndex, len); + } + pageLen += len; + doc.objects.remove(c); + objCount++; + } + } + hintTable.pageLengths.set(page.pageIndex, pageLen); + hintTable.objCount.set(page.pageIndex, objCount); + } +} diff --git a/src/java/org/apache/fop/pdf/PDFLink.java b/src/java/org/apache/fop/pdf/PDFLink.java index f35a15e57..fd74c100a 100644 --- a/src/java/org/apache/fop/pdf/PDFLink.java +++ b/src/java/org/apache/fop/pdf/PDFLink.java @@ -21,6 +21,7 @@ package org.apache.fop.pdf; // Java import java.awt.geom.Rectangle2D; +import java.util.Set; /** * class representing an /Annot object of /Subtype /Link @@ -142,5 +143,14 @@ public class PDFLink extends PDFObject { return true; } + + @Override + public void getChildren(Set children) { + super.getChildren(children); + if (action.hasObjectNumber()) { + children.add(action); + } + action.getChildren(children); + } } diff --git a/src/java/org/apache/fop/pdf/PDFObject.java b/src/java/org/apache/fop/pdf/PDFObject.java index 7ec1e2407..12402d72c 100644 --- a/src/java/org/apache/fop/pdf/PDFObject.java +++ b/src/java/org/apache/fop/pdf/PDFObject.java @@ -22,6 +22,7 @@ package org.apache.fop.pdf; // Java import java.io.IOException; import java.io.OutputStream; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -41,7 +42,8 @@ public abstract class PDFObject implements PDFWritable { /** * the object's number */ - private int objnum; + private boolean hasObjNum; + private PDFObjectNumber objNum = new PDFObjectNumber(); /** * the object's generation (0 in new documents) @@ -60,11 +62,11 @@ public abstract class PDFObject implements PDFWritable { * Returns the object's number. * @return the PDF Object number */ - public int getObjectNumber() { - if (this.objnum == 0) { + public PDFObjectNumber getObjectNumber() { + if (!hasObjNum) { throw new IllegalStateException("Object has no number assigned: " + this.toString()); } - return this.objnum; + return objNum; } /** @@ -88,23 +90,33 @@ public abstract class PDFObject implements PDFWritable { * @return True if it has an object number */ public boolean hasObjectNumber() { - return this.objnum > 0; + return hasObjNum; } /** * Sets the object number - * @param objnum the object number */ - public void setObjectNumber(int objnum) { - this.objnum = objnum; + public void setObjectNumber(PDFDocument document) { + objNum.setDocument(document); + hasObjNum = true; PDFDocument doc = getDocument(); setParent(null); setDocument(doc); //Restore reference to PDFDocument after setting parent to null if (log.isTraceEnabled()) { - log.trace("Assigning " + this + " object number " + objnum); + log.trace("Assigning " + this + " object number " + objNum); } } + public void setObjectNumber(PDFObjectNumber objectNumber) { + objNum = objectNumber; + hasObjNum = true; + } + + public void setObjectNumber(int objectNumber) { + objNum = new PDFObjectNumber(objectNumber); + hasObjNum = true; + } + /** * Returns this object's generation. * @return the PDF Object generation @@ -184,8 +196,7 @@ public abstract class PDFObject implements PDFWritable { throw new IllegalArgumentException( "Cannot reference this object. It doesn't have an object number"); } - String ref = getObjectNumber() + " " + getGeneration() + " R"; - return ref; + return makeReference().toString(); } /** @@ -344,4 +355,7 @@ public abstract class PDFObject implements PDFWritable { protected boolean contentEquals(PDFObject o) { return this.equals(o); } + + public void getChildren(Set children) { + } } diff --git a/src/java/org/apache/fop/pdf/PDFObjectNumber.java b/src/java/org/apache/fop/pdf/PDFObjectNumber.java new file mode 100644 index 000000000..d26823f6b --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFObjectNumber.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ +package org.apache.fop.pdf; + +public class PDFObjectNumber { + private int num; + private PDFDocument doc; + + public PDFObjectNumber() { + } + public PDFObjectNumber(int num) { + this.num = num; + } + + public void setDocument(PDFDocument doc) { + this.doc = doc; + } + + public int getNumber() { + if (num == 0 && doc != null) { +// assert doc.outputStarted; + num = ++doc.objectcount; + } + return num; + } + + public String toString() { + return String.valueOf(getNumber()); + } +} diff --git a/src/java/org/apache/fop/pdf/PDFOutline.java b/src/java/org/apache/fop/pdf/PDFOutline.java index 8edea457f..c43db61aa 100644 --- a/src/java/org/apache/fop/pdf/PDFOutline.java +++ b/src/java/org/apache/fop/pdf/PDFOutline.java @@ -22,6 +22,7 @@ package org.apache.fop.pdf; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; +import java.util.Set; /** *

This represents a single Outline object in a PDF, including the root Outlines @@ -58,7 +59,7 @@ public class PDFOutline extends PDFObject { */ private String title; - private String actionRef; + private PDFReference actionRef; /** * Create a PDF outline with the title and action. @@ -67,7 +68,7 @@ public class PDFOutline extends PDFObject { * @param action the action for this outline * @param openItem indicator of whether child items are visible or not */ - public PDFOutline(String title, String action, boolean openItem) { + public PDFOutline(String title, PDFReference action, boolean openItem) { super(); subentries = new java.util.ArrayList(); count = 0; @@ -170,4 +171,19 @@ public class PDFOutline extends PDFObject { return bout.toByteArray(); } + @Override + public void getChildren(Set children) { + if (parent != null) { + children.add(parent); + } + if (first != null && last != null) { + children.add(first); + children.add(last); + first.getChildren(children); + last.getChildren(children); + } + if (actionRef != null) { + children.add(actionRef.getObject()); + } + } } diff --git a/src/java/org/apache/fop/pdf/PDFPages.java b/src/java/org/apache/fop/pdf/PDFPages.java index b56594926..d335bde52 100644 --- a/src/java/org/apache/fop/pdf/PDFPages.java +++ b/src/java/org/apache/fop/pdf/PDFPages.java @@ -50,11 +50,9 @@ public class PDFPages extends PDFObject { * It must also be allocated an object ID (so that the kids * can refer to the parent) so that the XRef table needs to * be updated before this object is written. - * - * @param objnum the object's number */ - public PDFPages(int objnum) { - setObjectNumber(objnum); + public PDFPages(PDFDocument document) { + setObjectNumber(document); } /** @@ -81,9 +79,9 @@ public class PDFPages extends PDFObject { throw new IllegalStateException("A page already exists at index " + idx + " (zero-based)."); } - this.kids.set(idx, page.referencePDF()); + this.kids.set(idx, page.makeReference()); } else { - this.kids.add(page.referencePDF()); + this.kids.add(page.makeReference()); } } diff --git a/src/java/org/apache/fop/pdf/PDFReference.java b/src/java/org/apache/fop/pdf/PDFReference.java index 29f8c7c48..ca704978b 100644 --- a/src/java/org/apache/fop/pdf/PDFReference.java +++ b/src/java/org/apache/fop/pdf/PDFReference.java @@ -31,7 +31,7 @@ import java.lang.ref.SoftReference; */ public class PDFReference implements PDFWritable { - private int objectNumber; + private PDFObjectNumber objectNumber; private int generation; private Reference objReference; @@ -56,7 +56,7 @@ public class PDFReference implements PDFWritable { } String[] parts = ref.split(" "); assert parts.length == 3; - this.objectNumber = Integer.parseInt(parts[0]); + this.objectNumber = new PDFObjectNumber(Integer.parseInt(parts[0])); this.generation = Integer.parseInt(parts[1]); assert "R".equals(parts[2]); } @@ -81,7 +81,7 @@ public class PDFReference implements PDFWritable { * Returns the object number. * @return the object number */ - public int getObjectNumber() { + public PDFObjectNumber getObjectNumber() { return this.objectNumber; } @@ -103,7 +103,7 @@ public class PDFReference implements PDFWritable { /** {@inheritDoc} */ public void outputInline(OutputStream out, StringBuilder textBuffer) { - textBuffer.append(getObjectNumber()).append(' ').append(getGeneration()).append(" R"); + textBuffer.append(getObjectNumber().getNumber()).append(' ').append(getGeneration()).append(" R"); } } diff --git a/src/java/org/apache/fop/pdf/PDFResources.java b/src/java/org/apache/fop/pdf/PDFResources.java index f351b23be..9ba9c7f8f 100644 --- a/src/java/org/apache/fop/pdf/PDFResources.java +++ b/src/java/org/apache/fop/pdf/PDFResources.java @@ -52,7 +52,7 @@ public class PDFResources extends PDFDictionary { */ protected Set xObjects = new LinkedHashSet(); /** Map of color spaces (key: color space name) */ - protected Map colorSpaces = new LinkedHashMap(); + protected Map colorSpaces = new LinkedHashMap(); /** Map of ICC color spaces (key: ICC profile description) */ protected Map iccColorSpaces = new LinkedHashMap(); @@ -68,13 +68,11 @@ public class PDFResources extends PDFDictionary { /** * create a /Resources object. - * - * @param objnum the object's number */ - public PDFResources(int objnum) { + public PDFResources(PDFDocument doc) { /* generic creation of object */ super(); - setObjectNumber(objnum); + setObjectNumber(doc); } public void addContext(PDFResourceContext c) { @@ -156,7 +154,7 @@ public class PDFResources extends PDFDictionary { * @param colorSpace the color space */ public void addColorSpace(PDFColorSpace colorSpace) { - this.colorSpaces.put(new PDFName(colorSpace.getName()), colorSpace); + this.colorSpaces.put(new LazyName(colorSpace), colorSpace); if (colorSpace instanceof PDFICCBasedColorSpace) { PDFICCBasedColorSpace icc = (PDFICCBasedColorSpace)colorSpace; String desc = ColorProfileUtil.getICCProfileDescription( @@ -165,6 +163,16 @@ public class PDFResources extends PDFDictionary { } } + static class LazyName { + private PDFColorSpace colorSpace; + public LazyName(PDFColorSpace colorSpace) { + this.colorSpace = colorSpace; + } + public PDFName getName() { + return new PDFName(colorSpace.getName()); + } + } + /** * Returns a ICCBased color space by profile name. * @param desc the name of the color space @@ -181,8 +189,12 @@ public class PDFResources extends PDFDictionary { * @return the requested color space or null if it wasn't found */ public PDFColorSpace getColorSpace(PDFName name) { - PDFColorSpace cs = this.colorSpaces.get(name); - return cs; + for (Map.Entry x : colorSpaces.entrySet()) { + if (x.getKey().getName().equals(name)) { + return x.getValue(); + } + } + return null; } /** @@ -303,4 +315,43 @@ public class PDFResources extends PDFDictionary { } } + @Override + public void getChildren(Set children) { + getChildren(children, false); + } + + private void getChildren(Set children, boolean isParent) { + super.getChildren(children); + for (PDFDictionary f : fonts.values()) { + children.add(f); + f.getChildren(children); + } + for (PDFResourceContext c : contexts) { + for (PDFXObject x : c.getXObjects()) { + children.add(x); + x.getChildren(children); + } + for (PDFPattern x : c.getPatterns()) { + children.add(x); + x.getChildren(children); + } + for (PDFShading x : c.getShadings()) { + children.add(x); + x.getChildren(children); + } + for (PDFGState x : c.getGStates()) { + children.add(x); + x.getChildren(children); + } + } + if (!isParent) { + for (PDFColorSpace x : colorSpaces.values()) { + children.add((PDFObject)x); + ((PDFObject)x).getChildren(children); + } + } + if (parent != null) { + parent.getChildren(children, true); + } + } } diff --git a/src/java/org/apache/fop/pdf/PDFRoot.java b/src/java/org/apache/fop/pdf/PDFRoot.java index e74c77478..0a0f03d6d 100644 --- a/src/java/org/apache/fop/pdf/PDFRoot.java +++ b/src/java/org/apache/fop/pdf/PDFRoot.java @@ -50,6 +50,9 @@ public class PDFRoot extends PDFDictionary { */ public static final int PAGEMODE_FULLSCREEN = 3; + private final PDFDocument document; + + private static final PDFName[] PAGEMODE_NAMES = new PDFName[] { new PDFName("UseNone"), new PDFName("UseOutlines"), @@ -67,9 +70,9 @@ public class PDFRoot extends PDFDictionary { * @param objnum the object's number * @param pages the PDFPages object */ - public PDFRoot(int objnum, PDFPages pages) { - super(); - setObjectNumber(objnum); + public PDFRoot(PDFDocument document, PDFPages pages) { + this.document = document; + setObjectNumber(document); put("Type", new PDFName("Catalog")); setRootPages(pages); setLanguage("x-unknown"); diff --git a/src/java/org/apache/fop/pdf/PDFStream.java b/src/java/org/apache/fop/pdf/PDFStream.java index 2801d4d1f..d6dd98cf3 100644 --- a/src/java/org/apache/fop/pdf/PDFStream.java +++ b/src/java/org/apache/fop/pdf/PDFStream.java @@ -187,7 +187,7 @@ public class PDFStream extends AbstractPDFStream { final int len = super.output(stream); //Now that the data has been written, it can be discarded. - this.data = null; +// this.data = null; return len; } diff --git a/src/java/org/apache/fop/pdf/xref/CompressedObjectReference.java b/src/java/org/apache/fop/pdf/xref/CompressedObjectReference.java index eb619fcc6..8da267af1 100644 --- a/src/java/org/apache/fop/pdf/xref/CompressedObjectReference.java +++ b/src/java/org/apache/fop/pdf/xref/CompressedObjectReference.java @@ -22,15 +22,17 @@ package org.apache.fop.pdf.xref; import java.io.DataOutputStream; import java.io.IOException; +import org.apache.fop.pdf.PDFObjectNumber; + /** * A reference to an indirect object stored in an object stream. Contains the relevant * information to add to a cross-reference stream. */ public class CompressedObjectReference implements ObjectReference { - private final int objectNumber; + private final PDFObjectNumber objectNumber; - private final int objectStreamNumber; + private final PDFObjectNumber objectStreamNumber; private final int index; @@ -42,7 +44,7 @@ public class CompressedObjectReference implements ObjectReference { * object is to be found * @param index the index of the compressed object in the object stream */ - public CompressedObjectReference(int objectNumber, int objectStreamNumber, int index) { + public CompressedObjectReference(PDFObjectNumber objectNumber, PDFObjectNumber objectStreamNumber, int index) { this.objectNumber = objectNumber; this.objectStreamNumber = objectStreamNumber; this.index = index; @@ -50,16 +52,16 @@ public class CompressedObjectReference implements ObjectReference { public void output(DataOutputStream out) throws IOException { out.write(2); - out.writeLong(objectStreamNumber); + out.writeLong(objectStreamNumber.getNumber()); out.write(0); out.write(index); } - public int getObjectNumber() { + public PDFObjectNumber getObjectNumber() { return objectNumber; } - public int getObjectStreamNumber() { + public PDFObjectNumber getObjectStreamNumber() { return objectStreamNumber; } diff --git a/src/java/org/apache/fop/pdf/xref/CrossReferenceStream.java b/src/java/org/apache/fop/pdf/xref/CrossReferenceStream.java index 32c31573f..9dbd317a7 100644 --- a/src/java/org/apache/fop/pdf/xref/CrossReferenceStream.java +++ b/src/java/org/apache/fop/pdf/xref/CrossReferenceStream.java @@ -31,6 +31,7 @@ import org.apache.fop.pdf.PDFDictionary; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFFilterList; import org.apache.fop.pdf.PDFName; +import org.apache.fop.pdf.PDFObjectNumber; import org.apache.fop.pdf.PDFStream; /** @@ -60,7 +61,10 @@ public class CrossReferenceStream extends CrossReferenceObject { objectReferences.add(offset == null ? null : new UncompressedObjectReference(offset)); } for (CompressedObjectReference ref : compressedObjectReferences) { - this.objectReferences.set(ref.getObjectNumber() - 1, ref); + while (ref.getObjectNumber().getNumber() > objectReferences.size()) { + objectReferences.add(null); + } + this.objectReferences.set(ref.getObjectNumber().getNumber() - 1, ref); } } @@ -77,7 +81,7 @@ public class CrossReferenceStream extends CrossReferenceObject { } }; - helperStream.setObjectNumber(objectNumber); + helperStream.setObjectNumber(new PDFObjectNumber(objectNumber)); helperStream.setDocument(document); ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); DataOutputStream data = new DataOutputStream(byteArray); diff --git a/src/java/org/apache/fop/pdf/xref/CrossReferenceTable.java b/src/java/org/apache/fop/pdf/xref/CrossReferenceTable.java index 41a1146ca..09beab5b6 100644 --- a/src/java/org/apache/fop/pdf/xref/CrossReferenceTable.java +++ b/src/java/org/apache/fop/pdf/xref/CrossReferenceTable.java @@ -35,10 +35,17 @@ public class CrossReferenceTable extends CrossReferenceObject { private final StringBuilder pdf = new StringBuilder(256); + private int last; + private int first; + private int size; + public CrossReferenceTable(TrailerDictionary trailerDictionary, long startxref, - List location) { + List location, int first, int last, int size) { super(trailerDictionary, startxref); this.objectReferences = location; + this.first = first; + this.last = last; + this.size = size; } public void output(OutputStream stream) throws IOException { @@ -47,10 +54,17 @@ public class CrossReferenceTable extends CrossReferenceObject { } private void outputXref() throws IOException { - pdf.append("xref\n0 "); - pdf.append(objectReferences.size() + 1); - pdf.append("\n0000000000 65535 f \n"); - for (Long objectReference : objectReferences) { + if (first == 0) { + pdf.append("xref\n0 "); + pdf.append(last + 1); + pdf.append("\n0000000000 65535 f \n"); + } else { + pdf.append("xref\n" + (first + 1) + " "); + pdf.append(last + "\n"); + } + for (int i = first; i < first + last; i++) { + Long objectReference = objectReferences.get(i); + assert objectReference != null; final String padding = "0000000000"; String s = String.valueOf(objectReference); if (s.length() > 10) { @@ -66,7 +80,7 @@ public class CrossReferenceTable extends CrossReferenceObject { pdf.append("trailer\n"); stream.write(PDFDocument.encode(pdf.toString())); PDFDictionary dictionary = trailerDictionary.getDictionary(); - dictionary.put("/Size", objectReferences.size() + 1); + dictionary.put("/Size", size + 1); dictionary.output(stream); } diff --git a/src/java/org/apache/fop/pdf/xref/TrailerDictionary.java b/src/java/org/apache/fop/pdf/xref/TrailerDictionary.java index d5d62522f..09545d81c 100644 --- a/src/java/org/apache/fop/pdf/xref/TrailerDictionary.java +++ b/src/java/org/apache/fop/pdf/xref/TrailerDictionary.java @@ -87,7 +87,7 @@ public class TrailerDictionary { return this; } - PDFDictionary getDictionary() { + public PDFDictionary getDictionary() { return dictionary; } diff --git a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java index 6558bddc2..fc1f31cc6 100644 --- a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java +++ b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java @@ -28,6 +28,7 @@ import java.io.OutputStream; import org.apache.fop.pdf.PDFColorHandler; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFFilterList; +import org.apache.fop.pdf.PDFLinearization; import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFPaintingState; import org.apache.fop.pdf.PDFReference; @@ -148,6 +149,9 @@ public class PDFContentGenerator { * @throws IOException if an error occurs while flushing the PDF objects */ public void flushPDFDoc() throws IOException { + if (document.isLinearizationEnabled()) { + new PDFLinearization(document).outputPages(outputStream); + } this.document.output(this.outputStream); } diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java index fddbdff91..05e9cb580 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -41,6 +41,7 @@ import org.apache.fop.pdf.PDFAnnotList; import org.apache.fop.pdf.PDFArray; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFReference; import org.apache.fop.pdf.PDFResources; import org.apache.fop.render.extensions.prepress.PageBoundaries; import org.apache.fop.render.extensions.prepress.PageScale; @@ -180,9 +181,13 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public void endDocument() throws IFException { + pdfDoc.getResources().addFonts(pdfDoc, fontInfo); try { - pdfDoc.getResources().addFonts(pdfDoc, fontInfo); - pdfDoc.outputTrailer(this.outputStream); + if (pdfDoc.isLinearizationEnabled()) { + generator.flushPDFDoc(); + } else { + pdfDoc.outputTrailer(this.outputStream); + } this.pdfDoc = null; pdfResources = null; @@ -286,8 +291,11 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { this.pdfDoc.addObject(annots); } this.pdfDoc.addObject(currentPage); - this.generator.flushPDFDoc(); - this.generator = null; + + if (!pdfDoc.isLinearizationEnabled()) { + this.generator.flushPDFDoc(); + this.generator = null; + } } catch (IOException ioe) { throw new IFException("I/O error in endPage()", ioe); } @@ -329,17 +337,15 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { static final class PageReference { - private final String pageRef; + private final PDFReference pageRef; private final Dimension pageDimension; private PageReference(PDFPage page, Dimension dim) { - // Avoid keeping references to PDFPage as memory usage is - // considerably increased when handling thousands of pages. - this.pageRef = page.makeReference().toString(); + this.pageRef = page.makeReference(); this.pageDimension = new Dimension(dim); } - public String getPageRef() { + public PDFReference getPageRef() { return this.pageRef; } diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java index ce43fc14b..cb9f43fa9 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java @@ -31,6 +31,7 @@ import org.apache.fop.pdf.PDFFactory; import org.apache.fop.pdf.PDFGoTo; import org.apache.fop.pdf.PDFLink; import org.apache.fop.pdf.PDFOutline; +import org.apache.fop.pdf.PDFReference; import org.apache.fop.pdf.PDFStructElem; import org.apache.fop.render.intermediate.IFDocumentNavigationHandler; import org.apache.fop.render.intermediate.IFException; @@ -87,7 +88,7 @@ public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler parent = getPDFDoc().getOutlineRoot(); } PDFAction action = getAction(bookmark.getAction()); - String actionRef = (action != null ? action.makeReference().toString() : null); + PDFReference actionRef = (action != null ? action.makeReference() : null); PDFOutline pdfOutline = getPDFDoc().getFactory().makeOutline(parent, bookmark.getTitle(), actionRef, bookmark.isShown()); Iterator iter = bookmark.getChildBookmarks().iterator(); @@ -194,7 +195,7 @@ public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler p2d = new Point2D.Double( action.getTargetLocation().x / 1000.0, (pageRef.getPageDimension().height - action.getTargetLocation().y) / 1000.0); - String pdfPageRef = pageRef.getPageRef(); + PDFReference pdfPageRef = pageRef.getPageRef(); pdfGoTo.setPageReference(pdfPageRef); pdfGoTo.setPosition(p2d); diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java index 5ab0b700f..60f2c11bc 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java @@ -85,7 +85,7 @@ public class PDFImageHandlerGraphics2D extends AbstractImageHandlerGraphics2D { final boolean textAsShapes = false; PDFGraphics2D graphics = new PDFGraphics2D(textAsShapes, pdfContext.getFontInfo(), generator.getDocument(), - generator.getResourceContext(), pdfContext.getPage().referencePDF(), + generator.getResourceContext(), pdfContext.getPage().makeReference(), "", 0.0f, null); graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java index 13d61dc35..fa3813d06 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java @@ -171,7 +171,7 @@ public class PDFImageHandlerSVG implements ImageHandler { PDFGraphics2D graphics = new PDFGraphics2D(true, pdfContext.getFontInfo(), generator.getDocument(), - generator.getResourceContext(), pdfContext.getPage().referencePDF(), + generator.getResourceContext(), pdfContext.getPage().makeReference(), "", 0, new TransparencyIgnoredEventListener(pdfContext, imageSVG)); graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java index cd992841e..c43e6347a 100644 --- a/src/java/org/apache/fop/render/pdf/PDFPainter.java +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -171,7 +171,9 @@ public class PDFPainter extends AbstractIFPainter { prepareImageMCID(structElem); } drawImageUsingURI(uri, rect); - flushPDFDoc(); + if (!getDocumentHandler().getPDFDocument().isLinearizationEnabled()) { + flushPDFDoc(); + } } } @@ -228,7 +230,9 @@ public class PDFPainter extends AbstractIFPainter { prepareImageMCID(structElem); } drawImageUsingDocument(doc, rect); - flushPDFDoc(); + if (!getDocumentHandler().getPDFDocument().isLinearizationEnabled()) { + flushPDFDoc(); + } } private void flushPDFDoc() throws IFException { diff --git a/src/java/org/apache/fop/render/pdf/PDFRendererConfig.java b/src/java/org/apache/fop/render/pdf/PDFRendererConfig.java index f01cdb717..f25872c4c 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRendererConfig.java +++ b/src/java/org/apache/fop/render/pdf/PDFRendererConfig.java @@ -56,6 +56,7 @@ import static org.apache.fop.render.pdf.PDFEncryptionOption.OWNER_PASSWORD; import static org.apache.fop.render.pdf.PDFEncryptionOption.USER_PASSWORD; import static org.apache.fop.render.pdf.PDFRendererOption.DISABLE_SRGB_COLORSPACE; import static org.apache.fop.render.pdf.PDFRendererOption.FILTER_LIST; +import static org.apache.fop.render.pdf.PDFRendererOption.LINEARIZATION; import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FONTS; import static org.apache.fop.render.pdf.PDFRendererOption.OUTPUT_PROFILE; import static org.apache.fop.render.pdf.PDFRendererOption.PDF_A_MODE; @@ -137,6 +138,7 @@ public final class PDFRendererConfig implements RendererConfig { parseAndPut(OUTPUT_PROFILE, cfg); parseAndPut(DISABLE_SRGB_COLORSPACE, cfg); parseAndPut(MERGE_FONTS, cfg); + parseAndPut(LINEARIZATION, cfg); parseAndPut(VERSION, cfg); } catch (ConfigurationException e) { diff --git a/src/java/org/apache/fop/render/pdf/PDFRendererOption.java b/src/java/org/apache/fop/render/pdf/PDFRendererOption.java index 9c45f981d..cfad76b81 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRendererOption.java +++ b/src/java/org/apache/fop/render/pdf/PDFRendererOption.java @@ -72,6 +72,12 @@ public enum PDFRendererOption implements RendererConfigOption { return Boolean.valueOf(value); } }, + LINEARIZATION("linearization", false) { + @Override + Boolean deserialize(String value) { + return Boolean.valueOf(value); + } + }, /** Rendering Options key for the ICC profile for the output intent. */ OUTPUT_PROFILE("output-profile") { @Override diff --git a/src/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java b/src/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java index 3a1a24339..8124db9f5 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java +++ b/src/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java @@ -31,6 +31,7 @@ import org.apache.fop.pdf.Version; import static org.apache.fop.render.pdf.PDFRendererOption.DISABLE_SRGB_COLORSPACE; import static org.apache.fop.render.pdf.PDFRendererOption.FILTER_LIST; +import static org.apache.fop.render.pdf.PDFRendererOption.LINEARIZATION; import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FONTS; import static org.apache.fop.render.pdf.PDFRendererOption.OUTPUT_PROFILE; import static org.apache.fop.render.pdf.PDFRendererOption.PDF_A_MODE; @@ -125,4 +126,8 @@ public final class PDFRendererOptionsConfig { public Boolean getMergeFontsEnabled() { return (Boolean)properties.get(MERGE_FONTS); } + + public Boolean getLinearizationEnabled() { + return (Boolean)properties.get(LINEARIZATION); + } } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java index 111cca6b2..f93628331 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -588,6 +588,7 @@ class PDFRenderingUtil { this.pdfDoc.enableAccessibility(userAgent.isAccessibilityEnabled()); pdfDoc.setMergeFontsEnabled(rendererConfig.getMergeFontsEnabled()); + pdfDoc.setLinearizationEnabled(rendererConfig.getLinearizationEnabled()); return this.pdfDoc; } diff --git a/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java b/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java index cb2b58c40..19862480b 100644 --- a/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java +++ b/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java @@ -337,7 +337,7 @@ public class PDFDocumentGraphics2D extends PDFGraphics2D { width, height); resourceContext = page; pdfContext.setCurrentPage(page); - pageRef = page.referencePDF(); + pageRef = page.makeReference(); currentStream.write("q\n"); AffineTransform at = new AffineTransform(1.0, 0.0, 0.0, -1.0, diff --git a/src/java/org/apache/fop/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java index dc897f40e..5a0299281 100644 --- a/src/java/org/apache/fop/svg/PDFGraphics2D.java +++ b/src/java/org/apache/fop/svg/PDFGraphics2D.java @@ -88,6 +88,7 @@ import org.apache.fop.pdf.PDFLink; import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFPaintingState; import org.apache.fop.pdf.PDFPattern; +import org.apache.fop.pdf.PDFReference; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; import org.apache.fop.pdf.PDFShading; @@ -132,7 +133,7 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand /** * The PDF reference of the current page. */ - protected String pageRef; + protected PDFReference pageRef; /** * The PDF painting state @@ -212,7 +213,7 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand * @param size the current font size */ public PDFGraphics2D(boolean textAsShapes, FontInfo fi, PDFDocument doc, - PDFResourceContext page, String pref, String font, float size, + PDFResourceContext page, PDFReference pref, String font, float size, TransparencyIgnoredEventListener listener) { this(textAsShapes); pdfDoc = doc; @@ -331,7 +332,7 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand * Gets the PDF reference of the current page. * @return the PDF reference of the current page */ - public String getPageReference() { + public PDFReference getPageReference() { return this.pageRef; } @@ -422,7 +423,7 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand if (linkType != PDFLink.EXTERNAL) { String pdfdest = "/FitR " + dest; resourceContext.addAnnotation( - pdfDoc.getFactory().makeLink(rect, getPageReference(), pdfdest)); + pdfDoc.getFactory().makeLink(rect, getPageReference().toString(), pdfdest)); } else { resourceContext.addAnnotation( pdfDoc.getFactory().makeLink(rect, dest, linkType, 0)); @@ -474,7 +475,7 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand } private void flushPDFDocument() { - if (outputStream != null) { + if (outputStream != null && !pdfDoc.isLinearizationEnabled()) { try { this.pdfDoc.output(outputStream); } catch (IOException ioe) { @@ -1068,13 +1069,13 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand } } - String maskRef = null; + PDFReference maskRef = null; if (mask != null) { BitmapImage fopimg = new BitmapImage( "TempImageMask:" + pctx.toString(), devW, devH, mask, null); fopimg.setColorSpace(new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_GRAY)); PDFImageXObject xobj = pdfDoc.addImage(resourceContext, fopimg); - maskRef = xobj.referencePDF(); + maskRef = xobj.makeReference(); flushPDFDocument(); } diff --git a/test/java/org/apache/fop/pdf/AbstractPDFStreamTestCase.java b/test/java/org/apache/fop/pdf/AbstractPDFStreamTestCase.java index dec62c0a4..ae68cbeb7 100644 --- a/test/java/org/apache/fop/pdf/AbstractPDFStreamTestCase.java +++ b/test/java/org/apache/fop/pdf/AbstractPDFStreamTestCase.java @@ -49,7 +49,7 @@ public class AbstractPDFStreamTestCase extends PDFObjectTestCase { encodedBytes[i++] = (byte) (in & 0xff); } } - private String startStream = "<< /Length 5 0 R /Filter /FlateDecode >>\n" + private String startStream = "<< /Length 1 0 R /Filter /FlateDecode >>\n" + "stream\n"; private String endStream = "endstream"; diff --git a/test/java/org/apache/fop/pdf/ObjectStreamManagerTestCase.java b/test/java/org/apache/fop/pdf/ObjectStreamManagerTestCase.java index 21eb6088f..fda1a006e 100644 --- a/test/java/org/apache/fop/pdf/ObjectStreamManagerTestCase.java +++ b/test/java/org/apache/fop/pdf/ObjectStreamManagerTestCase.java @@ -46,7 +46,7 @@ public class ObjectStreamManagerTestCase { int objectStreamNumber2 = assertSameObjectStream(expectedCapacity, expectedCapacity * 2); int objectStreamNumber3 = assertSameObjectStream(expectedCapacity * 2, numCompressedObjects); assertDifferent(objectStreamNumber1, objectStreamNumber2, objectStreamNumber3); - assertEquals(objectStreamNumber3, pdfDocument.previous.getObjectNumber()); + assertEquals(objectStreamNumber3, pdfDocument.previous.getObjectNumber().getNumber()); } private void createCompressObjectReferences(int numObjects) { @@ -82,8 +82,8 @@ public class ObjectStreamManagerTestCase { private CompressedObject createCompressedObject(final int objectNumber) { return new CompressedObject() { - public int getObjectNumber() { - return objectNumber; + public PDFObjectNumber getObjectNumber() { + return new PDFObjectNumber(objectNumber); } public int output(OutputStream outputStream) throws IOException { @@ -101,7 +101,7 @@ public class ObjectStreamManagerTestCase { } private int getObjectStreamNumber(int index) { - return compressedObjectReferences.get(index).getObjectStreamNumber(); + return compressedObjectReferences.get(index).getObjectStreamNumber().getNumber(); } private void assertDifferent(int objectStreamNumber1, int objectStreamNumber2, diff --git a/test/java/org/apache/fop/pdf/ObjectStreamTestCase.java b/test/java/org/apache/fop/pdf/ObjectStreamTestCase.java index 3512e1147..bf8fe26c8 100644 --- a/test/java/org/apache/fop/pdf/ObjectStreamTestCase.java +++ b/test/java/org/apache/fop/pdf/ObjectStreamTestCase.java @@ -86,13 +86,14 @@ public class ObjectStreamTestCase { private String getExpectedOutput() { int numObs = compressedObjects.size(); - int objectStreamNumber = objectStream.getObjectNumber(); + int objectStreamNumber = objectStream.getObjectNumber().getNumber(); int offsetsLength = 9; StringBuilder expected = new StringBuilder(); expected.append("<<\n"); ObjectStream previous = (ObjectStream) objectStream.get("Extends"); if (previous != null) { expected.append(" /Extends ").append(previous.getObjectNumber()).append(" 0 R\n"); + objectStreamNumber++; } expected.append(" /Type /ObjStm\n") .append(" /N ").append(numObs).append("\n") diff --git a/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java b/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java index e3f8344e0..18d15c384 100644 --- a/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java +++ b/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java @@ -286,7 +286,7 @@ public class PDFEncryptionJCETestCase { public final void testMake() { PDFEncryption testEncryptionObj = createEncryptionObject(new PDFEncryptionParams()); assertTrue(testEncryptionObj instanceof PDFEncryptionJCE); - assertEquals(1, ((PDFEncryptionJCE) testEncryptionObj).getObjectNumber()); + assertEquals(1, ((PDFEncryptionJCE) testEncryptionObj).getObjectNumber().getNumber()); } @Test @@ -608,7 +608,7 @@ public class PDFEncryptionJCETestCase { }; } }; - return (PDFEncryptionJCE) PDFEncryptionJCE.make(1, params, doc); + return (PDFEncryptionJCE) PDFEncryptionJCE.make(new PDFObjectNumber(1), params, doc); } private void runEncryptionTests() throws IOException { diff --git a/test/java/org/apache/fop/pdf/PDFLinearizationTestCase.java b/test/java/org/apache/fop/pdf/PDFLinearizationTestCase.java new file mode 100644 index 000000000..7b78c1f5b --- /dev/null +++ b/test/java/org/apache/fop/pdf/PDFLinearizationTestCase.java @@ -0,0 +1,311 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ +package org.apache.fop.pdf; + +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.geom.Rectangle2D; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.transform.stream.StreamResult; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.FopFactory; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.render.intermediate.IFContext; +import org.apache.fop.render.pdf.PDFContentGenerator; +import org.apache.fop.render.pdf.PDFDocumentHandler; +import org.apache.fop.render.pdf.PDFPainter; + +public class PDFLinearizationTestCase { + private int objectLeast; + private int[] objects; + + @Test + public void testPDF() throws IOException { + PDFDocument doc = new PDFDocument(""); + doc.setLinearizationEnabled(true); + PDFResources resources = new PDFResources(doc); + PDFResourceContext context = new PDFResourceContext(resources); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PDFContentGenerator gen = null; + for (int i = 0; i < 2; i++) { + gen = new PDFContentGenerator(doc, out, context); + Rectangle2D.Float f = new Rectangle2D.Float(); + PDFPage page = new PDFPage(resources, i, f, f, f, f); + doc.registerObject(page); + doc.registerObject(gen.getStream()); + page.setContents(gen.getStream()); + } + gen.flushPDFDoc(); + byte[] data = out.toByteArray(); + checkPDF(data); + } + + @Test + public void testImage() throws Exception { + String fopxconf = "" + + "" + + "true" + + ""; + FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI(), + new ByteArrayInputStream(fopxconf.getBytes())); + FOUserAgent foUserAgent = fopFactory.newFOUserAgent(); + IFContext ifContext = new IFContext(foUserAgent); + PDFDocumentHandler documentHandler = new PDFDocumentHandler(ifContext); + documentHandler.getConfigurator().configure(documentHandler); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + documentHandler.setFontInfo(new FontInfo()); + documentHandler.setResult(new StreamResult(out)); + documentHandler.startDocument(); + documentHandler.startPage(0, "", "", new Dimension()); + PDFPainter pdfPainter = new PDFPainter(documentHandler, null); + pdfPainter.drawImage("test/resources/fop/svg/logo.jpg", new Rectangle()); + documentHandler.endPage(); + Assert.assertFalse(out.toString().contains("/Subtype /Image")); + documentHandler.endDocument(); + Assert.assertTrue(out.toString().contains("/Subtype /Image")); + } + + private void checkPDF(byte[] data) throws IOException { + checkHintTable(data); + InputStream is = new ByteArrayInputStream(data); + Map objs = readObjs(is); + + List keys = new ArrayList(objs.keySet()); + int start = keys.indexOf("1 0 obj"); + Assert.assertTrue(start > 1); + int j = 1; + for (int i = start; i < keys.size(); i++) { + Assert.assertEquals(keys.get(i), j + " 0 obj"); + j++; + } + for (int i = 0; i < start; i++) { + Assert.assertEquals(keys.get(i), j + " 0 obj"); + j++; + } + + checkFirstObj(data); + checkTrailer(data); + + String firstObj = objs.values().iterator().next().toString().replace("\n", ""); + Assert.assertTrue(firstObj.startsWith("<< /Linearized 1 /L " + data.length)); + Assert.assertTrue(firstObj.endsWith("startxref0%%EOF")); + int pageObjNumber = getValue("/O", firstObj); + Assert.assertTrue(objs.get(pageObjNumber + " 0 obj").toString().contains("/Type /Page")); + Assert.assertTrue(objs.get("5 0 obj").toString().contains("/Type /Pages")); + + int total = 0; + for (int i : objects) { + total += i; + } + Assert.assertEquals(total, objs.size() - 6); + } + + private void checkFirstObj(byte[] data) throws IOException { + int firstObjPos = getValue("/E", getFirstObj(data)); + InputStream is = new ByteArrayInputStream(data); + Assert.assertEquals(is.skip(firstObjPos), firstObjPos); + byte[] obj = new byte[10]; + Assert.assertEquals(is.read(obj), obj.length); + Assert.assertTrue(new String(obj).startsWith("1 0 obj")); + } + + private void checkTrailer(byte[] data) throws IOException { + int trailerPos = getValue("/T", getFirstObj(data)); + InputStream is = new ByteArrayInputStream(data); + Assert.assertEquals(is.skip(trailerPos), trailerPos); + byte[] obj = new byte[20]; + Assert.assertEquals(is.read(obj), obj.length); + Assert.assertTrue(new String(obj).startsWith("0000000000 65535 f")); + } + + private int getValue(String name, String firstObj) throws IOException { + String[] split = firstObj.split(" "); + for (int i = 0; i < split.length; i++) { + if (split[i].equals(name)) { + return Integer.valueOf(split[i + 1].replace(">>", "")); + } + } + throw new IOException(name + " not found " + firstObj); + } + + private int[] getArrayValue(String name, String firstObj) throws IOException { + String[] split = firstObj.split(" "); + for (int i = 0; i < split.length; i++) { + if (split[i].equals(name)) { + int[] v = new int[2]; + v[0] = Integer.valueOf(split[i + 1].replace("[", "")); + v[1] = Integer.valueOf(split[i + 2].replace("]", "")); + return v; + } + } + throw new IOException(name + " not found " + firstObj); + } + + private String getFirstObj(byte[] out) throws IOException { + InputStream data = new ByteArrayInputStream(out); + Map objs = readObjs(data); + return objs.values().iterator().next().toString().replace("\n", ""); + } + + private void checkHintTable(byte[] out) throws IOException { + String firstObj = getFirstObj(out); + int hintPos = getArrayValue("/H", firstObj)[0]; + int hintLength = getArrayValue("/H", firstObj)[1]; + + InputStream data = new ByteArrayInputStream(out); + Assert.assertEquals(data.skip(hintPos), hintPos); + + byte[] hintTable = new byte[hintLength]; + Assert.assertEquals(data.read(hintTable), hintLength); + String hintTableStr = new String(hintTable); + + Assert.assertTrue(hintTableStr.contains("/S ")); + Assert.assertTrue(hintTableStr.contains("/C ")); + Assert.assertTrue(hintTableStr.contains("/E ")); + Assert.assertTrue(hintTableStr.contains("/L ")); + Assert.assertTrue(hintTableStr.contains("/V ")); + Assert.assertTrue(hintTableStr.contains("/O ")); + Assert.assertTrue(hintTableStr.contains("/I ")); + Assert.assertTrue(hintTableStr.contains("/Length ")); + Assert.assertTrue(hintTableStr.contains("stream")); + Assert.assertTrue(hintTableStr.contains("endstream")); + Assert.assertTrue(hintTableStr.endsWith("endobj\n")); + + data = new ByteArrayInputStream(hintTable); + readStart(data); + int pages = getValue("/N", firstObj); + readObjectsTable(data, pages); + readSharedObjectsTable(data); + Assert.assertEquals(objectLeast, 1); + } + + private void readObjectsTable(InputStream data, int pages) + throws IOException { + objectLeast = read32(data); + read32(data); + int bitsDiffObjects = read16(data); + read32(data); + int bitsDiffPageLength = read16(data); + read32(data); + read16(data); + read32(data); + read16(data); + read16(data); + read16(data); + read16(data); + read16(data); + + objects = new int[pages]; + for (int i = 0; i < pages; i++) { + objects[i] = objectLeast + readBits(bitsDiffObjects, data); + } + for (int i = 0; i < pages; i++) { + readBits(bitsDiffPageLength, data); + } + for (int i = 0; i < pages; i++) { + readBits(32, data); + } + } + + private void readSharedObjectsTable(InputStream str) throws IOException { + readBits(32, str); + readBits(32, str); + readBits(32, str); + int sharedGroups = readBits(32, str); + readBits(16, str); + readBits(32, str); + int bitsDiffGroupLength = readBits(16, str); + for (int i = 0; i < sharedGroups; i++) { + readBits(bitsDiffGroupLength, str); + } + } + + private int readBits(int bits, InputStream data) throws IOException { + if (bits == 32) { + return read32(data); + } + if (bits == 16) { + return read16(data); + } + throw new IOException("Wrong bits"); + } + + private int read32(InputStream data) throws IOException { + int ch1 = data.read(); + int ch2 = data.read(); + int ch3 = data.read(); + int ch4 = data.read(); + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4)); + } + + private int read16(InputStream data) throws IOException { + int ch1 = data.read(); + int ch2 = data.read(); + return (ch1 << 8) + (ch2); + } + + private void readStart(InputStream inputStream) throws IOException { + StringBuilder sb = new StringBuilder(); + while (inputStream.available() > 0) { + int data = inputStream.read(); + if (data == '\n') { + if (sb.toString().equals("stream")) { + return; + } + sb.setLength(0); + } else { + sb.append((char)data); + } + } + } + + private Map readObjs(InputStream inputStream) throws IOException { + Map objs = new LinkedHashMap(); + StringBuilder sb = new StringBuilder(); + String key = null; + while (inputStream.available() > 0) { + int data = inputStream.read(); + if (data == '\n') { + if (sb.toString().endsWith(" 0 obj")) { + key = sb.toString().trim(); + objs.put(key, new StringBuilder()); + } else if (key != null) { + objs.get(key).append(sb).append("\n"); + } + sb.setLength(0); + } else { + sb.append((char)data); + } + } + return objs; + } +} diff --git a/test/java/org/apache/fop/pdf/PDFObjectTestCase.java b/test/java/org/apache/fop/pdf/PDFObjectTestCase.java index f35d2a15c..544352438 100644 --- a/test/java/org/apache/fop/pdf/PDFObjectTestCase.java +++ b/test/java/org/apache/fop/pdf/PDFObjectTestCase.java @@ -58,10 +58,10 @@ public class PDFObjectTestCase { @Test public void testSetObjectNumber() { pdfObjectUnderTest.setObjectNumber(1); - assertEquals(1, pdfObjectUnderTest.getObjectNumber()); + assertEquals(1, pdfObjectUnderTest.getObjectNumber().getNumber()); pdfObjectUnderTest.setObjectNumber(5); - assertEquals(5, pdfObjectUnderTest.getObjectNumber()); + assertEquals(5, pdfObjectUnderTest.getObjectNumber().getNumber()); } /** @@ -157,12 +157,12 @@ public class PDFObjectTestCase { PDFDictionary dict = new PDFDictionary(); dict.setObjectNumber(7); PDFReference ref = dict.makeReference(); - assertEquals(ref.getObjectNumber(), 7); + assertEquals(ref.getObjectNumber().getNumber(), 7); assertEquals(ref.getGeneration(), 0); assertEquals(ref.toString(), "7 0 R"); ref = new PDFReference("8 0 R"); - assertEquals(ref.getObjectNumber(), 8); + assertEquals(ref.getObjectNumber().getNumber(), 8); assertEquals(ref.getGeneration(), 0); assertEquals(ref.toString(), "8 0 R"); } diff --git a/test/java/org/apache/fop/pdf/xref/CompressedObjectReferenceTestCase.java b/test/java/org/apache/fop/pdf/xref/CompressedObjectReferenceTestCase.java index b7a326c64..b58d2f93e 100644 --- a/test/java/org/apache/fop/pdf/xref/CompressedObjectReferenceTestCase.java +++ b/test/java/org/apache/fop/pdf/xref/CompressedObjectReferenceTestCase.java @@ -27,6 +27,8 @@ import org.junit.Test; import static org.junit.Assert.assertArrayEquals; +import org.apache.fop.pdf.PDFObjectNumber; + public class CompressedObjectReferenceTestCase extends ObjectReferenceTest { @Test @@ -41,7 +43,7 @@ public class CompressedObjectReferenceTestCase extends ObjectReferenceTest { private void runTest(List expectedObjectStreamBytes, int index) throws IOException { int objectStreamNumber = (int) computeNumberFromBytes(expectedObjectStreamBytes); - sut = new CompressedObjectReference(0, objectStreamNumber, index); + sut = new CompressedObjectReference(new PDFObjectNumber(0), new PDFObjectNumber(objectStreamNumber), index); byte[] expected = createExpectedOutput((byte) 2, expectedObjectStreamBytes, index); byte[] actual = getActualOutput(); assertArrayEquals(expected, actual); diff --git a/test/java/org/apache/fop/pdf/xref/CrossReferenceObjectTest.java b/test/java/org/apache/fop/pdf/xref/CrossReferenceObjectTest.java index cd55577cd..513f0d754 100644 --- a/test/java/org/apache/fop/pdf/xref/CrossReferenceObjectTest.java +++ b/test/java/org/apache/fop/pdf/xref/CrossReferenceObjectTest.java @@ -52,7 +52,7 @@ public abstract class CrossReferenceObjectTest { pdfDocument = new PDFDocument("Apache FOP"); Map> filterMap = pdfDocument.getFilterMap(); filterMap.put("default", Arrays.asList("null")); - PDFRoot root = new PDFRoot(1, new PDFPages(10)); + PDFRoot root = new PDFRoot(pdfDocument, new PDFPages(pdfDocument)); PDFInfo info = new PDFInfo(); info.setObjectNumber(2); byte[] fileID = diff --git a/test/java/org/apache/fop/pdf/xref/CrossReferenceStreamTestCase.java b/test/java/org/apache/fop/pdf/xref/CrossReferenceStreamTestCase.java index 3e609635d..e0e8aef78 100644 --- a/test/java/org/apache/fop/pdf/xref/CrossReferenceStreamTestCase.java +++ b/test/java/org/apache/fop/pdf/xref/CrossReferenceStreamTestCase.java @@ -29,6 +29,8 @@ import java.util.List; import org.junit.Test; +import org.apache.fop.pdf.PDFObjectNumber; + public class CrossReferenceStreamTestCase extends CrossReferenceObjectTest { private List uncompressedObjectOffsets; @@ -54,7 +56,7 @@ public class CrossReferenceStreamTestCase extends CrossReferenceObjectTest { @Test public void testWithObjectStreams1() throws IOException { List compressedObjectReferences = - Arrays.asList(new CompressedObjectReference(2, 1, 0)); + Arrays.asList(new CompressedObjectReference(new PDFObjectNumber(2), new PDFObjectNumber(1), 0)); test(Arrays.asList(0L, null), compressedObjectReferences); } @@ -72,8 +74,8 @@ public class CrossReferenceStreamTestCase extends CrossReferenceObjectTest { for (int index = 0; index < numCompressedObjects; index++) { indirectObjectOffsets.add(null); int obNum = numIndirectObjects + index + 1; - compressedObjectReferences.add(new CompressedObjectReference(obNum, - numIndirectObjects, index)); + compressedObjectReferences.add(new CompressedObjectReference(new PDFObjectNumber(obNum), + new PDFObjectNumber(numIndirectObjects), index)); } test(indirectObjectOffsets, compressedObjectReferences); } @@ -108,7 +110,7 @@ public class CrossReferenceStreamTestCase extends CrossReferenceObjectTest { objectReferences.add(offset == null ? null : new UncompressedObjectReference(offset)); } for (CompressedObjectReference ref : compressedObjectReferences) { - objectReferences.set(ref.getObjectNumber() - 1, ref); + objectReferences.set(ref.getObjectNumber().getNumber() - 1, ref); } int maxObjectNumber = objectReferences.size() + 1; ByteArrayOutputStream stream = new ByteArrayOutputStream(); diff --git a/test/java/org/apache/fop/pdf/xref/CrossReferenceTableTestCase.java b/test/java/org/apache/fop/pdf/xref/CrossReferenceTableTestCase.java index 12f6e3c1b..1c609e30f 100644 --- a/test/java/org/apache/fop/pdf/xref/CrossReferenceTableTestCase.java +++ b/test/java/org/apache/fop/pdf/xref/CrossReferenceTableTestCase.java @@ -53,7 +53,7 @@ public class CrossReferenceTableTestCase extends CrossReferenceObjectTest { @Override protected CrossReferenceObject createCrossReferenceObject() { - return new CrossReferenceTable(trailerDictionary, STARTXREF, offsets); + return new CrossReferenceTable(trailerDictionary, STARTXREF, offsets, 0, offsets.size(), offsets.size()); } @Override -- 2.39.5