1 /*
2  * $Id: PdfContentByte.java 3912 2009-04-26 08:38:15Z blowagie $
3  *
4  * Copyright 1999, 2000, 2001, 2002 Bruno Lowagie
5  *
6  * The contents of this file are subject to the Mozilla Public License Version 1.1
7  * (the "License"); you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the License.
13  *
14  * The Original Code is 'iText, a free JAVA-PDF library'.
15  *
16  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
17  * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
18  * All Rights Reserved.
19  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
20  * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
21  *
22  * Contributor(s): all the names of the contributors are added in the source code
23  * where applicable.
24  *
25  * Alternatively, the contents of this file may be used under the terms of the
26  * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
27  * provisions of LGPL are applicable instead of those above.  If you wish to
28  * allow use of your version of this file only under the terms of the LGPL
29  * License and not to allow others to use your version of this file under
30  * the MPL, indicate your decision by deleting the provisions above and
31  * replace them with the notice and other provisions required by the LGPL.
32  * If you do not delete the provisions above, a recipient may use your version
33  * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
34  *
35  * This library is free software; you can redistribute it and/or modify it
36  * under the terms of the MPL as stated above or under the terms of the GNU
37  * Library General Public License as published by the Free Software Foundation;
38  * either version 2 of the License, or any later version.
39  *
40  * This library is distributed in the hope that it will be useful, but WITHOUT
41  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
42  * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
43  * details.
44  *
45  * If you didn't download this code from the following link, you should check if
46  * you aren't using an obsolete version:
47  * http://www.lowagie.com/iText/
48  */

49
50 package com.lowagie.text.pdf;
51 import java.awt.Color;
52 import java.awt.geom.AffineTransform;
53 import java.awt.print.PrinterJob;
54 import java.util.ArrayList;
55 import java.util.HashMap;
56 import java.util.Iterator;
57
58 import com.lowagie.text.Annotation;
59 import com.lowagie.text.DocumentException;
60 import com.lowagie.text.Element;
61 import com.lowagie.text.ExceptionConverter;
62 import com.lowagie.text.Image;
63 import com.lowagie.text.ImgJBIG2;
64 import com.lowagie.text.Rectangle;
65 import com.lowagie.text.exceptions.IllegalPdfSyntaxException;
66 import com.lowagie.text.pdf.internal.PdfAnnotationsImp;
67 import com.lowagie.text.pdf.internal.PdfXConformanceImp;
68
69 /**
70  * <CODE>PdfContentByte</CODE> is an object containing the user positioned
71  * text and graphic contents of a page. It knows how to apply the proper
72  * font encoding.
73  */

74
75 public class PdfContentByte {
76
77     /**
78      * This class keeps the graphic state of the current page
79      */

80
81     static class GraphicState {
82
83         /** This is the font in use */
84         FontDetails fontDetails;
85
86         /** This is the color in use */
87         ColorDetails colorDetails;
88
89         /** This is the font size in use */
90         float size;
91
92         /** The x position of the text line matrix. */
93         protected float xTLM = 0;
94         /** The y position of the text line matrix. */
95         protected float yTLM = 0;
96
97         /** The current text leading. */
98         protected float leading = 0;
99
100         /** The current horizontal scaling */
101         protected float scale = 100;
102
103         /** The current character spacing */
104         protected float charSpace = 0;
105
106         /** The current word spacing */
107         protected float wordSpace = 0;
108
109         GraphicState() {
110         }
111
112         GraphicState(GraphicState cp) {
113             fontDetails = cp.fontDetails;
114             colorDetails = cp.colorDetails;
115             size = cp.size;
116             xTLM = cp.xTLM;
117             yTLM = cp.yTLM;
118             leading = cp.leading;
119             scale = cp.scale;
120             charSpace = cp.charSpace;
121             wordSpace = cp.wordSpace;
122         }
123     }
124
125     /** The alignment is center */
126     public static final int ALIGN_CENTER = Element.ALIGN_CENTER;
127
128     /** The alignment is left */
129     public static final int ALIGN_LEFT = Element.ALIGN_LEFT;
130
131     /** The alignment is right */
132     public static final int ALIGN_RIGHT = Element.ALIGN_RIGHT;
133
134     /** A possible line cap value */
135     public static final int LINE_CAP_BUTT = 0;
136     /** A possible line cap value */
137     public static final int LINE_CAP_ROUND = 1;
138     /** A possible line cap value */
139     public static final int LINE_CAP_PROJECTING_SQUARE = 2;
140
141     /** A possible line join value */
142     public static final int LINE_JOIN_MITER = 0;
143     /** A possible line join value */
144     public static final int LINE_JOIN_ROUND = 1;
145     /** A possible line join value */
146     public static final int LINE_JOIN_BEVEL = 2;
147
148     /** A possible text rendering value */
149     public static final int TEXT_RENDER_MODE_FILL = 0;
150     /** A possible text rendering value */
151     public static final int TEXT_RENDER_MODE_STROKE = 1;
152     /** A possible text rendering value */
153     public static final int TEXT_RENDER_MODE_FILL_STROKE = 2;
154     /** A possible text rendering value */
155     public static final int TEXT_RENDER_MODE_INVISIBLE = 3;
156     /** A possible text rendering value */
157     public static final int TEXT_RENDER_MODE_FILL_CLIP = 4;
158     /** A possible text rendering value */
159     public static final int TEXT_RENDER_MODE_STROKE_CLIP = 5;
160     /** A possible text rendering value */
161     public static final int TEXT_RENDER_MODE_FILL_STROKE_CLIP = 6;
162     /** A possible text rendering value */
163     public static final int TEXT_RENDER_MODE_CLIP = 7;
164
165     private static final float[] unitRect = {0, 0, 0, 1, 1, 0, 1, 1};
166     // membervariables
167
168     /** This is the actual content */
169     protected ByteBuffer content = new ByteBuffer();
170
171     /** This is the writer */
172     protected PdfWriter writer;
173
174     /** This is the PdfDocument */
175     protected PdfDocument pdf;
176
177     /** This is the GraphicState in use */
178     protected GraphicState state = new GraphicState();
179
180     /** The list were we save/restore the state */
181     protected ArrayList stateList = new ArrayList();
182
183     /** The list were we save/restore the layer depth */
184     protected ArrayList layerDepth;
185
186     /** The separator between commands.
187      */

188     protected int separator = '\n';
189     
190     private int mcDepth = 0;
191     private boolean inText = false;
192
193     private static HashMap abrev = new HashMap();
194
195     static {
196         abrev.put(PdfName.BITSPERCOMPONENT, "/BPC ");
197         abrev.put(PdfName.COLORSPACE, "/CS ");
198         abrev.put(PdfName.DECODE, "/D ");
199         abrev.put(PdfName.DECODEPARMS, "/DP ");
200         abrev.put(PdfName.FILTER, "/F ");
201         abrev.put(PdfName.HEIGHT, "/H ");
202         abrev.put(PdfName.IMAGEMASK, "/IM ");
203         abrev.put(PdfName.INTENT, "/Intent ");
204         abrev.put(PdfName.INTERPOLATE, "/I ");
205         abrev.put(PdfName.WIDTH, "/W ");
206     }
207
208     // constructors
209
210     /**
211      * Constructs a new <CODE>PdfContentByte</CODE>-object.
212      *
213      * @param wr the writer associated to this content
214      */

215
216     public PdfContentByte(PdfWriter wr) {
217         if (wr != null) {
218             writer = wr;
219             pdf = writer.getPdfDocument();
220         }
221     }
222
223     // methods to get the content of this object
224
225     /**
226      * Returns the <CODE>String</CODE> representation of this <CODE>PdfContentByte</CODE>-object.
227      *
228      * @return      a <CODE>String</CODE>
229      */

230
231     public String toString() {
232         return content.toString();
233     }
234
235     /**
236      * Gets the internal buffer.
237      * @return the internal buffer
238      */

239     public ByteBuffer getInternalBuffer() {
240         return content;
241     }
242
243     /** Returns the PDF representation of this <CODE>PdfContentByte</CODE>-object.
244      *
245      * @param writer the <CODE>PdfWriter</CODE>
246      * @return a <CODE>byte</CODE> array with the representation
247      */

248
249     public byte[] toPdf(PdfWriter writer) {
250         sanityCheck();
251         return content.toByteArray();
252     }
253
254     // methods to add graphical content
255
256     /**
257      * Adds the content of another <CODE>PdfContent</CODE>-object to this object.
258      *
259      * @param       other       another <CODE>PdfByteContent</CODE>-object
260      */

261
262     public void add(PdfContentByte other) {
263         if (other.writer != null && writer != other.writer)
264             throw new RuntimeException("Inconsistent writers. Are you mixing two documents?");
265         content.append(other.content);
266     }
267
268     /**
269      * Gets the x position of the text line matrix.
270      *
271      * @return the x position of the text line matrix
272      */

273     public float getXTLM() {
274         return state.xTLM;
275     }
276
277     /**
278      * Gets the y position of the text line matrix.
279      *
280      * @return the y position of the text line matrix
281      */

282     public float getYTLM() {
283         return state.yTLM;
284     }
285
286     /**
287      * Gets the current text leading.
288      *
289      * @return the current text leading
290      */

291     public float getLeading() {
292         return state.leading;
293     }
294
295     /**
296      * Gets the current character spacing.
297      *
298      * @return the current character spacing
299      */

300     public float getCharacterSpacing() {
301         return state.charSpace;
302     }
303
304     /**
305      * Gets the current word spacing.
306      *
307      * @return the current word spacing
308      */

309     public float getWordSpacing() {
310         return state.wordSpace;
311     }
312
313     /**
314      * Gets the current character spacing.
315      *
316      * @return the current character spacing
317      */

318     public float getHorizontalScaling() {
319         return state.scale;
320     }
321
322     /**
323      * Changes the <VAR>Flatness</VAR>.
324      * <P>
325      * <VAR>Flatness</VAR> sets the maximum permitted distance in device pixels between the
326      * mathematically correct path and an approximation constructed from straight line segments.<BR>
327      *
328      * @param       flatness        a value
329      */

330
331     public void setFlatness(float flatness) {
332         if (flatness >= 0 && flatness <= 100) {
333             content.append(flatness).append(" i").append_i(separator);
334         }
335     }
336
337     /**
338      * Changes the <VAR>Line cap style</VAR>.
339      * <P>
340      * The <VAR>line cap style</VAR> specifies the shape to be used at the end of open subpaths
341      * when they are stroked.<BR>
342      * Allowed values are LINE_CAP_BUTT, LINE_CAP_ROUND and LINE_CAP_PROJECTING_SQUARE.<BR>
343      *
344      * @param       style       a value
345      */

346
347     public void setLineCap(int style) {
348         if (style >= 0 && style <= 2) {
349             content.append(style).append(" J").append_i(separator);
350         }
351     }
352
353     /**
354      * Changes the value of the <VAR>line dash pattern</VAR>.
355      * <P>
356      * The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
357      * It is specified by an <I>array</I> and a <I>phase</I>. The array specifies the length
358      * of the alternating dashes and gaps. The phase specifies the distance into the dash
359      * pattern to start the dash.<BR>
360      *
361      * @param       phase       the value of the phase
362      */

363
364     public void setLineDash(float phase) {
365         content.append("[] ").append(phase).append(" d").append_i(separator);
366     }
367
368     /**
369      * Changes the value of the <VAR>line dash pattern</VAR>.
370      * <P>
371      * The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
372      * It is specified by an <I>array</I> and a <I>phase</I>. The array specifies the length
373      * of the alternating dashes and gaps. The phase specifies the distance into the dash
374      * pattern to start the dash.<BR>
375      *
376      * @param       phase       the value of the phase
377      * @param       unitsOn     the number of units that must be 'on' (equals the number of units that must be 'off').
378      */

379
380     public void setLineDash(float unitsOn, float phase) {
381         content.append("[").append(unitsOn).append("] ").append(phase).append(" d").append_i(separator);
382     }
383
384     /**
385      * Changes the value of the <VAR>line dash pattern</VAR>.
386      * <P>
387      * The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
388      * It is specified by an <I>array</I> and a <I>phase</I>. The array specifies the length
389      * of the alternating dashes and gaps. The phase specifies the distance into the dash
390      * pattern to start the dash.<BR>
391      *
392      * @param       phase       the value of the phase
393      * @param       unitsOn     the number of units that must be 'on'
394      * @param       unitsOff    the number of units that must be 'off'
395      */

396
397     public void setLineDash(float unitsOn, float unitsOff, float phase) {
398         content.append("[").append(unitsOn).append(' ').append(unitsOff).append("] ").append(phase).append(" d").append_i(separator);
399     }
400
401     /**
402      * Changes the value of the <VAR>line dash pattern</VAR>.
403      * <P>
404      * The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
405      * It is specified by an <I>array</I> and a <I>phase</I>. The array specifies the length
406      * of the alternating dashes and gaps. The phase specifies the distance into the dash
407      * pattern to start the dash.<BR>
408      *
409      * @param       array       length of the alternating dashes and gaps
410      * @param       phase       the value of the phase
411      */

412
413     public final void setLineDash(float[] array, float phase) {
414         content.append("[");
415         for (int i = 0; i < array.length; i++) {
416             content.append(array[i]);
417             if (i < array.length - 1) content.append(' ');
418         }
419         content.append("] ").append(phase).append(" d").append_i(separator);
420     }
421
422     /**
423      * Changes the <VAR>Line join style</VAR>.
424      * <P>
425      * The <VAR>line join style</VAR> specifies the shape to be used at the corners of paths
426      * that are stroked.<BR>
427      * Allowed values are LINE_JOIN_MITER (Miter joins), LINE_JOIN_ROUND (Round joins) and LINE_JOIN_BEVEL (Bevel joins).<BR>
428      *
429      * @param       style       a value
430      */

431
432     public void setLineJoin(int style) {
433         if (style >= 0 && style <= 2) {
434             content.append(style).append(" j").append_i(separator);
435         }
436     }
437
438     /**
439      * Changes the <VAR>line width</VAR>.
440      * <P>
441      * The line width specifies the thickness of the line used to stroke a path and is measured
442      * in user space units.<BR>
443      *
444      * @param       w           a width
445      */

446
447     public void setLineWidth(float w) {
448         content.append(w).append(" w").append_i(separator);
449     }
450
451     /**
452      * Changes the <VAR>Miter limit</VAR>.
453      * <P>
454      * When two line segments meet at a sharp angle and mitered joins have been specified as the
455      * line join style, it is possible for the miter to extend far beyond the thickness of the line
456      * stroking path. The miter limit imposes a maximum on the ratio of the miter length to the line
457      * witdh. When the limit is exceeded, the join is converted from a miter to a bevel.<BR>
458      *
459      * @param       miterLimit      a miter limit
460      */

461
462     public void setMiterLimit(float miterLimit) {
463         if (miterLimit > 1) {
464             content.append(miterLimit).append(" M").append_i(separator);
465         }
466     }
467
468     /**
469      * Modify the current clipping path by intersecting it with the current path, using the
470      * nonzero winding number rule to determine which regions lie inside the clipping
471      * path.
472      */

473
474     public void clip() {
475         content.append("W").append_i(separator);
476     }
477
478     /**
479      * Modify the current clipping path by intersecting it with the current path, using the
480      * even-odd rule to determine which regions lie inside the clipping path.
481      */

482
483     public void eoClip() {
484         content.append("W*").append_i(separator);
485     }
486
487     /**
488      * Changes the currentgray tint for filling paths (device dependent colors!).
489      * <P>
490      * Sets the color space to <B>DeviceGray</B> (or the <B>DefaultGray</B> color space),
491      * and sets the gray tint to use for filling paths.</P>
492      *
493      * @param   gray    a value between 0 (black) and 1 (white)
494      */

495
496     public void setGrayFill(float gray) {
497         content.append(gray).append(" g").append_i(separator);
498     }
499
500     /**
501      * Changes the current gray tint for filling paths to black.
502      */

503
504     public void resetGrayFill() {
505         content.append("0 g").append_i(separator);
506     }
507
508     /**
509      * Changes the currentgray tint for stroking paths (device dependent colors!).
510      * <P>
511      * Sets the color space to <B>DeviceGray</B> (or the <B>DefaultGray</B> color space),
512      * and sets the gray tint to use for stroking paths.</P>
513      *
514      * @param   gray    a value between 0 (black) and 1 (white)
515      */

516
517     public void setGrayStroke(float gray) {
518         content.append(gray).append(" G").append_i(separator);
519     }
520
521     /**
522      * Changes the current gray tint for stroking paths to black.
523      */

524
525     public void resetGrayStroke() {
526         content.append("0 G").append_i(separator);
527     }
528
529     /**
530      * Helper to validate and write the RGB color components
531      * @param   red     the intensity of red. A value between 0 and 1
532      * @param   green   the intensity of green. A value between 0 and 1
533      * @param   blue    the intensity of blue. A value between 0 and 1
534      */

535     private void HelperRGB(float red, float green, float blue) {
536         PdfXConformanceImp.checkPDFXConformance(writer, PdfXConformanceImp.PDFXKEY_RGB, null);
537         if (red < 0)
538             red = 0.0f;
539         else if (red > 1.0f)
540             red = 1.0f;
541         if (green < 0)
542             green = 0.0f;
543         else if (green > 1.0f)
544             green = 1.0f;
545         if (blue < 0)
546             blue = 0.0f;
547         else if (blue > 1.0f)
548             blue = 1.0f;
549         content.append(red).append(' ').append(green).append(' ').append(blue);
550     }
551
552     /**
553      * Changes the current color for filling paths (device dependent colors!).
554      * <P>
555      * Sets the color space to <B>DeviceRGB</B> (or the <B>DefaultRGB</B> color space),
556      * and sets the color to use for filling paths.</P>
557      * <P>
558      * Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
559      * 1 (maximum intensity).</P>
560      *
561      * @param   red     the intensity of red. A value between 0 and 1
562      * @param   green   the intensity of green. A value between 0 and 1
563      * @param   blue    the intensity of blue. A value between 0 and 1
564      */

565
566     public void setRGBColorFillF(float red, float green, float blue) {
567         HelperRGB(red, green, blue);
568         content.append(" rg").append_i(separator);
569     }
570
571     /**
572      * Changes the current color for filling paths to black.
573      */

574
575     public void resetRGBColorFill() {
576         content.append("0 g").append_i(separator);
577     }
578
579     /**
580      * Changes the current color for stroking paths (device dependent colors!).
581      * <P>
582      * Sets the color space to <B>DeviceRGB</B> (or the <B>DefaultRGB</B> color space),
583      * and sets the color to use for stroking paths.</P>
584      * <P>
585      * Following the PDF manual, each operand must be a number between 0 (miniumum intensity) and
586      * 1 (maximum intensity).
587      *
588      * @param   red     the intensity of red. A value between 0 and 1
589      * @param   green   the intensity of green. A value between 0 and 1
590      * @param   blue    the intensity of blue. A value between 0 and 1
591      */

592
593     public void setRGBColorStrokeF(float red, float green, float blue) {
594         HelperRGB(red, green, blue);
595         content.append(" RG").append_i(separator);
596     }
597
598     /**
599      * Changes the current color for stroking paths to black.
600      *
601      */

602
603     public void resetRGBColorStroke() {
604         content.append("0 G").append_i(separator);
605     }
606
607     /**
608      * Helper to validate and write the CMYK color components.
609      *
610      * @param   cyan    the intensity of cyan. A value between 0 and 1
611      * @param   magenta the intensity of magenta. A value between 0 and 1
612      * @param   yellow  the intensity of yellow. A value between 0 and 1
613      * @param   black   the intensity of black. A value between 0 and 1
614      */

615     private void HelperCMYK(float cyan, float magenta, float yellow, float black) {
616         if (cyan < 0)
617             cyan = 0.0f;
618         else if (cyan > 1.0f)
619             cyan = 1.0f;
620         if (magenta < 0)
621             magenta = 0.0f;
622         else if (magenta > 1.0f)
623             magenta = 1.0f;
624         if (yellow < 0)
625             yellow = 0.0f;
626         else if (yellow > 1.0f)
627             yellow = 1.0f;
628         if (black < 0)
629             black = 0.0f;
630         else if (black > 1.0f)
631             black = 1.0f;
632         content.append(cyan).append(' ').append(magenta).append(' ').append(yellow).append(' ').append(black);
633     }
634
635     /**
636      * Changes the current color for filling paths (device dependent colors!).
637      * <P>
638      * Sets the color space to <B>DeviceCMYK</B> (or the <B>DefaultCMYK</B> color space),
639      * and sets the color to use for filling paths.</P>
640      * <P>
641      * Following the PDF manual, each operand must be a number between 0 (no ink) and
642      * 1 (maximum ink).</P>
643      *
644      * @param   cyan    the intensity of cyan. A value between 0 and 1
645      * @param   magenta the intensity of magenta. A value between 0 and 1
646      * @param   yellow  the intensity of yellow. A value between 0 and 1
647      * @param   black   the intensity of black. A value between 0 and 1
648      */

649
650     public void setCMYKColorFillF(float cyan, float magenta, float yellow, float black) {
651         HelperCMYK(cyan, magenta, yellow, black);
652         content.append(" k").append_i(separator);
653     }
654
655     /**
656      * Changes the current color for filling paths to black.
657      *
658      */

659
660     public void resetCMYKColorFill() {
661         content.append("0 0 0 1 k").append_i(separator);
662     }
663
664     /**
665      * Changes the current color for stroking paths (device dependent colors!).
666      * <P>
667      * Sets the color space to <B>DeviceCMYK</B> (or the <B>DefaultCMYK</B> color space),
668      * and sets the color to use for stroking paths.</P>
669      * <P>
670      * Following the PDF manual, each operand must be a number between 0 (miniumum intensity) and
671      * 1 (maximum intensity).
672      *
673      * @param   cyan    the intensity of cyan. A value between 0 and 1
674      * @param   magenta the intensity of magenta. A value between 0 and 1
675      * @param   yellow  the intensity of yellow. A value between 0 and 1
676      * @param   black   the intensity of black. A value between 0 and 1
677      */

678
679     public void setCMYKColorStrokeF(float cyan, float magenta, float yellow, float black) {
680         HelperCMYK(cyan, magenta, yellow, black);
681         content.append(" K").append_i(separator);
682     }
683
684     /**
685      * Changes the current color for stroking paths to black.
686      *
687      */

688
689     public void resetCMYKColorStroke() {
690         content.append("0 0 0 1 K").append_i(separator);
691     }
692
693     /**
694      * Move the current point <I>(x, y)</I>, omitting any connecting line segment.
695      *
696      * @param       x               new x-coordinate
697      * @param       y               new y-coordinate
698      */

699
700     public void moveTo(float x, float y) {
701         content.append(x).append(' ').append(y).append(" m").append_i(separator);
702     }
703
704     /**
705      * Appends a straight line segment from the current point <I>(x, y)</I>. The new current
706      * point is <I>(x, y)</I>.
707      *
708      * @param       x               new x-coordinate
709      * @param       y               new y-coordinate
710      */

711
712     public void lineTo(float x, float y) {
713         content.append(x).append(' ').append(y).append(" l").append_i(separator);
714     }
715
716     /**
717      * Appends a B&#xea;zier curve to the path, starting from the current point.
718      *
719      * @param       x1      x-coordinate of the first control point
720      * @param       y1      y-coordinate of the first control point
721      * @param       x2      x-coordinate of the second control point
722      * @param       y2      y-coordinate of the second control point
723      * @param       x3      x-coordinate of the ending point (= new current point)
724      * @param       y3      y-coordinate of the ending point (= new current point)
725      */

726
727     public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) {
728         content.append(x1).append(' ').append(y1).append(' ').append(x2).append(' ').append(y2).append(' ').append(x3).append(' ').append(y3).append(" c").append_i(separator);
729     }
730
731     /**
732      * Appends a B&#xea;zier curve to the path, starting from the current point.
733      *
734      * @param       x2      x-coordinate of the second control point
735      * @param       y2      y-coordinate of the second control point
736      * @param       x3      x-coordinate of the ending point (= new current point)
737      * @param       y3      y-coordinate of the ending point (= new current point)
738      */

