001: /*
002: * Copyright (c) 2007, intarsys consulting GmbH
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * - Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * - 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: *
014: * - Neither the name of intarsys nor the names of its contributors may be used
015: * to endorse or promote products derived from this software without specific
016: * prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
028: * POSSIBILITY OF SUCH DAMAGE.
029: */
030: package de.intarsys.pdf.content;
031:
032: import java.io.IOException;
033: import java.util.Iterator;
034:
035: import de.intarsys.pdf.content.common.CSCreator;
036: import de.intarsys.pdf.cos.COSArray;
037: import de.intarsys.pdf.cos.COSName;
038: import de.intarsys.pdf.cos.COSObject;
039: import de.intarsys.pdf.cos.COSRuntimeException;
040: import de.intarsys.pdf.cos.COSStream;
041: import de.intarsys.pdf.parser.COSLoadException;
042: import de.intarsys.pdf.parser.CSContentParser;
043: import de.intarsys.pdf.writer.COSWriter;
044: import de.intarsys.tools.randomaccess.RandomAccessByteArray;
045:
046: /**
047: * Represents the tokenized content of a PDF rendering program which is called a
048: * "content stream".
049: *
050: * <p>
051: * A PDF rendering program is a sequence of operations, each build by a list of
052: * operands followed by the operator.
053: * </p>
054: *
055: * <p>
056: * Any visual appearance in a PDF document is build on a content stream. For
057: * example a PDPage hosts a content stream (or a list of content streams) to
058: * define its appearance.
059: * </p>
060: *
061: * <p>
062: * A content stream has no access to indirect objects, this means object
063: * references are not valid operands for operations. Complex objects are used
064: * within a content stream via an indirection defined in a resource dictionary.
065: * </p>
066: * <p>
067: * The {@link CSContent} itself is decoupled from its source of creation, this
068: * means after manipulating the content stream you have to apply it where you
069: * want to (for example adding it to a page).
070: * <p>
071: * You can work on the content stream directly, adding and removing operations.
072: * A more elegant way to manipulate this is using a {@link CSCreator},
073: * providing high level methods for the different content stream operations.
074: */
075: public class CSContent {
076: /**
077: * the tokenized elements of the content stream. The elements of this list
078: * are COSStreamOperation objects.
079: */
080: private CSOperation[] operations = new CSOperation[100];
081:
082: private int size = 0;
083:
084: /**
085: * Create a new PDCContentStream.
086: *
087: * @param resourceDict
088: * The dictionary defining the external references of the content
089: * stream.
090: */
091: protected CSContent() {
092: super ();
093: }
094:
095: /**
096: * Create {@link CSContent} from a byte array containing a PDF content
097: * stream.
098: *
099: * @param data
100: * The bytes defining the PDF content stream.
101: *
102: * @return The new {@link CSContent}
103: */
104: static public CSContent createFromBytes(byte[] data) {
105: try {
106: CSContentParser parser = new CSContentParser();
107: return parser.parseStream(data);
108: } catch (IOException e) {
109: throw new COSRuntimeException(e);
110: } catch (COSLoadException e) {
111: throw new COSRuntimeException(e);
112: }
113: }
114:
115: /**
116: * Create {@link CSContent} from a {@link COSStream} containing a PDF
117: * content stream.
118: *
119: * @param stream
120: * The stream defining containing the PDF content stream.
121: *
122: * @return The new {@link CSContent}
123: */
124: static public CSContent createFromCos(COSStream stream) {
125: return createFromBytes(stream.getDecodedBytes());
126: }
127:
128: /**
129: * Create {@link CSContent} from an array of {@link COSStream}, together
130: * defining a PDF content stream.
131: *
132: * @param streams
133: * An array of {@link COSStream} objects containing each a chunk
134: * of the content stream.
135: * @return The new {@link CSContent}.
136: */
137: static public CSContent createFromCos(COSArray streams) {
138: RandomAccessByteArray data = new RandomAccessByteArray(null);
139: try {
140: for (Iterator it = streams.iterator(); it.hasNext();) {
141: COSStream stream = ((COSObject) it.next()).asStream();
142: if (stream != null) {
143: data.write(stream.getDecodedBytes());
144: // force at least a single space between streams
145: data.write(32);
146: }
147: }
148: CSContentParser parser = new CSContentParser();
149: data.seek(0);
150: return parser.parseStream(data);
151: } catch (IOException e) {
152: throw new COSRuntimeException(e);
153: } catch (COSLoadException e) {
154: throw new COSRuntimeException(e);
155: }
156: }
157:
158: /**
159: * Create a new {@link CSContent}.
160: *
161: * @return The new {@link CSContent}.
162: */
163: static public CSContent createNew() {
164: CSContent result = new CSContent();
165: return result;
166: }
167:
168: /**
169: * remove last operation from the rendering program.
170: *
171: * @return the last operation, or null of no operations left
172: */
173: public CSOperation getLastOperation() {
174: if (size == 0) {
175: return null;
176: }
177: return operations[size - 1];
178: }
179:
180: /**
181: * Set the "marked content" portion in the content stream of this. Marked
182: * content is enclosed between "BMC" and "EMC", the begin operation has an
183: * operand identifying the type of marked content.
184: *
185: * <p>
186: * The portion between the marks is replaced with <code>content</code>.If
187: * no marks are found, the new content is appended as a marked content
188: * section.
189: * </p>
190: *
191: * @param mark
192: * The type of marked content we search
193: * @param content
194: * The content we want to use.
195: */
196: public void setMarkedContent(COSName mark, byte[] content) {
197: int i = 0;
198: for (; i < size; i++) {
199: CSOperation operation = operations[i];
200: if (operation.isOpBeginMarkedContent(mark)) {
201: i++;
202: break;
203: }
204: }
205: if (i < size) {
206: // found
207: addOperation(i, new CSLiteral(content));
208: int nesting = 0;
209: for (i++; i < size;) {
210: CSOperation operation = operations[i];
211: if (operation.isOpBeginMarkedContent(null)) {
212: nesting++;
213: }
214: if (operation.isOpEndMarkedContent()) {
215: if (nesting == 0) {
216: break;
217: }
218: nesting--;
219: }
220: removeOperation(i);
221: }
222: } else {
223: CSOperation operation;
224: operation = new CSOperation(CSOperators.CSO_BMC);
225: operation.addOperand(CSOperation.OPERAND_Tx);
226: addOperation(operation);
227: addOperation(new CSLiteral(content));
228: operation = new CSOperation(CSOperators.CSO_EMC);
229: addOperation(operation);
230: }
231: }
232:
233: /**
234: * Add "content" at the end of the "marked content" portion in the content
235: * stream of this.
236: *
237: * @param mark
238: * The type of marked content we search
239: * @param content
240: * The content we want to use.
241: */
242: public void addMarkedContent(COSName mark, byte[] content) {
243: int i = 0;
244: for (; i < size; i++) {
245: CSOperation operation = operations[i];
246: if (operation.isOpBeginMarkedContent(mark)) {
247: i++;
248: break;
249: }
250: }
251: if (i < size) {
252: // found
253: int nesting = 0;
254: for (i++; i < size; i++) {
255: CSOperation operation = operations[i];
256: if (operation.isOpBeginMarkedContent(null)) {
257: nesting++;
258: }
259: if (operation.isOpEndMarkedContent()) {
260: if (nesting == 0) {
261: addOperation(i, new CSLiteral(content));
262: break;
263: }
264: nesting--;
265: }
266: }
267: } else {
268: CSOperation operation;
269: operation = new CSOperation(CSOperators.CSO_BMC);
270: operation.addOperand(CSOperation.OPERAND_Tx);
271: addOperation(operation);
272: addOperation(new CSLiteral(content));
273: operation = new CSOperation(CSOperators.CSO_EMC);
274: addOperation(operation);
275: }
276: }
277:
278: /**
279: * Add another operation to the rendering program.
280: *
281: * @param op
282: * The new operation to append.
283: */
284: public void addOperation(CSOperation op) {
285: if (size >= operations.length) {
286: CSOperation[] newOperations = new CSOperation[size * 2];
287: System.arraycopy(operations, 0, newOperations, 0, size);
288: operations = newOperations;
289: }
290: operations[size++] = op;
291: }
292:
293: /**
294: * Add another operation to the rendering program.
295: *
296: * @param op
297: * The new operation to append.
298: */
299: public void addOperation(int index, CSOperation op) {
300: if (size >= operations.length) {
301: CSOperation[] newOperations = new CSOperation[size * 2];
302: System.arraycopy(operations, 0, newOperations, 0, size);
303: operations = newOperations;
304: }
305: System.arraycopy(operations, index, operations, index + 1, size
306: - index);
307: size++;
308: operations[index] = op;
309: }
310:
311: public void removeOperation(int index) {
312: System.arraycopy(operations, index + 1, operations, index, size
313: - index - 1);
314: size--;
315: }
316:
317: public CSOperation[] getOperations() {
318: CSOperation[] copy = new CSOperation[size];
319: System.arraycopy(operations, 0, copy, 0, size());
320: return copy;
321: }
322:
323: public CSOperation getOperation(int index) {
324: return operations[index];
325: }
326:
327: public COSStream createStream() {
328: COSStream result = COSStream.create(null);
329: result.setDecodedBytes(toByteArray());
330: return result;
331: }
332:
333: /**
334: * remove last operation from the rendering program.
335: */
336: public void removeLastOperation() {
337: removeOperation(size - 1);
338: }
339:
340: /**
341: * The number of operations in the content stream.
342: *
343: * @return The number of operations in the content stream.
344: */
345: public int size() {
346: return size;
347: }
348:
349: /**
350: * Create the byte representation from the list of operations.
351: *
352: * @return The byte representation from the list of operations.
353: */
354: public byte[] toByteArray() {
355: RandomAccessByteArray randomAccess = new RandomAccessByteArray(
356: null);
357: COSWriter writer = new COSWriter(randomAccess, null);
358: try {
359: writer.writeContentStream(this );
360: } catch (IOException e) {
361: // this should not happen
362: }
363: return randomAccess.toByteArray();
364: }
365:
366: public String toString() {
367: return new String(toByteArray());
368: }
369: }
|