blob: 37d9bc537a706619edc379cb03de453a0324efdc [file] [log] [blame]
/*
* 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.render.pdf.pdfbox;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSBoolean;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSFloat;
import org.apache.pdfbox.cos.COSInteger;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSNull;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.pdmodel.common.COSObjectable;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.fop.pdf.DCTFilter;
import org.apache.fop.pdf.PDFArray;
import org.apache.fop.pdf.PDFDictionary;
import org.apache.fop.pdf.PDFDocument;
import org.apache.fop.pdf.PDFName;
import org.apache.fop.pdf.PDFNumber;
import org.apache.fop.pdf.PDFObject;
import org.apache.fop.pdf.PDFStream;
public class PDFCloner {
private PDFBoxAdapter adapter;
PDFCloner(PDFBoxAdapter adapter) {
this.adapter = adapter;
}
protected Object cloneForNewDocument(Object base) throws IOException {
return cloneForNewDocument(base, base);
}
protected Object cloneForNewDocument(Object base, Object keyBase) throws IOException {
return cloneForNewDocument(base, keyBase, Collections.EMPTY_LIST);
}
protected Object cloneForNewDocument(Object base, Object keyBase, Collection exclude) throws IOException {
if (base == null) {
return null;
}
Object cached = adapter.getCachedClone(keyBase);
if (cached != null) {
// we are done, it has already been converted.
return cached;
} else if (base instanceof COSObjectable && !(base instanceof COSBase)) {
Object o = ((COSObjectable)base).getCOSObject();
Object retval = cloneForNewDocument(o, o, exclude);
return cacheClonedObject(keyBase, retval);
} else if (base instanceof COSObject) {
return readCOSObject((COSObject) base, exclude);
} else if (base instanceof COSArray) {
PDFArray newArray = new PDFArray();
cacheClonedObject(keyBase, newArray);
COSArray array = (COSArray)base;
for (int i = 0; i < array.size(); i++) {
newArray.add(cloneForNewDocument(array.get(i), array.get(i), exclude));
}
return newArray;
// } else if (base instanceof COSStreamArray) {
// COSStreamArray array = (COSStreamArray)base;
// PDFArray newArray = new PDFArray();
// cacheClonedObject(keyBase, newArray);
// for (int i = 0, c = array.getStreamCount(); i < c; i++) {
// newArray.add(cloneForNewDocument(array.get(i)));
// }
// return newArray;
} else if (base instanceof COSStream) {
return readCOSStream((COSStream) base, keyBase);
} else if (base instanceof COSDictionary) {
return readCOSDictionary((COSDictionary) base, keyBase, exclude);
} else if (base instanceof COSName) {
PDFName newName = new PDFName(((COSName)base).getName());
return cacheClonedObject(keyBase, newName);
} else if (base instanceof COSInteger) {
PDFNumber number = new PDFNumber();
number.setNumber(((COSInteger)base).longValue());
return cacheClonedObject(keyBase, number);
} else if (base instanceof COSFloat) {
PDFNumber number = new PDFNumber();
number.setNumber(((COSFloat)base).floatValue());
return cacheClonedObject(keyBase, number);
} else if (base instanceof COSBoolean) {
//TODO Do we need a PDFBoolean here?
Boolean retval = ((COSBoolean)base).getValueAsObject();
if (keyBase instanceof COSObject) {
return cacheClonedObject(keyBase, new PDFBoolean(retval));
} else {
return cacheClonedObject(keyBase, retval);
}
} else if (base instanceof COSString) {
return readCOSString((COSString) base, keyBase);
} else if (base instanceof COSNull) {
return cacheClonedObject(keyBase, null);
} else {
throw new UnsupportedOperationException("NYI: " + base.getClass().getName());
}
}
protected Object readCOSObject(COSObject object, Collection exclude) throws IOException {
Object obj = cloneForNewDocument(object.getObject(), object, exclude);
if (obj instanceof PDFObject) {
PDFObject pdfobj = (PDFObject)obj;
//pdfDoc.registerObject(pdfobj);
if (!pdfobj.hasObjectNumber()) {
throw new IllegalStateException("PDF object was not registered!");
}
}
return obj;
}
private PDFDictionary readCOSDictionary(COSDictionary dic, Object keyBase, Collection exclude) throws IOException {
PDFDictionary newDict = new PDFDictionary();
cacheClonedObject(keyBase, newDict);
for (Map.Entry<COSName, COSBase> e : dic.entrySet()) {
if (!exclude.contains(e.getKey())) {
String name = e.getKey().getName();
if (adapter.uniqueName != null) {
name = adapter.uniqueName.getName(e.getKey());
}
newDict.put(name, cloneForNewDocument(e.getValue(), e.getValue(), exclude));
}
}
return newDict;
}
private Object readCOSString(COSString string, Object keyBase) throws IOException {
//retval = ((COSString)base).getString(); //this is unsafe for binary content
byte[] bytes = string.getBytes();
//Be on the safe side and use the byte array to avoid encoding problems
//as PDFBox doesn't indicate whether the string is just
//a string (PDF 1.4, 3.2.3) or a text string (PDF 1.4, 3.8.1).
if (keyBase instanceof COSObject) {
return cacheClonedObject(keyBase, new PDFString(bytes));
} else {
if (PDFString.isUSASCII(bytes)) {
return cacheClonedObject(keyBase, new String(string.getBytes(), PDFDocument.ENCODING));
} else {
return cacheClonedObject(keyBase, bytes);
}
}
}
private Object readCOSStream(COSStream originalStream, Object keyBase) throws IOException {
InputStream in;
Set filter;
PDFStream stream = new PDFStream();
if (adapter.pdfDoc.isEncryptionActive() && originalStream.getItem(COSName.FILTER) == COSName.DCT_DECODE) {
stream.getFilterList().addFilter(new DCTFilter());
in = originalStream.createRawInputStream();
filter = Collections.EMPTY_SET;
} else if (adapter.pdfDoc.isEncryptionActive()
|| (originalStream.containsKey(COSName.DECODE_PARMS) && !originalStream.containsKey(COSName.FILTER))) {
in = originalStream.createInputStream();
filter = adapter.FILTER_FILTER;
} else {
//transfer encoded data (don't reencode)
in = originalStream.createRawInputStream();
filter = Collections.EMPTY_SET;
}
OutputStream out = stream.getBufferOutputStream();
if ((originalStream.getItem(COSName.TYPE) == COSName.PATTERN
|| originalStream.getItem(COSName.SUBTYPE) == COSName.FORM) && adapter.uniqueName != null) {
PDFWriter writer = new PDFWriter(adapter.uniqueName, adapter.currentMCID);
try {
String newStream = writer.writeText(new PDStream(originalStream));
if (writer.keyUsed) {
filter = adapter.FILTER_FILTER;
out.write(newStream.getBytes(PDFDocument.ENCODING));
out.close();
in = null;
}
} catch (IOException e) {
//ignore
}
}
if (in != null) {
IOUtils.copyLarge(in, out);
}
adapter.transferDict(originalStream, stream, filter);
return cacheClonedObject(keyBase, stream);
}
protected Object cacheClonedObject(Object base, Object cloned) throws IOException {
Object key = PDFBoxAdapterUtil.getBaseKey(base);
if (key == null) {
return cloned;
}
PDFObject pdfobj = (PDFObject) cloned;
if (pdfobj != null && !pdfobj.hasObjectNumber() && !(base instanceof COSDictionary)) {
adapter.pdfDoc.registerObject(pdfobj);
}
adapter.clonedVersion.put(key, cloned);
if (key instanceof Integer) {
adapter.objectCache.put(key, cloned);
}
return cloned;
}
}