739
740     public void curveTo(float x2, float y2, float x3, float y3) {
741         content.append(x2).append(' ').append(y2).append(' ').append(x3).append(' ').append(y3).append(" v").append_i(separator);
742     }
743
744     /**
745      * Appends a B&#xea;zier curve to the path, starting from the current point.
746      *
747      * @param       x1      x-coordinate of the first control point
748      * @param       y1      y-coordinate of the first control point
749      * @param       x3      x-coordinate of the ending point (= new current point)
750      * @param       y3      y-coordinate of the ending point (= new current point)
751      */

752
753     public void curveFromTo(float x1, float y1, float x3, float y3) {
754         content.append(x1).append(' ').append(y1).append(' ').append(x3).append(' ').append(y3).append(" y").append_i(separator);
755     }
756
757     /** Draws a circle. The endpoint will (x+r, y).
758      *
759      * @param x x center of circle
760      * @param y y center of circle
761      * @param r radius of circle
762      */

763     public void circle(float x, float y, float r) {
764         float b = 0.5523f;
765         moveTo(x + r, y);
766         curveTo(x + r, y + r * b, x + r * b, y + r, x, y + r);
767         curveTo(x - r * b, y + r, x - r, y + r * b, x - r, y);
768         curveTo(x - r, y - r * b, x - r * b, y - r, x, y - r);
769         curveTo(x + r * b, y - r, x + r, y - r * b, x + r, y);
770     }
771
772
773
774     /**
775      * Adds a rectangle to the current path.
776      *
777      * @param       x       x-coordinate of the starting point
778      * @param       y       y-coordinate of the starting point
779      * @param       w       width
780      * @param       h       height
781      */

782
783     public void rectangle(float x, float y, float w, float h) {
784         content.append(x).append(' ').append(y).append(' ').append(w).append(' ').append(h).append(" re").append_i(separator);
785     }
786
787     private boolean compareColors(Color c1, Color c2) {
788         if (c1 == null && c2 == null)
789             return true;
790         if (c1 == null || c2 == null)
791             return false;
792         if (c1 instanceof ExtendedColor)
793             return c1.equals(c2);
794         return c2.equals(c1);
795     }
796
797     /**
798      * Adds a variable width border to the current path.
799      * Only use if {@link com.lowagie.text.Rectangle#isUseVariableBorders() Rectangle.isUseVariableBorders}
800      * = true.
801      * @param rect a <CODE>Rectangle</CODE>
802      */

803     public void variableRectangle(Rectangle rect) {
804         float t = rect.getTop();
805         float b = rect.getBottom();
806         float r = rect.getRight();
807         float l = rect.getLeft();
808         float wt = rect.getBorderWidthTop();
809         float wb = rect.getBorderWidthBottom();
810         float wr = rect.getBorderWidthRight();
811         float wl = rect.getBorderWidthLeft();
812         Color ct = rect.getBorderColorTop();
813         Color cb = rect.getBorderColorBottom();
814         Color cr = rect.getBorderColorRight();
815         Color cl = rect.getBorderColorLeft();
816         saveState();
817         setLineCap(PdfContentByte.LINE_CAP_BUTT);
818         setLineJoin(PdfContentByte.LINE_JOIN_MITER);
819         float clw = 0;
820         boolean cdef = false;
821         Color ccol = null;
822         boolean cdefi = false;
823         Color cfil = null;
824         // draw top
825         if (wt > 0) {
826             setLineWidth(clw = wt);
827             cdef = true;
828             if (ct == null)
829                 resetRGBColorStroke();
830             else
831                 setColorStroke(ct);
832             ccol = ct;
833             moveTo(l, t - wt / 2f);
834             lineTo(r, t - wt / 2f);
835             stroke();
836         }
837
838         // Draw bottom
839         if (wb > 0) {
840             if (wb != clw)
841                 setLineWidth(clw = wb);
842             if (!cdef || !compareColors(ccol, cb)) {
843                 cdef = true;
844                 if (cb == null)
845                     resetRGBColorStroke();
846                 else
847                     setColorStroke(cb);
848                 ccol = cb;
849             }
850             moveTo(r, b + wb / 2f);
851             lineTo(l, b + wb / 2f);
852             stroke();
853         }
854
855         // Draw right
856         if (wr > 0) {
857             if (wr != clw)
858                 setLineWidth(clw = wr);
859             if (!cdef || !compareColors(ccol, cr)) {
860                 cdef = true;
861                 if (cr == null)
862                     resetRGBColorStroke();
863                 else
864                     setColorStroke(cr);
865                 ccol = cr;
866             }
867             boolean bt = compareColors(ct, cr);
868             boolean bb = compareColors(cb, cr);
869             moveTo(r - wr / 2f, bt ? t : t - wt);
870             lineTo(r - wr / 2f, bb ? b : b + wb);
871             stroke();
872             if (!bt || !bb) {
873                 cdefi = true;
874                 if (cr == null)
875                     resetRGBColorFill();
876                 else
877                     setColorFill(cr);
878                 cfil = cr;
879                 if (!bt) {
880                     moveTo(r, t);
881                     lineTo(r, t - wt);
882                     lineTo(r - wr, t - wt);
883                     fill();
884                 }
885                 if (!bb) {
886                     moveTo(r, b);
887                     lineTo(r, b + wb);
888                     lineTo(r - wr, b + wb);
889                     fill();
890                 }
891             }
892         }
893
894         // Draw Left
895         if (wl > 0) {
896             if (wl != clw)
897                 setLineWidth(wl);
898             if (!cdef || !compareColors(ccol, cl)) {
899                 if (cl == null)
900                     resetRGBColorStroke();
901                 else
902                     setColorStroke(cl);
903             }
904             boolean bt = compareColors(ct, cl);
905             boolean bb = compareColors(cb, cl);
906             moveTo(l + wl / 2f, bt ? t : t - wt);
907             lineTo(l + wl / 2f, bb ? b : b + wb);
908             stroke();
909             if (!bt || !bb) {
910                 if (!cdefi || !compareColors(cfil, cl)) {
911                     if (cl == null)
912                         resetRGBColorFill();
913                     else
914                         setColorFill(cl);
915                 }
916                 if (!bt) {
917                     moveTo(l, t);
918                     lineTo(l, t - wt);
919                     lineTo(l + wl, t - wt);
920                     fill();
921                 }
922                 if (!bb) {
923                     moveTo(l, b);
924                     lineTo(l, b + wb);
925                     lineTo(l + wl, b + wb);
926                     fill();
927                 }
928             }
929         }
930         restoreState();
931     }
932
933     /**
934      * Adds a border (complete or partially) to the current path..
935      *
936      * @param       rectangle       a <CODE>Rectangle</CODE>
937      */

