1 /* 2 * $Id$ 3 * 4 * Copyright 1999, 2000, 2001, 2002 by 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 52 import java.awt.Color; 53 import java.io.IOException; 54 import java.util.ArrayList; 55 import java.util.HashMap; 56 import java.util.HashSet; 57 import java.util.Iterator; 58 import java.util.Map; 59 import java.util.Set; 60 import java.util.TreeMap; 61 import com.lowagie.text.error_messages.MessageLocalization; 62 63 import com.lowagie.text.Anchor; 64 import com.lowagie.text.Annotation; 65 import com.lowagie.text.BadElementException; 66 import com.lowagie.text.Chunk; 67 import com.lowagie.text.Document; 68 import com.lowagie.text.DocumentException; 69 import com.lowagie.text.Element; 70 import com.lowagie.text.ExceptionConverter; 71 import com.lowagie.text.Font; 72 import com.lowagie.text.HeaderFooter; 73 import com.lowagie.text.Image; 74 import com.lowagie.text.List; 75 import com.lowagie.text.ListItem; 76 import com.lowagie.text.MarkedObject; 77 import com.lowagie.text.MarkedSection; 78 import com.lowagie.text.Meta; 79 import com.lowagie.text.Paragraph; 80 import com.lowagie.text.Phrase; 81 import com.lowagie.text.Rectangle; 82 import com.lowagie.text.Section; 83 import com.lowagie.text.SimpleTable; 84 import com.lowagie.text.Table; 85 import com.lowagie.text.pdf.collection.PdfCollection; 86 import com.lowagie.text.pdf.draw.DrawInterface; 87 import com.lowagie.text.pdf.internal.PdfAnnotationsImp; 88 import com.lowagie.text.pdf.internal.PdfViewerPreferencesImp; 89 import java.text.DecimalFormat; 90 91 /** 92 * <CODE>PdfDocument</CODE> is the class that is used by <CODE>PdfWriter</CODE> 93 * to translate a <CODE>Document</CODE> into a PDF with different pages. 94 * <P> 95 * A <CODE>PdfDocument</CODE> always listens to a <CODE>Document</CODE> 96 * and adds the Pdf representation of every <CODE>Element</CODE> that is 97 * added to the <CODE>Document</CODE>. 98 * 99 * @see com.lowagie.text.Document 100 * @see com.lowagie.text.DocListener 101 * @see PdfWriter 102 * @since 2.0.8 (class was package-private before) 103 */ 104 105 public class PdfDocument extends Document { 106 107 /** 108 * <CODE>PdfInfo</CODE> is the PDF InfoDictionary. 109 * <P> 110 * A document's trailer may contain a reference to an Info dictionary that provides information 111 * about the document. This optional dictionary may contain one or more keys, whose values 112 * should be strings.<BR> 113 * This object is described in the 'Portable Document Format Reference Manual version 1.3' 114 * section 6.10 (page 120-121) 115 * @since 2.0.8 (PdfDocument was package-private before) 116 */ 117 118 public static class PdfInfo extends PdfDictionary { 119 120 /** 121 * Construct a <CODE>PdfInfo</CODE>-object. 122 */ 123 PdfInfo()124 PdfInfo() { 125 super(); 126 addProducer(); 127 addCreationDate(); 128 } 129 130 /** 131 * Constructs a <CODE>PdfInfo</CODE>-object. 132 * 133 * @param author name of the author of the document 134 * @param title title of the document 135 * @param subject subject of the document 136 */ 137 PdfInfo(String author, String title, String subject)138 PdfInfo(String author, String title, String subject) { 139 this(); 140 addTitle(title); 141 addSubject(subject); 142 addAuthor(author); 143 } 144 145 /** 146 * Adds the title of the document. 147 * 148 * @param title the title of the document 149 */ 150 addTitle(String title)151 void addTitle(String title) { 152 put(PdfName.TITLE, new PdfString(title, PdfObject.TEXT_UNICODE)); 153 } 154 155 /** 156 * Adds the subject to the document. 157 * 158 * @param subject the subject of the document 159 */ 160 addSubject(String subject)161 void addSubject(String subject) { 162 put(PdfName.SUBJECT, new PdfString(subject, PdfObject.TEXT_UNICODE)); 163 } 164 165 /** 166 * Adds some keywords to the document. 167 * 168 * @param keywords the keywords of the document 169 */ 170 addKeywords(String keywords)171 void addKeywords(String keywords) { 172 put(PdfName.KEYWORDS, new PdfString(keywords, PdfObject.TEXT_UNICODE)); 173 } 174 175 /** 176 * Adds the name of the author to the document. 177 * 178 * @param author the name of the author 179 */ 180 addAuthor(String author)181 void addAuthor(String author) { 182 put(PdfName.AUTHOR, new PdfString(author, PdfObject.TEXT_UNICODE)); 183 } 184 185 /** 186 * Adds the name of the creator to the document. 187 * 188 * @param creator the name of the creator 189 */ 190 addCreator(String creator)191 void addCreator(String creator) { 192 put(PdfName.CREATOR, new PdfString(creator, PdfObject.TEXT_UNICODE)); 193 } 194 195 /** 196 * Adds the name of the producer to the document. 197 */ 198 addProducer()199 void addProducer() { 200 put(PdfName.PRODUCER, new PdfString(getVersion())); 201 } 202 203 /** 204 * Adds the date of creation to the document. 205 */ 206 addCreationDate()207 void addCreationDate() { 208 PdfString date = new PdfDate(); 209 put(PdfName.CREATIONDATE, date); 210 put(PdfName.MODDATE, date); 211 } 212 addkey(String key, String value)213 void addkey(String key, String value) { 214 if (key.equals("Producer") || key.equals("CreationDate")) 215 return; 216 put(new PdfName(key), new PdfString(value, PdfObject.TEXT_UNICODE)); 217 } 218 } 219 220 /** 221 * <CODE>PdfCatalog</CODE> is the PDF Catalog-object. 222 * <P> 223 * The Catalog is a dictionary that is the root node of the document. It contains a reference 224 * to the tree of pages contained in the document, a reference to the tree of objects representing 225 * the document's outline, a reference to the document's article threads, and the list of named 226 * destinations. In addition, the Catalog indicates whether the document's outline or thumbnail 227 * page images should be displayed automatically when the document is viewed and whether some location 228 * other than the first page should be shown when the document is opened.<BR> 229 * In this class however, only the reference to the tree of pages is implemented.<BR> 230 * This object is described in the 'Portable Document Format Reference Manual version 1.3' 231 * section 6.2 (page 67-71) 232 */ 233 234 static class PdfCatalog extends PdfDictionary { 235 236 /** The writer writing the PDF for which we are creating this catalog object. */ 237 PdfWriter writer; 238 239 /** 240 * Constructs a <CODE>PdfCatalog</CODE>. 241 * 242 * @param pages an indirect reference to the root of the document's Pages tree. 243 * @param writer the writer the catalog applies to 244 */ 245 PdfCatalog(PdfIndirectReference pages, PdfWriter writer)246 PdfCatalog(PdfIndirectReference pages, PdfWriter writer) { 247 super(CATALOG); 248 this.writer = writer; 249 put(PdfName.PAGES, pages); 250 } 251 252 /** 253 * Adds the names of the named destinations to the catalog. 254 * @param localDestinations the local destinations 255 * @param documentLevelJS the javascript used in the document 256 * @param documentFileAttachment the attached files 257 * @param writer the writer the catalog applies to 258 */ addNames(TreeMap localDestinations, HashMap documentLevelJS, HashMap documentFileAttachment, PdfWriter writer)259 void addNames(TreeMap localDestinations, HashMap documentLevelJS, HashMap documentFileAttachment, PdfWriter writer) { 260 if (localDestinations.isEmpty() && documentLevelJS.isEmpty() && documentFileAttachment.isEmpty()) 261 return; 262 try { 263 PdfDictionary names = new PdfDictionary(); 264 if (!localDestinations.isEmpty()) { 265 PdfArray ar = new PdfArray(); 266 for (Iterator i = localDestinations.entrySet().iterator(); i.hasNext();) { 267 Map.Entry entry = (Map.Entry) i.next(); 268 String name = (String) entry.getKey(); 269 Object obj[] = (Object[]) entry.getValue(); 270 if (obj[2] == null) //no destination 271 continue; 272 PdfIndirectReference ref = (PdfIndirectReference)obj[1]; 273 ar.add(new PdfString(name, null)); 274 ar.add(ref); 275 } 276 if (ar.size() > 0) { 277 PdfDictionary dests = new PdfDictionary(); 278 dests.put(PdfName.NAMES, ar); 279 names.put(PdfName.DESTS, writer.addToBody(dests).getIndirectReference()); 280 } 281 } 282 if (!documentLevelJS.isEmpty()) { 283 PdfDictionary tree = PdfNameTree.writeTree(documentLevelJS, writer); 284 names.put(PdfName.JAVASCRIPT, writer.addToBody(tree).getIndirectReference()); 285 } 286 if (!documentFileAttachment.isEmpty()) { 287 names.put(PdfName.EMBEDDEDFILES, writer.addToBody(PdfNameTree.writeTree(documentFileAttachment, writer)).getIndirectReference()); 288 } 289 if (names.size() > 0) 290 put(PdfName.NAMES, writer.addToBody(names).getIndirectReference()); 291 } 292 catch (IOException e) { 293 throw new ExceptionConverter(e); 294 } 295 } 296 297 /** 298 * Adds an open action to the catalog. 299 * @param action the action that will be triggered upon opening the document 300 */ setOpenAction(PdfAction action)301 void setOpenAction(PdfAction action) { 302 put(PdfName.OPENACTION, action); 303 } 304 305 306 /** 307 * Sets the document level additional actions. 308 * @param actions dictionary of actions 309 */ setAdditionalActions(PdfDictionary actions)310 void setAdditionalActions(PdfDictionary actions) { 311 try { 312 put(PdfName.AA, writer.addToBody(actions).getIndirectReference()); 313 } catch (Exception e) { 314 throw new ExceptionConverter(e); 315 } 316 } 317 } 318 319 // CONSTRUCTING A PdfDocument/PdfWriter INSTANCE 320 321 /** 322 * Constructs a new PDF document. 323 */ PdfDocument()324 public PdfDocument() { 325 super(); 326 addProducer(); 327 addCreationDate(); 328 } 329 330 /** The <CODE>PdfWriter</CODE>. */ 331 protected PdfWriter writer; 332 333 /** 334 * Adds a <CODE>PdfWriter</CODE> to the <CODE>PdfDocument</CODE>. 335 * 336 * @param writer the <CODE>PdfWriter</CODE> that writes everything 337 * what is added to this document to an outputstream. 338 * @throws DocumentException on error 339 */ addWriter(PdfWriter writer)340 public void addWriter(PdfWriter writer) throws DocumentException { 341 if (this.writer == null) { 342 this.writer = writer; 343 annotationsImp = new PdfAnnotationsImp(writer); 344 return; 345 } 346 throw new DocumentException(MessageLocalization.getComposedMessage("you.can.only.add.a.writer.to.a.pdfdocument.once")); 347 } 348 349 // LISTENER METHODS START 350 351 // [L0] ElementListener interface 352 353 /** This is the PdfContentByte object, containing the text. */ 354 protected PdfContentByte text; 355 356 /** This is the PdfContentByte object, containing the borders and other Graphics. */ 357 protected PdfContentByte graphics; 358 359 /** This represents the leading of the lines. */ 360 protected float leading = 0; 361 362 /** 363 * Getter for the current leading. 364 * @return the current leading 365 * @since 2.1.2 366 */ getLeading()367 public float getLeading() { 368 return leading; 369 } 370 371 /** 372 * Setter for the current leading. 373 * @param leading the current leading 374 * @since 2.1.6 375 */ setLeading(float leading)376 void setLeading(float leading) { 377 this.leading = leading; 378 } 379 380 /** This represents the current alignment of the PDF Elements. */ 381 protected int alignment = Element.ALIGN_LEFT; 382 383 /** This is the current height of the document. */ 384 protected float currentHeight = 0; 385 386 /** 387 * Signals that onParagraph is valid (to avoid that a Chapter/Section title is treated as a Paragraph). 388 * @since 2.1.2 389 */ 390 protected boolean isSectionTitle = false; 391 392 /** 393 * Signals that the current leading has to be subtracted from a YMark object when positive. 394 * @since 2.1.2 395 */ 396 protected int leadingCount = 0; 397 398 /** The current active <CODE>PdfAction</CODE> when processing an <CODE>Anchor</CODE>. */ 399 protected PdfAction anchorAction = null; 400 401 /** 402 * Signals that an <CODE>Element</CODE> was added to the <CODE>Document</CODE>. 403 * 404 * @param element the element to add 405 * @return <CODE>true</CODE> if the element was added, <CODE>false</CODE> if not. 406 * @throws DocumentException when a document isn't open yet, or has been closed 407 */ add(Element element)408 public boolean add(Element element) throws DocumentException { 409 if (writer != null && writer.isPaused()) { 410 return false; 411 } 412 try { 413 switch(element.type()) { 414 // Information (headers) 415 case Element.HEADER: 416 info.addkey(((Meta)element).getName(), ((Meta)element).getContent()); 417 break; 418 case Element.TITLE: 419 info.addTitle(((Meta)element).getContent()); 420 break; 421 case Element.SUBJECT: 422 info.addSubject(((Meta)element).getContent()); 423 break; 424 case Element.KEYWORDS: 425 info.addKeywords(((Meta)element).getContent()); 426 break; 427 case Element.AUTHOR: 428 info.addAuthor(((Meta)element).getContent()); 429 break; 430 case Element.CREATOR: 431 info.addCreator(((Meta)element).getContent()); 432 break; 433 case Element.PRODUCER: 434 // you can not change the name of the producer 435 info.addProducer(); 436 break; 437 case Element.CREATIONDATE: 438 // you can not set the creation date, only reset it 439 info.addCreationDate(); 440 break; 441 442 // content (text) 443 case Element.CHUNK: { 444 // if there isn't a current line available, we make one 445 if (line == null) { 446 carriageReturn(); 447 } 448 449 // we cast the element to a chunk 450 PdfChunk chunk = new PdfChunk((Chunk) element, anchorAction); 451 // we try to add the chunk to the line, until we succeed 452 { 453 PdfChunk overflow; 454 while ((overflow = line.add(chunk)) != null) { 455 carriageReturn(); 456 chunk = overflow; 457 chunk.trimFirstSpace(); 458 } 459 } 460 pageEmpty = false; 461 if (chunk.isAttribute(Chunk.NEWPAGE)) { 462 newPage(); 463 } 464 break; 465 } 466 case Element.ANCHOR: { 467 leadingCount++; 468 Anchor anchor = (Anchor) element; 469 String url = anchor.getReference(); 470 leading = anchor.getLeading(); 471 if (url != null) { 472 anchorAction = new PdfAction(url); 473 } 474 // we process the element 475 element.process(this); 476 anchorAction = null; 477 leadingCount--; 478 break; 479 } 480 case Element.ANNOTATION: { 481 if (line == null) { 482 carriageReturn(); 483 } 484 Annotation annot = (Annotation) element; 485 Rectangle rect = new Rectangle(0, 0); 486 if (line != null) 487 rect = new Rectangle(annot.llx(indentRight() - line.widthLeft()), annot.ury(indentTop() - currentHeight - 20), annot.urx(indentRight() - line.widthLeft() + 20), annot.lly(indentTop() - currentHeight)); 488 PdfAnnotation an = PdfAnnotationsImp.convertAnnotation(writer, annot, rect); 489 annotationsImp.addPlainAnnotation(an); 490 pageEmpty = false; 491 break; 492 } 493 case Element.PHRASE: { 494 leadingCount++; 495 // we cast the element to a phrase and set the leading of the document 496 leading = ((Phrase) element).getLeading(); 497 // we process the element 498 element.process(this); 499 leadingCount--; 500 break; 501 } 502 case Element.PARAGRAPH: { 503 leadingCount++; 504 // we cast the element to a paragraph 505 Paragraph paragraph = (Paragraph) element; 506 addSpacing(paragraph.getSpacingBefore(), leading, paragraph.getFont()); 507 508 // we adjust the parameters of the document 509 alignment = paragraph.getAlignment(); 510 leading = paragraph.getTotalLeading(); 511 carriageReturn(); 512 513 // we don't want to make orphans/widows 514 if (currentHeight + line.height() + leading > indentTop() - indentBottom()) { 515 newPage(); 516 } 517 indentation.indentLeft += paragraph.getIndentationLeft(); 518 indentation.indentRight += paragraph.getIndentationRight(); 519 carriageReturn(); 520 521 PdfPageEvent pageEvent = writer.getPageEvent(); 522 if (pageEvent != null && !isSectionTitle) 523 pageEvent.onParagraph(writer, this, indentTop() - currentHeight); 524 525 // if a paragraph has to be kept together, we wrap it in a table object 526 if (paragraph.getKeepTogether()) { 527 carriageReturn(); 528 PdfPTable table = new PdfPTable(1); 529 table.setWidthPercentage(100f); 530 PdfPCell cell = new PdfPCell(); 531 cell.addElement(paragraph); 532 cell.setBorder(Table.NO_BORDER); 533 cell.setPadding(0); 534 table.addCell(cell); 535 indentation.indentLeft -= paragraph.getIndentationLeft(); 536 indentation.indentRight -= paragraph.getIndentationRight(); 537 this.add(table); 538 indentation.indentLeft += paragraph.getIndentationLeft(); 539 indentation.indentRight += paragraph.getIndentationRight(); 540 } 541 else { 542 line.setExtraIndent(paragraph.getFirstLineIndent()); 543 element.process(this); 544 carriageReturn(); 545 addSpacing(paragraph.getSpacingAfter(), paragraph.getTotalLeading(), paragraph.getFont()); 546 } 547 548 if (pageEvent != null && !isSectionTitle) 549 pageEvent.onParagraphEnd(writer, this, indentTop() - currentHeight); 550 551 alignment = Element.ALIGN_LEFT; 552 indentation.indentLeft -= paragraph.getIndentationLeft(); 553 indentation.indentRight -= paragraph.getIndentationRight(); 554 carriageReturn(); 555 leadingCount--; 556 break; 557 } 558 case Element.SECTION: 559 case Element.CHAPTER: { 560 // Chapters and Sections only differ in their constructor 561 // so we cast both to a Section 562 Section section = (Section) element; 563 PdfPageEvent pageEvent = writer.getPageEvent(); 564 565 boolean hasTitle = section.isNotAddedYet() 566 && section.getTitle() != null; 567 568 // if the section is a chapter, we begin a new page 569 if (section.isTriggerNewPage()) { 570 newPage(); 571 } 572 573 if (hasTitle) { 574 float fith = indentTop() - currentHeight; 575 int rotation = pageSize.getRotation(); 576 if (rotation == 90 || rotation == 180) 577 fith = pageSize.getHeight() - fith; 578 PdfDestination destination = new PdfDestination(PdfDestination.FITH, fith); 579 while (currentOutline.level() >= section.getDepth()) { 580 currentOutline = currentOutline.parent(); 581 } 582 PdfOutline outline = new PdfOutline(currentOutline, destination, section.getBookmarkTitle(), section.isBookmarkOpen()); 583 currentOutline = outline; 584 } 585 586 // some values are set 587 carriageReturn(); 588 indentation.sectionIndentLeft += section.getIndentationLeft(); 589 indentation.sectionIndentRight += section.getIndentationRight(); 590 591 if (section.isNotAddedYet() && pageEvent != null) 592 if (element.type() == Element.CHAPTER) 593 pageEvent.onChapter(writer, this, indentTop() - currentHeight, section.getTitle()); 594 else 595 pageEvent.onSection(writer, this, indentTop() - currentHeight, section.getDepth(), section.getTitle()); 596 597 // the title of the section (if any has to be printed) 598 if (hasTitle) { 599 isSectionTitle = true; 600 add(section.getTitle()); 601 isSectionTitle = false; 602 } 603 indentation.sectionIndentLeft += section.getIndentation(); 604 // we process the section 605 element.process(this); 606 flushLines(); 607 // some parameters are set back to normal again 608 indentation.sectionIndentLeft -= (section.getIndentationLeft() + section.getIndentation()); 609 indentation.sectionIndentRight -= section.getIndentationRight(); 610 611 if (section.isComplete() && pageEvent != null) 612 if (element.type() == Element.CHAPTER) 613 pageEvent.onChapterEnd(writer, this, indentTop() - currentHeight); 614 else 615 pageEvent.onSectionEnd(writer, this, indentTop() - currentHeight); 616 617 break; 618 } 619 case Element.LIST: { 620 // we cast the element to a List 621 List list = (List) element; 622 if (list.isAlignindent()) { 623 list.normalizeIndentation(); 624 } 625 // we adjust the document 626 indentation.listIndentLeft += list.getIndentationLeft(); 627 indentation.indentRight += list.getIndentationRight(); 628 // we process the items in the list 629 element.process(this); 630 // some parameters are set back to normal again 631 indentation.listIndentLeft -= list.getIndentationLeft(); 632 indentation.indentRight -= list.getIndentationRight(); 633 carriageReturn(); 634 break; 635 } 636 case Element.LISTITEM: { 637 leadingCount++; 638 // we cast the element to a ListItem 639 ListItem listItem = (ListItem) element; 640 641 addSpacing(listItem.getSpacingBefore(), leading, listItem.getFont()); 642 643 // we adjust the document 644 alignment = listItem.getAlignment(); 645 indentation.listIndentLeft += listItem.getIndentationLeft(); 646 indentation.indentRight += listItem.getIndentationRight(); 647 leading = listItem.getTotalLeading(); 648 carriageReturn(); 649 650 // we prepare the current line to be able to show us the listsymbol 651 line.setListItem(listItem); 652 // we process the item 653 element.process(this); 654 655 addSpacing(listItem.getSpacingAfter(), listItem.getTotalLeading(), listItem.getFont()); 656 657 // if the last line is justified, it should be aligned to the left 658 if (line.hasToBeJustified()) { 659 line.resetAlignment(); 660 } 661 // some parameters are set back to normal again 662 carriageReturn(); 663 indentation.listIndentLeft -= listItem.getIndentationLeft(); 664 indentation.indentRight -= listItem.getIndentationRight(); 665 leadingCount--; 666 break; 667 } 668 case Element.RECTANGLE: { 669 Rectangle rectangle = (Rectangle) element; 670 graphics.rectangle(rectangle); 671 pageEmpty = false; 672 break; 673 } 674 case Element.PTABLE: { 675 PdfPTable ptable = (PdfPTable)element; 676 if (ptable.size() <= ptable.getHeaderRows()) 677 break; //nothing to do 678 679 // before every table, we add a new line and flush all lines 680 ensureNewLine(); 681 flushLines(); 682 683 addPTable(ptable); 684 pageEmpty = false; 685 newLine(); 686 break; 687 } 688 case Element.MULTI_COLUMN_TEXT: { 689 ensureNewLine(); 690 flushLines(); 691 MultiColumnText multiText = (MultiColumnText) element; 692 float height = multiText.write(writer.getDirectContent(), this, indentTop() - currentHeight); 693 currentHeight += height; 694 text.moveText(0, -1f* height); 695 pageEmpty = false; 696 break; 697 } 698 case Element.TABLE : { 699 if (element instanceof SimpleTable) { 700 PdfPTable ptable = ((SimpleTable)element).createPdfPTable(); 701 if (ptable.size() <= ptable.getHeaderRows()) 702 break; //nothing to do 703 704 // before every table, we add a new line and flush all lines 705 ensureNewLine(); 706 flushLines(); 707 addPTable(ptable); 708 pageEmpty = false; 709 break; 710 } else if (element instanceof Table) { 711 try { 712 PdfPTable ptable = ((Table)element).createPdfPTable(); 713 if (ptable.size() <= ptable.getHeaderRows()) 714 break; //nothing to do 715 // before every table, we add a new line and flush all lines 716 ensureNewLine(); 717 flushLines(); 718 addPTable(ptable); 719 pageEmpty = false; 720 break; 721 } 722 catch(BadElementException bee) { 723 // constructing the PdfTable 724 // Before the table, add a blank line using offset or default leading 725 float offset = ((Table)element).getOffset(); 726 if (Float.isNaN(offset)) 727 offset = leading; 728 carriageReturn(); 729 lines.add(new PdfLine(indentLeft(), indentRight(), alignment, offset)); 730 currentHeight += offset; 731 addPdfTable((Table)element); 732 } 733 } else { 734 return false; 735 } 736 break; 737 } 738 case Element.JPEG: 739 case Element.JPEG2000: 740 case Element.JBIG2: 741 case Element.IMGRAW: 742 case Element.IMGTEMPLATE: { 743 //carriageReturn(); suggestion by Marc Campforts 744 add((Image) element); 745 break; 746 } 747 case Element.YMARK: { 748 DrawInterface zh = (DrawInterface)element; 749 zh.draw(graphics, indentLeft(), indentBottom(), indentRight(), indentTop(), indentTop() - currentHeight - (leadingCount > 0 ? leading : 0)); 750 pageEmpty = false; 751 break; 752 } 753 case Element.MARKED: { 754 MarkedObject mo; 755 if (element instanceof MarkedSection) { 756 mo = ((MarkedSection)element).getTitle(); 757 if (mo != null) { 758 mo.process(this); 759 } 760 } 761 mo = (MarkedObject)element; 762 mo.process(this); 763 break; 764 } 765 default: 766 return false; 767 } 768 lastElementType = element.type(); 769 return true; 770 } 771 catch(Exception e) { 772 throw new DocumentException(e); 773 } 774 } 775 776 // [L1] DocListener interface 777 778 /** 779 * Opens the document. 780 * <P> 781 * You have to open the document before you can begin to add content 782 * to the body of the document. 783 */ open()784 public void open() { 785 if (!open) { 786 super.open(); 787 writer.open(); 788 rootOutline = new PdfOutline(writer); 789 currentOutline = rootOutline; 790 } 791 try { 792 initPage(); 793 } 794 catch(DocumentException de) { 795 throw new ExceptionConverter(de); 796 } 797 } 798 799 // [L2] DocListener interface 800 801 /** 802 * Closes the document. 803 * <B> 804 * Once all the content has been written in the body, you have to close 805 * the body. After that nothing can be written to the body anymore. 806 */ close()807 public void close() { 808 if (close) { 809 return; 810 } 811 try { 812 boolean wasImage = (imageWait != null); 813 newPage(); 814 if (imageWait != null || wasImage) newPage(); 815 if (annotationsImp.hasUnusedAnnotations()) 816 throw new RuntimeException(MessageLocalization.getComposedMessage("not.all.annotations.could.be.added.to.the.document.the.document.doesn.t.have.enough.pages")); 817 PdfPageEvent pageEvent = writer.getPageEvent(); 818 if (pageEvent != null) 819 pageEvent.onCloseDocument(writer, this); 820 super.close(); 821 822 writer.addLocalDestinations(localDestinations); 823 calculateOutlineCount(); 824 writeOutlines(); 825 } 826 catch(Exception e) { 827 throw ExceptionConverter.convertException(e); 828 } 829 830 writer.close(); 831 } 832 833 // [L3] DocListener interface 834 protected int textEmptySize; 835 836 // [C9] Metadata for the page 837 /** XMP Metadata for the page. */ 838 protected byte[] xmpMetadata = null; 839 /** 840 * Use this method to set the XMP Metadata. 841 * @param xmpMetadata The xmpMetadata to set. 842 */ setXmpMetadata(byte[] xmpMetadata)843 public void setXmpMetadata(byte[] xmpMetadata) { 844 this.xmpMetadata = xmpMetadata; 845 } 846 847 /** 848 * Makes a new page and sends it to the <CODE>PdfWriter</CODE>. 849 * 850 * @return a <CODE>boolean</CODE> 851 */ newPage()852 public boolean newPage() { 853 lastElementType = -1; 854 if (isPageEmpty()) { 855 setNewPageSizeAndMargins(); 856 return false; 857 } 858 if (!open || close) { 859 throw new RuntimeException(MessageLocalization.getComposedMessage("the.document.is.not.open")); 860 } 861 PdfPageEvent pageEvent = writer.getPageEvent(); 862 if (pageEvent != null) 863 pageEvent.onEndPage(writer, this); 864 865 //Added to inform any listeners that we are moving to a new page (added by David Freels) 866 super.newPage(); 867 868 // the following 2 lines were added by Pelikan Stephan 869 indentation.imageIndentLeft = 0; 870 indentation.imageIndentRight = 0; 871 872 try { 873 // we flush the arraylist with recently written lines 874 flushLines(); 875 876 // we prepare the elements of the page dictionary 877 878 // [U1] page size and rotation 879 int rotation = pageSize.getRotation(); 880 881 // [C10] 882 if (writer.isPdfX()) { 883 if (thisBoxSize.containsKey("art") && thisBoxSize.containsKey("trim")) 884 throw new PdfXConformanceException(MessageLocalization.getComposedMessage("only.one.of.artbox.or.trimbox.can.exist.in.the.page")); 885 if (!thisBoxSize.containsKey("art") && !thisBoxSize.containsKey("trim")) { 886 if (thisBoxSize.containsKey("crop")) 887 thisBoxSize.put("trim", thisBoxSize.get("crop")); 888 else 889 thisBoxSize.put("trim", new PdfRectangle(pageSize, pageSize.getRotation())); 890 } 891 } 892 893 // [M1] 894 pageResources.addDefaultColorDiff(writer.getDefaultColorspace()); 895 if (writer.isRgbTransparencyBlending()) { 896 PdfDictionary dcs = new PdfDictionary(); 897 dcs.put(PdfName.CS, PdfName.DEVICERGB); 898 pageResources.addDefaultColorDiff(dcs); 899 } 900 PdfDictionary resources = pageResources.getResources(); 901 902 // we create the page dictionary 903 904 PdfPage page = new PdfPage(new PdfRectangle(pageSize, rotation), thisBoxSize, resources, rotation); 905 page.put(PdfName.TABS, writer.getTabs()); 906 907 // we complete the page dictionary 908 909 // [C9] if there is XMP data to add: add it 910 if (xmpMetadata != null) { 911 PdfStream xmp = new PdfStream(xmpMetadata); 912 xmp.put(PdfName.TYPE, PdfName.METADATA); 913 xmp.put(PdfName.SUBTYPE, PdfName.XML); 914 PdfEncryption crypto = writer.getEncryption(); 915 if (crypto != null && !crypto.isMetadataEncrypted()) { 916 PdfArray ar = new PdfArray(); 917 ar.add(PdfName.CRYPT); 918 xmp.put(PdfName.FILTER, ar); 919 } 920 page.put(PdfName.METADATA, writer.addToBody(xmp).getIndirectReference()); 921 } 922 923 // [U3] page actions: transition, duration, additional actions 924 if (this.transition!=null) { 925 page.put(PdfName.TRANS, this.transition.getTransitionDictionary()); 926 transition = null; 927 } 928 if (this.duration>0) { 929 page.put(PdfName.DUR,new PdfNumber(this.duration)); 930 duration = 0; 931 } 932 if (pageAA != null) { 933 page.put(PdfName.AA, writer.addToBody(pageAA).getIndirectReference()); 934 pageAA = null; 935 } 936 937 // [U4] we add the thumbs 938 if (thumb != null) { 939 page.put(PdfName.THUMB, thumb); 940 thumb = null; 941 } 942 943 // [U8] we check if the userunit is defined 944 if (writer.getUserunit() > 0f) { 945 page.put(PdfName.USERUNIT, new PdfNumber(writer.getUserunit())); 946 } 947 948 // [C5] and [C8] we add the annotations 949 if (annotationsImp.hasUnusedAnnotations()) { 950 PdfArray array = annotationsImp.rotateAnnotations(writer, pageSize); 951 if (array.size() != 0) 952 page.put(PdfName.ANNOTS, array); 953 } 954 955 // [F12] we add tag info 956 if (writer.isTagged()) 957 page.put(PdfName.STRUCTPARENTS, new PdfNumber(writer.getCurrentPageNumber() - 1)); 958 959 if (text.size() > textEmptySize) 960 text.endText(); 961 else 962 text = null; 963 writer.add(page, new PdfContents(writer.getDirectContentUnder(), graphics, text, writer.getDirectContent(), pageSize)); 964 // we initialize the new page 965 initPage(); 966 } 967 catch(DocumentException de) { 968 // maybe this never happens, but it's better to check. 969 throw new ExceptionConverter(de); 970 } 971 catch (IOException ioe) { 972 throw new ExceptionConverter(ioe); 973 } 974 return true; 975 } 976 977 // [L4] DocListener interface 978 979 /** 980 * Sets the pagesize. 981 * 982 * @param pageSize the new pagesize 983 * @return <CODE>true</CODE> if the page size was set 984 */ setPageSize(Rectangle pageSize)985 public boolean setPageSize(Rectangle pageSize) { 986 if (writer != null && writer.isPaused()) { 987 return false; 988 } 989 nextPageSize = new Rectangle(pageSize); 990 return true; 991 } 992 993 // [L5] DocListener interface 994 995 /** margin in x direction starting from the left. Will be valid in the next page */ 996 protected float nextMarginLeft; 997 998 /** margin in x direction starting from the right. Will be valid in the next page */ 999 protected float nextMarginRight; 1000 1001 /** margin in y direction starting from the top. Will be valid in the next page */ 1002 protected float nextMarginTop; 1003 1004 /** margin in y direction starting from the bottom. Will be valid in the next page */ 1005 protected float nextMarginBottom; 1006 1007 /** 1008 * Sets the margins. 1009 * 1010 * @param marginLeft the margin on the left 1011 * @param marginRight the margin on the right 1012 * @param marginTop the margin on the top 1013 * @param marginBottom the margin on the bottom 1014 * @return a <CODE>boolean</CODE> 1015 */ setMargins(float marginLeft, float marginRight, float marginTop, float marginBottom)1016 public boolean setMargins(float marginLeft, float marginRight, float marginTop, float marginBottom) { 1017 if (writer != null && writer.isPaused()) { 1018 return false; 1019 } 1020 nextMarginLeft = marginLeft; 1021 nextMarginRight = marginRight; 1022 nextMarginTop = marginTop; 1023 nextMarginBottom = marginBottom; 1024 return true; 1025 } 1026 1027 // [L6] DocListener interface 1028 1029 /** 1030 * @see com.lowagie.text.DocListener#setMarginMirroring(boolean) 1031 */ setMarginMirroring(boolean MarginMirroring)1032 public boolean setMarginMirroring(boolean MarginMirroring) { 1033 if (writer != null && writer.isPaused()) { 1034 return false; 1035 } 1036 return super.setMarginMirroring(MarginMirroring); 1037 } 1038 1039 /** 1040 * @see com.lowagie.text.DocListener#setMarginMirroring(boolean) 1041 * @since 2.1.6 1042 */ setMarginMirroringTopBottom(boolean MarginMirroringTopBottom)1043 public boolean setMarginMirroringTopBottom(boolean MarginMirroringTopBottom) { 1044 if (writer != null && writer.isPaused()) { 1045 return false; 1046 } 1047 return super.setMarginMirroringTopBottom(MarginMirroringTopBottom); 1048 } 1049 1050 // [L7] DocListener interface 1051 1052 /** 1053 * Sets the page number. 1054 * 1055 * @param pageN the new page number 1056 */ setPageCount(int pageN)1057 public void setPageCount(int pageN) { 1058 if (writer != null && writer.isPaused()) { 1059 return; 1060 } 1061 super.setPageCount(pageN); 1062 } 1063 1064 // [L8] DocListener interface 1065 1066 /** 1067 * Sets the page number to 0. 1068 */ resetPageCount()1069 public void resetPageCount() { 1070 if (writer != null && writer.isPaused()) { 1071 return; 1072 } 1073 super.resetPageCount(); 1074 } 1075 1076 // [L9] DocListener interface 1077 1078 /** 1079 * Changes the header of this document. 1080 * 1081 * @param header the new header 1082 */ setHeader(HeaderFooter header)1083 public void setHeader(HeaderFooter header) { 1084 if (writer != null && writer.isPaused()) { 1085 return; 1086 } 1087 super.setHeader(header); 1088 } 1089 1090 // [L10] DocListener interface 1091 1092 /** 1093 * Resets the header of this document. 1094 */ resetHeader()1095 public void resetHeader() { 1096 if (writer != null && writer.isPaused()) { 1097 return; 1098 } 1099 super.resetHeader(); 1100 } 1101 1102 // [L11] DocListener interface 1103 1104 /** 1105 * Changes the footer of this document. 1106 * 1107 * @param footer the new footer 1108 */ setFooter(HeaderFooter footer)1109 public void setFooter(HeaderFooter footer) { 1110 if (writer != null && writer.isPaused()) { 1111 return; 1112 } 1113 super.setFooter(footer); 1114 } 1115 1116 // [L12] DocListener interface 1117 1118 /** 1119 * Resets the footer of this document. 1120 */ resetFooter()1121 public void resetFooter() { 1122 if (writer != null && writer.isPaused()) { 1123 return; 1124 } 1125 super.resetFooter(); 1126 } 1127 1128 // DOCLISTENER METHODS END 1129 1130 /** Signals that OnOpenDocument should be called. */ 1131 protected boolean firstPageEvent = true; 1132 1133 /** 1134 * Initializes a page. 1135 * <P> 1136 * If the footer/header is set, it is printed. 1137 * @throws DocumentException on error 1138 */ initPage()1139 protected void initPage() throws DocumentException { 1140 // the pagenumber is incremented 1141 pageN++; 1142 1143 // initialization of some page objects 1144 annotationsImp.resetAnnotations(); 1145 pageResources = new PageResources(); 1146 1147 writer.resetContent(); 1148 graphics = new PdfContentByte(writer); 1149 text = new PdfContentByte(writer); 1150 text.reset(); 1151 text.beginText(); 1152 textEmptySize = text.size(); 1153 1154 markPoint = 0; 1155 setNewPageSizeAndMargins(); 1156 imageEnd = -1; 1157 indentation.imageIndentRight = 0; 1158 indentation.imageIndentLeft = 0; 1159 indentation.indentBottom = 0; 1160 indentation.indentTop = 0; 1161 currentHeight = 0; 1162 1163 // backgroundcolors, etc... 1164 thisBoxSize = new HashMap(boxSize); 1165 if (pageSize.getBackgroundColor() != null 1166 || pageSize.hasBorders() 1167 || pageSize.getBorderColor() != null) { 1168 add(pageSize); 1169 } 1170 1171 float oldleading = leading; 1172 int oldAlignment = alignment; 1173 // if there is a footer, the footer is added 1174 doFooter(); 1175 // we move to the left/top position of the page 1176 text.moveText(left(), top()); 1177 doHeader(); 1178 pageEmpty = true; 1179 // if there is an image waiting to be drawn, draw it 1180 try { 1181 if (imageWait != null) { 1182 add(imageWait); 1183 imageWait = null; 1184 } 1185 } 1186 catch(Exception e) { 1187 throw new ExceptionConverter(e); 1188 } 1189 leading = oldleading; 1190 alignment = oldAlignment; 1191 carriageReturn(); 1192 1193 PdfPageEvent pageEvent = writer.getPageEvent(); 1194 if (pageEvent != null) { 1195 if (firstPageEvent) { 1196 pageEvent.onOpenDocument(writer, this); 1197 } 1198 pageEvent.onStartPage(writer, this); 1199 } 1200 firstPageEvent = false; 1201 } 1202 1203 /** The line that is currently being written. */ 1204 protected PdfLine line = null; 1205 /** The lines that are written until now. */ 1206 protected ArrayList lines = new ArrayList(); 1207 1208 /** 1209 * Adds the current line to the list of lines and also adds an empty line. 1210 * @throws DocumentException on error 1211 */ newLine()1212 protected void newLine() throws DocumentException { 1213 lastElementType = -1; 1214 carriageReturn(); 1215 if (lines != null && !lines.isEmpty()) { 1216 lines.add(line); 1217 currentHeight += line.height(); 1218 } 1219 line = new PdfLine(indentLeft(), indentRight(), alignment, leading); 1220 } 1221 1222 /** 1223 * If the current line is not empty or null, it is added to the arraylist 1224 * of lines and a new empty line is added. 1225 */ carriageReturn()1226 protected void carriageReturn() { 1227 // the arraylist with lines may not be null 1228 if (lines == null) { 1229 lines = new ArrayList(); 1230 } 1231 // If the current line is not null 1232 if (line != null) { 1233 // we check if the end of the page is reached (bugfix by Francois Gravel) 1234 if (currentHeight + line.height() + leading < indentTop() - indentBottom()) { 1235 // if so nonempty lines are added and the height is augmented 1236 if (line.size() > 0) { 1237 currentHeight += line.height(); 1238 lines.add(line); 1239 pageEmpty = false; 1240 } 1241 } 1242 // if the end of the line is reached, we start a new page 1243 else { 1244 newPage(); 1245 } 1246 } 1247 if (imageEnd > -1 && currentHeight > imageEnd) { 1248 imageEnd = -1; 1249 indentation.imageIndentRight = 0; 1250 indentation.imageIndentLeft = 0; 1251 } 1252 // a new current line is constructed 1253 line = new PdfLine(indentLeft(), indentRight(), alignment, leading); 1254 } 1255 1256 /** 1257 * Gets the current vertical page position. 1258 * @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects 1259 * for elements that do not terminate the lines they've started because those lines will get 1260 * terminated. 1261 * @return The current vertical page position. 1262 */ getVerticalPosition(boolean ensureNewLine)1263 public float getVerticalPosition(boolean ensureNewLine) { 1264 // ensuring that a new line has been started. 1265 if (ensureNewLine) { 1266 ensureNewLine(); 1267 } 1268 return top() - currentHeight - indentation.indentTop; 1269 } 1270 1271 /** Holds the type of the last element, that has been added to the document. */ 1272 protected int lastElementType = -1; 1273 1274 /** 1275 * Ensures that a new line has been started. 1276 */ ensureNewLine()1277 protected void ensureNewLine() { 1278 try { 1279 if ((lastElementType == Element.PHRASE) || 1280 (lastElementType == Element.CHUNK)) { 1281 newLine(); 1282 flushLines(); 1283 } 1284 } catch (DocumentException ex) { 1285 throw new ExceptionConverter(ex); 1286 } 1287 } 1288 1289 /** 1290 * Writes all the lines to the text-object. 1291 * 1292 * @return the displacement that was caused 1293 * @throws DocumentException on error 1294 */ flushLines()1295 protected float flushLines() throws DocumentException { 1296 // checks if the ArrayList with the lines is not null 1297 if (lines == null) { 1298 return 0; 1299 } 1300 // checks if a new Line has to be made. 1301 if (line != null && line.size() > 0) { 1302 lines.add(line); 1303 line = new PdfLine(indentLeft(), indentRight(), alignment, leading); 1304 } 1305 1306 // checks if the ArrayList with the lines is empty 1307 if (lines.isEmpty()) { 1308 return 0; 1309 } 1310 1311 // initialization of some parameters 1312 Object currentValues[] = new Object[2]; 1313 PdfFont currentFont = null; 1314 float displacement = 0; 1315 PdfLine l; 1316 Float lastBaseFactor = new Float(0); 1317 currentValues[1] = lastBaseFactor; 1318 // looping over all the lines 1319 for (Iterator i = lines.iterator(); i.hasNext(); ) { 1320 1321 // this is a line in the loop 1322 l = (PdfLine) i.next(); 1323 1324 float moveTextX = l.indentLeft() - indentLeft() + indentation.indentLeft + indentation.listIndentLeft + indentation.sectionIndentLeft; 1325 text.moveText(moveTextX, -l.height()); 1326 // is the line preceded by a symbol? 1327 if (l.listSymbol() != null) { 1328 ColumnText.showTextAligned(graphics, Element.ALIGN_LEFT, new Phrase(l.listSymbol()), text.getXTLM() - l.listIndent(), text.getYTLM(), 0); 1329 } 1330 1331 currentValues[0] = currentFont; 1332 1333 writeLineToContent(l, text, graphics, currentValues, writer.getSpaceCharRatio()); 1334 1335 currentFont = (PdfFont)currentValues[0]; 1336 displacement += l.height(); 1337 text.moveText(-moveTextX, 0); 1338 1339 } 1340 lines = new ArrayList(); 1341 return displacement; 1342 } 1343 1344 /** The characters to be applied the hanging punctuation. */ 1345 static final String hangingPunctuation = ".,;:'"; 1346 1347 /** 1348 * Writes a text line to the document. It takes care of all the attributes. 1349 * <P> 1350 * Before entering the line position must have been established and the 1351 * <CODE>text</CODE> argument must be in text object scope (<CODE>beginText()</CODE>). 1352 * @param line the line to be written 1353 * @param text the <CODE>PdfContentByte</CODE> where the text will be written to 1354 * @param graphics the <CODE>PdfContentByte</CODE> where the graphics will be written to 1355 * @param currentValues the current font and extra spacing values 1356 * @param ratio 1357 * @throws DocumentException on error 1358 */ writeLineToContent(PdfLine line, PdfContentByte text, PdfContentByte graphics, Object currentValues[], float ratio)1359 void writeLineToContent(PdfLine line, PdfContentByte text, PdfContentByte graphics, Object currentValues[], float ratio) throws DocumentException { 1360 PdfFont currentFont = (PdfFont)(currentValues[0]); 1361 float lastBaseFactor = ((Float)(currentValues[1])).floatValue(); 1362 PdfChunk chunk; 1363 int numberOfSpaces; 1364 int lineLen; 1365 boolean isJustified; 1366 float hangingCorrection = 0; 1367 float hScale = 1; 1368 float lastHScale = Float.NaN; 1369 float baseWordSpacing = 0; 1370 float baseCharacterSpacing = 0; 1371 float glueWidth = 0; 1372 1373 numberOfSpaces = line.numberOfSpaces(); 1374 lineLen = line.GetLineLengthUtf32(); 1375 // does the line need to be justified? 1376 isJustified = line.hasToBeJustified() && (numberOfSpaces != 0 || lineLen > 1); 1377 int separatorCount = line.getSeparatorCount(); 1378 if (separatorCount > 0) { 1379 glueWidth = line.widthLeft() / separatorCount; 1380 } 1381 else if (isJustified) { 1382 if (line.isNewlineSplit() && line.widthLeft() >= (lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1))) { 1383 if (line.isRTL()) { 1384 text.moveText(line.widthLeft() - lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1), 0); 1385 } 1386 baseWordSpacing = ratio * lastBaseFactor; 1387 baseCharacterSpacing = lastBaseFactor; 1388 } 1389 else { 1390 float width = line.widthLeft(); 1391 PdfChunk last = line.getChunk(line.size() - 1); 1392 if (last != null) { 1393 String s = last.toString(); 1394 char c; 1395 if (s.length() > 0 && hangingPunctuation.indexOf((c = s.charAt(s.length() - 1))) >= 0) { 1396 float oldWidth = width; 1397 width += last.font().width(c) * 0.4f; 1398 hangingCorrection = width - oldWidth; 1399 } 1400 } 1401 float baseFactor = width / (ratio * numberOfSpaces + lineLen - 1); 1402 baseWordSpacing = ratio * baseFactor; 1403 baseCharacterSpacing = baseFactor; 1404 lastBaseFactor = baseFactor; 1405 } 1406 } 1407 1408 int lastChunkStroke = line.getLastStrokeChunk(); 1409 int chunkStrokeIdx = 0; 1410 float xMarker = text.getXTLM(); 1411 float baseXMarker = xMarker; 1412 float yMarker = text.getYTLM(); 1413 boolean adjustMatrix = false; 1414 float tabPosition = 0; 1415 1416 // looping over all the chunks in 1 line 1417 for (Iterator j = line.iterator(); j.hasNext(); ) { 1418 chunk = (PdfChunk) j.next(); 1419 Color color = chunk.color(); 1420 hScale = 1; 1421 1422 if (chunkStrokeIdx <= lastChunkStroke) { 1423 float width; 1424 if (isJustified) { 1425 width = chunk.getWidthCorrected(baseCharacterSpacing, baseWordSpacing); 1426 } 1427 else { 1428 width = chunk.width(); 1429 } 1430 if (chunk.isStroked()) { 1431 PdfChunk nextChunk = line.getChunk(chunkStrokeIdx + 1); 1432 if (chunk.isSeparator()) { 1433 width = glueWidth; 1434 Object[] sep = (Object[])chunk.getAttribute(Chunk.SEPARATOR); 1435 DrawInterface di = (DrawInterface)sep[0]; 1436 Boolean vertical = (Boolean)sep[1]; 1437 float fontSize = chunk.font().size(); 1438 float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize); 1439 float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize); 1440 if (vertical.booleanValue()) { 1441 di.draw(graphics, baseXMarker, yMarker + descender, baseXMarker + line.getOriginalWidth(), ascender - descender, yMarker); 1442 } 1443 else { 1444 di.draw(graphics, xMarker, yMarker + descender, xMarker + width, ascender - descender, yMarker); 1445 } 1446 } 1447 if (chunk.isTab()) { 1448 Object[] tab = (Object[])chunk.getAttribute(Chunk.TAB); 1449 DrawInterface di = (DrawInterface)tab[0]; 1450 tabPosition = ((Float)tab[1]).floatValue() + ((Float)tab[3]).floatValue(); 1451 float fontSize = chunk.font().size(); 1452 float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize); 1453 float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize); 1454 if (tabPosition > xMarker) { 1455 di.draw(graphics, xMarker, yMarker + descender, tabPosition, ascender - descender, yMarker); 1456 } 1457 float tmp = xMarker; 1458 xMarker = tabPosition; 1459 tabPosition = tmp; 1460 } 1461 if (chunk.isAttribute(Chunk.BACKGROUND)) { 1462 float subtract = lastBaseFactor; 1463 if (nextChunk != null && nextChunk.isAttribute(Chunk.BACKGROUND)) 1464 subtract = 0; 1465 if (nextChunk == null) 1466 subtract += hangingCorrection; 1467 float fontSize = chunk.font().size(); 1468 float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize); 1469 float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize); 1470 Object bgr[] = (Object[])chunk.getAttribute(Chunk.BACKGROUND); 1471 graphics.setColorFill((Color)bgr[0]); 1472 float extra[] = (float[])bgr[1]; 1473 graphics.rectangle(xMarker - extra[0], 1474 yMarker + descender - extra[1] + chunk.getTextRise(), 1475 width - subtract + extra[0] + extra[2], 1476 ascender - descender + extra[1] + extra[3]); 1477 graphics.fill(); 1478 graphics.setGrayFill(0); 1479 } 1480 if (chunk.isAttribute(Chunk.UNDERLINE)) { 1481 float subtract = lastBaseFactor; 1482 if (nextChunk != null && nextChunk.isAttribute(Chunk.UNDERLINE)) 1483 subtract = 0; 1484 if (nextChunk == null) 1485 subtract += hangingCorrection; 1486 Object unders[][] = (Object[][])chunk.getAttribute(Chunk.UNDERLINE); 1487 Color scolor = null; 1488 for (int k = 0; k < unders.length; ++k) { 1489 Object obj[] = unders[k]; 1490 scolor = (Color)obj[0]; 1491 float ps[] = (float[])obj[1]; 1492 if (scolor == null) 1493 scolor = color; 1494 if (scolor != null) 1495 graphics.setColorStroke(scolor); 1496 float fsize = chunk.font().size(); 1497 graphics.setLineWidth(ps[0] + fsize * ps[1]); 1498 float shift = ps[2] + fsize * ps[3]; 1499 int cap2 = (int)ps[4]; 1500 if (cap2 != 0) 1501 graphics.setLineCap(cap2); 1502 graphics.moveTo(xMarker, yMarker + shift); 1503 graphics.lineTo(xMarker + width - subtract, yMarker + shift); 1504 graphics.stroke(); 1505 if (scolor != null) 1506 graphics.resetGrayStroke(); 1507 if (cap2 != 0) 1508 graphics.setLineCap(0); 1509 } 1510 graphics.setLineWidth(1); 1511 } 1512 if (chunk.isAttribute(Chunk.ACTION)) { 1513 float subtract = lastBaseFactor; 1514 if (nextChunk != null && nextChunk.isAttribute(Chunk.ACTION)) 1515 subtract = 0; 1516 if (nextChunk == null) 1517 subtract += hangingCorrection; 1518 text.addAnnotation(new PdfAnnotation(writer, xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size(), (PdfAction)chunk.getAttribute(Chunk.ACTION))); 1519 } 1520 if (chunk.isAttribute(Chunk.REMOTEGOTO)) { 1521 float subtract = lastBaseFactor; 1522 if (nextChunk != null && nextChunk.isAttribute(Chunk.REMOTEGOTO)) 1523 subtract = 0; 1524 if (nextChunk == null) 1525 subtract += hangingCorrection; 1526 Object obj[] = (Object[])chunk.getAttribute(Chunk.REMOTEGOTO); 1527 String filename = (String)obj[0]; 1528 if (obj[1] instanceof String) 1529 remoteGoto(filename, (String)obj[1], xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size()); 1530 else 1531 remoteGoto(filename, ((Integer)obj[1]).intValue(), xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size()); 1532 } 1533 if (chunk.isAttribute(Chunk.LOCALGOTO)) { 1534 float subtract = lastBaseFactor; 1535 if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALGOTO)) 1536 subtract = 0; 1537 if (nextChunk == null) 1538 subtract += hangingCorrection; 1539 localGoto((String)chunk.getAttribute(Chunk.LOCALGOTO), xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size()); 1540 } 1541 if (chunk.isAttribute(Chunk.LOCALDESTINATION)) { 1542 float subtract = lastBaseFactor; 1543 if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALDESTINATION)) 1544 subtract = 0; 1545 if (nextChunk == null) 1546 subtract += hangingCorrection; 1547 localDestination((String)chunk.getAttribute(Chunk.LOCALDESTINATION), new PdfDestination(PdfDestination.XYZ, xMarker, yMarker + chunk.font().size(), 0)); 1548 } 1549 if (chunk.isAttribute(Chunk.GENERICTAG)) { 1550 float subtract = lastBaseFactor; 1551 if (nextChunk != null && nextChunk.isAttribute(Chunk.GENERICTAG)) 1552 subtract = 0; 1553 if (nextChunk == null) 1554 subtract += hangingCorrection; 1555 Rectangle rect = new Rectangle(xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size()); 1556 PdfPageEvent pev = writer.getPageEvent(); 1557 if (pev != null) 1558 pev.onGenericTag(writer, this, rect, (String)chunk.getAttribute(Chunk.GENERICTAG)); 1559 } 1560 if (chunk.isAttribute(Chunk.PDFANNOTATION)) { 1561 float subtract = lastBaseFactor; 1562 if (nextChunk != null && nextChunk.isAttribute(Chunk.PDFANNOTATION)) 1563 subtract = 0; 1564 if (nextChunk == null) 1565 subtract += hangingCorrection; 1566 float fontSize = chunk.font().size(); 1567 float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize); 1568 float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize); 1569 PdfAnnotation annot = PdfFormField.shallowDuplicate((PdfAnnotation)chunk.getAttribute(Chunk.PDFANNOTATION)); 1570 annot.put(PdfName.RECT, new PdfRectangle(xMarker, yMarker + descender, xMarker + width - subtract, yMarker + ascender)); 1571 text.addAnnotation(annot); 1572 } 1573 float params[] = (float[])chunk.getAttribute(Chunk.SKEW); 1574 Float hs = (Float)chunk.getAttribute(Chunk.HSCALE); 1575 if (params != null || hs != null) { 1576 float b = 0, c = 0; 1577 if (params != null) { 1578 b = params[0]; 1579 c = params[1]; 1580 } 1581 if (hs != null) 1582 hScale = hs.floatValue(); 1583 text.setTextMatrix(hScale, b, c, 1, xMarker, yMarker); 1584 } 1585 if (chunk.isAttribute(Chunk.CHAR_SPACING)) { 1586 Float cs = (Float) chunk.getAttribute(Chunk.CHAR_SPACING); 1587 text.setCharacterSpacing(cs.floatValue()); 1588 } 1589 if (chunk.isImage()) { 1590 Image image = chunk.getImage(); 1591 float matrix[] = image.matrix(); 1592 matrix[Image.CX] = xMarker + chunk.getImageOffsetX() - matrix[Image.CX]; 1593 matrix[Image.CY] = yMarker + chunk.getImageOffsetY() - matrix[Image.CY]; 1594 graphics.addImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); 1595 text.moveText(xMarker + lastBaseFactor + image.getScaledWidth() - text.getXTLM(), 0); 1596 } 1597 } 1598 xMarker += width; 1599 ++chunkStrokeIdx; 1600 } 1601 1602 if (chunk.font().compareTo(currentFont) != 0) { 1603 currentFont = chunk.font(); 1604 text.setFontAndSize(currentFont.getFont(), currentFont.size()); 1605 } 1606 float rise = 0; 1607 Object textRender[] = (Object[])chunk.getAttribute(Chunk.TEXTRENDERMODE); 1608 int tr = 0; 1609 float strokeWidth = 1; 1610 Color strokeColor = null; 1611 Float fr = (Float)chunk.getAttribute(Chunk.SUBSUPSCRIPT); 1612 if (textRender != null) { 1613 tr = ((Integer)textRender[0]).intValue() & 3; 1614 if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL) 1615 text.setTextRenderingMode(tr); 1616 if (tr == PdfContentByte.TEXT_RENDER_MODE_STROKE || tr == PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE) { 1617 strokeWidth = ((Float)textRender[1]).floatValue(); 1618 if (strokeWidth != 1) 1619 text.setLineWidth(strokeWidth); 1620 strokeColor = (Color)textRender[2]; 1621 if (strokeColor == null) 1622 strokeColor = color; 1623 if (strokeColor != null) 1624 text.setColorStroke(strokeColor); 1625 } 1626 } 1627 if (fr != null) 1628 rise = fr.floatValue(); 1629 if (color != null) 1630 text.setColorFill(color); 1631 if (rise != 0) 1632 text.setTextRise(rise); 1633 if (chunk.isImage()) { 1634 adjustMatrix = true; 1635 } 1636 else if (chunk.isHorizontalSeparator()) { 1637 PdfTextArray array = new PdfTextArray(); 1638 array.add(-glueWidth * 1000f / chunk.font.size() / hScale); 1639 text.showText(array); 1640 } 1641 else if (chunk.isTab()) { 1642 PdfTextArray array = new PdfTextArray(); 1643 array.add((tabPosition - xMarker) * 1000f / chunk.font.size() / hScale); 1644 text.showText(array); 1645 } 1646 // If it is a CJK chunk or Unicode TTF we will have to simulate the 1647 // space adjustment. 1648 else if (isJustified && numberOfSpaces > 0 && chunk.isSpecialEncoding()) { 1649 if (hScale != lastHScale) { 1650 lastHScale = hScale; 1651 text.setWordSpacing(baseWordSpacing / hScale); 1652 text.setCharacterSpacing(baseCharacterSpacing / hScale + text.getCharacterSpacing()); 1653 } 1654 String s = chunk.toString(); 1655 int idx = s.indexOf(' '); 1656 if (idx < 0) 1657 text.showText(s); 1658 else { 1659 float spaceCorrection = - baseWordSpacing * 1000f / chunk.font.size() / hScale; 1660 PdfTextArray textArray = new PdfTextArray(s.substring(0, idx)); 1661 int lastIdx = idx; 1662 while ((idx = s.indexOf(' ', lastIdx + 1)) >= 0) { 1663 textArray.add(spaceCorrection); 1664 textArray.add(s.substring(lastIdx, idx)); 1665 lastIdx = idx; 1666 } 1667 textArray.add(spaceCorrection); 1668 textArray.add(s.substring(lastIdx)); 1669 text.showText(textArray); 1670 } 1671 } 1672 else { 1673 if (isJustified && hScale != lastHScale) { 1674 lastHScale = hScale; 1675 text.setWordSpacing(baseWordSpacing / hScale); 1676 text.setCharacterSpacing(baseCharacterSpacing / hScale + text.getCharacterSpacing()); 1677 } 1678 text.showText(chunk.toString()); 1679 } 1680 1681 if (rise != 0) 1682 text.setTextRise(0); 1683 if (color != null) 1684 text.resetRGBColorFill(); 1685 if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL) 1686 text.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL); 1687 if (strokeColor != null) 1688 text.resetRGBColorStroke(); 1689 if (strokeWidth != 1) 1690 text.setLineWidth(1); 1691 if (chunk.isAttribute(Chunk.SKEW) || chunk.isAttribute(Chunk.HSCALE)) { 1692 adjustMatrix = true; 1693 text.setTextMatrix(xMarker, yMarker); 1694 } 1695 if (chunk.isAttribute(Chunk.CHAR_SPACING)) { 1696 text.setCharacterSpacing(baseCharacterSpacing); 1697 } 1698 } 1699 if (isJustified) { 1700 text.setWordSpacing(0); 1701 text.setCharacterSpacing(0); 1702 if (line.isNewlineSplit()) 1703 lastBaseFactor = 0; 1704 } 1705 if (adjustMatrix) 1706 text.moveText(baseXMarker - text.getXTLM(), 0); 1707 currentValues[0] = currentFont; 1708 currentValues[1] = new Float(lastBaseFactor); 1709 } 1710 1711 protected Indentation indentation = new Indentation(); 1712 1713 /** 1714 * @since 2.0.8 (PdfDocument was package-private before) 1715 */ 1716 public static class Indentation { 1717 1718 /** This represents the current indentation of the PDF Elements on the left side. */ 1719 float indentLeft = 0; 1720 1721 /** Indentation to the left caused by a section. */ 1722 float sectionIndentLeft = 0; 1723 1724 /** This represents the current indentation of the PDF Elements on the left side. */ 1725 float listIndentLeft = 0; 1726 1727 /** This is the indentation caused by an image on the left. */ 1728 float imageIndentLeft = 0; 1729 1730 /** This represents the current indentation of the PDF Elements on the right side. */ 1731 float indentRight = 0; 1732 1733 /** Indentation to the right caused by a section. */ 1734 float sectionIndentRight = 0; 1735 1736 /** This is the indentation caused by an image on the right. */ 1737 float imageIndentRight = 0; 1738 1739 /** This represents the current indentation of the PDF Elements on the top side. */ 1740 float indentTop = 0; 1741 1742 /** This represents the current indentation of the PDF Elements on the bottom side. */ 1743 float indentBottom = 0; 1744 } 1745 1746 /** 1747 * Gets the indentation on the left side. 1748 * 1749 * @return a margin 1750 */ 1751 indentLeft()1752 protected float indentLeft() { 1753 return left(indentation.indentLeft + indentation.listIndentLeft + indentation.imageIndentLeft + indentation.sectionIndentLeft); 1754 } 1755 1756 /** 1757 * Gets the indentation on the right side. 1758 * 1759 * @return a margin 1760 */ 1761 indentRight()1762 protected float indentRight() { 1763 return right(indentation.indentRight + indentation.sectionIndentRight + indentation.imageIndentRight); 1764 } 1765 1766 /** 1767 * Gets the indentation on the top side. 1768 * 1769 * @return a margin 1770 */ 1771 indentTop()1772 protected float indentTop() { 1773 return top(indentation.indentTop); 1774 } 1775 1776 /** 1777 * Gets the indentation on the bottom side. 1778 * 1779 * @return a margin 1780 */ 1781 indentBottom()1782 float indentBottom() { 1783 return bottom(indentation.indentBottom); 1784 } 1785 1786 /** 1787 * Adds extra space. 1788 * This method should probably be rewritten. 1789 */ addSpacing(float extraspace, float oldleading, Font f)1790 protected void addSpacing(float extraspace, float oldleading, Font f) { 1791 if (extraspace == 0) return; 1792 if (pageEmpty) return; 1793 if (currentHeight + line.height() + leading > indentTop() - indentBottom()) return; 1794 leading = extraspace; 1795 carriageReturn(); 1796 if (f.isUnderlined() || f.isStrikethru()) { 1797 f = new Font(f); 1798 int style = f.getStyle(); 1799 style &= ~Font.UNDERLINE; 1800 style &= ~Font.STRIKETHRU; 1801 f.setStyle(style); 1802 } 1803 Chunk space = new Chunk(" ", f); 1804 space.process(this); 1805 carriageReturn(); 1806 leading = oldleading; 1807 } 1808 1809 // Info Dictionary and Catalog 1810 1811 /** some meta information about the Document. */ 1812 protected PdfInfo info = new PdfInfo(); 1813 1814 /** 1815 * Gets the <CODE>PdfInfo</CODE>-object. 1816 * 1817 * @return <CODE>PdfInfo</COPE> 1818 */ 1819 getInfo()1820 PdfInfo getInfo() { 1821 return info; 1822 } 1823 1824 /** 1825 * Gets the <CODE>PdfCatalog</CODE>-object. 1826 * 1827 * @param pages an indirect reference to this document pages 1828 * @return <CODE>PdfCatalog</CODE> 1829 */ 1830 getCatalog(PdfIndirectReference pages)1831 PdfCatalog getCatalog(PdfIndirectReference pages) { 1832 PdfCatalog catalog = new PdfCatalog(pages, writer); 1833 1834 // [C1] outlines 1835 if (rootOutline.getKids().size() > 0) { 1836 catalog.put(PdfName.PAGEMODE, PdfName.USEOUTLINES); 1837 catalog.put(PdfName.OUTLINES, rootOutline.indirectReference()); 1838 } 1839 1840 // [C2] version 1841 writer.getPdfVersion().addToCatalog(catalog); 1842 1843 // [C3] preferences 1844 viewerPreferences.addToCatalog(catalog); 1845 1846 // [C4] pagelabels 1847 if (pageLabels != null) { 1848 catalog.put(PdfName.PAGELABELS, pageLabels.getDictionary(writer)); 1849 } 1850 1851 // [C5] named objects 1852 catalog.addNames(localDestinations, getDocumentLevelJS(), documentFileAttachment, writer); 1853 1854 // [C6] actions 1855 if (openActionName != null) { 1856 PdfAction action = getLocalGotoAction(openActionName); 1857 catalog.setOpenAction(action); 1858 } 1859 else if (openActionAction != null) 1860 catalog.setOpenAction(openActionAction); 1861 if (additionalActions != null) { 1862 catalog.setAdditionalActions(additionalActions); 1863 } 1864 1865 // [C7] portable collections 1866 if (collection != null) { 1867 catalog.put(PdfName.COLLECTION, collection); 1868 } 1869 1870 // [C8] AcroForm 1871 if (annotationsImp.hasValidAcroForm()) { 1872 try { 1873 catalog.put(PdfName.ACROFORM, writer.addToBody(annotationsImp.getAcroForm()).getIndirectReference()); 1874 } 1875 catch (IOException e) { 1876 throw new ExceptionConverter(e); 1877 } 1878 } 1879 1880 return catalog; 1881 } 1882 1883 // [C1] outlines 1884 1885 /** This is the root outline of the document. */ 1886 protected PdfOutline rootOutline; 1887 1888 /** This is the current <CODE>PdfOutline</CODE> in the hierarchy of outlines. */ 1889 protected PdfOutline currentOutline; 1890 1891 /** 1892 * Adds a named outline to the document . 1893 * @param outline the outline to be added 1894 * @param name the name of this local destination 1895 */ addOutline(PdfOutline outline, String name)1896 void addOutline(PdfOutline outline, String name) { 1897 localDestination(name, outline.getPdfDestination()); 1898 } 1899 1900 /** 1901 * Gets the root outline. All the outlines must be created with a parent. 1902 * The first level is created with this outline. 1903 * @return the root outline 1904 */ getRootOutline()1905 public PdfOutline getRootOutline() { 1906 return rootOutline; 1907 } 1908 1909 1910 /** 1911 * Updates the count in the outlines. 1912 */ calculateOutlineCount()1913 void calculateOutlineCount() { 1914 if (rootOutline.getKids().size() == 0) 1915 return; 1916 traverseOutlineCount(rootOutline); 1917 } 1918 1919 /** 1920 * Recursive method to update the count in the outlines. 1921 */ traverseOutlineCount(PdfOutline outline)1922 void traverseOutlineCount(PdfOutline outline) { 1923 ArrayList kids = outline.getKids(); 1924 PdfOutline parent = outline.parent(); 1925 if (kids.isEmpty()) { 1926 if (parent != null) { 1927 parent.setCount(parent.getCount() + 1); 1928 } 1929 } 1930 else { 1931 for (int k = 0; k < kids.size(); ++k) { 1932 traverseOutlineCount((PdfOutline)kids.get(k)); 1933 } 1934 if (parent != null) { 1935 if (outline.isOpen()) { 1936 parent.setCount(outline.getCount() + parent.getCount() + 1); 1937 } 1938 else { 1939 parent.setCount(parent.getCount() + 1); 1940 outline.setCount(-outline.getCount()); 1941 } 1942 } 1943 } 1944 } 1945 1946 /** 1947 * Writes the outline tree to the body of the PDF document. 1948 */ writeOutlines()1949 void writeOutlines() throws IOException { 1950 if (rootOutline.getKids().size() == 0) 1951 return; 1952 outlineTree(rootOutline); 1953 writer.addToBody(rootOutline, rootOutline.indirectReference()); 1954 } 1955 1956 /** 1957 * Recursive method used to write outlines. 1958 */ outlineTree(PdfOutline outline)1959 void outlineTree(PdfOutline outline) throws IOException { 1960 outline.setIndirectReference(writer.getPdfIndirectReference()); 1961 if (outline.parent() != null) 1962 outline.put(PdfName.PARENT, outline.parent().indirectReference()); 1963 ArrayList kids = outline.getKids(); 1964 int size = kids.size(); 1965 for (int k = 0; k < size; ++k) 1966 outlineTree((PdfOutline)kids.get(k)); 1967 for (int k = 0; k < size; ++k) { 1968 if (k > 0) 1969 ((PdfOutline)kids.get(k)).put(PdfName.PREV, ((PdfOutline)kids.get(k - 1)).indirectReference()); 1970 if (k < size - 1) 1971 ((PdfOutline)kids.get(k)).put(PdfName.NEXT, ((PdfOutline)kids.get(k + 1)).indirectReference()); 1972 } 1973 if (size > 0) { 1974 outline.put(PdfName.FIRST, ((PdfOutline)kids.get(0)).indirectReference()); 1975 outline.put(PdfName.LAST, ((PdfOutline)kids.get(size - 1)).indirectReference()); 1976 } 1977 for (int k = 0; k < size; ++k) { 1978 PdfOutline kid = (PdfOutline)kids.get(k); 1979 writer.addToBody(kid, kid.indirectReference()); 1980 } 1981 } 1982 1983 // [C3] PdfViewerPreferences interface 1984 1985 /** Contains the Viewer preferences of this PDF document. */ 1986 protected PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp(); 1987 /** @see com.lowagie.text.pdf.interfaces.PdfViewerPreferences#setViewerPreferences(int) */ setViewerPreferences(int preferences)1988 void setViewerPreferences(int preferences) { 1989 this.viewerPreferences.setViewerPreferences(preferences); 1990 } 1991 1992 /** @see com.lowagie.text.pdf.interfaces.PdfViewerPreferences#addViewerPreference(com.lowagie.text.pdf.PdfName, com.lowagie.text.pdf.PdfObject) */ addViewerPreference(PdfName key, PdfObject value)1993 void addViewerPreference(PdfName key, PdfObject value) { 1994 this.viewerPreferences.addViewerPreference(key, value); 1995 } 1996 1997 // [C4] Page labels 1998 1999 protected PdfPageLabels pageLabels; 2000 /** 2001 * Sets the page labels 2002 * @param pageLabels the page labels 2003 */ setPageLabels(PdfPageLabels pageLabels)2004 void setPageLabels(PdfPageLabels pageLabels) { 2005 this.pageLabels = pageLabels; 2006 } 2007 2008 // [C5] named objects: local destinations, javascript, embedded files 2009 2010 /** 2011 * Implements a link to other part of the document. The jump will 2012 * be made to a local destination with the same name, that must exist. 2013 * @param name the name for this link 2014 * @param llx the lower left x corner of the activation area 2015 * @param lly the lower left y corner of the activation area 2016 * @param urx the upper right x corner of the activation area 2017 * @param ury the upper right y corner of the activation area 2018 */ localGoto(String name, float llx, float lly, float urx, float ury)2019 void localGoto(String name, float llx, float lly, float urx, float ury) { 2020 PdfAction action = getLocalGotoAction(name); 2021 annotationsImp.addPlainAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action)); 2022 } 2023 2024 /** 2025 * Implements a link to another document. 2026 * @param filename the filename for the remote document 2027 * @param name the name to jump to 2028 * @param llx the lower left x corner of the activation area 2029 * @param lly the lower left y corner of the activation area 2030 * @param urx the upper right x corner of the activation area 2031 * @param ury the upper right y corner of the activation area 2032 */ remoteGoto(String filename, String name, float llx, float lly, float urx, float ury)2033 void remoteGoto(String filename, String name, float llx, float lly, float urx, float ury) { 2034 annotationsImp.addPlainAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, name))); 2035 } 2036 2037 /** 2038 * Implements a link to another document. 2039 * @param filename the filename for the remote document 2040 * @param page the page to jump to 2041 * @param llx the lower left x corner of the activation area 2042 * @param lly the lower left y corner of the activation area 2043 * @param urx the upper right x corner of the activation area 2044 * @param ury the upper right y corner of the activation area 2045 */ remoteGoto(String filename, int page, float llx, float lly, float urx, float ury)2046 void remoteGoto(String filename, int page, float llx, float lly, float urx, float ury) { 2047 addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, page))); 2048 } 2049 2050 /** Implements an action in an area. 2051 * @param action the <CODE>PdfAction</CODE> 2052 * @param llx the lower left x corner of the activation area 2053 * @param lly the lower left y corner of the activation area 2054 * @param urx the upper right x corner of the activation area 2055 * @param ury the upper right y corner of the activation area 2056 */ setAction(PdfAction action, float llx, float lly, float urx, float ury)2057 void setAction(PdfAction action, float llx, float lly, float urx, float ury) { 2058 addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action)); 2059 } 2060 2061 /** 2062 * Stores the destinations keyed by name. Value is 2063 * <CODE>Object[]{PdfAction,PdfIndirectReference,PdfDestintion}</CODE>. 2064 */ 2065 protected TreeMap localDestinations = new TreeMap(); 2066 getLocalGotoAction(String name)2067 PdfAction getLocalGotoAction(String name) { 2068 PdfAction action; 2069 Object obj[] = (Object[])localDestinations.get(name); 2070 if (obj == null) 2071 obj = new Object[3]; 2072 if (obj[0] == null) { 2073 if (obj[1] == null) { 2074 obj[1] = writer.getPdfIndirectReference(); 2075 } 2076 action = new PdfAction((PdfIndirectReference)obj[1]); 2077 obj[0] = action; 2078 localDestinations.put(name, obj); 2079 } 2080 else { 2081 action = (PdfAction)obj[0]; 2082 } 2083 return action; 2084 } 2085 2086 /** 2087 * The local destination to where a local goto with the same 2088 * name will jump to. 2089 * @param name the name of this local destination 2090 * @param destination the <CODE>PdfDestination</CODE> with the jump coordinates 2091 * @return <CODE>true</CODE> if the local destination was added, 2092 * <CODE>false</CODE> if a local destination with the same name 2093 * already existed 2094 */ localDestination(String name, PdfDestination destination)2095 boolean localDestination(String name, PdfDestination destination) { 2096 Object obj[] = (Object[])localDestinations.get(name); 2097 if (obj == null) 2098 obj = new Object[3]; 2099 if (obj[2] != null) 2100 return false; 2101 obj[2] = destination; 2102 localDestinations.put(name, obj); 2103 if (!destination.hasPage()) 2104 destination.addPage(writer.getCurrentPage()); 2105 return true; 2106 } 2107 2108 /** 2109 * Stores a list of document level JavaScript actions. 2110 */ 2111 int jsCounter; 2112 protected HashMap documentLevelJS = new HashMap(); 2113 protected static final DecimalFormat SIXTEEN_DIGITS = new DecimalFormat("0000000000000000"); addJavaScript(PdfAction js)2114 void addJavaScript(PdfAction js) { 2115 if (js.get(PdfName.JS) == null) 2116 throw new RuntimeException(MessageLocalization.getComposedMessage("only.javascript.actions.are.allowed")); 2117 try { 2118 documentLevelJS.put(SIXTEEN_DIGITS.format(jsCounter++), writer.addToBody(js).getIndirectReference()); 2119 } 2120 catch (IOException e) { 2121 throw new ExceptionConverter(e); 2122 } 2123 } addJavaScript(String name, PdfAction js)2124 void addJavaScript(String name, PdfAction js) { 2125 if (js.get(PdfName.JS) == null) 2126 throw new RuntimeException(MessageLocalization.getComposedMessage("only.javascript.actions.are.allowed")); 2127 try { 2128 documentLevelJS.put(name, writer.addToBody(js).getIndirectReference()); 2129 } 2130 catch (IOException e) { 2131 throw new ExceptionConverter(e); 2132 } 2133 } 2134 getDocumentLevelJS()2135 HashMap getDocumentLevelJS() { 2136 return documentLevelJS; 2137 } 2138 2139 protected HashMap documentFileAttachment = new HashMap(); 2140 addFileAttachment(String description, PdfFileSpecification fs)2141 void addFileAttachment(String description, PdfFileSpecification fs) throws IOException { 2142 if (description == null) { 2143 PdfString desc = (PdfString)fs.get(PdfName.DESC); 2144 if (desc == null) { 2145 description = ""; 2146 } 2147 else { 2148 description = PdfEncodings.convertToString(desc.getBytes(), null); 2149 } 2150 } 2151 fs.addDescription(description, true); 2152 if (description.length() == 0) 2153 description = "Unnamed"; 2154 String fn = PdfEncodings.convertToString(new PdfString(description, PdfObject.TEXT_UNICODE).getBytes(), null); 2155 int k = 0; 2156 while (documentFileAttachment.containsKey(fn)) { 2157 ++k; 2158 fn = PdfEncodings.convertToString(new PdfString(description + " " + k, PdfObject.TEXT_UNICODE).getBytes(), null); 2159 } 2160 documentFileAttachment.put(fn, fs.getReference()); 2161 } 2162 getDocumentFileAttachment()2163 HashMap getDocumentFileAttachment() { 2164 return documentFileAttachment; 2165 } 2166 2167 // [C6] document level actions 2168 2169 protected String openActionName; 2170 setOpenAction(String name)2171 void setOpenAction(String name) { 2172 openActionName = name; 2173 openActionAction = null; 2174 } 2175 2176 protected PdfAction openActionAction; setOpenAction(PdfAction action)2177 void setOpenAction(PdfAction action) { 2178 openActionAction = action; 2179 openActionName = null; 2180 } 2181 2182 protected PdfDictionary additionalActions; addAdditionalAction(PdfName actionType, PdfAction action)2183 void addAdditionalAction(PdfName actionType, PdfAction action) { 2184 if (additionalActions == null) { 2185 additionalActions = new PdfDictionary(); 2186 } 2187 if (action == null) 2188 additionalActions.remove(actionType); 2189 else 2190 additionalActions.put(actionType, action); 2191 if (additionalActions.size() == 0) 2192 additionalActions = null; 2193 } 2194 2195 // [C7] portable collections 2196 2197 protected PdfCollection collection; 2198 2199 /** 2200 * Sets the collection dictionary. 2201 * @param collection a dictionary of type PdfCollection 2202 */ setCollection(PdfCollection collection)2203 public void setCollection(PdfCollection collection) { 2204 this.collection = collection; 2205 } 2206 2207 // [C8] AcroForm 2208 2209 PdfAnnotationsImp annotationsImp; 2210 2211 /** 2212 * Gets the AcroForm object. 2213 * @return the PdfAcroform object of the PdfDocument 2214 */ getAcroForm()2215 PdfAcroForm getAcroForm() { 2216 return annotationsImp.getAcroForm(); 2217 } 2218 setSigFlags(int f)2219 void setSigFlags(int f) { 2220 annotationsImp.setSigFlags(f); 2221 } 2222 addCalculationOrder(PdfFormField formField)2223 void addCalculationOrder(PdfFormField formField) { 2224 annotationsImp.addCalculationOrder(formField); 2225 } 2226 addAnnotation(PdfAnnotation annot)2227 void addAnnotation(PdfAnnotation annot) { 2228 pageEmpty = false; 2229 annotationsImp.addAnnotation(annot); 2230 } 2231 2232 // [F12] tagged PDF 2233 2234 protected int markPoint; 2235 getMarkPoint()2236 int getMarkPoint() { 2237 return markPoint; 2238 } 2239 incMarkPoint()2240 void incMarkPoint() { 2241 ++markPoint; 2242 } 2243 2244 // [U1] page sizes 2245 2246 /** This is the size of the next page. */ 2247 protected Rectangle nextPageSize = null; 2248 2249 /** This is the size of the several boxes of the current Page. */ 2250 protected HashMap thisBoxSize = new HashMap(); 2251 2252 /** This is the size of the several boxes that will be used in 2253 * the next page. */ 2254 protected HashMap boxSize = new HashMap(); 2255 setCropBoxSize(Rectangle crop)2256 void setCropBoxSize(Rectangle crop) { 2257 setBoxSize("crop", crop); 2258 } 2259 setBoxSize(String boxName, Rectangle size)2260 void setBoxSize(String boxName, Rectangle size) { 2261 if (size == null) 2262 boxSize.remove(boxName); 2263 else 2264 boxSize.put(boxName, new PdfRectangle(size)); 2265 } 2266 setNewPageSizeAndMargins()2267 protected void setNewPageSizeAndMargins() { 2268 pageSize = nextPageSize; 2269 if (marginMirroring && (getPageNumber() & 1) == 0) { 2270 marginRight = nextMarginLeft; 2271 marginLeft = nextMarginRight; 2272 } 2273 else { 2274 marginLeft = nextMarginLeft; 2275 marginRight = nextMarginRight; 2276 } 2277 if (marginMirroringTopBottom && (getPageNumber() & 1) == 0) { 2278 marginTop = nextMarginBottom; 2279 marginBottom = nextMarginTop; 2280 } 2281 else { 2282 marginTop = nextMarginTop; 2283 marginBottom = nextMarginBottom; 2284 } 2285 } 2286 2287 /** 2288 * Gives the size of a trim, art, crop or bleed box, or null if not defined. 2289 * @param boxName crop, trim, art or bleed 2290 */ getBoxSize(String boxName)2291 Rectangle getBoxSize(String boxName) { 2292 PdfRectangle r = (PdfRectangle)thisBoxSize.get(boxName); 2293 if (r != null) return r.getRectangle(); 2294 return null; 2295 } 2296 2297 // [U2] empty pages 2298 2299 /** This checks if the page is empty. */ 2300 private boolean pageEmpty = true; 2301 setPageEmpty(boolean pageEmpty)2302 void setPageEmpty(boolean pageEmpty) { 2303 this.pageEmpty = pageEmpty; 2304 } 2305 isPageEmpty()2306 boolean isPageEmpty() { 2307 return writer == null || (writer.getDirectContent().size() == 0 && writer.getDirectContentUnder().size() == 0 && (pageEmpty || writer.isPaused())); 2308 } 2309 2310 // [U3] page actions 2311 2312 /** The duration of the page */ 2313 protected int duration=-1; // negative values will indicate no duration 2314 2315 /** The page transition */ 2316 protected PdfTransition transition=null; 2317 2318 /** 2319 * Sets the display duration for the page (for presentations) 2320 * @param seconds the number of seconds to display the page 2321 */ setDuration(int seconds)2322 void setDuration(int seconds) { 2323 if (seconds > 0) 2324 this.duration=seconds; 2325 else 2326 this.duration=-1; 2327 } 2328 2329 /** 2330 * Sets the transition for the page 2331 * @param transition the PdfTransition object 2332 */ setTransition(PdfTransition transition)2333 void setTransition(PdfTransition transition) { 2334 this.transition=transition; 2335 } 2336 2337 protected PdfDictionary pageAA = null; setPageAction(PdfName actionType, PdfAction action)2338 void setPageAction(PdfName actionType, PdfAction action) { 2339 if (pageAA == null) { 2340 pageAA = new PdfDictionary(); 2341 } 2342 pageAA.put(actionType, action); 2343 } 2344 2345 // [U8] thumbnail images 2346 2347 protected PdfIndirectReference thumb; setThumbnail(Image image)2348 void setThumbnail(Image image) throws PdfException, DocumentException { 2349 thumb = writer.getImageReference(writer.addDirectImageSimple(image)); 2350 } 2351 2352 // [M0] Page resources contain references to fonts, extgstate, images,... 2353 2354 /** This are the page resources of the current Page. */ 2355 protected PageResources pageResources; 2356 getPageResources()2357 PageResources getPageResources() { 2358 return pageResources; 2359 } 2360 2361 // [M3] Images 2362 2363 /** Holds value of property strictImageSequence. */ 2364 protected boolean strictImageSequence = false; 2365 2366 /** Getter for property strictImageSequence. 2367 * @return Value of property strictImageSequence. 2368 * 2369 */ isStrictImageSequence()2370 boolean isStrictImageSequence() { 2371 return this.strictImageSequence; 2372 } 2373 2374 /** Setter for property strictImageSequence. 2375 * @param strictImageSequence New value of property strictImageSequence. 2376 * 2377 */ setStrictImageSequence(boolean strictImageSequence)2378 void setStrictImageSequence(boolean strictImageSequence) { 2379 this.strictImageSequence = strictImageSequence; 2380 } 2381 2382 /** This is the position where the image ends. */ 2383 protected float imageEnd = -1; 2384 2385 /** 2386 * Method added by Pelikan Stephan 2387 */ clearTextWrap()2388 public void clearTextWrap() { 2389 float tmpHeight = imageEnd - currentHeight; 2390 if (line != null) { 2391 tmpHeight += line.height(); 2392 } 2393 if ((imageEnd > -1) && (tmpHeight > 0)) { 2394 carriageReturn(); 2395 currentHeight += tmpHeight; 2396 } 2397 } 2398 2399 /** This is the image that could not be shown on a previous page. */ 2400 protected Image imageWait = null; 2401 2402 /** 2403 * Adds an image to the document. 2404 * @param image the <CODE>Image</CODE> to add 2405 * @throws PdfException on error 2406 * @throws DocumentException on error 2407 */ 2408 add(Image image)2409 protected void add(Image image) throws PdfException, DocumentException { 2410 2411 if (image.hasAbsoluteY()) { 2412 graphics.addImage(image); 2413 pageEmpty = false; 2414 return; 2415 } 2416 2417 // if there isn't enough room for the image on this page, save it for the next page 2418 if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) { 2419 if (!strictImageSequence && imageWait == null) { 2420 imageWait = image; 2421 return; 2422 } 2423 newPage(); 2424 if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) { 2425 imageWait = image; 2426 return; 2427 } 2428 } 2429 pageEmpty = false; 2430 // avoid endless loops 2431 if (image == imageWait) 2432 imageWait = null; 2433 boolean textwrap = (image.getAlignment() & Image.TEXTWRAP) == Image.TEXTWRAP 2434 && !((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE); 2435 boolean underlying = (image.getAlignment() & Image.UNDERLYING) == Image.UNDERLYING; 2436 float diff = leading / 2; 2437 if (textwrap) { 2438 diff += leading; 2439 } 2440 float lowerleft = indentTop() - currentHeight - image.getScaledHeight() -diff; 2441 float mt[] = image.matrix(); 2442 float startPosition = indentLeft() - mt[4]; 2443 if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition = indentRight() - image.getScaledWidth() - mt[4]; 2444 if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition = indentLeft() + ((indentRight() - indentLeft() - image.getScaledWidth()) / 2) - mt[4]; 2445 if (image.hasAbsoluteX()) startPosition = image.getAbsoluteX(); 2446 if (textwrap) { 2447 if (imageEnd < 0 || imageEnd < currentHeight + image.getScaledHeight() + diff) { 2448 imageEnd = currentHeight + image.getScaledHeight() + diff; 2449 } 2450 if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) { 2451 // indentation suggested by Pelikan Stephan 2452 indentation.imageIndentRight += image.getScaledWidth() + image.getIndentationLeft(); 2453 } 2454 else { 2455 // indentation suggested by Pelikan Stephan 2456 indentation.imageIndentLeft += image.getScaledWidth() + image.getIndentationRight(); 2457 } 2458 } 2459 else { 2460 if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition -= image.getIndentationRight(); 2461 else if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition += image.getIndentationLeft() - image.getIndentationRight(); 2462 else startPosition += image.getIndentationLeft(); 2463 } 2464 graphics.addImage(image, mt[0], mt[1], mt[2], mt[3], startPosition, lowerleft - mt[5]); 2465 if (!(textwrap || underlying)) { 2466 currentHeight += image.getScaledHeight() + diff; 2467 flushLines(); 2468 text.moveText(0, - (image.getScaledHeight() + diff)); 2469 newLine(); 2470 } 2471 } 2472 2473 // [M4] Adding a PdfPTable 2474 2475 /** Adds a <CODE>PdfPTable</CODE> to the document. 2476 * @param ptable the <CODE>PdfPTable</CODE> to be added to the document. 2477 * @throws DocumentException on error 2478 */ addPTable(PdfPTable ptable)2479 void addPTable(PdfPTable ptable) throws DocumentException { 2480 ColumnText ct = new ColumnText(writer.getDirectContent()); 2481 // if the table prefers to be on a single page, and it wouldn't 2482 //fit on the current page, start a new page. 2483 if (ptable.getKeepTogether() && !fitsPage(ptable, 0f) && currentHeight > 0) { 2484 newPage(); 2485 } 2486 // add dummy paragraph if we aren't at the top of a page, so that 2487 // spacingBefore will be taken into account by ColumnText 2488 if (currentHeight > 0) { 2489 Paragraph p = new Paragraph(); 2490 p.setLeading(0); 2491 ct.addElement(p); 2492 } 2493 ct.addElement(ptable); 2494 boolean he = ptable.isHeadersInEvent(); 2495 ptable.setHeadersInEvent(true); 2496 int loop = 0; 2497 while (true) { 2498 ct.setSimpleColumn(indentLeft(), indentBottom(), indentRight(), indentTop() - currentHeight); 2499 int status = ct.go(); 2500 if ((status & ColumnText.NO_MORE_TEXT) != 0) { 2501 text.moveText(0, ct.getYLine() - indentTop() + currentHeight); 2502 currentHeight = indentTop() - ct.getYLine(); 2503 break; 2504 } 2505 if (indentTop() - currentHeight == ct.getYLine()) 2506 ++loop; 2507 else 2508 loop = 0; 2509 if (loop == 3) { 2510 add(new Paragraph("ERROR: Infinite table loop")); 2511 break; 2512 } 2513 newPage(); 2514 } 2515 ptable.setHeadersInEvent(he); 2516 } 2517 2518 /** 2519 * Checks if a <CODE>PdfPTable</CODE> fits the current page of the <CODE>PdfDocument</CODE>. 2520 * 2521 * @param table the table that has to be checked 2522 * @param margin a certain margin 2523 * @return <CODE>true</CODE> if the <CODE>PdfPTable</CODE> fits the page, <CODE>false</CODE> otherwise. 2524 */ 2525 fitsPage(PdfPTable table, float margin)2526 boolean fitsPage(PdfPTable table, float margin) { 2527 if (!table.isLockedWidth()) { 2528 float totalWidth = (indentRight() - indentLeft()) * table.getWidthPercentage() / 100; 2529 table.setTotalWidth(totalWidth); 2530 } 2531 // ensuring that a new line has been started. 2532 ensureNewLine(); 2533 return table.getTotalHeight() + ((currentHeight > 0) ? table.spacingBefore() : 0f) 2534 <= indentTop() - currentHeight - indentBottom() - margin; 2535 } 2536 2537 // [M4'] Adding a Table 2538 2539 /** 2540 * This is a helper class for adding a Table to a document. 2541 * @since 2.0.8 (PdfDocument was package-private before) 2542 */ 2543 protected static class RenderingContext { 2544 float pagetop = -1; 2545 float oldHeight = -1; 2546 2547 PdfContentByte cellGraphics = null; 2548 2549 float lostTableBottom; 2550 2551 float maxCellBottom; 2552 float maxCellHeight; 2553 2554 Map rowspanMap; 2555 Map pageMap = new HashMap(); 2556 /** 2557 * A PdfPTable 2558 */ 2559 public PdfTable table; 2560 2561 /** 2562 * Consumes the rowspan 2563 * @param c 2564 * @return a rowspan. 2565 */ consumeRowspan(PdfCell c)2566 public int consumeRowspan(PdfCell c) { 2567 if (c.rowspan() == 1) { 2568 return 1; 2569 } 2570 2571 Integer i = (Integer) rowspanMap.get(c); 2572 if (i == null) { 2573 i = new Integer(c.rowspan()); 2574 } 2575 2576 i = new Integer(i.intValue() - 1); 2577 rowspanMap.put(c, i); 2578 2579 if (i.intValue() < 1) { 2580 return 1; 2581 } 2582 return i.intValue(); 2583 } 2584 2585 /** 2586 * Looks at the current rowspan. 2587 * @param c 2588 * @return the current rowspan 2589 */ currentRowspan(PdfCell c)2590 public int currentRowspan(PdfCell c) { 2591 Integer i = (Integer) rowspanMap.get(c); 2592 if (i == null) { 2593 return c.rowspan(); 2594 } else { 2595 return i.intValue(); 2596 } 2597 } 2598 cellRendered(PdfCell cell, int pageNumber)2599 public int cellRendered(PdfCell cell, int pageNumber) { 2600 Integer i = (Integer) pageMap.get(cell); 2601 if (i == null) { 2602 i = new Integer(1); 2603 } else { 2604 i = new Integer(i.intValue() + 1); 2605 } 2606 pageMap.put(cell, i); 2607 2608 Integer pageInteger = new Integer(pageNumber); 2609 Set set = (Set) pageMap.get(pageInteger); 2610 2611 if (set == null) { 2612 set = new HashSet(); 2613 pageMap.put(pageInteger, set); 2614 } 2615 2616 set.add(cell); 2617 2618 return i.intValue(); 2619 } 2620 numCellRendered(PdfCell cell)2621 public int numCellRendered(PdfCell cell) { 2622 Integer i = (Integer) pageMap.get(cell); 2623 if (i == null) { 2624 i = new Integer(0); 2625 } 2626 return i.intValue(); 2627 } 2628 isCellRenderedOnPage(PdfCell cell, int pageNumber)2629 public boolean isCellRenderedOnPage(PdfCell cell, int pageNumber) { 2630 Integer pageInteger = new Integer(pageNumber); 2631 Set set = (Set) pageMap.get(pageInteger); 2632 2633 if (set != null) { 2634 return set.contains(cell); 2635 } 2636 2637 return false; 2638 } 2639 }; 2640 2641 /** 2642 * Adds a new table to the document. 2643 * @param t Table to add. Rendered rows will be deleted after processing. 2644 * @throws DocumentException 2645 * @since iText 2.0.8 2646 */ addPdfTable(Table t)2647 private void addPdfTable(Table t) throws DocumentException { 2648 // before every table, we flush all lines 2649 flushLines(); 2650 2651 PdfTable table = new PdfTable(t, indentLeft(), indentRight(), indentTop() - currentHeight); 2652 RenderingContext ctx = new RenderingContext(); 2653 ctx.pagetop = indentTop(); 2654 ctx.oldHeight = currentHeight; 2655 ctx.cellGraphics = new PdfContentByte(writer); 2656 ctx.rowspanMap = new HashMap(); 2657 ctx.table = table; 2658 2659 // initialization of parameters 2660 PdfCell cell; 2661 2662 // drawing the table 2663 ArrayList headercells = table.getHeaderCells(); 2664 ArrayList cells = table.getCells(); 2665 ArrayList rows = extractRows(cells, ctx); 2666 boolean isContinue = false; 2667 while (!cells.isEmpty()) { 2668 // initialization of some extra parameters; 2669 ctx.lostTableBottom = 0; 2670 2671 // loop over the cells 2672 boolean cellsShown = false; 2673 2674 // draw the cells (line by line) 2675 Iterator iterator = rows.iterator(); 2676 2677 boolean atLeastOneFits = false; 2678 while (iterator.hasNext()) { 2679 ArrayList row = (ArrayList) iterator.next(); 2680 analyzeRow(rows, ctx); 2681 renderCells(ctx, row, table.hasToFitPageCells() & atLeastOneFits); 2682 2683 if (!mayBeRemoved(row)) { 2684 break; 2685 } 2686 consumeRowspan(row, ctx); 2687 iterator.remove(); 2688 atLeastOneFits = true; 2689 } 2690 2691 // compose cells array list for subsequent code 2692 cells.clear(); 2693 Set opt = new HashSet(); 2694 iterator = rows.iterator(); 2695 while (iterator.hasNext()) { 2696 ArrayList row = (ArrayList) iterator.next(); 2697 2698 Iterator cellIterator = row.iterator(); 2699 while (cellIterator.hasNext()) { 2700 cell = (PdfCell) cellIterator.next(); 2701 2702 if (!opt.contains(cell)) { 2703 cells.add(cell); 2704 opt.add(cell); 2705 } 2706 } 2707 } 2708 2709 // we paint the graphics of the table after looping through all the cells 2710 Rectangle tablerec = new Rectangle(table); 2711 tablerec.setBorder(table.getBorder()); 2712 tablerec.setBorderWidth(table.getBorderWidth()); 2713 tablerec.setBorderColor(table.getBorderColor()); 2714 tablerec.setBackgroundColor(table.getBackgroundColor()); 2715 PdfContentByte under = writer.getDirectContentUnder(); 2716 under.rectangle(tablerec.rectangle(top(), indentBottom())); 2717 under.add(ctx.cellGraphics); 2718 // bugfix by Gerald Fehringer: now again add the border for the table 2719 // since it might have been covered by cell backgrounds 2720 tablerec.setBackgroundColor(null); 2721 tablerec = tablerec.rectangle(top(), indentBottom()); 2722 tablerec.setBorder(table.getBorder()); 2723 under.rectangle(tablerec); 2724 // end bugfix 2725 2726 ctx.cellGraphics = new PdfContentByte(null); 2727 // if the table continues on the next page 2728 2729 if (!rows.isEmpty()) { 2730 isContinue = true; 2731 graphics.setLineWidth(table.getBorderWidth()); 2732 if (cellsShown && (table.getBorder() & Rectangle.BOTTOM) == Rectangle.BOTTOM) { 2733 // Draw the bottom line 2734 2735 // the color is set to the color of the element 2736 Color tColor = table.getBorderColor(); 2737 if (tColor != null) { 2738 graphics.setColorStroke(tColor); 2739 } 2740 graphics.moveTo(table.getLeft(), Math.max(table.getBottom(), indentBottom())); 2741 graphics.lineTo(table.getRight(), Math.max(table.getBottom(), indentBottom())); 2742 graphics.stroke(); 2743 if (tColor != null) { 2744 graphics.resetRGBColorStroke(); 2745 } 2746 } 2747 2748 // old page 2749 pageEmpty = false; 2750 float difference = ctx.lostTableBottom; 2751 2752 // new page 2753 newPage(); 2754 2755 // G.F.: if something added in page event i.e. currentHeight > 0 2756 float heightCorrection = 0; 2757 boolean somethingAdded = false; 2758 if (currentHeight > 0) { 2759 heightCorrection = 6; 2760 currentHeight += heightCorrection; 2761 somethingAdded = true; 2762 newLine(); 2763 flushLines(); 2764 indentation.indentTop = currentHeight - leading; 2765 currentHeight = 0; 2766 } 2767 else { 2768 flushLines(); 2769 } 2770 2771 // this part repeats the table headers (if any) 2772 int size = headercells.size(); 2773 if (size > 0) { 2774 // this is the top of the headersection 2775 cell = (PdfCell) headercells.get(0); 2776 float oldTop = cell.getTop(0); 2777 // loop over all the cells of the table header 2778 for (int i = 0; i < size; i++) { 2779 cell = (PdfCell) headercells.get(i); 2780 // calculation of the new cellpositions 2781 cell.setTop(indentTop() - oldTop + cell.getTop(0)); 2782 cell.setBottom(indentTop() - oldTop + cell.getBottom(0)); 2783 ctx.pagetop = cell.getBottom(); 2784 // we paint the borders of the cell 2785 ctx.cellGraphics.rectangle(cell.rectangle(indentTop(), indentBottom())); 2786 // we write the text of the cell 2787 ArrayList images = cell.getImages(indentTop(), indentBottom()); 2788 for (Iterator im = images.iterator(); im.hasNext();) { 2789 cellsShown = true; 2790 Image image = (Image) im.next(); 2791 graphics.addImage(image); 2792 } 2793 lines = cell.getLines(indentTop(), indentBottom()); 2794 float cellTop = cell.getTop(indentTop()); 2795 text.moveText(0, cellTop-heightCorrection); 2796 float cellDisplacement = flushLines() - cellTop+heightCorrection; 2797 text.moveText(0, cellDisplacement); 2798 } 2799 2800 currentHeight = indentTop() - ctx.pagetop + table.cellspacing(); 2801 text.moveText(0, ctx.pagetop - indentTop() - currentHeight); 2802 } 2803 else { 2804 if (somethingAdded) { 2805 ctx.pagetop = indentTop(); 2806 text.moveText(0, -table.cellspacing()); 2807 } 2808 } 2809 ctx.oldHeight = currentHeight - heightCorrection; 2810 2811 // calculating the new positions of the table and the cells 2812 size = Math.min(cells.size(), table.columns()); 2813 int i = 0; 2814 while (i < size) { 2815 cell = (PdfCell) cells.get(i); 2816 if (cell.getTop(-table.cellspacing()) > ctx.lostTableBottom) { 2817 float newBottom = ctx.pagetop - difference + cell.getBottom(); 2818 float neededHeight = cell.remainingHeight(); 2819 if (newBottom > ctx.pagetop - neededHeight) { 2820 difference += newBottom - (ctx.pagetop - neededHeight); 2821 } 2822 } 2823 i++; 2824 } 2825 size = cells.size(); 2826 table.setTop(indentTop()); 2827 table.setBottom(ctx.pagetop - difference + table.getBottom(table.cellspacing())); 2828 for (i = 0; i < size; i++) { 2829 cell = (PdfCell) cells.get(i); 2830 float newBottom = ctx.pagetop - difference + cell.getBottom(); 2831 float newTop = ctx.pagetop - difference + cell.getTop(-table.cellspacing()); 2832 if (newTop > indentTop() - currentHeight) { 2833 newTop = indentTop() - currentHeight; 2834 } 2835 2836 cell.setTop(newTop ); 2837 cell.setBottom(newBottom ); 2838 } 2839 } 2840 } 2841 2842 float tableHeight = table.getTop() - table.getBottom(); 2843 // bugfix by Adauto Martins when have more than two tables and more than one page 2844 // If continuation of table in other page (bug report #1460051) 2845 if (isContinue) { 2846 currentHeight = tableHeight; 2847 text.moveText(0, -(tableHeight - (ctx.oldHeight * 2))); 2848 } else { 2849 currentHeight = ctx.oldHeight + tableHeight; 2850 text.moveText(0, -tableHeight); 2851 } 2852 // end bugfix 2853 pageEmpty = false; 2854 } 2855 analyzeRow(ArrayList rows, RenderingContext ctx)2856 protected void analyzeRow(ArrayList rows, RenderingContext ctx) { 2857 ctx.maxCellBottom = indentBottom(); 2858 2859 // determine whether row(index) is in a rowspan 2860 int rowIndex = 0; 2861 2862 ArrayList row = (ArrayList) rows.get(rowIndex); 2863 int maxRowspan = 1; 2864 Iterator iterator = row.iterator(); 2865 while (iterator.hasNext()) { 2866 PdfCell cell = (PdfCell) iterator.next(); 2867 maxRowspan = Math.max(ctx.currentRowspan(cell), maxRowspan); 2868 } 2869 rowIndex += maxRowspan; 2870 2871 boolean useTop = true; 2872 if (rowIndex == rows.size()) { 2873 rowIndex = rows.size() - 1; 2874 useTop = false; 2875 } 2876 2877 if (rowIndex < 0 || rowIndex >= rows.size()) return; 2878 2879 row = (ArrayList) rows.get(rowIndex); 2880 iterator = row.iterator(); 2881 while (iterator.hasNext()) { 2882 PdfCell cell = (PdfCell) iterator.next(); 2883 Rectangle cellRect = cell.rectangle(ctx.pagetop, indentBottom()); 2884 if (useTop) { 2885 ctx.maxCellBottom = Math.max(ctx.maxCellBottom, cellRect.getTop()); 2886 } else { 2887 if (ctx.currentRowspan(cell) == 1) { 2888 ctx.maxCellBottom = Math.max(ctx.maxCellBottom, cellRect.getBottom()); 2889 } 2890 } 2891 } 2892 } 2893 mayBeRemoved(ArrayList row)2894 protected boolean mayBeRemoved(ArrayList row) { 2895 Iterator iterator = row.iterator(); 2896 boolean mayBeRemoved = true; 2897 while (iterator.hasNext()) { 2898 PdfCell cell = (PdfCell) iterator.next(); 2899 2900 mayBeRemoved &= cell.mayBeRemoved(); 2901 } 2902 return mayBeRemoved; 2903 } 2904 consumeRowspan(ArrayList row, RenderingContext ctx)2905 protected void consumeRowspan(ArrayList row, RenderingContext ctx) { 2906 Iterator iterator = row.iterator(); 2907 while (iterator.hasNext()) { 2908 PdfCell c = (PdfCell) iterator.next(); 2909 ctx.consumeRowspan(c); 2910 } 2911 } 2912 extractRows(ArrayList cells, RenderingContext ctx)2913 protected ArrayList extractRows(ArrayList cells, RenderingContext ctx) { 2914 PdfCell cell; 2915 PdfCell previousCell = null; 2916 ArrayList rows = new ArrayList(); 2917 java.util.List rowCells = new ArrayList(); 2918 2919 Iterator iterator = cells.iterator(); 2920 while (iterator.hasNext()) { 2921 cell = (PdfCell) iterator.next(); 2922 2923 boolean isAdded = false; 2924 2925 boolean isEndOfRow = !iterator.hasNext(); 2926 boolean isCurrentCellPartOfRow = !iterator.hasNext(); 2927 2928 if (previousCell != null) { 2929 if (cell.getLeft() <= previousCell.getLeft()) { 2930 isEndOfRow = true; 2931 isCurrentCellPartOfRow = false; 2932 } 2933 } 2934 2935 if (isCurrentCellPartOfRow) { 2936 rowCells.add(cell); 2937 isAdded = true; 2938 } 2939 2940 if (isEndOfRow) { 2941 if (!rowCells.isEmpty()) { 2942 // add to rowlist 2943 rows.add(rowCells); 2944 } 2945 2946 // start a new list for next line 2947 rowCells = new ArrayList(); 2948 } 2949 2950 if (!isAdded) { 2951 rowCells.add(cell); 2952 } 2953 2954 previousCell = cell; 2955 } 2956 2957 if (!rowCells.isEmpty()) { 2958 rows.add(rowCells); 2959 } 2960 2961 // fill row information with rowspan cells to get complete "scan lines" 2962 for (int i = rows.size() - 1; i >= 0; i--) { 2963 ArrayList row = (ArrayList) rows.get(i); 2964 // iterator through row 2965 for (int j = 0; j < row.size(); j++) { 2966 PdfCell c = (PdfCell) row.get(j); 2967 int rowspan = c.rowspan(); 2968 // fill in missing rowspan cells to complete "scan line" 2969 for (int k = 1; k < rowspan && rows.size() < i+k; k++) { 2970 ArrayList spannedRow = ((ArrayList) rows.get(i + k)); 2971 if (spannedRow.size() > j) 2972 spannedRow.add(j, c); 2973 } 2974 } 2975 } 2976 2977 return rows; 2978 } 2979 renderCells(RenderingContext ctx, java.util.List cells, boolean hasToFit)2980 protected void renderCells(RenderingContext ctx, java.util.List cells, boolean hasToFit) throws DocumentException { 2981 PdfCell cell; 2982 Iterator iterator; 2983 if (hasToFit) { 2984 iterator = cells.iterator(); 2985 while (iterator.hasNext()) { 2986 cell = (PdfCell) iterator.next(); 2987 if (!cell.isHeader()) { 2988 if (cell.getBottom() < indentBottom()) return; 2989 } 2990 } 2991 } 2992 iterator = cells.iterator(); 2993 2994 while (iterator.hasNext()) { 2995 cell = (PdfCell) iterator.next(); 2996 if (!ctx.isCellRenderedOnPage(cell, getPageNumber())) { 2997 2998 float correction = 0; 2999 if (ctx.numCellRendered(cell) >= 1) { 3000 correction = 1.0f; 3001 } 3002 3003 lines = cell.getLines(ctx.pagetop, indentBottom() - correction); 3004 3005 // if there is still text to render we render it 3006 if (lines != null && !lines.isEmpty()) { 3007 // we write the text 3008 float cellTop = cell.getTop(ctx.pagetop - ctx.oldHeight); 3009 text.moveText(0, cellTop); 3010 float cellDisplacement = flushLines() - cellTop; 3011 3012 text.moveText(0, cellDisplacement); 3013 if (ctx.oldHeight + cellDisplacement > currentHeight) { 3014 currentHeight = ctx.oldHeight + cellDisplacement; 3015 } 3016 3017 ctx.cellRendered(cell, getPageNumber()); 3018 } 3019 float indentBottom = Math.max(cell.getBottom(), indentBottom()); 3020 Rectangle tableRect = ctx.table.rectangle(ctx.pagetop, indentBottom()); 3021 indentBottom = Math.max(tableRect.getBottom(), indentBottom); 3022 3023 // we paint the borders of the cells 3024 Rectangle cellRect = cell.rectangle(tableRect.getTop(), indentBottom); 3025 //cellRect.setBottom(cellRect.bottom()); 3026 if (cellRect.getHeight() > 0) { 3027 ctx.lostTableBottom = indentBottom; 3028 ctx.cellGraphics.rectangle(cellRect); 3029 } 3030 3031 // and additional graphics 3032 ArrayList images = cell.getImages(ctx.pagetop, indentBottom()); 3033 for (Iterator i = images.iterator(); i.hasNext();) { 3034 Image image = (Image) i.next(); 3035 graphics.addImage(image); 3036 } 3037 3038 } 3039 } 3040 } 3041 3042 /** 3043 * Returns the bottomvalue of a <CODE>Table</CODE> if it were added to this document. 3044 * 3045 * @param table the table that may or may not be added to this document 3046 * @return a bottom value 3047 */ bottom(Table table)3048 float bottom(Table table) { 3049 // constructing a PdfTable 3050 PdfTable tmp = new PdfTable(table, indentLeft(), indentRight(), indentTop() - currentHeight); 3051 return tmp.getBottom(); 3052 } 3053 3054 // [M5] header/footer doFooter()3055 protected void doFooter() throws DocumentException { 3056 if (footer == null) return; 3057 // Begin added by Edgar Leonardo Prieto Perilla 3058 // Avoid footer indentation 3059 float tmpIndentLeft = indentation.indentLeft; 3060 float tmpIndentRight = indentation.indentRight; 3061 // Begin added: Bonf (Marc Schneider) 2003-07-29 3062 float tmpListIndentLeft = indentation.listIndentLeft; 3063 float tmpImageIndentLeft = indentation.imageIndentLeft; 3064 float tmpImageIndentRight = indentation.imageIndentRight; 3065 // End added: Bonf (Marc Schneider) 2003-07-29 3066 3067 indentation.indentLeft = indentation.indentRight = 0; 3068 // Begin added: Bonf (Marc Schneider) 2003-07-29 3069 indentation.listIndentLeft = 0; 3070 indentation.imageIndentLeft = 0; 3071 indentation.imageIndentRight = 0; 3072 // End added: Bonf (Marc Schneider) 2003-07-29 3073 // End Added by Edgar Leonardo Prieto Perilla 3074 footer.setPageNumber(pageN); 3075 leading = footer.paragraph().getTotalLeading(); 3076 add(footer.paragraph()); 3077 // adding the footer limits the height 3078 indentation.indentBottom = currentHeight; 3079 text.moveText(left(), indentBottom()); 3080 flushLines(); 3081 text.moveText(-left(), -bottom()); 3082 footer.setTop(bottom(currentHeight)); 3083 footer.setBottom(bottom() - (0.75f * leading)); 3084 footer.setLeft(left()); 3085 footer.setRight(right()); 3086 graphics.rectangle(footer); 3087 indentation.indentBottom = currentHeight + leading * 2; 3088 currentHeight = 0; 3089 // Begin added by Edgar Leonardo Prieto Perilla 3090 indentation.indentLeft = tmpIndentLeft; 3091 indentation.indentRight = tmpIndentRight; 3092 // Begin added: Bonf (Marc Schneider) 2003-07-29 3093 indentation.listIndentLeft = tmpListIndentLeft; 3094 indentation.imageIndentLeft = tmpImageIndentLeft; 3095 indentation.imageIndentRight = tmpImageIndentRight; 3096 // End added: Bonf (Marc Schneider) 2003-07-29 3097 // End added by Edgar Leonardo Prieto Perilla 3098 } 3099 doHeader()3100 protected void doHeader() throws DocumentException { 3101 // if there is a header, the header = added 3102 if (header == null) return; 3103 // Begin added by Edgar Leonardo Prieto Perilla 3104 // Avoid header indentation 3105 float tmpIndentLeft = indentation.indentLeft; 3106 float tmpIndentRight = indentation.indentRight; 3107 // Begin added: Bonf (Marc Schneider) 2003-07-29 3108 float tmpListIndentLeft = indentation.listIndentLeft; 3109 float tmpImageIndentLeft = indentation.imageIndentLeft; 3110 float tmpImageIndentRight = indentation.imageIndentRight; 3111 // End added: Bonf (Marc Schneider) 2003-07-29 3112 indentation.indentLeft = indentation.indentRight = 0; 3113 // Added: Bonf 3114 indentation.listIndentLeft = 0; 3115 indentation.imageIndentLeft = 0; 3116 indentation.imageIndentRight = 0; 3117 // End added: Bonf 3118 // Begin added by Edgar Leonardo Prieto Perilla 3119 header.setPageNumber(pageN); 3120 leading = header.paragraph().getTotalLeading(); 3121 text.moveText(0, leading); 3122 add(header.paragraph()); 3123 newLine(); 3124 indentation.indentTop = currentHeight - leading; 3125 header.setTop(top() + leading); 3126 header.setBottom(indentTop() + leading * 2 / 3); 3127 header.setLeft(left()); 3128 header.setRight(right()); 3129 graphics.rectangle(header); 3130 flushLines(); 3131 currentHeight = 0; 3132 // Begin added by Edgar Leonardo Prieto Perilla 3133 // Restore indentation 3134 indentation.indentLeft = tmpIndentLeft; 3135 indentation.indentRight = tmpIndentRight; 3136 // Begin added: Bonf (Marc Schneider) 2003-07-29 3137 indentation.listIndentLeft = tmpListIndentLeft; 3138 indentation.imageIndentLeft = tmpImageIndentLeft; 3139 indentation.imageIndentRight = tmpImageIndentRight; 3140 // End added: Bonf (Marc Schneider) 2003-07-29 3141 // End Added by Edgar Leonardo Prieto Perilla 3142 } 3143 } 3144