View Javadoc

1   // Copyright 2000-2007, FreeHEP
2   package org.freehep.graphicsio.pdf;
3   
4   import java.awt.Color;
5   import java.awt.Shape;
6   import java.awt.geom.AffineTransform;
7   import java.awt.image.RenderedImage;
8   import java.io.IOException;
9   import java.io.OutputStream;
10  
11  import org.freehep.graphicsio.ImageConstants;
12  import org.freehep.util.io.ASCII85OutputStream;
13  import org.freehep.util.io.ASCIIHexOutputStream;
14  import org.freehep.util.io.CountedByteOutputStream;
15  import org.freehep.util.io.FinishableOutputStream;
16  import org.freehep.util.io.FlateOutputStream;
17  
18  /**
19   * This class allows you to write/print into a PDFStream. Several methods are
20   * available to specify the content of a page, image. This class performs some
21   * error checking, while writing the stream.
22   * <p>
23   * The stream allows to write dictionary entries. The /Length entry is written
24   * automatically, referencing an object which will also be written just after
25   * the stream is closed and the length is calculated.
26   * <p>
27   * 
28   * @author Mark Donszelmann
29   * @version $Id: PDFStream.java 12626 2007-06-08 22:23:13Z duns $
30   */
31  public class PDFStream extends PDFDictionary implements PDFConstants {
32  
33      private String name;
34  
35      private PDFObject object;
36  
37      private boolean dictionaryOpen;
38  
39      private OutputStream[] stream;
40  
41      private CountedByteOutputStream byteCountStream;
42  
43      private String[] encode;
44  
45      PDFStream(PDF pdf, PDFByteWriter writer, String name, PDFObject parent,
46              String[] encode) throws IOException {
47          super(pdf, writer);
48          this.name = name;
49          object = parent;
50          if (object == null)
51              System.err
52                      .println("PDFWriter: 'PDFStream' cannot have a null parent");
53          // first write the dictionary
54          dictionaryOpen = true;
55          this.encode = encode;
56      }
57  
58      /**
59       * Starts the stream, writes out the filters using the preset encoding, and
60       * encodes the stream.
61       */
62      private void startStream() throws IOException {
63          startStream(encode);
64      }
65  
66      /**
67       * Starts the stream, writes out the filters using the given encoding, and
68       * encodes the stream.
69       */
70      private void startStream(String[] encode) throws IOException {
71          if (dictionaryOpen) {
72              PDFName[] filters = decodeFilters(encode);
73              if (filters != null)
74                  entry("Filter", filters);
75  
76              super.close();
77              dictionaryOpen = false;
78              out.printPlain("stream\n");
79  
80              byteCountStream = new CountedByteOutputStream(out);
81              stream = openFilters(byteCountStream, encode);
82          }
83      }
84  
85      private void write(int b) throws IOException {
86          startStream();
87          stream[0].write(b);
88      }
89  
90      private void write(byte[] b) throws IOException {
91          for (int i = 0; i < b.length; i++) {
92              write((int) b[i]);
93          }
94      }
95  
96      private static PDFName[] decodeFilters(String[] encode) {
97          PDFName[] filters = null;
98          if ((encode != null) && (encode.length != 0)) {
99              filters = new PDFName[encode.length];
100             for (int i = 0; i < filters.length; i++) {
101                 filters[i] = new PDFName(encode[encode.length - i - 1]
102                         + "Decode");
103             }
104         }
105         return filters;
106     }
107 
108     // open new stream using Standard Filters (see table 3.5)
109     // stream[0] is the one to write to, the last one is s
110     private static OutputStream[] openFilters(OutputStream s, String[] filters) {
111         OutputStream[] os;
112         if ((filters != null) && (filters.length != 0)) {
113             os = new OutputStream[filters.length + 1];
114             os[os.length - 1] = s;
115             for (int i = os.length - 2; i >= 0; i--) {
116                 if (filters[i].equals("ASCIIHex")) {
117                     os[i] = new ASCIIHexOutputStream(os[i + 1]);
118                 } else if (filters[i].equals("ASCII85")) {
119                     os[i] = new ASCII85OutputStream(os[i + 1]);
120                 } else if (filters[i].equals("Flate")) {
121                     os[i] = new FlateOutputStream(os[i + 1]);
122                 } else if (filters[i].equals("DCT")) {
123                     os[i] = os[i + 1];
124                 } else {
125                     System.err.println("PDFWriter: unknown stream format: "
126                             + filters[i]);
127                 }
128             }
129         } else {
130             os = new OutputStream[1];
131             os[0] = s;
132         }
133         return os;
134     }
135 
136     // stream[0] is the first one to finish, the last one is not finished
137     private static void closeFilters(OutputStream[] s) throws IOException {
138         for (int i = 0; i < s.length - 1; i++) {
139             s[i].flush();
140             if (s[i] instanceof FinishableOutputStream) {
141                 ((FinishableOutputStream) s[i]).finish();
142             }
143         }
144         s[s.length - 1].flush();
145     }
146 
147     private void write(String s) throws IOException {
148         byte[] b = s.getBytes("ISO-8859-1");
149         for (int i = 0; i < b.length; i++) {
150             write(b[i]);
151         }
152     }
153 
154     void close() throws IOException {
155         closeFilters(stream);
156         stream = null;
157         out.printPlain("\nendstream");
158         out.println();
159         object.close();
160         
161         if (gStates > 0) {
162             System.err.println("PDFStream: unbalanced saves()/restores(), too many saves: "+gStates);
163         }
164     }
165 
166     String getName() {
167         return name;
168     }
169 
170     public int getLength() {
171         return byteCountStream.getCount();
172     }
173 
174     public void print(String s) throws IOException {
175         write(s);
176     }
177 
178     public void println(String s) throws IOException {
179         write(s);
180         write(EOL);
181     }
182 
183     public void comment(String comment) throws IOException {
184         println("% " + comment);
185     }
186 
187     // ==========================================================================
188     // PDFStream Operators according to Table 4.1
189     // ==========================================================================
190 
191     //
192     // Graphics State operators (see Table 4.7)
193     //
194     private int gStates = 0;
195 
196     public void save() throws IOException {
197         println("q");
198         gStates++;
199     }
200 
201     public void restore() throws IOException {
202         if (gStates <= 0) {
203             System.err.println("PDFStream: unbalanced saves()/restores(), too many restores");
204         }
205         gStates--;
206         println("Q");
207     }
208 
209     public void matrix(AffineTransform xform) throws IOException {
210         matrix(xform.getScaleX(), xform.getShearY(), xform.getShearX(), xform
211                 .getScaleY(), xform.getTranslateX(), xform.getTranslateY());
212     }
213 
214     public void matrix(double m00, double m10, double m01, double m11,
215             double m02, double m12) throws IOException {
216         println(PDFUtil.fixedPrecision(m00) + " " + PDFUtil.fixedPrecision(m10)
217                 + " " + PDFUtil.fixedPrecision(m01) + " "
218                 + PDFUtil.fixedPrecision(m11) + " "
219                 + PDFUtil.fixedPrecision(m02) + " "
220                 + PDFUtil.fixedPrecision(m12) + " cm");
221     }
222 
223     public void width(double width) throws IOException {
224         println(PDFUtil.fixedPrecision(width) + " w");
225     }
226 
227     public void cap(int capStyle) throws IOException {
228         println(capStyle + " J");
229     }
230 
231     public void join(int joinStyle) throws IOException {
232         println(joinStyle + " j");
233     }
234 
235     public void mitterLimit(double limit) throws IOException {
236         println(PDFUtil.fixedPrecision(limit) + " M");
237     }
238 
239     public void dash(int[] dash, double phase) throws IOException {
240         print("[");
241         for (int i = 0; i < dash.length; i++) {
242             print(" " + PDFUtil.fixedPrecision(dash[i]));
243         }
244         println("] " + PDFUtil.fixedPrecision(phase) + " d");
245     }
246 
247     public void dash(float[] dash, double phase) throws IOException {
248         print("[");
249         for (int i = 0; i < dash.length; i++) {
250             print(" " + PDFUtil.fixedPrecision(dash[i]));
251         }
252         println("] " + PDFUtil.fixedPrecision(phase) + " d");
253     }
254 
255     public void flatness(double flatness) throws IOException {
256         println(PDFUtil.fixedPrecision(flatness) + " i");
257     }
258 
259     public void state(PDFName stateDictionary) throws IOException {
260         println(stateDictionary + " gs");
261     }
262 
263     //
264     // Path Construction operators (see Table 4.9)
265     //
266     public void cubic(double x1, double y1, double x2, double y2, double x3,
267             double y3) throws IOException {
268         println(PDFUtil.fixedPrecision(x1) + " " + PDFUtil.fixedPrecision(y1)
269                 + " " + PDFUtil.fixedPrecision(x2) + " "
270                 + PDFUtil.fixedPrecision(y2) + " " + PDFUtil.fixedPrecision(x3)
271                 + " " + PDFUtil.fixedPrecision(y3) + " c");
272     }
273 
274     public void cubicV(double x2, double y2, double x3, double y3)
275             throws IOException {
276         println(PDFUtil.fixedPrecision(x2) + " " + PDFUtil.fixedPrecision(y2)
277                 + " " + PDFUtil.fixedPrecision(x3) + " "
278                 + PDFUtil.fixedPrecision(y3) + " v");
279     }
280 
281     public void cubicY(double x1, double y1, double x3, double y3)
282             throws IOException {
283         println(PDFUtil.fixedPrecision(x1) + " " + PDFUtil.fixedPrecision(y1)
284                 + " " + PDFUtil.fixedPrecision(x3) + " "
285                 + PDFUtil.fixedPrecision(y3) + " y");
286     }
287 
288     public void move(double x, double y) throws IOException {
289         println(PDFUtil.fixedPrecision(x) + " " + PDFUtil.fixedPrecision(y)
290                 + " m");
291     }
292 
293     public void line(double x, double y) throws IOException {
294         println(PDFUtil.fixedPrecision(x) + " " + PDFUtil.fixedPrecision(y)
295                 + " l");
296     }
297 
298     public void closePath() throws IOException {
299         println("h");
300     }
301 
302     public void rectangle(double x, double y, double width, double height)
303             throws IOException {
304         println(PDFUtil.fixedPrecision(x) + " " + PDFUtil.fixedPrecision(y)
305                 + " " + PDFUtil.fixedPrecision(width) + " "
306                 + PDFUtil.fixedPrecision(height) + " re");
307     }
308 
309     //
310     // Path Painting operators (see Table 4.10)
311     //
312     public void stroke() throws IOException {
313         println("S");
314     }
315 
316     public void closeAndStroke() throws IOException {
317         println("s");
318     }
319 
320     public void fill() throws IOException {
321         println("f");
322     }
323 
324     public void fillEvenOdd() throws IOException {
325         println("f*");
326     }
327 
328     public void fillAndStroke() throws IOException {
329         println("B");
330     }
331 
332     public void fillEvenOddAndStroke() throws IOException {
333         println("B*");
334     }
335 
336     public void closeFillAndStroke() throws IOException {
337         println("b");
338     }
339 
340     public void closeFillEvenOddAndStroke() throws IOException {
341         println("b*");
342     }
343 
344     public void endPath() throws IOException {
345         println("n");
346     }
347 
348     //
349     // Clipping Path operators (see Table 4.11)
350     //
351     public void clip() throws IOException {
352         println("W");
353     }
354 
355     public void clipEvenOdd() throws IOException {
356         println("W*");
357     }
358 
359     //
360     // Text Object operators (see Table 5.4)
361     //
362     private boolean textOpen = false;
363 
364     public void beginText() throws IOException {
365         if (textOpen)
366             System.err.println("PDFStream: nested beginText() not allowed.");
367         println("BT");
368         textOpen = true;
369     }
370 
371     public void endText() throws IOException {
372         if (!textOpen)
373             System.err
374                     .println("PDFStream: unbalanced use of beginText()/endText().");
375         println("ET");
376         textOpen = false;
377     }
378 
379     //
380     // Text State operators (see Table 5.2)
381     //
382     public void charSpace(double charSpace) throws IOException {
383         println(PDFUtil.fixedPrecision(charSpace) + " Tc");
384     }
385 
386     public void wordSpace(double wordSpace) throws IOException {
387         println(PDFUtil.fixedPrecision(wordSpace) + " Tw");
388     }
389 
390     public void scale(double scale) throws IOException {
391         println(PDFUtil.fixedPrecision(scale) + " Tz");
392     }
393 
394     public void leading(double leading) throws IOException {
395         println(PDFUtil.fixedPrecision(leading) + " TL");
396     }
397 
398     private boolean fontWasSet = false;
399 
400     public void font(PDFName fontName, double size) throws IOException {
401         println(fontName + " " + PDFUtil.fixedPrecision(size) + " Tf");
402         fontWasSet = true;
403     }
404 
405     public void rendering(int mode) throws IOException {
406         println(mode + " Tr");
407     }
408 
409     public void rise(double rise) throws IOException {
410         println(PDFUtil.fixedPrecision(rise) + " Ts");
411     }
412 
413     //
414     // Text Positioning operators (see Table 5.5)
415     //
416     public void text(double x, double y) throws IOException {
417         println(PDFUtil.fixedPrecision(x) + " " + PDFUtil.fixedPrecision(y)
418                 + " Td");
419     }
420 
421     public void textLeading(double x, double y) throws IOException {
422         println(PDFUtil.fixedPrecision(x) + " " + PDFUtil.fixedPrecision(y)
423                 + " TD");
424     }
425 
426     public void textMatrix(double a, double b, double c, double d, double e,
427             double f) throws IOException {
428         println(PDFUtil.fixedPrecision(a) + " " + PDFUtil.fixedPrecision(b)
429                 + " " + PDFUtil.fixedPrecision(c) + " "
430                 + PDFUtil.fixedPrecision(d) + " " + PDFUtil.fixedPrecision(e)
431                 + " " + PDFUtil.fixedPrecision(f) + " Tm");
432     }
433 
434     public void textLine() throws IOException {
435         println("T*");
436     }
437 
438     //
439     // Text Showing operators (see Table 5.6)
440     //
441     public void show(String text) throws IOException {
442         if (!fontWasSet)
443             System.err
444                     .println("PDFStream: cannot use Text Showing operator before font is set.");
445         if (!textOpen)
446             System.err
447                     .println("PDFStream: Text Showing operator only allowed inside Text section.");
448         println("(" + PDFUtil.escape(text) + ") Tj");
449     }
450 
451     public void showLine(String text) throws IOException {
452         if (!fontWasSet)
453             System.err
454                     .println("PDFStream: cannot use Text Showing operator before font is set.");
455         if (!textOpen)
456             System.err
457                     .println("PDFStream: Text Showing operator only allowed inside Text section.");
458         println("(" + PDFUtil.escape(text) + ") '");
459     }
460 
461     public void showLine(double wordSpace, double charSpace, String text)
462             throws IOException {
463         if (!fontWasSet)
464             System.err
465                     .println("PDFStream: cannot use Text Showing operator before font is set.");
466         if (!textOpen)
467             System.err
468                     .println("PDFStream: Text Showing operator only allowed inside Text section.");
469         println(PDFUtil.fixedPrecision(wordSpace) + " "
470                 + PDFUtil.fixedPrecision(charSpace) + " ("
471                 + PDFUtil.escape(text) + ") \"");
472     }
473 
474     public void show(Object[] array) throws IOException {
475         print("[");
476         for (int i = 0; i < array.length; i++) {
477             Object object = array[i];
478             if (object instanceof String) {
479                 print(" (" + PDFUtil.escape(object.toString()) + ")");
480             } else if (object instanceof Integer) {
481                 print(" " + ((Integer) object).intValue());
482             } else if (object instanceof Double) {
483                 print(" " + ((Double) object).doubleValue());
484             } else {
485                 System.err
486                         .println("PDFStream: input array of operator TJ may only contain objects of type 'String', 'Integer' or 'Double'");
487             }
488         }
489         println("] TJ");
490     }
491 
492     //
493     // Type 3 Font operators (see Table 5.10)
494     //
495     public void glyph(double wx, double wy) throws IOException {
496         println(PDFUtil.fixedPrecision(wx) + " " + PDFUtil.fixedPrecision(wy)
497                 + " d0");
498     }
499 
500     public void glyph(double wx, double wy, double llx, double lly, double urx,
501             double ury) throws IOException {
502         println(PDFUtil.fixedPrecision(wx) + " " + PDFUtil.fixedPrecision(wy)
503                 + " " + PDFUtil.fixedPrecision(llx) + " "
504                 + PDFUtil.fixedPrecision(lly) + " "
505                 + PDFUtil.fixedPrecision(urx) + " "
506                 + PDFUtil.fixedPrecision(ury) + " d1");
507     }
508 
509     //
510     // Color operators (see Table 4.21)
511     //
512     public void colorSpace(PDFName colorSpace) throws IOException {
513         println(colorSpace + " cs");
514     }
515 
516     public void colorSpaceStroke(PDFName colorSpace) throws IOException {
517         println(colorSpace + " CS");
518     }
519 
520     public void colorSpace(double[] color) throws IOException {
521         for (int i = 0; i < color.length; i++) {
522             print(" " + color[i]);
523         }
524         println(" scn");
525     }
526 
527     public void colorSpaceStroke(double[] color) throws IOException {
528         for (int i = 0; i < color.length; i++) {
529             print(" " + color[i]);
530         }
531         println(" SCN");
532     }
533 
534     public void colorSpace(double[] color, PDFName name) throws IOException {
535         if (color != null) {
536             for (int i = 0; i < color.length; i++) {
537                 print(PDFUtil.fixedPrecision(color[i]) + " ");
538             }
539         }
540         println(name + " scn");
541     }
542 
543     public void colorSpaceStroke(double[] color, PDFName name)
544             throws IOException {
545         if (color != null) {
546             for (int i = 0; i < color.length; i++) {
547                 print(PDFUtil.fixedPrecision(color[i]) + " ");
548             }
549         }
550         println(name + " SCN");
551     }
552 
553     public void colorSpace(double g) throws IOException {
554         println(PDFUtil.fixedPrecision(g) + " g");
555     }
556 
557     public void colorSpaceStroke(double g) throws IOException {
558         println(PDFUtil.fixedPrecision(g) + " G");
559     }
560 
561     public void colorSpace(double r, double g, double b) throws IOException {
562         println(PDFUtil.fixedPrecision(r) + " " + PDFUtil.fixedPrecision(g)
563                 + " " + PDFUtil.fixedPrecision(b) + " rg");
564     }
565 
566     public void colorSpaceStroke(double r, double g, double b)
567             throws IOException {
568         println(PDFUtil.fixedPrecision(r) + " " + PDFUtil.fixedPrecision(g)
569                 + " " + PDFUtil.fixedPrecision(b) + " RG");
570     }
571 
572     public void colorSpace(double c, double m, double y, double k)
573             throws IOException {
574         println(PDFUtil.fixedPrecision(c) + " " + PDFUtil.fixedPrecision(m)
575                 + " " + PDFUtil.fixedPrecision(y) + " "
576                 + PDFUtil.fixedPrecision(k) + " k");
577     }
578 
579     public void colorSpaceStroke(double c, double m, double y, double k)
580             throws IOException {
581         println(PDFUtil.fixedPrecision(c) + " " + PDFUtil.fixedPrecision(m)
582                 + " " + PDFUtil.fixedPrecision(y) + " "
583                 + PDFUtil.fixedPrecision(k) + " K");
584     }
585 
586     //
587     // Shading Pattern operator (see Table 4.24)
588     //
589     public void shade(PDFName name) throws IOException {
590         println(name + " sh");
591     }
592 
593     /**
594      * returns the decode-format for an image -format
595      *
596      * @param encode {@link ImageConstants#ZLIB} or {@link ImageConstants#JPG}
597      * @return {@link #decodeFilters(String[])}
598      */
599     private PDFName[] getFilterName(String encode) {
600         if (ImageConstants.ZLIB.equals(encode)) {
601             return decodeFilters(new String[] {
602                 ImageConstants.ENCODING_FLATE,
603                 ImageConstants.ENCODING_ASCII85});
604         }
605 
606         if (ImageConstants.JPG.equals(encode)) {
607             return decodeFilters(new String[] {
608                 ImageConstants.ENCODING_DCT,
609                 ImageConstants.ENCODING_ASCII85});
610         }
611 
612         throw new IllegalArgumentException("unknown image encoding " + encode  + " for PDFStream");
613     }
614 
615     /**
616      * Image convenience function (see Table 4.35). Ouputs the data of the image
617      * using "DeviceRGB" colorspace, and the requested encodings
618      *
619      * @param image Image to write
620      * @param bkg Background color, null for transparent image
621      * @param encode {@link org.freehep.graphicsio.ImageConstants#ZLIB} or {@link org.freehep.graphicsio.ImageConstants#JPG}
622      * @throws java.io.IOException thrown by ImageBytes
623      */
624     public void image(RenderedImage image, Color bkg, String encode)
625             throws IOException {
626 
627         ImageBytes bytes = new ImageBytes(image, bkg, encode, ImageConstants.COLOR_MODEL_RGB);
628 
629         entry("Width", image.getWidth());
630         entry("Height", image.getHeight());
631         entry("ColorSpace", pdf.name("DeviceRGB"));
632         entry("BitsPerComponent", 8);
633         entry("Filter", getFilterName(bytes.getFormat()));
634         write(bytes.getBytes());
635     }
636 
637     public void imageMask(RenderedImage image, String encode)
638             throws IOException {
639 
640         ImageBytes bytes = new ImageBytes(image, null, encode, ImageConstants.COLOR_MODEL_A);
641 
642         entry("Width", image.getWidth());
643         entry("Height", image.getHeight());
644         entry("BitsPerComponent", 8);
645         entry("ColorSpace", pdf.name("DeviceGray"));
646         entry("Filter", getFilterName(bytes.getFormat()));
647         write(bytes.getBytes());
648     }
649 
650 
651     /**
652      * Inline Image convenience function (see Table 4.39 and 4.40). Ouputs the
653      * data of the image using "DeviceRGB" colorspace, and the requested
654      * encoding.
655 
656      * @param image Image to write
657      * @param bkg Background color, null for transparent image
658      * @param encode {@link org.freehep.graphicsio.ImageConstants#ZLIB} or {@link org.freehep.graphicsio.ImageConstants#JPG}
659      * @throws java.io.IOException thrown by ImageBytes
660      */
661     public void inlineImage(RenderedImage image, Color bkg, String encode)
662             throws IOException {
663 
664         ImageBytes bytes = new ImageBytes(image, bkg, ImageConstants.JPG, ImageConstants.COLOR_MODEL_RGB);
665 
666         println("BI");
667         imageInfo("Width", image.getWidth());
668         imageInfo("Height", image.getHeight());
669         imageInfo("ColorSpace", pdf.name("DeviceRGB"));
670         imageInfo("BitsPerComponent", 8);
671 
672         imageInfo("Filter", getFilterName(bytes.getFormat()));
673         print("ID\n");
674 
675         write(bytes.getBytes());
676 
677         println("\nEI");
678     }
679 
680     //
681     // In-line Image operators (see Table 4.38)
682     //
683     private void imageInfo(String key, int number) throws IOException {
684         println("/" + key + " " + number);
685     }
686 
687     private void imageInfo(String key, PDFName name) throws IOException {
688         println("/" + key + " " + name);
689     }
690 
691     private void imageInfo(String key, Object[] array) throws IOException {
692         print("/" + key + " [");
693         for (int i = 0; i < array.length; i++) {
694             print(" " + array[i]);
695         }
696         println("]");
697     }
698 
699     /**
700      * Draws the <i>points</i> of the shape using path <i>construction</i>
701      * operators. The path is neither stroked nor filled.
702      * 
703      * @return true if even-odd winding rule should be used, false if non-zero
704      *         winding rule should be used.
705      */
706     public boolean drawPath(Shape s) throws IOException {
707         PDFPathConstructor path = new PDFPathConstructor(this);
708         return path.addPath(s);
709     }
710 
711     //
712     // XObject operators (see Table 4.34)
713     //
714     public void xObject(PDFName name) throws IOException {
715         println(name + " Do");
716     }
717 
718     //
719     // Marked Content operators (see Table 8.5)
720     //
721     // FIXME: missing all
722 
723     //
724     // Compatibility operators (see Table 3.19)
725     //
726     private boolean compatibilityOpen = false;
727 
728     public void beginCompatibility() throws IOException {
729         if (compatibilityOpen)
730             System.err
731                     .println("PDFStream: nested use of Compatibility sections not allowed.");
732         println("BX");
733         compatibilityOpen = true;
734     }
735 
736     public void endCompatibility() throws IOException {
737         if (!compatibilityOpen)
738             System.err
739                     .println("PDFStream: unbalanced use of begin/endCompatibilty().");
740         println("EX");
741         compatibilityOpen = false;
742     }
743 
744 }