Skip to content

Commit b624743

Browse files
FOP-3215: Add support for PDF object streams
1 parent 9ac71d8 commit b624743

12 files changed

+150
-24
lines changed

fop-core/src/main/java/org/apache/fop/pdf/AbstractPDFStream.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,8 @@ public void registerChildren() {
298298
getDocument().registerObject(refLength);
299299
}
300300
}
301+
302+
public boolean supportsObjectStream() {
303+
return false;
304+
}
301305
}

fop-core/src/main/java/org/apache/fop/pdf/PDFDocument.java

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ public class PDFDocument {
173173

174174
private FileIDGenerator fileIDGenerator;
175175

176+
private ObjectStreamManager objectStreamManager;
177+
176178
private boolean accessibilityEnabled;
177179

178180
private boolean mergeFontsEnabled;
@@ -185,6 +187,8 @@ public class PDFDocument {
185187

186188
protected boolean outputStarted;
187189

190+
private boolean objectStreamsEnabled;
191+
188192
/**
189193
* Creates an empty PDF document.
190194
*
@@ -1027,15 +1031,36 @@ public void output(OutputStream stream) throws IOException {
10271031
//Write out objects until the list is empty. This approach (used with a
10281032
//LinkedList) allows for output() methods to create and register objects
10291033
//on the fly even during serialization.
1030-
while (this.objects.size() > 0) {
1031-
PDFObject object = this.objects.remove(0);
1034+
1035+
if (objectStreamsEnabled) {
1036+
List<PDFObject> indirectObjects = new ArrayList<>();
1037+
while (objects.size() > 0) {
1038+
PDFObject object = objects.remove(0);
1039+
if (object.supportsObjectStream()) {
1040+
addToObjectStream(object);
1041+
} else {
1042+
indirectObjects.add(object);
1043+
}
1044+
}
1045+
objects.addAll(indirectObjects);
1046+
}
1047+
1048+
while (objects.size() > 0) {
1049+
PDFObject object = objects.remove(0);
10321050
streamIndirectObject(object, stream);
10331051
}
10341052
}
10351053

1054+
private void addToObjectStream(CompressedObject object) {
1055+
if (objectStreamManager == null) {
1056+
objectStreamManager = new ObjectStreamManager(this);
1057+
}
1058+
objectStreamManager.add(object);
1059+
}
1060+
10361061
protected void writeTrailer(OutputStream stream, int first, int last, int size, long mainOffset, long startxref)
10371062
throws IOException {
1038-
TrailerOutputHelper trailerOutputHelper = mayCompressStructureTreeElements()
1063+
TrailerOutputHelper trailerOutputHelper = useObjectStreams()
10391064
? new CompressedTrailerOutputHelper()
10401065
: new UncompressedTrailerOutputHelper();
10411066
if (structureTreeElements != null) {
@@ -1148,7 +1173,7 @@ private void createDestinations() {
11481173
}
11491174

11501175
private void outputTrailerObjectsAndXref(OutputStream stream) throws IOException {
1151-
TrailerOutputHelper trailerOutputHelper = mayCompressStructureTreeElements()
1176+
TrailerOutputHelper trailerOutputHelper = useObjectStreams()
11521177
? new CompressedTrailerOutputHelper()
11531178
: new UncompressedTrailerOutputHelper();
11541179
if (structureTreeElements != null) {
@@ -1170,10 +1195,15 @@ private void outputTrailerObjectsAndXref(OutputStream stream) throws IOException
11701195
stream.write(encode(trailer));
11711196
}
11721197

1173-
private boolean mayCompressStructureTreeElements() {
1174-
return accessibilityEnabled
1175-
&& versionController.getPDFVersion().compareTo(Version.V1_5) >= 0
1176-
&& !isLinearizationEnabled();
1198+
private boolean useObjectStreams() {
1199+
if (objectStreamsEnabled && linearizationEnabled) {
1200+
throw new UnsupportedOperationException("Linearization and use-object-streams can't be both enabled");
1201+
}
1202+
if (objectStreamsEnabled && isEncryptionActive()) {
1203+
throw new UnsupportedOperationException("Encryption and use-object-streams can't be both enabled");
1204+
}
1205+
return objectStreamsEnabled || (accessibilityEnabled
1206+
&& versionController.getPDFVersion().compareTo(Version.V1_5) >= 0 && !isLinearizationEnabled());
11771207
}
11781208

11791209
private TrailerDictionary createTrailerDictionary(boolean addRoot) {
@@ -1236,25 +1266,22 @@ public long outputCrossReferenceObject(OutputStream stream,
12361266
}
12371267

12381268
private class CompressedTrailerOutputHelper implements TrailerOutputHelper {
1239-
1240-
private ObjectStreamManager structureTreeObjectStreams;
1241-
1242-
public void outputStructureTreeElements(OutputStream stream)
1243-
throws IOException {
1269+
public void outputStructureTreeElements(OutputStream stream) {
12441270
assert structureTreeElements.size() > 0;
1245-
structureTreeObjectStreams = new ObjectStreamManager(PDFDocument.this);
1271+
if (objectStreamManager == null) {
1272+
objectStreamManager = new ObjectStreamManager(PDFDocument.this);
1273+
}
12461274
for (PDFStructElem structElem : structureTreeElements) {
1247-
structureTreeObjectStreams.add(structElem);
1275+
objectStreamManager.add(structElem);
12481276
}
12491277
}
12501278

12511279
public long outputCrossReferenceObject(OutputStream stream,
12521280
TrailerDictionary trailerDictionary, int first, int last, int size) throws IOException {
12531281
// Outputting the object streams should not have created new indirect objects
12541282
assert objects.isEmpty();
1255-
new CrossReferenceStream(PDFDocument.this, ++objectcount, trailerDictionary, position,
1256-
indirectObjectOffsets,
1257-
structureTreeObjectStreams.getCompressedObjectReferences())
1283+
new CrossReferenceStream(PDFDocument.this, trailerDictionary, position,
1284+
indirectObjectOffsets, objectStreamManager.getCompressedObjectReferences())
12581285
.output(stream);
12591286
return position;
12601287
}
@@ -1290,4 +1317,12 @@ public boolean isFormXObjectEnabled() {
12901317
public void setFormXObjectEnabled(boolean b) {
12911318
formXObjectEnabled = b;
12921319
}
1320+
1321+
public void setObjectStreamsEnabled(boolean b) {
1322+
objectStreamsEnabled = b;
1323+
}
1324+
1325+
public int getObjectCount() {
1326+
return objectcount;
1327+
}
12931328
}

fop-core/src/main/java/org/apache/fop/pdf/PDFNumber.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,8 @@ protected String toPDFString() {
120120
return sb.toString();
121121
}
122122

123+
public boolean supportsObjectStream() {
124+
return false;
125+
}
123126
}
124127

fop-core/src/main/java/org/apache/fop/pdf/PDFObject.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
* Object has a number and a generation (although the generation will always
3535
* be 0 in new documents).
3636
*/
37-
public abstract class PDFObject implements PDFWritable {
37+
public abstract class PDFObject implements PDFWritable, CompressedObject {
3838

3939
/** logger for all PDFObjects (and descendants) */
4040
protected static final Log log = LogFactory.getLog(PDFObject.class.getName());
@@ -358,4 +358,8 @@ protected boolean contentEquals(PDFObject o) {
358358

359359
public void getChildren(Set<PDFObject> children) {
360360
}
361+
362+
public boolean supportsObjectStream() {
363+
return true;
364+
}
361365
}

fop-core/src/main/java/org/apache/fop/pdf/PDFSignature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public int output(OutputStream stream) throws IOException {
120120
startOfDocMDP = countingOutputStream.getByteCount();
121121
return super.output(stream);
122122
}
123-
throw new IOException("Disable pdf linearization");
123+
throw new IOException("Disable pdf linearization and use-object-streams");
124124
}
125125
}
126126

fop-core/src/main/java/org/apache/fop/pdf/PDFStructElem.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@
3535
/**
3636
* Class representing a PDF Structure Element.
3737
*/
38-
public class PDFStructElem extends StructureHierarchyMember
39-
implements StructureTreeElement, CompressedObject, Serializable {
38+
public class PDFStructElem extends StructureHierarchyMember implements StructureTreeElement, Serializable {
4039
private static final List<StructureType> BLSE = Arrays.asList(StandardStructureTypes.Table.TABLE,
4140
StandardStructureTypes.List.L, StandardStructureTypes.Paragraphlike.P);
4241

fop-core/src/main/java/org/apache/fop/pdf/xref/CrossReferenceStream.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@ public class CrossReferenceStream extends CrossReferenceObject {
4747

4848
private final List<ObjectReference> objectReferences;
4949

50-
public CrossReferenceStream(PDFDocument document,
50+
public CrossReferenceStream(PDFDocument document, TrailerDictionary trailerDictionary, long startxref,
51+
List<Long> uncompressedObjectReferences, List<CompressedObjectReference> compressedObjectReferences) {
52+
this(document, document.getObjectCount() + 1, trailerDictionary, startxref,
53+
uncompressedObjectReferences, compressedObjectReferences);
54+
}
55+
56+
protected CrossReferenceStream(PDFDocument document,
5157
int objectNumber,
5258
TrailerDictionary trailerDictionary,
5359
long startxref,
@@ -56,7 +62,7 @@ public CrossReferenceStream(PDFDocument document,
5662
super(trailerDictionary, startxref);
5763
this.document = document;
5864
this.objectNumber = objectNumber;
59-
this.objectReferences = new ArrayList<ObjectReference>(uncompressedObjectReferences.size());
65+
this.objectReferences = new ArrayList<>(uncompressedObjectReferences.size());
6066
for (Long offset : uncompressedObjectReferences) {
6167
objectReferences.add(offset == null ? null : new UncompressedObjectReference(offset));
6268
}

fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import static org.apache.fop.render.pdf.PDFRendererOption.LINEARIZATION;
6363
import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FONTS;
6464
import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FORM_FIELDS;
65+
import static org.apache.fop.render.pdf.PDFRendererOption.OBJECT_STREAMS;
6566
import static org.apache.fop.render.pdf.PDFRendererOption.OUTPUT_PROFILE;
6667
import static org.apache.fop.render.pdf.PDFRendererOption.PDF_A_MODE;
6768
import static org.apache.fop.render.pdf.PDFRendererOption.PDF_UA_MODE;
@@ -155,6 +156,7 @@ private void configure(Configuration cfg, FOUserAgent userAgent, boolean strict)
155156
parseAndPut(MERGE_FORM_FIELDS, cfg);
156157
parseAndPut(LINEARIZATION, cfg);
157158
parseAndPut(FORM_XOBJECT, cfg);
159+
parseAndPut(OBJECT_STREAMS, cfg);
158160
parseAndPut(VERSION, cfg);
159161
configureSignParams(cfg);
160162
} catch (ConfigurationException e) {

fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOption.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ Boolean deserialize(String value) {
105105
return Boolean.valueOf(value);
106106
}
107107
},
108+
OBJECT_STREAMS("use-object-streams", false) {
109+
@Override
110+
Boolean deserialize(String value) {
111+
return Boolean.valueOf(value);
112+
}
113+
},
108114
/** Rendering Options key for the ICC profile for the output intent. */
109115
OUTPUT_PROFILE("output-profile") {
110116
@Override

fop-core/src/main/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import static org.apache.fop.render.pdf.PDFRendererOption.LINEARIZATION;
3939
import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FONTS;
4040
import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FORM_FIELDS;
41+
import static org.apache.fop.render.pdf.PDFRendererOption.OBJECT_STREAMS;
4142
import static org.apache.fop.render.pdf.PDFRendererOption.OUTPUT_PROFILE;
4243
import static org.apache.fop.render.pdf.PDFRendererOption.PDF_A_MODE;
4344
import static org.apache.fop.render.pdf.PDFRendererOption.PDF_UA_MODE;
@@ -157,4 +158,8 @@ public Boolean getLinearizationEnabled() {
157158
public Boolean getFormXObjectEnabled() {
158159
return (Boolean)properties.get(FORM_XOBJECT);
159160
}
161+
162+
public Boolean getObjectStreamsEnabled() {
163+
return (Boolean)properties.get(OBJECT_STREAMS);
164+
}
160165
}

fop-core/src/main/java/org/apache/fop/render/pdf/PDFRenderingUtil.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,7 @@ public PDFDocument setupPDFDocument(OutputStream out) throws IOException {
635635
pdfDoc.setMergeFormFieldsEnabled(rendererConfig.getMergeFormFieldsEnabled());
636636
pdfDoc.setLinearizationEnabled(rendererConfig.getLinearizationEnabled());
637637
pdfDoc.setFormXObjectEnabled(rendererConfig.getFormXObjectEnabled());
638+
pdfDoc.setObjectStreamsEnabled(rendererConfig.getObjectStreamsEnabled());
638639

639640
return this.pdfDoc;
640641
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/* $Id$ */
19+
package org.apache.fop.pdf;
20+
21+
import java.awt.geom.Rectangle2D;
22+
import java.io.ByteArrayOutputStream;
23+
import java.io.IOException;
24+
import java.util.ArrayList;
25+
import java.util.HashMap;
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.TimeZone;
29+
30+
import org.junit.Assert;
31+
import org.junit.Test;
32+
33+
import org.apache.fop.render.pdf.PDFContentGenerator;
34+
35+
public class PDFObjectStreamTestCase {
36+
@Test
37+
public void testObjectStreamsEnabled() throws IOException {
38+
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
39+
PDFDocument doc = new PDFDocument("");
40+
Map<String, List<String>> filterMap = new HashMap<>();
41+
List<String> filterList = new ArrayList<>();
42+
filterList.add("null");
43+
filterMap.put("default", filterList);
44+
doc.setFilterMap(filterMap);
45+
doc.setObjectStreamsEnabled(true);
46+
PDFResources resources = new PDFResources(doc);
47+
doc.addObject(resources);
48+
PDFResourceContext context = new PDFResourceContext(resources);
49+
ByteArrayOutputStream out = new ByteArrayOutputStream();
50+
PDFContentGenerator gen = new PDFContentGenerator(doc, out, context);
51+
Rectangle2D.Float f = new Rectangle2D.Float();
52+
PDFPage page = new PDFPage(resources, 0, f, f, f, f);
53+
doc.registerObject(page);
54+
doc.addImage(context, new BitmapImage("", 1, 1, new byte[0], null));
55+
gen.flushPDFDoc();
56+
doc.outputTrailer(out);
57+
Assert.assertTrue(out.toString().contains("/Subtype /Image"));
58+
Assert.assertTrue(out.toString().contains("<<\n /Type /ObjStm\n /N 3\n /First 15\n /Length 260\n>>\n"
59+
+ "stream\n8 0\n9 52\n4 121\n<<\n/Producer"));
60+
}
61+
}

0 commit comments

Comments
 (0)