938
939     public void rectangle(Rectangle rectangle) {
940         // the coordinates of the border are retrieved
941         float x1 = rectangle.getLeft();
942         float y1 = rectangle.getBottom();
943         float x2 = rectangle.getRight();
944         float y2 = rectangle.getTop();
945
946         // the backgroundcolor is set
947         Color background = rectangle.getBackgroundColor();
948         if (background != null) {
949             setColorFill(background);
950             rectangle(x1, y1, x2 - x1, y2 - y1);
951             fill();
952             resetRGBColorFill();
953         }
954
955         // if the element hasn't got any borders, nothing is added
956         if (! rectangle.hasBorders()) {
957             return;
958         }
959
960         // if any of the individual border colors are set
961         // we draw the borders all around using the
962         // different colors
963         if (rectangle.isUseVariableBorders()) {
964             variableRectangle(rectangle);
965         }
966         else {
967             // the width is set to the width of the element
968             if (rectangle.getBorderWidth() != Rectangle.UNDEFINED) {
969                 setLineWidth(rectangle.getBorderWidth());
970             }
971
972             // the color is set to the color of the element
973             Color color = rectangle.getBorderColor();
974             if (color != null) {
975                 setColorStroke(color);
976             }
977
978             // if the box is a rectangle, it is added as a rectangle
979             if (rectangle.hasBorder(Rectangle.BOX)) {
980                rectangle(x1, y1, x2 - x1, y2 - y1);
981             }
982             // if the border isn't a rectangle, the different sides are added apart
983             else {
984                 if (rectangle.hasBorder(Rectangle.RIGHT)) {
985                     moveTo(x2, y1);
986                     lineTo(x2, y2);
987                 }
988                 if (rectangle.hasBorder(Rectangle.LEFT)) {
989                     moveTo(x1, y1);
990                     lineTo(x1, y2);
991                 }
992                 if (rectangle.hasBorder(Rectangle.BOTTOM)) {
993                     moveTo(x1, y1);
994                     lineTo(x2, y1);
995                 }
996                 if (rectangle.hasBorder(Rectangle.TOP)) {
997                     moveTo(x1, y2);
998                     lineTo(x2, y2);
999                 }
1000             }
1001
1002             stroke();
1003
1004             if (color != null) {
1005                 resetRGBColorStroke();
1006             }
1007         }
1008     }
1009
1010     /**
1011      * Closes the current subpath by appending a straight line segment from the current point
1012      * to the starting point of the subpath.
1013      */

1014
1015     public void closePath() {
1016         content.append("h").append_i(separator);
1017     }
1018
1019     /**
1020      * Ends the path without filling or stroking it.
1021      */

1022
1023     public void newPath() {
1024         content.append("n").append_i(separator);
1025     }
1026
1027     /**
1028      * Strokes the path.
1029      */

1030
1031     public void stroke() {
1032         content.append("S").append_i(separator);
1033     }
1034
1035     /**
1036      * Closes the path and strokes it.
1037      */

1038
1039     public void closePathStroke() {
1040         content.append("s").append_i(separator);
1041     }
1042
1043     /**
1044      * Fills the path, using the non-zero winding number rule to determine the region to fill.
1045      */

1046
1047     public void fill() {
1048         content.append("f").append_i(separator);
1049     }
1050
1051     /**
1052      * Fills the path, using the even-odd rule to determine the region to fill.
1053      */

1054
1055     public void eoFill() {
1056         content.append("f*").append_i(separator);
1057     }
1058
1059     /**
1060      * Fills the path using the non-zero winding number rule to determine the region to fill and strokes it.
1061      */

1062
1063     public void fillStroke() {
1064         content.append("B").append_i(separator);
1065     }
1066
1067     /**
1068      * Closes the path, fills it using the non-zero winding number rule to determine the region to fill and strokes it.
1069      */

1070
1071     public void closePathFillStroke() {
1072         content.append("b").append_i(separator);
1073     }
1074
1075     /**
1076      * Fills the path, using the even-odd rule to determine the region to fill and strokes it.
1077      */

1078
1079     public void eoFillStroke() {
1080         content.append("B*").append_i(separator);
1081     }
1082
1083     /**
1084      * Closes the path, fills it using the even-odd rule to determine the region to fill and strokes it.
1085      */

1086
1087     public void closePathEoFillStroke() {
1088         content.append("b*").append_i(separator);
1089     }
1090
1091     /**
1092      * Adds an <CODE>Image</CODE> to the page. The <CODE>Image</CODE> must have
1093      * absolute positioning.
1094      * @param image the <CODE>Image</CODE> object
1095      * @throws DocumentException if the <CODE>Image</CODE> does not have absolute positioning
1096      */

1097     public void addImage(Image image) throws DocumentException {
1098         addImage(image, false);
1099     }
1100
1101     /**
1102      * Adds an <CODE>Image</CODE> to the page. The <CODE>Image</CODE> must have
1103      * absolute positioning. The image can be placed inline.
1104      * @param image the <CODE>Image</CODE> object
1105      * @param inlineImage <CODE>true</CODE> to place this image inline, <CODE>false</CODE> otherwise
1106      * @throws DocumentException if the <CODE>Image</CODE> does not have absolute positioning
1107      */

1108     public void addImage(Image image, boolean inlineImage) throws DocumentException {
1109         if (!image.hasAbsoluteY())
1110             throw new DocumentException("The image must have absolute positioning.");
1111         float matrix[] = image.matrix();
1112         matrix[Image.CX] = image.getAbsoluteX() - matrix[Image.CX];
1113         matrix[Image.CY] = image.getAbsoluteY() - matrix[Image.CY];
1114         addImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], inlineImage);
1115     }
1116
1117     /**
1118      * Adds an <CODE>Image</CODE> to the page. The positioning of the <CODE>Image</CODE>
1119      * is done with the transformation matrix. To position an <CODE>image</CODE> at (x,y)
1120      * use addImage(image, image_width, 0, 0, image_height, x, y).
1121      * @param image the <CODE>Image</CODE> object
1122      * @param a an element of the transformation matrix
1123      * @param b an element of the transformation matrix
1124      * @param c an element of the transformation matrix
1125      * @param d an element of the transformation matrix
1126      * @param e an element of the transformation matrix
1127      * @param f an element of the transformation matrix
1128      * @throws DocumentException on error
1129      */

1130     public void addImage(Image image, float a, float b, float c, float d, float e, float f) throws DocumentException {
1131         addImage(image, a, b, c, d, e, f, false);
1132     }
1133
1134     /**
1135      * Adds an <CODE>Image</CODE> to the page. The positioning of the <CODE>Image</CODE>
1136      * is done with the transformation matrix. To position an <CODE>image</CODE> at (x,y)
1137      * use addImage(image, image_width, 0, 0, image_height, x, y). The image can be placed inline.
1138      * @param image the <CODE>Image</CODE> object
1139      * @param a an element of the transformation matrix
1140      * @param b an element of the transformation matrix
1141      * @param c an element of the transformation matrix
1142      * @param d an element of the transformation matrix
1143      * @param e an element of the transformation matrix
1144      * @param f an element of the transformation matrix
1145      * @param inlineImage <CODE>true</CODE> to place this image inline, <CODE>false</CODE> otherwise
1146      * @throws DocumentException on error
1147      */

1148     public void addImage(Image image, float a, float b, float c, float d, float e, float f, boolean inlineImage) throws DocumentException {
1149         try {
1150             if (image.getLayer() != null)
1151                 beginLayer(image.getLayer());
1152             if (image.isImgTemplate()) {
1153                 writer.addDirectImageSimple(image);
1154                 PdfTemplate template = image.getTemplateData();
1155                 float w = template.getWidth();
1156                 float h = template.getHeight();
1157                 addTemplate(template, a / w, b / w, c / h, d / h, e, f);
1158             }
1159             else {
1160                 content.append("q ");
1161                 content.append(a).append(' ');
1162                 content.append(b).append(' ');
1163                 content.append(c).append(' ');
1164                 content.append(d).append(' ');
1165                 content.append(e).append(' ');
1166                 content.append(f).append(" cm");
1167                 if (inlineImage) {
1168                     content.append("\nBI\n");
1169                     PdfImage pimage = new PdfImage(image, ""null);
1170                     if (image instanceof ImgJBIG2) {
1171                         byte[] globals = ((ImgJBIG2)image).getGlobalBytes();
1172                         if (globals != null) {
1173                             PdfDictionary decodeparms = new PdfDictionary();
1174                             decodeparms.put(PdfName.JBIG2GLOBALS, writer.getReferenceJBIG2Globals(globals));
1175                             pimage.put(PdfName.DECODEPARMS, decodeparms);
1176                         }
1177                     }
1178                     for (Iterator it = pimage.getKeys().iterator(); it.hasNext();) {
1179                         PdfName key = (PdfName)it.next();
1180                         PdfObject value = pimage.get(key);
1181                         String s = (String)abrev.get(key);
1182                         if (s == null)
1183                             continue;
1184                         content.append(s);
1185                         boolean check = true;
1186                         if (key.equals(PdfName.COLORSPACE) && value.isArray()) {
1187                             PdfArray ar = (PdfArray)value;
1188                             if (ar.size() == 4 
1189                                 && PdfName.INDEXED.equals(ar.getAsName(0)) 
1190                                 && ar.getPdfObject(1).isName()
1191                                 && ar.getPdfObject(2).isNumber()
1192                                 && ar.getPdfObject(3).isString()
1193                             ) {
1194                                 check = false;
1195                             }
1196
1197                         }
1198                         if (check && key.equals(PdfName.COLORSPACE) && !value.isName()) {
1199                             PdfName cs = writer.getColorspaceName();
1200                             PageResources prs = getPageResources();
1201                             prs.addColor(cs, writer.addToBody(value).getIndirectReference());
1202                             value = cs;
1203                         }
1204                         value.toPdf(null, content);
1205                         content.append('\n');
1206                     }
1207                     content.append("ID\n");
1208                     pimage.writeContent(content);
1209                     content.append("\nEI\nQ").append_i(separator);
1210                 }
1211                 else {
1212                     PdfName name;
1213                     PageResources prs = getPageResources();
1214                     Image maskImage = image.getImageMask();
1215                     if (maskImage != null) {
1216                         name = writer.addDirectImageSimple(maskImage);
1217                         prs.addXObject(name, writer.getImageReference(name));
1218                     }
1219                     name = writer.addDirectImageSimple(image);
1220                     name = prs.addXObject(name, writer.getImageReference(name));
1221                     content.append(' ').append(name.getBytes()).append(" Do Q").append_i(separator);
1222                 }
1223             }
1224             if (image.hasBorders()) {
1225                 saveState();
1226                 float w = image.getWidth();
1227                 float h = image.getHeight();
1228                 concatCTM(a / w, b / w, c / h, d / h, e, f);
1229                 rectangle(image);
1230                 restoreState();
1231             }
1232             if (image.getLayer() != null)
1233                 endLayer();
1234             Annotation annot = image.getAnnotation();
1235             if (annot == null)
1236                 return;
1237             float[] r = new float[unitRect.length];
1238             for (int k = 0; k < unitRect.length; k += 2) {
1239                 r[k] = a * unitRect[k] + c * unitRect[k + 1] + e;
1240                 r[k + 1] = b * unitRect[k] + d * unitRect[k + 1] + f;
1241             }
1242             float llx = r[0];
1243             float lly = r[1];
1244             float urx = llx;
1245             float ury = lly;
1246             for (int k = 2; k < r.length; k += 2) {
1247                 llx = Math.min(llx, r[k]);
1248                 lly = Math.min(lly, r[k + 1]);
1249                 urx = Math.max(urx, r[k]);
1250                 ury = Math.max(ury, r[k + 1]);
1251             }
1252             annot = new Annotation(annot);
1253             annot.setDimensions(llx, lly, urx, ury);
1254             PdfAnnotation an = PdfAnnotationsImp.convertAnnotation(writer, annot, new Rectangle(llx, lly, urx, ury));
1255             if (an == null)
1256                 return;
1257             addAnnotation(an);
1258         }
1259         catch (Exception ee) {
1260             throw new DocumentException(ee);
1261         }
1262     }
1263
1264     /**
1265      * Makes this <CODE>PdfContentByte</CODE> empty.
1266      * Calls <code>reset( true )</code>
1267      */

1268     public void reset() {
1269         reset( true );
1270     }
1271
1272     /**
1273      * Makes this <CODE>PdfContentByte</CODE> empty.
1274      * @param validateContent will call <code>sanityCheck()</code> if true.
1275      * @since 2.1.6
1276      */

1277     public void reset( boolean validateContent ) {
1278         content.reset();
1279         if (validateContent) {
1280             sanityCheck();
1281         }
1282         state = new GraphicState();
1283     }
1284
1285     
1286     /**
1287      * Starts the writing of text.
1288      */

1289     public void beginText() {
1290         if (inText) {
1291             throw new IllegalPdfSyntaxException("Unbalanced begin/end text operators." );
1292         }
1293         inText = true;
1294         state.xTLM = 0;
1295         state.yTLM = 0;
1296         content.append("BT").append_i(separator);
1297     }
1298
1299     /**
1300      * Ends the writing of text and makes the current font invalid.
1301      */

