001/* 002 * $Id: PdfDocument.java 4879 2011-05-23 23:27:02Z redlab_b $ 003 * 004 * This file is part of the iText (R) project. 005 * Copyright (c) 1998-2011 1T3XT BVBA 006 * Authors: Bruno Lowagie, Paulo Soares, et al. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU Affero General Public License version 3 010 * as published by the Free Software Foundation with the addition of the 011 * following permission added to Section 15 as permitted in Section 7(a): 012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT, 013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. 014 * 015 * This program is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 017 * or FITNESS FOR A PARTICULAR PURPOSE. 018 * See the GNU Affero General Public License for more details. 019 * You should have received a copy of the GNU Affero General Public License 020 * along with this program; if not, see http://www.gnu.org/licenses or write to 021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 022 * Boston, MA, 02110-1301 USA, or download the license from the following URL: 023 * http://itextpdf.com/terms-of-use/ 024 * 025 * The interactive user interfaces in modified source and object code versions 026 * of this program must display Appropriate Legal Notices, as required under 027 * Section 5 of the GNU Affero General Public License. 028 * 029 * In accordance with Section 7(b) of the GNU Affero General Public License, 030 * a covered work must retain the producer line in every PDF that is created 031 * or manipulated using iText. 032 * 033 * You can be released from the requirements of the license by purchasing 034 * a commercial license. Buying such a license is mandatory as soon as you 035 * develop commercial activities involving the iText software without 036 * disclosing the source code of your own applications. 037 * These activities include: offering paid services to customers as an ASP, 038 * serving PDFs on the fly in a web application, shipping iText with a closed 039 * source product. 040 * 041 * For more information, please contact iText Software Corp. at this 042 * address: sales@itextpdf.com 043 */ 044package com.itextpdf.text.pdf; 045 046import java.io.IOException; 047import java.text.DecimalFormat; 048import java.util.ArrayList; 049import java.util.HashMap; 050import java.util.Iterator; 051import java.util.Map; 052import java.util.TreeMap; 053 054import com.itextpdf.text.Anchor; 055import com.itextpdf.text.Annotation; 056import com.itextpdf.text.BaseColor; 057import com.itextpdf.text.Chunk; 058import com.itextpdf.text.Document; 059import com.itextpdf.text.DocumentException; 060import com.itextpdf.text.Element; 061import com.itextpdf.text.ExceptionConverter; 062import com.itextpdf.text.Font; 063import com.itextpdf.text.Image; 064import com.itextpdf.text.List; 065import com.itextpdf.text.ListItem; 066import com.itextpdf.text.MarkedObject; 067import com.itextpdf.text.MarkedSection; 068import com.itextpdf.text.Meta; 069import com.itextpdf.text.Paragraph; 070import com.itextpdf.text.Phrase; 071import com.itextpdf.text.Rectangle; 072import com.itextpdf.text.Section; 073import com.itextpdf.text.api.WriterOperation; 074import com.itextpdf.text.error_messages.MessageLocalization; 075import com.itextpdf.text.pdf.collection.PdfCollection; 076import com.itextpdf.text.pdf.draw.DrawInterface; 077import com.itextpdf.text.pdf.internal.PdfAnnotationsImp; 078import com.itextpdf.text.pdf.internal.PdfViewerPreferencesImp; 079 080/** 081 * <CODE>PdfDocument</CODE> is the class that is used by <CODE>PdfWriter</CODE> 082 * to translate a <CODE>Document</CODE> into a PDF with different pages. 083 * <P> 084 * A <CODE>PdfDocument</CODE> always listens to a <CODE>Document</CODE> 085 * and adds the Pdf representation of every <CODE>Element</CODE> that is 086 * added to the <CODE>Document</CODE>. 087 * 088 * @see com.itextpdf.text.Document 089 * @see com.itextpdf.text.DocListener 090 * @see PdfWriter 091 * @since 2.0.8 (class was package-private before) 092 */ 093 094public class PdfDocument extends Document { 095 096 /** 097 * <CODE>PdfInfo</CODE> is the PDF InfoDictionary. 098 * <P> 099 * A document's trailer may contain a reference to an Info dictionary that provides information 100 * about the document. This optional dictionary may contain one or more keys, whose values 101 * should be strings.<BR> 102 * This object is described in the 'Portable Document Format Reference Manual version 1.3' 103 * section 6.10 (page 120-121) 104 * @since 2.0.8 (PdfDocument was package-private before) 105 */ 106 107 public static class PdfInfo extends PdfDictionary { 108 109 /** 110 * Construct a <CODE>PdfInfo</CODE>-object. 111 */ 112 113 PdfInfo() { 114 super(); 115 addProducer(); 116 addCreationDate(); 117 } 118 119 /** 120 * Constructs a <CODE>PdfInfo</CODE>-object. 121 * 122 * @param author name of the author of the document 123 * @param title title of the document 124 * @param subject subject of the document 125 */ 126 127 PdfInfo(final String author, final String title, final String subject) { 128 this(); 129 addTitle(title); 130 addSubject(subject); 131 addAuthor(author); 132 } 133 134 /** 135 * Adds the title of the document. 136 * 137 * @param title the title of the document 138 */ 139 140 void addTitle(final String title) { 141 put(PdfName.TITLE, new PdfString(title, PdfObject.TEXT_UNICODE)); 142 } 143 144 /** 145 * Adds the subject to the document. 146 * 147 * @param subject the subject of the document 148 */ 149 150 void addSubject(final String subject) { 151 put(PdfName.SUBJECT, new PdfString(subject, PdfObject.TEXT_UNICODE)); 152 } 153 154 /** 155 * Adds some keywords to the document. 156 * 157 * @param keywords the keywords of the document 158 */ 159 160 void addKeywords(final String keywords) { 161 put(PdfName.KEYWORDS, new PdfString(keywords, PdfObject.TEXT_UNICODE)); 162 } 163 164 /** 165 * Adds the name of the author to the document. 166 * 167 * @param author the name of the author 168 */ 169 170 void addAuthor(final String author) { 171 put(PdfName.AUTHOR, new PdfString(author, PdfObject.TEXT_UNICODE)); 172 } 173 174 /** 175 * Adds the name of the creator to the document. 176 * 177 * @param creator the name of the creator 178 */ 179 180 void addCreator(final String creator) { 181 put(PdfName.CREATOR, new PdfString(creator, PdfObject.TEXT_UNICODE)); 182 } 183 184 /** 185 * Adds the name of the producer to the document. 186 */ 187 188 void addProducer() { 189 put(PdfName.PRODUCER, new PdfString(getVersion())); 190 } 191 192 /** 193 * Adds the date of creation to the document. 194 */ 195 196 void addCreationDate() { 197 PdfString date = new PdfDate(); 198 put(PdfName.CREATIONDATE, date); 199 put(PdfName.MODDATE, date); 200 } 201 202 void addkey(final String key, final String value) { 203 if (key.equals("Producer") || key.equals("CreationDate")) 204 return; 205 put(new PdfName(key), new PdfString(value, PdfObject.TEXT_UNICODE)); 206 } 207 } 208 209 /** 210 * <CODE>PdfCatalog</CODE> is the PDF Catalog-object. 211 * <P> 212 * The Catalog is a dictionary that is the root node of the document. It contains a reference 213 * to the tree of pages contained in the document, a reference to the tree of objects representing 214 * the document's outline, a reference to the document's article threads, and the list of named 215 * destinations. In addition, the Catalog indicates whether the document's outline or thumbnail 216 * page images should be displayed automatically when the document is viewed and whether some location 217 * other than the first page should be shown when the document is opened.<BR> 218 * In this class however, only the reference to the tree of pages is implemented.<BR> 219 * This object is described in the 'Portable Document Format Reference Manual version 1.3' 220 * section 6.2 (page 67-71) 221 */ 222 223 static class PdfCatalog extends PdfDictionary { 224 225 /** The writer writing the PDF for which we are creating this catalog object. */ 226 PdfWriter writer; 227 228 /** 229 * Constructs a <CODE>PdfCatalog</CODE>. 230 * 231 * @param pages an indirect reference to the root of the document's Pages tree. 232 * @param writer the writer the catalog applies to 233 */ 234 235 PdfCatalog(final PdfIndirectReference pages, final PdfWriter writer) { 236 super(CATALOG); 237 this.writer = writer; 238 put(PdfName.PAGES, pages); 239 } 240 241 /** 242 * Adds the names of the named destinations to the catalog. 243 * @param localDestinations the local destinations 244 * @param documentLevelJS the javascript used in the document 245 * @param documentFileAttachment the attached files 246 * @param writer the writer the catalog applies to 247 */ 248 void addNames(final TreeMap<String, Destination> localDestinations, final HashMap<String, PdfObject> documentLevelJS, final HashMap<String, PdfObject> documentFileAttachment, final PdfWriter writer) { 249 if (localDestinations.isEmpty() && documentLevelJS.isEmpty() && documentFileAttachment.isEmpty()) 250 return; 251 try { 252 PdfDictionary names = new PdfDictionary(); 253 if (!localDestinations.isEmpty()) { 254 PdfArray ar = new PdfArray(); 255 for (Map.Entry<String, Destination> entry : localDestinations.entrySet()) { 256 String name = entry.getKey(); 257 Destination dest = entry.getValue(); 258 if (dest.destination == null) //no destination 259 continue; 260 PdfIndirectReference ref = dest.reference; 261 ar.add(new PdfString(name, null)); 262 ar.add(ref); 263 } 264 if (ar.size() > 0) { 265 PdfDictionary dests = new PdfDictionary(); 266 dests.put(PdfName.NAMES, ar); 267 names.put(PdfName.DESTS, writer.addToBody(dests).getIndirectReference()); 268 } 269 } 270 if (!documentLevelJS.isEmpty()) { 271 PdfDictionary tree = PdfNameTree.writeTree(documentLevelJS, writer); 272 names.put(PdfName.JAVASCRIPT, writer.addToBody(tree).getIndirectReference()); 273 } 274 if (!documentFileAttachment.isEmpty()) { 275 names.put(PdfName.EMBEDDEDFILES, writer.addToBody(PdfNameTree.writeTree(documentFileAttachment, writer)).getIndirectReference()); 276 } 277 if (names.size() > 0) 278 put(PdfName.NAMES, writer.addToBody(names).getIndirectReference()); 279 } 280 catch (IOException e) { 281 throw new ExceptionConverter(e); 282 } 283 } 284 285 /** 286 * Adds an open action to the catalog. 287 * @param action the action that will be triggered upon opening the document 288 */ 289 void setOpenAction(final PdfAction action) { 290 put(PdfName.OPENACTION, action); 291 } 292 293 294 /** 295 * Sets the document level additional actions. 296 * @param actions dictionary of actions 297 */ 298 void setAdditionalActions(final PdfDictionary actions) { 299 try { 300 put(PdfName.AA, writer.addToBody(actions).getIndirectReference()); 301 } catch (Exception e) { 302 throw new ExceptionConverter(e); 303 } 304 } 305 } 306 307// CONSTRUCTING A PdfDocument/PdfWriter INSTANCE 308 309 /** 310 * Constructs a new PDF document. 311 */ 312 public PdfDocument() { 313 super(); 314 addProducer(); 315 addCreationDate(); 316 } 317 318 /** The <CODE>PdfWriter</CODE>. */ 319 protected PdfWriter writer; 320 321 /** 322 * Adds a <CODE>PdfWriter</CODE> to the <CODE>PdfDocument</CODE>. 323 * 324 * @param writer the <CODE>PdfWriter</CODE> that writes everything 325 * what is added to this document to an outputstream. 326 * @throws DocumentException on error 327 */ 328 public void addWriter(final PdfWriter writer) throws DocumentException { 329 if (this.writer == null) { 330 this.writer = writer; 331 annotationsImp = new PdfAnnotationsImp(writer); 332 return; 333 } 334 throw new DocumentException(MessageLocalization.getComposedMessage("you.can.only.add.a.writer.to.a.pdfdocument.once")); 335 } 336 337// LISTENER METHODS START 338 339// [L0] ElementListener interface 340 341 /** This is the PdfContentByte object, containing the text. */ 342 protected PdfContentByte text; 343 344 /** This is the PdfContentByte object, containing the borders and other Graphics. */ 345 protected PdfContentByte graphics; 346 347 /** This represents the leading of the lines. */ 348 protected float leading = 0; 349 350 /** 351 * Getter for the current leading. 352 * @return the current leading 353 * @since 2.1.2 354 */ 355 public float getLeading() { 356 return leading; 357 } 358 359 /** 360 * Setter for the current leading. 361 * @param leading the current leading 362 * @since 2.1.6 363 */ 364 void setLeading(final float leading) { 365 this.leading = leading; 366 } 367 368 /** This represents the current alignment of the PDF Elements. */ 369 protected int alignment = Element.ALIGN_LEFT; 370 371 /** This is the current height of the document. */ 372 protected float currentHeight = 0; 373 374 /** 375 * Signals that onParagraph is valid (to avoid that a Chapter/Section title is treated as a Paragraph). 376 * @since 2.1.2 377 */ 378 protected boolean isSectionTitle = false; 379 380 /** 381 * Signals that the current leading has to be subtracted from a YMark object when positive. 382 * @since 2.1.2 383 */ 384 protected int leadingCount = 0; 385 386 /** The current active <CODE>PdfAction</CODE> when processing an <CODE>Anchor</CODE>. */ 387 protected PdfAction anchorAction = null; 388 389 /** 390 * Signals that an <CODE>Element</CODE> was added to the <CODE>Document</CODE>. 391 * 392 * @param element the element to add 393 * @return <CODE>true</CODE> if the element was added, <CODE>false</CODE> if not. 394 * @throws DocumentException when a document isn't open yet, or has been closed 395 */ 396 @Override 397 public boolean add(final Element element) throws DocumentException { 398 if (writer != null && writer.isPaused()) { 399 return false; 400 } 401 try { 402 // TODO refactor this uber long switch to State/Strategy or something ... 403 switch(element.type()) { 404 // Information (headers) 405 case Element.HEADER: 406 info.addkey(((Meta)element).getName(), ((Meta)element).getContent()); 407 break; 408 case Element.TITLE: 409 info.addTitle(((Meta)element).getContent()); 410 break; 411 case Element.SUBJECT: 412 info.addSubject(((Meta)element).getContent()); 413 break; 414 case Element.KEYWORDS: 415 info.addKeywords(((Meta)element).getContent()); 416 break; 417 case Element.AUTHOR: 418 info.addAuthor(((Meta)element).getContent()); 419 break; 420 case Element.CREATOR: 421 info.addCreator(((Meta)element).getContent()); 422 break; 423 case Element.PRODUCER: 424 // you can not change the name of the producer 425 info.addProducer(); 426 break; 427 case Element.CREATIONDATE: 428 // you can not set the creation date, only reset it 429 info.addCreationDate(); 430 break; 431 432 // content (text) 433 case Element.CHUNK: { 434 // if there isn't a current line available, we make one 435 if (line == null) { 436 carriageReturn(); 437 } 438 439 // we cast the element to a chunk 440 PdfChunk chunk = new PdfChunk((Chunk) element, anchorAction); 441 // we try to add the chunk to the line, until we succeed 442 { 443 PdfChunk overflow; 444 while ((overflow = line.add(chunk)) != null) { 445 carriageReturn(); 446 chunk = overflow; 447 chunk.trimFirstSpace(); 448 } 449 } 450 pageEmpty = false; 451 if (chunk.isAttribute(Chunk.NEWPAGE)) { 452 newPage(); 453 } 454 break; 455 } 456 case Element.ANCHOR: { 457 leadingCount++; 458 Anchor anchor = (Anchor) element; 459 String url = anchor.getReference(); 460 leading = anchor.getLeading(); 461 if (url != null) { 462 anchorAction = new PdfAction(url); 463 } 464 // we process the element 465 element.process(this); 466 anchorAction = null; 467 leadingCount--; 468 break; 469 } 470 case Element.ANNOTATION: { 471 if (line == null) { 472 carriageReturn(); 473 } 474 Annotation annot = (Annotation) element; 475 Rectangle rect = new Rectangle(0, 0); 476 if (line != null) 477 rect = new Rectangle(annot.llx(indentRight() - line.widthLeft()), annot.ury(indentTop() - currentHeight - 20), annot.urx(indentRight() - line.widthLeft() + 20), annot.lly(indentTop() - currentHeight)); 478 PdfAnnotation an = PdfAnnotationsImp.convertAnnotation(writer, annot, rect); 479 annotationsImp.addPlainAnnotation(an); 480 pageEmpty = false; 481 break; 482 } 483 case Element.PHRASE: { 484 leadingCount++; 485 // we cast the element to a phrase and set the leading of the document 486 leading = ((Phrase) element).getLeading(); 487 // we process the element 488 element.process(this); 489 leadingCount--; 490 break; 491 } 492 case Element.PARAGRAPH: { 493 leadingCount++; 494 // we cast the element to a paragraph 495 Paragraph paragraph = (Paragraph) element; 496 addSpacing(paragraph.getSpacingBefore(), leading, paragraph.getFont()); 497 498 // we adjust the parameters of the document 499 alignment = paragraph.getAlignment(); 500 leading = paragraph.getTotalLeading(); 501 carriageReturn(); 502 503 // we don't want to make orphans/widows 504 if (currentHeight + line.height() + leading > indentTop() - indentBottom()) { 505 newPage(); 506 } 507 indentation.indentLeft += paragraph.getIndentationLeft(); 508 indentation.indentRight += paragraph.getIndentationRight(); 509 carriageReturn(); 510 511 PdfPageEvent pageEvent = writer.getPageEvent(); 512 if (pageEvent != null && !isSectionTitle) 513 pageEvent.onParagraph(writer, this, indentTop() - currentHeight); 514 515 // if a paragraph has to be kept together, we wrap it in a table object 516 if (paragraph.getKeepTogether()) { 517 carriageReturn(); 518 PdfPTable table = new PdfPTable(1); 519 table.setWidthPercentage(100f); 520 PdfPCell cell = new PdfPCell(); 521 cell.addElement(paragraph); 522 cell.setBorder(Rectangle.NO_BORDER); 523 cell.setPadding(0); 524 table.addCell(cell); 525 indentation.indentLeft -= paragraph.getIndentationLeft(); 526 indentation.indentRight -= paragraph.getIndentationRight(); 527 this.add(table); 528 indentation.indentLeft += paragraph.getIndentationLeft(); 529 indentation.indentRight += paragraph.getIndentationRight(); 530 } 531 else { 532 line.setExtraIndent(paragraph.getFirstLineIndent()); 533 element.process(this); 534 carriageReturn(); 535 addSpacing(paragraph.getSpacingAfter(), paragraph.getTotalLeading(), paragraph.getFont()); 536 } 537 538 if (pageEvent != null && !isSectionTitle) 539 pageEvent.onParagraphEnd(writer, this, indentTop() - currentHeight); 540 541 alignment = Element.ALIGN_LEFT; 542 indentation.indentLeft -= paragraph.getIndentationLeft(); 543 indentation.indentRight -= paragraph.getIndentationRight(); 544 carriageReturn(); 545 leadingCount--; 546 break; 547 } 548 case Element.SECTION: 549 case Element.CHAPTER: { 550 // Chapters and Sections only differ in their constructor 551 // so we cast both to a Section 552 Section section = (Section) element; 553 PdfPageEvent pageEvent = writer.getPageEvent(); 554 555 boolean hasTitle = section.isNotAddedYet() 556 && section.getTitle() != null; 557 558 // if the section is a chapter, we begin a new page 559 if (section.isTriggerNewPage()) { 560 newPage(); 561 } 562 563 if (hasTitle) { 564 float fith = indentTop() - currentHeight; 565 int rotation = pageSize.getRotation(); 566 if (rotation == 90 || rotation == 180) 567 fith = pageSize.getHeight() - fith; 568 PdfDestination destination = new PdfDestination(PdfDestination.FITH, fith); 569 while (currentOutline.level() >= section.getDepth()) { 570 currentOutline = currentOutline.parent(); 571 } 572 PdfOutline outline = new PdfOutline(currentOutline, destination, section.getBookmarkTitle(), section.isBookmarkOpen()); 573 currentOutline = outline; 574 } 575 576 // some values are set 577 carriageReturn(); 578 indentation.sectionIndentLeft += section.getIndentationLeft(); 579 indentation.sectionIndentRight += section.getIndentationRight(); 580 581 if (section.isNotAddedYet() && pageEvent != null) 582 if (element.type() == Element.CHAPTER) 583 pageEvent.onChapter(writer, this, indentTop() - currentHeight, section.getTitle()); 584 else 585 pageEvent.onSection(writer, this, indentTop() - currentHeight, section.getDepth(), section.getTitle()); 586 587 // the title of the section (if any has to be printed) 588 if (hasTitle) { 589 isSectionTitle = true; 590 add(section.getTitle()); 591 isSectionTitle = false; 592 } 593 indentation.sectionIndentLeft += section.getIndentation(); 594 // we process the section 595 element.process(this); 596 flushLines(); 597 // some parameters are set back to normal again 598 indentation.sectionIndentLeft -= section.getIndentationLeft() + section.getIndentation(); 599 indentation.sectionIndentRight -= section.getIndentationRight(); 600 601 if (section.isComplete() && pageEvent != null) 602 if (element.type() == Element.CHAPTER) 603 pageEvent.onChapterEnd(writer, this, indentTop() - currentHeight); 604 else 605 pageEvent.onSectionEnd(writer, this, indentTop() - currentHeight); 606 607 break; 608 } 609 case Element.LIST: { 610 // we cast the element to a List 611 List list = (List) element; 612 if (list.isAlignindent()) { 613 list.normalizeIndentation(); 614 } 615 // we adjust the document 616 indentation.listIndentLeft += list.getIndentationLeft(); 617 indentation.indentRight += list.getIndentationRight(); 618 // we process the items in the list 619 element.process(this); 620 // some parameters are set back to normal again 621 indentation.listIndentLeft -= list.getIndentationLeft(); 622 indentation.indentRight -= list.getIndentationRight(); 623 carriageReturn(); 624 break; 625 } 626 case Element.LISTITEM: { 627 leadingCount++; 628 // we cast the element to a ListItem 629 ListItem listItem = (ListItem) element; 630 631 addSpacing(listItem.getSpacingBefore(), leading, listItem.getFont()); 632 633 // we adjust the document 634 alignment = listItem.getAlignment(); 635 indentation.listIndentLeft += listItem.getIndentationLeft(); 636 indentation.indentRight += listItem.getIndentationRight(); 637 leading = listItem.getTotalLeading(); 638 carriageReturn(); 639 640 // we prepare the current line to be able to show us the listsymbol 641 line.setListItem(listItem); 642 // we process the item 643 element.process(this); 644 645 addSpacing(listItem.getSpacingAfter(), listItem.getTotalLeading(), listItem.getFont()); 646 647 // if the last line is justified, it should be aligned to the left 648 if (line.hasToBeJustified()) { 649 line.resetAlignment(); 650 } 651 // some parameters are set back to normal again 652 carriageReturn(); 653 indentation.listIndentLeft -= listItem.getIndentationLeft(); 654 indentation.indentRight -= listItem.getIndentationRight(); 655 leadingCount--; 656 break; 657 } 658 case Element.RECTANGLE: { 659 Rectangle rectangle = (Rectangle) element; 660 graphics.rectangle(rectangle); 661 pageEmpty = false; 662 break; 663 } 664 case Element.PTABLE: { 665 PdfPTable ptable = (PdfPTable)element; 666 if (ptable.size() <= ptable.getHeaderRows()) 667 break; //nothing to do 668 669 // before every table, we add a new line and flush all lines 670 ensureNewLine(); 671 flushLines(); 672 673 addPTable(ptable); 674 pageEmpty = false; 675 newLine(); 676 break; 677 } 678 case Element.MULTI_COLUMN_TEXT: { 679 ensureNewLine(); 680 flushLines(); 681 MultiColumnText multiText = (MultiColumnText) element; 682 float height = multiText.write(writer.getDirectContent(), this, indentTop() - currentHeight); 683 currentHeight += height; 684 text.moveText(0, -1f* height); 685 pageEmpty = false; 686 break; 687 } 688 case Element.JPEG: 689 case Element.JPEG2000: 690 case Element.JBIG2: 691 case Element.IMGRAW: 692 case Element.IMGTEMPLATE: { 693 //carriageReturn(); suggestion by Marc Campforts 694 add((Image) element); 695 break; 696 } 697 case Element.YMARK: { 698 DrawInterface zh = (DrawInterface)element; 699 zh.draw(graphics, indentLeft(), indentBottom(), indentRight(), indentTop(), indentTop() - currentHeight - (leadingCount > 0 ? leading : 0)); 700 pageEmpty = false; 701 break; 702 } 703 case Element.MARKED: { 704 MarkedObject mo; 705 if (element instanceof MarkedSection) { 706 mo = ((MarkedSection)element).getTitle(); 707 if (mo != null) { 708 mo.process(this); 709 } 710 } 711 mo = (MarkedObject)element; 712 mo.process(this); 713 break; 714 } 715 case Element.WRITABLE_DIRECT: 716 if (null != writer) { 717 ((WriterOperation)element).write(writer, this); 718 } 719 break; 720 default: 721 return false; 722 } 723 lastElementType = element.type(); 724 return true; 725 } 726 catch(Exception e) { 727 throw new DocumentException(e); 728 } 729 } 730 731// [L1] DocListener interface 732 733 /** 734 * Opens the document. 735 * <P> 736 * You have to open the document before you can begin to add content 737 * to the body of the document. 738 */ 739 @Override 740 public void open() { 741 if (!open) { 742 super.open(); 743 writer.open(); 744 rootOutline = new PdfOutline(writer); 745 currentOutline = rootOutline; 746 } 747 try { 748 initPage(); 749 } 750 catch(DocumentException de) { 751 throw new ExceptionConverter(de); 752 } 753 } 754 755// [L2] DocListener interface 756 757 /** 758 * Closes the document. 759 * <B> 760 * Once all the content has been written in the body, you have to close 761 * the body. After that nothing can be written to the body anymore. 762 */ 763 @Override 764 public void close() { 765 if (close) { 766 return; 767 } 768 try { 769 boolean wasImage = imageWait != null; 770 newPage(); 771 if (imageWait != null || wasImage) newPage(); 772 if (annotationsImp.hasUnusedAnnotations()) 773 throw new RuntimeException(MessageLocalization.getComposedMessage("not.all.annotations.could.be.added.to.the.document.the.document.doesn.t.have.enough.pages")); 774 PdfPageEvent pageEvent = writer.getPageEvent(); 775 if (pageEvent != null) 776 pageEvent.onCloseDocument(writer, this); 777 super.close(); 778 779 writer.addLocalDestinations(localDestinations); 780 calculateOutlineCount(); 781 writeOutlines(); 782 } 783 catch(Exception e) { 784 throw ExceptionConverter.convertException(e); 785 } 786 787 writer.close(); 788 } 789 790// [L3] DocListener interface 791 protected int textEmptySize; 792 793 // [C9] Metadata for the page 794 /** 795 * Use this method to set the XMP Metadata. 796 * @param xmpMetadata The xmpMetadata to set. 797 * @throws IOException 798 */ 799 public void setXmpMetadata(final byte[] xmpMetadata) throws IOException { 800 PdfStream xmp = new PdfStream(xmpMetadata); 801 xmp.put(PdfName.TYPE, PdfName.METADATA); 802 xmp.put(PdfName.SUBTYPE, PdfName.XML); 803 PdfEncryption crypto = writer.getEncryption(); 804 if (crypto != null && !crypto.isMetadataEncrypted()) { 805 PdfArray ar = new PdfArray(); 806 ar.add(PdfName.CRYPT); 807 xmp.put(PdfName.FILTER, ar); 808 } 809 writer.addPageDictEntry(PdfName.METADATA, writer.addToBody(xmp).getIndirectReference()); 810 } 811 812 /** 813 * Makes a new page and sends it to the <CODE>PdfWriter</CODE>. 814 * 815 * @return a <CODE>boolean</CODE> 816 */ 817 @Override 818 public boolean newPage() { 819 lastElementType = -1; 820 if (isPageEmpty()) { 821 setNewPageSizeAndMargins(); 822 return false; 823 } 824 if (!open || close) { 825 throw new RuntimeException(MessageLocalization.getComposedMessage("the.document.is.not.open")); 826 } 827 PdfPageEvent pageEvent = writer.getPageEvent(); 828 if (pageEvent != null) 829 pageEvent.onEndPage(writer, this); 830 831 //Added to inform any listeners that we are moving to a new page (added by David Freels) 832 super.newPage(); 833 834 // the following 2 lines were added by Pelikan Stephan 835 indentation.imageIndentLeft = 0; 836 indentation.imageIndentRight = 0; 837 838 try { 839 // we flush the arraylist with recently written lines 840 flushLines(); 841 842 // we prepare the elements of the page dictionary 843 844 // [U1] page size and rotation 845 int rotation = pageSize.getRotation(); 846 847 // [C10] 848 if (writer.isPdfX()) { 849 if (thisBoxSize.containsKey("art") && thisBoxSize.containsKey("trim")) 850 throw new PdfXConformanceException(MessageLocalization.getComposedMessage("only.one.of.artbox.or.trimbox.can.exist.in.the.page")); 851 if (!thisBoxSize.containsKey("art") && !thisBoxSize.containsKey("trim")) { 852 if (thisBoxSize.containsKey("crop")) 853 thisBoxSize.put("trim", thisBoxSize.get("crop")); 854 else 855 thisBoxSize.put("trim", new PdfRectangle(pageSize, pageSize.getRotation())); 856 } 857 } 858 859 // [M1] 860 pageResources.addDefaultColorDiff(writer.getDefaultColorspace()); 861 if (writer.isRgbTransparencyBlending()) { 862 PdfDictionary dcs = new PdfDictionary(); 863 dcs.put(PdfName.CS, PdfName.DEVICERGB); 864 pageResources.addDefaultColorDiff(dcs); 865 } 866 PdfDictionary resources = pageResources.getResources(); 867 868 // we create the page dictionary 869 870 PdfPage page = new PdfPage(new PdfRectangle(pageSize, rotation), thisBoxSize, resources, rotation); 871 page.put(PdfName.TABS, writer.getTabs()); 872 page.putAll(writer.getPageDictEntries()); 873 writer.resetPageDictEntries(); 874 875 // we complete the page dictionary 876 877 // [U3] page actions: additional actions 878 if (pageAA != null) { 879 page.put(PdfName.AA, writer.addToBody(pageAA).getIndirectReference()); 880 pageAA = null; 881 } 882 883 // [C5] and [C8] we add the annotations 884 if (annotationsImp.hasUnusedAnnotations()) { 885 PdfArray array = annotationsImp.rotateAnnotations(writer, pageSize); 886 if (array.size() != 0) 887 page.put(PdfName.ANNOTS, array); 888 } 889 890 // [F12] we add tag info 891 if (writer.isTagged()) 892 page.put(PdfName.STRUCTPARENTS, new PdfNumber(writer.getCurrentPageNumber() - 1)); 893 894 if (text.size() > textEmptySize) 895 text.endText(); 896 else 897 text = null; 898 writer.add(page, new PdfContents(writer.getDirectContentUnder(), graphics, text, writer.getDirectContent(), pageSize)); 899 // we initialize the new page 900 initPage(); 901 } 902 catch(DocumentException de) { 903 // maybe this never happens, but it's better to check. 904 throw new ExceptionConverter(de); 905 } 906 catch (IOException ioe) { 907 throw new ExceptionConverter(ioe); 908 } 909 return true; 910 } 911 912// [L4] DocListener interface 913 914 /** 915 * Sets the pagesize. 916 * 917 * @param pageSize the new pagesize 918 * @return <CODE>true</CODE> if the page size was set 919 */ 920 @Override 921 public boolean setPageSize(final Rectangle pageSize) { 922 if (writer != null && writer.isPaused()) { 923 return false; 924 } 925 nextPageSize = new Rectangle(pageSize); 926 return true; 927 } 928 929// [L5] DocListener interface 930 931 /** margin in x direction starting from the left. Will be valid in the next page */ 932 protected float nextMarginLeft; 933 934 /** margin in x direction starting from the right. Will be valid in the next page */ 935 protected float nextMarginRight; 936 937 /** margin in y direction starting from the top. Will be valid in the next page */ 938 protected float nextMarginTop; 939 940 /** margin in y direction starting from the bottom. Will be valid in the next page */ 941 protected float nextMarginBottom; 942 943 /** 944 * Sets the margins. 945 * 946 * @param marginLeft the margin on the left 947 * @param marginRight the margin on the right 948 * @param marginTop the margin on the top 949 * @param marginBottom the margin on the bottom 950 * @return a <CODE>boolean</CODE> 951 */ 952 @Override 953 public boolean setMargins(final float marginLeft, final float marginRight, final float marginTop, final float marginBottom) { 954 if (writer != null && writer.isPaused()) { 955 return false; 956 } 957 nextMarginLeft = marginLeft; 958 nextMarginRight = marginRight; 959 nextMarginTop = marginTop; 960 nextMarginBottom = marginBottom; 961 return true; 962 } 963 964// [L6] DocListener interface 965 966 /** 967 * @see com.itextpdf.text.DocListener#setMarginMirroring(boolean) 968 */ 969 @Override 970 public boolean setMarginMirroring(final boolean MarginMirroring) { 971 if (writer != null && writer.isPaused()) { 972 return false; 973 } 974 return super.setMarginMirroring(MarginMirroring); 975 } 976 977 /** 978 * @see com.itextpdf.text.DocListener#setMarginMirroring(boolean) 979 * @since 2.1.6 980 */ 981 @Override 982 public boolean setMarginMirroringTopBottom(final boolean MarginMirroringTopBottom) { 983 if (writer != null && writer.isPaused()) { 984 return false; 985 } 986 return super.setMarginMirroringTopBottom(MarginMirroringTopBottom); 987 } 988 989// [L7] DocListener interface 990 991 /** 992 * Sets the page number. 993 * 994 * @param pageN the new page number 995 */ 996 @Override 997 public void setPageCount(final int pageN) { 998 if (writer != null && writer.isPaused()) { 999 return; 1000 } 1001 super.setPageCount(pageN); 1002 } 1003 1004// [L8] DocListener interface 1005 1006 /** 1007 * Sets the page number to 0. 1008 */ 1009 @Override 1010 public void resetPageCount() { 1011 if (writer != null && writer.isPaused()) { 1012 return; 1013 } 1014 super.resetPageCount(); 1015 } 1016 1017// DOCLISTENER METHODS END 1018 1019 /** Signals that OnOpenDocument should be called. */ 1020 protected boolean firstPageEvent = true; 1021 1022 /** 1023 * Initializes a page. 1024 * <P> 1025 * If the footer/header is set, it is printed. 1026 * @throws DocumentException on error 1027 */ 1028 protected void initPage() throws DocumentException { 1029 // the pagenumber is incremented 1030 pageN++; 1031 1032 // initialization of some page objects 1033 annotationsImp.resetAnnotations(); 1034 pageResources = new PageResources(); 1035 1036 writer.resetContent(); 1037 graphics = new PdfContentByte(writer); 1038 1039 markPoint = 0; 1040 setNewPageSizeAndMargins(); 1041 imageEnd = -1; 1042 indentation.imageIndentRight = 0; 1043 indentation.imageIndentLeft = 0; 1044 indentation.indentBottom = 0; 1045 indentation.indentTop = 0; 1046 currentHeight = 0; 1047 1048 // backgroundcolors, etc... 1049 thisBoxSize = new HashMap<String, PdfRectangle>(boxSize); 1050 if (pageSize.getBackgroundColor() != null 1051 || pageSize.hasBorders() 1052 || pageSize.getBorderColor() != null) { 1053 add(pageSize); 1054 } 1055 1056 float oldleading = leading; 1057 int oldAlignment = alignment; 1058 pageEmpty = true; 1059 // if there is an image waiting to be drawn, draw it 1060 try { 1061 if (imageWait != null) { 1062 add(imageWait); 1063 imageWait = null; 1064 } 1065 } 1066 catch(Exception e) { 1067 throw new ExceptionConverter(e); 1068 } 1069 leading = oldleading; 1070 alignment = oldAlignment; 1071 carriageReturn(); 1072 1073 PdfPageEvent pageEvent = writer.getPageEvent(); 1074 if (pageEvent != null) { 1075 if (firstPageEvent) { 1076 pageEvent.onOpenDocument(writer, this); 1077 } 1078 pageEvent.onStartPage(writer, this); 1079 } 1080 firstPageEvent = false; 1081 } 1082 1083 /** The line that is currently being written. */ 1084 protected PdfLine line = null; 1085 /** The lines that are written until now. */ 1086 protected ArrayList<PdfLine> lines = new ArrayList<PdfLine>(); 1087 1088 /** 1089 * Adds the current line to the list of lines and also adds an empty line. 1090 * @throws DocumentException on error 1091 */ 1092 protected void newLine() throws DocumentException { 1093 lastElementType = -1; 1094 carriageReturn(); 1095 if (lines != null && !lines.isEmpty()) { 1096 lines.add(line); 1097 currentHeight += line.height(); 1098 } 1099 line = new PdfLine(indentLeft(), indentRight(), alignment, leading); 1100 } 1101 1102 /** 1103 * If the current line is not empty or null, it is added to the arraylist 1104 * of lines and a new empty line is added. 1105 */ 1106 protected void carriageReturn() { 1107 // the arraylist with lines may not be null 1108 if (lines == null) { 1109 lines = new ArrayList<PdfLine>(); 1110 } 1111 // If the current line is not null or empty 1112 if (line != null && line.size() > 0) { 1113 // we check if the end of the page is reached (bugfix by Francois Gravel) 1114 if (currentHeight + line.height() + leading > indentTop() - indentBottom()) { 1115 // if the end of the line is reached, we start a newPage which will flush existing lines 1116 // then move to next page but before then we need to exclude the current one that does not fit 1117 // After the new page we add the current line back in 1118 PdfLine overflowLine = line; 1119 line = null; 1120 newPage(); 1121 line = overflowLine; 1122 } 1123 currentHeight += line.height(); 1124 lines.add(line); 1125 pageEmpty = false; 1126 } 1127 if (imageEnd > -1 && currentHeight > imageEnd) { 1128 imageEnd = -1; 1129 indentation.imageIndentRight = 0; 1130 indentation.imageIndentLeft = 0; 1131 } 1132 // a new current line is constructed 1133 line = new PdfLine(indentLeft(), indentRight(), alignment, leading); 1134 } 1135 1136 /** 1137 * Gets the current vertical page position. 1138 * @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects 1139 * for elements that do not terminate the lines they've started because those lines will get 1140 * terminated. 1141 * @return The current vertical page position. 1142 */ 1143 public float getVerticalPosition(final boolean ensureNewLine) { 1144 // ensuring that a new line has been started. 1145 if (ensureNewLine) { 1146 ensureNewLine(); 1147 } 1148 return top() - currentHeight - indentation.indentTop; 1149 } 1150 1151 /** Holds the type of the last element, that has been added to the document. */ 1152 protected int lastElementType = -1; 1153 1154 /** 1155 * Ensures that a new line has been started. 1156 */ 1157 protected void ensureNewLine() { 1158 try { 1159 if (lastElementType == Element.PHRASE || 1160 lastElementType == Element.CHUNK) { 1161 newLine(); 1162 flushLines(); 1163 } 1164 } catch (DocumentException ex) { 1165 throw new ExceptionConverter(ex); 1166 } 1167 } 1168 1169 /** 1170 * Writes all the lines to the text-object. 1171 * 1172 * @return the displacement that was caused 1173 * @throws DocumentException on error 1174 */ 1175 protected float flushLines() throws DocumentException { 1176 // checks if the ArrayList with the lines is not null 1177 if (lines == null) { 1178 return 0; 1179 } 1180 // checks if a new Line has to be made. 1181 if (line != null && line.size() > 0) { 1182 lines.add(line); 1183 line = new PdfLine(indentLeft(), indentRight(), alignment, leading); 1184 } 1185 1186 // checks if the ArrayList with the lines is empty 1187 if (lines.isEmpty()) { 1188 return 0; 1189 } 1190 1191 // initialization of some parameters 1192 Object currentValues[] = new Object[2]; 1193 PdfFont currentFont = null; 1194 float displacement = 0; 1195 Float lastBaseFactor = new Float(0); 1196 currentValues[1] = lastBaseFactor; 1197 // looping over all the lines 1198 for (PdfLine l: lines) { 1199 float moveTextX = l.indentLeft() - indentLeft() + indentation.indentLeft + indentation.listIndentLeft + indentation.sectionIndentLeft; 1200 text.moveText(moveTextX, -l.height()); 1201 // is the line preceded by a symbol? 1202 if (l.listSymbol() != null) { 1203 ColumnText.showTextAligned(graphics, Element.ALIGN_LEFT, new Phrase(l.listSymbol()), text.getXTLM() - l.listIndent(), text.getYTLM(), 0); 1204 } 1205 1206 currentValues[0] = currentFont; 1207 1208 writeLineToContent(l, text, graphics, currentValues, writer.getSpaceCharRatio()); 1209 1210 currentFont = (PdfFont)currentValues[0]; 1211 displacement += l.height(); 1212 text.moveText(-moveTextX, 0); 1213 1214 } 1215 lines = new ArrayList<PdfLine>(); 1216 return displacement; 1217 } 1218 1219 /** The characters to be applied the hanging punctuation. */ 1220 static final String hangingPunctuation = ".,;:'"; 1221 1222 /** 1223 * Writes a text line to the document. It takes care of all the attributes. 1224 * <P> 1225 * Before entering the line position must have been established and the 1226 * <CODE>text</CODE> argument must be in text object scope (<CODE>beginText()</CODE>). 1227 * @param line the line to be written 1228 * @param text the <CODE>PdfContentByte</CODE> where the text will be written to 1229 * @param graphics the <CODE>PdfContentByte</CODE> where the graphics will be written to 1230 * @param currentValues the current font and extra spacing values 1231 * @param ratio 1232 * @throws DocumentException on error 1233 * @since 5.0.3 returns a float instead of void 1234 */ 1235 float writeLineToContent(final PdfLine line, final PdfContentByte text, final PdfContentByte graphics, final Object currentValues[], final float ratio) throws DocumentException { 1236 PdfFont currentFont = (PdfFont)currentValues[0]; 1237 float lastBaseFactor = ((Float)currentValues[1]).floatValue(); 1238 PdfChunk chunk; 1239 int numberOfSpaces; 1240 int lineLen; 1241 boolean isJustified; 1242 float hangingCorrection = 0; 1243 float hScale = 1; 1244 float lastHScale = Float.NaN; 1245 float baseWordSpacing = 0; 1246 float baseCharacterSpacing = 0; 1247 float glueWidth = 0; 1248 float lastX = text.getXTLM() + line.getOriginalWidth(); 1249 numberOfSpaces = line.numberOfSpaces(); 1250 lineLen = line.getLineLengthUtf32(); 1251 // does the line need to be justified? 1252 isJustified = line.hasToBeJustified() && (numberOfSpaces != 0 || lineLen > 1); 1253 int separatorCount = line.getSeparatorCount(); 1254 if (separatorCount > 0) { 1255 glueWidth = line.widthLeft() / separatorCount; 1256 } 1257 else if (isJustified && separatorCount == 0) { 1258 if (line.isNewlineSplit() && line.widthLeft() >= lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1)) { 1259 if (line.isRTL()) { 1260 text.moveText(line.widthLeft() - lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1), 0); 1261 } 1262 baseWordSpacing = ratio * lastBaseFactor; 1263 baseCharacterSpacing = lastBaseFactor; 1264 } 1265 else { 1266 float width = line.widthLeft(); 1267 PdfChunk last = line.getChunk(line.size() - 1); 1268 if (last != null) { 1269 String s = last.toString(); 1270 char c; 1271 if (s.length() > 0 && hangingPunctuation.indexOf((c = s.charAt(s.length() - 1))) >= 0) { 1272 float oldWidth = width; 1273 width += last.font().width(c) * 0.4f; 1274 hangingCorrection = width - oldWidth; 1275 } 1276 } 1277 float baseFactor = width / (ratio * numberOfSpaces + lineLen - 1); 1278 baseWordSpacing = ratio * baseFactor; 1279 baseCharacterSpacing = baseFactor; 1280 lastBaseFactor = baseFactor; 1281 } 1282 } 1283 else if (line.alignment == Element.ALIGN_LEFT || line.alignment == Element.ALIGN_UNDEFINED) { 1284 lastX -= line.widthLeft(); 1285 } 1286 1287 int lastChunkStroke = line.getLastStrokeChunk(); 1288 int chunkStrokeIdx = 0; 1289 float xMarker = text.getXTLM(); 1290 float baseXMarker = xMarker; 1291 float yMarker = text.getYTLM(); 1292 boolean adjustMatrix = false; 1293 float tabPosition = 0; 1294 1295 // looping over all the chunks in 1 line 1296 for (Iterator<PdfChunk> j = line.iterator(); j.hasNext(); ) { 1297 chunk = j.next(); 1298 BaseColor color = chunk.color(); 1299 float fontSize = chunk.font().size(); 1300 float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize); 1301 float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize); 1302 hScale = 1; 1303 1304 if (chunkStrokeIdx <= lastChunkStroke) { 1305 float width; 1306 if (isJustified) { 1307 width = chunk.getWidthCorrected(baseCharacterSpacing, baseWordSpacing); 1308 } 1309 else { 1310 width = chunk.width(); 1311 } 1312 if (chunk.isStroked()) { 1313 PdfChunk nextChunk = line.getChunk(chunkStrokeIdx + 1); 1314 if (chunk.isSeparator()) { 1315 width = glueWidth; 1316 Object[] sep = (Object[])chunk.getAttribute(Chunk.SEPARATOR); 1317 DrawInterface di = (DrawInterface)sep[0]; 1318 Boolean vertical = (Boolean)sep[1]; 1319 if (vertical.booleanValue()) { 1320 di.draw(graphics, baseXMarker, yMarker + descender, baseXMarker + line.getOriginalWidth(), ascender - descender, yMarker); 1321 } 1322 else { 1323 di.draw(graphics, xMarker, yMarker + descender, xMarker + width, ascender - descender, yMarker); 1324 } 1325 } 1326 if (chunk.isTab()) { 1327 Object[] tab = (Object[])chunk.getAttribute(Chunk.TAB); 1328 DrawInterface di = (DrawInterface)tab[0]; 1329 tabPosition = ((Float)tab[1]).floatValue() + ((Float)tab[3]).floatValue(); 1330 if (tabPosition > xMarker) { 1331 di.draw(graphics, xMarker, yMarker + descender, tabPosition, ascender - descender, yMarker); 1332 } 1333 float tmp = xMarker; 1334 xMarker = tabPosition; 1335 tabPosition = tmp; 1336 } 1337 if (chunk.isAttribute(Chunk.BACKGROUND)) { 1338 float subtract = lastBaseFactor; 1339 if (nextChunk != null && nextChunk.isAttribute(Chunk.BACKGROUND)) 1340 subtract = 0; 1341 if (nextChunk == null) 1342 subtract += hangingCorrection; 1343 Object bgr[] = (Object[])chunk.getAttribute(Chunk.BACKGROUND); 1344 graphics.setColorFill((BaseColor)bgr[0]); 1345 float extra[] = (float[])bgr[1]; 1346 graphics.rectangle(xMarker - extra[0], 1347 yMarker + descender - extra[1] + chunk.getTextRise(), 1348 width - subtract + extra[0] + extra[2], 1349 ascender - descender + extra[1] + extra[3]); 1350 graphics.fill(); 1351 graphics.setGrayFill(0); 1352 } 1353 if (chunk.isAttribute(Chunk.UNDERLINE)) { 1354 float subtract = lastBaseFactor; 1355 if (nextChunk != null && nextChunk.isAttribute(Chunk.UNDERLINE)) 1356 subtract = 0; 1357 if (nextChunk == null) 1358 subtract += hangingCorrection; 1359 Object unders[][] = (Object[][])chunk.getAttribute(Chunk.UNDERLINE); 1360 BaseColor scolor = null; 1361 for (int k = 0; k < unders.length; ++k) { 1362 Object obj[] = unders[k]; 1363 scolor = (BaseColor)obj[0]; 1364 float ps[] = (float[])obj[1]; 1365 if (scolor == null) 1366 scolor = color; 1367 if (scolor != null) 1368 graphics.setColorStroke(scolor); 1369 graphics.setLineWidth(ps[0] + fontSize * ps[1]); 1370 float shift = ps[2] + fontSize * ps[3]; 1371 int cap2 = (int)ps[4]; 1372 if (cap2 != 0) 1373 graphics.setLineCap(cap2); 1374 graphics.moveTo(xMarker, yMarker + shift); 1375 graphics.lineTo(xMarker + width - subtract, yMarker + shift); 1376 graphics.stroke(); 1377 if (scolor != null) 1378 graphics.resetGrayStroke(); 1379 if (cap2 != 0) 1380 graphics.setLineCap(0); 1381 } 1382 graphics.setLineWidth(1); 1383 } 1384 if (chunk.isAttribute(Chunk.ACTION)) { 1385 float subtract = lastBaseFactor; 1386 if (nextChunk != null && nextChunk.isAttribute(Chunk.ACTION)) 1387 subtract = 0; 1388 if (nextChunk == null) 1389 subtract += hangingCorrection; 1390 text.addAnnotation(new PdfAnnotation(writer, xMarker, yMarker + descender + chunk.getTextRise(), xMarker + width - subtract, yMarker + ascender + chunk.getTextRise(), (PdfAction)chunk.getAttribute(Chunk.ACTION))); 1391 } 1392 if (chunk.isAttribute(Chunk.REMOTEGOTO)) { 1393 float subtract = lastBaseFactor; 1394 if (nextChunk != null && nextChunk.isAttribute(Chunk.REMOTEGOTO)) 1395 subtract = 0; 1396 if (nextChunk == null) 1397 subtract += hangingCorrection; 1398 Object obj[] = (Object[])chunk.getAttribute(Chunk.REMOTEGOTO); 1399 String filename = (String)obj[0]; 1400 if (obj[1] instanceof String) 1401 remoteGoto(filename, (String)obj[1], xMarker, yMarker + descender + chunk.getTextRise(), xMarker + width - subtract, yMarker + ascender + chunk.getTextRise()); 1402 else 1403 remoteGoto(filename, ((Integer)obj[1]).intValue(), xMarker, yMarker + descender + chunk.getTextRise(), xMarker + width - subtract, yMarker + ascender + chunk.getTextRise()); 1404 } 1405 if (chunk.isAttribute(Chunk.LOCALGOTO)) { 1406 float subtract = lastBaseFactor; 1407 if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALGOTO)) 1408 subtract = 0; 1409 if (nextChunk == null) 1410 subtract += hangingCorrection; 1411 localGoto((String)chunk.getAttribute(Chunk.LOCALGOTO), xMarker, yMarker, xMarker + width - subtract, yMarker + fontSize); 1412 } 1413 if (chunk.isAttribute(Chunk.LOCALDESTINATION)) { 1414 float subtract = lastBaseFactor; 1415 if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALDESTINATION)) 1416 subtract = 0; 1417 if (nextChunk == null) 1418 subtract += hangingCorrection; 1419 localDestination((String)chunk.getAttribute(Chunk.LOCALDESTINATION), new PdfDestination(PdfDestination.XYZ, xMarker, yMarker + fontSize, 0)); 1420 } 1421 if (chunk.isAttribute(Chunk.GENERICTAG)) { 1422 float subtract = lastBaseFactor; 1423 if (nextChunk != null && nextChunk.isAttribute(Chunk.GENERICTAG)) 1424 subtract = 0; 1425 if (nextChunk == null) 1426 subtract += hangingCorrection; 1427 Rectangle rect = new Rectangle(xMarker, yMarker, xMarker + width - subtract, yMarker + fontSize); 1428 PdfPageEvent pev = writer.getPageEvent(); 1429 if (pev != null) 1430 pev.onGenericTag(writer, this, rect, (String)chunk.getAttribute(Chunk.GENERICTAG)); 1431 } 1432 if (chunk.isAttribute(Chunk.PDFANNOTATION)) { 1433 float subtract = lastBaseFactor; 1434 if (nextChunk != null && nextChunk.isAttribute(Chunk.PDFANNOTATION)) 1435 subtract = 0; 1436 if (nextChunk == null) 1437 subtract += hangingCorrection; 1438 PdfAnnotation annot = PdfFormField.shallowDuplicate((PdfAnnotation)chunk.getAttribute(Chunk.PDFANNOTATION)); 1439 annot.put(PdfName.RECT, new PdfRectangle(xMarker, yMarker + descender, xMarker + width - subtract, yMarker + ascender)); 1440 text.addAnnotation(annot); 1441 } 1442 float params[] = (float[])chunk.getAttribute(Chunk.SKEW); 1443 Float hs = (Float)chunk.getAttribute(Chunk.HSCALE); 1444 if (params != null || hs != null) { 1445 float b = 0, c = 0; 1446 if (params != null) { 1447 b = params[0]; 1448 c = params[1]; 1449 } 1450 if (hs != null) 1451 hScale = hs.floatValue(); 1452 text.setTextMatrix(hScale, b, c, 1, xMarker, yMarker); 1453 } 1454 if (chunk.isAttribute(Chunk.CHAR_SPACING)) { 1455 Float cs = (Float) chunk.getAttribute(Chunk.CHAR_SPACING); 1456 text.setCharacterSpacing(cs.floatValue()); 1457 } 1458 if (chunk.isImage()) { 1459 Image image = chunk.getImage(); 1460 float matrix[] = image.matrix(); 1461 matrix[Image.CX] = xMarker + chunk.getImageOffsetX() - matrix[Image.CX]; 1462 matrix[Image.CY] = yMarker + chunk.getImageOffsetY() - matrix[Image.CY]; 1463 graphics.addImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); 1464 text.moveText(xMarker + lastBaseFactor + image.getScaledWidth() - text.getXTLM(), 0); 1465 } 1466 } 1467 xMarker += width; 1468 ++chunkStrokeIdx; 1469 } 1470 1471 if (chunk.font().compareTo(currentFont) != 0) { 1472 currentFont = chunk.font(); 1473 text.setFontAndSize(currentFont.getFont(), currentFont.size()); 1474 } 1475 float rise = 0; 1476 Object textRender[] = (Object[])chunk.getAttribute(Chunk.TEXTRENDERMODE); 1477 int tr = 0; 1478 float strokeWidth = 1; 1479 BaseColor strokeColor = null; 1480 Float fr = (Float)chunk.getAttribute(Chunk.SUBSUPSCRIPT); 1481 if (textRender != null) { 1482 tr = ((Integer)textRender[0]).intValue() & 3; 1483 if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL) 1484 text.setTextRenderingMode(tr); 1485 if (tr == PdfContentByte.TEXT_RENDER_MODE_STROKE || tr == PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE) { 1486 strokeWidth = ((Float)textRender[1]).floatValue(); 1487 if (strokeWidth != 1) 1488 text.setLineWidth(strokeWidth); 1489 strokeColor = (BaseColor)textRender[2]; 1490 if (strokeColor == null) 1491 strokeColor = color; 1492 if (strokeColor != null) 1493 text.setColorStroke(strokeColor); 1494 } 1495 } 1496 if (fr != null) 1497 rise = fr.floatValue(); 1498 if (color != null) 1499 text.setColorFill(color); 1500 if (rise != 0) 1501 text.setTextRise(rise); 1502 if (chunk.isImage()) { 1503 adjustMatrix = true; 1504 } 1505 else if (chunk.isHorizontalSeparator()) { 1506 PdfTextArray array = new PdfTextArray(); 1507 array.add(-glueWidth * 1000f / chunk.font.size() / hScale); 1508 text.showText(array); 1509 } 1510 else if (chunk.isTab()) { 1511 PdfTextArray array = new PdfTextArray(); 1512 array.add((tabPosition - xMarker) * 1000f / chunk.font.size() / hScale); 1513 text.showText(array); 1514 } 1515 // If it is a CJK chunk or Unicode TTF we will have to simulate the 1516 // space adjustment. 1517 else if (isJustified && numberOfSpaces > 0 && chunk.isSpecialEncoding()) { 1518 if (hScale != lastHScale) { 1519 lastHScale = hScale; 1520 text.setWordSpacing(baseWordSpacing / hScale); 1521 text.setCharacterSpacing(baseCharacterSpacing / hScale + text.getCharacterSpacing()); 1522 } 1523 String s = chunk.toString(); 1524 int idx = s.indexOf(' '); 1525 if (idx < 0) 1526 text.showText(s); 1527 else { 1528 float spaceCorrection = - baseWordSpacing * 1000f / chunk.font.size() / hScale; 1529 PdfTextArray textArray = new PdfTextArray(s.substring(0, idx)); 1530 int lastIdx = idx; 1531 while ((idx = s.indexOf(' ', lastIdx + 1)) >= 0) { 1532 textArray.add(spaceCorrection); 1533 textArray.add(s.substring(lastIdx, idx)); 1534 lastIdx = idx; 1535 } 1536 textArray.add(spaceCorrection); 1537 textArray.add(s.substring(lastIdx)); 1538 text.showText(textArray); 1539 } 1540 } 1541 else { 1542 if (isJustified && hScale != lastHScale) { 1543 lastHScale = hScale; 1544 text.setWordSpacing(baseWordSpacing / hScale); 1545 text.setCharacterSpacing(baseCharacterSpacing / hScale + text.getCharacterSpacing()); 1546 } 1547 text.showText(chunk.toString()); 1548 } 1549 1550 if (rise != 0) 1551 text.setTextRise(0); 1552 if (color != null) 1553 text.resetRGBColorFill(); 1554 if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL) 1555 text.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL); 1556 if (strokeColor != null) 1557 text.resetRGBColorStroke(); 1558 if (strokeWidth != 1) 1559 text.setLineWidth(1); 1560 if (chunk.isAttribute(Chunk.SKEW) || chunk.isAttribute(Chunk.HSCALE)) { 1561 adjustMatrix = true; 1562 text.setTextMatrix(xMarker, yMarker); 1563 } 1564 if (chunk.isAttribute(Chunk.CHAR_SPACING)) { 1565 text.setCharacterSpacing(baseCharacterSpacing); 1566 } 1567 } 1568 if (isJustified) { 1569 text.setWordSpacing(0); 1570 text.setCharacterSpacing(0); 1571 if (line.isNewlineSplit()) 1572 lastBaseFactor = 0; 1573 } 1574 if (adjustMatrix) 1575 text.moveText(baseXMarker - text.getXTLM(), 0); 1576 currentValues[0] = currentFont; 1577 currentValues[1] = new Float(lastBaseFactor); 1578 return lastX; 1579 } 1580 1581 protected Indentation indentation = new Indentation(); 1582 1583 /** 1584 * @since 2.0.8 (PdfDocument was package-private before) 1585 */ 1586 public static class Indentation { 1587 1588 /** This represents the current indentation of the PDF Elements on the left side. */ 1589 float indentLeft = 0; 1590 1591 /** Indentation to the left caused by a section. */ 1592 float sectionIndentLeft = 0; 1593 1594 /** This represents the current indentation of the PDF Elements on the left side. */ 1595 float listIndentLeft = 0; 1596 1597 /** This is the indentation caused by an image on the left. */ 1598 float imageIndentLeft = 0; 1599 1600 /** This represents the current indentation of the PDF Elements on the right side. */ 1601 float indentRight = 0; 1602 1603 /** Indentation to the right caused by a section. */ 1604 float sectionIndentRight = 0; 1605 1606 /** This is the indentation caused by an image on the right. */ 1607 float imageIndentRight = 0; 1608 1609 /** This represents the current indentation of the PDF Elements on the top side. */ 1610 float indentTop = 0; 1611 1612 /** This represents the current indentation of the PDF Elements on the bottom side. */ 1613 float indentBottom = 0; 1614 } 1615 1616 /** 1617 * Gets the indentation on the left side. 1618 * 1619 * @return a margin 1620 */ 1621 1622 protected float indentLeft() { 1623 return left(indentation.indentLeft + indentation.listIndentLeft + indentation.imageIndentLeft + indentation.sectionIndentLeft); 1624 } 1625 1626 /** 1627 * Gets the indentation on the right side. 1628 * 1629 * @return a margin 1630 */ 1631 1632 protected float indentRight() { 1633 return right(indentation.indentRight + indentation.sectionIndentRight + indentation.imageIndentRight); 1634 } 1635 1636 /** 1637 * Gets the indentation on the top side. 1638 * 1639 * @return a margin 1640 */ 1641 1642 protected float indentTop() { 1643 return top(indentation.indentTop); 1644 } 1645 1646 /** 1647 * Gets the indentation on the bottom side. 1648 * 1649 * @return a margin 1650 */ 1651 1652 float indentBottom() { 1653 return bottom(indentation.indentBottom); 1654 } 1655 1656 /** 1657 * Adds extra space. 1658 * This method should probably be rewritten. 1659 */ 1660 protected void addSpacing(final float extraspace, final float oldleading, Font f) { 1661 if (extraspace == 0) return; 1662 if (pageEmpty) return; 1663 if (currentHeight + line.height() + leading > indentTop() - indentBottom()) return; 1664 leading = extraspace; 1665 carriageReturn(); 1666 if (f.isUnderlined() || f.isStrikethru()) { 1667 f = new Font(f); 1668 int style = f.getStyle(); 1669 style &= ~Font.UNDERLINE; 1670 style &= ~Font.STRIKETHRU; 1671 f.setStyle(style); 1672 } 1673 Chunk space = new Chunk(" ", f); 1674 space.process(this); 1675 carriageReturn(); 1676 leading = oldleading; 1677 } 1678 1679// Info Dictionary and Catalog 1680 1681 /** some meta information about the Document. */ 1682 protected PdfInfo info = new PdfInfo(); 1683 1684 /** 1685 * Gets the <CODE>PdfInfo</CODE>-object. 1686 * 1687 * @return <CODE>PdfInfo</COPE> 1688 */ 1689 1690 PdfInfo getInfo() { 1691 return info; 1692 } 1693 1694 /** 1695 * Gets the <CODE>PdfCatalog</CODE>-object. 1696 * 1697 * @param pages an indirect reference to this document pages 1698 * @return <CODE>PdfCatalog</CODE> 1699 */ 1700 1701 PdfCatalog getCatalog(final PdfIndirectReference pages) { 1702 PdfCatalog catalog = new PdfCatalog(pages, writer); 1703 1704 // [C1] outlines 1705 if (rootOutline.getKids().size() > 0) { 1706 catalog.put(PdfName.PAGEMODE, PdfName.USEOUTLINES); 1707 catalog.put(PdfName.OUTLINES, rootOutline.indirectReference()); 1708 } 1709 1710 // [C2] version 1711 writer.getPdfVersion().addToCatalog(catalog); 1712 1713 // [C3] preferences 1714 viewerPreferences.addToCatalog(catalog); 1715 1716 // [C4] pagelabels 1717 if (pageLabels != null) { 1718 catalog.put(PdfName.PAGELABELS, pageLabels.getDictionary(writer)); 1719 } 1720 1721 // [C5] named objects 1722 catalog.addNames(localDestinations, getDocumentLevelJS(), documentFileAttachment, writer); 1723 1724 // [C6] actions 1725 if (openActionName != null) { 1726 PdfAction action = getLocalGotoAction(openActionName); 1727 catalog.setOpenAction(action); 1728 } 1729 else if (openActionAction != null) 1730 catalog.setOpenAction(openActionAction); 1731 if (additionalActions != null) { 1732 catalog.setAdditionalActions(additionalActions); 1733 } 1734 1735 // [C7] portable collections 1736 if (collection != null) { 1737 catalog.put(PdfName.COLLECTION, collection); 1738 } 1739 1740 // [C8] AcroForm 1741 if (annotationsImp.hasValidAcroForm()) { 1742 try { 1743 catalog.put(PdfName.ACROFORM, writer.addToBody(annotationsImp.getAcroForm()).getIndirectReference()); 1744 } 1745 catch (IOException e) { 1746 throw new ExceptionConverter(e); 1747 } 1748 } 1749 1750 return catalog; 1751 } 1752 1753// [C1] outlines 1754 1755 /** This is the root outline of the document. */ 1756 protected PdfOutline rootOutline; 1757 1758 /** This is the current <CODE>PdfOutline</CODE> in the hierarchy of outlines. */ 1759 protected PdfOutline currentOutline; 1760 1761 /** 1762 * Adds a named outline to the document . 1763 * @param outline the outline to be added 1764 * @param name the name of this local destination 1765 */ 1766 void addOutline(final PdfOutline outline, final String name) { 1767 localDestination(name, outline.getPdfDestination()); 1768 } 1769 1770 /** 1771 * Gets the root outline. All the outlines must be created with a parent. 1772 * The first level is created with this outline. 1773 * @return the root outline 1774 */ 1775 public PdfOutline getRootOutline() { 1776 return rootOutline; 1777 } 1778 1779 1780 /** 1781 * Updates the count in the outlines. 1782 */ 1783 void calculateOutlineCount() { 1784 if (rootOutline.getKids().size() == 0) 1785 return; 1786 traverseOutlineCount(rootOutline); 1787 } 1788 1789 /** 1790 * Recursive method to update the count in the outlines. 1791 */ 1792 void traverseOutlineCount(final PdfOutline outline) { 1793 ArrayList<PdfOutline> kids = outline.getKids(); 1794 PdfOutline parent = outline.parent(); 1795 if (kids.isEmpty()) { 1796 if (parent != null) { 1797 parent.setCount(parent.getCount() + 1); 1798 } 1799 } 1800 else { 1801 for (int k = 0; k < kids.size(); ++k) { 1802 traverseOutlineCount(kids.get(k)); 1803 } 1804 if (parent != null) { 1805 if (outline.isOpen()) { 1806 parent.setCount(outline.getCount() + parent.getCount() + 1); 1807 } 1808 else { 1809 parent.setCount(parent.getCount() + 1); 1810 outline.setCount(-outline.getCount()); 1811 } 1812 } 1813 } 1814 } 1815 1816 /** 1817 * Writes the outline tree to the body of the PDF document. 1818 */ 1819 void writeOutlines() throws IOException { 1820 if (rootOutline.getKids().size() == 0) 1821 return; 1822 outlineTree(rootOutline); 1823 writer.addToBody(rootOutline, rootOutline.indirectReference()); 1824 } 1825 1826 /** 1827 * Recursive method used to write outlines. 1828 */ 1829 void outlineTree(final PdfOutline outline) throws IOException { 1830 outline.setIndirectReference(writer.getPdfIndirectReference()); 1831 if (outline.parent() != null) 1832 outline.put(PdfName.PARENT, outline.parent().indirectReference()); 1833 ArrayList<PdfOutline> kids = outline.getKids(); 1834 int size = kids.size(); 1835 for (int k = 0; k < size; ++k) 1836 outlineTree(kids.get(k)); 1837 for (int k = 0; k < size; ++k) { 1838 if (k > 0) 1839 kids.get(k).put(PdfName.PREV, kids.get(k - 1).indirectReference()); 1840 if (k < size - 1) 1841 kids.get(k).put(PdfName.NEXT, kids.get(k + 1).indirectReference()); 1842 } 1843 if (size > 0) { 1844 outline.put(PdfName.FIRST, kids.get(0).indirectReference()); 1845 outline.put(PdfName.LAST, kids.get(size - 1).indirectReference()); 1846 } 1847 for (int k = 0; k < size; ++k) { 1848 PdfOutline kid = kids.get(k); 1849 writer.addToBody(kid, kid.indirectReference()); 1850 } 1851 } 1852 1853// [C3] PdfViewerPreferences interface 1854 1855 /** Contains the Viewer preferences of this PDF document. */ 1856 protected PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp(); 1857 /** @see com.itextpdf.text.pdf.interfaces.PdfViewerPreferences#setViewerPreferences(int) */ 1858 void setViewerPreferences(final int preferences) { 1859 this.viewerPreferences.setViewerPreferences(preferences); 1860 } 1861 1862 /** @see com.itextpdf.text.pdf.interfaces.PdfViewerPreferences#addViewerPreference(com.itextpdf.text.pdf.PdfName, com.itextpdf.text.pdf.PdfObject) */ 1863 void addViewerPreference(final PdfName key, final PdfObject value) { 1864 this.viewerPreferences.addViewerPreference(key, value); 1865 } 1866 1867// [C4] Page labels 1868 1869 protected PdfPageLabels pageLabels; 1870 /** 1871 * Sets the page labels 1872 * @param pageLabels the page labels 1873 */ 1874 void setPageLabels(final PdfPageLabels pageLabels) { 1875 this.pageLabels = pageLabels; 1876 } 1877 1878// [C5] named objects: local destinations, javascript, embedded files 1879 1880 /** 1881 * Implements a link to other part of the document. The jump will 1882 * be made to a local destination with the same name, that must exist. 1883 * @param name the name for this link 1884 * @param llx the lower left x corner of the activation area 1885 * @param lly the lower left y corner of the activation area 1886 * @param urx the upper right x corner of the activation area 1887 * @param ury the upper right y corner of the activation area 1888 */ 1889 void localGoto(final String name, final float llx, final float lly, final float urx, final float ury) { 1890 PdfAction action = getLocalGotoAction(name); 1891 annotationsImp.addPlainAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action)); 1892 } 1893 1894 /** 1895 * Implements a link to another document. 1896 * @param filename the filename for the remote document 1897 * @param name the name to jump to 1898 * @param llx the lower left x corner of the activation area 1899 * @param lly the lower left y corner of the activation area 1900 * @param urx the upper right x corner of the activation area 1901 * @param ury the upper right y corner of the activation area 1902 */ 1903 void remoteGoto(final String filename, final String name, final float llx, final float lly, final float urx, final float ury) { 1904 annotationsImp.addPlainAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, name))); 1905 } 1906 1907 /** 1908 * Implements a link to another document. 1909 * @param filename the filename for the remote document 1910 * @param page the page to jump to 1911 * @param llx the lower left x corner of the activation area 1912 * @param lly the lower left y corner of the activation area 1913 * @param urx the upper right x corner of the activation area 1914 * @param ury the upper right y corner of the activation area 1915 */ 1916 void remoteGoto(final String filename, final int page, final float llx, final float lly, final float urx, final float ury) { 1917 addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, page))); 1918 } 1919 1920 /** Implements an action in an area. 1921 * @param action the <CODE>PdfAction</CODE> 1922 * @param llx the lower left x corner of the activation area 1923 * @param lly the lower left y corner of the activation area 1924 * @param urx the upper right x corner of the activation area 1925 * @param ury the upper right y corner of the activation area 1926 */ 1927 void setAction(final PdfAction action, final float llx, final float lly, final float urx, final float ury) { 1928 addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action)); 1929 } 1930 1931 /** 1932 * Stores the destinations keyed by name. Value is a <Code>Destination</Code>. 1933 */ 1934 protected TreeMap<String, Destination> localDestinations = new TreeMap<String, Destination>(); 1935 1936 PdfAction getLocalGotoAction(final String name) { 1937 PdfAction action; 1938 Destination dest = localDestinations.get(name); 1939 if (dest == null) 1940 dest = new Destination(); 1941 if (dest.action == null) { 1942 if (dest.reference == null) { 1943 dest.reference = writer.getPdfIndirectReference(); 1944 } 1945 action = new PdfAction(dest.reference); 1946 dest.action = action; 1947 localDestinations.put(name, dest); 1948 } 1949 else { 1950 action = dest.action; 1951 } 1952 return action; 1953 } 1954 1955 /** 1956 * The local destination to where a local goto with the same 1957 * name will jump to. 1958 * @param name the name of this local destination 1959 * @param destination the <CODE>PdfDestination</CODE> with the jump coordinates 1960 * @return <CODE>true</CODE> if the local destination was added, 1961 * <CODE>false</CODE> if a local destination with the same name 1962 * already existed 1963 */ 1964 boolean localDestination(final String name, final PdfDestination destination) { 1965 Destination dest = localDestinations.get(name); 1966 if (dest == null) 1967 dest = new Destination(); 1968 if (dest.destination != null) 1969 return false; 1970 dest.destination = destination; 1971 localDestinations.put(name, dest); 1972 if (!destination.hasPage()) 1973 destination.addPage(writer.getCurrentPage()); 1974 return true; 1975 } 1976 1977 /** 1978 * Stores a list of document level JavaScript actions. 1979 */ 1980 int jsCounter; 1981 protected HashMap<String, PdfObject> documentLevelJS = new HashMap<String, PdfObject>(); 1982 protected static final DecimalFormat SIXTEEN_DIGITS = new DecimalFormat("0000000000000000"); 1983 void addJavaScript(final PdfAction js) { 1984 if (js.get(PdfName.JS) == null) 1985 throw new RuntimeException(MessageLocalization.getComposedMessage("only.javascript.actions.are.allowed")); 1986 try { 1987 documentLevelJS.put(SIXTEEN_DIGITS.format(jsCounter++), writer.addToBody(js).getIndirectReference()); 1988 } 1989 catch (IOException e) { 1990 throw new ExceptionConverter(e); 1991 } 1992 } 1993 void addJavaScript(final String name, final PdfAction js) { 1994 if (js.get(PdfName.JS) == null) 1995 throw new RuntimeException(MessageLocalization.getComposedMessage("only.javascript.actions.are.allowed")); 1996 try { 1997 documentLevelJS.put(name, writer.addToBody(js).getIndirectReference()); 1998 } 1999 catch (IOException e) { 2000 throw new ExceptionConverter(e); 2001 } 2002 } 2003 2004 HashMap<String, PdfObject> getDocumentLevelJS() { 2005 return documentLevelJS; 2006 } 2007 2008 protected HashMap<String, PdfObject> documentFileAttachment = new HashMap<String, PdfObject>(); 2009 2010 void addFileAttachment(String description, final PdfFileSpecification fs) throws IOException { 2011 if (description == null) { 2012 PdfString desc = (PdfString)fs.get(PdfName.DESC); 2013 if (desc == null) { 2014 description = ""; 2015 } 2016 else { 2017 description = PdfEncodings.convertToString(desc.getBytes(), null); 2018 } 2019 } 2020 fs.addDescription(description, true); 2021 if (description.length() == 0) 2022 description = "Unnamed"; 2023 String fn = PdfEncodings.convertToString(new PdfString(description, PdfObject.TEXT_UNICODE).getBytes(), null); 2024 int k = 0; 2025 while (documentFileAttachment.containsKey(fn)) { 2026 ++k; 2027 fn = PdfEncodings.convertToString(new PdfString(description + " " + k, PdfObject.TEXT_UNICODE).getBytes(), null); 2028 } 2029 documentFileAttachment.put(fn, fs.getReference()); 2030 } 2031 2032 HashMap<String, PdfObject> getDocumentFileAttachment() { 2033 return documentFileAttachment; 2034 } 2035 2036// [C6] document level actions 2037 2038 protected String openActionName; 2039 2040 void setOpenAction(final String name) { 2041 openActionName = name; 2042 openActionAction = null; 2043 } 2044 2045 protected PdfAction openActionAction; 2046 void setOpenAction(final PdfAction action) { 2047 openActionAction = action; 2048 openActionName = null; 2049 } 2050 2051 protected PdfDictionary additionalActions; 2052 void addAdditionalAction(final PdfName actionType, final PdfAction action) { 2053 if (additionalActions == null) { 2054 additionalActions = new PdfDictionary(); 2055 } 2056 if (action == null) 2057 additionalActions.remove(actionType); 2058 else 2059 additionalActions.put(actionType, action); 2060 if (additionalActions.size() == 0) 2061 additionalActions = null; 2062 } 2063 2064// [C7] portable collections 2065 2066 protected PdfCollection collection; 2067 2068 /** 2069 * Sets the collection dictionary. 2070 * @param collection a dictionary of type PdfCollection 2071 */ 2072 public void setCollection(final PdfCollection collection) { 2073 this.collection = collection; 2074 } 2075 2076// [C8] AcroForm 2077 2078 PdfAnnotationsImp annotationsImp; 2079 2080 /** 2081 * Gets the AcroForm object. 2082 * @return the PdfAcroform object of the PdfDocument 2083 */ 2084 PdfAcroForm getAcroForm() { 2085 return annotationsImp.getAcroForm(); 2086 } 2087 2088 void setSigFlags(final int f) { 2089 annotationsImp.setSigFlags(f); 2090 } 2091 2092 void addCalculationOrder(final PdfFormField formField) { 2093 annotationsImp.addCalculationOrder(formField); 2094 } 2095 2096 void addAnnotation(final PdfAnnotation annot) { 2097 pageEmpty = false; 2098 annotationsImp.addAnnotation(annot); 2099 } 2100 2101// [F12] tagged PDF 2102 2103 protected int markPoint; 2104 2105 int getMarkPoint() { 2106 return markPoint; 2107 } 2108 2109 void incMarkPoint() { 2110 ++markPoint; 2111 } 2112 2113// [U1] page sizes 2114 2115 /** This is the size of the next page. */ 2116 protected Rectangle nextPageSize = null; 2117 2118 /** This is the size of the several boxes of the current Page. */ 2119 protected HashMap<String, PdfRectangle> thisBoxSize = new HashMap<String, PdfRectangle>(); 2120 2121 /** This is the size of the several boxes that will be used in 2122 * the next page. */ 2123 protected HashMap<String, PdfRectangle> boxSize = new HashMap<String, PdfRectangle>(); 2124 2125 void setCropBoxSize(final Rectangle crop) { 2126 setBoxSize("crop", crop); 2127 } 2128 2129 void setBoxSize(final String boxName, final Rectangle size) { 2130 if (size == null) 2131 boxSize.remove(boxName); 2132 else 2133 boxSize.put(boxName, new PdfRectangle(size)); 2134 } 2135 2136 protected void setNewPageSizeAndMargins() { 2137 pageSize = nextPageSize; 2138 if (marginMirroring && (getPageNumber() & 1) == 0) { 2139 marginRight = nextMarginLeft; 2140 marginLeft = nextMarginRight; 2141 } 2142 else { 2143 marginLeft = nextMarginLeft; 2144 marginRight = nextMarginRight; 2145 } 2146 if (marginMirroringTopBottom && (getPageNumber() & 1) == 0) { 2147 marginTop = nextMarginBottom; 2148 marginBottom = nextMarginTop; 2149 } 2150 else { 2151 marginTop = nextMarginTop; 2152 marginBottom = nextMarginBottom; 2153 } 2154 text = new PdfContentByte(writer); 2155 text.reset(); 2156 text.beginText(); 2157 textEmptySize = text.size(); 2158 // we move to the left/top position of the page 2159 text.moveText(left(), top()); 2160 } 2161 2162 /** 2163 * Gives the size of a trim, art, crop or bleed box, or null if not defined. 2164 * @param boxName crop, trim, art or bleed 2165 */ 2166 Rectangle getBoxSize(final String boxName) { 2167 PdfRectangle r = thisBoxSize.get(boxName); 2168 if (r != null) return r.getRectangle(); 2169 return null; 2170 } 2171 2172// [U2] empty pages 2173 2174 /** This checks if the page is empty. */ 2175 private boolean pageEmpty = true; 2176 2177 void setPageEmpty(final boolean pageEmpty) { 2178 this.pageEmpty = pageEmpty; 2179 } 2180 2181 boolean isPageEmpty() { 2182 return writer == null || writer.getDirectContent().size() == 0 && writer.getDirectContentUnder().size() == 0 && (pageEmpty || writer.isPaused()); 2183 } 2184 2185// [U3] page actions 2186 2187 /** 2188 * Sets the display duration for the page (for presentations) 2189 * @param seconds the number of seconds to display the page 2190 */ 2191 void setDuration(final int seconds) { 2192 if (seconds > 0) 2193 writer.addPageDictEntry(PdfName.DUR, new PdfNumber(seconds)); 2194 } 2195 2196 /** 2197 * Sets the transition for the page 2198 * @param transition the PdfTransition object 2199 */ 2200 void setTransition(final PdfTransition transition) { 2201 writer.addPageDictEntry(PdfName.TRANS, transition.getTransitionDictionary()); 2202 } 2203 2204 protected PdfDictionary pageAA = null; 2205 void setPageAction(final PdfName actionType, final PdfAction action) { 2206 if (pageAA == null) { 2207 pageAA = new PdfDictionary(); 2208 } 2209 pageAA.put(actionType, action); 2210 } 2211 2212// [U8] thumbnail images 2213 2214 void setThumbnail(final Image image) throws PdfException, DocumentException { 2215 writer.addPageDictEntry(PdfName.THUMB, writer.getImageReference(writer.addDirectImageSimple(image))); 2216 } 2217 2218// [M0] Page resources contain references to fonts, extgstate, images,... 2219 2220 /** This are the page resources of the current Page. */ 2221 protected PageResources pageResources; 2222 2223 PageResources getPageResources() { 2224 return pageResources; 2225 } 2226 2227// [M3] Images 2228 2229 /** Holds value of property strictImageSequence. */ 2230 protected boolean strictImageSequence = false; 2231 2232 /** Getter for property strictImageSequence. 2233 * @return Value of property strictImageSequence. 2234 * 2235 */ 2236 boolean isStrictImageSequence() { 2237 return this.strictImageSequence; 2238 } 2239 2240 /** Setter for property strictImageSequence. 2241 * @param strictImageSequence New value of property strictImageSequence. 2242 * 2243 */ 2244 void setStrictImageSequence(final boolean strictImageSequence) { 2245 this.strictImageSequence = strictImageSequence; 2246 } 2247 2248 /** This is the position where the image ends. */ 2249 protected float imageEnd = -1; 2250 2251 /** 2252 * Method added by Pelikan Stephan 2253 */ 2254 public void clearTextWrap() { 2255 float tmpHeight = imageEnd - currentHeight; 2256 if (line != null) { 2257 tmpHeight += line.height(); 2258 } 2259 if (imageEnd > -1 && tmpHeight > 0) { 2260 carriageReturn(); 2261 currentHeight += tmpHeight; 2262 } 2263 } 2264 2265 /** This is the image that could not be shown on a previous page. */ 2266 protected Image imageWait = null; 2267 2268 /** 2269 * Adds an image to the document. 2270 * @param image the <CODE>Image</CODE> to add 2271 * @throws PdfException on error 2272 * @throws DocumentException on error 2273 */ 2274 2275 protected void add(final Image image) throws PdfException, DocumentException { 2276 2277 if (image.hasAbsoluteY()) { 2278 graphics.addImage(image); 2279 pageEmpty = false; 2280 return; 2281 } 2282 2283 // if there isn't enough room for the image on this page, save it for the next page 2284 if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) { 2285 if (!strictImageSequence && imageWait == null) { 2286 imageWait = image; 2287 return; 2288 } 2289 newPage(); 2290 if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) { 2291 imageWait = image; 2292 return; 2293 } 2294 } 2295 pageEmpty = false; 2296 // avoid endless loops 2297 if (image == imageWait) 2298 imageWait = null; 2299 boolean textwrap = (image.getAlignment() & Image.TEXTWRAP) == Image.TEXTWRAP 2300 && !((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE); 2301 boolean underlying = (image.getAlignment() & Image.UNDERLYING) == Image.UNDERLYING; 2302 float diff = leading / 2; 2303 if (textwrap) { 2304 diff += leading; 2305 } 2306 float lowerleft = indentTop() - currentHeight - image.getScaledHeight() -diff; 2307 float mt[] = image.matrix(); 2308 float startPosition = indentLeft() - mt[4]; 2309 if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition = indentRight() - image.getScaledWidth() - mt[4]; 2310 if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition = indentLeft() + (indentRight() - indentLeft() - image.getScaledWidth()) / 2 - mt[4]; 2311 if (image.hasAbsoluteX()) startPosition = image.getAbsoluteX(); 2312 if (textwrap) { 2313 if (imageEnd < 0 || imageEnd < currentHeight + image.getScaledHeight() + diff) { 2314 imageEnd = currentHeight + image.getScaledHeight() + diff; 2315 } 2316 if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) { 2317 // indentation suggested by Pelikan Stephan 2318 indentation.imageIndentRight += image.getScaledWidth() + image.getIndentationLeft(); 2319 } 2320 else { 2321 // indentation suggested by Pelikan Stephan 2322 indentation.imageIndentLeft += image.getScaledWidth() + image.getIndentationRight(); 2323 } 2324 } 2325 else { 2326 if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition -= image.getIndentationRight(); 2327 else if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition += image.getIndentationLeft() - image.getIndentationRight(); 2328 else startPosition += image.getIndentationLeft(); 2329 } 2330 graphics.addImage(image, mt[0], mt[1], mt[2], mt[3], startPosition, lowerleft - mt[5]); 2331 if (!(textwrap || underlying)) { 2332 currentHeight += image.getScaledHeight() + diff; 2333 flushLines(); 2334 text.moveText(0, - (image.getScaledHeight() + diff)); 2335 newLine(); 2336 } 2337 } 2338 2339// [M4] Adding a PdfPTable 2340 2341 /** Adds a <CODE>PdfPTable</CODE> to the document. 2342 * @param ptable the <CODE>PdfPTable</CODE> to be added to the document. 2343 * @throws DocumentException on error 2344 */ 2345 void addPTable(final PdfPTable ptable) throws DocumentException { 2346 ColumnText ct = new ColumnText(writer.getDirectContent()); 2347 // if the table prefers to be on a single page, and it wouldn't 2348 //fit on the current page, start a new page. 2349 if (ptable.getKeepTogether() && !fitsPage(ptable, 0f) && currentHeight > 0) { 2350 newPage(); 2351 } 2352 if (currentHeight == 0) { 2353 ct.setAdjustFirstLine(false); 2354 } 2355 ct.addElement(ptable); 2356 boolean he = ptable.isHeadersInEvent(); 2357 ptable.setHeadersInEvent(true); 2358 int loop = 0; 2359 while (true) { 2360 ct.setSimpleColumn(indentLeft(), indentBottom(), indentRight(), indentTop() - currentHeight); 2361 int status = ct.go(); 2362 if ((status & ColumnText.NO_MORE_TEXT) != 0) { 2363 text.moveText(0, ct.getYLine() - indentTop() + currentHeight); 2364 currentHeight = indentTop() - ct.getYLine(); 2365 break; 2366 } 2367 if (indentTop() - currentHeight == ct.getYLine()) 2368 ++loop; 2369 else 2370 loop = 0; 2371 if (loop == 3) { 2372 throw new DocumentException(MessageLocalization.getComposedMessage("infinite.table.loop")); 2373 } 2374 newPage(); 2375 } 2376 ptable.setHeadersInEvent(he); 2377 } 2378 2379 /** 2380 * Checks if a <CODE>PdfPTable</CODE> fits the current page of the <CODE>PdfDocument</CODE>. 2381 * 2382 * @param table the table that has to be checked 2383 * @param margin a certain margin 2384 * @return <CODE>true</CODE> if the <CODE>PdfPTable</CODE> fits the page, <CODE>false</CODE> otherwise. 2385 */ 2386 2387 boolean fitsPage(final PdfPTable table, final float margin) { 2388 if (!table.isLockedWidth()) { 2389 float totalWidth = (indentRight() - indentLeft()) * table.getWidthPercentage() / 100; 2390 table.setTotalWidth(totalWidth); 2391 } 2392 // ensuring that a new line has been started. 2393 ensureNewLine(); 2394 return table.getTotalHeight() + (currentHeight > 0 ? table.spacingBefore() : 0f) 2395 <= indentTop() - currentHeight - indentBottom() - margin; 2396 } 2397 2398 /** 2399 * @since 5.0.1 2400 */ 2401 public class Destination { 2402 public PdfAction action; 2403 public PdfIndirectReference reference; 2404 public PdfDestination destination; 2405 } 2406}