getDocument().registerObject(refLength);
}
}
+
+ public boolean supportsObjectStream() {
+ return false;
+ }
}
private FileIDGenerator fileIDGenerator;
+ private ObjectStreamManager objectStreamManager;
+
private boolean accessibilityEnabled;
private boolean mergeFontsEnabled;
protected boolean outputStarted;
+ private boolean objectStreamsEnabled;
+
/**
* Creates an empty PDF document.
*
//Write out objects until the list is empty. This approach (used with a
//LinkedList) allows for output() methods to create and register objects
//on the fly even during serialization.
- while (this.objects.size() > 0) {
- PDFObject object = this.objects.remove(0);
+
+ if (objectStreamsEnabled) {
+ List<PDFObject> indirectObjects = new ArrayList<>();
+ while (objects.size() > 0) {
+ PDFObject object = objects.remove(0);
+ if (object.supportsObjectStream()) {
+ addToObjectStream(object);
+ } else {
+ indirectObjects.add(object);
+ }
+ }
+ objects.addAll(indirectObjects);
+ }
+
+ while (objects.size() > 0) {
+ PDFObject object = objects.remove(0);
streamIndirectObject(object, stream);
}
}
+ private void addToObjectStream(CompressedObject object) {
+ if (objectStreamManager == null) {
+ objectStreamManager = new ObjectStreamManager(this);
+ }
+ objectStreamManager.add(object);
+ }
+
protected void writeTrailer(OutputStream stream, int first, int last, int size, long mainOffset, long startxref)
throws IOException {
- TrailerOutputHelper trailerOutputHelper = mayCompressStructureTreeElements()
+ TrailerOutputHelper trailerOutputHelper = useObjectStreams()
? new CompressedTrailerOutputHelper()
: new UncompressedTrailerOutputHelper();
if (structureTreeElements != null) {
}
private void outputTrailerObjectsAndXref(OutputStream stream) throws IOException {
- TrailerOutputHelper trailerOutputHelper = mayCompressStructureTreeElements()
+ TrailerOutputHelper trailerOutputHelper = useObjectStreams()
? new CompressedTrailerOutputHelper()
: new UncompressedTrailerOutputHelper();
if (structureTreeElements != null) {
stream.write(encode(trailer));
}
- private boolean mayCompressStructureTreeElements() {
- return accessibilityEnabled
- && versionController.getPDFVersion().compareTo(Version.V1_5) >= 0
- && !isLinearizationEnabled();
+ private boolean useObjectStreams() {
+ if (objectStreamsEnabled && linearizationEnabled) {
+ throw new UnsupportedOperationException("Linearization and use-object-streams can't be both enabled");
+ }
+ if (objectStreamsEnabled && isEncryptionActive()) {
+ throw new UnsupportedOperationException("Encryption and use-object-streams can't be both enabled");
+ }
+ return objectStreamsEnabled || (accessibilityEnabled
+ && versionController.getPDFVersion().compareTo(Version.V1_5) >= 0 && !isLinearizationEnabled());
}
private TrailerDictionary createTrailerDictionary(boolean addRoot) {
}
private class CompressedTrailerOutputHelper implements TrailerOutputHelper {
-
- private ObjectStreamManager structureTreeObjectStreams;
-
- public void outputStructureTreeElements(OutputStream stream)
- throws IOException {
+ public void outputStructureTreeElements(OutputStream stream) {
assert structureTreeElements.size() > 0;
- structureTreeObjectStreams = new ObjectStreamManager(PDFDocument.this);
+ if (objectStreamManager == null) {
+ objectStreamManager = new ObjectStreamManager(PDFDocument.this);
+ }
for (PDFStructElem structElem : structureTreeElements) {
- structureTreeObjectStreams.add(structElem);
+ objectStreamManager.add(structElem);
}
}
TrailerDictionary trailerDictionary, int first, int last, int size) throws IOException {
// Outputting the object streams should not have created new indirect objects
assert objects.isEmpty();
- new CrossReferenceStream(PDFDocument.this, ++objectcount, trailerDictionary, position,
- indirectObjectOffsets,
- structureTreeObjectStreams.getCompressedObjectReferences())
+ new CrossReferenceStream(PDFDocument.this, trailerDictionary, position,
+ indirectObjectOffsets, objectStreamManager.getCompressedObjectReferences())
.output(stream);
return position;
}
public void setFormXObjectEnabled(boolean b) {
formXObjectEnabled = b;
}
+
+ public void setObjectStreamsEnabled(boolean b) {
+ objectStreamsEnabled = b;
+ }
+
+ public int getObjectCount() {
+ return objectcount;
+ }
}
return sb.toString();
}
+ public boolean supportsObjectStream() {
+ return false;
+ }
}
* Object has a number and a generation (although the generation will always
* be 0 in new documents).
*/
-public abstract class PDFObject implements PDFWritable {
+public abstract class PDFObject implements PDFWritable, CompressedObject {
/** logger for all PDFObjects (and descendants) */
protected static final Log log = LogFactory.getLog(PDFObject.class.getName());
public void getChildren(Set<PDFObject> children) {
}
+
+ public boolean supportsObjectStream() {
+ return true;
+ }
}
startOfDocMDP = countingOutputStream.getByteCount();
return super.output(stream);
}
- throw new IOException("Disable pdf linearization");
+ throw new IOException("Disable pdf linearization and use-object-streams");
}
}
/**
* Class representing a PDF Structure Element.
*/
-public class PDFStructElem extends StructureHierarchyMember
- implements StructureTreeElement, CompressedObject, Serializable {
+public class PDFStructElem extends StructureHierarchyMember implements StructureTreeElement, Serializable {
private static final List<StructureType> BLSE = Arrays.asList(StandardStructureTypes.Table.TABLE,
StandardStructureTypes.List.L, StandardStructureTypes.Paragraphlike.P);
private final List<ObjectReference> objectReferences;
- public CrossReferenceStream(PDFDocument document,
+ public CrossReferenceStream(PDFDocument document, TrailerDictionary trailerDictionary, long startxref,
+ List<Long> uncompressedObjectReferences, List<CompressedObjectReference> compressedObjectReferences) {
+ this(document, document.getObjectCount() + 1, trailerDictionary, startxref,
+ uncompressedObjectReferences, compressedObjectReferences);
+ }
+
+ protected CrossReferenceStream(PDFDocument document,
int objectNumber,
TrailerDictionary trailerDictionary,
long startxref,
super(trailerDictionary, startxref);
this.document = document;
this.objectNumber = objectNumber;
- this.objectReferences = new ArrayList<ObjectReference>(uncompressedObjectReferences.size());
+ this.objectReferences = new ArrayList<>(uncompressedObjectReferences.size());
for (Long offset : uncompressedObjectReferences) {
objectReferences.add(offset == null ? null : new UncompressedObjectReference(offset));
}
import static org.apache.fop.render.pdf.PDFRendererOption.LINEARIZATION;
import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FONTS;
import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FORM_FIELDS;
+import static org.apache.fop.render.pdf.PDFRendererOption.OBJECT_STREAMS;
import static org.apache.fop.render.pdf.PDFRendererOption.OUTPUT_PROFILE;
import static org.apache.fop.render.pdf.PDFRendererOption.PDF_A_MODE;
import static org.apache.fop.render.pdf.PDFRendererOption.PDF_UA_MODE;
parseAndPut(MERGE_FORM_FIELDS, cfg);
parseAndPut(LINEARIZATION, cfg);
parseAndPut(FORM_XOBJECT, cfg);
+ parseAndPut(OBJECT_STREAMS, cfg);
parseAndPut(VERSION, cfg);
configureSignParams(cfg);
} catch (ConfigurationException e) {
return Boolean.valueOf(value);
}
},
+ OBJECT_STREAMS("use-object-streams", false) {
+ @Override
+ Boolean deserialize(String value) {
+ return Boolean.valueOf(value);
+ }
+ },
/** Rendering Options key for the ICC profile for the output intent. */
OUTPUT_PROFILE("output-profile") {
@Override
import static org.apache.fop.render.pdf.PDFRendererOption.LINEARIZATION;
import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FONTS;
import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FORM_FIELDS;
+import static org.apache.fop.render.pdf.PDFRendererOption.OBJECT_STREAMS;
import static org.apache.fop.render.pdf.PDFRendererOption.OUTPUT_PROFILE;
import static org.apache.fop.render.pdf.PDFRendererOption.PDF_A_MODE;
import static org.apache.fop.render.pdf.PDFRendererOption.PDF_UA_MODE;
public Boolean getFormXObjectEnabled() {
return (Boolean)properties.get(FORM_XOBJECT);
}
+
+ public Boolean getObjectStreamsEnabled() {
+ return (Boolean)properties.get(OBJECT_STREAMS);
+ }
}
pdfDoc.setMergeFormFieldsEnabled(rendererConfig.getMergeFormFieldsEnabled());
pdfDoc.setLinearizationEnabled(rendererConfig.getLinearizationEnabled());
pdfDoc.setFormXObjectEnabled(rendererConfig.getFormXObjectEnabled());
+ pdfDoc.setObjectStreamsEnabled(rendererConfig.getObjectStreamsEnabled());
return this.pdfDoc;
}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+package org.apache.fop.pdf;
+
+import java.awt.geom.Rectangle2D;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.fop.render.pdf.PDFContentGenerator;
+
+public class PDFObjectStreamTestCase {
+ @Test
+ public void testObjectStreamsEnabled() throws IOException {
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ PDFDocument doc = new PDFDocument("");
+ Map<String, List<String>> filterMap = new HashMap<>();
+ List<String> filterList = new ArrayList<>();
+ filterList.add("null");
+ filterMap.put("default", filterList);
+ doc.setFilterMap(filterMap);
+ doc.setObjectStreamsEnabled(true);
+ PDFResources resources = new PDFResources(doc);
+ doc.addObject(resources);
+ PDFResourceContext context = new PDFResourceContext(resources);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PDFContentGenerator gen = new PDFContentGenerator(doc, out, context);
+ Rectangle2D.Float f = new Rectangle2D.Float();
+ PDFPage page = new PDFPage(resources, 0, f, f, f, f);
+ doc.registerObject(page);
+ doc.addImage(context, new BitmapImage("", 1, 1, new byte[0], null));
+ gen.flushPDFDoc();
+ doc.outputTrailer(out);
+ Assert.assertTrue(out.toString().contains("/Subtype /Image"));
+ Assert.assertTrue(out.toString().contains("<<\n /Type /ObjStm\n /N 3\n /First 15\n /Length 260\n>>\n"
+ + "stream\n8 0\n9 52\n4 121\n<<\n/Producer"));
+ }
+}