aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Steiner <ssteiner@apache.org>2024-10-22 09:47:25 +0100
committerSimon Steiner <ssteiner@apache.org>2024-10-22 09:47:25 +0100
commitb6247436442d04237338fd90a93efcc79c102bf8 (patch)
treede9cb4e49c0ed82729440bc000f16214d56a17ea
parent9ac71d8d7435a147c1283f641bbab07f777818c7 (diff)
downloadxmlgraphics-fop-b6247436442d04237338fd90a93efcc79c102bf8.tar.gz
xmlgraphics-fop-b6247436442d04237338fd90a93efcc79c102bf8.zip
FOP-3215: Add support for PDF object streams
-rw-r--r--fop-core/src/main/java/org/apache/fop/pdf/AbstractPDFStream.java4
-rw-r--r--fop-core/src/main/java/org/apache/fop/pdf/PDFDocument.java71
-rw-r--r--fop-core/src/main/java/org/apache/fop/pdf/PDFNumber.java3
-rw-r--r--fop-core/src/main/java/org/apache/fop/pdf/PDFObject.java6
-rw-r--r--fop-core/src/main/java/org/apache/fop/pdf/PDFSignature.java2
-rw-r--r--fop-core/src/main/java/org/apache/fop/pdf/PDFStructElem.java3
-rw-r--r--fop-core/src/main/java/org/apache/fop/pdf/xref/CrossReferenceStream.java10
-rw-r--r--fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererConfig.java2
-rw-r--r--fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOption.java6
-rw-r--r--fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java5
-rw-r--r--fop-core/src/main/java/org/apache/fop/render/pdf/PDFRenderingUtil.java1
-rw-r--r--fop-core/src/test/java/org/apache/fop/pdf/PDFObjectStreamTestCase.java61
12 files changed, 150 insertions, 24 deletions
diff --git a/fop-core/src/main/java/org/apache/fop/pdf/AbstractPDFStream.java b/fop-core/src/main/java/org/apache/fop/pdf/AbstractPDFStream.java
index 8dba4981d..c70b812c3 100644
--- a/fop-core/src/main/java/org/apache/fop/pdf/AbstractPDFStream.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/AbstractPDFStream.java
@@ -298,4 +298,8 @@ public abstract class AbstractPDFStream extends PDFObject {
getDocument().registerObject(refLength);
}
}
+
+ public boolean supportsObjectStream() {
+ return false;
+ }
}
diff --git a/fop-core/src/main/java/org/apache/fop/pdf/PDFDocument.java b/fop-core/src/main/java/org/apache/fop/pdf/PDFDocument.java
index 34421858a..12f14354e 100644
--- a/fop-core/src/main/java/org/apache/fop/pdf/PDFDocument.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/PDFDocument.java
@@ -173,6 +173,8 @@ public class PDFDocument {
private FileIDGenerator fileIDGenerator;
+ private ObjectStreamManager objectStreamManager;
+
private boolean accessibilityEnabled;
private boolean mergeFontsEnabled;
@@ -185,6 +187,8 @@ public class PDFDocument {
protected boolean outputStarted;
+ private boolean objectStreamsEnabled;
+
/**
* Creates an empty PDF document.
*
@@ -1027,15 +1031,36 @@ public class PDFDocument {
//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) {
@@ -1148,7 +1173,7 @@ public class PDFDocument {
}
private void outputTrailerObjectsAndXref(OutputStream stream) throws IOException {
- TrailerOutputHelper trailerOutputHelper = mayCompressStructureTreeElements()
+ TrailerOutputHelper trailerOutputHelper = useObjectStreams()
? new CompressedTrailerOutputHelper()
: new UncompressedTrailerOutputHelper();
if (structureTreeElements != null) {
@@ -1170,10 +1195,15 @@ public class PDFDocument {
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) {
@@ -1236,15 +1266,13 @@ public class PDFDocument {
}
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);
}
}
@@ -1252,9 +1280,8 @@ public class PDFDocument {
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;
}
@@ -1290,4 +1317,12 @@ public class PDFDocument {
public void setFormXObjectEnabled(boolean b) {
formXObjectEnabled = b;
}
+
+ public void setObjectStreamsEnabled(boolean b) {
+ objectStreamsEnabled = b;
+ }
+
+ public int getObjectCount() {
+ return objectcount;
+ }
}
diff --git a/fop-core/src/main/java/org/apache/fop/pdf/PDFNumber.java b/fop-core/src/main/java/org/apache/fop/pdf/PDFNumber.java
index 26489c6ec..5b362f1be 100644
--- a/fop-core/src/main/java/org/apache/fop/pdf/PDFNumber.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/PDFNumber.java
@@ -120,5 +120,8 @@ public class PDFNumber extends PDFObject {
return sb.toString();
}
+ public boolean supportsObjectStream() {
+ return false;
+ }
}
diff --git a/fop-core/src/main/java/org/apache/fop/pdf/PDFObject.java b/fop-core/src/main/java/org/apache/fop/pdf/PDFObject.java
index 12402d72c..e1e429062 100644
--- a/fop-core/src/main/java/org/apache/fop/pdf/PDFObject.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/PDFObject.java
@@ -34,7 +34,7 @@ import org.apache.commons.logging.LogFactory;
* 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());
@@ -358,4 +358,8 @@ public abstract class PDFObject implements PDFWritable {
public void getChildren(Set<PDFObject> children) {
}
+
+ public boolean supportsObjectStream() {
+ return true;
+ }
}
diff --git a/fop-core/src/main/java/org/apache/fop/pdf/PDFSignature.java b/fop-core/src/main/java/org/apache/fop/pdf/PDFSignature.java
index 5bdf5ebdf..1ff785553 100644
--- a/fop-core/src/main/java/org/apache/fop/pdf/PDFSignature.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/PDFSignature.java
@@ -120,7 +120,7 @@ public class PDFSignature {
startOfDocMDP = countingOutputStream.getByteCount();
return super.output(stream);
}
- throw new IOException("Disable pdf linearization");
+ throw new IOException("Disable pdf linearization and use-object-streams");
}
}
diff --git a/fop-core/src/main/java/org/apache/fop/pdf/PDFStructElem.java b/fop-core/src/main/java/org/apache/fop/pdf/PDFStructElem.java
index 4f4845cfc..cc299fa1d 100644
--- a/fop-core/src/main/java/org/apache/fop/pdf/PDFStructElem.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/PDFStructElem.java
@@ -35,8 +35,7 @@ import org.apache.fop.util.LanguageTags;
/**
* 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);
diff --git a/fop-core/src/main/java/org/apache/fop/pdf/xref/CrossReferenceStream.java b/fop-core/src/main/java/org/apache/fop/pdf/xref/CrossReferenceStream.java
index 9dbd317a7..c7c3765d5 100644
--- a/fop-core/src/main/java/org/apache/fop/pdf/xref/CrossReferenceStream.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/xref/CrossReferenceStream.java
@@ -47,7 +47,13 @@ public class CrossReferenceStream extends CrossReferenceObject {
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,
@@ -56,7 +62,7 @@ public class CrossReferenceStream extends CrossReferenceObject {
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));
}
diff --git a/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererConfig.java b/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererConfig.java
index 075e6037e..358cbab21 100644
--- a/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererConfig.java
+++ b/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererConfig.java
@@ -62,6 +62,7 @@ import static org.apache.fop.render.pdf.PDFRendererOption.FORM_XOBJECT;
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;
@@ -155,6 +156,7 @@ public final class PDFRendererConfig implements RendererConfig {
parseAndPut(MERGE_FORM_FIELDS, cfg);
parseAndPut(LINEARIZATION, cfg);
parseAndPut(FORM_XOBJECT, cfg);
+ parseAndPut(OBJECT_STREAMS, cfg);
parseAndPut(VERSION, cfg);
configureSignParams(cfg);
} catch (ConfigurationException e) {
diff --git a/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOption.java b/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOption.java
index f39d4e08b..20b97772a 100644
--- a/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOption.java
+++ b/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOption.java
@@ -105,6 +105,12 @@ public enum PDFRendererOption implements RendererConfigOption {
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
diff --git a/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java b/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java
index 250857709..4ebd2da48 100644
--- a/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java
+++ b/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java
@@ -38,6 +38,7 @@ import static org.apache.fop.render.pdf.PDFRendererOption.FORM_XOBJECT;
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;
@@ -157,4 +158,8 @@ public final class PDFRendererOptionsConfig {
public Boolean getFormXObjectEnabled() {
return (Boolean)properties.get(FORM_XOBJECT);
}
+
+ public Boolean getObjectStreamsEnabled() {
+ return (Boolean)properties.get(OBJECT_STREAMS);
+ }
}
diff --git a/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
index 8a6ebe5be..5d565cb47 100644
--- a/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
+++ b/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
@@ -635,6 +635,7 @@ class PDFRenderingUtil {
pdfDoc.setMergeFormFieldsEnabled(rendererConfig.getMergeFormFieldsEnabled());
pdfDoc.setLinearizationEnabled(rendererConfig.getLinearizationEnabled());
pdfDoc.setFormXObjectEnabled(rendererConfig.getFormXObjectEnabled());
+ pdfDoc.setObjectStreamsEnabled(rendererConfig.getObjectStreamsEnabled());
return this.pdfDoc;
}
diff --git a/fop-core/src/test/java/org/apache/fop/pdf/PDFObjectStreamTestCase.java b/fop-core/src/test/java/org/apache/fop/pdf/PDFObjectStreamTestCase.java
new file mode 100644
index 000000000..702c320a8
--- /dev/null
+++ b/fop-core/src/test/java/org/apache/fop/pdf/PDFObjectStreamTestCase.java
@@ -0,0 +1,61 @@
+/*
+ * 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"));
+ }
+}