001: /**
002: * Copyright (c) 2004-2005, www.pdfbox.org
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain the above copyright notice,
009: * this list of conditions and the following disclaimer.
010: * 2. Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: * 3. Neither the name of pdfbox; nor the names of its
014: * contributors may be used to endorse or promote products derived from this
015: * software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
021: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: *
028: * http://www.pdfbox.org
029: *
030: */package org.pdfbox.pdmodel.common;
031:
032: import java.io.ByteArrayInputStream;
033: import java.io.ByteArrayOutputStream;
034: import java.io.IOException;
035: import java.io.InputStream;
036: import java.io.OutputStream;
037:
038: import java.util.ArrayList;
039: import java.util.Iterator;
040: import java.util.List;
041: import java.util.Map;
042:
043: import org.pdfbox.cos.COSArray;
044: import org.pdfbox.cos.COSBase;
045: import org.pdfbox.cos.COSDictionary;
046: import org.pdfbox.cos.COSName;
047: import org.pdfbox.cos.COSStream;
048:
049: import org.pdfbox.filter.Filter;
050: import org.pdfbox.filter.FilterManager;
051:
052: import org.pdfbox.pdmodel.PDDocument;
053:
054: import org.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
055:
056: /**
057: * A PDStream represents a stream in a PDF document. Streams are tied to a single
058: * PDF document.
059: *
060: * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
061: * @version $Revision: 1.16 $
062: */
063: public class PDStream implements COSObjectable {
064: private COSStream stream;
065:
066: /**
067: * This will create a new PDStream object.
068: */
069: protected PDStream() {
070: //should only be called by PDMemoryStream
071: }
072:
073: /**
074: * This will create a new PDStream object.
075: *
076: * @param document The document that the stream will be part of.
077: */
078: public PDStream(PDDocument document) {
079: stream = new COSStream(document.getDocument().getScratchFile());
080: }
081:
082: /**
083: * Constructor.
084: *
085: * @param str The stream parameter.
086: */
087: public PDStream(COSStream str) {
088: stream = str;
089: }
090:
091: /**
092: * Constructor. Reads all data from the input stream and embeds it into the
093: * document, this will close the InputStream.
094: *
095: * @param doc The document that will hold the stream.
096: * @param str The stream parameter.
097: * @throws IOException If there is an error creating the stream in the document.
098: */
099: public PDStream(PDDocument doc, InputStream str) throws IOException {
100: this (doc, str, false);
101: }
102:
103: /**
104: * Constructor. Reads all data from the input stream and embeds it into the
105: * document, this will close the InputStream.
106: *
107: * @param doc The document that will hold the stream.
108: * @param str The stream parameter.
109: * @param filtered True if the stream already has a filter applied.
110: * @throws IOException If there is an error creating the stream in the document.
111: */
112: public PDStream(PDDocument doc, InputStream str, boolean filtered)
113: throws IOException {
114: OutputStream output = null;
115: try {
116: stream = new COSStream(doc.getDocument().getScratchFile());
117: if (filtered) {
118: output = stream.createFilteredStream();
119: } else {
120: output = stream.createUnfilteredStream();
121: }
122: byte[] buffer = new byte[1024];
123: int amountRead = -1;
124: while ((amountRead = str.read(buffer)) != -1) {
125: output.write(buffer, 0, amountRead);
126: }
127: } finally {
128: if (output != null) {
129: output.close();
130: }
131: if (str != null) {
132: str.close();
133: }
134: }
135: }
136:
137: /**
138: * If there are not compression filters on the current stream then this
139: * will add a compression filter, flate compression for example.
140: */
141: public void addCompression() {
142: List filters = getFilters();
143: if (filters == null) {
144: filters = new ArrayList();
145: filters.add(COSName.FLATE_DECODE);
146: setFilters(filters);
147: }
148: }
149:
150: /**
151: * Create a pd stream from either a regular COSStream on a COSArray of cos streams.
152: * @param base Either a COSStream or COSArray.
153: * @return A PDStream or null if base is null.
154: * @throws IOException If there is an error creating the PDStream.
155: */
156: public static PDStream createFromCOS(COSBase base)
157: throws IOException {
158: PDStream retval = null;
159: if (base instanceof COSStream) {
160: retval = new PDStream((COSStream) base);
161: } else if (base instanceof COSArray) {
162: retval = new PDStream(new COSStreamArray((COSArray) base));
163: } else {
164: if (base != null) {
165: throw new IOException("Contents are unknown type:"
166: + base.getClass().getName());
167: }
168: }
169: return retval;
170: }
171:
172: /**
173: * Convert this standard java object to a COS object.
174: *
175: * @return The cos object that matches this Java object.
176: */
177: public COSBase getCOSObject() {
178: return stream;
179: }
180:
181: /**
182: * This will get a stream that can be written to.
183: *
184: * @return An output stream to write data to.
185: *
186: * @throws IOException If an IO error occurs during writing.
187: */
188: public OutputStream createOutputStream() throws IOException {
189: return stream.createUnfilteredStream();
190: }
191:
192: /**
193: * This will get a stream that can be read from.
194: *
195: * @return An input stream that can be read from.
196: *
197: * @throws IOException If an IO error occurs during reading.
198: */
199: public InputStream createInputStream() throws IOException {
200: return stream.getUnfilteredStream();
201: }
202:
203: /**
204: * This will get a stream with some filters applied but not others. This is useful
205: * when doing images, ie filters = [flate,dct], we want to remove flate but leave dct
206: *
207: * @param stopFilters A list of filters to stop decoding at.
208: * @return A stream with decoded data.
209: * @throws IOException If there is an error processing the stream.
210: */
211: public InputStream getPartiallyFilteredStream(List stopFilters)
212: throws IOException {
213: FilterManager manager = stream.getFilterManager();
214: InputStream is = stream.getFilteredStream();
215: ByteArrayOutputStream os = new ByteArrayOutputStream();
216: List filters = getFilters();
217: Iterator iter = filters.iterator();
218: String nextFilter = null;
219: boolean done = false;
220: while (iter.hasNext() && !done) {
221: os.reset();
222: nextFilter = (String) iter.next();
223: if (stopFilters.contains(nextFilter)) {
224: done = true;
225: } else {
226: Filter filter = manager.getFilter(COSName
227: .getPDFName(nextFilter));
228: filter.decode(is, os, stream);
229: is = new ByteArrayInputStream(os.toByteArray());
230: }
231: }
232: return is;
233: }
234:
235: /**
236: * Get the cos stream associated with this object.
237: *
238: * @return The cos object that matches this Java object.
239: */
240: public COSStream getStream() {
241: return stream;
242: }
243:
244: /**
245: * This will get the length of the filtered/compressed stream. This is readonly in the
246: * PD Model and will be managed by this class.
247: *
248: * @return The length of the filtered stream.
249: */
250: public int getLength() {
251: return stream.getInt("Length", 0);
252: }
253:
254: /**
255: * This will get the list of filters that are associated with this stream. Or
256: * null if there are none.
257: * @return A list of all encoding filters to apply to this stream.
258: */
259: public List getFilters() {
260: List retval = null;
261: COSBase filters = stream.getFilters();
262: if (filters instanceof COSName) {
263: COSName name = (COSName) filters;
264: retval = new COSArrayList(name.getName(), name, stream,
265: "Filter");
266: } else if (filters instanceof COSArray) {
267: retval = COSArrayList
268: .convertCOSNameCOSArrayToList((COSArray) filters);
269: }
270: return retval;
271: }
272:
273: /**
274: * This will set the filters that are part of this stream.
275: *
276: * @param filters The filters that are part of this stream.
277: */
278: public void setFilters(List filters) {
279: COSBase obj = COSArrayList
280: .convertStringListToCOSNameCOSArray(filters);
281: stream.setItem("Filter", obj);
282: }
283:
284: /**
285: * Get the list of decode parameters. Each entry in the list will refer to
286: * an entry in the filters list.
287: *
288: * @return The list of decode parameters.
289: *
290: * @throws IOException if there is an error retrieving the parameters.
291: */
292: public List getDecodeParams() throws IOException {
293: List retval = null;
294:
295: COSBase dp = stream.getDictionaryObject("DecodeParms");
296: if (dp == null) {
297: //See PDF Ref 1.5 implementation note 7, the DP is sometimes used instead.
298: dp = stream.getDictionaryObject("DP");
299: }
300: if (dp instanceof COSDictionary) {
301: Map map = COSDictionaryMap
302: .convertBasicTypesToMap((COSDictionary) dp);
303: retval = new COSArrayList(map, dp, stream, "DecodeParams");
304: } else if (dp instanceof COSArray) {
305: COSArray array = (COSArray) dp;
306: List actuals = new ArrayList();
307: for (int i = 0; i < array.size(); i++) {
308: actuals.add(COSDictionaryMap
309: .convertBasicTypesToMap((COSDictionary) array
310: .getObject(i)));
311: }
312: retval = new COSArrayList(actuals, array);
313: }
314:
315: return retval;
316: }
317:
318: /**
319: * This will set the list of decode params.
320: *
321: * @param decodeParams The list of decode params.
322: */
323: public void setDecodeParams(List decodeParams) {
324: stream.setItem("DecodeParams", COSArrayList
325: .converterToCOSArray(decodeParams));
326: }
327:
328: /**
329: * This will get the file specification for this stream. This is only
330: * required for external files.
331: *
332: * @return The file specification.
333: *
334: * @throws IOException If there is an error creating the file spec.
335: */
336: public PDFileSpecification getFile() throws IOException {
337: COSBase f = stream.getDictionaryObject("F");
338: PDFileSpecification retval = PDFileSpecification.createFS(f);
339: return retval;
340: }
341:
342: /**
343: * Set the file specification.
344: * @param f The file specification.
345: */
346: public void setFile(PDFileSpecification f) {
347: stream.setItem("F", f);
348: }
349:
350: /**
351: * This will get the list of filters that are associated with this stream. Or
352: * null if there are none.
353: * @return A list of all encoding filters to apply to this stream.
354: */
355: public List getFileFilters() {
356: List retval = null;
357: COSBase filters = stream.getDictionaryObject("FFilter");
358: if (filters instanceof COSName) {
359: COSName name = (COSName) filters;
360: retval = new COSArrayList(name.getName(), name, stream,
361: "FFilter");
362: } else if (filters instanceof COSArray) {
363: retval = COSArrayList
364: .convertCOSNameCOSArrayToList((COSArray) filters);
365: }
366: return retval;
367: }
368:
369: /**
370: * This will set the filters that are part of this stream.
371: *
372: * @param filters The filters that are part of this stream.
373: */
374: public void setFileFilters(List filters) {
375: COSBase obj = COSArrayList
376: .convertStringListToCOSNameCOSArray(filters);
377: stream.setItem("FFilter", obj);
378: }
379:
380: /**
381: * Get the list of decode parameters. Each entry in the list will refer to
382: * an entry in the filters list.
383: *
384: * @return The list of decode parameters.
385: *
386: * @throws IOException if there is an error retrieving the parameters.
387: */
388: public List getFileDecodeParams() throws IOException {
389: List retval = null;
390:
391: COSBase dp = stream.getDictionaryObject("FDecodeParms");
392: if (dp instanceof COSDictionary) {
393: Map map = COSDictionaryMap
394: .convertBasicTypesToMap((COSDictionary) dp);
395: retval = new COSArrayList(map, dp, stream, "FDecodeParams");
396: } else if (dp instanceof COSArray) {
397: COSArray array = (COSArray) dp;
398: List actuals = new ArrayList();
399: for (int i = 0; i < array.size(); i++) {
400: actuals.add(COSDictionaryMap
401: .convertBasicTypesToMap((COSDictionary) array
402: .getObject(i)));
403: }
404: retval = new COSArrayList(actuals, array);
405: }
406:
407: return retval;
408: }
409:
410: /**
411: * This will set the list of decode params.
412: *
413: * @param decodeParams The list of decode params.
414: */
415: public void setFileDecodeParams(List decodeParams) {
416: stream.setItem("FDecodeParams", COSArrayList
417: .converterToCOSArray(decodeParams));
418: }
419:
420: /**
421: * This will copy the stream into a byte array.
422: *
423: * @return The byte array of the filteredStream
424: * @throws IOException When getFilteredStream did not work
425: */
426: public byte[] getByteArray() throws IOException {
427: ByteArrayOutputStream output = new ByteArrayOutputStream();
428: byte[] buf = new byte[1024];
429: InputStream is = null;
430: try {
431: is = createInputStream();
432: int amountRead = -1;
433: while ((amountRead = is.read(buf)) != -1) {
434: output.write(buf, 0, amountRead);
435: }
436: } finally {
437: if (is != null) {
438: is.close();
439: }
440: }
441: return output.toByteArray();
442: }
443:
444: /**
445: * A convenience method to get this stream as a string. Uses
446: * the default system encoding.
447: *
448: * @return a String representation of this (input) stream, with the
449: * platform default encoding.
450: *
451: * @throws IOException if there is an error while converting the stream
452: * to a string.
453: */
454: public String getInputStreamAsString() throws IOException {
455: byte[] bStream = getByteArray();
456: return new String(bStream);
457: }
458:
459: /**
460: * Get the metadata that is part of the document catalog. This will
461: * return null if there is no meta data for this object.
462: *
463: * @return The metadata for this object.
464: */
465: public PDMetadata getMetadata() {
466: PDMetadata retval = null;
467: COSStream mdStream = (COSStream) stream
468: .getDictionaryObject("Metadata");
469: if (mdStream != null) {
470: retval = new PDMetadata(mdStream);
471: }
472: return retval;
473: }
474:
475: /**
476: * Set the metadata for this object. This can be null.
477: *
478: * @param meta The meta data for this object.
479: */
480: public void setMetadata(PDMetadata meta) {
481: stream.setItem("Metadata", meta);
482: }
483: }
|