diff options
author | Vincent Hennebert <vhennebert@apache.org> | 2012-03-21 15:12:43 +0000 |
---|---|---|
committer | Vincent Hennebert <vhennebert@apache.org> | 2012-03-21 15:12:43 +0000 |
commit | b9fbfa6b71b5e0d67806c066ea422819de08f3b2 (patch) | |
tree | ea0b24983c14733ec584a04b79ac108634a9cef2 /src/java/org/apache | |
parent | 54b6471e8fe27378222389de496721cc379a812d (diff) | |
download | xmlgraphics-fop-b9fbfa6b71b5e0d67806c066ea422819de08f3b2.tar.gz xmlgraphics-fop-b9fbfa6b71b5e0d67806c066ea422819de08f3b2.zip |
Added support for PDF object streams.Temp_PDF_ObjectStreams
When accessibility is enabled and PDF version 1.5 selected, the structure tree will be stored in object streams in order to reduce the size of the final PDF.
This can lead to file reductions by up to 75%
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_PDF_ObjectStreams@1303431 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache')
57 files changed, 1051 insertions, 398 deletions
diff --git a/src/java/org/apache/fop/pdf/AbstractPDFStream.java b/src/java/org/apache/fop/pdf/AbstractPDFStream.java index 1b25c113f..0181728b8 100644 --- a/src/java/org/apache/fop/pdf/AbstractPDFStream.java +++ b/src/java/org/apache/fop/pdf/AbstractPDFStream.java @@ -29,16 +29,48 @@ import org.apache.fop.util.CloseBlockerOutputStream; /** * This is an abstract base class for PDF streams. */ -public abstract class AbstractPDFStream extends PDFDictionary { +public abstract class AbstractPDFStream extends PDFObject { + + private final PDFDictionary dictionary; /** The filters that should be applied */ private PDFFilterList filters; + private final boolean encodeOnTheFly; + + protected AbstractPDFStream() { + this(true); + } + + protected AbstractPDFStream(PDFDictionary dictionary) { + this(dictionary, true); + } + + protected AbstractPDFStream(boolean encodeOnTheFly) { + this(new PDFDictionary(), encodeOnTheFly); + } + + protected AbstractPDFStream(PDFDictionary dictionary, boolean encodeOnTheFly) { + this.dictionary = dictionary; + this.encodeOnTheFly = encodeOnTheFly; + } + + protected final PDFDictionary getDictionary() { + return dictionary; + } + + protected Object get(String key) { + return dictionary.get(key); + } + /** - * Constructor for AbstractPDFStream. + * Puts the given object in the dictionary associated to this stream. + * + * @param key the key in the dictionary + * @param value the value to store */ - public AbstractPDFStream() { - super(); + public void put(String key, Object value) { + dictionary.put(key, value); } /** @@ -180,17 +212,16 @@ public abstract class AbstractPDFStream extends PDFDictionary { * {@inheritDoc} */ @Override - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { setupFilterList(); CountingOutputStream cout = new CountingOutputStream(stream); StringBuilder textBuffer = new StringBuilder(64); - textBuffer.append(getObjectID()); StreamCache encodedStream = null; PDFNumber refLength = null; final Object lengthEntry; - if (isEncodingOnTheFly()) { + if (encodeOnTheFly) { refLength = new PDFNumber(); getDocumentSafely().registerObject(refLength); lengthEntry = refLength; @@ -200,7 +231,7 @@ public abstract class AbstractPDFStream extends PDFDictionary { } populateStreamDict(lengthEntry); - writeDictionary(cout, textBuffer); + dictionary.writeDictionary(cout, textBuffer); //Send encoded stream to target OutputStream PDFDocument.flushTextBuffer(textBuffer, cout); @@ -211,18 +242,14 @@ public abstract class AbstractPDFStream extends PDFDictionary { encodedStream.clear(); //Encoded stream can now be discarded } - textBuffer.append("\nendobj\n"); PDFDocument.flushTextBuffer(textBuffer, cout); return cout.getCount(); } - /** - * Indicates whether encoding may happen without buffering the encoded data. If this method - * returns true, the /Length entry will be an indirect object, a direct object otherwise. - * @return true if encoding should happen "on the fly" - */ - protected boolean isEncodingOnTheFly() { - return getDocument().isEncodingOnTheFly(); + @Override + public void setDocument(PDFDocument doc) { + dictionary.setDocument(doc); + super.setDocument(doc); } /** @@ -233,7 +260,7 @@ public abstract class AbstractPDFStream extends PDFDictionary { protected void populateStreamDict(Object lengthEntry) { put("Length", lengthEntry); if (!getFilterList().isDisableAllFilters()) { - getFilterList().putFilterDictEntries(this); + getFilterList().putFilterDictEntries(dictionary); } } diff --git a/src/java/org/apache/fop/pdf/CompressedObject.java b/src/java/org/apache/fop/pdf/CompressedObject.java new file mode 100644 index 000000000..55d9c2953 --- /dev/null +++ b/src/java/org/apache/fop/pdf/CompressedObject.java @@ -0,0 +1,49 @@ +/* + * 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.IOException; +import java.io.OutputStream; + +/** + * Represents a PDF object that may appear in an object stream. An object stream is a PDF + * stream whose content is a sequence of PDF objects. See Section 3.4.6 of the PDF 1.5 + * Reference. + */ +interface CompressedObject { + + /** + * Returns the object number of this indirect object. Note that a compressed object + * must have a generation number of 0. + * + * @return the object number. + */ + int getObjectNumber(); + + /** + * Outputs this object's content into the given stream. + * + * @param outputStream a stream, likely to be provided by the containing object stream + * @return the number of bytes written to the stream + * @throws IOException + */ + int output(OutputStream outputStream) throws IOException; + +} diff --git a/src/java/org/apache/fop/pdf/ObjectStream.java b/src/java/org/apache/fop/pdf/ObjectStream.java new file mode 100644 index 000000000..e9335bc6f --- /dev/null +++ b/src/java/org/apache/fop/pdf/ObjectStream.java @@ -0,0 +1,85 @@ +/* + * 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.List; + +import org.apache.fop.pdf.xref.CompressedObjectReference; + +/** + * An object stream, as described in section 3.4.6 of the PDF 1.5 Reference. + */ +public class ObjectStream extends PDFStream { + + private static final PDFName OBJ_STM = new PDFName("ObjStm"); + + private List<CompressedObject> objects = new ArrayList<CompressedObject>(); + + private int firstObjectOffset; + + ObjectStream() { + super(false); + } + + ObjectStream(ObjectStream previous) { + this(); + put("Extends", previous); + } + + CompressedObjectReference addObject(CompressedObject obj) { + if (obj == null) { + throw new NullPointerException("obj must not be null"); + } + CompressedObjectReference reference = new CompressedObjectReference(obj.getObjectNumber(), + getObjectNumber(), objects.size()); + objects.add(obj); + return reference; + } + + @Override + protected void outputRawStreamData(OutputStream out) throws IOException { + int currentOffset = 0; + StringBuilder offsetsPart = new StringBuilder(); + ByteArrayOutputStream streamContent = new ByteArrayOutputStream(); + for (CompressedObject object : objects) { + offsetsPart.append(object.getObjectNumber()) + .append(' ') + .append(currentOffset) + .append('\n'); + currentOffset += object.output(streamContent); + } + byte[] offsets = PDFDocument.encode(offsetsPart.toString()); + firstObjectOffset = offsets.length; + out.write(offsets); + streamContent.writeTo(out); + } + + @Override + protected void populateStreamDict(Object lengthEntry) { + put("Type", OBJ_STM); + put("N", objects.size()); + put("First", firstObjectOffset); + super.populateStreamDict(lengthEntry); + } +} diff --git a/src/java/org/apache/fop/pdf/ObjectStreamManager.java b/src/java/org/apache/fop/pdf/ObjectStreamManager.java new file mode 100644 index 000000000..723facd96 --- /dev/null +++ b/src/java/org/apache/fop/pdf/ObjectStreamManager.java @@ -0,0 +1,69 @@ +/* + * 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.util.ArrayList; +import java.util.List; + +import org.apache.fop.pdf.xref.CompressedObjectReference; + +/** + * Manages a collection of object streams, creating new streams as necessary to keep the + * number of objects in each stream at the recommended value. Streams are related to each + * other through the use of the Extends entry in the stream dictionary. + */ +class ObjectStreamManager { + + private static final int OBJECT_STREAM_CAPACITY = 100; + + private final PDFDocument pdfDocument; + + private final List<CompressedObjectReference> compressedObjectReferences; + + private int numObjectsInStream; + + private ObjectStream currentObjectStream; + + ObjectStreamManager(PDFDocument pdfDocument) { + this.pdfDocument = pdfDocument; + createObjectStream(); + compressedObjectReferences = new ArrayList<CompressedObjectReference>(); + } + + void add(CompressedObject compressedObject) { + if (numObjectsInStream++ == OBJECT_STREAM_CAPACITY) { + createObjectStream(); + numObjectsInStream = 1; + } + compressedObjectReferences.add(currentObjectStream.addObject(compressedObject)); + } + + private void createObjectStream() { + currentObjectStream = currentObjectStream == null + ? new ObjectStream() + : new ObjectStream(currentObjectStream); + pdfDocument.assignObjectNumber(currentObjectStream); + pdfDocument.addTrailerObject(currentObjectStream); + } + + List<CompressedObjectReference> getCompressedObjectReferences() { + return compressedObjectReferences; + } +} diff --git a/src/java/org/apache/fop/pdf/PDFAnnotList.java b/src/java/org/apache/fop/pdf/PDFAnnotList.java index 0a8710627..65b327e31 100644 --- a/src/java/org/apache/fop/pdf/PDFAnnotList.java +++ b/src/java/org/apache/fop/pdf/PDFAnnotList.java @@ -58,22 +58,19 @@ public class PDFAnnotList extends PDFObject { */ public String toPDFString() { StringBuffer p = new StringBuffer(128); - p.append(getObjectID()); p.append("[\n"); for (int i = 0; i < getCount(); i++) { p.append(((PDFObject)links.get(i)).referencePDF()); p.append("\n"); } - p.append("]\nendobj\n"); + p.append("]"); return p.toString(); } /* * example - * 20 0 obj * [ * 19 0 R * ] - * endobj */ } diff --git a/src/java/org/apache/fop/pdf/PDFArray.java b/src/java/org/apache/fop/pdf/PDFArray.java index 78eba3bb9..131830328 100644 --- a/src/java/org/apache/fop/pdf/PDFArray.java +++ b/src/java/org/apache/fop/pdf/PDFArray.java @@ -21,7 +21,6 @@ package org.apache.fop.pdf; import java.io.IOException; import java.io.OutputStream; -import java.util.Collection; import java.util.List; import org.apache.commons.io.output.CountingOutputStream; @@ -48,7 +47,7 @@ public class PDFArray extends PDFObject { * Create a new, empty array object with no parent. */ public PDFArray() { - this(null); + this((PDFObject) null); } /** @@ -84,7 +83,7 @@ public class PDFArray extends PDFObject { * @param parent the array's parent if any * @param values the actual values wrapped by this object */ - public PDFArray(PDFObject parent, Collection<Object> values) { + public PDFArray(PDFObject parent, List<?> values) { /* generic creation of PDF object */ super(parent); @@ -92,6 +91,15 @@ public class PDFArray extends PDFObject { } /** + * Creates an array object made of the given elements. + * + * @param elements the array content + */ + public PDFArray(Object... elements) { + this(null, elements); + } + + /** * Create the array object * @param parent the array's parent if any * @param values the actual array wrapped by this object @@ -180,13 +188,9 @@ public class PDFArray extends PDFObject { /** {@inheritDoc} */ @Override - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { CountingOutputStream cout = new CountingOutputStream(stream); StringBuilder textBuffer = new StringBuilder(64); - if (hasObjectNumber()) { - textBuffer.append(getObjectID()); - } - textBuffer.append('['); for (int i = 0; i < values.size(); i++) { if (i > 0) { @@ -196,11 +200,6 @@ public class PDFArray extends PDFObject { formatObject(obj, cout, textBuffer); } textBuffer.append(']'); - - if (hasObjectNumber()) { - textBuffer.append("\nendobj\n"); - } - PDFDocument.flushTextBuffer(textBuffer, cout); return cout.getCount(); } diff --git a/src/java/org/apache/fop/pdf/PDFCIDFont.java b/src/java/org/apache/fop/pdf/PDFCIDFont.java index 459fe2584..46374d869 100644 --- a/src/java/org/apache/fop/pdf/PDFCIDFont.java +++ b/src/java/org/apache/fop/pdf/PDFCIDFont.java @@ -200,7 +200,6 @@ public class PDFCIDFont extends PDFObject { */ public String toPDFString() { StringBuffer p = new StringBuffer(128); - p.append(getObjectID()); p.append("<< /Type /Font"); p.append("\n/BaseFont /"); p.append(this.basefont); @@ -234,14 +233,13 @@ public class PDFCIDFont extends PDFObject { p.append("\n/DW2 ["); // always two values, see p 211 p.append(this.dw2[0]); p.append(this.dw2[1]); - p.append("] \n>>\nendobj\n"); + p.append("]"); } if (w2 != null) { p.append("\n/W2 "); p.append(w2.toPDFString()); - p.append(" \n>>\nendobj\n"); } - p.append(" \n>>\nendobj\n"); + p.append("\n>>"); return p.toString(); } diff --git a/src/java/org/apache/fop/pdf/PDFCMap.java b/src/java/org/apache/fop/pdf/PDFCMap.java index 57d148fc2..34bc0f952 100644 --- a/src/java/org/apache/fop/pdf/PDFCMap.java +++ b/src/java/org/apache/fop/pdf/PDFCMap.java @@ -423,7 +423,7 @@ public class PDFCMap extends PDFStream { } /** {@inheritDoc} */ - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { CMapBuilder builder = createCMapBuilder(getBufferWriter()); builder.writeCMap(); return super.output(stream); diff --git a/src/java/org/apache/fop/pdf/PDFDestination.java b/src/java/org/apache/fop/pdf/PDFDestination.java index 400c4a4b6..21c655832 100644 --- a/src/java/org/apache/fop/pdf/PDFDestination.java +++ b/src/java/org/apache/fop/pdf/PDFDestination.java @@ -50,9 +50,8 @@ public class PDFDestination extends PDFObject { this.idRef = idRef; } - /** {@inheritDoc} */ @Override - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { CountingOutputStream cout = new CountingOutputStream(stream); StringBuilder textBuffer = new StringBuilder(64); diff --git a/src/java/org/apache/fop/pdf/PDFDictionary.java b/src/java/org/apache/fop/pdf/PDFDictionary.java index 5a6724304..6bacd31a3 100644 --- a/src/java/org/apache/fop/pdf/PDFDictionary.java +++ b/src/java/org/apache/fop/pdf/PDFDictionary.java @@ -98,19 +98,10 @@ public class PDFDictionary extends PDFObject { /** {@inheritDoc} */ @Override - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { CountingOutputStream cout = new CountingOutputStream(stream); StringBuilder textBuffer = new StringBuilder(64); - if (hasObjectNumber()) { - textBuffer.append(getObjectID()); - } - writeDictionary(cout, textBuffer); - - if (hasObjectNumber()) { - textBuffer.append("\nendobj\n"); - } - PDFDocument.flushTextBuffer(textBuffer, cout); return cout.getCount(); } diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index e9886fc37..9850c605e 100644 --- a/src/java/org/apache/fop/pdf/PDFDocument.java +++ b/src/java/org/apache/fop/pdf/PDFDocument.java @@ -26,6 +26,7 @@ import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -37,6 +38,10 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fop.pdf.xref.CrossReferenceStream; +import org.apache.fop.pdf.xref.CrossReferenceTable; +import org.apache.fop.pdf.xref.TrailerDictionary; + /* image support modified from work of BoBoGi */ /* font support based on work by Takayuki Takeuchi */ @@ -63,31 +68,28 @@ import org.apache.commons.logging.LogFactory; */ public class PDFDocument { - private static final Long LOCATION_PLACEHOLDER = new Long(0); - /** the encoding to use when converting strings to PDF commands */ public static final String ENCODING = "ISO-8859-1"; /** the counter for object numbering */ - protected int objectcount = 0; + protected int objectcount; /** the logger instance */ private Log log = LogFactory.getLog("org.apache.fop.pdf"); /** the current character position */ - private long position = 0; - - /** character position of xref table */ - private long xref; + private long position; /** the character position of each object */ - private List<Long> location = new ArrayList<Long>(); + private List<Long> indirectObjectOffsets = new ArrayList<Long>(); + + private Collection<PDFStructElem> structureTreeElements; /** List of objects to write in the trailer */ - private List trailerObjects = new ArrayList(); + private List<PDFObject> trailerObjects = new ArrayList<PDFObject>(); /** the objects themselves */ - private List objects = new LinkedList(); + private List<PDFObject> objects = new LinkedList<PDFObject>(); /** Controls the PDF version of this document */ private VersionController versionController; @@ -99,7 +101,7 @@ public class PDFDocument { private PDFRoot root; /** The root outline object */ - private PDFOutline outlineRoot = null; + private PDFOutline outlineRoot; /** The /Pages object (mark-fop@inomial.com) */ private PDFPages pages; @@ -118,66 +120,47 @@ public class PDFDocument { = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); /** the counter for Pattern name numbering (e.g. 'Pattern1') */ - private int patternCount = 0; + private int patternCount; /** the counter for Shading name numbering */ - private int shadingCount = 0; + private int shadingCount; /** the counter for XObject numbering */ - private int xObjectCount = 0; + private int xObjectCount; - /** the {@link PDFXObject}s map */ /* TODO: Should be modified (works only for image subtype) */ - private Map xObjectsMap = new HashMap(); - - /** The {@link PDFFont} map */ - private Map fontMap = new HashMap(); + private Map<String, PDFXObject> xObjectsMap = new HashMap<String, PDFXObject>(); - /** The {@link PDFFilter} map */ - private Map filterMap = new HashMap(); + private Map<String, PDFFont> fontMap = new HashMap<String, PDFFont>(); - /** List of {@link PDFGState}s. */ - private List gstates = new ArrayList(); + private Map<String, List<String>> filterMap = new HashMap<String, List<String>>(); - /** List of {@link PDFFunction}s. */ - private List functions = new ArrayList(); + private List<PDFGState> gstates = new ArrayList<PDFGState>(); - /** List of {@link PDFShading}s. */ - private List shadings = new ArrayList(); + private List<PDFFunction> functions = new ArrayList<PDFFunction>(); - /** List of {@link PDFPattern}s. */ - private List patterns = new ArrayList(); + private List<PDFShading> shadings = new ArrayList<PDFShading>(); - /** List of {@link PDFLink}s. */ - private List links = new ArrayList(); + private List<PDFPattern> patterns = new ArrayList<PDFPattern>(); - /** List of {@link PDFDestination}s. */ - private List destinations; + private List<PDFLink> links = new ArrayList<PDFLink>(); - /** List of {@link PDFFileSpec}s. */ - private List filespecs = new ArrayList(); + private List<PDFDestination> destinations; - /** List of {@link PDFGoToRemote}s. */ - private List gotoremotes = new ArrayList(); + private List<PDFFileSpec> filespecs = new ArrayList<PDFFileSpec>(); - /** List of {@link PDFGoTo}s. */ - private List gotos = new ArrayList(); + private List<PDFGoToRemote> gotoremotes = new ArrayList<PDFGoToRemote>(); - /** List of {@link PDFLaunch}es. */ - private List launches = new ArrayList(); + private List<PDFGoTo> gotos = new ArrayList<PDFGoTo>(); - /** - * The PDFDests object for the name dictionary. - * Note: This object is not a list. - */ - private PDFDests dests; + private List<PDFLaunch> launches = new ArrayList<PDFLaunch>(); private PDFFactory factory; - private boolean encodingOnTheFly = true; - private FileIDGenerator fileIDGenerator; + private boolean accessibilityEnabled; + /** * Creates an empty PDF document. * @@ -266,17 +249,6 @@ public class PDFDocument { } /** - * Indicates whether stream encoding on-the-fly is enabled. If enabled - * stream can be serialized without the need for a buffer to merely - * calculate the stream length. - * - * @return <code>true</code> if on-the-fly encoding is enabled - */ - public boolean isEncodingOnTheFly() { - return this.encodingOnTheFly; - } - - /** * Converts text to a byte array for writing to a PDF file. * * @param text text to convert/encode @@ -336,7 +308,7 @@ public class PDFDocument { * * @param map the map of filter lists for each stream type */ - public void setFilterMap(Map map) { + public void setFilterMap(Map<String, List<String>> map) { this.filterMap = map; } @@ -345,7 +317,7 @@ public class PDFDocument { * * @return the map of filters being used */ - public Map getFilterMap() { + public Map<String, List<String>> getFilterMap() { return this.filterMap; } @@ -368,6 +340,37 @@ public class PDFDocument { } /** + * Creates and returns a StructTreeRoot object. + * + * @param parentTree the value of the ParenTree entry + * @return the structure tree root + */ + public PDFStructTreeRoot makeStructTreeRoot(PDFParentTree parentTree) { + PDFStructTreeRoot structTreeRoot = new PDFStructTreeRoot(parentTree); + assignObjectNumber(structTreeRoot); + addTrailerObject(structTreeRoot); + root.setStructTreeRoot(structTreeRoot); + structureTreeElements = new ArrayList<PDFStructElem>(); + return structTreeRoot; + } + + /** + * Creates and returns a structure element. + * + * @param structureType the structure type of the new element (value for the + * S entry) + * @param parent the parent of the new structure element in the structure + * hierarchy + * @return a dictionary of type StructElem + */ + public PDFStructElem makeStructureElement(PDFName structureType, PDFObject parent) { + PDFStructElem structElem = new PDFStructElem(parent, structureType); + assignObjectNumber(structElem); + structureTreeElements.add(structElem); + return structElem; + } + + /** * Get the {@link PDFInfo} object for this document. * * @return the {@link PDFInfo} object @@ -439,39 +442,39 @@ public class PDFDocument { //Add object to special lists where necessary if (obj instanceof PDFFunction) { - this.functions.add(obj); + this.functions.add((PDFFunction) obj); } if (obj instanceof PDFShading) { final String shadingName = "Sh" + (++this.shadingCount); ((PDFShading)obj).setName(shadingName); - this.shadings.add(obj); + this.shadings.add((PDFShading) obj); } if (obj instanceof PDFPattern) { final String patternName = "Pa" + (++this.patternCount); ((PDFPattern)obj).setName(patternName); - this.patterns.add(obj); + this.patterns.add((PDFPattern) obj); } if (obj instanceof PDFFont) { final PDFFont font = (PDFFont)obj; this.fontMap.put(font.getName(), font); } if (obj instanceof PDFGState) { - this.gstates.add(obj); + this.gstates.add((PDFGState) obj); } if (obj instanceof PDFPage) { this.pages.notifyKidRegistered((PDFPage)obj); } if (obj instanceof PDFLaunch) { - this.launches.add(obj); + this.launches.add((PDFLaunch) obj); } if (obj instanceof PDFLink) { - this.links.add(obj); + this.links.add((PDFLink) obj); } if (obj instanceof PDFFileSpec) { - this.filespecs.add(obj); + this.filespecs.add((PDFFileSpec) obj); } if (obj instanceof PDFGoToRemote) { - this.gotoremotes.add(obj); + this.gotoremotes.add((PDFGoToRemote) obj); } } @@ -485,7 +488,7 @@ public class PDFDocument { this.trailerObjects.add(obj); if (obj instanceof PDFGoTo) { - this.gotos.add(obj); + this.gotos.add((PDFGoTo) obj); } } @@ -537,9 +540,8 @@ public class PDFDocument { return this.encryption; } - private Object findPDFObject(List list, PDFObject compare) { - for (Iterator iter = list.iterator(); iter.hasNext();) { - PDFObject obj = (PDFObject) iter.next(); + private Object findPDFObject(List<? extends PDFObject> list, PDFObject compare) { + for (PDFObject obj : list) { if (compare.contentEquals(obj)) { return obj; } @@ -589,7 +591,7 @@ public class PDFDocument { * @return PDFFont the requested font, null if it wasn't found */ protected PDFFont findFont(String fontname) { - return (PDFFont)this.fontMap.get(fontname); + return this.fontMap.get(fontname); } /** @@ -601,7 +603,7 @@ public class PDFDocument { protected PDFDestination findDestination(PDFDestination compare) { int index = getDestinationList().indexOf(compare); if (index >= 0) { - return (PDFDestination)getDestinationList().get(index); + return getDestinationList().get(index); } else { return null; } @@ -666,9 +668,9 @@ public class PDFDocument { */ protected PDFGState findGState(PDFGState wanted, PDFGState current) { PDFGState poss; - Iterator iter = this.gstates.iterator(); + Iterator<PDFGState> iter = this.gstates.iterator(); while (iter.hasNext()) { - PDFGState avail = (PDFGState)iter.next(); + PDFGState avail = iter.next(); poss = new PDFGState(); poss.addValues(current); poss.addValues(avail); @@ -712,7 +714,7 @@ public class PDFDocument { * * @return the map of fonts used in this document */ - public Map getFontMap() { + public Map<String, PDFFont> getFontMap() { return this.fontMap; } @@ -753,16 +755,7 @@ public class PDFDocument { * @return the PDFXObject for the key if found */ public PDFXObject getXObject(String key) { - return (PDFXObject)this.xObjectsMap.get(key); - } - - /** - * Gets the PDFDests object (which represents the /Dests entry). - * - * @return the PDFDests object (which represents the /Dests entry). - */ - public PDFDests getDests() { - return this.dests; + return this.xObjectsMap.get(key); } /** @@ -771,7 +764,7 @@ public class PDFDocument { */ public void addDestination(PDFDestination destination) { if (this.destinations == null) { - this.destinations = new ArrayList(); + this.destinations = new ArrayList<PDFDestination>(); } this.destinations.add(destination); } @@ -781,11 +774,11 @@ public class PDFDocument { * * @return the list of named destinations. */ - public List getDestinationList() { + public List<PDFDestination> getDestinationList() { if (hasDestinations()) { return this.destinations; } else { - return Collections.EMPTY_LIST; + return Collections.emptyList(); } } @@ -900,17 +893,8 @@ public class PDFDocument { return this.resources; } - /** - * Ensure there is room in the locations xref for the number of - * objects that have been created. - * @param objidx the object's index - * @param position the position - */ - private void setLocation(int objidx, long position) { - while (this.location.size() <= objidx) { - this.location.add(LOCATION_PLACEHOLDER); - } - this.location.set(objidx, position); + public void enableAccessibility(boolean enableAccessibility) { + this.accessibilityEnabled = enableAccessibility; } /** @@ -924,23 +908,50 @@ public class PDFDocument { //LinkedList) allows for output() methods to create and register objects //on the fly even during serialization. while (this.objects.size() > 0) { - /* Retrieve first */ - PDFObject object = (PDFObject)this.objects.remove(0); - /* - * add the position of this object to the list of object - * locations - */ - setLocation(object.getObjectNumber() - 1, this.position); - - /* - * output the object and increment the character position - * by the object's length - */ - this.position += object.output(stream); + PDFObject object = this.objects.remove(0); + streamIndirectObject(object, stream); } + } - //Clear all objects written to the file - //this.objects.clear(); + private void streamIndirectObject(PDFObject o, OutputStream stream) throws IOException { + recordObjectOffset(o); + this.position += outputIndirectObject(o, stream); + } + + private void streamIndirectObjects(Collection<? extends PDFObject> objects, OutputStream stream) + throws IOException { + for (PDFObject o : objects) { + streamIndirectObject(o, stream); + } + } + + private void recordObjectOffset(PDFObject object) { + int index = object.getObjectNumber() - 1; + while (indirectObjectOffsets.size() <= index) { + indirectObjectOffsets.add(null); + } + indirectObjectOffsets.set(index, position); + } + + /** + * Outputs the given object, wrapped by obj/endobj, to the given stream. + * + * @param object an indirect object, as described in Section 3.2.9 of the PDF 1.5 + * Reference. + * @param stream the stream to which the object must be output + * @throws IllegalArgumentException if the object is not an indirect object + */ + public static int outputIndirectObject(PDFObject object, OutputStream stream) + throws IOException { + if (!object.hasObjectNumber()) { + throw new IllegalArgumentException("Not an indirect object"); + } + byte[] obj = encode(object.getObjectID()); + stream.write(obj); + int length = object.output(stream); + byte[] endobj = encode("\nendobj\n"); + stream.write(endobj); + return obj.length + length + endobj.length; } /** @@ -980,89 +991,102 @@ public class PDFDocument { * @throws IOException if there is an exception writing to the output stream */ public void outputTrailer(OutputStream stream) throws IOException { + createDestinations(); + output(stream); + outputTrailerObjectsAndXref(stream); + } + + private void createDestinations() { if (hasDestinations()) { Collections.sort(this.destinations, new DestinationComparator()); - this.dests = getFactory().makeDests(this.destinations); + PDFDests dests = getFactory().makeDests(this.destinations); if (this.root.getNames() == null) { this.root.setNames(getFactory().makeNames()); } this.root.getNames().setDests(dests); } - output(stream); - for (int count = 0; count < this.trailerObjects.size(); count++) { - PDFObject o = (PDFObject)this.trailerObjects.get(count); - setLocation(o.getObjectNumber() - 1, this.position); - this.position += o.output(stream); + } + + private void outputTrailerObjectsAndXref(OutputStream stream) throws IOException { + TrailerOutputHelper trailerOutputHelper = mayCompressStructureTreeElements() + ? new CompressedTrailerOutputHelper() + : new UncompressedTrailerOutputHelper(); + if (structureTreeElements != null) { + trailerOutputHelper.outputStructureTreeElements(stream); } - /* output the xref table and increment the character position - by the table's length */ - this.position += outputXref(stream); - - /* construct the trailer */ - StringBuffer pdf = new StringBuffer(128); - pdf.append("trailer\n<<\n/Size ") - .append(this.objectcount + 1) - .append("\n/Root ") - .append(this.root.referencePDF()) - .append("\n/Info ") - .append(this.info.referencePDF()) - .append('\n'); - - if (this.isEncryptionActive()) { - pdf.append(this.encryption.getTrailerEntry()); - } else { - byte[] fileID = getFileIDGenerator().getOriginalFileID(); - String fileIDAsString = PDFText.toHex(fileID); - pdf.append("/ID [" + fileIDAsString + " " + fileIDAsString + "]"); + streamIndirectObjects(trailerObjects, stream); + TrailerDictionary trailerDictionary = createTrailerDictionary(); + long startxref = trailerOutputHelper.outputCrossReferenceObject(stream, trailerDictionary); + String trailer = "startxref\n" + startxref + "\n%%EOF\n"; + stream.write(encode(trailer)); + } + + private boolean mayCompressStructureTreeElements() { + return accessibilityEnabled + && versionController.getPDFVersion().compareTo(Version.V1_5) >= 0; + } + + private TrailerDictionary createTrailerDictionary() { + FileIDGenerator gen = getFileIDGenerator(); + TrailerDictionary trailerDictionary = new TrailerDictionary(this) + .setRoot(root) + .setInfo(info) + .setFileID(gen.getOriginalFileID(), gen.getUpdatedFileID()); + if (isEncryptionActive()) { + trailerDictionary.setEncryption(encryption); } + return trailerDictionary; + } - pdf.append("\n>>\nstartxref\n") - .append(this.xref) - .append("\n%%EOF\n"); + private interface TrailerOutputHelper { - /* write the trailer */ - stream.write(encode(pdf.toString())); + void outputStructureTreeElements(OutputStream stream) throws IOException; + + /** + * @return the offset of the cross-reference object (the value of startxref) + */ + long outputCrossReferenceObject(OutputStream stream, TrailerDictionary trailerDictionary) + throws IOException; } - /** - * Write the xref table - * - * @param stream the OutputStream to write the xref table to - * @return the number of characters written - * @throws IOException in case of an error writing the result to - * the parameter stream - */ - private int outputXref(OutputStream stream) throws IOException { - - /* remember position of xref table */ - this.xref = this.position; - - /* construct initial part of xref */ - StringBuffer pdf = new StringBuffer(128); - pdf.append("xref\n0 "); - pdf.append(this.objectcount + 1); - pdf.append("\n0000000000 65535 f \n"); - - String s; - String loc; - for (int count = 0; count < this.location.size(); count++) { - final String padding = "0000000000"; - s = this.location.get(count).toString(); - if (s.length() > 10) { - throw new IOException("PDF file too large. PDF cannot grow beyond approx. 9.3GB."); - } + private class UncompressedTrailerOutputHelper implements TrailerOutputHelper { - /* contruct xref entry for object */ - loc = padding.substring(s.length()) + s; + public void outputStructureTreeElements(OutputStream stream) + throws IOException { + streamIndirectObjects(structureTreeElements, stream); + } - /* append to xref table */ - pdf = pdf.append(loc).append(" 00000 n \n"); + public long outputCrossReferenceObject(OutputStream stream, + TrailerDictionary trailerDictionary) throws IOException { + new CrossReferenceTable(trailerDictionary, position, + indirectObjectOffsets).output(stream); + return position; } + } + + private class CompressedTrailerOutputHelper implements TrailerOutputHelper { + + private ObjectStreamManager structureTreeObjectStreams; - /* write the xref table and return the character length */ - byte[] pdfBytes = encode(pdf.toString()); - stream.write(pdfBytes); - return pdfBytes.length; + public void outputStructureTreeElements(OutputStream stream) + throws IOException { + assert structureTreeElements.size() > 0; + structureTreeObjectStreams = new ObjectStreamManager(PDFDocument.this); + for (PDFStructElem structElem : structureTreeElements) { + structureTreeObjectStreams.add(structElem); + } + } + + public long outputCrossReferenceObject(OutputStream stream, + TrailerDictionary trailerDictionary) throws IOException { + // Outputting the object streams should not have created new indirect objects + assert objects.isEmpty(); + new CrossReferenceStream(PDFDocument.this, ++objectcount, trailerDictionary, position, + indirectObjectOffsets, + structureTreeObjectStreams.getCompressedObjectReferences()) + .output(stream); + return position; + } } long getCurrentFileSize() { diff --git a/src/java/org/apache/fop/pdf/PDFEncryption.java b/src/java/org/apache/fop/pdf/PDFEncryption.java index 277cf0a94..fcb56e50b 100644 --- a/src/java/org/apache/fop/pdf/PDFEncryption.java +++ b/src/java/org/apache/fop/pdf/PDFEncryption.java @@ -40,8 +40,10 @@ public interface PDFEncryption { byte[] encrypt(byte[] data, PDFObject refObj); /** - * Returns the trailer entry for encryption. - * @return the trailer entry + * Returns the /Encrypt entry in the file trailer dictionary. + * + * @return the string "/Encrypt n g R\n" where n and g are the number and generation + * of the document's encryption dictionary */ String getTrailerEntry(); } diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java index c9b9c58ba..2ef3b93da 100644 --- a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java +++ b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java @@ -69,8 +69,9 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { ? new Rev2Engine(encryptionSettings) : new Rev3Engine(encryptionSettings); initializationEngine.run(); - encryptionDictionary = createEncryptionDictionary(getObjectID(), permissions, - initializationEngine.oValue, initializationEngine.uValue); + encryptionDictionary = createEncryptionDictionary(permissions, + initializationEngine.oValue, + initializationEngine.uValue); } private void determineEncryptionAlgorithm() { @@ -91,18 +92,16 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { && encryptionParams.isAllowPrintHq(); } - private String createEncryptionDictionary(final String objectId, final int permissions, - final byte[] oValue, final byte[] uValue) { - return objectId - + "<< /Filter /Standard\n" + private String createEncryptionDictionary(final int permissions, final byte[] oValue, + final byte[] uValue) { + return "<< /Filter /Standard\n" + "/V " + version + "\n" + "/R " + revision + "\n" + "/Length " + encryptionLength + "\n" + "/P " + permissions + "\n" + "/O " + PDFText.toHex(oValue) + "\n" + "/U " + PDFText.toHex(uValue) + "\n" - + ">>\n" - + "endobj\n"; + + ">>"; } } @@ -488,14 +487,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { /** {@inheritDoc} */ public String getTrailerEntry() { - PDFDocument doc = getDocumentSafely(); - FileIDGenerator gen = doc.getFileIDGenerator(); - return "/Encrypt " + getObjectNumber() + " " - + getGeneration() + " R\n" - + "/ID[" - + PDFText.toHex(gen.getOriginalFileID()) - + PDFText.toHex(gen.getUpdatedFileID()) - + "]\n"; + return "/Encrypt " + getObjectNumber() + " " + getGeneration() + " R\n"; } private static byte[] encryptWithKey(byte[] key, byte[] data) { diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index a981dae88..2c347031f 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -913,35 +913,6 @@ public class PDFFactory { } /** - * Creates and returns a StructTreeRoot object. Used for accessibility. - * @param parentTree the value of the ParenTree entry - * @return structure Tree Root element - */ - public PDFStructTreeRoot makeStructTreeRoot(PDFParentTree parentTree) { - PDFStructTreeRoot structTreeRoot = new PDFStructTreeRoot(parentTree); - getDocument().assignObjectNumber(structTreeRoot); - getDocument().addTrailerObject(structTreeRoot); - getDocument().getRoot().setStructTreeRoot(structTreeRoot); - return structTreeRoot; - } - - /** - * Creates and returns a StructElem object. - * - * @param structureType the structure type of the new element (value for the - * S entry) - * @param parent the parent of the new structure element in the structure - * hierarchy - * @return the newly created element - */ - public PDFStructElem makeStructureElement(PDFName structureType, PDFObject parent) { - PDFStructElem structElem = new PDFStructElem(parent, structureType); - getDocument().assignObjectNumber(structElem); - getDocument().addTrailerObject(structElem); - return structElem; - } - - /** * Make a the head object of the name dictionary (the /Dests object). * * @param destinationList a list of PDFDestination instances diff --git a/src/java/org/apache/fop/pdf/PDFFilterList.java b/src/java/org/apache/fop/pdf/PDFFilterList.java index 3025b8788..3f26f454c 100644 --- a/src/java/org/apache/fop/pdf/PDFFilterList.java +++ b/src/java/org/apache/fop/pdf/PDFFilterList.java @@ -21,6 +21,7 @@ package org.apache.fop.pdf; import java.io.IOException; import java.io.OutputStream; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -47,7 +48,7 @@ public class PDFFilterList { /** Key for the filter used for metadata */ public static final String METADATA_FILTER = "metadata"; - private List filters = new java.util.ArrayList(); + private List<PDFFilter> filters = new java.util.ArrayList<PDFFilter>(); private boolean ignoreASCIIFilters = false; @@ -197,6 +198,10 @@ public class PDFFilterList { } } + List<PDFFilter> getFilters() { + return Collections.unmodifiableList(filters); + } + /** * Apply the filters to the data * in the order given and return the /Filter and /DecodeParms @@ -206,7 +211,7 @@ public class PDFFilterList { * @return a String representing the filter list */ protected String buildFilterDictEntries() { - if (filters != null && filters.size() > 0) { + if (filters.size() > 0) { List names = new java.util.ArrayList(); List parms = new java.util.ArrayList(); @@ -229,7 +234,7 @@ public class PDFFilterList { * @param dict the PDFDictionary to set the entries on */ protected void putFilterDictEntries(PDFDictionary dict) { - if (filters != null && filters.size() > 0) { + if (filters.size() > 0) { List names = new java.util.ArrayList(); List parms = new java.util.ArrayList(); @@ -358,7 +363,7 @@ public class PDFFilterList { */ public OutputStream applyFilters(OutputStream stream) throws IOException { OutputStream out = stream; - if (filters != null && !isDisableAllFilters()) { + if (!isDisableAllFilters()) { for (int count = filters.size() - 1; count >= 0; count--) { PDFFilter filter = (PDFFilter)filters.get(count); out = filter.applyFilter(out); diff --git a/src/java/org/apache/fop/pdf/PDFFont.java b/src/java/org/apache/fop/pdf/PDFFont.java index 2808e5ba7..191fd223e 100644 --- a/src/java/org/apache/fop/pdf/PDFFont.java +++ b/src/java/org/apache/fop/pdf/PDFFont.java @@ -174,7 +174,7 @@ public class PDFFont extends PDFDictionary { } /** {@inheritDoc} */ - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { validate(); return super.output(stream); } diff --git a/src/java/org/apache/fop/pdf/PDFFormXObject.java b/src/java/org/apache/fop/pdf/PDFFormXObject.java index 2ccc17086..bc81a0a35 100644 --- a/src/java/org/apache/fop/pdf/PDFFormXObject.java +++ b/src/java/org/apache/fop/pdf/PDFFormXObject.java @@ -44,7 +44,7 @@ public class PDFFormXObject extends PDFXObject { * @param resources the resource PDF reference */ public PDFFormXObject(int xnumber, PDFStream contents, PDFReference resources) { - super(); + super(contents.getDictionary()); put("Name", new PDFName("Form" + xnumber)); this.contents = contents; @@ -160,7 +160,7 @@ public class PDFFormXObject extends PDFXObject { } /** {@inheritDoc} */ - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { final int len = super.output(stream); //Now that the data has been written, it can be discarded. diff --git a/src/java/org/apache/fop/pdf/PDFFunction.java b/src/java/org/apache/fop/pdf/PDFFunction.java index 9e93ebee8..44198105b 100644 --- a/src/java/org/apache/fop/pdf/PDFFunction.java +++ b/src/java/org/apache/fop/pdf/PDFFunction.java @@ -380,8 +380,7 @@ public class PDFFunction extends PDFObject { int numberOfFunctions = 0; int tempInt = 0; StringBuffer p = new StringBuffer(256); - p.append(getObjectID() - + "<< \n/FunctionType " + this.functionType + " \n"); + p.append("<< \n/FunctionType " + this.functionType + " \n"); // FunctionType 0 if (this.functionType == 0) { @@ -482,15 +481,14 @@ public class PDFFunction extends PDFObject { p.append("] \n"); } } - p.append(">> \n"); + p.append(">>"); // stream representing the function if (this.functionDataStream != null) { - p.append("stream\n" + this.functionDataStream - + "\nendstream\n"); + p.append("\nstream\n" + this.functionDataStream + + "\nendstream"); } - p.append("endobj\n"); // end of if FunctionType 0 } else if (this.functionType == 2) { @@ -550,7 +548,7 @@ public class PDFFunction extends PDFObject { + PDFNumber.doubleOut(new Double(this.interpolationExponentN)) + " \n"); - p.append(">> \nendobj\n"); + p.append(">>"); } else if (this.functionType == 3) { // fix this up when my eyes uncross @@ -643,10 +641,7 @@ public class PDFFunction extends PDFObject { } } - p.append("] \n"); - - - p.append(">> \nendobj\n"); + p.append("]\n>>"); } else if (this.functionType == 4) { // fix this up when my eyes uncross // DOMAIN @@ -681,15 +676,14 @@ public class PDFFunction extends PDFObject { + " \n"); } - p.append(">> \n"); + p.append(">>"); // stream representing the function if (this.functionDataStream != null) { - p.append("stream\n{ " + this.functionDataStream - + " } \nendstream\n"); + p.append("\nstream\n{ " + this.functionDataStream + + " }\nendstream"); } - p.append("endobj\n"); } diff --git a/src/java/org/apache/fop/pdf/PDFGState.java b/src/java/org/apache/fop/pdf/PDFGState.java index fe57e39c2..7306218ae 100644 --- a/src/java/org/apache/fop/pdf/PDFGState.java +++ b/src/java/org/apache/fop/pdf/PDFGState.java @@ -149,12 +149,10 @@ public class PDFGState extends PDFObject { */ public String toPDFString() { StringBuffer sb = new StringBuffer(64); - sb.append(getObjectID()); sb.append("<<\n/Type /ExtGState\n"); appendVal(sb, GSTATE_ALPHA_NONSTROKE); appendVal(sb, GSTATE_ALPHA_STROKE); - - sb.append(">>\nendobj\n"); + sb.append(">>"); return sb.toString(); } diff --git a/src/java/org/apache/fop/pdf/PDFGoTo.java b/src/java/org/apache/fop/pdf/PDFGoTo.java index 0626f0fcd..784aa4f95 100644 --- a/src/java/org/apache/fop/pdf/PDFGoTo.java +++ b/src/java/org/apache/fop/pdf/PDFGoTo.java @@ -124,9 +124,7 @@ public class PDFGoTo extends PDFAction { } else { dest = "/D [" + this.pageReference + " " + destination + "]\n"; } - return getObjectID() - + "<< /Type /Action\n/S /GoTo\n" + dest - + ">>\nendobj\n"; + return "<< /Type /Action\n/S /GoTo\n" + dest + ">>"; } /* diff --git a/src/java/org/apache/fop/pdf/PDFGoToRemote.java b/src/java/org/apache/fop/pdf/PDFGoToRemote.java index 93fbe47de..2dccdafce 100644 --- a/src/java/org/apache/fop/pdf/PDFGoToRemote.java +++ b/src/java/org/apache/fop/pdf/PDFGoToRemote.java @@ -106,7 +106,6 @@ public class PDFGoToRemote extends PDFAction { */ public String toPDFString() { StringBuffer sb = new StringBuffer(64); - sb.append(getObjectID()); sb.append("<<\n/S /GoToR\n/F "); sb.append(pdfFileSpec.toString()); sb.append("\n"); @@ -121,7 +120,7 @@ public class PDFGoToRemote extends PDFAction { sb.append("/NewWindow true"); } - sb.append(" \n>>\nendobj\n"); + sb.append("\n>>"); return sb.toString(); } diff --git a/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java b/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java index 87e8f5475..b86ba29f5 100644 --- a/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java +++ b/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java @@ -99,9 +99,7 @@ public class PDFICCBasedColorSpace extends PDFObject implements PDFColorSpace { @Override protected String toPDFString() { StringBuffer sb = new StringBuffer(64); - sb.append(getObjectID()); sb.append("[/ICCBased ").append(getICCStream().referencePDF()).append("]"); - sb.append("\nendobj\n"); return sb.toString(); } diff --git a/src/java/org/apache/fop/pdf/PDFICCStream.java b/src/java/org/apache/fop/pdf/PDFICCStream.java index 9002fcc94..33b81307b 100644 --- a/src/java/org/apache/fop/pdf/PDFICCStream.java +++ b/src/java/org/apache/fop/pdf/PDFICCStream.java @@ -64,7 +64,7 @@ public class PDFICCStream extends PDFStream { * {@inheritDoc} */ @Override - protected int output(java.io.OutputStream stream) + public int output(java.io.OutputStream stream) throws java.io.IOException { int length = super.output(stream); this.cp = null; //Free ICC stream when it's not used anymore diff --git a/src/java/org/apache/fop/pdf/PDFImageXObject.java b/src/java/org/apache/fop/pdf/PDFImageXObject.java index 9ec8f1595..acab4c19c 100644 --- a/src/java/org/apache/fop/pdf/PDFImageXObject.java +++ b/src/java/org/apache/fop/pdf/PDFImageXObject.java @@ -61,7 +61,7 @@ public class PDFImageXObject extends PDFXObject { * @throws IOException if there is an error writing the data * @return the length of the data written */ - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { int length = super.output(stream); // let it gc @@ -137,7 +137,7 @@ public class PDFImageXObject extends PDFXObject { put("SMask", ref); } //Important: do this at the end so previous values can be overwritten. - pdfimage.populateXObjectDictionary(this); + pdfimage.populateXObjectDictionary(getDictionary()); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/pdf/PDFInfo.java b/src/java/org/apache/fop/pdf/PDFInfo.java index 3f3fb3b46..d457c2888 100644 --- a/src/java/org/apache/fop/pdf/PDFInfo.java +++ b/src/java/org/apache/fop/pdf/PDFInfo.java @@ -169,7 +169,6 @@ public class PDFInfo extends PDFObject { PDFProfile profile = getDocumentSafely().getProfile(); ByteArrayOutputStream bout = new ByteArrayOutputStream(128); try { - bout.write(encode(getObjectID())); bout.write(encode("<<\n")); if (title != null && title.length() > 0) { bout.write(encode("/Title ")); @@ -229,7 +228,7 @@ public class PDFInfo extends PDFObject { bout.write(encode("/Trapped /False\n")); } - bout.write(encode(">>\nendobj\n")); + bout.write(encode(">>")); } catch (IOException ioe) { log.error("Ignored I/O exception", ioe); } diff --git a/src/java/org/apache/fop/pdf/PDFLaunch.java b/src/java/org/apache/fop/pdf/PDFLaunch.java index e62da5279..7d80ddb43 100644 --- a/src/java/org/apache/fop/pdf/PDFLaunch.java +++ b/src/java/org/apache/fop/pdf/PDFLaunch.java @@ -54,10 +54,9 @@ public class PDFLaunch extends PDFAction { /** {@inheritDoc} */ public String toPDFString() { StringBuffer sb = new StringBuffer(64); - sb.append(getObjectID()); sb.append("<<\n/S /Launch\n/F "); sb.append(externalFileSpec.toString()); - sb.append(" \n>>\nendobj\n"); + sb.append("\n>>"); return sb.toString(); } diff --git a/src/java/org/apache/fop/pdf/PDFLink.java b/src/java/org/apache/fop/pdf/PDFLink.java index 934932648..ffec6f09c 100644 --- a/src/java/org/apache/fop/pdf/PDFLink.java +++ b/src/java/org/apache/fop/pdf/PDFLink.java @@ -92,15 +92,14 @@ public class PDFLink extends PDFObject { f |= 1 << (5 - 1); //NoRotate, bit 5 fFlag = "/F " + f; } - String s = getObjectID() - + "<< /Type /Annot\n" + "/Subtype /Link\n" + "/Rect [ " + String s = "<< /Type /Annot\n" + "/Subtype /Link\n" + "/Rect [ " + (ulx) + " " + (uly) + " " + (brx) + " " + (bry) + " ]\n" + "/C [ " + this.color + " ]\n" + "/Border [ 0 0 0 ]\n" + "/A " + this.action.getAction() + "\n" + "/H /I\n" + (this.structParent != null ? "/StructParent " + this.structParent.toString() + "\n" : "") - + fFlag + "\n>>\nendobj\n"; + + fFlag + "\n>>"; return s; } diff --git a/src/java/org/apache/fop/pdf/PDFMetadata.java b/src/java/org/apache/fop/pdf/PDFMetadata.java index b9612d11b..9b1b8165b 100644 --- a/src/java/org/apache/fop/pdf/PDFMetadata.java +++ b/src/java/org/apache/fop/pdf/PDFMetadata.java @@ -79,7 +79,7 @@ public class PDFMetadata extends PDFStream { * byte arrays around so much * {@inheritDoc} */ - protected int output(java.io.OutputStream stream) + public int output(java.io.OutputStream stream) throws java.io.IOException { int length = super.output(stream); this.xmpMetadata = null; //Release DOM when it's not used anymore diff --git a/src/java/org/apache/fop/pdf/PDFName.java b/src/java/org/apache/fop/pdf/PDFName.java index 7fa6842fa..23294cc54 100644 --- a/src/java/org/apache/fop/pdf/PDFName.java +++ b/src/java/org/apache/fop/pdf/PDFName.java @@ -108,22 +108,11 @@ public class PDFName extends PDFObject { return name.hashCode(); } - - /** {@inheritDoc} */ @Override - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { CountingOutputStream cout = new CountingOutputStream(stream); StringBuilder textBuffer = new StringBuilder(64); - if (hasObjectNumber()) { - textBuffer.append(getObjectID()); - } - textBuffer.append(toString()); - - if (hasObjectNumber()) { - textBuffer.append("\nendobj\n"); - } - PDFDocument.flushTextBuffer(textBuffer, cout); return cout.getCount(); } diff --git a/src/java/org/apache/fop/pdf/PDFNumber.java b/src/java/org/apache/fop/pdf/PDFNumber.java index 5bc648ced..95242305c 100644 --- a/src/java/org/apache/fop/pdf/PDFNumber.java +++ b/src/java/org/apache/fop/pdf/PDFNumber.java @@ -85,13 +85,7 @@ public class PDFNumber extends PDFObject { "The number of this PDFNumber must not be empty"); } StringBuffer sb = new StringBuffer(64); - if (hasObjectNumber()) { - sb.append(getObjectID()); - } sb.append(doubleOut(getNumber().doubleValue(), 10)); - if (hasObjectNumber()) { - sb.append("\nendobj\n"); - } return sb.toString(); } diff --git a/src/java/org/apache/fop/pdf/PDFNumsArray.java b/src/java/org/apache/fop/pdf/PDFNumsArray.java index ecd301647..e9e1855b0 100644 --- a/src/java/org/apache/fop/pdf/PDFNumsArray.java +++ b/src/java/org/apache/fop/pdf/PDFNumsArray.java @@ -88,13 +88,9 @@ public class PDFNumsArray extends PDFObject { /** {@inheritDoc} */ @Override - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { CountingOutputStream cout = new CountingOutputStream(stream); StringBuilder textBuffer = new StringBuilder(64); - if (hasObjectNumber()) { - textBuffer.append(getObjectID()); - } - textBuffer.append('['); boolean first = true; for (Map.Entry<Integer, Object> entry : this.map.entrySet()) { @@ -107,11 +103,6 @@ public class PDFNumsArray extends PDFObject { formatObject(entry.getValue(), cout, textBuffer); } textBuffer.append(']'); - - if (hasObjectNumber()) { - textBuffer.append("\nendobj\n"); - } - PDFDocument.flushTextBuffer(textBuffer, cout); return cout.getCount(); } diff --git a/src/java/org/apache/fop/pdf/PDFObject.java b/src/java/org/apache/fop/pdf/PDFObject.java index 5abb5a142..1b9c4eea7 100644 --- a/src/java/org/apache/fop/pdf/PDFObject.java +++ b/src/java/org/apache/fop/pdf/PDFObject.java @@ -204,7 +204,7 @@ public abstract class PDFObject implements PDFWritable { * @throws IOException if there is an error writing to the stream * @return the number of bytes written */ - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { byte[] pdf = this.toPDF(); stream.write(pdf); return pdf.length; diff --git a/src/java/org/apache/fop/pdf/PDFOutline.java b/src/java/org/apache/fop/pdf/PDFOutline.java index 66116c681..61c562548 100644 --- a/src/java/org/apache/fop/pdf/PDFOutline.java +++ b/src/java/org/apache/fop/pdf/PDFOutline.java @@ -131,7 +131,6 @@ public class PDFOutline extends PDFObject { protected byte[] toPDF() { ByteArrayOutputStream bout = new ByteArrayOutputStream(128); try { - bout.write(encode(getObjectID())); bout.write(encode("<<")); if (parent == null) { // root Outlines object @@ -164,7 +163,7 @@ public class PDFOutline extends PDFObject { bout.write(encode(" /A " + actionRef + "\n")); } } - bout.write(encode(">> endobj\n")); + bout.write(encode(">>")); } catch (IOException ioe) { log.error("Ignored I/O exception", ioe); } diff --git a/src/java/org/apache/fop/pdf/PDFOutputIntent.java b/src/java/org/apache/fop/pdf/PDFOutputIntent.java index ea073829b..1c0373944 100644 --- a/src/java/org/apache/fop/pdf/PDFOutputIntent.java +++ b/src/java/org/apache/fop/pdf/PDFOutputIntent.java @@ -130,7 +130,6 @@ public class PDFOutputIntent extends PDFObject { public byte[] toPDF() { ByteArrayOutputStream bout = new ByteArrayOutputStream(128); try { - bout.write(encode(getObjectID())); bout.write(encode("<<\n")); bout.write(encode("/Type /OutputIntent\n")); @@ -164,7 +163,7 @@ public class PDFOutputIntent extends PDFObject { bout.write(encode("/DestOutputProfile " + destOutputProfile.referencePDF() + "\n")); } - bout.write(encode(">>\nendobj\n")); + bout.write(encode(">>")); } catch (IOException ioe) { log.error("Ignored I/O exception", ioe); } diff --git a/src/java/org/apache/fop/pdf/PDFPages.java b/src/java/org/apache/fop/pdf/PDFPages.java index 61d860eaa..98c293a97 100644 --- a/src/java/org/apache/fop/pdf/PDFPages.java +++ b/src/java/org/apache/fop/pdf/PDFPages.java @@ -109,10 +109,9 @@ public class PDFPages extends PDFObject { */ public String toPDFString() { StringBuffer sb = new StringBuffer(64); - sb.append(getObjectID()) - .append("<< /Type /Pages\n/Count ") - .append(this.getCount()) - .append("\n/Kids ["); + sb.append("<< /Type /Pages\n/Count ") + .append(this.getCount()) + .append("\n/Kids ["); for (int i = 0; i < kids.size(); i++) { Object kid = kids.get(i); if (kid == null) { @@ -120,7 +119,7 @@ public class PDFPages extends PDFObject { } sb.append(kid).append(" "); } - sb.append("] >>\nendobj\n"); + sb.append("] >>"); return sb.toString(); } diff --git a/src/java/org/apache/fop/pdf/PDFPattern.java b/src/java/org/apache/fop/pdf/PDFPattern.java index 378b1cf8b..88a2ff492 100644 --- a/src/java/org/apache/fop/pdf/PDFPattern.java +++ b/src/java/org/apache/fop/pdf/PDFPattern.java @@ -210,13 +210,12 @@ public class PDFPattern extends PDFPathPaint { * @throws IOException if there is an error writing to the stream * @return the PDF string. */ - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { int vectorSize = 0; int tempInt = 0; byte[] buffer; StringBuffer p = new StringBuffer(64); - p.append(getObjectID()); p.append("<< \n/Type /Pattern \n"); if (this.resources != null) { @@ -323,10 +322,6 @@ public class PDFPattern extends PDFPathPaint { length += pdfStream.outputStreamData(encodedStream, stream); } - buffer = encode("\nendobj\n"); - stream.write(buffer); - length += buffer.length; - return length; } diff --git a/src/java/org/apache/fop/pdf/PDFResources.java b/src/java/org/apache/fop/pdf/PDFResources.java index 70f9af504..df8647bf4 100644 --- a/src/java/org/apache/fop/pdf/PDFResources.java +++ b/src/java/org/apache/fop/pdf/PDFResources.java @@ -192,8 +192,8 @@ public class PDFResources extends PDFDictionary { return cs; } - /** {@inheritDoc} */ - protected int output(OutputStream stream) throws IOException { + @Override + public int output(OutputStream stream) throws IOException { populateDictionary(); return super.output(stream); } diff --git a/src/java/org/apache/fop/pdf/PDFRoot.java b/src/java/org/apache/fop/pdf/PDFRoot.java index 81b93b159..e74c77478 100644 --- a/src/java/org/apache/fop/pdf/PDFRoot.java +++ b/src/java/org/apache/fop/pdf/PDFRoot.java @@ -76,7 +76,7 @@ public class PDFRoot extends PDFDictionary { } /** {@inheritDoc} */ - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { getDocument().getProfile().verifyTaggedPDF(); return super.output(stream); } diff --git a/src/java/org/apache/fop/pdf/PDFShading.java b/src/java/org/apache/fop/pdf/PDFShading.java index 2f955b850..90953968c 100644 --- a/src/java/org/apache/fop/pdf/PDFShading.java +++ b/src/java/org/apache/fop/pdf/PDFShading.java @@ -342,8 +342,7 @@ public class PDFShading extends PDFObject { int vectorSize; int tempInt; StringBuffer p = new StringBuffer(128); - p.append(getObjectID() - + "<< \n/ShadingType " + this.shadingType + " \n"); + p.append("<<\n/ShadingType " + this.shadingType + " \n"); if (this.colorSpace != null) { p.append("/ColorSpace /" + this.colorSpace.getName() + " \n"); @@ -528,7 +527,7 @@ public class PDFShading extends PDFObject { } - p.append(">> \nendobj\n"); + p.append(">>"); return (p.toString()); } diff --git a/src/java/org/apache/fop/pdf/PDFStream.java b/src/java/org/apache/fop/pdf/PDFStream.java index 5f74a2613..a0b990ec5 100644 --- a/src/java/org/apache/fop/pdf/PDFStream.java +++ b/src/java/org/apache/fop/pdf/PDFStream.java @@ -21,6 +21,7 @@ package org.apache.fop.pdf; import java.io.IOException; import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.io.Writer; /** @@ -44,16 +45,33 @@ public class PDFStream extends AbstractPDFStream { * Create an empty stream object */ public PDFStream() { - super(); + setUp(); + } + + public PDFStream(PDFDictionary dictionary) { + super(dictionary); + setUp(); + } + + public PDFStream(PDFDictionary dictionary, boolean encodeOnTheFly) { + super(dictionary, encodeOnTheFly); + setUp(); + } + + public PDFStream(boolean encodeOnTheFly) { + super(encodeOnTheFly); + setUp(); + } + + private void setUp() { try { data = StreamCacheFactory.getInstance().createStreamCache(); - this.streamWriter = new java.io.OutputStreamWriter( + this.streamWriter = new OutputStreamWriter( getBufferOutputStream(), PDFDocument.ENCODING); //Buffer to minimize calls to the converter this.streamWriter = new java.io.BufferedWriter(this.streamWriter); - } catch (IOException ex) { - //TODO throw the exception and catch it elsewhere - ex.printStackTrace(); + } catch (IOException e) { + throw new RuntimeException(e); } } @@ -136,7 +154,7 @@ public class PDFStream extends AbstractPDFStream { /** * {@inheritDoc} */ - protected int output(OutputStream stream) throws IOException { + public int output(OutputStream stream) throws IOException { final int len = super.output(stream); //Now that the data has been written, it can be discarded. diff --git a/src/java/org/apache/fop/pdf/PDFStructElem.java b/src/java/org/apache/fop/pdf/PDFStructElem.java index e35a860d9..90a41fb72 100644 --- a/src/java/org/apache/fop/pdf/PDFStructElem.java +++ b/src/java/org/apache/fop/pdf/PDFStructElem.java @@ -31,7 +31,7 @@ import org.apache.fop.util.LanguageTags; /** * Class representing a PDF Structure Element. */ -public class PDFStructElem extends PDFDictionary implements StructureTreeElement { +public class PDFStructElem extends PDFDictionary implements StructureTreeElement, CompressedObject { private PDFStructElem parentElement; diff --git a/src/java/org/apache/fop/pdf/PDFT1Stream.java b/src/java/org/apache/fop/pdf/PDFT1Stream.java index d723625eb..2bae5dca3 100644 --- a/src/java/org/apache/fop/pdf/PDFT1Stream.java +++ b/src/java/org/apache/fop/pdf/PDFT1Stream.java @@ -46,7 +46,7 @@ public class PDFT1Stream extends AbstractPDFFontStream { * byte arrays around so much * {@inheritDoc} */ - protected int output(java.io.OutputStream stream) + public int output(java.io.OutputStream stream) throws java.io.IOException { if (pfb == null) { throw new IllegalStateException("pfb must not be null at this point"); diff --git a/src/java/org/apache/fop/pdf/PDFTTFStream.java b/src/java/org/apache/fop/pdf/PDFTTFStream.java index 643ddb1e8..998e14f28 100644 --- a/src/java/org/apache/fop/pdf/PDFTTFStream.java +++ b/src/java/org/apache/fop/pdf/PDFTTFStream.java @@ -53,7 +53,7 @@ public class PDFTTFStream extends AbstractPDFFontStream { * byte arrays around so much * {@inheritDoc} */ - protected int output(java.io.OutputStream stream) + public int output(java.io.OutputStream stream) throws java.io.IOException { if (log.isDebugEnabled()) { log.debug("Writing " + origLength + " bytes of TTF font data"); diff --git a/src/java/org/apache/fop/pdf/PDFText.java b/src/java/org/apache/fop/pdf/PDFText.java index 9566f60da..3cddfe426 100644 --- a/src/java/org/apache/fop/pdf/PDFText.java +++ b/src/java/org/apache/fop/pdf/PDFText.java @@ -60,11 +60,9 @@ public class PDFText extends PDFObject { "The text of this PDFText must not be empty"); } StringBuffer sb = new StringBuffer(64); - sb.append(getObjectID()); sb.append("("); sb.append(escapeText(getText())); sb.append(")"); - sb.append("\nendobj\n"); return sb.toString(); } diff --git a/src/java/org/apache/fop/pdf/PDFUri.java b/src/java/org/apache/fop/pdf/PDFUri.java index a6124ec03..b3d377ff1 100644 --- a/src/java/org/apache/fop/pdf/PDFUri.java +++ b/src/java/org/apache/fop/pdf/PDFUri.java @@ -55,8 +55,7 @@ public class PDFUri extends PDFAction { /** {@inheritDoc} */ public String toPDFString() { //TODO Convert this class into a dictionary - return getObjectID() + getDictString() + "\nendobj\n"; - //throw new UnsupportedOperationException("This method should not be called"); + return getDictString(); } } diff --git a/src/java/org/apache/fop/pdf/PDFXObject.java b/src/java/org/apache/fop/pdf/PDFXObject.java index d1ce6d75e..c2b702650 100644 --- a/src/java/org/apache/fop/pdf/PDFXObject.java +++ b/src/java/org/apache/fop/pdf/PDFXObject.java @@ -41,6 +41,10 @@ public abstract class PDFXObject extends AbstractPDFStream { super(); } + protected PDFXObject(PDFDictionary dictionary) { + super(dictionary); + } + /** * Returns the XObject's name. * @return the name of the XObject diff --git a/src/java/org/apache/fop/pdf/Version.java b/src/java/org/apache/fop/pdf/Version.java index 0df63d312..4bdc7a1b4 100644 --- a/src/java/org/apache/fop/pdf/Version.java +++ b/src/java/org/apache/fop/pdf/Version.java @@ -48,12 +48,13 @@ public enum Version { } /** - * Given the PDF version as a String, returns the corresponding enumerated type. The String - * should be in the format "1.x" for PDF v1.x. + * Given the PDF version as a String, returns the corresponding enumerated type. The + * String should be in the format "1.x" for PDF v1.x. * * @param version a version number * @return the corresponding Version instance - * @throws IllegalArgumentException if the argument does not correspond to any existing PDF version + * @throws IllegalArgumentException if the argument does not correspond to any + * existing PDF version */ public static Version getValueOf(String version) { for (Version pdfVersion : Version.values()) { diff --git a/src/java/org/apache/fop/pdf/xref/CompressedObjectReference.java b/src/java/org/apache/fop/pdf/xref/CompressedObjectReference.java new file mode 100644 index 000000000..eb619fcc6 --- /dev/null +++ b/src/java/org/apache/fop/pdf/xref/CompressedObjectReference.java @@ -0,0 +1,66 @@ +/* + * 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.xref; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * 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 int objectStreamNumber; + + private final int index; + + /** + * Creates a new reference. + * + * @param objectNumber the number of the compressed object being referenced + * @param objectStreamNumber the number of the object stream in which the compressed + * 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) { + this.objectNumber = objectNumber; + this.objectStreamNumber = objectStreamNumber; + this.index = index; + } + + public void output(DataOutputStream out) throws IOException { + out.write(2); + out.writeLong(objectStreamNumber); + out.write(0); + out.write(index); + } + + public int getObjectNumber() { + return objectNumber; + } + + public int getObjectStreamNumber() { + return objectStreamNumber; + } + +} diff --git a/src/java/org/apache/fop/pdf/xref/CrossReferenceObject.java b/src/java/org/apache/fop/pdf/xref/CrossReferenceObject.java new file mode 100644 index 000000000..3db50eca9 --- /dev/null +++ b/src/java/org/apache/fop/pdf/xref/CrossReferenceObject.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.xref; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * A representation of the cross-reference data to be output at the end of a PDF file. + */ +public abstract class CrossReferenceObject { + + protected final TrailerDictionary trailerDictionary; + + protected final long startxref; + + CrossReferenceObject(TrailerDictionary trailerDictionary, long startxref) { + this.trailerDictionary = trailerDictionary; + this.startxref = startxref; + } + + /** + * Writes the cross reference data to a PDF stream + * + * @param stream the stream to write the cross reference to + * @throws IOException if an I/O exception occurs while writing the data + */ + public abstract void output(OutputStream stream) throws IOException; +} diff --git a/src/java/org/apache/fop/pdf/xref/CrossReferenceStream.java b/src/java/org/apache/fop/pdf/xref/CrossReferenceStream.java new file mode 100644 index 000000000..32c31573f --- /dev/null +++ b/src/java/org/apache/fop/pdf/xref/CrossReferenceStream.java @@ -0,0 +1,107 @@ +/* + * 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.xref; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.fop.pdf.PDFArray; +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.PDFStream; + +/** + * A cross-reference stream, as described in Section 3.4.7 of the PDF 1.5 Reference. + */ +public class CrossReferenceStream extends CrossReferenceObject { + + private static final PDFName XREF = new PDFName("XRef"); + + private final PDFDocument document; + + private final int objectNumber; + + private final List<ObjectReference> objectReferences; + + public CrossReferenceStream(PDFDocument document, + int objectNumber, + TrailerDictionary trailerDictionary, + long startxref, + List<Long> uncompressedObjectReferences, + List<CompressedObjectReference> compressedObjectReferences) { + super(trailerDictionary, startxref); + this.document = document; + this.objectNumber = objectNumber; + this.objectReferences = new ArrayList<ObjectReference>(uncompressedObjectReferences.size()); + for (Long offset : uncompressedObjectReferences) { + objectReferences.add(offset == null ? null : new UncompressedObjectReference(offset)); + } + for (CompressedObjectReference ref : compressedObjectReferences) { + this.objectReferences.set(ref.getObjectNumber() - 1, ref); + } + } + + /** {@inheritDoc} */ + public void output(OutputStream stream) throws IOException { + populateDictionary(); + PDFStream helperStream = new PDFStream(trailerDictionary.getDictionary(), false) { + + @Override + protected void setupFilterList() { + PDFFilterList filterList = getFilterList(); + assert !filterList.isInitialized(); + filterList.addDefaultFilters(document.getFilterMap(), getDefaultFilterName()); + } + + }; + helperStream.setObjectNumber(objectNumber); + helperStream.setDocument(document); + ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); + DataOutputStream data = new DataOutputStream(byteArray); + addFreeEntryForObject0(data); + for (ObjectReference objectReference : objectReferences) { + assert objectReference != null; + objectReference.output(data); + } + new UncompressedObjectReference(startxref).output(data); + data.close(); + helperStream.setData(byteArray.toByteArray()); + PDFDocument.outputIndirectObject(helperStream, stream); + } + + private void populateDictionary() throws IOException { + int objectCount = objectReferences.size() + 1; + PDFDictionary dictionary = trailerDictionary.getDictionary(); + dictionary.put("/Type", XREF); + dictionary.put("/Size", objectCount + 1); + dictionary.put("/W", new PDFArray(1, 8, 2)); + } + + private void addFreeEntryForObject0(DataOutputStream data) throws IOException { + data.write(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xff, (byte) 0xff}); + } + +} diff --git a/src/java/org/apache/fop/pdf/xref/CrossReferenceTable.java b/src/java/org/apache/fop/pdf/xref/CrossReferenceTable.java new file mode 100644 index 000000000..41a1146ca --- /dev/null +++ b/src/java/org/apache/fop/pdf/xref/CrossReferenceTable.java @@ -0,0 +1,73 @@ +/* + * 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.xref; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +import org.apache.fop.pdf.PDFDictionary; +import org.apache.fop.pdf.PDFDocument; + +/** + * A cross-reference table, as described in Section 3.4.3 of the PDF 1.5 Reference. + */ +public class CrossReferenceTable extends CrossReferenceObject { + + private final List<Long> objectReferences; + + private final StringBuilder pdf = new StringBuilder(256); + + public CrossReferenceTable(TrailerDictionary trailerDictionary, long startxref, + List<Long> location) { + super(trailerDictionary, startxref); + this.objectReferences = location; + } + + public void output(OutputStream stream) throws IOException { + outputXref(); + writeTrailer(stream); + } + + private void outputXref() throws IOException { + pdf.append("xref\n0 "); + pdf.append(objectReferences.size() + 1); + pdf.append("\n0000000000 65535 f \n"); + for (Long objectReference : objectReferences) { + final String padding = "0000000000"; + String s = String.valueOf(objectReference); + if (s.length() > 10) { + throw new IOException("PDF file too large." + + " PDF 1.4 cannot grow beyond approx. 9.3GB."); + } + String loc = padding.substring(s.length()) + s; + pdf.append(loc).append(" 00000 n \n"); + } + } + + private void writeTrailer(OutputStream stream) throws IOException { + pdf.append("trailer\n"); + stream.write(PDFDocument.encode(pdf.toString())); + PDFDictionary dictionary = trailerDictionary.getDictionary(); + dictionary.put("/Size", objectReferences.size() + 1); + dictionary.output(stream); + } + +} diff --git a/src/java/org/apache/fop/pdf/xref/ObjectReference.java b/src/java/org/apache/fop/pdf/xref/ObjectReference.java new file mode 100644 index 000000000..894a66ce6 --- /dev/null +++ b/src/java/org/apache/fop/pdf/xref/ObjectReference.java @@ -0,0 +1,39 @@ +/* + * 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.xref; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * A reference to an indirect object. + */ +interface ObjectReference { + + /** + * Outputs this reference to the given stream, in the cross-reference stream format. + * For example, a object may output the bytes 01 00 00 00 00 00 00 01 ff 00 to + * indicate a non-compressed object (01), at offset 511 from the beginning of the file + * (00 00 00 00 00 00 01 ff), of generation number 0 (00). + * + * @param out the stream to which to output the reference + */ + void output(DataOutputStream out) throws IOException; +} diff --git a/src/java/org/apache/fop/pdf/xref/TrailerDictionary.java b/src/java/org/apache/fop/pdf/xref/TrailerDictionary.java new file mode 100644 index 000000000..d5d62522f --- /dev/null +++ b/src/java/org/apache/fop/pdf/xref/TrailerDictionary.java @@ -0,0 +1,94 @@ +/* + * 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.xref; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.fop.pdf.PDFArray; +import org.apache.fop.pdf.PDFDictionary; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFEncryption; +import org.apache.fop.pdf.PDFInfo; +import org.apache.fop.pdf.PDFRoot; +import org.apache.fop.pdf.PDFText; +import org.apache.fop.pdf.PDFWritable; + +/** + * A data class representing entries of the file trailer dictionary. + */ +public class TrailerDictionary { + + private final PDFDictionary dictionary; + + public TrailerDictionary(PDFDocument pdfDocument) { + this.dictionary = new PDFDictionary(); + this.dictionary.setDocument(pdfDocument); + } + + /** Sets the value of the Root entry. */ + public TrailerDictionary setRoot(PDFRoot root) { + dictionary.put("/Root", root); + return this; + } + + /** Sets the value of the Info entry. */ + public TrailerDictionary setInfo(PDFInfo info) { + dictionary.put("/Info", info); + return this; + } + + /** Sets the value of the Encrypt entry. */ + public TrailerDictionary setEncryption(PDFEncryption encryption) { + dictionary.put("/Encrypt", encryption); + return this; + } + + /** Sets the value of the ID entry. */ + public TrailerDictionary setFileID(byte[] originalFileID, byte[] updatedFileID) { + // TODO this is ugly! Used to circumvent the fact that the file ID will be + // encrypted if directly stored as a byte array + class FileID implements PDFWritable { + + private final byte[] fileID; + + FileID(byte[] id) { + fileID = id; + } + + public void outputInline(OutputStream out, StringBuilder textBuffer) + throws IOException { + PDFDocument.flushTextBuffer(textBuffer, out); + String hex = PDFText.toHex(fileID, true); + byte[] encoded = hex.getBytes("US-ASCII"); + out.write(encoded); + } + + } + PDFArray fileID = new PDFArray(new FileID(originalFileID), new FileID(updatedFileID)); + dictionary.put("/ID", fileID); + return this; + } + + PDFDictionary getDictionary() { + return dictionary; + } + +} diff --git a/src/java/org/apache/fop/pdf/xref/UncompressedObjectReference.java b/src/java/org/apache/fop/pdf/xref/UncompressedObjectReference.java new file mode 100644 index 000000000..a54ec62e7 --- /dev/null +++ b/src/java/org/apache/fop/pdf/xref/UncompressedObjectReference.java @@ -0,0 +1,48 @@ +/* + * 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.xref; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * A reference to an indirect object that is not stored in an object stream. + */ +class UncompressedObjectReference implements ObjectReference { + + final long offset; + + /** + * Creates a new reference. + * + * @param offset offset of the object from the beginning of the PDF file + */ + UncompressedObjectReference(long offset) { + this.offset = offset; + } + + public void output(DataOutputStream out) throws IOException { + out.write(1); + out.writeLong(offset); + out.write(0); + out.write(0); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java index 88a6e9c22..1c9f9b49d 100644 --- a/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java @@ -95,15 +95,15 @@ class PDFLogicalStructureHandler { */ PDFLogicalStructureHandler(PDFDocument pdfDoc) { this.pdfDoc = pdfDoc; - PDFStructTreeRoot structTreeRoot = pdfDoc.getFactory().makeStructTreeRoot(parentTree); - rootStructureElement = pdfDoc.getFactory().makeStructureElement( + PDFStructTreeRoot structTreeRoot = pdfDoc.makeStructTreeRoot(parentTree); + rootStructureElement = pdfDoc.makeStructureElement( FOToPDFRoleMap.mapFormattingObject("root", structTreeRoot), structTreeRoot); structTreeRoot.addKid(rootStructureElement); } PDFStructElem createPageSequence(Locale language) { - PDFStructElem structElemPart = pdfDoc.getFactory().makeStructureElement( + PDFStructElem structElemPart = pdfDoc.makeStructureElement( FOToPDFRoleMap.mapFormattingObject("page-sequence", rootStructureElement), rootStructureElement); rootStructureElement.addKid(structElemPart); diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java index 83f6ccab6..53d259677 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -385,8 +385,8 @@ class PDFRenderingUtil implements PDFConfigurationConstants { if (maxPDFVersion == null) { this.pdfDoc = new PDFDocument(producer); } else { - VersionController controller = - VersionController.getFixedVersionController(maxPDFVersion); + VersionController controller + = VersionController.getFixedVersionController(maxPDFVersion); this.pdfDoc = new PDFDocument(producer, controller); } updateInfo(); @@ -411,6 +411,9 @@ class PDFRenderingUtil implements PDFConfigurationConstants { log.debug("PDF/A is active. Conformance Level: " + pdfAMode); addPDFA1OutputIntent(); } + + this.pdfDoc.enableAccessibility(userAgent.isAccessibilityEnabled()); + return this.pdfDoc; } diff --git a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java index 11eba4ea4..2a2a4a392 100644 --- a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java +++ b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java @@ -65,7 +65,7 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { PDFStructElem parent = ancestors.getFirst(); String role = attributes.getValue("role"); PDFStructElem created; - created = pdfFactory.makeStructureElement( + created = pdfFactory.getDocument().makeStructureElement( FOToPDFRoleMap.mapFormattingObject(name, role, parent, eventBroadcaster), parent); parent.addKid(created); ancestors.addFirst(created); @@ -84,7 +84,7 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { PDFStructElem parent = ancestors.getFirst(); String role = attributes.getValue("role"); PDFStructElem created; - created = pdfFactory.makeStructureElement( + created = pdfFactory.getDocument().makeStructureElement( FOToPDFRoleMap.mapFormattingObject(name, role, parent, eventBroadcaster), parent); parent.addKid(created); String altTextNode = attributes.getValue(ExtensionElementMapping.URI, "alt-text"); @@ -104,7 +104,7 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { if ("#PCDATA".equals(name)) { created = new PDFStructElem.Placeholder(parent, name); } else { - created = pdfFactory.makeStructureElement( + created = pdfFactory.getDocument().makeStructureElement( FOToPDFRoleMap.mapFormattingObject(name, role, parent, eventBroadcaster), parent); } |