1302     public void endText() {
1303         if (!inText) {
1304             throw new IllegalPdfSyntaxException("Unbalanced begin/end text operators." );
1305         }
1306         inText = false;
1307         content.append("ET").append_i(separator);
1308     }
1309
1310     /**
1311      * Saves the graphic state. <CODE>saveState</CODE> and
1312      * <CODE>restoreState</CODE> must be balanced.
1313      */

1314     public void saveState() {
1315         content.append("q").append_i(separator);
1316         stateList.add(new GraphicState(state));
1317     }
1318
1319     /**
1320      * Restores the graphic state. <CODE>saveState</CODE> and
1321      * <CODE>restoreState</CODE> must be balanced.
1322      */

1323     public void restoreState() {
1324         content.append("Q").append_i(separator);
1325         int idx = stateList.size() - 1;
1326         if (idx < 0)
1327             throw new IllegalPdfSyntaxException("Unbalanced save/restore state operators.");
1328         state = (GraphicState)stateList.get(idx);
1329         stateList.remove(idx);
1330     }
1331
1332     /**
1333      * Sets the character spacing parameter.
1334      *
1335      * @param       charSpace           a parameter
1336      */

1337     public void setCharacterSpacing(float charSpace) {
1338         state.charSpace = charSpace;
1339         content.append(charSpace).append(" Tc").append_i(separator);
1340     }
1341
1342     /**
1343      * Sets the word spacing parameter.
1344      *
1345      * @param       wordSpace           a parameter
1346      */

1347     public void setWordSpacing(float wordSpace) {
1348         state.wordSpace = wordSpace;
1349         content.append(wordSpace).append(" Tw").append_i(separator);
1350     }
1351
1352     /**
1353      * Sets the horizontal scaling parameter.
1354      *
1355      * @param       scale               a parameter
1356      */

1357     public void setHorizontalScaling(float scale) {
1358         state.scale = scale;
1359         content.append(scale).append(" Tz").append_i(separator);
1360     }
1361
1362     /**
1363      * Sets the text leading parameter.
1364      * <P>
1365      * The leading parameter is measured in text space units. It specifies the vertical distance
1366      * between the baselines of adjacent lines of text.</P>
1367      *
1368      * @param       leading         the new leading
1369      */

1370     public void setLeading(float leading) {
1371         state.leading = leading;
1372         content.append(leading).append(" TL").append_i(separator);
1373     }
1374
1375     /**
1376      * Set the font and the size for the subsequent text writing.
1377      *
1378      * @param bf the font
1379      * @param size the font size in points
1380      */

1381     public void setFontAndSize(BaseFont bf, float size) {
1382         checkWriter();
1383         if (size < 0.0001f && size > -0.0001f)
1384             throw new IllegalArgumentException("Font size too small: " + size);
1385         state.size = size;
1386         state.fontDetails = writer.addSimple(bf);
1387         PageResources prs = getPageResources();
1388         PdfName name = state.fontDetails.getFontName();
1389         name = prs.addFont(name, state.fontDetails.getIndirectReference());
1390         content.append(name.getBytes()).append(' ').append(size).append(" Tf").append_i(separator);
1391     }
1392
1393     /**
1394      * Sets the text rendering parameter.
1395      *
1396      * @param       rendering               a parameter
1397      */

1398     public void setTextRenderingMode(int rendering) {
1399         content.append(rendering).append(" Tr").append_i(separator);
1400     }
1401
1402     /**
1403      * Sets the text rise parameter.
1404      * <P>
1405      * This allows to write text in subscript or superscript mode.</P>
1406      *
1407      * @param       rise                a parameter
1408      */

1409     public void setTextRise(float rise) {
1410         content.append(rise).append(" Ts").append_i(separator);
1411     }
1412
1413     /**
1414      * A helper to insert into the content stream the <CODE>text</CODE>
1415      * converted to bytes according to the font's encoding.
1416      *
1417      * @param text the text to write
1418      */

1419     private void showText2(String text) {
1420         if (state.fontDetails == null)
1421             throw new NullPointerException("Font and size must be set before writing any text");
1422         byte b[] = state.fontDetails.convertToBytes(text);
1423         escapeString(b, content);
1424     }
1425
1426     /**
1427      * Shows the <CODE>text</CODE>.
1428      *
1429      * @param text the text to write
1430      */

1431     public void showText(String text) {
1432         showText2(text);
1433         content.append("Tj").append_i(separator);
1434     }
1435
1436     /**
1437      * Constructs a kern array for a text in a certain font
1438      * @param text the text
1439      * @param font the font
1440      * @return a PdfTextArray
1441      */

1442     public static PdfTextArray getKernArray(String text, BaseFont font) {
1443         PdfTextArray pa = new PdfTextArray();
1444         StringBuffer acc = new StringBuffer();
1445         int len = text.length() - 1;
1446         char c[] = text.toCharArray();
1447         if (len >= 0)
1448             acc.append(c, 0, 1);
1449         for (int k = 0; k < len; ++k) {
1450             char c2 = c[k + 1];
1451             int kern = font.getKerning(c[k], c2);
1452             if (kern == 0) {
1453                 acc.append(c2);
1454             }
1455             else {
1456                 pa.add(acc.toString());
1457                 acc.setLength(0);
1458                 acc.append(c, k + 1, 1);
1459                 pa.add(-kern);
1460             }
1461         }
1462         pa.add(acc.toString());
1463         return pa;
1464     }
1465
1466     /**
1467      * Shows the <CODE>text</CODE> kerned.
1468      *
1469      * @param text the text to write
1470      */

1471     public void showTextKerned(String text) {
1472         if (state.fontDetails == null)
1473             throw new NullPointerException("Font and size must be set before writing any text");
1474         BaseFont bf = state.fontDetails.getBaseFont();
1475         if (bf.hasKernPairs())
1476             showText(getKernArray(text, bf));
1477         else
1478             showText(text);
1479     }
1480
1481     /**
1482      * Moves to the next line and shows <CODE>text</CODE>.
1483      *
1484      * @param text the text to write
1485      */

1486     public void newlineShowText(String text) {
1487         state.yTLM -= state.leading;
1488         showText2(text);
1489         content.append("'").append_i(separator);
1490     }
1491
1492     /**
1493      * Moves to the next line and shows text string, using the given values of the character and word spacing parameters.
1494      *
1495      * @param       wordSpacing     a parameter
1496      * @param       charSpacing     a parameter
1497      * @param text the text to write
1498      */

1499     public void newlineShowText(float wordSpacing, float charSpacing, String text) {
1500         state.yTLM -= state.leading;
1501         content.append(wordSpacing).append(' ').append(charSpacing);
1502         showText2(text);
1503         content.append("\"").append_i(separator);
1504
1505         // The " operator sets charSpace and wordSpace into graphics state
1506         // (cfr PDF reference v1.6, table 5.6)
1507         state.charSpace = charSpacing;
1508         state.wordSpace = wordSpacing;
1509     }
1510
1511     /**
1512      * Changes the text matrix.
1513      * <P>
1514      * Remark: this operation also initializes the current point position.</P>
1515      *
1516      * @param       a           operand 1,1 in the matrix
1517      * @param       b           operand 1,2 in the matrix
1518      * @param       c           operand 2,1 in the matrix
1519      * @param       d           operand 2,2 in the matrix
1520      * @param       x           operand 3,1 in the matrix
1521      * @param       y           operand 3,2 in the matrix
1522      */

1523     public void setTextMatrix(float a, float b, float c, float d, float x, float y) {
1524         state.xTLM = x;
1525         state.yTLM = y;
1526         content.append(a).append(' ').append(b).append_i(' ')
1527         .append(c).append_i(' ').append(d).append_i(' ')
1528         .append(x).append_i(' ').append(y).append(" Tm").append_i(separator);
1529     }
1530
1531     /**
1532      * Changes the text matrix. The first four parameters are {1,0,0,1}.
1533      * <P>
1534      * Remark: this operation also initializes the current point position.</P>
1535      *
1536      * @param       x           operand 3,1 in the matrix
1537      * @param       y           operand 3,2 in the matrix
1538      */

1539     public void setTextMatrix(float x, float y) {
1540         setTextMatrix(1, 0, 0, 1, x, y);
1541     }
1542
1543     /**
1544      * Moves to the start of the next line, offset from the start of the current line.
1545      *
1546      * @param       x           x-coordinate of the new current point
1547      * @param       y           y-coordinate of the new current point
1548      */

1549     public void moveText(float x, float y) {
1550         state.xTLM += x;
1551         state.yTLM += y;
1552         content.append(x).append(' ').append(y).append(" Td").append_i(separator);
1553     }
1554
1555     /**
1556      * Moves to the start of the next line, offset from the start of the current line.
1557      * <P>
1558      * As a side effect, this sets the leading parameter in the text state.</P>
1559      *
1560      * @param       x           offset of the new current point
1561      * @param       y           y-coordinate of the new current point
1562      */

1563     public void moveTextWithLeading(float x, float y) {
1564         state.xTLM += x;
1565         state.yTLM += y;
1566         state.leading = -y;
1567         content.append(x).append(' ').append(y).append(" TD").append_i(separator);
1568     }
1569
1570     /**
1571      * Moves to the start of the next line.
1572      */

1573     public void newlineText() {
1574         state.yTLM -= state.leading;
1575         content.append("T*").append_i(separator);
1576     }
1577
1578     /**
1579      * Gets the size of this content.
1580      *
1581      * @return the size of the content
1582      */

1583     int size() {
1584         return content.size();
1585     }
1586
1587     /**
1588      * Escapes a <CODE>byte</CODE> array according to the PDF conventions.
1589      *
1590      * @param b the <CODE>byte</CODE> array to escape
1591      * @return an escaped <CODE>byte</CODE> array
1592      */

1593     static byte[] escapeString(byte b[]) {
1594         ByteBuffer content = new ByteBuffer();
1595         escapeString(b, content);
1596         return content.toByteArray();
1597     }
1598
1599     /**
1600      * Escapes a <CODE>byte</CODE> array according to the PDF conventions.
1601      *
1602      * @param b the <CODE>byte</CODE> array to escape
1603      * @param content the content
1604      */

1605     static void escapeString(byte b[], ByteBuffer content) {
1606         content.append_i('(');
1607         for (int k = 0; k < b.length; ++k) {
1608             byte c = b[k];
1609             switch (c) {
1610                 case '\r':
1611                     content.append("\\r");
1612                     break;
1613                 case '\n':
1614                     content.append("\\n");
1615                     break;
1616                 case '\t':
1617                     content.append("\\t");
1618                     break;
1619                 case '\b':
1620                     content.append("\\b");
1621                     break;
1622                 case '\f':
1623                     content.append("\\f");
1624                     break;
1625                 case '(':
1626                 case ')':
1627                 case '\\':
1628                     content.append_i('\\').append_i(c);
1629                     break;
1630                 default:
1631                     content.append_i(c);
1632             }
1633         }
1634         content.append(")");
1635     }
1636
1637     /**
1638      * Adds a named outline to the document.
1639      *
1640      * @param outline the outline
1641      * @param name the name for the local destination
1642      */

1643     public void addOutline(PdfOutline outline, String name) {
1644         checkWriter();
1645         pdf.addOutline(outline, name);
1646     }
1647     /**
1648      * Gets the root outline.
1649      *
1650      * @return the root outline
1651      */

1652     public PdfOutline getRootOutline() {
1653         checkWriter();
1654         return pdf.getRootOutline();
1655     }
1656
1657     /**
1658      * Computes the width of the given string taking in account
1659      * the current values of "Character spacing""Word Spacing"
1660      * and "Horizontal Scaling".
1661      * The additional spacing is not computed for the last character
1662      * of the string.
1663      * @param text the string to get width of
1664      * @param kerned the kerning option
1665      * @return the width
1666      */

1667
1668     public float getEffectiveStringWidth(String text, boolean kerned) {
1669         BaseFont bf = state.fontDetails.getBaseFont();
1670
1671         float w;
1672         if (kerned)
1673             w = bf.getWidthPointKerned(text, state.size);
1674         else
1675             w = bf.getWidthPoint(text, state.size);
1676
1677         if (state.charSpace != 0.0f && text.length() > 1) {
1678             w += state.charSpace * (text.length() -1);
1679         }
1680
1681         int ft = bf.getFontType();
1682         if (state.wordSpace != 0.0f && (ft == BaseFont.FONT_TYPE_T1 || ft == BaseFont.FONT_TYPE_TT || ft == BaseFont.FONT_TYPE_T3)) {
1683             for (int i = 0; i < (text.length() -1); i++) {
1684                 if (text.charAt(i) == ' ')
1685                     w += state.wordSpace;
1686             }
1687         }
1688         if (state.scale != 100.0)
1689             w = (w * state.scale) / 100.0f;
1690
1691         //System.out.println("String width = " + Float.toString(w));
1692         return w;
1693     }
1694
1695     /**
1696      * Shows text right, left or center aligned with rotation.
1697      * @param alignment the alignment can be ALIGN_CENTER, ALIGN_RIGHT or ALIGN_LEFT
1698      * @param text the text to show
1699      * @param x the x pivot position
1700      * @param y the y pivot position
1701      * @param rotation the rotation to be applied in degrees counterclockwise
1702      */

1703     public void showTextAligned(int alignment, String text, float x, float y, float rotation) {
1704         showTextAligned(alignment, text, x, y, rotation, false);
1705     }
1706
1707     private void showTextAligned(int alignment, String text, float x, float y, float rotation, boolean kerned) {
1708         if (state.fontDetails == null)
1709             throw new NullPointerException("Font and size must be set before writing any text");
1710         if (rotation == 0) {
1711             switch (alignment) {
1712                 case ALIGN_CENTER:
1713                     x -= getEffectiveStringWidth(text, kerned) / 2;
1714                     break;
1715                 case ALIGN_RIGHT:
1716                     x -= getEffectiveStringWidth(text, kerned);
1717                     break;
1718             }
1719             setTextMatrix(x, y);
1720             if (kerned)
1721                 showTextKerned(text);
1722             else
1723                 showText(text);
1724         }
1725         else {
1726             double alpha = rotation * Math.PI / 180.0;
1727             float cos = (float)Math.cos(alpha);
1728             float sin = (float)Math.sin(alpha);
1729             float len;
1730             switch (alignment) {
1731                 case ALIGN_CENTER:
1732                     len = getEffectiveStringWidth(text, kerned) / 2;
1733                     x -=  len * cos;
1734                     y -=  len * sin;
1735                     break;
1736                 case ALIGN_RIGHT:
1737                     len = getEffectiveStringWidth(text, kerned);
1738                     x -=  len * cos;
1739                     y -=  len * sin;
1740                     break;
1741             }
1742             setTextMatrix(cos, sin, -sin, cos, x, y);
1743             if (kerned)
1744                 showTextKerned(text);
1745             else
1746                 showText(text);
1747             setTextMatrix(0f, 0f);
1748         }
1749     }
1750
1751     /**
1752      * Shows text kerned right, left or center aligned with rotation.
1753      * @param alignment the alignment can be ALIGN_CENTER, ALIGN_RIGHT or ALIGN_LEFT
1754      * @param text the text to show
1755      * @param x the x pivot position
1756      * @param y the y pivot position
1757      * @param rotation the rotation to be applied in degrees counterclockwise
1758      */

