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