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