/*
 * 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: AbstractPDFStream.java 1758773 2016-09-01 13:02:29Z ssteiner $ */

package org.apache.fop.pdf;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Set;

import org.apache.commons.io.output.CountingOutputStream;

import org.apache.fop.util.CloseBlockerOutputStream;

This is an abstract base class for PDF streams.
/** * This is an abstract base class for PDF streams. */
public abstract class AbstractPDFStream extends PDFObject { private final PDFDictionary dictionary;
The filters that should be applied
/** The filters that should be applied */
private PDFFilterList filters; private boolean encodeOnTheFly; private PDFNumber refLength = new PDFNumber(); 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; dictionary.setParent(this); this.encodeOnTheFly = encodeOnTheFly; } protected final PDFDictionary getDictionary() { return dictionary; } public Object get(String key) { return dictionary.get(key); }
Puts the given object in the dictionary associated to this stream.
Params:
  • key – the key in the dictionary
  • value – the value to store
/** * 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 void put(String key, Object value) { dictionary.put(key, value); }
Sets up the default filters for this stream if they haven't been set from outside.
/** * Sets up the default filters for this stream if they haven't been set * from outside. */
protected void setupFilterList() { if (multipleFiltersAllowed() && !getFilterList().isInitialized()) { getFilterList().addDefaultFilters( getDocumentSafely().getFilterMap(), getDefaultFilterName()); } prepareImplicitFilters(); getDocument().applyEncryption(this); }
Returns the name of a suitable filter for this PDF object.
See Also:
Returns:the default filter
/** * 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.
Returns:the filter list
/** * 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.
Throws:
Returns:an estimated size (0 if no hint can be given)
/** * 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.
Params:
  • out – OutputStream to write to
Throws:
/** * 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
Params:
  • encodedStream – already encoded/filtered stream to write
  • out – OutputStream to write to
Throws:
Returns:int number of bytes written
/** * 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("\nstream\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.
Throws:
Returns:the encoded stream
/** * 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.
Params:
  • out – OutputStream to write to
  • refLength – PDFNumber object to receive the stream length
Throws:
Returns:number of bytes written (header and trailer included)
/** * 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("\nstream\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(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}
/** * Overload the base object method so we don't have to copy * byte arrays around so much * {@inheritDoc} */
@Override public int output(OutputStream stream) throws IOException { setupFilterList(); CountingOutputStream cout = new CountingOutputStream(stream); StringBuilder textBuffer = new StringBuilder(64); StreamCache encodedStream = null; final Object lengthEntry; if (encodeOnTheFly) { if (!refLength.hasObjectNumber()) { registerChildren(); } lengthEntry = refLength; } else { encodedStream = encodeStream(); lengthEntry = encodedStream.getSize(); } populateStreamDict(lengthEntry); dictionary.writeDictionary(cout, textBuffer); //Send encoded stream to target OutputStream PDFDocument.flushTextBuffer(textBuffer, cout); if (encodedStream == null) { encodeAndWriteStream(cout, refLength); } else { outputStreamData(encodedStream, cout); encodedStream.clear(); //Encoded stream can now be discarded } PDFDocument.flushTextBuffer(textBuffer, cout); return cout.getCount(); } @Override public void setDocument(PDFDocument doc) { dictionary.setDocument(doc); super.setDocument(doc); }
Populates the dictionary with all necessary entries for the stream. Override this method if you need additional entries.
Params:
  • lengthEntry – value for the /Length entry
/** * 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(dictionary); } }
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.
/** * 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 }
Whether multiple filters can be applied.
Returns:true if multiple filters allowed
/** * Whether multiple filters can be applied. * @return true if multiple filters allowed */
protected boolean multipleFiltersAllowed() { return true; } @Override public void getChildren(Set<PDFObject> children) { dictionary.getChildren(children); if (encodeOnTheFly) { children.add(refLength); } } public void registerChildren() { if (encodeOnTheFly) { getDocument().registerObject(refLength); } } }