001/*
002 * $Id: PdfContentByte.java 4784 2011-03-15 08:33:00Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045import java.awt.geom.AffineTransform;
046import java.awt.print.PrinterJob;
047import java.util.ArrayList;
048import java.util.HashMap;
049
050import com.itextpdf.text.Annotation;
051import com.itextpdf.text.BaseColor;
052import com.itextpdf.text.DocumentException;
053import com.itextpdf.text.Element;
054import com.itextpdf.text.ExceptionConverter;
055import com.itextpdf.text.Image;
056import com.itextpdf.text.ImgJBIG2;
057import com.itextpdf.text.Rectangle;
058import com.itextpdf.text.error_messages.MessageLocalization;
059import com.itextpdf.text.exceptions.IllegalPdfSyntaxException;
060import com.itextpdf.text.pdf.internal.PdfAnnotationsImp;
061import com.itextpdf.text.pdf.internal.PdfXConformanceImp;
062
063/**
064 * <CODE>PdfContentByte</CODE> is an object containing the user positioned
065 * text and graphic contents of a page. It knows how to apply the proper
066 * font encoding.
067 */
068
069public class PdfContentByte {
070
071    /**
072     * This class keeps the graphic state of the current page
073     */
074
075    static class GraphicState {
076
077        /** This is the font in use */
078        FontDetails fontDetails;
079
080        /** This is the color in use */
081        ColorDetails colorDetails;
082
083        /** This is the font size in use */
084        float size;
085
086        /** The x position of the text line matrix. */
087        protected float xTLM = 0;
088        /** The y position of the text line matrix. */
089        protected float yTLM = 0;
090
091        /** The current text leading. */
092        protected float leading = 0;
093
094        /** The current horizontal scaling */
095        protected float scale = 100;
096
097        /** The current character spacing */
098        protected float charSpace = 0;
099
100        /** The current word spacing */
101        protected float wordSpace = 0;
102
103        GraphicState() {
104        }
105
106        GraphicState(GraphicState cp) {
107            fontDetails = cp.fontDetails;
108            colorDetails = cp.colorDetails;
109            size = cp.size;
110            xTLM = cp.xTLM;
111            yTLM = cp.yTLM;
112            leading = cp.leading;
113            scale = cp.scale;
114            charSpace = cp.charSpace;
115            wordSpace = cp.wordSpace;
116        }
117    }
118
119    /** The alignment is center */
120    public static final int ALIGN_CENTER = Element.ALIGN_CENTER;
121
122    /** The alignment is left */
123    public static final int ALIGN_LEFT = Element.ALIGN_LEFT;
124
125    /** The alignment is right */
126    public static final int ALIGN_RIGHT = Element.ALIGN_RIGHT;
127
128    /** A possible line cap value */
129    public static final int LINE_CAP_BUTT = 0;
130    /** A possible line cap value */
131    public static final int LINE_CAP_ROUND = 1;
132    /** A possible line cap value */
133    public static final int LINE_CAP_PROJECTING_SQUARE = 2;
134
135    /** A possible line join value */
136    public static final int LINE_JOIN_MITER = 0;
137    /** A possible line join value */
138    public static final int LINE_JOIN_ROUND = 1;
139    /** A possible line join value */
140    public static final int LINE_JOIN_BEVEL = 2;
141
142    /** A possible text rendering value */
143    public static final int TEXT_RENDER_MODE_FILL = 0;
144    /** A possible text rendering value */
145    public static final int TEXT_RENDER_MODE_STROKE = 1;
146    /** A possible text rendering value */
147    public static final int TEXT_RENDER_MODE_FILL_STROKE = 2;
148    /** A possible text rendering value */
149    public static final int TEXT_RENDER_MODE_INVISIBLE = 3;
150    /** A possible text rendering value */
151    public static final int TEXT_RENDER_MODE_FILL_CLIP = 4;
152    /** A possible text rendering value */
153    public static final int TEXT_RENDER_MODE_STROKE_CLIP = 5;
154    /** A possible text rendering value */
155    public static final int TEXT_RENDER_MODE_FILL_STROKE_CLIP = 6;
156    /** A possible text rendering value */
157    public static final int TEXT_RENDER_MODE_CLIP = 7;
158
159    private static final float[] unitRect = {0, 0, 0, 1, 1, 0, 1, 1};
160    // membervariables
161
162    /** This is the actual content */
163    protected ByteBuffer content = new ByteBuffer();
164
165    /** This is the writer */
166    protected PdfWriter writer;
167
168    /** This is the PdfDocument */
169    protected PdfDocument pdf;
170
171    /** This is the GraphicState in use */
172    protected GraphicState state = new GraphicState();
173
174    /** The list were we save/restore the state */
175    protected ArrayList<GraphicState> stateList = new ArrayList<GraphicState>();
176
177    /** The list were we save/restore the layer depth */
178    protected ArrayList<Integer> layerDepth;
179
180    /** The separator between commands.
181     */
182    protected int separator = '\n';
183
184    private int mcDepth = 0;
185    private boolean inText = false;
186
187    private static HashMap<PdfName, String> abrev = new HashMap<PdfName, String>();
188
189    static {
190        abrev.put(PdfName.BITSPERCOMPONENT, "/BPC ");
191        abrev.put(PdfName.COLORSPACE, "/CS ");
192        abrev.put(PdfName.DECODE, "/D ");
193        abrev.put(PdfName.DECODEPARMS, "/DP ");
194        abrev.put(PdfName.FILTER, "/F ");
195        abrev.put(PdfName.HEIGHT, "/H ");
196        abrev.put(PdfName.IMAGEMASK, "/IM ");
197        abrev.put(PdfName.INTENT, "/Intent ");
198        abrev.put(PdfName.INTERPOLATE, "/I ");
199        abrev.put(PdfName.WIDTH, "/W ");
200    }
201
202    // constructors
203
204    /**
205     * Constructs a new <CODE>PdfContentByte</CODE>-object.
206     *
207     * @param wr the writer associated to this content
208     */
209
210    public PdfContentByte(PdfWriter wr) {
211        if (wr != null) {
212            writer = wr;
213            pdf = writer.getPdfDocument();
214        }
215    }
216
217    // methods to get the content of this object
218
219    /**
220     * Returns the <CODE>String</CODE> representation of this <CODE>PdfContentByte</CODE>-object.
221     *
222     * @return      a <CODE>String</CODE>
223     */
224
225    @Override
226    public String toString() {
227        return content.toString();
228    }
229
230    /**
231     * Gets the internal buffer.
232     * @return the internal buffer
233     */
234    public ByteBuffer getInternalBuffer() {
235        return content;
236    }
237
238    /** Returns the PDF representation of this <CODE>PdfContentByte</CODE>-object.
239     *
240     * @param writer the <CODE>PdfWriter</CODE>
241     * @return a <CODE>byte</CODE> array with the representation
242     */
243
244    public byte[] toPdf(PdfWriter writer) {
245        sanityCheck();
246        return content.toByteArray();
247    }
248
249    // methods to add graphical content
250
251    /**
252     * Adds the content of another <CODE>PdfContentByte</CODE>-object to this object.
253     *
254     * @param       other       another <CODE>PdfByteContent</CODE>-object
255     */
256
257    public void add(PdfContentByte other) {
258        if (other.writer != null && writer != other.writer)
259            throw new RuntimeException(MessageLocalization.getComposedMessage("inconsistent.writers.are.you.mixing.two.documents"));
260        content.append(other.content);
261    }
262
263    /**
264     * Gets the x position of the text line matrix.
265     *
266     * @return the x position of the text line matrix
267     */
268    public float getXTLM() {
269        return state.xTLM;
270    }
271
272    /**
273     * Gets the y position of the text line matrix.
274     *
275     * @return the y position of the text line matrix
276     */
277    public float getYTLM() {
278        return state.yTLM;
279    }
280
281    /**
282     * Gets the current text leading.
283     *
284     * @return the current text leading
285     */
286    public float getLeading() {
287        return state.leading;
288    }
289
290    /**
291     * Gets the current character spacing.
292     *
293     * @return the current character spacing
294     */
295    public float getCharacterSpacing() {
296        return state.charSpace;
297    }
298
299    /**
300     * Gets the current word spacing.
301     *
302     * @return the current word spacing
303     */
304    public float getWordSpacing() {
305        return state.wordSpace;
306    }
307
308    /**
309     * Gets the current character spacing.
310     *
311     * @return the current character spacing
312     */
313    public float getHorizontalScaling() {
314        return state.scale;
315    }
316
317    /**
318     * Changes the <VAR>Flatness</VAR>.
319     * <P>
320     * <VAR>Flatness</VAR> sets the maximum permitted distance in device pixels between the
321     * mathematically correct path and an approximation constructed from straight line segments.<BR>
322     *
323     * @param       flatness        a value
324     */
325
326    public void setFlatness(float flatness) {
327        if (flatness >= 0 && flatness <= 100) {
328            content.append(flatness).append(" i").append_i(separator);
329        }
330    }
331
332    /**
333     * Changes the <VAR>Line cap style</VAR>.
334     * <P>
335     * The <VAR>line cap style</VAR> specifies the shape to be used at the end of open subpaths
336     * when they are stroked.<BR>
337     * Allowed values are LINE_CAP_BUTT, LINE_CAP_ROUND and LINE_CAP_PROJECTING_SQUARE.<BR>
338     *
339     * @param       style       a value
340     */
341
342    public void setLineCap(int style) {
343        if (style >= 0 && style <= 2) {
344            content.append(style).append(" J").append_i(separator);
345        }
346    }
347
348    /**
349     * Changes the value of the <VAR>line dash pattern</VAR>.
350     * <P>
351     * The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
352     * It is specified by an <I>array</I> and a <I>phase</I>. The array specifies the length
353     * of the alternating dashes and gaps. The phase specifies the distance into the dash
354     * pattern to start the dash.<BR>
355     *
356     * @param       phase       the value of the phase
357     */
358
359    public void setLineDash(float phase) {
360        content.append("[] ").append(phase).append(" d").append_i(separator);
361    }
362
363    /**
364     * Changes the value of the <VAR>line dash pattern</VAR>.
365     * <P>
366     * The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
367     * It is specified by an <I>array</I> and a <I>phase</I>. The array specifies the length
368     * of the alternating dashes and gaps. The phase specifies the distance into the dash
369     * pattern to start the dash.<BR>
370     *
371     * @param       phase       the value of the phase
372     * @param       unitsOn     the number of units that must be 'on' (equals the number of units that must be 'off').
373     */
374
375    public void setLineDash(float unitsOn, float phase) {
376        content.append("[").append(unitsOn).append("] ").append(phase).append(" d").append_i(separator);
377    }
378
379    /**
380     * Changes the value of the <VAR>line dash pattern</VAR>.
381     * <P>
382     * The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
383     * It is specified by an <I>array</I> and a <I>phase</I>. The array specifies the length
384     * of the alternating dashes and gaps. The phase specifies the distance into the dash
385     * pattern to start the dash.<BR>
386     *
387     * @param       phase       the value of the phase
388     * @param       unitsOn     the number of units that must be 'on'
389     * @param       unitsOff    the number of units that must be 'off'
390     */
391
392    public void setLineDash(float unitsOn, float unitsOff, float phase) {
393        content.append("[").append(unitsOn).append(' ').append(unitsOff).append("] ").append(phase).append(" d").append_i(separator);
394    }
395
396    /**
397     * Changes the value of the <VAR>line dash pattern</VAR>.
398     * <P>
399     * The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
400     * It is specified by an <I>array</I> and a <I>phase</I>. The array specifies the length
401     * of the alternating dashes and gaps. The phase specifies the distance into the dash
402     * pattern to start the dash.<BR>
403     *
404     * @param       array       length of the alternating dashes and gaps
405     * @param       phase       the value of the phase
406     */
407
408    public final void setLineDash(float[] array, float phase) {
409        content.append("[");
410        for (int i = 0; i < array.length; i++) {
411            content.append(array[i]);
412            if (i < array.length - 1) content.append(' ');
413        }
414        content.append("] ").append(phase).append(" d").append_i(separator);
415    }
416
417    /**
418     * Changes the <VAR>Line join style</VAR>.
419     * <P>
420     * The <VAR>line join style</VAR> specifies the shape to be used at the corners of paths
421     * that are stroked.<BR>
422     * Allowed values are LINE_JOIN_MITER (Miter joins), LINE_JOIN_ROUND (Round joins) and LINE_JOIN_BEVEL (Bevel joins).<BR>
423     *
424     * @param       style       a value
425     */
426
427    public void setLineJoin(int style) {
428        if (style >= 0 && style <= 2) {
429            content.append(style).append(" j").append_i(separator);
430        }
431    }
432
433    /**
434     * Changes the <VAR>line width</VAR>.
435     * <P>
436     * The line width specifies the thickness of the line used to stroke a path and is measured
437     * in user space units.<BR>
438     *
439     * @param       w           a width
440     */
441
442    public void setLineWidth(float w) {
443        content.append(w).append(" w").append_i(separator);
444    }
445
446    /**
447     * Changes the <VAR>Miter limit</VAR>.
448     * <P>
449     * When two line segments meet at a sharp angle and mitered joins have been specified as the
450     * line join style, it is possible for the miter to extend far beyond the thickness of the line
451     * stroking path. The miter limit imposes a maximum on the ratio of the miter length to the line
452     * witdh. When the limit is exceeded, the join is converted from a miter to a bevel.<BR>
453     *
454     * @param       miterLimit      a miter limit
455     */
456
457    public void setMiterLimit(float miterLimit) {
458        if (miterLimit > 1) {
459            content.append(miterLimit).append(" M").append_i(separator);
460        }
461    }
462
463    /**
464     * Modify the current clipping path by intersecting it with the current path, using the
465     * nonzero winding number rule to determine which regions lie inside the clipping
466     * path.
467     */
468
469    public void clip() {
470        content.append("W").append_i(separator);
471    }
472
473    /**
474     * Modify the current clipping path by intersecting it with the current path, using the
475     * even-odd rule to determine which regions lie inside the clipping path.
476     */
477
478    public void eoClip() {
479        content.append("W*").append_i(separator);
480    }
481
482    /**
483     * Changes the currentgray tint for filling paths (device dependent colors!).
484     * <P>
485     * Sets the color space to <B>DeviceGray</B> (or the <B>DefaultGray</B> color space),
486     * and sets the gray tint to use for filling paths.</P>
487     *
488     * @param   gray    a value between 0 (black) and 1 (white)
489     */
490
491    public void setGrayFill(float gray) {
492        content.append(gray).append(" g").append_i(separator);
493    }
494
495    /**
496     * Changes the current gray tint for filling paths to black.
497     */
498
499    public void resetGrayFill() {
500        content.append("0 g").append_i(separator);
501    }
502
503    /**
504     * Changes the currentgray tint for stroking paths (device dependent colors!).
505     * <P>
506     * Sets the color space to <B>DeviceGray</B> (or the <B>DefaultGray</B> color space),
507     * and sets the gray tint to use for stroking paths.</P>
508     *
509     * @param   gray    a value between 0 (black) and 1 (white)
510     */
511
512    public void setGrayStroke(float gray) {
513        content.append(gray).append(" G").append_i(separator);
514    }
515
516    /**
517     * Changes the current gray tint for stroking paths to black.
518     */
519
520    public void resetGrayStroke() {
521        content.append("0 G").append_i(separator);
522    }
523
524    /**
525     * Helper to validate and write the RGB color components
526     * @param   red     the intensity of red. A value between 0 and 1
527     * @param   green   the intensity of green. A value between 0 and 1
528     * @param   blue    the intensity of blue. A value between 0 and 1
529     */
530    private void HelperRGB(float red, float green, float blue) {
531        PdfXConformanceImp.checkPDFXConformance(writer, PdfXConformanceImp.PDFXKEY_RGB, null);
532        if (red < 0)
533            red = 0.0f;
534        else if (red > 1.0f)
535            red = 1.0f;
536        if (green < 0)
537            green = 0.0f;
538        else if (green > 1.0f)
539            green = 1.0f;
540        if (blue < 0)
541            blue = 0.0f;
542        else if (blue > 1.0f)
543            blue = 1.0f;
544        content.append(red).append(' ').append(green).append(' ').append(blue);
545    }
546
547    /**
548     * Changes the current color for filling paths (device dependent colors!).
549     * <P>
550     * Sets the color space to <B>DeviceRGB</B> (or the <B>DefaultRGB</B> color space),
551     * and sets the color to use for filling paths.</P>
552     * <P>
553     * Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
554     * 1 (maximum intensity).</P>
555     *
556     * @param   red     the intensity of red. A value between 0 and 1
557     * @param   green   the intensity of green. A value between 0 and 1
558     * @param   blue    the intensity of blue. A value between 0 and 1
559     */
560
561    public void setRGBColorFillF(float red, float green, float blue) {
562        HelperRGB(red, green, blue);
563        content.append(" rg").append_i(separator);
564    }
565
566    /**
567     * Changes the current color for filling paths to black.
568     */
569
570    public void resetRGBColorFill() {
571        content.append("0 g").append_i(separator);
572    }
573
574    /**
575     * Changes the current color for stroking paths (device dependent colors!).
576     * <P>
577     * Sets the color space to <B>DeviceRGB</B> (or the <B>DefaultRGB</B> color space),
578     * and sets the color to use for stroking paths.</P>
579     * <P>
580     * Following the PDF manual, each operand must be a number between 0 (miniumum intensity) and
581     * 1 (maximum intensity).
582     *
583     * @param   red     the intensity of red. A value between 0 and 1
584     * @param   green   the intensity of green. A value between 0 and 1
585     * @param   blue    the intensity of blue. A value between 0 and 1
586     */
587
588    public void setRGBColorStrokeF(float red, float green, float blue) {
589        HelperRGB(red, green, blue);
590        content.append(" RG").append_i(separator);
591    }
592
593    /**
594     * Changes the current color for stroking paths to black.
595     *
596     */
597
598    public void resetRGBColorStroke() {
599        content.append("0 G").append_i(separator);
600    }
601
602    /**
603     * Helper to validate and write the CMYK color components.
604     *
605     * @param   cyan    the intensity of cyan. A value between 0 and 1
606     * @param   magenta the intensity of magenta. A value between 0 and 1
607     * @param   yellow  the intensity of yellow. A value between 0 and 1
608     * @param   black   the intensity of black. A value between 0 and 1
609     */
610    private void HelperCMYK(float cyan, float magenta, float yellow, float black) {
611        if (cyan < 0)
612            cyan = 0.0f;
613        else if (cyan > 1.0f)
614            cyan = 1.0f;
615        if (magenta < 0)
616            magenta = 0.0f;
617        else if (magenta > 1.0f)
618            magenta = 1.0f;
619        if (yellow < 0)
620            yellow = 0.0f;
621        else if (yellow > 1.0f)
622            yellow = 1.0f;
623        if (black < 0)
624            black = 0.0f;
625        else if (black > 1.0f)
626            black = 1.0f;
627        content.append(cyan).append(' ').append(magenta).append(' ').append(yellow).append(' ').append(black);
628    }
629
630    /**
631     * Changes the current color for filling paths (device dependent colors!).
632     * <P>
633     * Sets the color space to <B>DeviceCMYK</B> (or the <B>DefaultCMYK</B> color space),
634     * and sets the color to use for filling paths.</P>
635     * <P>
636     * Following the PDF manual, each operand must be a number between 0 (no ink) and
637     * 1 (maximum ink).</P>
638     *
639     * @param   cyan    the intensity of cyan. A value between 0 and 1
640     * @param   magenta the intensity of magenta. A value between 0 and 1
641     * @param   yellow  the intensity of yellow. A value between 0 and 1
642     * @param   black   the intensity of black. A value between 0 and 1
643     */
644
645    public void setCMYKColorFillF(float cyan, float magenta, float yellow, float black) {
646        HelperCMYK(cyan, magenta, yellow, black);
647        content.append(" k").append_i(separator);
648    }
649
650    /**
651     * Changes the current color for filling paths to black.
652     *
653     */
654
655    public void resetCMYKColorFill() {
656        content.append("0 0 0 1 k").append_i(separator);
657    }
658
659    /**
660     * Changes the current color for stroking paths (device dependent colors!).
661     * <P>
662     * Sets the color space to <B>DeviceCMYK</B> (or the <B>DefaultCMYK</B> color space),
663     * and sets the color to use for stroking paths.</P>
664     * <P>
665     * Following the PDF manual, each operand must be a number between 0 (miniumum intensity) and
666     * 1 (maximum intensity).
667     *
668     * @param   cyan    the intensity of cyan. A value between 0 and 1
669     * @param   magenta the intensity of magenta. A value between 0 and 1
670     * @param   yellow  the intensity of yellow. A value between 0 and 1
671     * @param   black   the intensity of black. A value between 0 and 1
672     */
673
674    public void setCMYKColorStrokeF(float cyan, float magenta, float yellow, float black) {
675        HelperCMYK(cyan, magenta, yellow, black);
676        content.append(" K").append_i(separator);
677    }
678
679    /**
680     * Changes the current color for stroking paths to black.
681     *
682     */
683
684    public void resetCMYKColorStroke() {
685        content.append("0 0 0 1 K").append_i(separator);
686    }
687
688    /**
689     * Move the current point <I>(x, y)</I>, omitting any connecting line segment.
690     *
691     * @param       x               new x-coordinate
692     * @param       y               new y-coordinate
693     */
694
695    public void moveTo(float x, float y) {
696        content.append(x).append(' ').append(y).append(" m").append_i(separator);
697    }
698
699    /**
700     * Appends a straight line segment from the current point <I>(x, y)</I>. The new current
701     * point is <I>(x, y)</I>.
702     *
703     * @param       x               new x-coordinate
704     * @param       y               new y-coordinate
705     */
706
707    public void lineTo(float x, float y) {
708        content.append(x).append(' ').append(y).append(" l").append_i(separator);
709    }
710
711    /**
712     * Appends a B&#xea;zier curve to the path, starting from the current point.
713     *
714     * @param       x1      x-coordinate of the first control point
715     * @param       y1      y-coordinate of the first control point
716     * @param       x2      x-coordinate of the second control point
717     * @param       y2      y-coordinate of the second control point
718     * @param       x3      x-coordinate of the ending point (= new current point)
719     * @param       y3      y-coordinate of the ending point (= new current point)
720     */
721
722    public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) {
723        content.append(x1).append(' ').append(y1).append(' ').append(x2).append(' ').append(y2).append(' ').append(x3).append(' ').append(y3).append(" c").append_i(separator);
724    }
725
726    /**
727     * Appends a B&#xea;zier curve to the path, starting from the current point.
728     *
729     * @param       x2      x-coordinate of the second control point
730     * @param       y2      y-coordinate of the second control point
731     * @param       x3      x-coordinate of the ending point (= new current point)
732     * @param       y3      y-coordinate of the ending point (= new current point)
733     */
734
735    public void curveTo(float x2, float y2, float x3, float y3) {
736        content.append(x2).append(' ').append(y2).append(' ').append(x3).append(' ').append(y3).append(" v").append_i(separator);
737    }
738
739    /**
740     * Appends a B&#xea;zier curve to the path, starting from the current point.
741     *
742     * @param       x1      x-coordinate of the first control point
743     * @param       y1      y-coordinate of the first control point
744     * @param       x3      x-coordinate of the ending point (= new current point)
745     * @param       y3      y-coordinate of the ending point (= new current point)
746     */
747
748    public void curveFromTo(float x1, float y1, float x3, float y3) {
749        content.append(x1).append(' ').append(y1).append(' ').append(x3).append(' ').append(y3).append(" y").append_i(separator);
750    }
751
752    /** Draws a circle. The endpoint will (x+r, y).
753     *
754     * @param x x center of circle
755     * @param y y center of circle
756     * @param r radius of circle
757     */
758    public void circle(float x, float y, float r) {
759        float b = 0.5523f;
760        moveTo(x + r, y);
761        curveTo(x + r, y + r * b, x + r * b, y + r, x, y + r);
762        curveTo(x - r * b, y + r, x - r, y + r * b, x - r, y);
763        curveTo(x - r, y - r * b, x - r * b, y - r, x, y - r);
764        curveTo(x + r * b, y - r, x + r, y - r * b, x + r, y);
765    }
766
767
768
769    /**
770     * Adds a rectangle to the current path.
771     *
772     * @param       x       x-coordinate of the starting point
773     * @param       y       y-coordinate of the starting point
774     * @param       w       width
775     * @param       h       height
776     */
777
778    public void rectangle(float x, float y, float w, float h) {
779        content.append(x).append(' ').append(y).append(' ').append(w).append(' ').append(h).append(" re").append_i(separator);
780    }
781
782    private boolean compareColors(BaseColor c1, BaseColor c2) {
783        if (c1 == null && c2 == null)
784            return true;
785        if (c1 == null || c2 == null)
786            return false;
787        if (c1 instanceof ExtendedColor)
788            return c1.equals(c2);
789        return c2.equals(c1);
790    }
791
792    /**
793     * Adds a variable width border to the current path.
794     * Only use if {@link com.itextpdf.text.Rectangle#isUseVariableBorders() Rectangle.isUseVariableBorders}
795     * = true.
796     * @param rect a <CODE>Rectangle</CODE>
797     */
798    public void variableRectangle(Rectangle rect) {
799        float t = rect.getTop();
800        float b = rect.getBottom();
801        float r = rect.getRight();
802        float l = rect.getLeft();
803        float wt = rect.getBorderWidthTop();
804        float wb = rect.getBorderWidthBottom();
805        float wr = rect.getBorderWidthRight();
806        float wl = rect.getBorderWidthLeft();
807        BaseColor ct = rect.getBorderColorTop();
808        BaseColor cb = rect.getBorderColorBottom();
809        BaseColor cr = rect.getBorderColorRight();
810        BaseColor cl = rect.getBorderColorLeft();
811        saveState();
812        setLineCap(PdfContentByte.LINE_CAP_BUTT);
813        setLineJoin(PdfContentByte.LINE_JOIN_MITER);
814        float clw = 0;
815        boolean cdef = false;
816        BaseColor ccol = null;
817        boolean cdefi = false;
818        BaseColor cfil = null;
819        // draw top
820        if (wt > 0) {
821            setLineWidth(clw = wt);
822            cdef = true;
823            if (ct == null)
824                resetRGBColorStroke();
825            else
826                setColorStroke(ct);
827            ccol = ct;
828            moveTo(l, t - wt / 2f);
829            lineTo(r, t - wt / 2f);
830            stroke();
831        }
832
833        // Draw bottom
834        if (wb > 0) {
835            if (wb != clw)
836                setLineWidth(clw = wb);
837            if (!cdef || !compareColors(ccol, cb)) {
838                cdef = true;
839                if (cb == null)
840                    resetRGBColorStroke();
841                else
842                    setColorStroke(cb);
843                ccol = cb;
844            }
845            moveTo(r, b + wb / 2f);
846            lineTo(l, b + wb / 2f);
847            stroke();
848        }
849
850        // Draw right
851        if (wr > 0) {
852            if (wr != clw)
853                setLineWidth(clw = wr);
854            if (!cdef || !compareColors(ccol, cr)) {
855                cdef = true;
856                if (cr == null)
857                    resetRGBColorStroke();
858                else
859                    setColorStroke(cr);
860                ccol = cr;
861            }
862            boolean bt = compareColors(ct, cr);
863            boolean bb = compareColors(cb, cr);
864            moveTo(r - wr / 2f, bt ? t : t - wt);
865            lineTo(r - wr / 2f, bb ? b : b + wb);
866            stroke();
867            if (!bt || !bb) {
868                cdefi = true;
869                if (cr == null)
870                    resetRGBColorFill();
871                else
872                    setColorFill(cr);
873                cfil = cr;
874                if (!bt) {
875                    moveTo(r, t);
876                    lineTo(r, t - wt);
877                    lineTo(r - wr, t - wt);
878                    fill();
879                }
880                if (!bb) {
881                    moveTo(r, b);
882                    lineTo(r, b + wb);
883                    lineTo(r - wr, b + wb);
884                    fill();
885                }
886            }
887        }
888
889        // Draw Left
890        if (wl > 0) {
891            if (wl != clw)
892                setLineWidth(wl);
893            if (!cdef || !compareColors(ccol, cl)) {
894                if (cl == null)
895                    resetRGBColorStroke();
896                else
897                    setColorStroke(cl);
898            }
899            boolean bt = compareColors(ct, cl);
900            boolean bb = compareColors(cb, cl);
901            moveTo(l + wl / 2f, bt ? t : t - wt);
902            lineTo(l + wl / 2f, bb ? b : b + wb);
903            stroke();
904            if (!bt || !bb) {
905                if (!cdefi || !compareColors(cfil, cl)) {
906                    if (cl == null)
907                        resetRGBColorFill();
908                    else
909                        setColorFill(cl);
910                }
911                if (!bt) {
912                    moveTo(l, t);
913                    lineTo(l, t - wt);
914                    lineTo(l + wl, t - wt);
915                    fill();
916                }
917                if (!bb) {
918                    moveTo(l, b);
919                    lineTo(l, b + wb);
920                    lineTo(l + wl, b + wb);
921                    fill();
922                }
923            }
924        }
925        restoreState();
926    }
927
928    /**
929     * Adds a border (complete or partially) to the current path..
930     *
931     * @param       rectangle       a <CODE>Rectangle</CODE>
932     */
933
934    public void rectangle(Rectangle rectangle) {
935        // the coordinates of the border are retrieved
936        float x1 = rectangle.getLeft();
937        float y1 = rectangle.getBottom();
938        float x2 = rectangle.getRight();
939        float y2 = rectangle.getTop();
940
941        // the backgroundcolor is set
942        BaseColor background = rectangle.getBackgroundColor();
943        if (background != null) {
944                saveState();
945            setColorFill(background);
946            rectangle(x1, y1, x2 - x1, y2 - y1);
947            fill();
948            restoreState();
949        }
950
951        // if the element hasn't got any borders, nothing is added
952        if (! rectangle.hasBorders()) {
953            return;
954        }
955
956        // if any of the individual border colors are set
957        // we draw the borders all around using the
958        // different colors
959        if (rectangle.isUseVariableBorders()) {
960            variableRectangle(rectangle);
961        }
962        else {
963            // the width is set to the width of the element
964            if (rectangle.getBorderWidth() != Rectangle.UNDEFINED) {
965                setLineWidth(rectangle.getBorderWidth());
966            }
967
968            // the color is set to the color of the element
969            BaseColor color = rectangle.getBorderColor();
970            if (color != null) {
971                setColorStroke(color);
972            }
973
974            // if the box is a rectangle, it is added as a rectangle
975            if (rectangle.hasBorder(Rectangle.BOX)) {
976               rectangle(x1, y1, x2 - x1, y2 - y1);
977            }
978            // if the border isn't a rectangle, the different sides are added apart
979            else {
980                if (rectangle.hasBorder(Rectangle.RIGHT)) {
981                    moveTo(x2, y1);
982                    lineTo(x2, y2);
983                }
984                if (rectangle.hasBorder(Rectangle.LEFT)) {
985                    moveTo(x1, y1);
986                    lineTo(x1, y2);
987                }
988                if (rectangle.hasBorder(Rectangle.BOTTOM)) {
989                    moveTo(x1, y1);
990                    lineTo(x2, y1);
991                }
992                if (rectangle.hasBorder(Rectangle.TOP)) {
993                    moveTo(x1, y2);
994                    lineTo(x2, y2);
995                }
996            }
997
998            stroke();
999
1000            if (color != null) {
1001                resetRGBColorStroke();
1002            }
1003        }
1004    }
1005
1006    /**
1007     * Closes the current subpath by appending a straight line segment from the current point
1008     * to the starting point of the subpath.
1009     */
1010
1011    public void closePath() {
1012        content.append("h").append_i(separator);
1013    }
1014
1015    /**
1016     * Ends the path without filling or stroking it.
1017     */
1018
1019    public void newPath() {
1020        content.append("n").append_i(separator);
1021    }
1022
1023    /**
1024     * Strokes the path.
1025     */
1026
1027    public void stroke() {
1028        content.append("S").append_i(separator);
1029    }
1030
1031    /**
1032     * Closes the path and strokes it.
1033     */
1034
1035    public void closePathStroke() {
1036        content.append("s").append_i(separator);
1037    }
1038
1039    /**
1040     * Fills the path, using the non-zero winding number rule to determine the region to fill.
1041     */
1042
1043    public void fill() {
1044        content.append("f").append_i(separator);
1045    }
1046
1047    /**
1048     * Fills the path, using the even-odd rule to determine the region to fill.
1049     */
1050
1051    public void eoFill() {
1052        content.append("f*").append_i(separator);
1053    }
1054
1055    /**
1056     * Fills the path using the non-zero winding number rule to determine the region to fill and strokes it.
1057     */
1058
1059    public void fillStroke() {
1060        content.append("B").append_i(separator);
1061    }
1062
1063    /**
1064     * Closes the path, fills it using the non-zero winding number rule to determine the region to fill and strokes it.
1065     */
1066
1067    public void closePathFillStroke() {
1068        content.append("b").append_i(separator);
1069    }
1070
1071    /**
1072     * Fills the path, using the even-odd rule to determine the region to fill and strokes it.
1073     */
1074
1075    public void eoFillStroke() {
1076        content.append("B*").append_i(separator);
1077    }
1078
1079    /**
1080     * Closes the path, fills it using the even-odd rule to determine the region to fill and strokes it.
1081     */
1082
1083    public void closePathEoFillStroke() {
1084        content.append("b*").append_i(separator);
1085    }
1086
1087    /**
1088     * Adds an <CODE>Image</CODE> to the page. The <CODE>Image</CODE> must have
1089     * absolute positioning.
1090     * @param image the <CODE>Image</CODE> object
1091     * @throws DocumentException if the <CODE>Image</CODE> does not have absolute positioning
1092     */
1093    public void addImage(Image image) throws DocumentException {
1094        addImage(image, false);
1095    }
1096
1097    /**
1098     * Adds an <CODE>Image</CODE> to the page. The <CODE>Image</CODE> must have
1099     * absolute positioning. The image can be placed inline.
1100     * @param image the <CODE>Image</CODE> object
1101     * @param inlineImage <CODE>true</CODE> to place this image inline, <CODE>false</CODE> otherwise
1102     * @throws DocumentException if the <CODE>Image</CODE> does not have absolute positioning
1103     */
1104    public void addImage(Image image, boolean inlineImage) throws DocumentException {
1105        if (!image.hasAbsoluteY())
1106            throw new DocumentException(MessageLocalization.getComposedMessage("the.image.must.have.absolute.positioning"));
1107        float matrix[] = image.matrix();
1108        matrix[Image.CX] = image.getAbsoluteX() - matrix[Image.CX];
1109        matrix[Image.CY] = image.getAbsoluteY() - matrix[Image.CY];
1110        addImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], inlineImage);
1111    }
1112
1113    /**
1114     * Adds an <CODE>Image</CODE> to the page. The positioning of the <CODE>Image</CODE>
1115     * is done with the transformation matrix. To position an <CODE>image</CODE> at (x,y)
1116     * use addImage(image, image_width, 0, 0, image_height, x, y).
1117     * @param image the <CODE>Image</CODE> object
1118     * @param a an element of the transformation matrix
1119     * @param b an element of the transformation matrix
1120     * @param c an element of the transformation matrix
1121     * @param d an element of the transformation matrix
1122     * @param e an element of the transformation matrix
1123     * @param f an element of the transformation matrix
1124     * @throws DocumentException on error
1125     */
1126    public void addImage(Image image, float a, float b, float c, float d, float e, float f) throws DocumentException {
1127        addImage(image, a, b, c, d, e, f, false);
1128    }
1129    
1130    /**
1131     * adds an image with the given matrix.
1132     * @param image image to add
1133     * @param transform transform to apply to the template prior to adding it.
1134     * @since 5.0.1
1135     */
1136    public void addImage(Image image, AffineTransform transform) throws DocumentException {
1137        double matrix[] = new double[6];
1138        transform.getMatrix(matrix);
1139        addImage( image, (float)matrix[0], (float)matrix[1], (float)matrix[2], 
1140                          (float)matrix[3], (float)matrix[4],(float) matrix[5], false );
1141    }
1142
1143    /**
1144     * Adds an <CODE>Image</CODE> to the page. The positioning of the <CODE>Image</CODE>
1145     * is done with the transformation matrix. To position an <CODE>image</CODE> at (x,y)
1146     * use addImage(image, image_width, 0, 0, image_height, x, y). The image can be placed inline.
1147     * @param image the <CODE>Image</CODE> object
1148     * @param a an element of the transformation matrix
1149     * @param b an element of the transformation matrix
1150     * @param c an element of the transformation matrix
1151     * @param d an element of the transformation matrix
1152     * @param e an element of the transformation matrix
1153     * @param f an element of the transformation matrix
1154     * @param inlineImage <CODE>true</CODE> to place this image inline, <CODE>false</CODE> otherwise
1155     * @throws DocumentException on error
1156     */
1157    public void addImage(Image image, float a, float b, float c, float d, float e, float f, boolean inlineImage) throws DocumentException {
1158        try {
1159            if (image.getLayer() != null)
1160                beginLayer(image.getLayer());
1161            if (image.isImgTemplate()) {
1162                writer.addDirectImageSimple(image);
1163                PdfTemplate template = image.getTemplateData();
1164                float w = template.getWidth();
1165                float h = template.getHeight();
1166                addTemplate(template, a / w, b / w, c / h, d / h, e, f);
1167            }
1168            else {
1169                content.append("q ");
1170                content.append(a).append(' ');
1171                content.append(b).append(' ');
1172                content.append(c).append(' ');
1173                content.append(d).append(' ');
1174                content.append(e).append(' ');
1175                content.append(f).append(" cm");
1176                if (inlineImage) {
1177                    content.append("\nBI\n");
1178                    PdfImage pimage = new PdfImage(image, "", null);
1179                    if (image instanceof ImgJBIG2) {
1180                        byte[] globals = ((ImgJBIG2)image).getGlobalBytes();
1181                        if (globals != null) {
1182                                PdfDictionary decodeparms = new PdfDictionary();
1183                                decodeparms.put(PdfName.JBIG2GLOBALS, writer.getReferenceJBIG2Globals(globals));
1184                                pimage.put(PdfName.DECODEPARMS, decodeparms);
1185                        }
1186                    }
1187                    for (Object element : pimage.getKeys()) {
1188                        PdfName key = (PdfName)element;
1189                        PdfObject value = pimage.get(key);
1190                        String s = abrev.get(key);
1191                        if (s == null)
1192                            continue;
1193                        content.append(s);
1194                        boolean check = true;
1195                        if (key.equals(PdfName.COLORSPACE) && value.isArray()) {
1196                            PdfArray ar = (PdfArray)value;
1197                            if (ar.size() == 4
1198                                && PdfName.INDEXED.equals(ar.getAsName(0))
1199                                && ar.getPdfObject(1).isName()
1200                                && ar.getPdfObject(2).isNumber()
1201                                && ar.getPdfObject(3).isString()
1202                            ) {
1203                                check = false;
1204                            }
1205
1206                        }
1207                        if (check && key.equals(PdfName.COLORSPACE) && !value.isName()) {
1208                            PdfName cs = writer.getColorspaceName();
1209                            PageResources prs = getPageResources();
1210                            prs.addColor(cs, writer.addToBody(value).getIndirectReference());
1211                            value = cs;
1212                        }
1213                        value.toPdf(null, content);
1214                        content.append('\n');
1215                    }
1216                    content.append("ID\n");
1217                    pimage.writeContent(content);
1218                    content.append("\nEI\nQ").append_i(separator);
1219                }
1220                else {
1221                    PdfName name;
1222                    PageResources prs = getPageResources();
1223                    Image maskImage = image.getImageMask();
1224                    if (maskImage != null) {
1225                        name = writer.addDirectImageSimple(maskImage);
1226                        prs.addXObject(name, writer.getImageReference(name));
1227                    }
1228                    name = writer.addDirectImageSimple(image);
1229                    name = prs.addXObject(name, writer.getImageReference(name));
1230                    content.append(' ').append(name.getBytes()).append(" Do Q").append_i(separator);
1231                }
1232            }
1233            if (image.hasBorders()) {
1234                saveState();
1235                float w = image.getWidth();
1236                float h = image.getHeight();
1237                concatCTM(a / w, b / w, c / h, d / h, e, f);
1238                rectangle(image);
1239                restoreState();
1240            }
1241            if (image.getLayer() != null)
1242                endLayer();
1243            Annotation annot = image.getAnnotation();
1244            if (annot == null)
1245                return;
1246            float[] r = new float[unitRect.length];
1247            for (int k = 0; k < unitRect.length; k += 2) {
1248                r[k] = a * unitRect[k] + c * unitRect[k + 1] + e;
1249                r[k + 1] = b * unitRect[k] + d * unitRect[k + 1] + f;
1250            }
1251            float llx = r[0];
1252            float lly = r[1];
1253            float urx = llx;
1254            float ury = lly;
1255            for (int k = 2; k < r.length; k += 2) {
1256                llx = Math.min(llx, r[k]);
1257                lly = Math.min(lly, r[k + 1]);
1258                urx = Math.max(urx, r[k]);
1259                ury = Math.max(ury, r[k + 1]);
1260            }
1261            annot = new Annotation(annot);
1262            annot.setDimensions(llx, lly, urx, ury);
1263            PdfAnnotation an = PdfAnnotationsImp.convertAnnotation(writer, annot, new Rectangle(llx, lly, urx, ury));
1264            if (an == null)
1265                return;
1266            addAnnotation(an);
1267        }
1268        catch (Exception ee) {
1269            throw new DocumentException(ee);
1270        }
1271    }
1272
1273    /**
1274     * Makes this <CODE>PdfContentByte</CODE> empty.
1275     * Calls <code>reset( true )</code>
1276     */
1277    public void reset() {
1278        reset( true );
1279    }
1280
1281    /**
1282     * Makes this <CODE>PdfContentByte</CODE> empty.
1283     * @param validateContent will call <code>sanityCheck()</code> if true.
1284     * @since 2.1.6
1285     */
1286    public void reset( boolean validateContent ) {
1287        content.reset();
1288        if (validateContent) {
1289                sanityCheck();
1290        }
1291        state = new GraphicState();
1292    }
1293
1294
1295    /**
1296     * Starts the writing of text.
1297     */
1298    public void beginText() {
1299        if (inText) {
1300                throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.begin.end.text.operators"));
1301        }
1302        inText = true;
1303        state.xTLM = 0;
1304        state.yTLM = 0;
1305        content.append("BT").append_i(separator);
1306    }
1307
1308    /**
1309     * Ends the writing of text and makes the current font invalid.
1310     */
1311    public void endText() {
1312        if (!inText) {
1313                throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.begin.end.text.operators"));
1314        }
1315        inText = false;
1316        content.append("ET").append_i(separator);
1317    }
1318
1319    /**
1320     * Saves the graphic state. <CODE>saveState</CODE> and
1321     * <CODE>restoreState</CODE> must be balanced.
1322     */
1323    public void saveState() {
1324        content.append("q").append_i(separator);
1325        stateList.add(new GraphicState(state));
1326    }
1327
1328    /**
1329     * Restores the graphic state. <CODE>saveState</CODE> and
1330     * <CODE>restoreState</CODE> must be balanced.
1331     */
1332    public void restoreState() {
1333        content.append("Q").append_i(separator);
1334        int idx = stateList.size() - 1;
1335        if (idx < 0)
1336            throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.save.restore.state.operators"));
1337        state = stateList.get(idx);
1338        stateList.remove(idx);
1339    }
1340
1341    /**
1342     * Sets the character spacing parameter.
1343     *
1344     * @param       charSpace           a parameter
1345     */
1346    public void setCharacterSpacing(float charSpace) {
1347        state.charSpace = charSpace;
1348        content.append(charSpace).append(" Tc").append_i(separator);
1349    }
1350
1351    /**
1352     * Sets the word spacing parameter.
1353     *
1354     * @param       wordSpace           a parameter
1355     */
1356    public void setWordSpacing(float wordSpace) {
1357        state.wordSpace = wordSpace;
1358        content.append(wordSpace).append(" Tw").append_i(separator);
1359    }
1360
1361    /**
1362     * Sets the horizontal scaling parameter.
1363     *
1364     * @param       scale               a parameter
1365     */
1366    public void setHorizontalScaling(float scale) {
1367        state.scale = scale;
1368        content.append(scale).append(" Tz").append_i(separator);
1369    }
1370
1371    /**
1372     * Sets the text leading parameter.
1373     * <P>
1374     * The leading parameter is measured in text space units. It specifies the vertical distance
1375     * between the baselines of adjacent lines of text.</P>
1376     *
1377     * @param       leading         the new leading
1378     */
1379    public void setLeading(float leading) {
1380        state.leading = leading;
1381        content.append(leading).append(" TL").append_i(separator);
1382    }
1383
1384    /**
1385     * Set the font and the size for the subsequent text writing.
1386     *
1387     * @param bf the font
1388     * @param size the font size in points
1389     */
1390    public void setFontAndSize(BaseFont bf, float size) {
1391        checkWriter();
1392        if (size < 0.0001f && size > -0.0001f)
1393            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("font.size.too.small.1", String.valueOf(size)));
1394        state.size = size;
1395        state.fontDetails = writer.addSimple(bf);
1396        PageResources prs = getPageResources();
1397        PdfName name = state.fontDetails.getFontName();
1398        name = prs.addFont(name, state.fontDetails.getIndirectReference());
1399        content.append(name.getBytes()).append(' ').append(size).append(" Tf").append_i(separator);
1400    }
1401
1402    /**
1403     * Sets the text rendering parameter.
1404     *
1405     * @param       rendering               a parameter
1406     */
1407    public void setTextRenderingMode(int rendering) {
1408        content.append(rendering).append(" Tr").append_i(separator);
1409    }
1410
1411    /**
1412     * Sets the text rise parameter.
1413     * <P>
1414     * This allows to write text in subscript or superscript mode.</P>
1415     *
1416     * @param       rise                a parameter
1417     */
1418    public void setTextRise(float rise) {
1419        content.append(rise).append(" Ts").append_i(separator);
1420    }
1421
1422    /**
1423     * A helper to insert into the content stream the <CODE>text</CODE>
1424     * converted to bytes according to the font's encoding.
1425     *
1426     * @param text the text to write
1427     */
1428    private void showText2(String text) {
1429        if (state.fontDetails == null)
1430            throw new NullPointerException(MessageLocalization.getComposedMessage("font.and.size.must.be.set.before.writing.any.text"));
1431        byte b[] = state.fontDetails.convertToBytes(text);
1432        escapeString(b, content);
1433    }
1434
1435    /**
1436     * Shows the <CODE>text</CODE>.
1437     *
1438     * @param text the text to write
1439     */
1440    public void showText(String text) {
1441        showText2(text);
1442        content.append("Tj").append_i(separator);
1443    }
1444
1445    /**
1446     * Constructs a kern array for a text in a certain font
1447     * @param text the text
1448     * @param font the font
1449     * @return a PdfTextArray
1450     */
1451    public static PdfTextArray getKernArray(String text, BaseFont font) {
1452        PdfTextArray pa = new PdfTextArray();
1453        StringBuffer acc = new StringBuffer();
1454        int len = text.length() - 1;
1455        char c[] = text.toCharArray();
1456        if (len >= 0)
1457            acc.append(c, 0, 1);
1458        for (int k = 0; k < len; ++k) {
1459            char c2 = c[k + 1];
1460            int kern = font.getKerning(c[k], c2);
1461            if (kern == 0) {
1462                acc.append(c2);
1463            }
1464            else {
1465                pa.add(acc.toString());
1466                acc.setLength(0);
1467                acc.append(c, k + 1, 1);
1468                pa.add(-kern);
1469            }
1470        }
1471        pa.add(acc.toString());
1472        return pa;
1473    }
1474
1475    /**
1476     * Shows the <CODE>text</CODE> kerned.
1477     *
1478     * @param text the text to write
1479     */
1480    public void showTextKerned(String text) {
1481        if (state.fontDetails == null)
1482            throw new NullPointerException(MessageLocalization.getComposedMessage("font.and.size.must.be.set.before.writing.any.text"));
1483        BaseFont bf = state.fontDetails.getBaseFont();
1484        if (bf.hasKernPairs())
1485            showText(getKernArray(text, bf));
1486        else
1487            showText(text);
1488    }
1489
1490    /**
1491     * Moves to the next line and shows <CODE>text</CODE>.
1492     *
1493     * @param text the text to write
1494     */
1495    public void newlineShowText(String text) {
1496        state.yTLM -= state.leading;
1497        showText2(text);
1498        content.append("'").append_i(separator);
1499    }
1500
1501    /**
1502     * Moves to the next line and shows text string, using the given values of the character and word spacing parameters.
1503     *
1504     * @param       wordSpacing     a parameter
1505     * @param       charSpacing     a parameter
1506     * @param text the text to write
1507     */
1508    public void newlineShowText(float wordSpacing, float charSpacing, String text) {
1509        state.yTLM -= state.leading;
1510        content.append(wordSpacing).append(' ').append(charSpacing);
1511        showText2(text);
1512        content.append("\"").append_i(separator);
1513
1514        // The " operator sets charSpace and wordSpace into graphics state
1515        // (cfr PDF reference v1.6, table 5.6)
1516        state.charSpace = charSpacing;
1517        state.wordSpace = wordSpacing;
1518    }
1519
1520    /**
1521     * Changes the text matrix.
1522     * <P>
1523     * Remark: this operation also initializes the current point position.</P>
1524     *
1525     * @param       a           operand 1,1 in the matrix
1526     * @param       b           operand 1,2 in the matrix
1527     * @param       c           operand 2,1 in the matrix
1528     * @param       d           operand 2,2 in the matrix
1529     * @param       x           operand 3,1 in the matrix
1530     * @param       y           operand 3,2 in the matrix
1531     */
1532    public void setTextMatrix(float a, float b, float c, float d, float x, float y) {
1533        state.xTLM = x;
1534        state.yTLM = y;
1535        content.append(a).append(' ').append(b).append_i(' ')
1536        .append(c).append_i(' ').append(d).append_i(' ')
1537        .append(x).append_i(' ').append(y).append(" Tm").append_i(separator);
1538    }
1539
1540    /**
1541     * Changes the text matrix.
1542     * <P>
1543     * @param transform overwrite the current text matrix with this one
1544     * @since 5.0.1
1545     */
1546    public void setTextMatrix(AffineTransform transform) {
1547        double matrix[] = new double[6];
1548        transform.getMatrix(matrix);
1549        setTextMatrix((float)matrix[0], (float)matrix[1], (float)matrix[2], 
1550                              (float)matrix[3], (float)matrix[4], (float)matrix[5] );
1551    }
1552    /**
1553     * Changes the text matrix. The first four parameters are {1,0,0,1}.
1554     * <P>
1555     * Remark: this operation also initializes the current point position.</P>
1556     *
1557     * @param       x           operand 3,1 in the matrix
1558     * @param       y           operand 3,2 in the matrix
1559     */
1560    public void setTextMatrix(float x, float y) {
1561        setTextMatrix(1, 0, 0, 1, x, y);
1562    }
1563
1564    /**
1565     * Moves to the start of the next line, offset from the start of the current line.
1566     *
1567     * @param       x           x-coordinate of the new current point
1568     * @param       y           y-coordinate of the new current point
1569     */
1570    public void moveText(float x, float y) {
1571        state.xTLM += x;
1572        state.yTLM += y;
1573        content.append(x).append(' ').append(y).append(" Td").append_i(separator);
1574    }
1575
1576    /**
1577     * Moves to the start of the next line, offset from the start of the current line.
1578     * <P>
1579     * As a side effect, this sets the leading parameter in the text state.</P>
1580     *
1581     * @param       x           offset of the new current point
1582     * @param       y           y-coordinate of the new current point
1583     */
1584    public void moveTextWithLeading(float x, float y) {
1585        state.xTLM += x;
1586        state.yTLM += y;
1587        state.leading = -y;
1588        content.append(x).append(' ').append(y).append(" TD").append_i(separator);
1589    }
1590
1591    /**
1592     * Moves to the start of the next line.
1593     */
1594    public void newlineText() {
1595        state.yTLM -= state.leading;
1596        content.append("T*").append_i(separator);
1597    }
1598
1599    /**
1600     * Gets the size of this content.
1601     *
1602     * @return the size of the content
1603     */
1604    int size() {
1605        return content.size();
1606    }
1607
1608    /**
1609     * Escapes a <CODE>byte</CODE> array according to the PDF conventions.
1610     *
1611     * @param b the <CODE>byte</CODE> array to escape
1612     * @return an escaped <CODE>byte</CODE> array
1613     */
1614    static byte[] escapeString(byte b[]) {
1615        ByteBuffer content = new ByteBuffer();
1616        escapeString(b, content);
1617        return content.toByteArray();
1618    }
1619
1620    /**
1621     * Escapes a <CODE>byte</CODE> array according to the PDF conventions.
1622     *
1623     * @param b the <CODE>byte</CODE> array to escape
1624     * @param content the content
1625     */
1626    static void escapeString(byte b[], ByteBuffer content) {
1627        content.append_i('(');
1628        for (int k = 0; k < b.length; ++k) {
1629            byte c = b[k];
1630            switch (c) {
1631                case '\r':
1632                    content.append("\\r");
1633                    break;
1634                case '\n':
1635                    content.append("\\n");
1636                    break;
1637                case '\t':
1638                    content.append("\\t");
1639                    break;
1640                case '\b':
1641                    content.append("\\b");
1642                    break;
1643                case '\f':
1644                    content.append("\\f");
1645                    break;
1646                case '(':
1647                case ')':
1648                case '\\':
1649                    content.append_i('\\').append_i(c);
1650                    break;
1651                default:
1652                    content.append_i(c);
1653            }
1654        }
1655        content.append(")");
1656    }
1657
1658    /**
1659     * Adds a named outline to the document.
1660     *
1661     * @param outline the outline
1662     * @param name the name for the local destination
1663     */
1664    public void addOutline(PdfOutline outline, String name) {
1665        checkWriter();
1666        pdf.addOutline(outline, name);
1667    }
1668    /**
1669     * Gets the root outline.
1670     *
1671     * @return the root outline
1672     */
1673    public PdfOutline getRootOutline() {
1674        checkWriter();
1675        return pdf.getRootOutline();
1676    }
1677
1678    /**
1679     * Computes the width of the given string taking in account
1680     * the current values of "Character spacing", "Word Spacing"
1681     * and "Horizontal Scaling".
1682     * The additional spacing is not computed for the last character
1683     * of the string.
1684     * @param text the string to get width of
1685     * @param kerned the kerning option
1686     * @return the width
1687     */
1688
1689    public float getEffectiveStringWidth(String text, boolean kerned) {
1690        BaseFont bf = state.fontDetails.getBaseFont();
1691
1692        float w;
1693        if (kerned)
1694            w = bf.getWidthPointKerned(text, state.size);
1695        else
1696            w = bf.getWidthPoint(text, state.size);
1697
1698        if (state.charSpace != 0.0f && text.length() > 1) {
1699            w += state.charSpace * (text.length() -1);
1700        }
1701
1702        int ft = bf.getFontType();
1703        if (state.wordSpace != 0.0f && (ft == BaseFont.FONT_TYPE_T1 || ft == BaseFont.FONT_TYPE_TT || ft == BaseFont.FONT_TYPE_T3)) {
1704            for (int i = 0; i < text.length() -1; i++) {
1705                if (text.charAt(i) == ' ')
1706                    w += state.wordSpace;
1707            }
1708        }
1709        if (state.scale != 100.0)
1710            w = w * state.scale / 100.0f;
1711
1712        //System.out.println("String width = " + Float.toString(w));
1713        return w;
1714    }
1715
1716    /**
1717     * Shows text right, left or center aligned with rotation.
1718     * @param alignment the alignment can be ALIGN_CENTER, ALIGN_RIGHT or ALIGN_LEFT
1719     * @param text the text to show
1720     * @param x the x pivot position
1721     * @param y the y pivot position
1722     * @param rotation the rotation to be applied in degrees counterclockwise
1723     */
1724    public void showTextAligned(int alignment, String text, float x, float y, float rotation) {
1725        showTextAligned(alignment, text, x, y, rotation, false);
1726    }
1727
1728    private void showTextAligned(int alignment, String text, float x, float y, float rotation, boolean kerned) {
1729        if (state.fontDetails == null)
1730            throw new NullPointerException(MessageLocalization.getComposedMessage("font.and.size.must.be.set.before.writing.any.text"));
1731        if (rotation == 0) {
1732            switch (alignment) {
1733                case ALIGN_CENTER:
1734                    x -= getEffectiveStringWidth(text, kerned) / 2;
1735                    break;
1736                case ALIGN_RIGHT:
1737                    x -= getEffectiveStringWidth(text, kerned);
1738                    break;
1739            }
1740            setTextMatrix(x, y);
1741            if (kerned)
1742                showTextKerned(text);
1743            else
1744                showText(text);
1745        }
1746        else {
1747            double alpha = rotation * Math.PI / 180.0;
1748            float cos = (float)Math.cos(alpha);
1749            float sin = (float)Math.sin(alpha);
1750            float len;
1751            switch (alignment) {
1752                case ALIGN_CENTER:
1753                    len = getEffectiveStringWidth(text, kerned) / 2;
1754                    x -=  len * cos;
1755                    y -=  len * sin;
1756                    break;
1757                case ALIGN_RIGHT:
1758                    len = getEffectiveStringWidth(text, kerned);
1759                    x -=  len * cos;
1760                    y -=  len * sin;
1761                    break;
1762            }
1763            setTextMatrix(cos, sin, -sin, cos, x, y);
1764            if (kerned)
1765                showTextKerned(text);
1766            else
1767                showText(text);
1768            setTextMatrix(0f, 0f);
1769        }
1770    }
1771
1772    /**
1773     * Shows text kerned right, left or center aligned with rotation.
1774     * @param alignment the alignment can be ALIGN_CENTER, ALIGN_RIGHT or ALIGN_LEFT
1775     * @param text the text to show
1776     * @param x the x pivot position
1777     * @param y the y pivot position
1778     * @param rotation the rotation to be applied in degrees counterclockwise
1779     */
1780    public void showTextAlignedKerned(int alignment, String text, float x, float y, float rotation) {
1781        showTextAligned(alignment, text, x, y, rotation, true);
1782    }
1783
1784    /**
1785     * Concatenate a matrix to the current transformation matrix.
1786     * @param a an element of the transformation matrix
1787     * @param b an element of the transformation matrix
1788     * @param c an element of the transformation matrix
1789     * @param d an element of the transformation matrix
1790     * @param e an element of the transformation matrix
1791     * @param f an element of the transformation matrix
1792     **/
1793    public void concatCTM(float a, float b, float c, float d, float e, float f) {
1794        content.append(a).append(' ').append(b).append(' ').append(c).append(' ');
1795        content.append(d).append(' ').append(e).append(' ').append(f).append(" cm").append_i(separator);
1796    }
1797    
1798    /**
1799     * Concatenate a matrix to the current transformation matrix.
1800     * @param transform added to the Current Transformation Matrix
1801     * @since 5.0.1
1802     */
1803    public void concatCTM(AffineTransform transform) {
1804        double matrix[] = new double[6];
1805        transform.getMatrix(matrix);
1806        concatCTM( (float)matrix[0], (float)matrix[1], (float)matrix[2], 
1807                          (float)matrix[3], (float)matrix[4],(float) matrix[5] );
1808    }
1809
1810    /**
1811     * Generates an array of bezier curves to draw an arc.
1812     * <P>
1813     * (x1, y1) and (x2, y2) are the corners of the enclosing rectangle.
1814     * Angles, measured in degrees, start with 0 to the right (the positive X
1815     * axis) and increase counter-clockwise.  The arc extends from startAng
1816     * to startAng+extent.  I.e. startAng=0 and extent=180 yields an openside-down
1817     * semi-circle.
1818     * <P>
1819     * The resulting coordinates are of the form float[]{x1,y1,x2,y2,x3,y3, x4,y4}
1820     * such that the curve goes from (x1, y1) to (x4, y4) with (x2, y2) and
1821     * (x3, y3) as their respective Bezier control points.
1822     * <P>
1823     * Note: this code was taken from ReportLab (www.reportlab.org), an excellent
1824     * PDF generator for Python (BSD license: http://www.reportlab.org/devfaq.html#1.3 ).
1825     *
1826     * @param x1 a corner of the enclosing rectangle
1827     * @param y1 a corner of the enclosing rectangle
1828     * @param x2 a corner of the enclosing rectangle
1829     * @param y2 a corner of the enclosing rectangle
1830     * @param startAng starting angle in degrees
1831     * @param extent angle extent in degrees
1832     * @return a list of float[] with the bezier curves
1833     */
1834    public static ArrayList<float[]> bezierArc(float x1, float y1, float x2, float y2, float startAng, float extent) {
1835        float tmp;
1836        if (x1 > x2) {
1837            tmp = x1;
1838            x1 = x2;
1839            x2 = tmp;
1840        }
1841        if (y2 > y1) {
1842            tmp = y1;
1843            y1 = y2;
1844            y2 = tmp;
1845        }
1846
1847        float fragAngle;
1848        int Nfrag;
1849        if (Math.abs(extent) <= 90f) {
1850            fragAngle = extent;
1851            Nfrag = 1;
1852        }
1853        else {
1854            Nfrag = (int)Math.ceil(Math.abs(extent)/90f);
1855            fragAngle = extent / Nfrag;
1856        }
1857        float x_cen = (x1+x2)/2f;
1858        float y_cen = (y1+y2)/2f;
1859        float rx = (x2-x1)/2f;
1860        float ry = (y2-y1)/2f;
1861        float halfAng = (float)(fragAngle * Math.PI / 360.);
1862        float kappa = (float)Math.abs(4. / 3. * (1. - Math.cos(halfAng)) / Math.sin(halfAng));
1863        ArrayList<float[]> pointList = new ArrayList<float[]>();
1864        for (int i = 0; i < Nfrag; ++i) {
1865            float theta0 = (float)((startAng + i*fragAngle) * Math.PI / 180.);
1866            float theta1 = (float)((startAng + (i+1)*fragAngle) * Math.PI / 180.);
1867            float cos0 = (float)Math.cos(theta0);
1868            float cos1 = (float)Math.cos(theta1);
1869            float sin0 = (float)Math.sin(theta0);
1870            float sin1 = (float)Math.sin(theta1);
1871            if (fragAngle > 0f) {
1872                pointList.add(new float[]{x_cen + rx * cos0,
1873                y_cen - ry * sin0,
1874                x_cen + rx * (cos0 - kappa * sin0),
1875                y_cen - ry * (sin0 + kappa * cos0),
1876                x_cen + rx * (cos1 + kappa * sin1),
1877                y_cen - ry * (sin1 - kappa * cos1),
1878                x_cen + rx * cos1,
1879                y_cen - ry * sin1});
1880            }
1881            else {
1882                pointList.add(new float[]{x_cen + rx * cos0,
1883                y_cen - ry * sin0,
1884                x_cen + rx * (cos0 + kappa * sin0),
1885                y_cen - ry * (sin0 - kappa * cos0),
1886                x_cen + rx * (cos1 - kappa * sin1),
1887                y_cen - ry * (sin1 + kappa * cos1),
1888                x_cen + rx * cos1,
1889                y_cen - ry * sin1});
1890            }
1891        }
1892        return pointList;
1893    }
1894
1895    /**
1896     * Draws a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
1897     * starting at startAng degrees and covering extent degrees. Angles
1898     * start with 0 to the right (+x) and increase counter-clockwise.
1899     *
1900     * @param x1 a corner of the enclosing rectangle
1901     * @param y1 a corner of the enclosing rectangle
1902     * @param x2 a corner of the enclosing rectangle
1903     * @param y2 a corner of the enclosing rectangle
1904     * @param startAng starting angle in degrees
1905     * @param extent angle extent in degrees
1906     */
1907    public void arc(float x1, float y1, float x2, float y2, float startAng, float extent) {
1908        ArrayList<float[]> ar = bezierArc(x1, y1, x2, y2, startAng, extent);
1909        if (ar.isEmpty())
1910            return;
1911        float pt[] = ar.get(0);
1912        moveTo(pt[0], pt[1]);
1913        for (int k = 0; k < ar.size(); ++k) {
1914            pt = ar.get(k);
1915            curveTo(pt[2], pt[3], pt[4], pt[5], pt[6], pt[7]);
1916        }
1917    }
1918
1919    /**
1920     * Draws an ellipse inscribed within the rectangle x1,y1,x2,y2.
1921     *
1922     * @param x1 a corner of the enclosing rectangle
1923     * @param y1 a corner of the enclosing rectangle
1924     * @param x2 a corner of the enclosing rectangle
1925     * @param y2 a corner of the enclosing rectangle
1926     */
1927    public void ellipse(float x1, float y1, float x2, float y2) {
1928        arc(x1, y1, x2, y2, 0f, 360f);
1929    }
1930
1931    /**
1932     * Create a new colored tiling pattern.
1933     *
1934     * @param width the width of the pattern
1935     * @param height the height of the pattern
1936     * @param xstep the desired horizontal spacing between pattern cells.
1937     * May be either positive or negative, but not zero.
1938     * @param ystep the desired vertical spacing between pattern cells.
1939     * May be either positive or negative, but not zero.
1940     * @return the <CODE>PdfPatternPainter</CODE> where the pattern will be created
1941     */
1942    public PdfPatternPainter createPattern(float width, float height, float xstep, float ystep) {
1943        checkWriter();
1944        if ( xstep == 0.0f || ystep == 0.0f )
1945            throw new RuntimeException(MessageLocalization.getComposedMessage("xstep.or.ystep.can.not.be.zero"));
1946        PdfPatternPainter painter = new PdfPatternPainter(writer);
1947        painter.setWidth(width);
1948        painter.setHeight(height);
1949        painter.setXStep(xstep);
1950        painter.setYStep(ystep);
1951        writer.addSimplePattern(painter);
1952        return painter;
1953    }
1954
1955    /**
1956     * Create a new colored tiling pattern. Variables xstep and ystep are set to the same values
1957     * of width and height.
1958     * @param width the width of the pattern
1959     * @param height the height of the pattern
1960     * @return the <CODE>PdfPatternPainter</CODE> where the pattern will be created
1961     */
1962    public PdfPatternPainter createPattern(float width, float height) {
1963        return createPattern(width, height, width, height);
1964    }
1965
1966    /**
1967     * Create a new uncolored tiling pattern.
1968     *
1969     * @param width the width of the pattern
1970     * @param height the height of the pattern
1971     * @param xstep the desired horizontal spacing between pattern cells.
1972     * May be either positive or negative, but not zero.
1973     * @param ystep the desired vertical spacing between pattern cells.
1974     * May be either positive or negative, but not zero.
1975     * @param color the default color. Can be <CODE>null</CODE>
1976     * @return the <CODE>PdfPatternPainter</CODE> where the pattern will be created
1977     */
1978    public PdfPatternPainter createPattern(float width, float height, float xstep, float ystep, BaseColor color) {
1979        checkWriter();
1980        if ( xstep == 0.0f || ystep == 0.0f )
1981            throw new RuntimeException(MessageLocalization.getComposedMessage("xstep.or.ystep.can.not.be.zero"));
1982        PdfPatternPainter painter = new PdfPatternPainter(writer, color);
1983        painter.setWidth(width);
1984        painter.setHeight(height);
1985        painter.setXStep(xstep);
1986        painter.setYStep(ystep);
1987        writer.addSimplePattern(painter);
1988        return painter;
1989    }
1990
1991    /**
1992     * Create a new uncolored tiling pattern.
1993     * Variables xstep and ystep are set to the same values
1994     * of width and height.
1995     * @param width the width of the pattern
1996     * @param height the height of the pattern
1997     * @param color the default color. Can be <CODE>null</CODE>
1998     * @return the <CODE>PdfPatternPainter</CODE> where the pattern will be created
1999     */
2000    public PdfPatternPainter createPattern(float width, float height, BaseColor color) {
2001        return createPattern(width, height, width, height, color);
2002    }
2003
2004    /**
2005     * Creates a new template.
2006     * <P>
2007     * Creates a new template that is nothing more than a form XObject. This template can be included
2008     * in this <CODE>PdfContentByte</CODE> or in another template. Templates are only written
2009     * to the output when the document is closed permitting things like showing text in the first page
2010     * that is only defined in the last page.
2011     *
2012     * @param width the bounding box width
2013     * @param height the bounding box height
2014     * @return the created template
2015     */
2016    public PdfTemplate createTemplate(float width, float height) {
2017        return createTemplate(width, height, null);
2018    }
2019
2020    PdfTemplate createTemplate(float width, float height, PdfName forcedName) {
2021        checkWriter();
2022        PdfTemplate template = new PdfTemplate(writer);
2023        template.setWidth(width);
2024        template.setHeight(height);
2025        writer.addDirectTemplateSimple(template, forcedName);
2026        return template;
2027    }
2028
2029    /**
2030     * Creates a new appearance to be used with form fields.
2031     *
2032     * @param width the bounding box width
2033     * @param height the bounding box height
2034     * @return the appearance created
2035     */
2036    public PdfAppearance createAppearance(float width, float height) {
2037        return createAppearance(width, height, null);
2038    }
2039
2040    PdfAppearance createAppearance(float width, float height, PdfName forcedName) {
2041        checkWriter();
2042        PdfAppearance template = new PdfAppearance(writer);
2043        template.setWidth(width);
2044        template.setHeight(height);
2045        writer.addDirectTemplateSimple(template, forcedName);
2046        return template;
2047    }
2048
2049    /**
2050     * Adds a PostScript XObject to this content.
2051     *
2052     * @param psobject the object
2053     */
2054    public void addPSXObject(PdfPSXObject psobject) {
2055        checkWriter();
2056        PdfName name = writer.addDirectTemplateSimple(psobject, null);
2057        PageResources prs = getPageResources();
2058        name = prs.addXObject(name, psobject.getIndirectReference());
2059        content.append(name.getBytes()).append(" Do").append_i(separator);
2060    }
2061
2062    /**
2063     * Adds a template to this content.
2064     *
2065     * @param template the template
2066     * @param a an element of the transformation matrix
2067     * @param b an element of the transformation matrix
2068     * @param c an element of the transformation matrix
2069     * @param d an element of the transformation matrix
2070     * @param e an element of the transformation matrix
2071     * @param f an element of the transformation matrix
2072     */
2073    public void addTemplate(PdfTemplate template, float a, float b, float c, float d, float e, float f) {
2074        checkWriter();
2075        checkNoPattern(template);
2076        PdfName name = writer.addDirectTemplateSimple(template, null);
2077        PageResources prs = getPageResources();
2078        name = prs.addXObject(name, template.getIndirectReference());
2079        content.append("q ");
2080        content.append(a).append(' ');
2081        content.append(b).append(' ');
2082        content.append(c).append(' ');
2083        content.append(d).append(' ');
2084        content.append(e).append(' ');
2085        content.append(f).append(" cm ");
2086        content.append(name.getBytes()).append(" Do Q").append_i(separator);
2087    }
2088    
2089    /**
2090     * adds a template with the given matrix.
2091     * @param template template to add
2092     * @param transform transform to apply to the template prior to adding it.
2093     * @since 5.0.1
2094     */
2095    public void addTemplate(PdfTemplate template, AffineTransform transform) {
2096        double matrix[] = new double[6];
2097        transform.getMatrix(matrix);
2098        addTemplate( template, (float)matrix[0], (float)matrix[1], (float)matrix[2], 
2099                          (float)matrix[3], (float)matrix[4],(float) matrix[5] );
2100
2101    }
2102
2103    void addTemplateReference(PdfIndirectReference template, PdfName name, float a, float b, float c, float d, float e, float f) {
2104        checkWriter();
2105        PageResources prs = getPageResources();
2106        name = prs.addXObject(name, template);
2107        content.append("q ");
2108        content.append(a).append(' ');
2109        content.append(b).append(' ');
2110        content.append(c).append(' ');
2111        content.append(d).append(' ');
2112        content.append(e).append(' ');
2113        content.append(f).append(" cm ");
2114        content.append(name.getBytes()).append(" Do Q").append_i(separator);
2115    }
2116
2117    /**
2118     * Adds a template to this content.
2119     *
2120     * @param template the template
2121     * @param x the x location of this template
2122     * @param y the y location of this template
2123     */
2124    public void addTemplate(PdfTemplate template, float x, float y) {
2125        addTemplate(template, 1, 0, 0, 1, x, y);
2126    }
2127
2128    /**
2129     * Changes the current color for filling paths (device dependent colors!).
2130     * <P>
2131     * Sets the color space to <B>DeviceCMYK</B> (or the <B>DefaultCMYK</B> color space),
2132     * and sets the color to use for filling paths.</P>
2133     * <P>
2134     * This method is described in the 'Portable Document Format Reference Manual version 1.3'
2135     * section 8.5.2.1 (page 331).</P>
2136     * <P>
2137     * Following the PDF manual, each operand must be a number between 0 (no ink) and
2138     * 1 (maximum ink). This method however accepts only integers between 0x00 and 0xFF.</P>
2139     *
2140     * @param cyan the intensity of cyan
2141     * @param magenta the intensity of magenta
2142     * @param yellow the intensity of yellow
2143     * @param black the intensity of black
2144     */
2145
2146    public void setCMYKColorFill(int cyan, int magenta, int yellow, int black) {
2147        content.append((float)(cyan & 0xFF) / 0xFF);
2148        content.append(' ');
2149        content.append((float)(magenta & 0xFF) / 0xFF);
2150        content.append(' ');
2151        content.append((float)(yellow & 0xFF) / 0xFF);
2152        content.append(' ');
2153        content.append((float)(black & 0xFF) / 0xFF);
2154        content.append(" k").append_i(separator);
2155    }
2156    /**
2157     * Changes the current color for stroking paths (device dependent colors!).
2158     * <P>
2159     * Sets the color space to <B>DeviceCMYK</B> (or the <B>DefaultCMYK</B> color space),
2160     * and sets the color to use for stroking paths.</P>
2161     * <P>
2162     * This method is described in the 'Portable Document Format Reference Manual version 1.3'
2163     * section 8.5.2.1 (page 331).</P>
2164     * Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
2165     * 1 (maximum intensity). This method however accepts only integers between 0x00 and 0xFF.
2166     *
2167     * @param cyan the intensity of red
2168     * @param magenta the intensity of green
2169     * @param yellow the intensity of blue
2170     * @param black the intensity of black
2171     */
2172
2173    public void setCMYKColorStroke(int cyan, int magenta, int yellow, int black) {
2174        content.append((float)(cyan & 0xFF) / 0xFF);
2175        content.append(' ');
2176        content.append((float)(magenta & 0xFF) / 0xFF);
2177        content.append(' ');
2178        content.append((float)(yellow & 0xFF) / 0xFF);
2179        content.append(' ');
2180        content.append((float)(black & 0xFF) / 0xFF);
2181        content.append(" K").append_i(separator);
2182    }
2183
2184    /**
2185     * Changes the current color for filling paths (device dependent colors!).
2186     * <P>
2187     * Sets the color space to <B>DeviceRGB</B> (or the <B>DefaultRGB</B> color space),
2188     * and sets the color to use for filling paths.</P>
2189     * <P>
2190     * This method is described in the 'Portable Document Format Reference Manual version 1.3'
2191     * section 8.5.2.1 (page 331).</P>
2192     * <P>
2193     * Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
2194     * 1 (maximum intensity). This method however accepts only integers between 0x00 and 0xFF.</P>
2195     *
2196     * @param red the intensity of red
2197     * @param green the intensity of green
2198     * @param blue the intensity of blue
2199     */
2200
2201    public void setRGBColorFill(int red, int green, int blue) {
2202        HelperRGB((float)(red & 0xFF) / 0xFF, (float)(green & 0xFF) / 0xFF, (float)(blue & 0xFF) / 0xFF);
2203        content.append(" rg").append_i(separator);
2204    }
2205
2206    /**
2207     * Changes the current color for stroking paths (device dependent colors!).
2208     * <P>
2209     * Sets the color space to <B>DeviceRGB</B> (or the <B>DefaultRGB</B> color space),
2210     * and sets the color to use for stroking paths.</P>
2211     * <P>
2212     * This method is described in the 'Portable Document Format Reference Manual version 1.3'
2213     * section 8.5.2.1 (page 331).</P>
2214     * Following the PDF manual, each operand must be a number between 0 (minimum intensity) and
2215     * 1 (maximum intensity). This method however accepts only integers between 0x00 and 0xFF.
2216     *
2217     * @param red the intensity of red
2218     * @param green the intensity of green
2219     * @param blue the intensity of blue
2220     */
2221
2222    public void setRGBColorStroke(int red, int green, int blue) {
2223        HelperRGB((float)(red & 0xFF) / 0xFF, (float)(green & 0xFF) / 0xFF, (float)(blue & 0xFF) / 0xFF);
2224        content.append(" RG").append_i(separator);
2225    }
2226
2227    /** Sets the stroke color. <CODE>color</CODE> can be an
2228     * <CODE>ExtendedColor</CODE>.
2229     * @param color the color
2230     */
2231    public void setColorStroke(BaseColor color) {
2232        PdfXConformanceImp.checkPDFXConformance(writer, PdfXConformanceImp.PDFXKEY_COLOR, color);
2233        int type = ExtendedColor.getType(color);
2234        switch (type) {
2235            case ExtendedColor.TYPE_GRAY: {
2236                setGrayStroke(((GrayColor)color).getGray());
2237                break;
2238            }
2239            case ExtendedColor.TYPE_CMYK: {
2240                CMYKColor cmyk = (CMYKColor)color;
2241                setCMYKColorStrokeF(cmyk.getCyan(), cmyk.getMagenta(), cmyk.getYellow(), cmyk.getBlack());
2242                break;
2243            }
2244            case ExtendedColor.TYPE_SEPARATION: {
2245                SpotColor spot = (SpotColor)color;
2246                setColorStroke(spot.getPdfSpotColor(), spot.getTint());
2247                break;
2248            }
2249            case ExtendedColor.TYPE_PATTERN: {
2250                PatternColor pat = (PatternColor) color;
2251                setPatternStroke(pat.getPainter());
2252                break;
2253            }
2254            case ExtendedColor.TYPE_SHADING: {
2255                ShadingColor shading = (ShadingColor) color;
2256                setShadingStroke(shading.getPdfShadingPattern());
2257                break;
2258            }
2259            default:
2260                setRGBColorStroke(color.getRed(), color.getGreen(), color.getBlue());
2261        }
2262    }
2263
2264    /** Sets the fill color. <CODE>color</CODE> can be an
2265     * <CODE>ExtendedColor</CODE>.
2266     * @param color the color
2267     */
2268    public void setColorFill(BaseColor color) {
2269        PdfXConformanceImp.checkPDFXConformance(writer, PdfXConformanceImp.PDFXKEY_COLOR, color);
2270        int type = ExtendedColor.getType(color);
2271        switch (type) {
2272            case ExtendedColor.TYPE_GRAY: {
2273                setGrayFill(((GrayColor)color).getGray());
2274                break;
2275            }
2276            case ExtendedColor.TYPE_CMYK: {
2277                CMYKColor cmyk = (CMYKColor)color;
2278                setCMYKColorFillF(cmyk.getCyan(), cmyk.getMagenta(), cmyk.getYellow(), cmyk.getBlack());
2279                break;
2280            }
2281            case ExtendedColor.TYPE_SEPARATION: {
2282                SpotColor spot = (SpotColor)color;
2283                setColorFill(spot.getPdfSpotColor(), spot.getTint());
2284                break;
2285            }
2286            case ExtendedColor.TYPE_PATTERN: {
2287                PatternColor pat = (PatternColor) color;
2288                setPatternFill(pat.getPainter());
2289                break;
2290            }
2291            case ExtendedColor.TYPE_SHADING: {
2292                ShadingColor shading = (ShadingColor) color;
2293                setShadingFill(shading.getPdfShadingPattern());
2294                break;
2295            }
2296            default:
2297                setRGBColorFill(color.getRed(), color.getGreen(), color.getBlue());
2298        }
2299    }
2300
2301    /** Sets the fill color to a spot color.
2302     * @param sp the spot color
2303     * @param tint the tint for the spot color. 0 is no color and 1
2304     * is 100% color
2305     */
2306    public void setColorFill(PdfSpotColor sp, float tint) {
2307        checkWriter();
2308        state.colorDetails = writer.addSimple(sp);
2309        PageResources prs = getPageResources();
2310        PdfName name = state.colorDetails.getColorName();
2311        name = prs.addColor(name, state.colorDetails.getIndirectReference());
2312        content.append(name.getBytes()).append(" cs ").append(tint).append(" scn").append_i(separator);
2313    }
2314
2315    /** Sets the stroke color to a spot color.
2316     * @param sp the spot color
2317     * @param tint the tint for the spot color. 0 is no color and 1
2318     * is 100% color
2319     */
2320    public void setColorStroke(PdfSpotColor sp, float tint) {
2321        checkWriter();
2322        state.colorDetails = writer.addSimple(sp);
2323        PageResources prs = getPageResources();
2324        PdfName name = state.colorDetails.getColorName();
2325        name = prs.addColor(name, state.colorDetails.getIndirectReference());
2326        content.append(name.getBytes()).append(" CS ").append(tint).append(" SCN").append_i(separator);
2327    }
2328
2329    /** Sets the fill color to a pattern. The pattern can be
2330     * colored or uncolored.
2331     * @param p the pattern
2332     */
2333    public void setPatternFill(PdfPatternPainter p) {
2334        if (p.isStencil()) {
2335            setPatternFill(p, p.getDefaultColor());
2336            return;
2337        }
2338        checkWriter();
2339        PageResources prs = getPageResources();
2340        PdfName name = writer.addSimplePattern(p);
2341        name = prs.addPattern(name, p.getIndirectReference());
2342        content.append(PdfName.PATTERN.getBytes()).append(" cs ").append(name.getBytes()).append(" scn").append_i(separator);
2343    }
2344
2345    /** Outputs the color values to the content.
2346     * @param color The color
2347     * @param tint the tint if it is a spot color, ignored otherwise
2348     */
2349    void outputColorNumbers(BaseColor color, float tint) {
2350        PdfXConformanceImp.checkPDFXConformance(writer, PdfXConformanceImp.PDFXKEY_COLOR, color);
2351        int type = ExtendedColor.getType(color);
2352        switch (type) {
2353            case ExtendedColor.TYPE_RGB:
2354                content.append((float)color.getRed() / 0xFF);
2355                content.append(' ');
2356                content.append((float)color.getGreen() / 0xFF);
2357                content.append(' ');
2358                content.append((float)color.getBlue() / 0xFF);
2359                break;
2360            case ExtendedColor.TYPE_GRAY:
2361                content.append(((GrayColor)color).getGray());
2362                break;
2363            case ExtendedColor.TYPE_CMYK: {
2364                CMYKColor cmyk = (CMYKColor)color;
2365                content.append(cmyk.getCyan()).append(' ').append(cmyk.getMagenta());
2366                content.append(' ').append(cmyk.getYellow()).append(' ').append(cmyk.getBlack());
2367                break;
2368            }
2369            case ExtendedColor.TYPE_SEPARATION:
2370                content.append(tint);
2371                break;
2372            default:
2373                throw new RuntimeException(MessageLocalization.getComposedMessage("invalid.color.type"));
2374        }
2375    }
2376
2377    /** Sets the fill color to an uncolored pattern.
2378     * @param p the pattern
2379     * @param color the color of the pattern
2380     */
2381    public void setPatternFill(PdfPatternPainter p, BaseColor color) {
2382        if (ExtendedColor.getType(color) == ExtendedColor.TYPE_SEPARATION)
2383            setPatternFill(p, color, ((SpotColor)color).getTint());
2384        else
2385            setPatternFill(p, color, 0);
2386    }
2387
2388    /** Sets the fill color to an uncolored pattern.
2389     * @param p the pattern
2390     * @param color the color of the pattern
2391     * @param tint the tint if the color is a spot color, ignored otherwise
2392     */
2393    public void setPatternFill(PdfPatternPainter p, BaseColor color, float tint) {
2394        checkWriter();
2395        if (!p.isStencil())
2396            throw new RuntimeException(MessageLocalization.getComposedMessage("an.uncolored.pattern.was.expected"));
2397        PageResources prs = getPageResources();
2398        PdfName name = writer.addSimplePattern(p);
2399        name = prs.addPattern(name, p.getIndirectReference());
2400        ColorDetails csDetail = writer.addSimplePatternColorspace(color);
2401        PdfName cName = prs.addColor(csDetail.getColorName(), csDetail.getIndirectReference());
2402        content.append(cName.getBytes()).append(" cs").append_i(separator);
2403        outputColorNumbers(color, tint);
2404        content.append(' ').append(name.getBytes()).append(" scn").append_i(separator);
2405    }
2406
2407    /** Sets the stroke color to an uncolored pattern.
2408     * @param p the pattern
2409     * @param color the color of the pattern
2410     */
2411    public void setPatternStroke(PdfPatternPainter p, BaseColor color) {
2412        if (ExtendedColor.getType(color) == ExtendedColor.TYPE_SEPARATION)
2413            setPatternStroke(p, color, ((SpotColor)color).getTint());
2414        else
2415            setPatternStroke(p, color, 0);
2416    }
2417
2418    /** Sets the stroke color to an uncolored pattern.
2419     * @param p the pattern
2420     * @param color the color of the pattern
2421     * @param tint the tint if the color is a spot color, ignored otherwise
2422     */
2423    public void setPatternStroke(PdfPatternPainter p, BaseColor color, float tint) {
2424        checkWriter();
2425        if (!p.isStencil())
2426            throw new RuntimeException(MessageLocalization.getComposedMessage("an.uncolored.pattern.was.expected"));
2427        PageResources prs = getPageResources();
2428        PdfName name = writer.addSimplePattern(p);
2429        name = prs.addPattern(name, p.getIndirectReference());
2430        ColorDetails csDetail = writer.addSimplePatternColorspace(color);
2431        PdfName cName = prs.addColor(csDetail.getColorName(), csDetail.getIndirectReference());
2432        content.append(cName.getBytes()).append(" CS").append_i(separator);
2433        outputColorNumbers(color, tint);
2434        content.append(' ').append(name.getBytes()).append(" SCN").append_i(separator);
2435    }
2436
2437    /** Sets the stroke color to a pattern. The pattern can be
2438     * colored or uncolored.
2439     * @param p the pattern
2440     */
2441    public void setPatternStroke(PdfPatternPainter p) {
2442        if (p.isStencil()) {
2443            setPatternStroke(p, p.getDefaultColor());
2444            return;
2445        }
2446        checkWriter();
2447        PageResources prs = getPageResources();
2448        PdfName name = writer.addSimplePattern(p);
2449        name = prs.addPattern(name, p.getIndirectReference());
2450        content.append(PdfName.PATTERN.getBytes()).append(" CS ").append(name.getBytes()).append(" SCN").append_i(separator);
2451    }
2452
2453    /**
2454     * Paints using a shading object.
2455     * @param shading the shading object
2456     */
2457    public void paintShading(PdfShading shading) {
2458        writer.addSimpleShading(shading);
2459        PageResources prs = getPageResources();
2460        PdfName name = prs.addShading(shading.getShadingName(), shading.getShadingReference());
2461        content.append(name.getBytes()).append(" sh").append_i(separator);
2462        ColorDetails details = shading.getColorDetails();
2463        if (details != null)
2464            prs.addColor(details.getColorName(), details.getIndirectReference());
2465    }
2466
2467    /**
2468     * Paints using a shading pattern.
2469     * @param shading the shading pattern
2470     */
2471    public void paintShading(PdfShadingPattern shading) {
2472        paintShading(shading.getShading());
2473    }
2474
2475    /**
2476     * Sets the shading fill pattern.
2477     * @param shading the shading pattern
2478     */
2479    public void setShadingFill(PdfShadingPattern shading) {
2480        writer.addSimpleShadingPattern(shading);
2481        PageResources prs = getPageResources();
2482        PdfName name = prs.addPattern(shading.getPatternName(), shading.getPatternReference());
2483        content.append(PdfName.PATTERN.getBytes()).append(" cs ").append(name.getBytes()).append(" scn").append_i(separator);
2484        ColorDetails details = shading.getColorDetails();
2485        if (details != null)
2486            prs.addColor(details.getColorName(), details.getIndirectReference());
2487    }
2488
2489    /**
2490     * Sets the shading stroke pattern
2491     * @param shading the shading pattern
2492     */
2493    public void setShadingStroke(PdfShadingPattern shading) {
2494        writer.addSimpleShadingPattern(shading);
2495        PageResources prs = getPageResources();
2496        PdfName name = prs.addPattern(shading.getPatternName(), shading.getPatternReference());
2497        content.append(PdfName.PATTERN.getBytes()).append(" CS ").append(name.getBytes()).append(" SCN").append_i(separator);
2498        ColorDetails details = shading.getColorDetails();
2499        if (details != null)
2500            prs.addColor(details.getColorName(), details.getIndirectReference());
2501    }
2502
2503    /** Check if we have a valid PdfWriter.
2504     *
2505     */
2506    protected void checkWriter() {
2507        if (writer == null)
2508            throw new NullPointerException(MessageLocalization.getComposedMessage("the.writer.in.pdfcontentbyte.is.null"));
2509    }
2510
2511    /**
2512     * Show an array of text.
2513     * @param text array of text
2514     */
2515    public void showText(PdfTextArray text) {
2516        if (state.fontDetails == null)
2517            throw new NullPointerException(MessageLocalization.getComposedMessage("font.and.size.must.be.set.before.writing.any.text"));
2518        content.append("[");
2519        ArrayList<Object> arrayList = text.getArrayList();
2520        boolean lastWasNumber = false;
2521        for (Object obj : arrayList) {
2522            if (obj instanceof String) {
2523                showText2((String)obj);
2524                lastWasNumber = false;
2525            }
2526            else {
2527                if (lastWasNumber)
2528                    content.append(' ');
2529                else
2530                    lastWasNumber = true;
2531                content.append(((Float)obj).floatValue());
2532            }
2533        }
2534        content.append("]TJ").append_i(separator);
2535    }
2536
2537    /**
2538     * Gets the <CODE>PdfWriter</CODE> in use by this object.
2539     * @return the <CODE>PdfWriter</CODE> in use by this object
2540     */
2541    public PdfWriter getPdfWriter() {
2542        return writer;
2543    }
2544
2545    /**
2546     * Gets the <CODE>PdfDocument</CODE> in use by this object.
2547     * @return the <CODE>PdfDocument</CODE> in use by this object
2548     */
2549    public PdfDocument getPdfDocument() {
2550        return pdf;
2551    }
2552
2553    /**
2554     * Implements a link to other part of the document. The jump will
2555     * be made to a local destination with the same name, that must exist.
2556     * @param name the name for this link
2557     * @param llx the lower left x corner of the activation area
2558     * @param lly the lower left y corner of the activation area
2559     * @param urx the upper right x corner of the activation area
2560     * @param ury the upper right y corner of the activation area
2561     */
2562    public void localGoto(String name, float llx, float lly, float urx, float ury) {
2563        pdf.localGoto(name, llx, lly, urx, ury);
2564    }
2565
2566    /**
2567     * The local destination to where a local goto with the same
2568     * name will jump.
2569     * @param name the name of this local destination
2570     * @param destination the <CODE>PdfDestination</CODE> with the jump coordinates
2571     * @return <CODE>true</CODE> if the local destination was added,
2572     * <CODE>false</CODE> if a local destination with the same name
2573     * already exists
2574     */
2575    public boolean localDestination(String name, PdfDestination destination) {
2576        return pdf.localDestination(name, destination);
2577    }
2578
2579    /**
2580     * Gets a duplicate of this <CODE>PdfContentByte</CODE>. All
2581     * the members are copied by reference but the buffer stays different.
2582     *
2583     * @return a copy of this <CODE>PdfContentByte</CODE>
2584     */
2585    public PdfContentByte getDuplicate() {
2586        return new PdfContentByte(writer);
2587    }
2588
2589    /**
2590     * Implements a link to another document.
2591     * @param filename the filename for the remote document
2592     * @param name the name to jump to
2593     * @param llx the lower left x corner of the activation area
2594     * @param lly the lower left y corner of the activation area
2595     * @param urx the upper right x corner of the activation area
2596     * @param ury the upper right y corner of the activation area
2597     */
2598    public void remoteGoto(String filename, String name, float llx, float lly, float urx, float ury) {
2599        pdf.remoteGoto(filename, name, llx, lly, urx, ury);
2600    }
2601
2602    /**
2603     * Implements a link to another document.
2604     * @param filename the filename for the remote document
2605     * @param page the page to jump to
2606     * @param llx the lower left x corner of the activation area
2607     * @param lly the lower left y corner of the activation area
2608     * @param urx the upper right x corner of the activation area
2609     * @param ury the upper right y corner of the activation area
2610     */
2611    public void remoteGoto(String filename, int page, float llx, float lly, float urx, float ury) {
2612        pdf.remoteGoto(filename, page, llx, lly, urx, ury);
2613    }
2614    /**
2615     * Adds a round rectangle to the current path.
2616     *
2617     * @param x x-coordinate of the starting point
2618     * @param y y-coordinate of the starting point
2619     * @param w width
2620     * @param h height
2621     * @param r radius of the arc corner
2622     */
2623    public void roundRectangle(float x, float y, float w, float h, float r) {
2624        if (w < 0) {
2625            x += w;
2626            w = -w;
2627        }
2628        if (h < 0) {
2629            y += h;
2630            h = -h;
2631        }
2632        if (r < 0)
2633            r = -r;
2634        float b = 0.4477f;
2635        moveTo(x + r, y);
2636        lineTo(x + w - r, y);
2637        curveTo(x + w - r * b, y, x + w, y + r * b, x + w, y + r);
2638        lineTo(x + w, y + h - r);
2639        curveTo(x + w, y + h - r * b, x + w - r * b, y + h, x + w - r, y + h);
2640        lineTo(x + r, y + h);
2641        curveTo(x + r * b, y + h, x, y + h - r * b, x, y + h - r);
2642        lineTo(x, y + r);
2643        curveTo(x, y + r * b, x + r * b, y, x + r, y);
2644    }
2645
2646    /** Implements an action in an area.
2647     * @param action the <CODE>PdfAction</CODE>
2648     * @param llx the lower left x corner of the activation area
2649     * @param lly the lower left y corner of the activation area
2650     * @param urx the upper right x corner of the activation area
2651     * @param ury the upper right y corner of the activation area
2652     */
2653    public void setAction(PdfAction action, float llx, float lly, float urx, float ury) {
2654        pdf.setAction(action, llx, lly, urx, ury);
2655    }
2656
2657    /** Outputs a <CODE>String</CODE> directly to the content.
2658     * @param s the <CODE>String</CODE>
2659     */
2660    public void setLiteral(String s) {
2661        content.append(s);
2662    }
2663
2664    /** Outputs a <CODE>char</CODE> directly to the content.
2665     * @param c the <CODE>char</CODE>
2666     */
2667    public void setLiteral(char c) {
2668        content.append(c);
2669    }
2670
2671    /** Outputs a <CODE>float</CODE> directly to the content.
2672     * @param n the <CODE>float</CODE>
2673     */
2674    public void setLiteral(float n) {
2675        content.append(n);
2676    }
2677
2678    /** Throws an error if it is a pattern.
2679     * @param t the object to check
2680     */
2681    void checkNoPattern(PdfTemplate t) {
2682        if (t.getType() == PdfTemplate.TYPE_PATTERN)
2683            throw new RuntimeException(MessageLocalization.getComposedMessage("invalid.use.of.a.pattern.a.template.was.expected"));
2684    }
2685
2686    /**
2687     * Draws a TextField.
2688     * @param llx
2689     * @param lly
2690     * @param urx
2691     * @param ury
2692     * @param on
2693     */
2694    public void drawRadioField(float llx, float lly, float urx, float ury, boolean on) {
2695        if (llx > urx) { float x = llx; llx = urx; urx = x; }
2696        if (lly > ury) { float y = lly; lly = ury; ury = y; }
2697        // silver circle
2698        setLineWidth(1);
2699        setLineCap(1);
2700        setColorStroke(new BaseColor(0xC0, 0xC0, 0xC0));
2701        arc(llx + 1f, lly + 1f, urx - 1f, ury - 1f, 0f, 360f);
2702        stroke();
2703        // gray circle-segment
2704        setLineWidth(1);
2705        setLineCap(1);
2706        setColorStroke(new BaseColor(0xA0, 0xA0, 0xA0));
2707        arc(llx + 0.5f, lly + 0.5f, urx - 0.5f, ury - 0.5f, 45, 180);
2708        stroke();
2709        // black circle-segment
2710        setLineWidth(1);
2711        setLineCap(1);
2712        setColorStroke(new BaseColor(0x00, 0x00, 0x00));
2713        arc(llx + 1.5f, lly + 1.5f, urx - 1.5f, ury - 1.5f, 45, 180);
2714        stroke();
2715        if (on) {
2716            // gray circle
2717            setLineWidth(1);
2718            setLineCap(1);
2719            setColorFill(new BaseColor(0x00, 0x00, 0x00));
2720            arc(llx + 4f, lly + 4f, urx - 4f, ury - 4f, 0, 360);
2721            fill();
2722        }
2723    }
2724
2725    /**
2726     * Draws a TextField.
2727     * @param llx
2728     * @param lly
2729     * @param urx
2730     * @param ury
2731     */
2732    public void drawTextField(float llx, float lly, float urx, float ury) {
2733        if (llx > urx) { float x = llx; llx = urx; urx = x; }
2734        if (lly > ury) { float y = lly; lly = ury; ury = y; }
2735        // silver rectangle not filled
2736        setColorStroke(new BaseColor(0xC0, 0xC0, 0xC0));
2737        setLineWidth(1);
2738        setLineCap(0);
2739        rectangle(llx, lly, urx - llx, ury - lly);
2740        stroke();
2741        // white rectangle filled
2742        setLineWidth(1);
2743        setLineCap(0);
2744        setColorFill(new BaseColor(0xFF, 0xFF, 0xFF));
2745        rectangle(llx + 0.5f, lly + 0.5f, urx - llx - 1f, ury -lly - 1f);
2746        fill();
2747        // silver lines
2748        setColorStroke(new BaseColor(0xC0, 0xC0, 0xC0));
2749        setLineWidth(1);
2750        setLineCap(0);
2751        moveTo(llx + 1f, lly + 1.5f);
2752        lineTo(urx - 1.5f, lly + 1.5f);
2753        lineTo(urx - 1.5f, ury - 1f);
2754        stroke();
2755        // gray lines
2756        setColorStroke(new BaseColor(0xA0, 0xA0, 0xA0));
2757        setLineWidth(1);
2758        setLineCap(0);
2759        moveTo(llx + 1f, lly + 1);
2760        lineTo(llx + 1f, ury - 1f);
2761        lineTo(urx - 1f, ury - 1f);
2762        stroke();
2763        // black lines
2764        setColorStroke(new BaseColor(0x00, 0x00, 0x00));
2765        setLineWidth(1);
2766        setLineCap(0);
2767        moveTo(llx + 2f, lly + 2f);
2768        lineTo(llx + 2f, ury - 2f);
2769        lineTo(urx - 2f, ury - 2f);
2770        stroke();
2771    }
2772
2773    /**
2774     * Draws a button.
2775     * @param llx
2776     * @param lly
2777     * @param urx
2778     * @param ury
2779     * @param text
2780     * @param bf
2781     * @param size
2782     */
2783    public void drawButton(float llx, float lly, float urx, float ury, String text, BaseFont bf, float size) {
2784        if (llx > urx) { float x = llx; llx = urx; urx = x; }
2785        if (lly > ury) { float y = lly; lly = ury; ury = y; }
2786        // black rectangle not filled
2787        setColorStroke(new BaseColor(0x00, 0x00, 0x00));
2788        setLineWidth(1);
2789        setLineCap(0);
2790        rectangle(llx, lly, urx - llx, ury - lly);
2791        stroke();
2792        // silver rectangle filled
2793        setLineWidth(1);
2794        setLineCap(0);
2795        setColorFill(new BaseColor(0xC0, 0xC0, 0xC0));
2796        rectangle(llx + 0.5f, lly + 0.5f, urx - llx - 1f, ury -lly - 1f);
2797        fill();
2798        // white lines
2799        setColorStroke(new BaseColor(0xFF, 0xFF, 0xFF));
2800        setLineWidth(1);
2801        setLineCap(0);
2802        moveTo(llx + 1f, lly + 1f);
2803        lineTo(llx + 1f, ury - 1f);
2804        lineTo(urx - 1f, ury - 1f);
2805        stroke();
2806        // dark grey lines
2807        setColorStroke(new BaseColor(0xA0, 0xA0, 0xA0));
2808        setLineWidth(1);
2809        setLineCap(0);
2810        moveTo(llx + 1f, lly + 1f);
2811        lineTo(urx - 1f, lly + 1f);
2812        lineTo(urx - 1f, ury - 1f);
2813        stroke();
2814        // text
2815        resetRGBColorFill();
2816        beginText();
2817        setFontAndSize(bf, size);
2818        showTextAligned(PdfContentByte.ALIGN_CENTER, text, llx + (urx - llx) / 2, lly + (ury - lly - size) / 2, 0);
2819        endText();
2820    }
2821
2822    /** Gets a <CODE>Graphics2D</CODE> to write on. The graphics
2823     * are translated to PDF commands as shapes. No PDF fonts will appear.
2824     * @param width the width of the panel
2825     * @param height the height of the panel
2826     * @return a <CODE>Graphics2D</CODE>
2827     */
2828    public java.awt.Graphics2D createGraphicsShapes(float width, float height) {
2829        return new PdfGraphics2D(this, width, height, null, true, false, 0);
2830    }
2831
2832    /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2833     * are translated to PDF commands as shapes. No PDF fonts will appear.
2834     * @param width the width of the panel
2835     * @param height the height of the panel
2836     * @param printerJob a printer job
2837     * @return a <CODE>Graphics2D</CODE>
2838     */
2839    public java.awt.Graphics2D createPrinterGraphicsShapes(float width, float height, PrinterJob printerJob) {
2840        return new PdfPrinterGraphics2D(this, width, height, null, true, false, 0, printerJob);
2841    }
2842
2843    /** Gets a <CODE>Graphics2D</CODE> to write on. The graphics
2844     * are translated to PDF commands.
2845     * @param width the width of the panel
2846     * @param height the height of the panel
2847     * @return a <CODE>Graphics2D</CODE>
2848     */
2849    public java.awt.Graphics2D createGraphics(float width, float height) {
2850        return new PdfGraphics2D(this, width, height, null, false, false, 0);
2851    }
2852
2853    /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2854     * are translated to PDF commands.
2855     * @param width the width of the panel
2856     * @param height the height of the panel
2857     * @param printerJob
2858     * @return a <CODE>Graphics2D</CODE>
2859     */
2860    public java.awt.Graphics2D createPrinterGraphics(float width, float height, PrinterJob printerJob) {
2861        return new PdfPrinterGraphics2D(this, width, height, null, false, false, 0, printerJob);
2862    }
2863
2864    /** Gets a <CODE>Graphics2D</CODE> to write on. The graphics
2865     * are translated to PDF commands.
2866     * @param width the width of the panel
2867     * @param height the height of the panel
2868     * @param convertImagesToJPEG
2869     * @param quality
2870     * @return a <CODE>Graphics2D</CODE>
2871     */
2872    public java.awt.Graphics2D createGraphics(float width, float height, boolean convertImagesToJPEG, float quality) {
2873        return new PdfGraphics2D(this, width, height, null, false, convertImagesToJPEG, quality);
2874    }
2875
2876    /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2877     * are translated to PDF commands.
2878     * @param width the width of the panel
2879     * @param height the height of the panel
2880     * @param convertImagesToJPEG
2881     * @param quality
2882     * @param printerJob
2883     * @return a <CODE>Graphics2D</CODE>
2884     */
2885    public java.awt.Graphics2D createPrinterGraphics(float width, float height, boolean convertImagesToJPEG, float quality, PrinterJob printerJob) {
2886        return new PdfPrinterGraphics2D(this, width, height, null, false, convertImagesToJPEG, quality, printerJob);
2887    }
2888
2889    /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2890     * are translated to PDF commands.
2891     * @param width
2892     * @param height
2893     * @param convertImagesToJPEG
2894     * @param quality
2895     * @return A Graphics2D object
2896     */
2897    public java.awt.Graphics2D createGraphicsShapes(float width, float height, boolean convertImagesToJPEG, float quality) {
2898        return new PdfGraphics2D(this, width, height, null, true, convertImagesToJPEG, quality);
2899    }
2900
2901    /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2902     * are translated to PDF commands.
2903     * @param width
2904     * @param height
2905     * @param convertImagesToJPEG
2906     * @param quality
2907     * @param printerJob
2908     * @return a Graphics2D object
2909     */
2910    public java.awt.Graphics2D createPrinterGraphicsShapes(float width, float height, boolean convertImagesToJPEG, float quality, PrinterJob printerJob) {
2911        return new PdfPrinterGraphics2D(this, width, height, null, true, convertImagesToJPEG, quality, printerJob);
2912    }
2913
2914    /** Gets a <CODE>Graphics2D</CODE> to write on. The graphics
2915     * are translated to PDF commands.
2916     * @param width the width of the panel
2917     * @param height the height of the panel
2918     * @param fontMapper the mapping from awt fonts to <CODE>BaseFont</CODE>
2919     * @return a <CODE>Graphics2D</CODE>
2920     */
2921    public java.awt.Graphics2D createGraphics(float width, float height, FontMapper fontMapper) {
2922        return new PdfGraphics2D(this, width, height, fontMapper, false, false, 0);
2923    }
2924
2925    /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2926     * are translated to PDF commands.
2927     * @param width the width of the panel
2928     * @param height the height of the panel
2929     * @param fontMapper the mapping from awt fonts to <CODE>BaseFont</CODE>
2930     * @param printerJob a printer job
2931     * @return a <CODE>Graphics2D</CODE>
2932     */
2933    public java.awt.Graphics2D createPrinterGraphics(float width, float height, FontMapper fontMapper, PrinterJob printerJob) {
2934        return new PdfPrinterGraphics2D(this, width, height, fontMapper, false, false, 0, printerJob);
2935    }
2936
2937    /** Gets a <CODE>Graphics2D</CODE> to write on. The graphics
2938     * are translated to PDF commands.
2939     * @param width the width of the panel
2940     * @param height the height of the panel
2941     * @param fontMapper the mapping from awt fonts to <CODE>BaseFont</CODE>
2942     * @param convertImagesToJPEG converts awt images to jpeg before inserting in pdf
2943     * @param quality the quality of the jpeg
2944     * @return a <CODE>Graphics2D</CODE>
2945     */
2946    public java.awt.Graphics2D createGraphics(float width, float height, FontMapper fontMapper, boolean convertImagesToJPEG, float quality) {
2947        return new PdfGraphics2D(this, width, height, fontMapper, false, convertImagesToJPEG, quality);
2948    }
2949
2950    /** Gets a <CODE>Graphics2D</CODE> to print on. The graphics
2951     * are translated to PDF commands.
2952     * @param width the width of the panel
2953     * @param height the height of the panel
2954     * @param fontMapper the mapping from awt fonts to <CODE>BaseFont</CODE>
2955     * @param convertImagesToJPEG converts awt images to jpeg before inserting in pdf
2956     * @param quality the quality of the jpeg
2957     * @param printerJob a printer job
2958     * @return a <CODE>Graphics2D</CODE>
2959     */
2960    public java.awt.Graphics2D createPrinterGraphics(float width, float height, FontMapper fontMapper, boolean convertImagesToJPEG, float quality, PrinterJob printerJob) {
2961        return new PdfPrinterGraphics2D(this, width, height, fontMapper, false, convertImagesToJPEG, quality, printerJob);
2962    }
2963
2964    PageResources getPageResources() {
2965        return pdf.getPageResources();
2966    }
2967
2968    /** Sets the graphic state
2969     * @param gstate the graphic state
2970     */
2971    public void setGState(PdfGState gstate) {
2972        PdfObject obj[] = writer.addSimpleExtGState(gstate);
2973        PageResources prs = getPageResources();
2974        PdfName name = prs.addExtGState((PdfName)obj[0], (PdfIndirectReference)obj[1]);
2975        content.append(name.getBytes()).append(" gs").append_i(separator);
2976    }
2977
2978    /**
2979     * Begins a graphic block whose visibility is controlled by the <CODE>layer</CODE>.
2980     * Blocks can be nested. Each block must be terminated by an {@link #endLayer()}.<p>
2981     * Note that nested layers with {@link PdfLayer#addChild(PdfLayer)} only require a single
2982     * call to this method and a single call to {@link #endLayer()}; all the nesting control
2983     * is built in.
2984     * @param layer the layer
2985     */
2986    public void beginLayer(PdfOCG layer) {
2987        if (layer instanceof PdfLayer && ((PdfLayer)layer).getTitle() != null)
2988            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("a.title.is.not.a.layer"));
2989        if (layerDepth == null)
2990            layerDepth = new ArrayList<Integer>();
2991        if (layer instanceof PdfLayerMembership) {
2992            layerDepth.add(Integer.valueOf(1));
2993            beginLayer2(layer);
2994            return;
2995        }
2996        int n = 0;
2997        PdfLayer la = (PdfLayer)layer;
2998        while (la != null) {
2999            if (la.getTitle() == null) {
3000                beginLayer2(la);
3001                ++n;
3002            }
3003            la = la.getParent();
3004        }
3005        layerDepth.add(Integer.valueOf(n));
3006    }
3007
3008    private void beginLayer2(PdfOCG layer) {
3009        PdfName name = (PdfName)writer.addSimpleProperty(layer, layer.getRef())[0];
3010        PageResources prs = getPageResources();
3011        name = prs.addProperty(name, layer.getRef());
3012        content.append("/OC ").append(name.getBytes()).append(" BDC").append_i(separator);
3013    }
3014
3015    /**
3016     * Ends a layer controlled graphic block. It will end the most recent open block.
3017     */
3018    public void endLayer() {
3019        int n = 1;
3020        if (layerDepth != null && !layerDepth.isEmpty()) {
3021            n = layerDepth.get(layerDepth.size() - 1).intValue();
3022            layerDepth.remove(layerDepth.size() - 1);
3023        } else {
3024                throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.layer.operators"));
3025        }
3026        while (n-- > 0)
3027            content.append("EMC").append_i(separator);
3028    }
3029
3030    /** Concatenates a transformation to the current transformation
3031     * matrix.
3032     * @param af the transformation
3033     */
3034    public void transform(AffineTransform af) {
3035        double arr[] = new double[6];
3036        af.getMatrix(arr);
3037        content.append(arr[0]).append(' ').append(arr[1]).append(' ').append(arr[2]).append(' ');
3038        content.append(arr[3]).append(' ').append(arr[4]).append(' ').append(arr[5]).append(" cm").append_i(separator);
3039    }
3040
3041    void addAnnotation(PdfAnnotation annot) {
3042        writer.addAnnotation(annot);
3043    }
3044
3045    /**
3046     * Sets the default colorspace.
3047     * @param name the name of the colorspace. It can be <CODE>PdfName.DEFAULTGRAY</CODE>, <CODE>PdfName.DEFAULTRGB</CODE>
3048     * or <CODE>PdfName.DEFAULTCMYK</CODE>
3049     * @param obj the colorspace. A <CODE>null</CODE> or <CODE>PdfNull</CODE> removes any colorspace with the same name
3050     */
3051    public void setDefaultColorspace(PdfName name, PdfObject obj) {
3052        PageResources prs = getPageResources();
3053        prs.addDefaultColor(name, obj);
3054    }
3055
3056    /**
3057     * Begins a marked content sequence. This sequence will be tagged with the structure <CODE>struc</CODE>.
3058     * The same structure can be used several times to connect text that belongs to the same logical segment
3059     * but is in a different location, like the same paragraph crossing to another page, for example.
3060     * @param struc the tagging structure
3061     */
3062    public void beginMarkedContentSequence(PdfStructureElement struc) {
3063        PdfObject obj = struc.get(PdfName.K);
3064        int mark = pdf.getMarkPoint();
3065        if (obj != null) {
3066            PdfArray ar = null;
3067            if (obj.isNumber()) {
3068                ar = new PdfArray();
3069                ar.add(obj);
3070                struc.put(PdfName.K, ar);
3071            }
3072            else if (obj.isArray()) {
3073                ar = (PdfArray)obj;
3074                if (!ar.getPdfObject(0).isNumber())
3075                    throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.structure.has.kids"));
3076            }
3077            else
3078                throw new IllegalArgumentException(MessageLocalization.getComposedMessage("unknown.object.at.k.1", obj.getClass().toString()));
3079            PdfDictionary dic = new PdfDictionary(PdfName.MCR);
3080            dic.put(PdfName.PG, writer.getCurrentPage());
3081            dic.put(PdfName.MCID, new PdfNumber(mark));
3082            ar.add(dic);
3083            struc.setPageMark(writer.getPageNumber() - 1, -1);
3084        }
3085        else {
3086            struc.setPageMark(writer.getPageNumber() - 1, mark);
3087            struc.put(PdfName.PG, writer.getCurrentPage());
3088        }
3089        pdf.incMarkPoint();
3090        mcDepth++;
3091        content.append(struc.get(PdfName.S).getBytes()).append(" <</MCID ").append(mark).append(">> BDC").append_i(separator);
3092    }
3093
3094    /**
3095     * Ends a marked content sequence
3096     */
3097    public void endMarkedContentSequence() {
3098        if (mcDepth == 0) {
3099                throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.begin.end.marked.content.operators"));
3100        }
3101        --mcDepth;
3102        content.append("EMC").append_i(separator);
3103    }
3104
3105    /**
3106     * Begins a marked content sequence. If property is <CODE>null</CODE> the mark will be of the type
3107     * <CODE>BMC</CODE> otherwise it will be <CODE>BDC</CODE>.
3108     * @param tag the tag
3109     * @param property the property
3110     * @param inline <CODE>true</CODE> to include the property in the content or <CODE>false</CODE>
3111     * to include the property in the resource dictionary with the possibility of reusing
3112     */
3113    public void beginMarkedContentSequence(PdfName tag, PdfDictionary property, boolean inline) {
3114        if (property == null) {
3115            content.append(tag.getBytes()).append(" BMC").append_i(separator);
3116            return;
3117        }
3118        content.append(tag.getBytes()).append(' ');
3119        if (inline)
3120            try {
3121                property.toPdf(writer, content);
3122            }
3123            catch (Exception e) {
3124                throw new ExceptionConverter(e);
3125            }
3126        else {
3127            PdfObject[] objs;
3128            if (writer.propertyExists(property))
3129                objs = writer.addSimpleProperty(property, null);
3130            else
3131                objs = writer.addSimpleProperty(property, writer.getPdfIndirectReference());
3132            PdfName name = (PdfName)objs[0];
3133            PageResources prs = getPageResources();
3134            name = prs.addProperty(name, (PdfIndirectReference)objs[1]);
3135            content.append(name.getBytes());
3136        }
3137        content.append(" BDC").append_i(separator);
3138        ++mcDepth;
3139    }
3140
3141    /**
3142     * This is just a shorthand to <CODE>beginMarkedContentSequence(tag, null, false)</CODE>.
3143     * @param tag the tag
3144     */
3145    public void beginMarkedContentSequence(PdfName tag) {
3146        beginMarkedContentSequence(tag, null, false);
3147    }
3148
3149    /**
3150     * Checks for any dangling state: Mismatched save/restore state, begin/end text,
3151     * begin/end layer, or begin/end marked content sequence.
3152     * If found, this function will throw.  This function is called automatically
3153     * during a reset() (from Document.newPage() for example), and before writing
3154     * itself out in toPdf().
3155     * One possible cause: not calling myPdfGraphics2D.dispose() will leave dangling
3156     *                     saveState() calls.
3157     * @since 2.1.6
3158     * @throws IllegalPdfSyntaxException (a runtime exception)
3159     */
3160    public void sanityCheck() {
3161        if (mcDepth != 0) {
3162                throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.marked.content.operators"));
3163        }
3164        if (inText) {
3165                throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.begin.end.text.operators"));
3166        }
3167        if (layerDepth != null && !layerDepth.isEmpty()) {
3168                throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.layer.operators"));
3169        }
3170        if (!stateList.isEmpty()) {
3171                throw new IllegalPdfSyntaxException(MessageLocalization.getComposedMessage("unbalanced.save.restore.state.operators"));
3172        }
3173    }
3174}