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