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.writer;
031:
032: import java.io.IOException;
033: import java.text.NumberFormat;
034: import java.util.ArrayList;
035: import java.util.Collection;
036: import java.util.Iterator;
037: import java.util.List;
038: import java.util.Locale;
039: import java.util.Map;
040:
041: import de.intarsys.pdf.content.CSContent;
042: import de.intarsys.pdf.content.CSOperation;
043: import de.intarsys.pdf.cos.COSArray;
044: import de.intarsys.pdf.cos.COSBoolean;
045: import de.intarsys.pdf.cos.COSDictionary;
046: import de.intarsys.pdf.cos.COSDocumentElement;
047: import de.intarsys.pdf.cos.COSFixed;
048: import de.intarsys.pdf.cos.COSIndirectObject;
049: import de.intarsys.pdf.cos.COSInteger;
050: import de.intarsys.pdf.cos.COSName;
051: import de.intarsys.pdf.cos.COSNull;
052: import de.intarsys.pdf.cos.COSObject;
053: import de.intarsys.pdf.cos.COSObjectKey;
054: import de.intarsys.pdf.cos.COSObjectProxy;
055: import de.intarsys.pdf.cos.COSStream;
056: import de.intarsys.pdf.cos.COSString;
057: import de.intarsys.pdf.cos.COSVisitorException;
058: import de.intarsys.pdf.cos.ICOSObjectVisitor;
059: import de.intarsys.pdf.cos.ICOSProxyVisitor;
060: import de.intarsys.pdf.crypt.COSSecurityException;
061: import de.intarsys.pdf.crypt.ISystemSecurityHandler;
062: import de.intarsys.pdf.parser.PDFParser;
063: import de.intarsys.pdf.st.AbstractXRefWriter;
064: import de.intarsys.pdf.st.STDocument;
065: import de.intarsys.pdf.st.STXRefEntryOccupied;
066: import de.intarsys.pdf.st.STXRefSection;
067: import de.intarsys.tools.hex.HexTools;
068: import de.intarsys.tools.randomaccess.IRandomAccess;
069: import de.intarsys.tools.randomaccess.RandomAccessByteArray;
070: import de.intarsys.tools.string.StringTools;
071:
072: /**
073: * A writer for PDF related data structures.
074: */
075: public class COSWriter implements ICOSObjectVisitor, ICOSProxyVisitor {
076: public static final byte[] ARRAY_CLOSE = "]".getBytes(); //$NON-NLS-1$
077:
078: public static final byte[] ARRAY_OPEN = "[".getBytes(); //$NON-NLS-1$
079:
080: public static final byte[] COMMENT = "%".getBytes(); //$NON-NLS-1$
081:
082: /*
083: * todo 1 @mit break up streams longer than allowed line (255 chars, pp67)
084: */
085:
086: /** To be used when 2 byte sequence is enforced. */
087: public static final byte[] CRLF = "\r\n".getBytes(); //$NON-NLS-1$
088:
089: public static final byte[] DICT_CLOSE = ">>".getBytes(); //$NON-NLS-1$
090:
091: public static final byte[] DICT_OPEN = "<<".getBytes(); //$NON-NLS-1$
092:
093: /** a fast lookup for serializing digits */
094: protected static final char[] DIGITS = new char[] { '0', '1', '2',
095: '3', '4', '5', '6', '7', '8', '9', '9' };
096:
097: public static final byte[] ENDOBJ = "endobj".getBytes(); //$NON-NLS-1$
098:
099: public static final byte[] ENDSTREAM = "endstream".getBytes(); //$NON-NLS-1$
100:
101: public static final byte[] EOF = "%%EOF".getBytes(); //$NON-NLS-1$
102:
103: /** standard line separator on this platform */
104: public static final byte[] EOL = System.getProperty(
105: "line.separator") //$NON-NLS-1$
106: .getBytes();
107:
108: public static final byte[] FALSE = "false".getBytes(); //$NON-NLS-1$
109:
110: public static final byte[] GARBAGE = "öäüß".getBytes(); //$NON-NLS-1$
111:
112: /** Line feed character. */
113: public static final byte[] LF = "\n".getBytes(); //$NON-NLS-1$
114:
115: public static final byte[] LITERAL_ESCAPED_BS = "\\b".getBytes(); //$NON-NLS-1$
116:
117: public static final byte[] LITERAL_ESCAPED_CR = "\\r".getBytes(); //$NON-NLS-1$
118:
119: public static final byte[] LITERAL_ESCAPED_FF = "\\f".getBytes(); //$NON-NLS-1$
120:
121: public static final byte[] LITERAL_ESCAPED_HT = "\\t".getBytes(); //$NON-NLS-1$
122:
123: public static final byte[] LITERAL_ESCAPED_LF = "\\n".getBytes(); //$NON-NLS-1$
124:
125: public static final byte[] NAME_ESCAPE = "#".getBytes(); //$NON-NLS-1$
126:
127: public static final byte[] NAME_PREFIX = "/".getBytes(); //$NON-NLS-1$
128:
129: public static final byte[] NULL = "null".getBytes(); //$NON-NLS-1$
130:
131: public static final byte[] OBJ = "obj".getBytes(); //$NON-NLS-1$
132:
133: public static final byte[] PDF_ESCAPE = "\\".getBytes(); //$NON-NLS-1$
134:
135: public static final byte[] REFERENCE = "R".getBytes(); //$NON-NLS-1$
136:
137: public static final byte[] SPACE = " ".getBytes(); //$NON-NLS-1$
138:
139: public static final byte[] STREAM = "stream".getBytes(); //$NON-NLS-1$
140:
141: public static final byte[] STRING_CLOSE = ")".getBytes(); //$NON-NLS-1$
142:
143: public static final byte[] STRING_HEX_CLOSE = ">".getBytes(); //$NON-NLS-1$
144:
145: public static final byte[] STRING_HEX_OPEN = "<".getBytes(); //$NON-NLS-1$
146:
147: public static final byte[] STRING_OPEN = "(".getBytes(); //$NON-NLS-1$
148:
149: public static final byte[] TRAILER = "trailer".getBytes(); //$NON-NLS-1$
150:
151: public static final byte[] TRUE = "true".getBytes(); //$NON-NLS-1$
152:
153: /*
154: * (non-Javadoc)
155: *
156: * @see de.intarsys.pdf.cos.COSObject#writeOn(java.io.OutputStream)
157: */
158: public static void basicWriteFixed(IRandomAccess randomAccess,
159: float value, int precision) throws IOException {
160: NumberFormat format = NumberFormat
161: .getIntegerInstance(Locale.US);
162: format.setMaximumFractionDigits(precision);
163: format.setGroupingUsed(false);
164: randomAccess.write(StringTools
165: .toByteArray(format.format(value)));
166: }
167:
168: /*
169: * (non-Javadoc)
170: *
171: * @see de.intarsys.pdf.cos.COSObject#writeOn(java.io.OutputStream)
172: */
173: public static void basicWriteInteger(IRandomAccess randomAccess,
174: int value) throws IOException {
175: randomAccess.write(StringTools.toByteArray(Integer
176: .toString(value)));
177: }
178:
179: /**
180: * create the byte stream for the representation of a name
181: *
182: * @param randomAccess
183: * the randomAccessData to write to
184: * @param name
185: * the names byte stream
186: *
187: * @throws IOException
188: */
189: public static void basicWriteName(IRandomAccess randomAccess,
190: byte[] name) throws IOException {
191: randomAccess.write(NAME_PREFIX);
192: for (int i = 0; i < name.length; i++) {
193: int current = name[i] & 0xff; // convert to unsigned byte
194: if ((current <= 32) || (current >= 127)
195: || PDFParser.isDelimiter(current)
196: || (current == 35)) {
197: randomAccess.write(NAME_ESCAPE);
198: randomAccess.write(HexTools.ByteToHex[current]);
199: } else {
200: randomAccess.write(current);
201: }
202: }
203: }
204:
205: /**
206: * create a hex encoded byte stream representation of string
207: *
208: * @param randomAccess
209: * the randomAccessData to write to
210: * @param string
211: * the string to write
212: *
213: * @throws IOException
214: */
215: public static void basicWriteStringHex(IRandomAccess randomAccess,
216: byte[] string) throws IOException {
217: randomAccess.write(STRING_HEX_OPEN);
218: for (int i = 0; i < string.length; i++) {
219: randomAccess.write(HexTools.ByteToHex[string[i] & 0xFF]);
220: }
221: randomAccess.write(STRING_HEX_CLOSE);
222: }
223:
224: /**
225: * create the literal byte stream representation of string
226: *
227: * @param randomAccess
228: * the randomAccessData to write to
229: * @param string
230: * the string to write
231: *
232: * @throws IOException
233: */
234: public static void basicWriteStringLiteral(
235: IRandomAccess randomAccess, byte[] string)
236: throws IOException {
237: randomAccess.write(STRING_OPEN);
238: for (int i = 0; i < string.length; i++) {
239: int b = string[i];
240: if (b == '\n') {
241: randomAccess.write(LITERAL_ESCAPED_LF);
242: } else if (b == '\r') {
243: randomAccess.write(LITERAL_ESCAPED_CR);
244: } else if (b == '\t') {
245: randomAccess.write(LITERAL_ESCAPED_HT);
246: } else if (b == '\f') {
247: randomAccess.write(LITERAL_ESCAPED_FF);
248: } else if (b == '\b') {
249: randomAccess.write(LITERAL_ESCAPED_BS);
250: } else if ((b == '(') || (b == ')') || (b == '\\')) {
251: randomAccess.write(PDF_ESCAPE);
252: randomAccess.write(b);
253: } else {
254: randomAccess.write(b);
255: }
256: }
257: randomAccess.write(STRING_CLOSE);
258: }
259:
260: /**
261: * Create a byte array representation from a COSObject.
262: *
263: * @param object
264: * The object to be serialized.
265: *
266: * @return A byte array representation from a COSObject.
267: */
268: public static final byte[] toByteArray(COSObject object) {
269: RandomAccessByteArray tempRandom = new RandomAccessByteArray(
270: null);
271: COSWriter writer = new COSWriter(tempRandom, null);
272: try {
273: writer.writeObject(object);
274: } catch (IOException e) {
275: return "*** not printable ***".getBytes(); //$NON-NLS-1$
276: }
277: return tempRandom.toByteArray();
278: }
279:
280: private COSIndirectObject currentObject;
281:
282: private ISystemSecurityHandler securityHandler;
283:
284: private boolean incremental = true;
285:
286: /** flag to prevent generating two newlines in sequence */
287: private boolean onNewLine = false;
288:
289: private List proxies = new ArrayList();
290:
291: /**
292: * The IRandomAccess we write to.
293: */
294: private IRandomAccess randomAccess;
295:
296: public COSWriter(IRandomAccess randomAccess,
297: ISystemSecurityHandler securityHandler) {
298: this .securityHandler = securityHandler;
299: this .randomAccess = randomAccess;
300: }
301:
302: protected void basicWriteDocument(STDocument doc)
303: throws IOException {
304: if (doc.isDirty()) {
305: doc.updateModificationDate();
306: doc.getTrailer().updateFileID();
307: }
308: if (doc.isNew()) {
309: // force complete write on new document
310: setIncremental(false);
311: }
312: synchronized (doc.getAccessLock()) {
313: if (!isIncremental()) {
314: doc.garbageCollect();
315: getRandomAccess().setLength(0);
316: writeHeader(doc);
317: } else {
318: doc.incrementalGarbageCollect();
319: }
320: Collection changes = doc.getChanges();
321: if (changes.size() > 0) {
322: seekToEnd();
323: STXRefSection xrefSection = doc.createNewXRefSection();
324: for (Iterator it = changes.iterator(); it.hasNext();) {
325: COSIndirectObject object = (COSIndirectObject) it
326: .next();
327: writeEntry(xrefSection, object);
328: object.setDirty(false);
329: }
330: seekToEnd();
331: writeXRef(xrefSection);
332: writeEOF();
333: doc.setXRefSection(xrefSection);
334: doc.setDirty(false);
335: }
336: }
337: }
338:
339: protected void close(STDocument doc) throws IOException {
340: for (Iterator it = getProxies().iterator(); it.hasNext();) {
341: COSObjectProxy proxy = (COSObjectProxy) it.next();
342: proxy.ended(this );
343: }
344: getRandomAccess().flush();
345: // todo 1 change dirty
346: }
347:
348: protected ISystemSecurityHandler getSecurityHandler() {
349: return securityHandler;
350: }
351:
352: protected byte[] encryptString(byte[] bytes) throws IOException {
353: if ((getSecurityHandler() != null)
354: && (getCurrentObject() != null)
355: && getCurrentObject().isEncryptOnWrite()) {
356: try {
357: return getSecurityHandler().encryptString(
358: getCurrentObject().getKey(), bytes);
359: } catch (COSSecurityException e) {
360: IOException ioe = new IOException(
361: "error encrypting data"); //$NON-NLS-1$
362: ioe.initCause(e);
363: throw ioe;
364: }
365: }
366: return bytes;
367: }
368:
369: protected byte[] encryptStream(COSDictionary dict, byte[] bytes)
370: throws IOException {
371: if ((getSecurityHandler() != null)
372: && (getCurrentObject() != null)
373: && getCurrentObject().isEncryptOnWrite()) {
374: try {
375: return getSecurityHandler().encryptStream(
376: getCurrentObject().getKey(), dict, bytes);
377: } catch (COSSecurityException e) {
378: IOException ioe = new IOException(
379: "error encrypting data"); //$NON-NLS-1$
380: ioe.initCause(e);
381: throw ioe;
382: }
383: }
384: return bytes;
385: }
386:
387: protected COSIndirectObject getCurrentObject() {
388: return currentObject;
389: }
390:
391: /**
392: * The collection of proxies to COSObjects visited by the writer.
393: *
394: * @return The collection of proxies to COSObjects visited by the writer.
395: */
396: public List getProxies() {
397: return proxies;
398: }
399:
400: public IRandomAccess getRandomAccess() {
401: return randomAccess;
402: }
403:
404: public boolean isIncremental() {
405: return incremental;
406: }
407:
408: /**
409: * This will tell if we are on a new line.
410: *
411: * @return true If we are on a new line.
412: */
413: protected boolean isOnNewLine() {
414: return onNewLine;
415: }
416:
417: protected void reset() {
418: onNewLine = false;
419: }
420:
421: public void seekToEnd() throws IOException {
422: randomAccess.seek(randomAccess.getLength());
423: reset();
424: }
425:
426: protected void setCurrentObject(COSIndirectObject currentObject) {
427: this .currentObject = currentObject;
428: }
429:
430: public void setIncremental(boolean incremental) {
431: this .incremental = incremental;
432: }
433:
434: /**
435: * visitFromArray.
436: *
437: * @param obj
438: * The object that is being visited.
439: *
440: * @return unused
441: *
442: * @throws COSVisitorException
443: * If there is an exception while visiting this object.
444: */
445: public Object visitFromArray(COSArray obj)
446: throws COSVisitorException {
447: try {
448: int count = 0;
449: write(ARRAY_OPEN);
450: for (Iterator i = obj.basicIterator(); i.hasNext();) {
451: COSDocumentElement current = (COSDocumentElement) i
452: .next();
453: current.accept(this );
454: count++;
455: if (i.hasNext()) {
456: if ((count % 10) == 0) {
457: writeEOL();
458: } else {
459: write(SPACE);
460: }
461: }
462: }
463: write(ARRAY_CLOSE);
464: writeEOL();
465: } catch (IOException e) {
466: throw new COSVisitorException(e);
467: }
468: return null;
469: }
470:
471: /**
472: * visitFromBoolean.
473: *
474: * @param obj
475: * The object that is being visited.
476: *
477: * @return unused
478: *
479: * @throws COSVisitorException
480: * If there is an exception while visiting this object.
481: */
482: public Object visitFromBoolean(COSBoolean obj)
483: throws COSVisitorException {
484: try {
485: if (obj.booleanValue()) {
486: write(TRUE);
487: } else {
488: write(FALSE);
489: }
490: } catch (IOException e) {
491: throw new COSVisitorException(e);
492: }
493: return null;
494: }
495:
496: /**
497: * visitFromDictionary.
498: *
499: * @param obj
500: * The object that is being visited.
501: *
502: * @return unused
503: *
504: * @throws COSVisitorException
505: * If there is an exception while visiting this object.
506: */
507: public Object visitFromDictionary(COSDictionary obj)
508: throws COSVisitorException {
509: try {
510: write(DICT_OPEN);
511: writeEOL();
512: for (Iterator i = obj.basicEntryIterator(); i.hasNext();) {
513: Map.Entry entry = (Map.Entry) i.next();
514: COSName name = (COSName) entry.getKey();
515: COSDocumentElement current = (COSDocumentElement) entry
516: .getValue();
517: if (current != null) {
518: // this is purely defensive, if entry is set to null instead
519: // of removed
520: basicWriteName(randomAccess, name.byteValue());
521: write(SPACE);
522: current.accept(this );
523: writeEOL();
524: }
525: }
526: write(DICT_CLOSE);
527: writeEOL();
528: } catch (IOException e) {
529: throw new COSVisitorException(e);
530: }
531: return null;
532: }
533:
534: /**
535: * visitFromFixed.
536: *
537: * @param obj
538: * The object that is being visited.
539: *
540: * @return unused
541: *
542: * @throws COSVisitorException
543: * If there is an exception while visiting this object.
544: */
545: public Object visitFromFixed(COSFixed obj)
546: throws COSVisitorException {
547: try {
548: basicWriteFixed(randomAccess, obj.floatValue(), obj
549: .getPrecision());
550: onNewLine = false;
551: } catch (IOException e) {
552: throw new COSVisitorException(e);
553: }
554: return null;
555: }
556:
557: /**
558: * This will write an indirect object reference
559: *
560: * @param obj
561: * The indirect object to write.
562: * @throws COSVisitorException
563: * If there is an exception while visiting this object.
564: */
565: public Object visitFromIndirectObject(COSIndirectObject obj)
566: throws COSVisitorException {
567: reset();
568: try {
569: COSObjectKey key = obj.getKey();
570: write(StringTools.toByteArray(Integer.toString(key
571: .getObjectNumber())));
572: write(SPACE);
573: write(StringTools.toByteArray(Integer.toString(key
574: .getGenerationNumber())));
575: write(SPACE);
576: write(REFERENCE);
577: } catch (IOException e) {
578: throw new COSVisitorException(e);
579: }
580: return null;
581: }
582:
583: /**
584: * visitFromInteger.
585: *
586: * @param obj
587: * The object that is being visited.
588: *
589: * @return unused
590: *
591: * @throws COSVisitorException
592: * If there is an exception while visiting this object.
593: */
594: public Object visitFromInteger(COSInteger obj)
595: throws COSVisitorException {
596: try {
597: basicWriteInteger(randomAccess, obj.intValue());
598: onNewLine = false;
599: } catch (IOException e) {
600: throw new COSVisitorException(e);
601: }
602: return null;
603: }
604:
605: /**
606: * visitFromName.
607: *
608: * @param obj
609: * The object that is being visited.
610: *
611: * @return unused
612: *
613: * @throws COSVisitorException
614: * If there is an exception while visiting this object.
615: */
616: public Object visitFromName(COSName obj) throws COSVisitorException {
617: try {
618: basicWriteName(randomAccess, obj.byteValue());
619: onNewLine = false;
620: } catch (IOException e) {
621: throw new COSVisitorException(e);
622: }
623: return null;
624: }
625:
626: /**
627: * visitFromNull.
628: *
629: * @param obj
630: * The object that is being visited.
631: *
632: * @return unused
633: *
634: * @throws COSVisitorException
635: * If there is an exception while visiting this object.
636: */
637: public Object visitFromNull(COSNull obj) throws COSVisitorException {
638: try {
639: write(NULL);
640: } catch (IOException e) {
641: throw new COSVisitorException(e);
642: }
643: return null;
644: }
645:
646: protected void writeOperation(CSOperation obj)
647: throws COSVisitorException, IOException {
648: if ((obj.getOperatorToken().length > 1)
649: && (obj.getOperatorToken()[0] == 'E')
650: && (obj.getOperatorToken()[1] == 'I')) {
651: COSString operand;
652:
653: // expect 1 COSString operand
654: operand = (COSString) obj.getOperand(0);
655: writeImageData(operand);
656: } else {
657: for (Iterator i = obj.getOperands(); i.hasNext();) {
658: COSObject operand = (COSObject) i.next();
659: operand.accept(this );
660: write(' ');
661: }
662: }
663: write(obj.getOperatorToken());
664: }
665:
666: public Object visitFromProxy(COSObjectProxy obj)
667: throws COSVisitorException {
668: try {
669: obj.setPosition(getRandomAccess().getOffset());
670: for (int i = 0; i < obj.getLength(); i++) {
671: write(' ');
672: }
673: } catch (IOException e) {
674: throw new COSVisitorException(e);
675: }
676: proxies.add(obj);
677: return null;
678: }
679:
680: /**
681: * visitFromStream.
682: *
683: * @param obj
684: * The object that is being visited.
685: *
686: * @return unused
687: *
688: * @throws COSVisitorException
689: * If there is an exception while visiting this object.
690: */
691: public Object visitFromStream(COSStream obj)
692: throws COSVisitorException {
693: try {
694: byte[] bytes = new byte[0];
695: if (!obj.isExternal()) {
696: // only standard (internal) streams have a writable byte content
697: bytes = obj.getEncodedBytes();
698: }
699: // MUST encrypt before dict is written - length may be changed
700: byte[] encrypted = encryptStream(obj.getDict(), bytes);
701: // stream dictionaries are not indirect
702: obj.getDict().accept(this );
703: writeCRLF();
704: // write the stream content
705: writeStreamContent(encrypted);
706: } catch (IOException e) {
707: throw new COSVisitorException(e);
708: }
709: return null;
710: }
711:
712: /**
713: * visitFromString.
714: *
715: * @param obj
716: * The object that is being visited.
717: *
718: * @return unused
719: *
720: * @throws COSVisitorException
721: * If there is an exception while visiting this object.
722: */
723: public Object visitFromString(COSString obj)
724: throws COSVisitorException {
725: try {
726: byte[] bytes = obj.byteValue();
727: if (obj.isHexMode()) {
728: writeStringHex(encryptString(bytes));
729: } else {
730: writeStringLiteral(encryptString(bytes));
731: }
732: onNewLine = false;
733: } catch (IOException e) {
734: throw new COSVisitorException(e);
735: }
736: return null;
737: }
738:
739: /**
740: * This will write some byte to the stream.
741: *
742: * @param b
743: * The source byte array.
744: *
745: * @throws IOException
746: * If the underlying stream throws an exception.
747: */
748: public void write(byte[] b) throws IOException {
749: onNewLine = false;
750: randomAccess.write(b);
751: }
752:
753: /**
754: * This will write some byte to the stream.
755: *
756: * @param b
757: * The source byte array.
758: * @param off
759: * The offset into the array to start writing.
760: * @param len
761: * The number of bytes to write.
762: *
763: * @throws IOException
764: * If the underlying stream throws an exception.
765: */
766: public void write(byte[] b, int off, int len) throws IOException {
767: onNewLine = false;
768: randomAccess.write(b, off, len);
769: }
770:
771: /**
772: * This will write a single byte to the stream.
773: *
774: * @param b
775: * The byte to write to the stream.
776: *
777: * @throws IOException
778: * If there is an error writing to the underlying stream.
779: */
780: public void write(int b) throws IOException {
781: onNewLine = false;
782: randomAccess.write(b);
783: }
784:
785: public void writeContentStream(CSContent contentStream)
786: throws IOException {
787: int len = contentStream.size();
788: for (int i = 0; i < len; i++) {
789: CSOperation operation = contentStream.getOperation(i);
790: try {
791: writeOperation(operation);
792: } catch (COSVisitorException e) {
793: IOException ioe = new IOException(e.getMessage());
794: ioe.initCause(e);
795: throw ioe;
796: }
797: write(' ');
798: }
799: }
800:
801: /**
802: * This will write a CRLF to the stream
803: *
804: * @throws IOException
805: * If there is an error writing the data to the stream.
806: */
807: protected void writeCRLF() throws IOException {
808: randomAccess.write(CRLF);
809: }
810:
811: public void writeDocument(STDocument doc) throws IOException {
812: basicWriteDocument(doc);
813: close(doc);
814: }
815:
816: protected void writeEntry(STXRefSection xrefSection,
817: COSIndirectObject object) throws IOException {
818: STXRefEntryOccupied entry = new STXRefEntryOccupied(object
819: .getKey(), getRandomAccess().getOffset());
820: xrefSection.addEntry(entry);
821: writeIndirectObject(object);
822: }
823:
824: protected void writeEOF() throws IOException {
825: write(COSWriter.EOF);
826: writeEOL();
827: }
828:
829: /**
830: * This will write an EOL to the stream.
831: *
832: * @throws IOException
833: * If there is an error writing to the stream
834: */
835: public void writeEOL() throws IOException {
836: if (!isOnNewLine()) {
837: randomAccess.write(EOL);
838: onNewLine = true;
839: }
840: }
841:
842: /**
843: * This will write the header to the PDF document.
844: *
845: * @throws IOException
846: * If there is an error writing to the stream.
847: */
848: protected void writeHeader(STDocument stdoc) throws IOException {
849: write(COMMENT);
850: write(stdoc.getVersion().getBytes());
851: writeEOL();
852: write(COMMENT);
853: write(GARBAGE);
854: writeEOL();
855: }
856:
857: protected void writeImageData(COSString imageData)
858: throws IOException {
859: randomAccess.write(imageData.byteValue());
860: randomAccess.write('\r');
861: }
862:
863: public void writeIndirectObject(COSIndirectObject object)
864: throws IOException {
865: setCurrentObject(object);
866: reset();
867: COSObjectKey key = object.getKey();
868: write(StringTools.toByteArray(Integer.toString(key
869: .getObjectNumber())));
870: write(SPACE);
871: write(StringTools.toByteArray(Integer.toString(key
872: .getGenerationNumber())));
873: write(SPACE);
874: write(OBJ);
875: writeEOL();
876: try {
877: object.dereference().accept(this );
878: } catch (COSVisitorException e) {
879: Throwable cause = (e.getCause() == null) ? e : e.getCause();
880: IOException ioe = new IOException(cause.getMessage());
881: ioe.initCause(e);
882: throw ioe;
883: }
884: writeEOL();
885: write(ENDOBJ);
886: writeEOL();
887: setCurrentObject(null);
888: }
889:
890: /**
891: * This will write a cos object to the stream
892: *
893: * @param object
894: * the object to write
895: *
896: * @throws IOException
897: * If an error occurs while generating the data.
898: */
899: public void writeObject(COSObject object) throws IOException {
900: try {
901: object.accept(this );
902: } catch (COSVisitorException e) {
903: IOException ioe = new IOException(e.getMessage());
904: ioe.initCause(e);
905: throw ioe;
906: }
907: }
908:
909: protected void writeStreamContent(byte[] bytes) throws IOException {
910: write(STREAM);
911: writeCRLF();
912: if (bytes != null) {
913: write(bytes);
914: }
915: writeCRLF();
916: write(ENDSTREAM);
917: writeEOL();
918: }
919:
920: protected void writeStringHex(byte[] bytes) throws IOException {
921: basicWriteStringHex(randomAccess, bytes);
922: }
923:
924: protected void writeStringLiteral(byte[] bytes) throws IOException {
925: basicWriteStringLiteral(randomAccess, bytes);
926: }
927:
928: protected void writeXRef(STXRefSection xrefSection)
929: throws IOException {
930: AbstractXRefWriter xrefWriter = xrefSection.getWriter(this);
931: xrefWriter.writeXRef(xrefSection);
932: }
933: }
|