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