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