1759     public void showTextAlignedKerned(int alignment, String text, float x, float y, float rotation) {
1760         showTextAligned(alignment, text, x, y, rotation, true);
1761     }
1762
1763     /**
1764      * Concatenate a matrix to the current transformation matrix.
1765      * @param a an element of the transformation matrix
1766      * @param b an element of the transformation matrix
1767      * @param c an element of the transformation matrix
1768      * @param d an element of the transformation matrix
1769      * @param e an element of the transformation matrix
1770      * @param f an element of the transformation matrix
1771      **/

1772     public void concatCTM(float a, float b, float c, float d, float e, float f) {
1773         content.append(a).append(' ').append(b).append(' ').append(c).append(' ');
1774         content.append(d).append(' ').append(e).append(' ').append(f).append(" cm").append_i(separator);
1775     }
1776
1777     /**
1778      * Generates an array of bezier curves to draw an arc.
1779      * <P>
1780      * (x1, y1) and (x2, y2) are the corners of the enclosing rectangle.
1781      * Angles, measured in degrees, start with 0 to the right (the positive X
1782      * axis) and increase counter-clockwise.  The arc extends from startAng
1783      * to startAng+extent.  I.e. startAng=0 and extent=180 yields an openside-down
1784      * semi-circle.
1785      * <P>
1786      * The resulting coordinates are of the form float[]{x1,y1,x2,y2,x3,y3, x4,y4}
1787      * such that the curve goes from (x1, y1) to (x4, y4) with (x2, y2) and
1788      * (x3, y3) as their respective Bezier control points.
1789      * <P>
1790      * Note: this code was taken from ReportLab (www.reportlab.org), an excellent
1791      * PDF generator for Python (BSD license: http://www.reportlab.org/devfaq.html#1.3 ).
1792      *
1793      * @param x1 a corner of the enclosing rectangle
1794      * @param y1 a corner of the enclosing rectangle
1795      * @param x2 a corner of the enclosing rectangle
1796      * @param y2 a corner of the enclosing rectangle
1797      * @param startAng starting angle in degrees
1798      * @param extent angle extent in degrees
1799      * @return a list of float[] with the bezier curves
1800      */

1801     public static ArrayList bezierArc(float x1, float y1, float x2, float y2, float startAng, float extent) {
1802         float tmp;
1803         if (x1 > x2) {
1804             tmp = x1;
1805             x1 = x2;
1806             x2 = tmp;
1807         }
1808         if (y2 > y1) {
1809             tmp = y1;
1810             y1 = y2;
1811             y2 = tmp;
1812         }
1813
1814         float fragAngle;
1815         int Nfrag;
1816         if (Math.abs(extent) <= 90f) {
1817             fragAngle = extent;
1818             Nfrag = 1;
1819         }
1820         else {
1821             Nfrag = (int)(Math.ceil(Math.abs(extent)/90f));
1822             fragAngle = extent / Nfrag;
1823         }
1824         float x_cen = (x1+x2)/2f;
1825         float y_cen = (y1+y2)/2f;
1826         float rx = (x2-x1)/2f;
1827         float ry = (y2-y1)/2f;
1828         float halfAng = (float)(fragAngle * Math.PI / 360.);
1829         float kappa = (float)(Math.abs(4. / 3. * (1. - Math.cos(halfAng)) / Math.sin(halfAng)));
1830         ArrayList pointList = new ArrayList();
1831         for (int i = 0; i < Nfrag; ++i) {
1832             float theta0 = (float)((startAng + i*fragAngle) * Math.PI / 180.);
1833             float theta1 = (float)((startAng + (i+1)*fragAngle) * Math.PI / 180.);
1834             float cos0 = (float)Math.cos(theta0);
1835             float cos1 = (float)Math.cos(theta1);
1836             float sin0 = (float)Math.sin(theta0);
1837             float sin1 = (float)Math.sin(theta1);
1838             if (fragAngle > 0f) {
1839                 pointList.add(new float[]{x_cen + rx * cos0,
1840                 y_cen - ry * sin0,
1841                 x_cen + rx * (cos0 - kappa * sin0),
1842                 y_cen - ry * (sin0 + kappa * cos0),
1843                 x_cen + rx * (cos1 + kappa * sin1),
1844                 y_cen - ry * (sin1 - kappa * cos1),
1845                 x_cen + rx * cos1,
1846                 y_cen - ry * sin1});
1847             }
1848             else {
1849                 pointList.add(new float[]{x_cen + rx * cos0,
1850                 y_cen - ry * sin0,
1851                 x_cen + rx * (cos0 + kappa * sin0),
1852                 y_cen - ry * (sin0 - kappa * cos0),
1853                 x_cen + rx * (cos1 - kappa * sin1),
1854                 y_cen - ry * (sin1 + kappa * cos1),
1855                 x_cen + rx * cos1,
1856                 y_cen - ry * sin1});
1857             }
1858         }
1859         return pointList;
1860     }
1861
1862     /**
1863      * Draws a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
1864      * starting at startAng degrees and covering extent degrees. Angles
1865      * start with 0 to the right (+x) and increase counter-clockwise.
1866      *
1867      * @param x1 a corner of the enclosing rectangle
1868      * @param y1 a corner of the enclosing rectangle
1869      * @param x2 a corner of the enclosing rectangle
1870      * @param y2 a corner of the enclosing rectangle
1871      * @param startAng starting angle in degrees
1872      * @param extent angle extent in degrees
1873      */

1874     public void arc(float x1, float y1, float x2, float y2, float startAng, float extent) {
1875         ArrayList ar = bezierArc(x1, y1, x2, y2, startAng, extent);
1876         if (ar.isEmpty())
1877             return;
1878         float pt[] = (float [])ar.get(0);
1879         moveTo(pt[0], pt[1]);
1880         for (int k = 0; k < ar.size(); ++k) {
1881             pt = (float [])ar.get(k);
1882             curveTo(pt[2], pt[3], pt[4], pt[5], pt[6], pt[7]);
1883         }
1884     }
1885
1886     /**
1887      * Draws an ellipse inscribed within the rectangle x1,y1,x2,y2.
1888      *
1889      * @param x1 a corner of the enclosing rectangle
1890      * @param y1 a corner of the enclosing rectangle
1891      * @param x2 a corner of the enclosing rectangle
1892      * @param y2 a corner of the enclosing rectangle
1893      */

1894     public void ellipse(float x1, float y1, float x2, float y2) {
1895         arc(x1, y1, x2, y2, 0f, 360f);
1896     }
1897
1898     /**
1899      * Create a new colored tiling pattern.
1900      *
1901      * @param width the width of the pattern
1902      * @param height the height of the pattern
1903      * @param xstep the desired horizontal spacing between pattern cells.
1904      * May be either positive or negative, but not zero.
1905      * @param ystep the desired vertical spacing between pattern cells.
1906      * May be either positive or negative, but not zero.
1907      * @return the <CODE>PdfPatternPainter</CODE> where the pattern will be created
1908      */

1909     public PdfPatternPainter createPattern(float width, float height, float xstep, float ystep) {
1910         checkWriter();
1911         if ( xstep == 0.0f || ystep == 0.0f )
1912             throw new RuntimeException("XStep or YStep can not be ZERO.");
1913         PdfPatternPainter painter = new PdfPatternPainter(writer);
1914         painter.setWidth(width);
1915         painter.setHeight(height);
1916         painter.setXStep(xstep);
1917         painter.setYStep(ystep);
1918         writer.addSimplePattern(painter);
1919         return painter;
1920     }
1921
1922     /**
1923      * Create a new colored tiling pattern. Variables xstep and ystep are set to the same values
1924      * of width and height.
1925      * @param width the width of the pattern
1926      * @param height the height of the pattern
1927      * @return the <CODE>PdfPatternPainter</CODE> where the pattern will be created
1928      */

1929     public PdfPatternPainter createPattern(float width, float height) {
1930         return createPattern(width, height, width, height);
1931     }
1932
1933     /**
1934      * Create a new uncolored tiling pattern.
1935      *
1936      * @param width the width of the pattern
1937      * @param height the height of the pattern
1938      * @param xstep the desired horizontal spacing between pattern cells.
1939      * May be either positive or negative, but not zero.
1940      * @param ystep the desired vertical spacing between pattern cells.
1941      * May be either positive or negative, but not zero.
1942      * @param color the default color. Can be <CODE>null</CODE>
1943      * @return the <CODE>PdfPatternPainter</CODE> where the pattern will be created
1944      */

1945     public PdfPatternPainter createPattern(float width, float height, float xstep, float ystep, Color color) {
1946         checkWriter();
1947         if ( xstep == 0.0f || ystep == 0.0f )
1948             throw new RuntimeException("XStep or YStep can not be ZERO.");
1949         PdfPatternPainter painter = new PdfPatternPainter(writer, color);
1950         painter.setWidth(width);
1951         painter.setHeight(height);
1952         painter.setXStep(xstep);
1953         painter.setYStep(ystep);
1954         writer.addSimplePattern(painter);
1955         return painter;
1956     }
1957
1958     /**
1959      * Create a new uncolored tiling pattern.
1960      * Variables xstep and ystep are set to the same values
1961      * of width and height.
1962      * @param width the width of the pattern
1963      * @param height the height of the pattern
1964      * @param color the default color. Can be <CODE>null</CODE>
1965      * @return the <CODE>PdfPatternPainter</CODE> where the pattern will be created
1966      */

1967     public PdfPatternPainter createPattern(float width, float height, Color color) {
1968         return createPattern(width, height, width, height, color);
1969     }
1970
1971     /**
1972      * Creates a new template.
1973      * <P>
1974      * Creates a new template that is nothing more than a form XObject. This template can be included
1975      * in this <CODE>PdfContentByte</CODE> or in another template. Templates are only written
1976      * to the output when the document is closed permitting things like showing text in the first page
1977      * that is only defined in the last page.
1978      *
1979      * @param width the bounding box width
1980      * @param height the bounding box height
1981      * @return the created template
1982      */

1983     public PdfTemplate createTemplate(float width, float height) {
1984         return createTemplate(width, height, null);
1985     }
1986
1987     PdfTemplate createTemplate(float width, float height, PdfName forcedName) {
1988         checkWriter();
1989         PdfTemplate template = new PdfTemplate(writer);
1990         template.setWidth(width);
1991         template.setHeight(height);
1992         writer.addDirectTemplateSimple(template, forcedName);
1993         return template;
1994     }
1995
1996     /**
1997      * Creates a new appearance to be used with form fields.
1998      *
1999      * @param width the bounding box width
2000      * @param height the bounding box height
2001      * @return the appearance created
2002      */

2003     public PdfAppearance createAppearance(float width, float height) {
2004         return createAppearance(width, height, null);
2005     }
2006
2007     PdfAppearance createAppearance(float width, float height, PdfName forcedName) {
2008         checkWriter();
2009         PdfAppearance template = new PdfAppearance(writer);
2010         template.setWidth(width);
2011         template.setHeight(height);
2012         writer.addDirectTemplateSimple(template, forcedName);
2013         return template;
2014     }
2015
2016     /**
2017      * Adds a PostScript XObject to this content.
2018      *
2019      * @param psobject the object
2020      */

2021     public void addPSXObject(PdfPSXObject psobject) {
2022         checkWriter();
2023         PdfName name = writer.addDirectTemplateSimple(psobject, null);
2024         PageResources prs = getPageResources();
2025         name = prs.addXObject(name, psobject.getIndirectReference());
2026         content.append(name.getBytes()).append(" Do").append_i(separator);
2027     }
2028
2029     /**
2030      * Adds a template to this content.
2031      *
2032      * @param template the template
2033      * @param a an element of the transformation matrix
2034      * @param b an element of the transformation matrix
2035      * @param c an element of the transformation matrix
2036      * @param d an element of the transformation matrix
2037      * @param e an element of the transformation matrix
2038      * @param f an element of the transformation matrix
2039      */

2040     public void addTemplate(PdfTemplate template, float a, float b, float c, float d, float e, float f) {
2041         checkWriter();
2042         checkNoPattern(template);
2043         PdfName name = writer.addDirectTemplateSimple(template, null);
2044         PageResources prs = getPageResources();
2045         name = prs.addXObject(name, template.getIndirectReference());
2046         content.append("q ");
2047         content.append(a).append(' ');
2048         content.append(b).append(' ');
2049         content.append(c).append(' ');
2050         content.append(d).append(' ');
2051         content.append(e).append(' ');
2052         content.append(f).append(" cm ");
2053         content.append(name.getBytes()).append(" Do Q").append_i(separator);
2054     }
2055
2056     void addTemplateReference(PdfIndirectReference template, PdfName name, float a, float b, float c, float d, float e, float f) {
2057         checkWriter();
2058         PageResources prs = getPageResources();
2059         name = prs.addXObject(name, template);
2060         content.append("q ");
2061         content.append(a).append(' ');
2062         content.append(b).append(' ');
2063         content.append(c).append(' ');
2064         content.append(d).append(' ');
2065         content.append(e).append(' ');
2066         content.append(f).append(" cm ");
2067         content.append(name.getBytes()).append(" Do Q").append_i(separator);
2068     }
2069
2070     /**
2071      * Adds a template to this content.
2072      *
2073      * @param template the template
2074      * @param x the x location of this template
2075      * @param y the y location of this template
2076      */

2077     public void addTemplate(PdfTemplate template, float x, float y) {
2078         addTemplate(template, 1, 0, 0, 1, x, y);
2079     }
2080
2081     /**
2082      * Changes the current color for filling paths (device dependent colors!).
2083      * <P>
2084      * Sets the color space to <B>DeviceCMYK</B> (or the <B>DefaultCMYK</B> color space),
2085      * and sets the color to use for filling paths.</P>
2086      * <P>
2087      * This method is described in the 'Portable Document Format Reference Manual version 1.3'
2088      * section 8.5.2.1 (page 331).</P>
2089      * <P>
2090      * Following the PDF manual, each operand must be a number between 0 (no ink) and
2091      * 1 (maximum ink). This method however accepts only integers between 0x00 and 0xFF.</P>
2092      *
2093      * @param cyan the intensity of cyan
2094      * @param magenta the intensity of magenta
2095      * @param yellow the intensity of yellow
2096      * @param black the intensity of black
2097      */

