git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1305467 13f79535-47bb-0310-9956-ffa450edef68tags/fop-1_1rc1old
@@ -255,14 +255,6 @@ | |||
<module name="UpperEll"/> | |||
<!-- ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... --> | |||
<!-- ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... --> | |||
<module name="VisibilityModifier"> | |||
<property name="packageAllowed" value="false"/> | |||
<property name="protectedAllowed" value="true"/> | |||
<property name="publicMemberPattern" value="^serialVersionUID"/> | |||
</module> | |||
<!-- ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... --> | |||
<!-- ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... --> | |||
<module name="WhitespaceAfter"> | |||
<property name="tokens" value="COMMA,SEMI"/> <!-- adding TYPECAST produces 2203 new errors --> |
@@ -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); | |||
} | |||
} | |||
@@ -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; | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 | |||
*/ | |||
} |
@@ -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,13 +83,22 @@ 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); | |||
this.values.addAll(values); | |||
} | |||
/** | |||
* 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 | |||
@@ -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(); | |||
} |
@@ -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(); | |||
} | |||
@@ -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); |
@@ -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); | |||
@@ -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(); | |||
} |
@@ -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. | |||
* | |||
@@ -265,17 +248,6 @@ public class PDFDocument { | |||
return this.factory; | |||
} | |||
/** | |||
* 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. | |||
* | |||
@@ -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; | |||
} | |||
@@ -367,6 +339,37 @@ public class PDFDocument { | |||
return this.root; | |||
} | |||
/** | |||
* 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. | |||
* | |||
@@ -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() { |
@@ -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(); | |||
} |
@@ -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) { |
@@ -912,35 +912,6 @@ public class PDFFactory { | |||
return pageLabels; | |||
} | |||
/** | |||
* 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). | |||
* |
@@ -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); |
@@ -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); | |||
} |
@@ -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. |
@@ -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"); | |||
} | |||
@@ -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(); | |||
} | |||
@@ -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 + ">>"; | |||
} | |||
/* |
@@ -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(); | |||
} |
@@ -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(); | |||
} | |||
@@ -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 |
@@ -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} */ |
@@ -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); | |||
} |
@@ -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(); | |||
} |
@@ -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; | |||
} | |||
@@ -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 |
@@ -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(); | |||
} |
@@ -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(); | |||
} | |||
@@ -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(); | |||
} |
@@ -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; |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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(); | |||
} | |||
@@ -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; | |||
} | |||
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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()); | |||
} |
@@ -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. |
@@ -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; | |||
@@ -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"); |
@@ -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"); |
@@ -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(); | |||
} | |||
@@ -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(); | |||
} | |||
} |
@@ -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 |
@@ -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()) { |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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}); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); |
@@ -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; | |||
} | |||
@@ -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); | |||
} |
@@ -62,6 +62,11 @@ | |||
documents. Example: the fix of marks layering will be such a case when it's done. | |||
--> | |||
<release version="FOP Trunk" date="TBD"> | |||
<action context="Code" dev="PH,VH" type="add"> | |||
Added support for PDF Object Streams. When accessibility is enabled and PDF version 1.5 | |||
selected, the structure tree will be stored in object streams in order to reduce the size of | |||
the final PDF. | |||
</action> | |||
<action context="Code" dev="VH" type="add" fixes-bug="46962" due-to="Alexios Giotis"> | |||
Fixed deadlock in PropertyCache. | |||
</action> |
@@ -48,10 +48,10 @@ public class AbstractPDFStreamTestCase extends PDFObjectTestCase { | |||
encodedBytes[i++] = (byte) (in & 0xff); | |||
} | |||
} | |||
private String startStream = "1 0 obj\n" + | |||
"<< /Length 5 0 R /Filter /FlateDecode >>\n" + | |||
"stream\n"; | |||
private String endStream = "endstream\nendobj\n"; | |||
private String startStream = "<< /Length 5 0 R /Filter /FlateDecode >>\n" | |||
+ "stream\n"; | |||
private String endStream = "endstream"; | |||
@Before | |||
public void setUp() { |
@@ -0,0 +1,113 @@ | |||
/* | |||
* 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 static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.util.List; | |||
import org.junit.Test; | |||
import org.apache.fop.pdf.xref.CompressedObjectReference; | |||
public class ObjectStreamManagerTestCase { | |||
private List<CompressedObjectReference> compressedObjectReferences; | |||
private MockPdfDocument pdfDocument; | |||
@Test | |||
public void add() { | |||
final int expectedCapacity = 100; | |||
final int numCompressedObjects = expectedCapacity * 2 + 1; | |||
createCompressObjectReferences(numCompressedObjects); | |||
assertEquals(numCompressedObjects, compressedObjectReferences.size()); | |||
int objectStreamNumber1 = assertSameObjectStream(0, expectedCapacity); | |||
int objectStreamNumber2 = assertSameObjectStream(expectedCapacity, expectedCapacity * 2); | |||
int objectStreamNumber3 = assertSameObjectStream(expectedCapacity * 2, numCompressedObjects); | |||
assertDifferent(objectStreamNumber1, objectStreamNumber2, objectStreamNumber3); | |||
assertEquals(objectStreamNumber3, pdfDocument.previous.getObjectNumber()); | |||
} | |||
private void createCompressObjectReferences(int numObjects) { | |||
pdfDocument = new MockPdfDocument(); | |||
ObjectStreamManager sut = new ObjectStreamManager(pdfDocument); | |||
for (int obNum = 1; obNum <= numObjects; obNum++) { | |||
sut.add(createCompressedObject(obNum)); | |||
} | |||
compressedObjectReferences = sut.getCompressedObjectReferences(); | |||
} | |||
private static class MockPdfDocument extends PDFDocument { | |||
private ObjectStream previous; | |||
public MockPdfDocument() { | |||
super(""); | |||
} | |||
public void assignObjectNumber(PDFObject obj) { | |||
super.assignObjectNumber(obj); | |||
if (obj instanceof ObjectStream) { | |||
ObjectStream objStream = (ObjectStream) obj; | |||
ObjectStream previous = (ObjectStream) objStream.get("Extends"); | |||
if (previous == null) { | |||
assertEquals(this.previous, previous); | |||
} | |||
this.previous = objStream; | |||
} | |||
} | |||
} | |||
private CompressedObject createCompressedObject(final int objectNumber) { | |||
return new CompressedObject() { | |||
public int getObjectNumber() { | |||
return objectNumber; | |||
} | |||
public int output(OutputStream outputStream) throws IOException { | |||
throw new UnsupportedOperationException(); | |||
} | |||
}; | |||
} | |||
private int assertSameObjectStream(int from, int to) { | |||
int objectStreamNumber = getObjectStreamNumber(from); | |||
for (int i = from + 1; i < to; i++) { | |||
assertEquals(objectStreamNumber, getObjectStreamNumber(i)); | |||
} | |||
return objectStreamNumber; | |||
} | |||
private int getObjectStreamNumber(int index) { | |||
return compressedObjectReferences.get(index).getObjectStreamNumber(); | |||
} | |||
private void assertDifferent(int objectStreamNumber1, int objectStreamNumber2, | |||
int objectStreamNumber3) { | |||
assertTrue(objectStreamNumber1 != objectStreamNumber2); | |||
assertTrue(objectStreamNumber1 != objectStreamNumber3); | |||
assertTrue(objectStreamNumber2 != objectStreamNumber3); | |||
} | |||
} |
@@ -0,0 +1,131 @@ | |||
/* | |||
* 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 static org.junit.Assert.assertEquals; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
public class ObjectStreamTestCase { | |||
private static final String OBJECT_CONTENT = "<<\n /Foo True\n /Bar False\n>>\n"; | |||
private PDFDocument pdfDocument; | |||
private ObjectStream objectStream; | |||
private List<MockCompressedObject> compressedObjects; | |||
@Before | |||
public void setUp() throws Exception { | |||
pdfDocument = new PDFDocument("PDFObjectStreamTestCase"); | |||
objectStream = new ObjectStream(); | |||
pdfDocument.assignObjectNumber(objectStream); | |||
compressedObjects = Arrays.asList(new MockCompressedObject(), new MockCompressedObject()); | |||
} | |||
@Test | |||
public void testSingleObjectStream() throws IOException { | |||
populateObjectStream(); | |||
testOutput(); | |||
} | |||
@Test | |||
public void testObjectStreamCollection() throws IOException { | |||
objectStream = new ObjectStream(objectStream); | |||
pdfDocument.assignObjectNumber(objectStream); | |||
populateObjectStream(); | |||
testOutput(); | |||
} | |||
@Test(expected = IllegalStateException.class) | |||
public void directObjectsAreNotAllowed() throws Exception { | |||
objectStream.addObject(new MockCompressedObject()); | |||
} | |||
@Test(expected = NullPointerException.class) | |||
public void nullObjectsAreNotAllowed() throws Exception { | |||
objectStream.addObject(null); | |||
} | |||
private void testOutput() throws IOException { | |||
String expected = getExpectedOutput(); | |||
String actual = getActualOutput(); | |||
assertEquals(expected, actual); | |||
} | |||
private void populateObjectStream() { | |||
for (MockCompressedObject obj : compressedObjects) { | |||
pdfDocument.assignObjectNumber(obj); | |||
objectStream.addObject(obj); | |||
} | |||
} | |||
private String getExpectedOutput() { | |||
int numObs = compressedObjects.size(); | |||
int objectStreamNumber = objectStream.getObjectNumber(); | |||
int offsetsLength = 9; | |||
StringBuilder expected = new StringBuilder(); | |||
expected.append("<<\n"); | |||
ObjectStream previous = (ObjectStream) objectStream.get("Extends"); | |||
if (previous != null) { | |||
expected.append(" /Extends ").append(previous.getObjectNumber()).append(" 0 R\n"); | |||
} | |||
expected.append(" /Type /ObjStm\n") | |||
.append(" /N ").append(numObs).append("\n") | |||
.append(" /First ").append(offsetsLength).append('\n') | |||
.append(" /Length ").append(OBJECT_CONTENT.length() * 2 + offsetsLength + 1).append('\n') | |||
.append(">>\n") | |||
.append("stream\n"); | |||
int offset = 0; | |||
int num = 1; | |||
for (PDFObject ob : compressedObjects) { | |||
expected.append(objectStreamNumber + num++).append(' ').append(offset).append('\n'); | |||
offset += ob.toPDFString().length(); | |||
} | |||
for (PDFObject ob : compressedObjects) { | |||
expected.append(ob.toPDFString()); | |||
} | |||
expected.append("\nendstream"); | |||
return expected.toString(); | |||
} | |||
private String getActualOutput() throws IOException { | |||
ByteArrayOutputStream actual = new ByteArrayOutputStream(); | |||
objectStream.getFilterList().setDisableAllFilters(true); | |||
objectStream.output(actual); | |||
return actual.toString("US-ASCII"); | |||
} | |||
private static class MockCompressedObject extends PDFObject implements CompressedObject { | |||
@Override | |||
protected String toPDFString() { | |||
return OBJECT_CONTENT; | |||
} | |||
} | |||
} |
@@ -223,8 +223,6 @@ public class PDFEncryptionJCETestCase { | |||
final String digits = "\\d+"; | |||
final String hexDigits = "\\p{XDigit}+"; | |||
dictionary.mustContain("1" + whitespace + "0" + whitespace + "obj"); | |||
dictionary.mustContain("/Filter" + whitespace + "/Standard\\b"); | |||
dictionary.mustContain("/V" + whitespace + "(" + digits + ")") |
@@ -0,0 +1,33 @@ | |||
/* | |||
* 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 static org.junit.Assert.assertFalse; | |||
import org.junit.Test; | |||
public class PDFFilterListTestCase { | |||
@Test | |||
public void testFilterList() { | |||
PDFFilterList filterList = new PDFFilterList(); | |||
assertFalse(filterList.isInitialized()); | |||
} | |||
} |
@@ -40,7 +40,9 @@ import org.junit.runners.Suite.SuiteClasses; | |||
PDFNullTestCase.class, | |||
PDFNumsArrayTestCase.class, | |||
PDFRectangleTestCase.class, | |||
PDFReferenceTestCase.class | |||
PDFReferenceTestCase.class, | |||
VersionTestCase.class, | |||
VersionControllerTestCase.class | |||
}) | |||
public class PDFLibraryTestSuite { | |||
} |
@@ -40,10 +40,6 @@ public class PDFObjectTestCase { | |||
protected final PDFObject parent = new DummyPDFObject(); | |||
/** The test subject */ | |||
protected PDFObject pdfObjectUnderTest; | |||
/** The string to begin describing the object <code>"1 0 obj\n"</code> */ | |||
protected final String beginObj = "1 0 obj\n"; | |||
/** The string to end describing the object <code>"\nendobj\n"</code> */ | |||
protected final String endObj = "\nendobj\n"; | |||
private static class DummyPDFObject extends PDFObject { | |||
@@ -195,8 +191,7 @@ public class PDFObjectTestCase { | |||
outStream.reset(); | |||
object.setObjectNumber(1); | |||
// Test the length of the output string is returned correctly. | |||
String string = beginObj + expectedString + endObj; | |||
assertEquals(string.length(), object.output(outStream)); | |||
assertEquals(string, outStream.toString()); | |||
assertEquals(expectedString.length(), object.output(outStream)); | |||
assertEquals(expectedString, outStream.toString()); | |||
} | |||
} |
@@ -0,0 +1,126 @@ | |||
/* | |||
* 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 static org.junit.Assert.assertArrayEquals; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertTrue; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
public class PDFStreamTestCase { | |||
private PDFStream stream; | |||
@Before | |||
public void createStream() { | |||
stream = new PDFStream(); | |||
stream.setObjectNumber(1); | |||
PDFDocument pdfDocument = new PDFDocument("Apache FOP"); | |||
stream.setDocument(pdfDocument); | |||
} | |||
@Test | |||
public void testFilterSetup() { | |||
testGetFilterList(); | |||
testSetupFilterList(); | |||
} | |||
private void testGetFilterList() { | |||
PDFFilterList filterList = stream.getFilterList(); | |||
assertFalse(filterList.isInitialized()); | |||
assertEquals(0, filterList.getFilters().size()); | |||
} | |||
private void testSetupFilterList() { | |||
stream.setupFilterList(); | |||
PDFFilterList filterList = stream.getFilterList(); | |||
assertTrue(filterList.isInitialized()); | |||
assertEquals(1, filterList.getFilters().size()); | |||
PDFFilter filter = filterList.getFilters().get(0); | |||
assertEquals("/FlateDecode", filter.getName()); | |||
} | |||
@Test | |||
public void customFilter() { | |||
PDFFilterList filters = stream.getFilterList(); | |||
filters.addFilter("null"); | |||
assertTrue(filters.isInitialized()); | |||
assertEquals(1, filters.getFilters().size()); | |||
PDFFilter filter = filters.getFilters().get(0); | |||
assertEquals("", filter.getName()); | |||
} | |||
@Test | |||
public void testStream() throws IOException { | |||
PDFFilterList filters = stream.getFilterList(); | |||
filters.addFilter("null"); | |||
byte[] bytes = createSampleData(); | |||
stream.setData(bytes); | |||
ByteArrayOutputStream actual = new ByteArrayOutputStream(); | |||
stream.outputRawStreamData(actual); | |||
assertArrayEquals(bytes, actual.toByteArray()); | |||
} | |||
@Test | |||
public void testEncodeStream() throws IOException { | |||
PDFFilterList filters = stream.getFilterList(); | |||
filters.addFilter("null"); | |||
byte[] bytes = createSampleData(); | |||
stream.setData(bytes); | |||
ByteArrayOutputStream actual = new ByteArrayOutputStream(); | |||
StreamCache streamCache = stream.encodeStream(); | |||
streamCache.outputContents(actual); | |||
assertArrayEquals(bytes, actual.toByteArray()); | |||
} | |||
@Test | |||
public void testEncodeAndWriteStream() throws IOException { | |||
PDFFilterList filters = stream.getFilterList(); | |||
filters.addFilter("null"); | |||
byte[] bytes = createSampleData(); | |||
stream.setData(bytes); | |||
ByteArrayOutputStream actual = new ByteArrayOutputStream(); | |||
PDFNumber number = new PDFNumber(); | |||
stream.encodeAndWriteStream(actual, number); | |||
assertArrayEquals(createSampleStreamData(), actual.toByteArray()); | |||
} | |||
private byte[] createSampleData() { | |||
byte[] bytes = new byte[10]; | |||
for (int i = 0; i < 10; i++) { | |||
bytes[i] = (byte) i; | |||
} | |||
return bytes; | |||
} | |||
private byte[] createSampleStreamData() throws IOException { | |||
ByteArrayOutputStream stream = new ByteArrayOutputStream(); | |||
stream.write("stream\n".getBytes("US-ASCII")); | |||
stream.write(createSampleData()); | |||
stream.write("\nendstream".getBytes("US-ASCII")); | |||
return stream.toByteArray(); | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
/* | |||
* 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 static org.junit.Assert.assertArrayEquals; | |||
import java.io.IOException; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.junit.Test; | |||
public class CompressedObjectReferenceTestCase extends ObjectReferenceTest { | |||
@Test | |||
public void testOutput() throws IOException { | |||
runTest(Arrays.asList(0, 0, 0, 0, 0, 0, 0, 0), 0); | |||
runTest(Arrays.asList(0, 0, 0, 0, 0, 0, 0, 0x1), 4); | |||
runTest(Arrays.asList(0, 0, 0, 0, 0, 0, 0, 0xf3), 16); | |||
runTest(Arrays.asList(0, 0, 0, 0, 0, 0, 0x5, 0xf7), 128); | |||
runTest(Arrays.asList(0, 0, 0, 0, 0, 0x9, 0xfb, 0xd), 0xae); | |||
runTest(Arrays.asList(0, 0, 0, 0, 0x11, 0xff, 0x15, 0xe9), 0xff); | |||
} | |||
private void runTest(List<Integer> expectedObjectStreamBytes, int index) throws IOException { | |||
int objectStreamNumber = (int) computeNumberFromBytes(expectedObjectStreamBytes); | |||
sut = new CompressedObjectReference(0, objectStreamNumber, index); | |||
byte[] expected = createExpectedOutput((byte) 2, expectedObjectStreamBytes, index); | |||
byte[] actual = getActualOutput(); | |||
assertArrayEquals(expected, actual); | |||
} | |||
} |
@@ -0,0 +1,106 @@ | |||
/* | |||
* 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 static org.junit.Assert.assertArrayEquals; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.io.UnsupportedEncodingException; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.junit.Before; | |||
import org.apache.fop.pdf.PDFDocument; | |||
import org.apache.fop.pdf.PDFInfo; | |||
import org.apache.fop.pdf.PDFPages; | |||
import org.apache.fop.pdf.PDFRoot; | |||
public abstract class CrossReferenceObjectTest { | |||
protected static final int STARTXREF = 12345; | |||
protected PDFDocument pdfDocument; | |||
protected TrailerDictionary trailerDictionary; | |||
private CrossReferenceObject crossReferenceObject; | |||
@Before | |||
public void setUp() throws UnsupportedEncodingException { | |||
pdfDocument = new PDFDocument("Apache FOP"); | |||
Map<String, List<String>> filterMap = pdfDocument.getFilterMap(); | |||
filterMap.put("default", Arrays.asList("null")); | |||
PDFRoot root = new PDFRoot(1, new PDFPages(10)); | |||
PDFInfo info = new PDFInfo(); | |||
info.setObjectNumber(2); | |||
byte[] fileID = | |||
new byte[] {0x01, 0x23, 0x45, 0x67, (byte) 0x89, (byte) 0xab, (byte) 0xcd, (byte) 0xef}; | |||
trailerDictionary = new TrailerDictionary(pdfDocument) | |||
.setRoot(root) | |||
.setInfo(info) | |||
.setFileID(fileID, fileID); | |||
} | |||
protected void runTest() throws IOException { | |||
crossReferenceObject = createCrossReferenceObject(); | |||
byte[] expected = createExpectedCrossReferenceData(); | |||
byte[] actual = createActualCrossReferenceData(); | |||
assertArrayEquals(expected, actual); | |||
} | |||
protected abstract CrossReferenceObject createCrossReferenceObject(); | |||
protected abstract byte[] createExpectedCrossReferenceData() throws IOException; | |||
protected byte[] createActualCrossReferenceData() throws IOException { | |||
ByteArrayOutputStream pdf = new ByteArrayOutputStream(); | |||
crossReferenceObject.output(pdf); | |||
pdf.close(); | |||
return pdf.toByteArray(); | |||
} | |||
protected byte[] getBytes(StringBuilder stringBuilder) { | |||
return getBytes(stringBuilder.toString()); | |||
} | |||
protected byte[] getBytes(String string) { | |||
try { | |||
return string.getBytes("US-ASCII"); | |||
} catch (UnsupportedEncodingException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
/** | |||
* Outputs the given byte array to a file with the given name. Use for debugging | |||
* purpose. | |||
*/ | |||
protected void streamToFile(byte[] bytes, String filename) throws IOException { | |||
OutputStream output = new FileOutputStream(filename); | |||
output.write(bytes); | |||
output.close(); | |||
} | |||
} |
@@ -0,0 +1,142 @@ | |||
/* | |||
* 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.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import org.junit.Test; | |||
public class CrossReferenceStreamTestCase extends CrossReferenceObjectTest { | |||
private List<Long> uncompressedObjectOffsets; | |||
private List<CompressedObjectReference> compressedObjectReferences; | |||
@Test | |||
public void testWithNoOffset() throws IOException { | |||
List<Long> emptyList = Collections.emptyList(); | |||
test(emptyList); | |||
} | |||
@Test | |||
public void testWithOffsets() throws IOException { | |||
test(new ArrayList<Long>(Arrays.asList(0L, 1L, 2L, 3L, 4L))); | |||
} | |||
@Test | |||
public void testWithBigOffsets() throws IOException { | |||
test(new ArrayList<Long>(Arrays.asList(0xffL, 0xffffL, 0xffffffffL, 0xffffffffffffffffL))); | |||
} | |||
@Test | |||
public void testWithObjectStreams1() throws IOException { | |||
List<CompressedObjectReference> compressedObjectReferences = | |||
Arrays.asList(new CompressedObjectReference(2, 1, 0)); | |||
test(Arrays.asList(0L, null), compressedObjectReferences); | |||
} | |||
@Test | |||
public void testWithObjectStreams2() throws IOException { | |||
int numIndirectObjects = 2; | |||
int numCompressedObjects = 1; | |||
List<Long> indirectObjectOffsets | |||
= new ArrayList<Long>(numIndirectObjects + numCompressedObjects); | |||
for (long i = 0; i < numIndirectObjects; i++) { | |||
indirectObjectOffsets.add(i); | |||
} | |||
List<CompressedObjectReference> compressedObjectReferences | |||
= new ArrayList<CompressedObjectReference>(); | |||
for (int index = 0; index < numCompressedObjects; index++) { | |||
indirectObjectOffsets.add(null); | |||
int obNum = numIndirectObjects + index + 1; | |||
compressedObjectReferences.add(new CompressedObjectReference(obNum, | |||
numIndirectObjects, index)); | |||
} | |||
test(indirectObjectOffsets, compressedObjectReferences); | |||
} | |||
private void test(List<Long> indirectObjectOffsets) throws IOException { | |||
List<CompressedObjectReference> compressedObjectReferences = Collections.emptyList(); | |||
test(indirectObjectOffsets, compressedObjectReferences); | |||
} | |||
private void test(List<Long> indirectObjectOffsets, | |||
List<CompressedObjectReference> compressedObjectReferences) throws IOException { | |||
this.uncompressedObjectOffsets = indirectObjectOffsets; | |||
this.compressedObjectReferences = compressedObjectReferences; | |||
runTest(); | |||
} | |||
@Override | |||
protected CrossReferenceObject createCrossReferenceObject() { | |||
return new CrossReferenceStream(pdfDocument, | |||
uncompressedObjectOffsets.size() + 1, | |||
trailerDictionary, | |||
STARTXREF, | |||
uncompressedObjectOffsets, | |||
compressedObjectReferences); | |||
} | |||
@Override | |||
protected byte[] createExpectedCrossReferenceData() throws IOException { | |||
List<ObjectReference> objectReferences | |||
= new ArrayList<ObjectReference>(uncompressedObjectOffsets.size()); | |||
for (Long offset : uncompressedObjectOffsets) { | |||
objectReferences.add(offset == null ? null : new UncompressedObjectReference(offset)); | |||
} | |||
for (CompressedObjectReference ref : compressedObjectReferences) { | |||
objectReferences.set(ref.getObjectNumber() - 1, ref); | |||
} | |||
int maxObjectNumber = objectReferences.size() + 1; | |||
ByteArrayOutputStream stream = new ByteArrayOutputStream(); | |||
StringBuilder expected = new StringBuilder(256); | |||
expected.append(maxObjectNumber + " 0 obj\n") | |||
.append("<<\n") | |||
.append(" /Root 1 0 R\n") | |||
.append(" /Info 2 0 R\n") | |||
.append(" /ID [<0123456789ABCDEF> <0123456789ABCDEF>]\n") | |||
.append(" /Type /XRef\n") | |||
.append(" /Size ").append(Integer.toString(maxObjectNumber + 1)).append('\n') | |||
.append(" /W [1 8 2]\n") | |||
.append(" /Length ").append(Integer.toString((maxObjectNumber + 1) * 11 + 1)).append('\n') | |||
.append(">>\n") | |||
.append("stream\n"); | |||
stream.write(getBytes(expected)); | |||
DataOutputStream data = new DataOutputStream(stream); | |||
data.write(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xff, (byte) 0xff}); | |||
for (ObjectReference objectReference : objectReferences) { | |||
objectReference.output(data); | |||
} | |||
data.write(1); | |||
data.writeLong(STARTXREF); | |||
data.write(0); | |||
data.write(0); | |||
data.close(); | |||
stream.write(getBytes("\nendstream\nendobj\n")); | |||
return stream.toByteArray(); | |||
} | |||
} |
@@ -0,0 +1,80 @@ | |||
/* | |||
* 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.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import org.junit.Test; | |||
public class CrossReferenceTableTestCase extends CrossReferenceObjectTest { | |||
private List<Long> offsets; | |||
@Test | |||
public void testWithNoOffset() throws IOException { | |||
List<Long> emptyList = Collections.emptyList(); | |||
runTest(emptyList); | |||
} | |||
@Test | |||
public void testWithOffsets() throws IOException { | |||
runTest(Arrays.asList(0L, 1L, 2L, 3L, 4L)); | |||
} | |||
@Test | |||
public void testWithBigOffsets() throws IOException { | |||
runTest(Arrays.asList(0xffL, 0xffffL, 0x7fffffffL)); | |||
} | |||
private void runTest(List<Long> offsets) throws IOException { | |||
this.offsets = offsets; | |||
runTest(); | |||
} | |||
@Override | |||
protected CrossReferenceObject createCrossReferenceObject() { | |||
return new CrossReferenceTable(trailerDictionary, STARTXREF, offsets); | |||
} | |||
@Override | |||
protected byte[] createExpectedCrossReferenceData() throws IOException { | |||
StringBuilder expected = new StringBuilder(256); | |||
expected.append("xref\n0 ") | |||
.append(offsets.size() + 1) | |||
.append("\n0000000000 65535 f \n"); | |||
for (Long objectReference : offsets) { | |||
final String padding = "0000000000"; | |||
String s = String.valueOf(objectReference).toString(); | |||
String loc = padding.substring(s.length()) + s; | |||
expected.append(loc).append(" 00000 n \n"); | |||
} | |||
expected.append("trailer\n<<\n") | |||
.append(" /Root 1 0 R\n") | |||
.append(" /Info 2 0 R\n") | |||
.append(" /ID [<0123456789ABCDEF> <0123456789ABCDEF>]\n") | |||
.append(" /Size ").append(Integer.toString(offsets.size() + 1)).append('\n') | |||
.append(">>\n"); | |||
return getBytes(expected); | |||
} | |||
} |
@@ -0,0 +1,62 @@ | |||
/* | |||
* 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.util.List; | |||
abstract class ObjectReferenceTest { | |||
protected ObjectReference sut; | |||
protected long computeNumberFromBytes(List<Integer> expectedOffsetBytes) { | |||
assert expectedOffsetBytes.size() <= 8; | |||
long offset = 0; | |||
for (int b : expectedOffsetBytes) { | |||
offset = offset << 8 | b; | |||
} | |||
return offset; | |||
} | |||
protected byte[] createExpectedOutput(byte field1, List<Integer> field2, int field3) { | |||
assert field2.size() == 8; | |||
assert (field3 & 0xffff) == field3; | |||
byte[] expected = new byte[11]; | |||
int index = 0; | |||
expected[index++] = field1; | |||
for (Integer b : field2) { | |||
expected[index++] = b.byteValue(); | |||
} | |||
expected[index++] = (byte) ((field3 & 0xff00) >> 8); | |||
expected[index++] = (byte) (field3 & 0xff); | |||
return expected; | |||
} | |||
protected byte[] getActualOutput() throws IOException { | |||
ByteArrayOutputStream out = new ByteArrayOutputStream(); | |||
DataOutputStream dataOutputStream = new DataOutputStream(out); | |||
sut.output(dataOutputStream); | |||
dataOutputStream.close(); | |||
return out.toByteArray(); | |||
} | |||
} |
@@ -0,0 +1,90 @@ | |||
/* | |||
* 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 static org.junit.Assert.assertArrayEquals; | |||
import java.io.IOException; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.junit.Test; | |||
public class UncompressedObjectReferenceTestCase extends ObjectReferenceTest { | |||
@Test | |||
public void test1ByteOffsets() throws IOException { | |||
run1ByteOffsetTest(0x0); | |||
run1ByteOffsetTest(0xf); | |||
run1ByteOffsetTest(0x10); | |||
run1ByteOffsetTest(0xff); | |||
} | |||
private void run1ByteOffsetTest(int offset) throws IOException { | |||
runIntegerOffsetTest(Arrays.asList(0, 0, 0, offset)); | |||
} | |||
@Test | |||
public void test2ByteOffsets() throws IOException { | |||
runIntegerOffsetTest(Arrays.asList(0, 0, 1, 0xff)); | |||
runIntegerOffsetTest(Arrays.asList(0, 0, 0xa0, 0xff)); | |||
} | |||
@Test | |||
public void test3ByteOffsets() throws IOException { | |||
runIntegerOffsetTest(Arrays.asList(0, 2, 0x12, 0x34)); | |||
runIntegerOffsetTest(Arrays.asList(0, 0xee, 0x56, 0x78)); | |||
} | |||
@Test | |||
public void test4ByteOffsets() throws IOException { | |||
runIntegerOffsetTest(Arrays.asList(0x6, 0x12, 0x34, 0x56)); | |||
runIntegerOffsetTest(Arrays.asList(0xf1, 0x9a, 0xbc, 0xde)); | |||
} | |||
@Test | |||
public void test5ByteOffsets() throws IOException { | |||
runTest(Arrays.asList(0, 0, 0, 0x7, 0x78, 0x9a, 0xbc, 0xde)); | |||
runTest(Arrays.asList(0, 0, 0, 0xbf, 0xf0, 0, 0x1, 0x2)); | |||
} | |||
@Test | |||
public void test8ByteOffsets() throws IOException { | |||
runTest(Arrays.asList(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8)); | |||
runTest(Arrays.asList(0xf9, 0xe8, 0xd7, 0xc6, 0xb5, 0xa4, 0x93, 0x82)); | |||
} | |||
private void runIntegerOffsetTest(List<Integer> expectedOffsetBytes) throws IOException { | |||
List<Integer> expectedLongOffset = new ArrayList<Integer>(8); | |||
expectedLongOffset.addAll(Arrays.asList(0, 0, 0, 0)); | |||
expectedLongOffset.addAll(expectedOffsetBytes); | |||
runTest(expectedLongOffset); | |||
} | |||
private void runTest(List<Integer> expectedOffsetBytes) throws IOException { | |||
long offset = computeNumberFromBytes(expectedOffsetBytes); | |||
sut = new UncompressedObjectReference(offset); | |||
byte[] expected = createExpectedOutput((byte) 1, expectedOffsetBytes, (byte) 0); | |||
byte[] actual = getActualOutput(); | |||
assertArrayEquals(expected, actual); | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<fop version="1.0"> | |||
<accessibility>true</accessibility> | |||
<source-resolution>144</source-resolution> | |||
<use-cache>false</use-cache> | |||
<font-base>../../resources/fonts/ttf/</font-base> | |||
<renderers> | |||
<renderer mime="application/pdf"> | |||
<version>1.5</version> | |||
<filterList> | |||
<value>null</value> | |||
</filterList> | |||
<filterList type="image"> | |||
<value>flate</value> | |||
<value>ascii-85</value> | |||
</filterList> | |||
<fonts> | |||
<font embed-url="DejaVuLGCSerif.ttf"> | |||
<font-triplet name="DejaVu" style="normal" weight="normal"/> | |||
</font> | |||
</fonts> | |||
</renderer> | |||
</renderers> | |||
</fop> |
@@ -0,0 +1,207 @@ | |||
<?xml version="1.0" standalone="no"?> | |||
<!-- | |||
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$ --> | |||
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" | |||
xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" language="en" country="GB"> | |||
<fo:layout-master-set> | |||
<fo:simple-page-master master-name="page" | |||
page-height="220pt" page-width="320pt" margin="10pt"> | |||
<fo:region-body column-count="2" margin-top="15pt"/> | |||
<fo:region-before extent="12pt"/> | |||
</fo:simple-page-master> | |||
</fo:layout-master-set> | |||
<fo:page-sequence master-reference="page"> | |||
<fo:static-content flow-name="xsl-region-before"> | |||
<fo:block font-size="8pt" text-align-last="justify">This is the page header<fo:leader/>Page | |||
<fo:page-number/></fo:block> | |||
</fo:static-content> | |||
<fo:static-content flow-name="xsl-footnote-separator"> | |||
<fo:block><fo:leader leader-length="100pt" leader-pattern="rule"/></fo:block> | |||
</fo:static-content> | |||
<fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify"> | |||
<fo:block>(There’s another page sequence <fo:wrapper color="blue"><fo:basic-link | |||
internal-destination="second">below</fo:basic-link></fo:wrapper>.)</fo:block> | |||
<fo:block font-family="sans-serif" font-weight="bold" space-before="1em" space-after="0.2em" | |||
role="H1"><fo:block>About Apache FOP</fo:block></fo:block> | |||
<fo:block>It is a print formatter driven by XSL formatting objects (XSL-FO) and an output | |||
independent formatter<fo:footnote><fo:inline baseline-shift="super" | |||
font-size="70%">1</fo:inline><fo:footnote-body><fo:block>See the <fo:wrapper | |||
color="blue"><fo:basic-link | |||
external-destination="http://xmlgraphics.apache.org/fop/">FOP | |||
website</fo:basic-link></fo:wrapper> for more | |||
information</fo:block></fo:footnote-body></fo:footnote>. FOP has a nice logo: | |||
<fo:external-graphic src="../../resources/images/fop-logo-color-24bit.png" | |||
inline-progression-dimension.maximum="100%" content-width="scale-to-fit" | |||
fox:alt-text="FOP Logo"/></fo:block> | |||
<fo:table space-before="10pt" space-after="10pt" width="100%" table-layout="fixed"> | |||
<fo:table-header> | |||
<fo:table-row> | |||
<fo:table-cell border="2pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Header 1.1</fo:block> | |||
</fo:table-cell> | |||
<fo:table-cell border="2pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Header 1.2</fo:block> | |||
</fo:table-cell> | |||
</fo:table-row> | |||
</fo:table-header> | |||
<fo:table-body> | |||
<fo:table-row> | |||
<fo:table-cell border="1pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Cell 1.1</fo:block> | |||
</fo:table-cell> | |||
<fo:table-cell border="1pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Cell 1.2</fo:block> | |||
</fo:table-cell> | |||
</fo:table-row> | |||
<fo:table-row> | |||
<fo:table-cell border="1pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Cell 2.1</fo:block> | |||
</fo:table-cell> | |||
<fo:table-cell border="1pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Cell 2.2</fo:block> | |||
</fo:table-cell> | |||
</fo:table-row> | |||
</fo:table-body> | |||
</fo:table> | |||
<fo:block>Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL | |||
formatting objects (XSL-FO) and an output independent formatter. It is a Java application | |||
that reads a formatting object (FO) tree and renders the resulting pages to a specified | |||
output.</fo:block> | |||
<fo:block span="all" border-top="1pt solid black" border-bottom="1pt solid black" | |||
padding-before="2pt" padding-after="2pt">This fo:block element spans all the columns of the | |||
document. This is intended to test the abilities of the text-to-speech program.</fo:block> | |||
<fo:block>And now we are back to normal content flowing in two columns. Let’s start a numbered | |||
list:</fo:block> | |||
<fo:list-block provisional-distance-between-starts="15pt" provisional-label-separation="0mm" | |||
keep-with-previous="auto"> | |||
<fo:list-item keep-with-previous="always"> | |||
<fo:list-item-label end-indent="label-end()"> | |||
<fo:block>1.</fo:block> | |||
</fo:list-item-label> | |||
<fo:list-item-body start-indent="body-start()"> | |||
<fo:block> | |||
<fo:block>Line 1 of item 1</fo:block> | |||
<fo:block>Line 2 of item 1</fo:block> | |||
<fo:block>Line 3 of item 1</fo:block> | |||
</fo:block> | |||
</fo:list-item-body> | |||
</fo:list-item> | |||
<fo:list-item keep-with-previous="always"> | |||
<fo:list-item-label end-indent="label-end()"> | |||
<fo:block>2.</fo:block> | |||
</fo:list-item-label> | |||
<fo:list-item-body start-indent="body-start()"> | |||
<fo:block> | |||
<fo:block>Line 1 of item 2</fo:block> | |||
<fo:block>Line 2 of item 2</fo:block> | |||
<fo:block>Line 3 of item 2</fo:block> | |||
</fo:block> | |||
</fo:list-item-body> | |||
</fo:list-item> | |||
</fo:list-block> | |||
<fo:block>And now we are going to see how a second page sequence is handled.</fo:block> | |||
</fo:flow> | |||
</fo:page-sequence> | |||
<fo:page-sequence master-reference="page"> | |||
<fo:static-content flow-name="xsl-region-before"> | |||
<fo:block font-size="8pt" text-align-last="justify">This is the page header<fo:leader/>Page | |||
<fo:page-number/></fo:block> | |||
</fo:static-content> | |||
<fo:static-content flow-name="xsl-footnote-separator"> | |||
<fo:block><fo:leader leader-length="100pt" leader-pattern="rule"/></fo:block> | |||
</fo:static-content> | |||
<fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify"> | |||
<fo:block id="second">Apache FOP (Formatting Objects Processor) is a print formatter driven by | |||
XSL formatting objects (XSL-FO) and an output independent formatter<fo:footnote><fo:inline | |||
baseline-shift="super" font-size="70%">1</fo:inline><fo:footnote-body><fo:block>See the | |||
<fo:wrapper color="blue"><fo:basic-link | |||
external-destination="http://xmlgraphics.apache.org/fop/">FOP | |||
website</fo:basic-link></fo:wrapper> for more | |||
information</fo:block></fo:footnote-body></fo:footnote>. It is a Java application that | |||
reads a formatting object (FO) tree and renders the resulting pages to a specified | |||
output.</fo:block> | |||
<fo:table space-before="10pt" space-after="10pt" width="100%" table-layout="fixed"> | |||
<fo:table-header> | |||
<fo:table-row> | |||
<fo:table-cell border="2pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Header 1.1</fo:block> | |||
</fo:table-cell> | |||
<fo:table-cell border="2pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Header 1.2</fo:block> | |||
</fo:table-cell> | |||
</fo:table-row> | |||
</fo:table-header> | |||
<fo:table-body> | |||
<fo:table-row> | |||
<fo:table-cell border="1pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Cell 1.1</fo:block> | |||
</fo:table-cell> | |||
<fo:table-cell border="1pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Cell 1.2</fo:block> | |||
</fo:table-cell> | |||
</fo:table-row> | |||
<fo:table-row> | |||
<fo:table-cell border="1pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Cell 2.1</fo:block> | |||
</fo:table-cell> | |||
<fo:table-cell border="1pt solid black" padding="2pt 2pt 0"> | |||
<fo:block>Cell 2.2</fo:block> | |||
</fo:table-cell> | |||
</fo:table-row> | |||
</fo:table-body> | |||
</fo:table> | |||
<fo:block language="fr" country="FR">Apache FOP (Formatting Objects Processor) est une | |||
application de mise en page de documents respectant le standard XSL-FO. À partir d’un | |||
document au format XSL-FO, cette application écrite en Java effectue une mise en page et | |||
renvoie un document prêt pour impression.</fo:block> | |||
<fo:block span="all" border-top="1pt solid black" border-bottom="1pt solid black" | |||
padding-before="2pt" padding-after="2pt">This fo:block element spans all the columns of the | |||
document. This is intended to test the abilities of the text-to-speech program.</fo:block> | |||
<fo:block>And now we are back to normal content flowing in two columns. Let’s start a numbered | |||
list:</fo:block> | |||
<fo:list-block provisional-distance-between-starts="15pt" provisional-label-separation="0mm" | |||
keep-with-previous="auto"> | |||
<fo:list-item keep-with-previous="always"> | |||
<fo:list-item-label end-indent="label-end()"> | |||
<fo:block>1.</fo:block> | |||
</fo:list-item-label> | |||
<fo:list-item-body start-indent="body-start()"> | |||
<fo:block> | |||
<fo:block>Line 1 of item 1</fo:block> | |||
<fo:block>Line 2 of item 1</fo:block> | |||
<fo:block>Line 3 of item 1</fo:block> | |||
</fo:block> | |||
</fo:list-item-body> | |||
</fo:list-item> | |||
<fo:list-item keep-with-previous="always"> | |||
<fo:list-item-label end-indent="label-end()"> | |||
<fo:block>2.</fo:block> | |||
</fo:list-item-label> | |||
<fo:list-item-body start-indent="body-start()"> | |||
<fo:block> | |||
<fo:block>Line 1 of item 2</fo:block> | |||
<fo:block>Line 2 of item 2</fo:block> | |||
<fo:block>Line 3 of item 2</fo:block> | |||
</fo:block> | |||
</fo:list-item-body> | |||
</fo:list-item> | |||
</fo:list-block> | |||
<fo:block>The end of the document has now been reached.</fo:block> | |||
</fo:flow> | |||
</fo:page-sequence> | |||
</fo:root> |
@@ -20,7 +20,7 @@ | |||
<fo:layout-master-set> | |||
<fo:simple-page-master master-name="page" | |||
page-height="220pt" page-width="320pt" margin="10pt"> | |||
<fo:region-body background-image="../resources/images/bgimg72dpi.jpg"/> | |||
<fo:region-body background-image="../../resources/images/bgimg72dpi.jpg"/> | |||
</fo:simple-page-master> | |||
</fo:layout-master-set> | |||
<fo:page-sequence master-reference="page"> |
@@ -20,7 +20,7 @@ | |||
<fo:layout-master-set> | |||
<fo:simple-page-master master-name="page" | |||
page-height="220pt" page-width="320pt" margin="10pt"> | |||
<fo:region-body background-image="../resources/images/bgimg72dpi.jpg" | |||
<fo:region-body background-image="../../resources/images/bgimg72dpi.jpg" | |||
background-repeat="no-repeat" background-position-horizontal="50%" | |||
background-position-vertical="50%"/> | |||
</fo:simple-page-master> |
@@ -20,7 +20,7 @@ | |||
<fo:layout-master-set> | |||
<fo:simple-page-master master-name="page" | |||
page-height="220pt" page-width="320pt" margin="10pt"> | |||
<fo:region-body background-image="../resources/images/bgimg72dpi.png"/> | |||
<fo:region-body background-image="../../resources/images/bgimg72dpi.png"/> | |||
</fo:simple-page-master> | |||
</fo:layout-master-set> | |||
<fo:page-sequence master-reference="page"> |
@@ -20,7 +20,7 @@ | |||
<fo:layout-master-set> | |||
<fo:simple-page-master master-name="page" | |||
page-height="220pt" page-width="320pt" margin="10pt"> | |||
<fo:region-body background-image="../resources/images/fop-logo-color-24bit.png" | |||
<fo:region-body background-image="../../resources/images/fop-logo-color-24bit.png" | |||
background-repeat="no-repeat" background-position-horizontal="50%" | |||
background-position-vertical="50%"/> | |||
</fo:simple-page-master> |
@@ -20,7 +20,7 @@ | |||
<fo:layout-master-set> | |||
<fo:simple-page-master master-name="page" | |||
page-height="220pt" page-width="320pt" margin="10pt"> | |||
<fo:region-body background-image="../resources/images/rgb-circles.svg"/> | |||
<fo:region-body background-image="../../resources/images/rgb-circles.svg"/> | |||
</fo:simple-page-master> | |||
</fo:layout-master-set> | |||
<fo:page-sequence master-reference="page"> |
@@ -20,7 +20,7 @@ | |||
<fo:layout-master-set> | |||
<fo:simple-page-master master-name="page" | |||
page-height="220pt" page-width="320pt" margin="10pt"> | |||
<fo:region-body background-image="../resources/images/rgb-circles.svg" | |||
<fo:region-body background-image="../../resources/images/rgb-circles.svg" | |||
background-repeat="no-repeat" background-position-horizontal="50%" | |||
background-position-vertical="50%"/> | |||
</fo:simple-page-master> |
@@ -45,7 +45,7 @@ | |||
external-destination="http://xmlgraphics.apache.org/fop/">FOP | |||
website</fo:basic-link></fo:wrapper> for more | |||
information</fo:block></fo:footnote-body></fo:footnote>. FOP has a nice logo: | |||
<fo:external-graphic src="../resources/images/fop-logo-color-24bit.png" | |||
<fo:external-graphic src="../../resources/images/fop-logo-color-24bit.png" | |||
inline-progression-dimension.maximum="100%" content-width="scale-to-fit" | |||
fox:alt-text="FOP Logo"/></fo:block> | |||
<fo:table space-before="10pt" space-after="10pt" width="100%" table-layout="fixed"> |
@@ -3,7 +3,7 @@ | |||
<accessibility>true</accessibility> | |||
<source-resolution>144</source-resolution> | |||
<use-cache>false</use-cache> | |||
<font-base>../resources/fonts/</font-base> | |||
<font-base>../../resources/fonts/ttf/</font-base> | |||
<renderers> | |||
<renderer mime="application/pdf"> | |||
<filterList> |
@@ -27,7 +27,7 @@ | |||
<fo:page-sequence master-reference="page"> | |||
<fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify"> | |||
<fo:block>This document contains an image in the JPEG format: <fo:external-graphic | |||
src="../resources/images/cmyk.jpg" | |||
src="../../resources/images/cmyk.jpg" | |||
inline-progression-dimension.maximum="100%" content-width="scale-to-fit" | |||
fox:alt-text="CMYK colours"/>. Here is the end of the text.</fo:block> | |||
</fo:flow> |
@@ -27,7 +27,7 @@ | |||
<fo:page-sequence master-reference="page"> | |||
<fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify"> | |||
<fo:block>This document contains an image in the PNG format: <fo:external-graphic | |||
src="../resources/images/fop-logo-color-24bit.png" | |||
src="../../resources/images/fop-logo-color-24bit.png" | |||
inline-progression-dimension.maximum="100%" content-width="scale-to-fit" | |||
fox:alt-text="FOP Logo"/>. Here is the end of the text.</fo:block> | |||
</fo:flow> |
@@ -27,7 +27,7 @@ | |||
<fo:page-sequence master-reference="page"> | |||
<fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify"> | |||
<fo:block>This document contains an image in the SVG format: <fo:external-graphic | |||
src="../resources/images/circles.svg" | |||
src="../../resources/images/circles.svg" | |||
inline-progression-dimension.maximum="75pt" content-width="scale-to-fit" | |||
fox:alt-text="Nice circles"/>. And here is the same image as an instream-foreign-object: | |||
<fo:instream-foreign-object inline-progression-dimension.maximum="75pt" |
@@ -27,7 +27,7 @@ | |||
<fo:page-sequence master-reference="page"> | |||
<fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify"> | |||
<fo:block>This document contains an image in the WMF format: <fo:external-graphic | |||
src="../resources/images/testChart.wmf" | |||
src="../../resources/images/testChart.wmf" | |||
inline-progression-dimension.maximum="100%" content-width="scale-to-fit" | |||
fox:alt-text="Metafile Companion Test Chart"/> Here is the end of the text.</fo:block> | |||
</fo:flow> |
@@ -32,7 +32,7 @@ | |||
<fo:block space-before="10pt">This is a text followed by a leader with | |||
leader-pattern="use-content", the content being images:<fo:leader | |||
leader-pattern="use-content"><fo:external-graphic | |||
src="../resources/images/list-item.png"/></fo:leader>1</fo:block> | |||
src="../../resources/images/list-item.png"/></fo:leader>1</fo:block> | |||
</fo:flow> | |||
</fo:page-sequence> | |||
</fo:root> |