blob: 4107c7e7af081d67c3679c53a2f7f60248d47e2f [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.pdf;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import org.apache.commons.io.output.CountingOutputStream;
import org.apache.fop.util.CloseBlockerOutputStream;
/**
* This is an abstract base class for PDF streams.
*/
public abstract class AbstractPDFStream extends PDFDictionary {
/** The filters that should be applied */
private PDFFilterList filters;
/**
* Constructor for AbstractPDFStream.
*/
public AbstractPDFStream() {
super();
}
/**
* Sets up the default filters for this stream if they haven't been set
* from outside.
*/
protected void setupFilterList() {
if (!getFilterList().isInitialized()) {
getFilterList().addDefaultFilters(
getDocumentSafely().getFilterMap(),
getDefaultFilterName());
}
prepareImplicitFilters();
getDocument().applyEncryption(this);
}
/**
* Returns the name of a suitable filter for this PDF object.
*
* @return the default filter
* @see PDFFilterList
*/
protected String getDefaultFilterName() {
return PDFFilterList.DEFAULT_FILTER;
}
/**
* Returns the associated filter list.
* @return the filter list
*/
public PDFFilterList getFilterList() {
if (this.filters == null) {
if (getDocument() == null) {
this.filters = new PDFFilterList();
} else {
this.filters = new PDFFilterList(getDocument().isEncryptionActive());
}
boolean hasFilterEntries = (get("Filter") != null);
if (hasFilterEntries) {
this.filters.setDisableAllFilters(true);
}
}
return this.filters;
}
/**
* Returns a value that hints at the size of the encoded stream. This is
* used to optimize buffer allocation so fewer buffer reallocations are
* necessary.
* @return an estimated size (0 if no hint can be given)
* @throws IOException in case of an I/O problem
*/
protected abstract int getSizeHint() throws IOException;
/**
* Sends the raw stream data to the target OutputStream.
* @param out OutputStream to write to
* @throws IOException In case of an I/O problem
*/
protected abstract void outputRawStreamData(OutputStream out)
throws IOException;
/**
* Output just the stream data enclosed by stream/endstream markers
* @param encodedStream already encoded/filtered stream to write
* @param out OutputStream to write to
* @return int number of bytes written
* @throws IOException in case of an I/O problem
*/
protected int outputStreamData(StreamCache encodedStream, OutputStream out) throws IOException {
int length = 0;
byte[] p = encode("stream\n");
out.write(p);
length += p.length;
encodedStream.outputContents(out);
length += encodedStream.getSize();
p = encode("\nendstream");
out.write(p);
length += p.length;
return length;
}
/**
* Encodes the raw data stream for output to a PDF file.
* @return the encoded stream
* @throws IOException in case of an I/O problem
*/
protected StreamCache encodeStream() throws IOException {
//Allocate a temporary buffer to find out the size of the encoded stream
final StreamCache encodedStream = StreamCacheFactory.getInstance().
createStreamCache(getSizeHint());
OutputStream filteredOutput
= getFilterList().applyFilters(encodedStream.getOutputStream());
outputRawStreamData(filteredOutput);
filteredOutput.flush();
filteredOutput.close();
return encodedStream;
}
/**
* Encodes and writes a stream directly to an OutputStream. The length of
* the stream, in this case, is set on a PDFNumber object that has to be
* prepared beforehand.
* @param out OutputStream to write to
* @param refLength PDFNumber object to receive the stream length
* @return number of bytes written (header and trailer included)
* @throws IOException in case of an I/O problem
*/
protected int encodeAndWriteStream(OutputStream out, PDFNumber refLength)
throws IOException {
int bytesWritten = 0;
//Stream header
byte[] buf = encode("stream\n");
out.write(buf);
bytesWritten += buf.length;
//Stream contents
CloseBlockerOutputStream cbout = new CloseBlockerOutputStream(out);
CountingOutputStream cout = new CountingOutputStream(cbout);
OutputStream filteredOutput = getFilterList().applyFilters(cout);
outputRawStreamData(filteredOutput);
filteredOutput.close();
refLength.setNumber(new Integer(cout.getCount()));
bytesWritten += cout.getCount();
//Stream trailer
buf = encode("\nendstream");
out.write(buf);
bytesWritten += buf.length;
return bytesWritten;
}
/**
* Overload the base object method so we don't have to copy
* byte arrays around so much
* {@inheritDoc}
*/
protected int output(OutputStream stream) throws IOException {
setupFilterList();
CountingOutputStream cout = new CountingOutputStream(stream);
Writer writer = PDFDocument.getWriterFor(cout);
writer.write(getObjectID());
//int length = 0;
StreamCache encodedStream = null;
PDFNumber refLength = null;
final Object lengthEntry;
if (getDocument().isEncodingOnTheFly()) {
refLength = new PDFNumber();
getDocumentSafely().registerObject(refLength);
lengthEntry = refLength;
} else {
encodedStream = encodeStream();
lengthEntry = new Integer(encodedStream.getSize() + 1);
}
populateStreamDict(lengthEntry);
writeDictionary(cout, writer);
//Send encoded stream to target OutputStream
writer.flush();
if (encodedStream == null) {
encodeAndWriteStream(cout, refLength);
} else {
outputStreamData(encodedStream, cout);
encodedStream.clear(); //Encoded stream can now be discarded
}
writer.write("\nendobj\n");
writer.flush();
return cout.getCount();
}
/**
* Populates the dictionary with all necessary entries for the stream.
* Override this method if you need additional entries.
* @param lengthEntry value for the /Length entry
*/
protected void populateStreamDict(Object lengthEntry) {
put("Length", lengthEntry);
if (!getFilterList().isDisableAllFilters()) {
getFilterList().putFilterDictEntries(this);
}
}
/**
* Prepares implicit filters (such as the DCTFilter for JPEG images). You
* must make sure that the appropriate filters are in the filter list at
* the right places.
*/
protected void prepareImplicitFilters() {
//nop: No default implicit filters
}
}