2098
2099     public void setCMYKColorFill(int cyan, int magenta, int yellow, int black) {
2100         content.append((float)(cyan & 0xFF) / 0xFF);
2101         content.append(' ');
2102         content.append((float)(magenta & 0xFF) / 0xFF);
2103         content.append(' ');
2104         content.append((float)(yellow & 0xFF) / 0xFF);
2105         content.append(' ');
2106         content.append((float)(black & 0xFF) / 0xFF);
2107         content.append(" k").append_i(separator);
2108     }
2109     /**
2110      * Changes the current color for stroking paths (device dependent colors!).
2111      * <P>
2112      * Sets the color space to <B>DeviceCMYK</B> (or the <B>DefaultCMYK</B> color space),
2113      * and sets the color to use for stroking paths.</P>
2114      * <P>
2115      * This method is described in the 'Portable Document Format Reference Manual version 1.3'
2116      * section 8.5.2.1 (page 331).</P>
2117      * Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
2118      * 1 (maximum intensity). This method however accepts only integers between 0x00 and 0xFF.
2119      *
2120      * @param cyan the intensity of red
2121      * @param magenta the intensity of green
2122      * @param yellow the intensity of blue
2123      * @param black the intensity of black
2124      */

2125
2126     public void setCMYKColorStroke(int cyan, int magenta, int yellow, int black) {
2127         content.append((float)(cyan & 0xFF) / 0xFF);
2128         content.append(' ');
2129         content.append((float)(magenta & 0xFF) / 0xFF);
2130         content.append(' ');
2131         content.append((float)(yellow & 0xFF) / 0xFF);
2132         content.append(' ');
2133         content.append((float)(black & 0xFF) / 0xFF);
2134         content.append(" K").append_i(separator);
2135     }
2136
2137     /**
2138      * Changes the current color for filling paths (device dependent colors!).
2139      * <P>
2140      * Sets the color space to <B>DeviceRGB</B> (or the <B>DefaultRGB</B> color space),
2141      * and sets the color to use for filling paths.</P>
2142      * <P>
2143      * This method is described in the 'Portable Document Format Reference Manual version 1.3'
2144      * section 8.5.2.1 (page 331).</P>
2145      * <P>
2146      * Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
2147      * 1 (maximum intensity). This method however accepts only integers between 0x00 and 0xFF.</P>
2148      *
2149      * @param red the intensity of red
2150      * @param green the intensity of green
2151      * @param blue the intensity of blue
2152      */

2153
2154     public void setRGBColorFill(int red, int green, int blue) {
2155         HelperRGB((float)(red & 0xFF) / 0xFF, (float)(green & 0xFF) / 0xFF, (float)(blue & 0xFF) / 0xFF);
2156         content.append(" rg").append_i(separator);
2157     }
2158
2159     /**
2160      * Changes the current color for stroking paths (device dependent colors!).
2161      * <P>
2162      * Sets the color space to <B>DeviceRGB</B> (or the <B>DefaultRGB</B> color space),
2163      * and sets the color to use for stroking paths.</P>
2164      * <P>
2165      * This method is described in the 'Portable Document Format Reference Manual version 1.3'
2166      * section 8.5.2.1 (page 331).</P>
2167      * Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
2168      * 1 (maximum intensity). This method however accepts only integers between 0x00 and 0xFF.
2169      *
2170      * @param red the intensity of red
2171      * @param green the intensity of green
2172      * @param blue the intensity of blue
2173      */

2174
2175     public void setRGBColorStroke(int red, int green, int blue) {
2176         HelperRGB((float)(red & 0xFF) / 0xFF, (float)(green & 0xFF) / 0xFF, (float)(blue & 0xFF) / 0xFF);
2177         content.append(" RG").append_i(separator);
2178     }
2179
2180     /** Sets the stroke color. <CODE>color</CODE> can be an
2181      * <CODE>ExtendedColor</CODE>.
2182      * @param color the color
2183      */

2184     public void setColorStroke(Color color) {
2185         PdfXConformanceImp.checkPDFXConformance(writer, PdfXConformanceImp.PDFXKEY_COLOR, color);
2186         int type = ExtendedColor.getType(color);
2187         switch (type) {
2188             case ExtendedColor.TYPE_GRAY: {
2189                 setGrayStroke(((GrayColor)color).getGray());
2190                 break;
2191             }
2192             case ExtendedColor.TYPE_CMYK: {
2193                 CMYKColor cmyk = (CMYKColor)color;
2194                 setCMYKColorStrokeF(cmyk.getCyan(), cmyk.getMagenta(), cmyk.getYellow(), cmyk.getBlack());
2195                 break;
2196             }
2197             case ExtendedColor.TYPE_SEPARATION: {
2198                 SpotColor spot = (SpotColor)color;
2199                 setColorStroke(spot.getPdfSpotColor(), spot.getTint());
2200                 break;
2201             }
2202             case ExtendedColor.TYPE_PATTERN: {
2203                 PatternColor pat = (PatternColor) color;
2204                 setPatternStroke(pat.getPainter());
2205                 break;
2206             }
2207             case ExtendedColor.TYPE_SHADING: {
2208                 ShadingColor shading = (ShadingColor) color;
2209                 setShadingStroke(shading.getPdfShadingPattern());
2210                 break;
2211             }
2212             default:
2213                 setRGBColorStroke(color.getRed(), color.getGreen(), color.getBlue());
2214         }
2215     }
2216
2217     /** Sets the fill color. <CODE>color</CODE> can be an
2218      * <CODE>ExtendedColor</CODE>.
2219      * @param color the color
2220      */

2221     public void setColorFill(Color color) {
2222         PdfXConformanceImp.checkPDFXConformance(writer, PdfXConformanceImp.PDFXKEY_COLOR, color);
2223         int type = ExtendedColor.getType(color);
2224         switch (type) {
2225             case ExtendedColor.TYPE_GRAY: {
2226                 setGrayFill(((GrayColor)color).getGray());
2227                 break;
2228             }
2229             case ExtendedColor.TYPE_CMYK: {
2230                 CMYKColor cmyk = (CMYKColor)color;
2231                 setCMYKColorFillF(cmyk.getCyan(), cmyk.getMagenta(), cmyk.getYellow(), cmyk.getBlack());
2232                 break;
2233             }
2234             case ExtendedColor.TYPE_SEPARATION: {
2235                 SpotColor spot = (SpotColor)color;
2236                 setColorFill(spot.getPdfSpotColor(), spot.getTint());
2237                 break;
2238             }
2239             case ExtendedColor.TYPE_PATTERN: {
2240                 PatternColor pat = (PatternColor) color;
2241                 setPatternFill(pat.getPainter());
2242                 break;
2243             }
2244             case ExtendedColor.TYPE_SHADING: {
2245                 ShadingColor shading = (ShadingColor) color;
2246                 setShadingFill(shading.getPdfShadingPattern());
2247                 break;
2248             }
2249             default:
2250                 setRGBColorFill(color.getRed(), color.getGreen(), color.getBlue());
2251         }
2252     }
2253
2254     /** Sets the fill color to a spot color.
2255      * @param sp the spot color
2256      * @param tint the tint for the spot color. 0 is no color and 1
2257      * is 100% color
2258      */

2259     public void setColorFill(PdfSpotColor sp, float tint) {
2260         checkWriter();
2261         state.colorDetails = writer.addSimple(sp);
2262         PageResources prs = getPageResources();
2263         PdfName name = state.colorDetails.getColorName();
2264         name = prs.addColor(name, state.colorDetails.getIndirectReference());
2265         content.append(name.getBytes()).append(" cs ").append(tint).append(" scn").append_i(separator);
2266     }
2267
2268     /** Sets the stroke color to a spot color.
2269      * @param sp the spot color
2270      * @param tint the tint for the spot color. 0 is no color and 1
2271      * is 100% color
2272      */

2273     public void setColorStroke(PdfSpotColor sp, float tint) {
2274         checkWriter();
2275         state.colorDetails = writer.addSimple(sp);
2276         PageResources prs = getPageResources();
2277         PdfName name = state.colorDetails.getColorName();
2278         name = prs.addColor(name, state.colorDetails.getIndirectReference());
2279         content.append(name.getBytes()).append(" CS ").append(tint).append(" SCN").append_i(separator);
2280     }
2281
2282     /** Sets the fill color to a pattern. The pattern can be
2283      * colored or uncolored.
2284      * @param p the pattern
2285      */

2286     public void setPatternFill(PdfPatternPainter p) {
2287         if (p.isStencil()) {
2288             setPatternFill(p, p.getDefaultColor());
2289             return;
2290         }
2291         checkWriter();
2292         PageResources prs = getPageResources();
2293         PdfName name = writer.addSimplePattern(p);
2294         name = prs.addPattern(name, p.getIndirectReference());
2295         content.append(PdfName.PATTERN.getBytes()).append(" cs ").append(name.getBytes()).append(" scn").append_i(separator);
2296     }
2297
2298     /** Outputs the color values to the content.
2299      * @param color The color
2300      * @param tint the tint if it is a spot color, ignored otherwise
2301      */

2302     void outputColorNumbers(Color color, float tint) {
2303         PdfXConformanceImp.checkPDFXConformance(writer, PdfXConformanceImp.PDFXKEY_COLOR, color);
2304         int type = ExtendedColor.getType(color);
2305         switch (type) {
2306             case ExtendedColor.TYPE_RGB:
2307                 content.append((float)(color.getRed()) / 0xFF);
2308                 content.append(' ');
2309                 content.append((float)(color.getGreen()) / 0xFF);
2310                 content.append(' ');
2311                 content.append((float)(color.getBlue()) / 0xFF);
2312                 break;
2313             case ExtendedColor.TYPE_GRAY:
2314                 content.append(((GrayColor)color).getGray());
2315                 break;
2316             case ExtendedColor.TYPE_CMYK: {
2317                 CMYKColor cmyk = (CMYKColor)color;
2318                 content.append(cmyk.getCyan()).append(' ').append(cmyk.getMagenta());
2319                 content.append(' ').append(cmyk.getYellow()).append(' ').append(cmyk.getBlack());
2320                 break;
2321             }
2322             case ExtendedColor.TYPE_SEPARATION:
2323                 content.append(tint);
2324                 break;
2325             default:
2326                 throw new RuntimeException("Invalid color type.");
2327         }
2328     }
2329
2330     /** Sets the fill color to an uncolored pattern.
2331      * @param p the pattern
2332      * @param color the color of the pattern
2333      */

2334     public void setPatternFill(PdfPatternPainter p, Color color) {
2335         if (ExtendedColor.getType(color) == ExtendedColor.TYPE_SEPARATION)
2336             setPatternFill(p, color, ((SpotColor)color).getTint());
2337         else
2338             setPatternFill(p, color, 0);
2339     }
2340
2341     /** Sets the fill color to an uncolored pattern.
2342      * @param p the pattern
2343      * @param color the color of the pattern
2344      * @param tint the tint if the color is a spot color, ignored otherwise
2345      */

2346     public void setPatternFill(PdfPatternPainter p, Color color, float tint) {
2347         checkWriter();
2348         if (!p.isStencil())
2349             throw new RuntimeException("An uncolored pattern was expected.");
2350         PageResources prs = getPageResources();
2351         PdfName name = writer.addSimplePattern(p);
2352         name = prs.addPattern(name, p.getIndirectReference());
2353         ColorDetails csDetail = writer.addSimplePatternColorspace(color);
2354         PdfName cName = prs.addColor(csDetail.getColorName(), csDetail.getIndirectReference());
2355         content.append(cName.getBytes()).append(" cs").append_i(separator);
2356         outputColorNumbers(color, tint);
2357         content.append(' ').append(name.getBytes()).append(" scn").append_i(separator);
2358     }
2359
2360     /** Sets the stroke color to an uncolored pattern.
2361      * @param p the pattern
2362      * @param color the color of the pattern
2363      */

2364     public void setPatternStroke(PdfPatternPainter p, Color color) {
2365         if (ExtendedColor.getType(color) == ExtendedColor.TYPE_SEPARATION)
2366             setPatternStroke(p, color, ((SpotColor)color).getTint());
2367         else
2368             setPatternStroke(p, color, 0);
2369     }
2370
2371     /** Sets the stroke color to an uncolored pattern.
2372      * @param p the pattern
2373      * @param color the color of the pattern
2374      * @param tint the tint if the color is a spot color, ignored otherwise
2375      */

2376     public void setPatternStroke(PdfPatternPainter p, Color color, float tint) {
2377         checkWriter();
2378         if (!p.isStencil())
2379             throw new RuntimeException("An uncolored pattern was expected.");
2380         PageResources prs = getPageResources();
2381         PdfName name = writer.addSimplePattern(p);
2382         name = prs.addPattern(name, p.getIndirectReference());
2383         ColorDetails csDetail = writer.addSimplePatternColorspace(color);
2384         PdfName cName = prs.addColor(csDetail.getColorName(), csDetail.getIndirectReference());
2385         content.append(cName.getBytes()).append(" CS").append_i(separator);
2386         outputColorNumbers(color, tint);
2387         content.append(' ').append(name.getBytes()).append(" SCN").append_i(separator);
2388     }
2389
2390     /** Sets the stroke color to a pattern. The pattern can be
2391      * colored or uncolored.
2392      * @param p the pattern
2393      */

2394     public void setPatternStroke(PdfPatternPainter p) {
2395         if (p.isStencil()) {
2396             setPatternStroke(p, p.getDefaultColor());
2397             return;
2398         }
2399         checkWriter();
2400         PageResources prs = getPageResources();
2401         PdfName name = writer.addSimplePattern(p);
2402         name = prs.addPattern(name, p.getIndirectReference());
2403         content.append(PdfName.PATTERN.getBytes()).append(" CS ").append(name.getBytes()).append(" SCN").append_i(separator);
2404     }
2405
2406     /**
2407      * Paints using a shading object.
2408      * @param shading the shading object
2409      */

2410     public void paintShading(PdfShading shading) {
2411         writer.addSimpleShading(shading);
2412         PageResources prs = getPageResources();
2413         PdfName name = prs.addShading(shading.getShadingName(), shading.getShadingReference());
2414         content.append(name.getBytes()).append(" sh").append_i(separator);
2415         ColorDetails details = shading.getColorDetails();
2416         if (details != null)
2417             prs.addColor(details.getColorName(), details.getIndirectReference());
2418     }
2419
2420     /**
2421      * Paints using a shading pattern.
2422      * @param shading the shading pattern
2423      */

