diff options
Diffstat (limited to 'src/java/org')
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); } |