2424     public void paintShading(PdfShadingPattern shading) {
2425         paintShading(shading.getShading());
2426     }
2427
2428     /**
2429      * Sets the shading fill pattern.
2430      * @param shading the shading pattern
2431      */

2432     public void setShadingFill(PdfShadingPattern shading) {
2433         writer.addSimpleShadingPattern(shading);
2434         PageResources prs = getPageResources();
2435         PdfName name = prs.addPattern(shading.getPatternName(), shading.getPatternReference());
2436         content.append(PdfName.PATTERN.getBytes()).append(" cs ").append(name.getBytes()).append(" scn").append_i(separator);
2437         ColorDetails details = shading.getColorDetails();
2438         if (details != null)
2439             prs.addColor(details.getColorName(), details.getIndirectReference());
2440     }
2441
2442     /**
2443      * Sets the shading stroke pattern
2444      * @param shading the shading pattern
2445      */

2446     public void setShadingStroke(PdfShadingPattern shading) {
2447         writer.addSimpleShadingPattern(shading);
2448         PageResources prs = getPageResources();
2449         PdfName name = prs.addPattern(shading.getPatternName(), shading.getPatternReference());
2450         content.append(PdfName.PATTERN.getBytes()).append(" CS ").append(name.getBytes()).append(" SCN").append_i(separator);
2451         ColorDetails details = shading.getColorDetails();
2452         if (details != null)
2453             prs.addColor(details.getColorName(), details.getIndirectReference());
2454     }
2455
2456     /** Check if we have a valid PdfWriter.
2457      *
2458      */

2459     protected void checkWriter() {
2460         if (writer == null)
2461             throw new NullPointerException("The writer in PdfContentByte is null.");
2462     }
2463
2464     /**
2465      * Show an array of text.
2466      * @param text array of text
2467      */

2468     public void showText(PdfTextArray text) {
2469         if (state.fontDetails == null)
2470             throw new NullPointerException("Font and size must be set before writing any text");
2471         content.append("[");
2472         ArrayList arrayList = text.getArrayList();
2473         boolean lastWasNumber = false;
2474         for (int k = 0; k < arrayList.size(); ++k) {
2475             Object obj = arrayList.get(k);
2476             if (obj instanceof String) {
2477                 showText2((String)obj);
2478                 lastWasNumber = false;
2479             }
2480             else {
2481                 if (lastWasNumber)
2482                     content.append(' ');
2483                 else
2484                     lastWasNumber = true;
2485                 content.append(((Float)obj).floatValue());
2486             }
2487         }
2488         content.append("]TJ").append_i(separator);
2489     }
2490
2491     /**
2492      * Gets the <CODE>PdfWriter</CODE> in use by this object.
2493      * @return the <CODE>PdfWriter</CODE> in use by this object
2494      */

2495     public PdfWriter getPdfWriter() {
2496         return writer;
2497     }
2498
2499     /**
2500      * Gets the <CODE>PdfDocument</CODE> in use by this object.
2501      * @return the <CODE>PdfDocument</CODE> in use by this object
2502      */

2503     public PdfDocument getPdfDocument() {
2504         return pdf;
2505     }
2506
2507     /**
2508      * Implements a link to other part of the document. The jump will
2509      * be made to a local destination with the same name, that must exist.
2510      * @param name the name for this link
2511      * @param llx the lower left x corner of the activation area
2512      * @param lly the lower left y corner of the activation area
2513      * @param urx the upper right x corner of the activation area
2514      * @param ury the upper right y corner of the activation area
2515      */

2516     public void localGoto(String name, float llx, float lly, float urx, float ury) {
2517         pdf.localGoto(name, llx, lly, urx, ury);
2518     }
2519
2520     /**
2521      * The local destination to where a local goto with the same
2522      * name will jump.
2523      * @param name the name of this local destination
2524      * @param destination the <CODE>PdfDestination</CODE> with the jump coordinates
2525      * @return <CODE>true</CODE> if the local destination was added,
2526      * <CODE>false</CODE> if a local destination with the same name
2527      * already exists
2528      */

2529     public boolean localDestination(String name, PdfDestination destination) {
2530         return pdf.localDestination(name, destination);
2531     }
2532
2533     /**
2534      * Gets a duplicate of this <CODE>PdfContentByte</CODE>. All
2535      * the members are copied by reference but the buffer stays different.
2536      *
2537      * @return a copy of this <CODE>PdfContentByte</CODE>
2538      */

2539     public PdfContentByte getDuplicate() {
2540         return new PdfContentByte(writer);
2541     }
2542
2543     /**
2544      * Implements a link to another document.
2545      * @param filename the filename for the remote document
2546      * @param name the name to jump to
2547      * @param llx the lower left x corner of the activation area
2548      * @param lly the lower left y corner of the activation area
2549      * @param urx the upper right x corner of the activation area
2550      * @param ury the upper right y corner of the activation area
2551      */

2552     public void remoteGoto(String filename, String name, float llx, float lly, float urx, float ury) {
2553         pdf.remoteGoto(filename, name, llx, lly, urx, ury);
2554     }
2555
2556     /**
2557      * Implements a link to another document.
2558      * @param filename the filename for the remote document
2559      * @param page the page to jump to
2560      * @param llx the lower left x corner of the activation area
2561      * @param lly the lower left y corner of the activation area
2562      * @param urx the upper right x corner of the activation area
2563      * @param ury the upper right y corner of the activation area
2564      */

2565     public void remoteGoto(String filename, int page, float llx, float lly, float urx, float ury) {
2566         pdf.remoteGoto(filename, page, llx, lly, urx, ury);
2567     }
2568     /**
2569      * Adds a round rectangle to the current path.
2570      *
2571      * @param x x-coordinate of the starting point
2572      * @param y y-coordinate of the starting point
2573      * @param w width
2574      * @param h height
2575      * @param r radius of the arc corner
2576      */

2577     public void roundRectangle(float x, float y, float w, float h, float r) {
2578         if (w < 0) {
2579             x += w;
2580             w = -w;
2581         }
2582         if (h < 0) {
2583             y += h;
2584             h = -h;
2585         }
2586         if (r < 0)
2587             r = -r;
2588         float b = 0.4477f;
2589         moveTo(x + r, y);
2590         lineTo(x + w - r, y);
2591         curveTo(x + w - r * b, y, x + w, y + r * b, x + w, y + r);
2592         lineTo(x + w, y + h - r);
2593         curveTo(x + w, y + h - r * b, x + w - r * b, y + h, x + w - r, y + h);
2594         lineTo(x + r, y + h);
2595         curveTo(x + r * b, y + h, x, y + h - r * b, x, y + h - r);
2596         lineTo(x, y + r);
2597         curveTo(x, y + r * b, x + r * b, y, x + r, y);
2598     }
2599
2600     /** Implements an action in an area.
2601      * @param action the <CODE>PdfAction</CODE>
2602      * @param llx the lower left x corner of the activation area
2603      * @param lly the lower left y corner of the activation area
2604      * @param urx the upper right x corner of the activation area
2605      * @param ury the upper right y corner of the activation area
2606      */

2607     public void setAction(PdfAction action, float llx, float lly, float urx, float ury) {
2608         pdf.setAction(action, llx, lly, urx, ury);
2609     }
2610
2611     /** Outputs a <CODE>String</CODE> directly to the content.
2612      * @param s the <CODE>String</CODE>
2613      */

2614     public void setLiteral(String s) {
2615         content.append(s);
2616     }
2617
2618     /** Outputs a <CODE>char</CODE> directly to the content.
2619      * @param c the <CODE>char</CODE>
2620      */

2621     public void setLiteral(char c) {
2622         content.append(c);
2623     }
2624
2625     /** Outputs a <CODE>float</CODE> directly to the content.
2626      * @param n the <CODE>float</CODE>
2627      */

2628     public void setLiteral(float n) {
2629         content.append(n);
2630     }
2631
2632     /** Throws an error if it is a pattern.
2633      * @param t the object to check
2634      */

2635     void checkNoPattern(PdfTemplate t) {
2636         if (t.getType() == PdfTemplate.TYPE_PATTERN)
2637             throw new RuntimeException("Invalid use of a pattern. A template was expected.");
2638     }
2639
2640     /**
2641      * Draws a TextField.
2642      * @param llx
2643      * @param lly
2644      * @param urx
2645      * @param ury
2646      * @param on
2647      */

2648     public void drawRadioField(float llx, float lly, float urx, float ury, boolean on) {
2649         if (llx > urx) { float x = llx; llx = urx; urx = x; }
2650         if (lly > ury) { float y = lly; lly = ury; ury = y; }
2651         // silver circle
2652         setLineWidth(1);
2653         setLineCap(1);
2654         setColorStroke(new Color(0xC0, 0xC0, 0xC0));
2655         arc(llx + 1f, lly + 1f, urx - 1f, ury - 1f, 0f, 360f);
2656         stroke();
2657         // gray circle-segment
2658         setLineWidth(1);
2659         setLineCap(1);
2660         setColorStroke(new Color(0xA0, 0xA0, 0xA0));
2661         arc(llx + 0.5f, lly + 0.5f, urx - 0.5f, ury - 0.5f, 45, 180);
2662         stroke();
2663         // black circle-segment
2664         setLineWidth(1);
2665         setLineCap(1);
2666         setColorStroke(new Color(0x00, 0x00, 0x00));
2667         arc(llx + 1.5f, lly + 1.5f, urx - 1.5f, ury - 1.5f, 45, 180);
2668         stroke();
2669         if (on) {
2670             // gray circle
2671             setLineWidth(1);
2672             setLineCap(1);
2673             setColorFill(new Color(0x00, 0x00, 0x00));
2674             arc(llx + 4f, lly + 4f, urx - 4f, ury - 4f, 0, 360);
2675             fill();
2676         }
2677     }
2678
2679     /**
2680      * Draws a TextField.
2681      * @param llx
2682      * @param lly
2683      * @param urx
2684      * @param ury
2685      */

2686     public void drawTextField(float llx, float lly, float urx, float ury) {
2687         if (llx > urx) { float x = llx; llx = urx; urx = x; }
2688         if (lly > ury) { float y = lly; lly = ury; ury = y; }
2689         // silver rectangle not filled
2690         setColorStroke(new Color(0xC0, 0xC0, 0xC0));
2691         setLineWidth(1);
2692         setLineCap(0);
2693         rectangle(llx, lly, urx - llx, ury - lly);
2694         stroke();
2695         // white rectangle filled
2696         setLineWidth(1);
2697         setLineCap(0);
2698         setColorFill(new Color(0xFF, 0xFF, 0xFF));
2699         rectangle(llx + 0.5f, lly + 0.5f, urx - llx - 1f, ury -lly - 1f);
2700         fill();
2701         // silver lines
2702         setColorStroke(new Color(0xC0, 0xC0, 0xC0));
2703         setLineWidth(1);
2704         setLineCap(0);
2705         moveTo(llx + 1f, lly + 1.5f);
2706         lineTo(urx - 1.5f, lly + 1.5f);
2707         lineTo(urx - 1.5f, ury - 1f);
2708         stroke();
2709         // gray lines
2710         setColorStroke(new Color(0xA0, 0xA0, 0xA0));
2711         setLineWidth(1);
2712         setLineCap(0);
2713         moveTo(llx + 1f, lly + 1);
2714         lineTo(llx + 1f, ury - 1f);
2715         lineTo(urx - 1f, ury - 1f);
2716         stroke();
2717         // black lines
2718         setColorStroke(new Color(0x00, 0x00, 0x00));
2719         setLineWidth(1);
2720         setLineCap(0);
2721         moveTo(llx + 2f, lly + 2f);
2722         lineTo(llx + 2f, ury - 2f);
2723         lineTo(urx - 2f, ury - 2f);
2724         stroke();
2725     }
2726
2727     /**
2728      * Draws a button.
2729      * @param llx
2730      * @param lly
2731      * @param urx
2732      * @param ury
2733      * @param text
2734      * @param bf
2735      * @param size
2736      */

2737     public void drawButton(float llx, float lly, float urx, float ury, String text, BaseFont bf, float size) {
2738         if (llx > urx) { float x = llx; llx = urx; urx = x; }
2739         if (lly > ury) { float y = lly; lly = ury; ury = y; }
2740         // black rectangle not filled
2741         setColorStroke(new Color(0x00, 0x00, 0x00));
2742         setLineWidth(1);
2743         setLineCap(0);
2744         rectangle(llx, lly, urx - llx, ury - lly);
2745         stroke();
2746         // silver rectangle filled
2747         setLineWidth(1);
2748         setLineCap(0);
2749         setColorFill(new Color(0xC0, 0xC0, 0xC0));
2750         rectangle(llx + 0.5f, lly + 0.5f, urx - llx - 1f, ury -lly - 1f);
2751         fill();
2752         // white lines
2753         setColorStroke(new Color(0xFF, 0xFF, 0xFF));
2754         setLineWidth(1);
2755         setLineCap(0);
2756         moveTo(llx + 1f, lly + 1f);
2757         lineTo(llx + 1f, ury - 1f);
2758         lineTo(urx - 1f, ury - 1f);
2759         stroke();
2760         // dark grey lines
2761         setColorStroke(new Color(0xA0, 0xA0, 0xA0));
2762         setLineWidth(1);
2763         setLineCap(0);
2764         moveTo(llx + 1f, lly + 1f);
2765         lineTo(urx - 1f, lly + 1f);
2766         lineTo(urx - 1f, ury - 1f);
2767         stroke();
2768         // text
2769         resetRGBColorFill();
2770         beginText();
2771         setFontAndSize(bf, size);
2772         showTextAligned(PdfContentByte.ALIGN_CENTER, text, llx + (urx - llx) / 2, lly + (ury - lly - size) / 2, 0);
2773         endText();
2774     }
2775
2776     /** Gets a <CODE>Graphics2D</CODE> to write on. The graphics
2777      * are translated to PDF commands as shapes. No PDF fonts will appear.
2778      * @param width the width of the panel
2779      * @param height the height of the panel
2780      * @return a <CODE>Graphics2D</CODE>
2781      */

2782     public java.awt.Graphics2D createGraphicsShapes(float width, float height) {
2783         return new PdfGraphics2D(this, width, height, nulltruefalse, 0);
2784     }
2785
2786     /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2787      * are translated to PDF commands as shapes. No PDF fonts will appear.
2788      * @param width the width of the panel
2789      * @param height the height of the panel
2790      * @param printerJob a printer job
2791      * @return a <CODE>Graphics2D</CODE>
2792      */

2793     public java.awt.Graphics2D createPrinterGraphicsShapes(float width, float height, PrinterJob printerJob) {
2794         return new PdfPrinterGraphics2D(this, width, height, nulltruefalse, 0, printerJob);
2795     }
2796
2797     /** Gets a <CODE>Graphics2D</CODE> to write on. The graphics
2798      * are translated to PDF commands.
2799      * @param width the width of the panel
2800      * @param height the height of the panel
2801      * @return a <CODE>Graphics2D</CODE>
2802      */

2803     public java.awt.Graphics2D createGraphics(float width, float height) {
2804         return new PdfGraphics2D(this, width, height, nullfalsefalse, 0);
2805     }
2806
2807     /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2808      * are translated to PDF commands.
2809      * @param width the width of the panel
2810      * @param height the height of the panel
2811      * @param printerJob
2812      * @return a <CODE>Graphics2D</CODE>
2813      */

2814     public java.awt.Graphics2D createPrinterGraphics(float width, float height, PrinterJob printerJob) {
2815         return new PdfPrinterGraphics2D(this, width, height, nullfalsefalse, 0, printerJob);
2816     }
2817
2818     /** Gets a <CODE>Graphics2D</CODE> to write on. The graphics
2819      * are translated to PDF commands.
2820      * @param width the width of the panel
2821      * @param height the height of the panel
2822      * @param convertImagesToJPEG
2823      * @param quality
2824      * @return a <CODE>Graphics2D</CODE>
2825      */

2826     public java.awt.Graphics2D createGraphics(float width, float height, boolean convertImagesToJPEG, float quality) {
2827         return new PdfGraphics2D(this, width, height, nullfalse, convertImagesToJPEG, quality);
2828     }
2829
2830     /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2831      * are translated to PDF commands.
2832      * @param width the width of the panel
2833      * @param height the height of the panel
2834      * @param convertImagesToJPEG
2835      * @param quality
2836      * @param printerJob
2837      * @return a <CODE>Graphics2D</CODE>
2838      */

2839     public java.awt.Graphics2D createPrinterGraphics(float width, float height, boolean convertImagesToJPEG, float quality, PrinterJob printerJob) {
2840         return new PdfPrinterGraphics2D(this, width, height, nullfalse, convertImagesToJPEG, quality, printerJob);
2841     }
2842
2843     /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2844      * are translated to PDF commands.
2845      * @param width
2846      * @param height
2847      * @param convertImagesToJPEG
2848      * @param quality
2849      * @return A Graphics2D object
2850      */

2851     public java.awt.Graphics2D createGraphicsShapes(float width, float height, boolean convertImagesToJPEG, float quality) {
2852         return new PdfGraphics2D(this, width, height, nulltrue, convertImagesToJPEG, quality);
2853     }
2854
2855     /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2856      * are translated to PDF commands.
2857      * @param width
2858      * @param height
2859      * @param convertImagesToJPEG
2860      * @param quality
2861      * @param printerJob
2862      * @return a Graphics2D object
2863      */

2864     public java.awt.Graphics2D createPrinterGraphicsShapes(float width, float height, boolean convertImagesToJPEG, float quality, PrinterJob printerJob) {
2865         return new PdfPrinterGraphics2D(this, width, height, nulltrue, convertImagesToJPEG, quality, printerJob);
2866     }
2867
2868     /** Gets a <CODE>Graphics2D</CODE> to write on. The graphics
2869      * are translated to PDF commands.
2870      * @param width the width of the panel
2871      * @param height the height of the panel
2872      * @param fontMapper the mapping from awt fonts to <CODE>BaseFont</CODE>
2873      * @return a <CODE>Graphics2D</CODE>
2874      */

2875     public java.awt.Graphics2D createGraphics(float width, float height, FontMapper fontMapper) {
2876         return new PdfGraphics2D(this, width, height, fontMapper, falsefalse, 0);
2877     }
2878
2879     /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2880      * are translated to PDF commands.
2881      * @param width the width of the panel
2882      * @param height the height of the panel
2883      * @param fontMapper the mapping from awt fonts to <CODE>BaseFont</CODE>
2884      * @param printerJob a printer job
2885      * @return a <CODE>Graphics2D</CODE>
2886      */

2887     public java.awt.Graphics2D createPrinterGraphics(float width, float height, FontMapper fontMapper, PrinterJob printerJob) {
2888         return new PdfPrinterGraphics2D(this, width, height, fontMapper, falsefalse, 0, printerJob);
2889     }
2890
2891     /** Gets a <CODE>Graphics2D</CODE> to write on. The graphics
2892      * are translated to PDF commands.
2893      * @param width the width of the panel
2894      * @param height the height of the panel
2895      * @param fontMapper the mapping from awt fonts to <CODE>BaseFont</CODE>
2896      * @param convertImagesToJPEG converts awt images to jpeg before inserting in pdf
2897      * @param quality the quality of the jpeg
2898      * @return a <CODE>Graphics2D</CODE>
2899      */

2900     public java.awt.Graphics2D createGraphics(float width, float height, FontMapper fontMapper, boolean convertImagesToJPEG, float quality) {
2901         return new PdfGraphics2D(this, width, height, fontMapper, false, convertImagesToJPEG, quality);
2902     }
2903
2904     /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2905      * are translated to PDF commands.
2906      * @param width the width of the panel
2907      * @param height the height of the panel
2908      * @param fontMapper the mapping from awt fonts to <CODE>BaseFont</CODE>
2909      * @param convertImagesToJPEG converts awt images to jpeg before inserting in pdf
2910      * @param quality the quality of the jpeg
2911      * @param printerJob a printer job
2912      * @return a <CODE>Graphics2D</CODE>
2913      */

2914     public java.awt.Graphics2D createPrinterGraphics(float width, float height, FontMapper fontMapper, boolean convertImagesToJPEG, float quality, PrinterJob printerJob) {
2915         return new PdfPrinterGraphics2D(this, width, height, fontMapper, false, convertImagesToJPEG, quality, printerJob);
2916     }
2917
2918     PageResources getPageResources() {
2919         return pdf.getPageResources();
2920     }
2921
2922     /** Sets the graphic state
2923      * @param gstate the graphic state
2924      */

2925     public void setGState(PdfGState gstate) {
2926         PdfObject obj[] = writer.addSimpleExtGState(gstate);
2927         PageResources prs = getPageResources();
2928         PdfName name = prs.addExtGState((PdfName)obj[0], (PdfIndirectReference)obj[1]);
2929         content.append(name.getBytes()).append(" gs").append_i(separator);
2930     }
2931
2932     /**
2933      * Begins a graphic block whose visibility is controlled by the <CODE>layer</CODE>.
2934      * Blocks can be nested. Each block must be terminated by an {@link #endLayer()}.<p>
2935      * Note that nested layers with {@link PdfLayer#addChild(PdfLayer)} only require a single
2936      * call to this method and a single call to {@link #endLayer()}; all the nesting control
2937      * is built in.
2938      * @param layer the layer
2939      */

2940     public void beginLayer(PdfOCG layer) {
2941         if ((layer instanceof PdfLayer) && ((PdfLayer)layer).getTitle() != null)
2942             throw new IllegalArgumentException("A title is not a layer");
2943         if (layerDepth == null)
2944             layerDepth = new ArrayList();
2945         if (layer instanceof PdfLayerMembership) {
2946             layerDepth.add(new Integer(1));
2947             beginLayer2(layer);
2948             return;
2949         }
2950         int n = 0;
2951         PdfLayer la = (PdfLayer)layer;
2952         while (la != null) {
2953             if (la.getTitle() == null) {
2954                 beginLayer2(la);
2955                 ++n;
2956             }
2957             la = la.getParent();
2958         }
2959         layerDepth.add(new Integer(n));
2960     }
2961
2962     private void beginLayer2(PdfOCG layer) {
2963         PdfName name = (PdfName)writer.addSimpleProperty(layer, layer.getRef())[0];
2964         PageResources prs = getPageResources();
2965         name = prs.addProperty(name, layer.getRef());
2966         content.append("/OC ").append(name.getBytes()).append(" BDC").append_i(separator);
2967     }
2968
2969     /**
2970      * Ends a layer controlled graphic block. It will end the most recent open block.
2971      */

2972     public void endLayer() {
2973         int n = 1;
2974         if (layerDepth != null && !layerDepth.isEmpty()) {
2975             n = ((Integer)layerDepth.get(layerDepth.size() - 1)).intValue();
2976             layerDepth.remove(layerDepth.size() - 1);
2977         } else {
2978             throw new IllegalPdfSyntaxException("Unbalanced layer operators." );
2979         }
2980         while (n-- > 0)
2981             content.append("EMC").append_i(separator);
2982     }
2983
2984     /** Concatenates a transformation to the current transformation
2985      * matrix.
2986      * @param af the transformation
2987      */

2988     public void transform(AffineTransform af) {
2989         double arr[] = new double[6];
2990         af.getMatrix(arr);
2991         content.append(arr[0]).append(' ').append(arr[1]).append(' ').append(arr[2]).append(' ');
2992         content.append(arr[3]).append(' ').append(arr[4]).append(' ').append(arr[5]).append(" cm").append_i(separator);
2993     }
2994
2995     void addAnnotation(PdfAnnotation annot) {
2996         writer.addAnnotation(annot);
2997     }
2998
2999     /**
3000      * Sets the default colorspace.
3001      * @param name the name of the colorspace. It can be <CODE>PdfName.DEFAULTGRAY</CODE>, <CODE>PdfName.DEFAULTRGB</CODE>
3002      * or <CODE>PdfName.DEFAULTCMYK</CODE>
3003      * @param obj the colorspace. A <CODE>null</CODE> or <CODE>PdfNull</CODE> removes any colorspace with the same name
3004      */

3005     public void setDefaultColorspace(PdfName name, PdfObject obj) {
3006         PageResources prs = getPageResources();
3007         prs.addDefaultColor(name, obj);
3008     }
3009
3010     /**
3011      * Begins a marked content sequence. This sequence will be tagged with the structure <CODE>struc</CODE>.
3012      * The same structure can be used several times to connect text that belongs to the same logical segment
3013      * but is in a different location, like the same paragraph crossing to another page, for example.
3014      * @param struc the tagging structure
3015      */

3016     public void beginMarkedContentSequence(PdfStructureElement struc) {
3017         PdfObject obj = struc.get(PdfName.K);
3018         int mark = pdf.getMarkPoint();
3019         if (obj != null) {
3020             PdfArray ar = null;
3021             if (obj.isNumber()) {
3022                 ar = new PdfArray();
3023                 ar.add(obj);
3024                 struc.put(PdfName.K, ar);
3025             }
3026             else if (obj.isArray()) {
3027                 ar = (PdfArray)obj;
3028                 if (!(ar.getPdfObject(0)).isNumber())
3029                     throw new IllegalArgumentException("The structure has kids.");
3030             }
3031             else
3032                 throw new IllegalArgumentException("Unknown object at /K " + obj.getClass().toString());
3033             PdfDictionary dic = new PdfDictionary(PdfName.MCR);
3034             dic.put(PdfName.PG, writer.getCurrentPage());
3035             dic.put(PdfName.MCID, new PdfNumber(mark));
3036             ar.add(dic);
3037             struc.setPageMark(writer.getPageNumber() - 1, -1);
3038         }
3039         else {
3040             struc.setPageMark(writer.getPageNumber() - 1, mark);
3041             struc.put(PdfName.PG, writer.getCurrentPage());
3042         }
3043         pdf.incMarkPoint();
3044         mcDepth++;
3045         content.append(struc.get(PdfName.S).getBytes()).append(" <</MCID ").append(mark).append(">> BDC").append_i(separator);
3046     }
3047
3048     /**
3049      * Ends a marked content sequence
3050      */

3051     public void endMarkedContentSequence() {
3052         if (mcDepth == 0) {
3053             throw new IllegalPdfSyntaxException("Unbalanced begin/end marked content operators." );
3054         }
3055         --mcDepth;
3056         content.append("EMC").append_i(separator);
3057     }
3058
3059     /**
3060      * Begins a marked content sequence. If property is <CODE>null</CODE> the mark will be of the type
3061      * <CODE>BMC</CODE> otherwise it will be <CODE>BDC</CODE>.
3062      * @param tag the tag
3063      * @param property the property
3064      * @param inline <CODE>true</CODE> to include the property in the content or <CODE>false</CODE>
3065      * to include the property in the resource dictionary with the possibility of reusing
3066      */

3067     public void beginMarkedContentSequence(PdfName tag, PdfDictionary property, boolean inline) {
3068         if (property == null) {
3069             content.append(tag.getBytes()).append(" BMC").append_i(separator);
3070             return;
3071         }
3072         content.append(tag.getBytes()).append(' ');
3073         if (inline)
3074             try {
3075                 property.toPdf(writer, content);
3076             }
3077             catch (Exception e) {
3078                 throw new ExceptionConverter(e);
3079             }
3080         else {
3081             PdfObject[] objs;
3082             if (writer.propertyExists(property))
3083                 objs = writer.addSimpleProperty(property, null);
3084             else
3085                 objs = writer.addSimpleProperty(property, writer.getPdfIndirectReference());
3086             PdfName name = (PdfName)objs[0];
3087             PageResources prs = getPageResources();
3088             name = prs.addProperty(name, (PdfIndirectReference)objs[1]);
3089             content.append(name.getBytes());
3090         }
3091         content.append(" BDC").append_i(separator);
3092         ++mcDepth;
3093     }
3094
3095     /**
3096      * This is just a shorthand to <CODE>beginMarkedContentSequence(tag, nullfalse)</CODE>.
3097      * @param tag the tag
3098      */

3099     public void beginMarkedContentSequence(PdfName tag) {
3100         beginMarkedContentSequence(tag, nullfalse);
3101     }
3102     
3103     /**
3104      * Checks for any dangling state: Mismatched save/restore state, begin/end text,
3105      * begin/end layer, or begin/end marked content sequence.
3106      * If found, this function will throw.  This function is called automatically
3107      * during a reset() (from Document.newPage() for example), and before writing 
3108      * itself out in toPdf().
3109      * One possible cause: not calling myPdfGraphics2D.dispose() will leave dangling
3110      *                     saveState() calls.
3111      * @since 2.1.6
3112      * @throws IllegalPdfSyntaxException (a runtime exception)
3113      */

3114     public void sanityCheck() {
3115         if (mcDepth != 0) {
3116             throw new IllegalPdfSyntaxException("Unbalanced marked content operators." );
3117         }
3118         if (inText) {
3119             throw new IllegalPdfSyntaxException("Unbalanced begin/end text operators." );
3120         }
3121         if (layerDepth != null && !layerDepth.isEmpty()) {
3122             throw new IllegalPdfSyntaxException("Unbalanced layer operators." );
3123         }
3124         if (!stateList.isEmpty()) {
3125             throw new IllegalPdfSyntaxException("Unbalanced save/restore state operators." );
3126         }
3127     }
3128 }
3129