/* * $Name: $ * $Id: PdfDocument.java,v 1.229 2006/06/04 22:23:34 psoares33 Exp $ * * Copyright 1999, 2000, 2001, 2002 by Bruno Lowagie. * * The contents of this file are subject to the Mozilla Public License Version 1.1 * (the "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the License. * * The Original Code is 'iText, a free JAVA-PDF library'. * * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie. * All Rights Reserved. * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved. * * Contributor(s): all the names of the contributors are added in the source code * where applicable. * * Alternatively, the contents of this file may be used under the terms of the * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the * provisions of LGPL are applicable instead of those above. If you wish to * allow use of your version of this file only under the terms of the LGPL * License and not to allow others to use your version of this file under * the MPL, indicate your decision by deleting the provisions above and * replace them with the notice and other provisions required by the LGPL. * If you do not delete the provisions above, a recipient may use your version * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE. * * This library is free software; you can redistribute it and/or modify it * under the terms of the MPL as stated above or under the terms of the GNU * Library General Public License as published by the Free Software Foundation; * either version 2 of the License, or any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more * details. * * If you didn't download this code from the following link, you should check if * you aren't using an obsolete version: * http://www.lowagie.com/iText/ */ package com.lowagie.text.pdf; import java.awt.Color; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; import com.lowagie.text.Anchor; import com.lowagie.text.Annotation; import com.lowagie.text.BadElementException; import com.lowagie.text.Cell; import com.lowagie.text.Chunk; import com.lowagie.text.DocListener; import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.Element; import com.lowagie.text.ExceptionConverter; import com.lowagie.text.Graphic; import com.lowagie.text.HeaderFooter; import com.lowagie.text.Image; import com.lowagie.text.List; import com.lowagie.text.ListItem; import com.lowagie.text.Meta; import com.lowagie.text.Paragraph; import com.lowagie.text.Phrase; import com.lowagie.text.Rectangle; import com.lowagie.text.Section; import com.lowagie.text.SimpleTable; import com.lowagie.text.StringCompare; import com.lowagie.text.Table; import com.lowagie.text.Watermark; import com.lowagie.text.xml.xmp.XmpWriter; /** * PdfDocument is the class that is used by PdfWriter * to translate a Document into a PDF with different pages. *

* A PdfDocument always listens to a Document * and adds the Pdf representation of every Element that is * added to the Document. * * @see com.lowagie.text.Document * @see com.lowagie.text.DocListener * @see PdfWriter */ class PdfDocument extends Document implements DocListener { /** * PdfInfo is the PDF InfoDictionary. *

* A document's trailer may contain a reference to an Info dictionary that provides information * about the document. This optional dictionary may contain one or more keys, whose values * should be strings.
* This object is described in the 'Portable Document Format Reference Manual version 1.3' * section 6.10 (page 120-121) */ public static class PdfInfo extends PdfDictionary { // constructors /** * Construct a PdfInfo-object. */ PdfInfo() { super(); addProducer(); addCreationDate(); } /** * Constructs a PdfInfo-object. * * @param author name of the author of the document * @param title title of the document * @param subject subject of the document */ PdfInfo(String author, String title, String subject) { this(); addTitle(title); addSubject(subject); addAuthor(author); } /** * Adds the title of the document. * * @param title the title of the document */ void addTitle(String title) { put(PdfName.TITLE, new PdfString(title, PdfObject.TEXT_UNICODE)); } /** * Adds the subject to the document. * * @param subject the subject of the document */ void addSubject(String subject) { put(PdfName.SUBJECT, new PdfString(subject, PdfObject.TEXT_UNICODE)); } /** * Adds some keywords to the document. * * @param keywords the keywords of the document */ void addKeywords(String keywords) { put(PdfName.KEYWORDS, new PdfString(keywords, PdfObject.TEXT_UNICODE)); } /** * Adds the name of the author to the document. * * @param author the name of the author */ void addAuthor(String author) { put(PdfName.AUTHOR, new PdfString(author, PdfObject.TEXT_UNICODE)); } /** * Adds the name of the creator to the document. * * @param creator the name of the creator */ void addCreator(String creator) { put(PdfName.CREATOR, new PdfString(creator, PdfObject.TEXT_UNICODE)); } /** * Adds the name of the producer to the document. */ void addProducer() { // This line may only be changed by Bruno Lowagie or Paulo Soares put(PdfName.PRODUCER, new PdfString(getVersion())); // Do not edit the line above! } /** * Adds the date of creation to the document. */ void addCreationDate() { PdfString date = new PdfDate(); put(PdfName.CREATIONDATE, date); put(PdfName.MODDATE, date); } void addkey(String key, String value) { if (key.equals("Producer") || key.equals("CreationDate")) return; put(new PdfName(key), new PdfString(value, PdfObject.TEXT_UNICODE)); } } /** * PdfCatalog is the PDF Catalog-object. *

* The Catalog is a dictionary that is the root node of the document. It contains a reference * to the tree of pages contained in the document, a reference to the tree of objects representing * the document's outline, a reference to the document's article threads, and the list of named * destinations. In addition, the Catalog indicates whether the document's outline or thumbnail * page images should be displayed automatically when the document is viewed and wether some location * other than the first page should be shown when the document is opened.
* In this class however, only the reference to the tree of pages is implemented.
* This object is described in the 'Portable Document Format Reference Manual version 1.3' * section 6.2 (page 67-71) */ static class PdfCatalog extends PdfDictionary { PdfWriter writer; // constructors /** * Constructs a PdfCatalog. * * @param pages an indirect reference to the root of the document's Pages tree. * @param writer the writer the catalog applies to */ PdfCatalog(PdfIndirectReference pages, PdfWriter writer) { super(CATALOG); this.writer = writer; put(PdfName.PAGES, pages); } /** * Constructs a PdfCatalog. * * @param pages an indirect reference to the root of the document's Pages tree. * @param outlines an indirect reference to the outline tree. * @param writer the writer the catalog applies to */ PdfCatalog(PdfIndirectReference pages, PdfIndirectReference outlines, PdfWriter writer) { super(CATALOG); this.writer = writer; put(PdfName.PAGES, pages); put(PdfName.PAGEMODE, PdfName.USEOUTLINES); put(PdfName.OUTLINES, outlines); } /** * Adds the names of the named destinations to the catalog. * @param localDestinations the local destinations * @param documentJavaScript the javascript used in the document * @param writer the writer the catalog applies to */ void addNames(TreeMap localDestinations, ArrayList documentJavaScript, HashMap documentFileAttachment, PdfWriter writer) { if (localDestinations.size() == 0 && documentJavaScript.size() == 0 && documentFileAttachment.size() == 0) return; try { PdfDictionary names = new PdfDictionary(); if (localDestinations.size() > 0) { PdfArray ar = new PdfArray(); for (Iterator i = localDestinations.keySet().iterator(); i.hasNext();) { String name = (String)i.next(); Object obj[] = (Object[])localDestinations.get(name); PdfIndirectReference ref = (PdfIndirectReference)obj[1]; ar.add(new PdfString(name)); ar.add(ref); } PdfDictionary dests = new PdfDictionary(); dests.put(PdfName.NAMES, ar); names.put(PdfName.DESTS, writer.addToBody(dests).getIndirectReference()); } if (documentJavaScript.size() > 0) { String s[] = new String[documentJavaScript.size()]; for (int k = 0; k < s.length; ++k) s[k] = Integer.toHexString(k); Arrays.sort(s, new StringCompare()); PdfArray ar = new PdfArray(); for (int k = 0; k < s.length; ++k) { ar.add(new PdfString(s[k])); ar.add((PdfIndirectReference)documentJavaScript.get(k)); } PdfDictionary js = new PdfDictionary(); js.put(PdfName.NAMES, ar); names.put(PdfName.JAVASCRIPT, writer.addToBody(js).getIndirectReference()); } if (documentFileAttachment.size() > 0) { names.put(PdfName.EMBEDDEDFILES, writer.addToBody(PdfNameTree.writeTree(documentFileAttachment, writer)).getIndirectReference()); } put(PdfName.NAMES, writer.addToBody(names).getIndirectReference()); } catch (IOException e) { throw new ExceptionConverter(e); } } /** Sets the viewer preferences as the sum of several constants. * @param preferences the viewer preferences * @see PdfWriter#setViewerPreferences */ void setViewerPreferences(int preferences) { PdfReader.setViewerPreferences(preferences, this); } void setOpenAction(PdfAction action) { put(PdfName.OPENACTION, action); } /** Sets the document level additional actions. * @param actions dictionary of actions */ void setAdditionalActions(PdfDictionary actions) { try { put(PdfName.AA, writer.addToBody(actions).getIndirectReference()); } catch (Exception e) { new ExceptionConverter(e); } } void setPageLabels(PdfPageLabels pageLabels) { put(PdfName.PAGELABELS, pageLabels.getDictionary()); } void setAcroForm(PdfObject fields) { put(PdfName.ACROFORM, fields); } } // membervariables private PdfIndirectReference thumb; /** The characters to be applied the hanging ponctuation. */ static final String hangingPunctuation = ".,;:'"; /** The PdfWriter. */ private PdfWriter writer; /** some meta information about the Document. */ private PdfInfo info = new PdfInfo(); /** Signals that OnOpenDocument should be called. */ private boolean firstPageEvent = true; /** Signals that onParagraph is valid. */ private boolean isParagraph = true; // Horizontal line /** The line that is currently being written. */ private PdfLine line = null; /** This represents the current indentation of the PDF Elements on the left side. */ private float indentLeft = 0; /** This represents the current indentation of the PDF Elements on the right side. */ private float indentRight = 0; /** This represents the current indentation of the PDF Elements on the left side. */ private float listIndentLeft = 0; /** This represents the current alignment of the PDF Elements. */ private int alignment = Element.ALIGN_LEFT; // Vertical lines /** This is the PdfContentByte object, containing the text. */ private PdfContentByte text; /** This is the PdfContentByte object, containing the borders and other Graphics. */ private PdfContentByte graphics; /** The lines that are written until now. */ private ArrayList lines = new ArrayList(); /** This represents the leading of the lines. */ private float leading = 0; /** This is the current height of the document. */ private float currentHeight = 0; /** This represents the current indentation of the PDF Elements on the top side. */ private float indentTop = 0; /** This represents the current indentation of the PDF Elements on the bottom side. */ private float indentBottom = 0; /** This checks if the page is empty. */ private boolean pageEmpty = true; private int textEmptySize; // resources /** This is the size of the next page. */ protected Rectangle nextPageSize = null; /** This is the size of the several boxes of the current Page. */ protected HashMap thisBoxSize = new HashMap(); /** This is the size of the several boxes that will be used in * the next page. */ protected HashMap boxSize = new HashMap(); /** This are the page resources of the current Page. */ protected PageResources pageResources; // images /** This is the image that could not be shown on a previous page. */ private Image imageWait = null; /** This is the position where the image ends. */ private float imageEnd = -1; /** This is the indentation caused by an image on the left. */ private float imageIndentLeft = 0; /** This is the indentation caused by an image on the right. */ private float imageIndentRight = 0; // annotations and outlines /** This is the array containing the references to the annotations. */ private ArrayList annotations; /** This is an array containg references to some delayed annotations. */ private ArrayList delayedAnnotations = new ArrayList(); /** This is the AcroForm object. */ PdfAcroForm acroForm; /** This is the root outline of the document. */ private PdfOutline rootOutline; /** This is the current PdfOutline in the hierarchy of outlines. */ private PdfOutline currentOutline; /** The current active PdfAction when processing an Anchor. */ private PdfAction currentAction = null; /** * Stores the destinations keyed by name. Value is * Object[]{PdfAction,PdfIndirectReference,PdfDestintion}. */ private TreeMap localDestinations = new TreeMap(new StringCompare()); private ArrayList documentJavaScript = new ArrayList(); private HashMap documentFileAttachment = new HashMap(); /** these are the viewerpreferences of the document */ private int viewerPreferences = 0; private String openActionName; private PdfAction openActionAction; private PdfDictionary additionalActions; private PdfPageLabels pageLabels; //add by Jin-Hsia Yang private boolean isNewpage = false; private float paraIndent = 0; //end add by Jin-Hsia Yang /** margin in x direction starting from the left. Will be valid in the next page */ protected float nextMarginLeft; /** margin in x direction starting from the right. Will be valid in the next page */ protected float nextMarginRight; /** margin in y direction starting from the top. Will be valid in the next page */ protected float nextMarginTop; /** margin in y direction starting from the bottom. Will be valid in the next page */ protected float nextMarginBottom; /** The duration of the page */ protected int duration=-1; // negative values will indicate no duration /** The page transition */ protected PdfTransition transition=null; protected PdfDictionary pageAA = null; /** Holds value of property strictImageSequence. */ private boolean strictImageSequence = false; /** Holds the type of the last element, that has been added to the document. */ private int lastElementType = -1; private boolean isNewPagePending; protected int markPoint; // constructors /** * Constructs a new PDF document. * @throws DocumentException on error */ public PdfDocument() throws DocumentException { super(); addProducer(); addCreationDate(); } // listener methods /** * Adds a PdfWriter to the PdfDocument. * * @param writer the PdfWriter that writes everything * what is added to this document to an outputstream. * @throws DocumentException on error */ public void addWriter(PdfWriter writer) throws DocumentException { if (this.writer == null) { this.writer = writer; acroForm = new PdfAcroForm(writer); return; } throw new DocumentException("You can only add a writer to a PdfDocument once."); } /** * Sets the pagesize. * * @param pageSize the new pagesize * @return true if the page size was set */ public boolean setPageSize(Rectangle pageSize) { if (writer != null && writer.isPaused()) { return false; } nextPageSize = new Rectangle(pageSize); return true; } /** * Changes the header of this document. * * @param header the new header */ public void setHeader(HeaderFooter header) { if (writer != null && writer.isPaused()) { return; } super.setHeader(header); } /** * Resets the header of this document. */ public void resetHeader() { if (writer != null && writer.isPaused()) { return; } super.resetHeader(); } /** * Changes the footer of this document. * * @param footer the new footer */ public void setFooter(HeaderFooter footer) { if (writer != null && writer.isPaused()) { return; } super.setFooter(footer); } /** * Resets the footer of this document. */ public void resetFooter() { if (writer != null && writer.isPaused()) { return; } super.resetFooter(); } /** * Sets the page number to 0. */ public void resetPageCount() { if (writer != null && writer.isPaused()) { return; } super.resetPageCount(); } /** * Sets the page number. * * @param pageN the new page number */ public void setPageCount(int pageN) { if (writer != null && writer.isPaused()) { return; } super.setPageCount(pageN); } /** * Sets the Watermark. * * @param watermark the watermark to add * @return true if the element was added, false if not. */ public boolean add(Watermark watermark) { if (writer != null && writer.isPaused()) { return false; } this.watermark = watermark; return true; } /** * Removes the Watermark. */ public void removeWatermark() { if (writer != null && writer.isPaused()) { return; } this.watermark = null; } /** * Sets the margins. * * @param marginLeft the margin on the left * @param marginRight the margin on the right * @param marginTop the margin on the top * @param marginBottom the margin on the bottom * @return a boolean */ public boolean setMargins(float marginLeft, float marginRight, float marginTop, float marginBottom) { if (writer != null && writer.isPaused()) { return false; } nextMarginLeft = marginLeft; nextMarginRight = marginRight; nextMarginTop = marginTop; nextMarginBottom = marginBottom; return true; } protected PdfArray rotateAnnotations() { PdfArray array = new PdfArray(); int rotation = pageSize.getRotation() % 360; int currentPage = writer.getCurrentPageNumber(); for (int k = 0; k < annotations.size(); ++k) { PdfAnnotation dic = (PdfAnnotation)annotations.get(k); int page = dic.getPlaceInPage(); if (page > currentPage) { delayedAnnotations.add(dic); continue; } if (dic.isForm()) { if (!dic.isUsed()) { HashMap templates = dic.getTemplates(); if (templates != null) acroForm.addFieldTemplates(templates); } PdfFormField field = (PdfFormField)dic; if (field.getParent() == null) acroForm.addDocumentField(field.getIndirectReference()); } if (dic.isAnnotation()) { array.add(dic.getIndirectReference()); if (!dic.isUsed()) { PdfRectangle rect = (PdfRectangle)dic.get(PdfName.RECT); if (rect != null) { switch (rotation) { case 90: dic.put(PdfName.RECT, new PdfRectangle( pageSize.top() - rect.bottom(), rect.left(), pageSize.top() - rect.top(), rect.right())); break; case 180: dic.put(PdfName.RECT, new PdfRectangle( pageSize.right() - rect.left(), pageSize.top() - rect.bottom(), pageSize.right() - rect.right(), pageSize.top() - rect.top())); break; case 270: dic.put(PdfName.RECT, new PdfRectangle( rect.bottom(), pageSize.right() - rect.left(), rect.top(), pageSize.right() - rect.right())); break; } } } } if (!dic.isUsed()) { dic.setUsed(); try { writer.addToBody(dic, dic.getIndirectReference()); } catch (IOException e) { throw new ExceptionConverter(e); } } } return array; } /** * Makes a new page and sends it to the PdfWriter. * * @return a boolean * @throws DocumentException on error */ public boolean newPage() throws DocumentException { lastElementType = -1; //add by Jin-Hsia Yang isNewpage = true; //end add by Jin-Hsia Yang if (writer.getDirectContent().size() == 0 && writer.getDirectContentUnder().size() == 0 && (pageEmpty || (writer != null && writer.isPaused()))) { return false; } PdfPageEvent pageEvent = writer.getPageEvent(); if (pageEvent != null) pageEvent.onEndPage(writer, this); //Added to inform any listeners that we are moving to a new page (added by David Freels) super.newPage(); // the following 2 lines were added by Pelikan Stephan imageIndentLeft = 0; imageIndentRight = 0; // we flush the arraylist with recently written lines flushLines(); // we assemble the resources of this pages pageResources.addDefaultColorDiff(writer.getDefaultColorspace()); PdfDictionary resources = pageResources.getResources(); // we make a new page and add it to the document if (writer.getPDFXConformance() != PdfWriter.PDFXNONE) { if (thisBoxSize.containsKey("art") && thisBoxSize.containsKey("trim")) throw new PdfXConformanceException("Only one of ArtBox or TrimBox can exist in the page."); if (!thisBoxSize.containsKey("art") && !thisBoxSize.containsKey("trim")) { if (thisBoxSize.containsKey("crop")) thisBoxSize.put("trim", thisBoxSize.get("crop")); else thisBoxSize.put("trim", new PdfRectangle(pageSize, pageSize.getRotation())); } } PdfPage page; int rotation = pageSize.getRotation(); page = new PdfPage(new PdfRectangle(pageSize, rotation), thisBoxSize, resources, rotation); // we add tag info if (writer.isTagged()) page.put(PdfName.STRUCTPARENTS, new PdfNumber(writer.getCurrentPageNumber() - 1)); // we add the transitions if (this.transition!=null) { page.put(PdfName.TRANS, this.transition.getTransitionDictionary()); transition = null; } if (this.duration>0) { page.put(PdfName.DUR,new PdfNumber(this.duration)); duration = 0; } // we add the page object additional actions if (pageAA != null) { try { page.put(PdfName.AA, writer.addToBody(pageAA).getIndirectReference()); } catch (IOException ioe) { throw new ExceptionConverter(ioe); } pageAA = null; } // we check if the userunit is defined if (writer.getUserunit() > 0f) { page.put(PdfName.USERUNIT, new PdfNumber(writer.getUserunit())); } // we add the annotations if (annotations.size() > 0) { PdfArray array = rotateAnnotations(); if (array.size() != 0) page.put(PdfName.ANNOTS, array); } // we add the thumbs if (thumb != null) { page.put(PdfName.THUMB, thumb); thumb = null; } if (!open || close) { throw new PdfException("The document isn't open."); } if (text.size() > textEmptySize) text.endText(); else text = null; writer.add(page, new PdfContents(writer.getDirectContentUnder(), graphics, text, writer.getDirectContent(), pageSize)); // we initialize the new page initPage(); //add by Jin-Hsia Yang isNewpage = false; //end add by Jin-Hsia Yang return true; } // methods to open and close a document /** * Opens the document. *

* You have to open the document before you can begin to add content * to the body of the document. */ public void open() { if (!open) { super.open(); writer.open(); rootOutline = new PdfOutline(writer); currentOutline = rootOutline; } try { initPage(); } catch(DocumentException de) { throw new ExceptionConverter(de); } } void outlineTree(PdfOutline outline) throws IOException { outline.setIndirectReference(writer.getPdfIndirectReference()); if (outline.parent() != null) outline.put(PdfName.PARENT, outline.parent().indirectReference()); ArrayList kids = outline.getKids(); int size = kids.size(); for (int k = 0; k < size; ++k) outlineTree((PdfOutline)kids.get(k)); for (int k = 0; k < size; ++k) { if (k > 0) ((PdfOutline)kids.get(k)).put(PdfName.PREV, ((PdfOutline)kids.get(k - 1)).indirectReference()); if (k < size - 1) ((PdfOutline)kids.get(k)).put(PdfName.NEXT, ((PdfOutline)kids.get(k + 1)).indirectReference()); } if (size > 0) { outline.put(PdfName.FIRST, ((PdfOutline)kids.get(0)).indirectReference()); outline.put(PdfName.LAST, ((PdfOutline)kids.get(size - 1)).indirectReference()); } for (int k = 0; k < size; ++k) { PdfOutline kid = (PdfOutline)kids.get(k); writer.addToBody(kid, kid.indirectReference()); } } void writeOutlines() throws IOException { if (rootOutline.getKids().size() == 0) return; outlineTree(rootOutline); writer.addToBody(rootOutline, rootOutline.indirectReference()); } void traverseOutlineCount(PdfOutline outline) { ArrayList kids = outline.getKids(); PdfOutline parent = outline.parent(); if (kids.size() == 0) { if (parent != null) { parent.setCount(parent.getCount() + 1); } } else { for (int k = 0; k < kids.size(); ++k) { traverseOutlineCount((PdfOutline)kids.get(k)); } if (parent != null) { if (outline.isOpen()) { parent.setCount(outline.getCount() + parent.getCount() + 1); } else { parent.setCount(parent.getCount() + 1); outline.setCount(-outline.getCount()); } } } } void calculateOutlineCount() { if (rootOutline.getKids().size() == 0) return; traverseOutlineCount(rootOutline); } /** * Closes the document. * * Once all the content has been written in the body, you have to close * the body. After that nothing can be written to the body anymore. */ public void close() { if (close) { return; } try { boolean wasImage = (imageWait != null); newPage(); if (imageWait != null || wasImage) newPage(); if (annotations.size() > 0) throw new RuntimeException(annotations.size() + " annotations had invalid placement pages."); PdfPageEvent pageEvent = writer.getPageEvent(); if (pageEvent != null) pageEvent.onCloseDocument(writer, this); super.close(); writer.addLocalDestinations(localDestinations); calculateOutlineCount(); writeOutlines(); } catch(Exception e) { throw new ExceptionConverter(e); } writer.close(); } PageResources getPageResources() { return pageResources; } /** Adds a PdfPTable to the document. * @param ptable the PdfPTable to be added to the document. * @throws DocumentException on error */ void addPTable(PdfPTable ptable) throws DocumentException { ColumnText ct = new ColumnText(writer.getDirectContent()); if (currentHeight > 0) { Paragraph p = new Paragraph(); p.setLeading(0); ct.addElement(p); // if the table prefers to be on a single page, and it wouldn't //fit on the current page, start a new page. if (ptable.getKeepTogether() && !fitsPage(ptable, 0f)) newPage(); } ct.addElement(ptable); boolean he = ptable.isHeadersInEvent(); ptable.setHeadersInEvent(true); int loop = 0; while (true) { ct.setSimpleColumn(indentLeft(), indentBottom(), indentRight(), indentTop() - currentHeight); int status = ct.go(); if ((status & ColumnText.NO_MORE_TEXT) != 0) { text.moveText(0, ct.getYLine() - indentTop() + currentHeight); currentHeight = indentTop() - ct.getYLine(); break; } if (indentTop() - currentHeight == ct.getYLine()) ++loop; else loop = 0; if (loop == 3) { add(new Paragraph("ERROR: Infinite table loop")); break; } newPage(); } ptable.setHeadersInEvent(he); } /** * Gets a PdfTable object * (contributed by dperezcar@fcc.es) * @param table a high level table object * @param supportRowAdditions * @return returns a PdfTable object * @see PdfWriter#getPdfTable(Table) */ PdfTable getPdfTable(Table table, boolean supportRowAdditions) { return new PdfTable(table, indentLeft(), indentRight(), indentTop() - currentHeight, supportRowAdditions); } /** * @see PdfWriter#breakTableIfDoesntFit(PdfTable) * (contributed by dperezcar@fcc.es) * @param table Table to add * @return true if the table will be broken * @throws DocumentException */ boolean breakTableIfDoesntFit(PdfTable table) throws DocumentException { table.updateRowAdditions(); // Do we have any full page available? if (!table.hasToFitPageTable() && table.bottom() <= indentBottom) { // Then output that page add(table, true); return true; } return false; } private static class RenderingContext { int countPageBreaks = 0; float pagetop = -1; float oldHeight = -1; PdfContentByte cellGraphics = null; float lostTableBottom; float maxCellBottom; float maxCellHeight; Map rowspanMap; Map pageMap = new HashMap(); /** * A PdfPTable */ public PdfTable table; /** * Consumes the rowspan * @param c * @return a rowspan. */ public int consumeRowspan(PdfCell c) { if (c.rowspan() == 1) { return 1; } Integer i = (Integer) rowspanMap.get(c); if (i == null) { i = new Integer(c.rowspan()); } i = new Integer(i.intValue() - 1); rowspanMap.put(c, i); if (i.intValue() < 1) { return 1; } return i.intValue(); } /** * Looks at the current rowspan. * @param c * @return the current rowspan */ public int currentRowspan(PdfCell c) { Integer i = (Integer) rowspanMap.get(c); if (i == null) { return c.rowspan(); } else { return i.intValue(); } } public int cellRendered(PdfCell cell, int pageNumber) { Integer i = (Integer) pageMap.get(cell); if (i == null) { i = new Integer(1); } else { i = new Integer(i.intValue() + 1); } pageMap.put(cell, i); Integer pageInteger = new Integer(pageNumber); Set set = (Set) pageMap.get(pageInteger); if (set == null) { set = new HashSet(); pageMap.put(pageInteger, set); } set.add(cell); return i.intValue(); } public int numCellRendered(PdfCell cell) { Integer i = (Integer) pageMap.get(cell); if (i == null) { i = new Integer(0); } return i.intValue(); } public boolean isCellRenderedOnPage(PdfCell cell, int pageNumber) { Integer pageInteger = new Integer(pageNumber); Set set = (Set) pageMap.get(pageInteger); if (set != null) { return set.contains(cell); } return false; } }; private void analyzeRow(ArrayList rows, RenderingContext ctx) { ctx.maxCellBottom = indentBottom(); // determine whether row(index) is in a rowspan int rowIndex = 0; ArrayList row = (ArrayList) rows.get(rowIndex); int maxRowspan = 1; Iterator iterator = row.iterator(); while (iterator.hasNext()) { PdfCell cell = (PdfCell) iterator.next(); maxRowspan = Math.max(ctx.currentRowspan(cell), maxRowspan); } rowIndex += maxRowspan; boolean useTop = true; if (rowIndex == rows.size()) { rowIndex = rows.size() - 1; useTop = false; } if (rowIndex < 0 || rowIndex >= rows.size()) return; row = (ArrayList) rows.get(rowIndex); iterator = row.iterator(); while (iterator.hasNext()) { PdfCell cell = (PdfCell) iterator.next(); Rectangle cellRect = cell.rectangle(ctx.pagetop, indentBottom()); if (useTop) { ctx.maxCellBottom = Math.max(ctx.maxCellBottom, cellRect.top()); } else { if (ctx.currentRowspan(cell) == 1) { ctx.maxCellBottom = Math.max(ctx.maxCellBottom, cellRect.bottom()); } } } } /** * Adds a new table to * @param table Table to add. Rendered rows will be deleted after processing. * @param onlyFirstPage Render only the first full page * @throws DocumentException */ private void add(PdfTable table, boolean onlyFirstPage) throws DocumentException { // before every table, we flush all lines flushLines(); RenderingContext ctx = new RenderingContext(); ctx.pagetop = indentTop(); ctx.oldHeight = currentHeight; ctx.cellGraphics = new PdfContentByte(writer); ctx.rowspanMap = new HashMap(); ctx.table = table; // initialisation of parameters PdfCell cell; boolean tableHasToFit = table.hasToFitPageTable() ? (table.bottom() < indentBottom() && table.height() < (top() - bottom())) : false; if (pageEmpty) tableHasToFit = false; boolean cellsHaveToFit = table.hasToFitPageCells(); // drawing the table ArrayList dataCells = table.getCells(); ArrayList headercells = table.getHeaderCells(); // Check if we have removed header cells in a previous call if (headercells.size() > 0 && (dataCells.size() == 0 || dataCells.get(0) != headercells.get(0))) { ArrayList allCells = new ArrayList(dataCells.size()+headercells.size()); allCells.addAll(headercells); allCells.addAll(dataCells); dataCells = allCells; } ArrayList cells = dataCells; ArrayList rows = extractRows(cells, ctx); boolean isContinue = false; while (!cells.isEmpty()) { // initialisation of some extra parameters; ctx.lostTableBottom = 0; // loop over the cells boolean cellsShown = false; int currentGroupNumber = 0; boolean headerChecked = false; float headerHeight = 0; // draw the cells (line by line) Iterator iterator = rows.iterator(); boolean atLeastOneFits = false; while (iterator.hasNext()) { ArrayList row = (ArrayList) iterator.next(); analyzeRow(rows, ctx); renderCells(ctx, row, table.hasToFitPageCells() & atLeastOneFits); if (!mayBeRemoved(row)) { break; } consumeRowspan(row, ctx); iterator.remove(); atLeastOneFits = true; } // compose cells array list for subsequent code cells.clear(); Set opt = new HashSet(); iterator = rows.iterator(); while (iterator.hasNext()) { ArrayList row = (ArrayList) iterator.next(); Iterator cellIterator = row.iterator(); while (cellIterator.hasNext()) { cell = (PdfCell) cellIterator.next(); if (!opt.contains(cell)) { cells.add(cell); opt.add(cell); } } } tableHasToFit = false; // we paint the graphics of the table after looping through all the cells Rectangle tablerec = new Rectangle(table); tablerec.setBorder(table.border()); tablerec.setBorderWidth(table.borderWidth()); tablerec.setBorderColor(table.borderColor()); tablerec.setBackgroundColor(table.backgroundColor()); PdfContentByte under = writer.getDirectContentUnder(); under.rectangle(tablerec.rectangle(top(), indentBottom())); under.add(ctx.cellGraphics); // bugfix by Gerald Fehringer: now again add the border for the table // since it might have been covered by cell backgrounds tablerec.setBackgroundColor(null); tablerec = tablerec.rectangle(top(), indentBottom()); tablerec.setBorder(table.border()); under.rectangle(tablerec); // end bugfix ctx.cellGraphics = new PdfContentByte(null); // if the table continues on the next page if (!rows.isEmpty()) { isContinue = true; graphics.setLineWidth(table.borderWidth()); if (cellsShown && (table.border() & Rectangle.BOTTOM) == Rectangle.BOTTOM) { // Draw the bottom line // the color is set to the color of the element Color tColor = table.borderColor(); if (tColor != null) { graphics.setColorStroke(tColor); } graphics.moveTo(table.left(), Math.max(table.bottom(), indentBottom())); graphics.lineTo(table.right(), Math.max(table.bottom(), indentBottom())); graphics.stroke(); if (tColor != null) { graphics.resetRGBColorStroke(); } } // old page pageEmpty = false; float difference = ctx.lostTableBottom; // new page newPage(); ctx.countPageBreaks++; // G.F.: if something added in page event i.e. currentHeight > 0 float heightCorrection = 0; boolean somethingAdded = false; if (currentHeight > 0) { heightCorrection = 6; currentHeight += heightCorrection; somethingAdded = true; newLine(); flushLines(); indentTop = currentHeight - leading; currentHeight = 0; } else { flushLines(); } // this part repeats the table headers (if any) int size = headercells.size(); if (size > 0) { // this is the top of the headersection cell = (PdfCell) headercells.get(0); float oldTop = cell.top(0); // loop over all the cells of the table header for (int i = 0; i < size; i++) { cell = (PdfCell) headercells.get(i); // calculation of the new cellpositions cell.setTop(indentTop() - oldTop + cell.top(0)); cell.setBottom(indentTop() - oldTop + cell.bottom(0)); ctx.pagetop = cell.bottom(); // we paint the borders of the cell ctx.cellGraphics.rectangle(cell.rectangle(indentTop(), indentBottom())); // we write the text of the cell ArrayList images = cell.getImages(indentTop(), indentBottom()); for (Iterator im = images.iterator(); im.hasNext();) { cellsShown = true; Image image = (Image) im.next(); graphics.addImage(image); } lines = cell.getLines(indentTop(), indentBottom()); float cellTop = cell.top(indentTop()); text.moveText(0, cellTop-heightCorrection); float cellDisplacement = flushLines() - cellTop+heightCorrection; text.moveText(0, cellDisplacement); } currentHeight = indentTop() - ctx.pagetop + table.cellspacing(); text.moveText(0, ctx.pagetop - indentTop() - currentHeight); } else { if (somethingAdded) { ctx.pagetop = indentTop(); text.moveText(0, -table.cellspacing()); } } ctx.oldHeight = currentHeight - heightCorrection; // calculating the new positions of the table and the cells size = Math.min(cells.size(), table.columns()); int i = 0; while (i < size) { cell = (PdfCell) cells.get(i); if (cell.top(-table.cellspacing()) > ctx.lostTableBottom) { float newBottom = ctx.pagetop - difference + cell.bottom(); float neededHeight = cell.remainingHeight(); if (newBottom > ctx.pagetop - neededHeight) { difference += newBottom - (ctx.pagetop - neededHeight); } } i++; } size = cells.size(); table.setTop(indentTop()); table.setBottom(ctx.pagetop - difference + table.bottom(table.cellspacing())); for (i = 0; i < size; i++) { cell = (PdfCell) cells.get(i); float newBottom = ctx.pagetop - difference + cell.bottom(); float newTop = ctx.pagetop - difference + cell.top(-table.cellspacing()); if (newTop > indentTop() - currentHeight) { newTop = indentTop() - currentHeight; } cell.setTop(newTop ); cell.setBottom(newBottom ); } if (onlyFirstPage) { break; } } } float tableHeight = table.top() - table.bottom(); // bugfix by Adauto Martins when have more than two tables and more than one page // If continuation of table in other page (bug report #1460051) if (isContinue) { currentHeight = tableHeight; text.moveText(0, -(tableHeight - (ctx.oldHeight * 2))); } else { currentHeight = ctx.oldHeight + tableHeight; text.moveText(0, -tableHeight); } // end bugfix pageEmpty = false; if (ctx.countPageBreaks > 0) { // in case of tables covering more that one page have to have // a newPage followed to reset some internal state. Otherwise // subsequent tables are rendered incorrectly. isNewPagePending = true; } } private boolean mayBeRemoved(ArrayList row) { Iterator iterator = row.iterator(); boolean mayBeRemoved = true; while (iterator.hasNext()) { PdfCell cell = (PdfCell) iterator.next(); mayBeRemoved &= cell.mayBeRemoved(); } return mayBeRemoved; } private void consumeRowspan(ArrayList row, RenderingContext ctx) { Iterator iterator = row.iterator(); while (iterator.hasNext()) { PdfCell c = (PdfCell) iterator.next(); ctx.consumeRowspan(c); } } private ArrayList extractRows(ArrayList cells, RenderingContext ctx) { PdfCell cell; PdfCell previousCell = null; ArrayList rows = new ArrayList(); java.util.List rowCells = new ArrayList(); Iterator iterator = cells.iterator(); while (iterator.hasNext()) { cell = (PdfCell) iterator.next(); boolean isAdded = false; boolean isEndOfRow = !iterator.hasNext(); boolean isCurrentCellPartOfRow = !iterator.hasNext(); if (previousCell != null) { if (cell.left() <= previousCell.left()) { isEndOfRow = true; isCurrentCellPartOfRow = false; } } if (isCurrentCellPartOfRow) { rowCells.add(cell); isAdded = true; } if (isEndOfRow) { if (!rowCells.isEmpty()) { // add to rowlist rows.add(rowCells); } // start a new list for next line rowCells = new ArrayList(); } if (!isAdded) { rowCells.add(cell); } previousCell = cell; } if (!rowCells.isEmpty()) { rows.add(rowCells); } // fill row information with rowspan cells to get complete "scan lines" for (int i = rows.size() - 1; i >= 0; i--) { ArrayList row = (ArrayList) rows.get(i); // iterator through row for (int j = 0; j < row.size(); j++) { PdfCell c = (PdfCell) row.get(j); int rowspan = c.rowspan(); // fill in missing rowspan cells to complete "scan line" for (int k = 1; k < rowspan; k++) { ArrayList spannedRow = ((ArrayList) rows.get(i + k)); if (spannedRow.size() > j) spannedRow.add(j, c); } } } return rows; } private void renderCells(RenderingContext ctx, java.util.List cells, boolean hasToFit) throws DocumentException { PdfCell cell; Iterator iterator; if (hasToFit) { iterator = cells.iterator(); while (iterator.hasNext()) { cell = (PdfCell) iterator.next(); if (!cell.isHeader()) { if (cell.bottom() < indentBottom()) return; } } } iterator = cells.iterator(); while (iterator.hasNext()) { cell = (PdfCell) iterator.next(); if (!ctx.isCellRenderedOnPage(cell, getPageNumber())) { float correction = 0; if (ctx.numCellRendered(cell) >= 1) { correction = 1.0f; } lines = cell.getLines(ctx.pagetop, indentBottom() - correction); // if there is still text to render we render it if (lines != null && lines.size() > 0) { // we write the text float cellTop = cell.top(ctx.pagetop - ctx.oldHeight); text.moveText(0, cellTop); float cellDisplacement = flushLines() - cellTop; text.moveText(0, cellDisplacement); if (ctx.oldHeight + cellDisplacement > currentHeight) { currentHeight = ctx.oldHeight + cellDisplacement; } ctx.cellRendered(cell, getPageNumber()); } float indentBottom = Math.max(cell.bottom(), indentBottom()); Rectangle tableRect = ctx.table.rectangle(ctx.pagetop, indentBottom()); indentBottom = Math.max(tableRect.bottom(), indentBottom); // we paint the borders of the cells Rectangle cellRect = cell.rectangle(tableRect.top(), indentBottom); //cellRect.setBottom(cellRect.bottom()); if (cellRect.height() > 0) { ctx.lostTableBottom = indentBottom; ctx.cellGraphics.rectangle(cellRect); } // and additional graphics ArrayList images = cell.getImages(ctx.pagetop, indentBottom()); for (Iterator i = images.iterator(); i.hasNext();) { Image image = (Image) i.next(); graphics.addImage(image); } } } } /** * Signals that an Element was added to the Document. * * @param element the element to add * @return true if the element was added, false if not. * @throws DocumentException when a document isn't open yet, or has been closed */ public boolean add(Element element) throws DocumentException { if (writer != null && writer.isPaused()) { return false; } try { // resolves problem described in add(PdfTable) if (isNewPagePending) { isNewPagePending = false; newPage(); } switch(element.type()) { // Information (headers) case Element.HEADER: info.addkey(((Meta)element).name(), ((Meta)element).content()); break; case Element.TITLE: info.addTitle(((Meta)element).content()); break; case Element.SUBJECT: info.addSubject(((Meta)element).content()); break; case Element.KEYWORDS: info.addKeywords(((Meta)element).content()); break; case Element.AUTHOR: info.addAuthor(((Meta)element).content()); break; case Element.CREATOR: info.addCreator(((Meta)element).content()); break; case Element.PRODUCER: // you can not change the name of the producer info.addProducer(); break; case Element.CREATIONDATE: // you can not set the creation date, only reset it info.addCreationDate(); break; // content (text) case Element.CHUNK: { // if there isn't a current line available, we make one if (line == null) { carriageReturn(); } // we cast the element to a chunk PdfChunk chunk = new PdfChunk((Chunk) element, currentAction); // we try to add the chunk to the line, until we succeed { PdfChunk overflow; while ((overflow = line.add(chunk)) != null) { carriageReturn(); chunk = overflow; } } pageEmpty = false; if (chunk.isAttribute(Chunk.NEWPAGE)) { newPage(); } break; } case Element.ANCHOR: { Anchor anchor = (Anchor) element; String url = anchor.reference(); leading = anchor.leading(); if (url != null) { currentAction = new PdfAction(url); } // we process the element element.process(this); currentAction = null; break; } case Element.ANNOTATION: { if (line == null) { carriageReturn(); } Annotation annot = (Annotation) element; PdfAnnotation an = convertAnnotation(writer, annot); annotations.add(an); pageEmpty = false; break; } case Element.PHRASE: { // we cast the element to a phrase and set the leading of the document leading = ((Phrase) element).leading(); // we process the element element.process(this); break; } case Element.PARAGRAPH: { // we cast the element to a paragraph Paragraph paragraph = (Paragraph) element; float spacingBefore = paragraph.spacingBefore(); if (spacingBefore != 0) { leading = spacingBefore; carriageReturn(); if (!pageEmpty) { /* * Don't add spacing before a paragraph if it's the first * on the page */ Chunk space = new Chunk(" "); space.process(this); carriageReturn(); } } // we adjust the parameters of the document alignment = paragraph.alignment(); leading = paragraph.leading(); carriageReturn(); // we don't want to make orphans/widows if (currentHeight + line.height() + leading > indentTop() - indentBottom()) { newPage(); } // Begin added: Bonf (Marc Schneider) 2003-07-29 //carriageReturn(); // End added: Bonf (Marc Schneider) 2003-07-29 indentLeft += paragraph.indentationLeft(); indentRight += paragraph.indentationRight(); // Begin removed: Bonf (Marc Schneider) 2003-07-29 carriageReturn(); // End removed: Bonf (Marc Schneider) 2003-07-29 //add by Jin-Hsia Yang paraIndent += paragraph.indentationLeft(); //end add by Jin-Hsia Yang PdfPageEvent pageEvent = writer.getPageEvent(); if (pageEvent != null && isParagraph) pageEvent.onParagraph(writer, this, indentTop() - currentHeight); // if a paragraph has to be kept together, we wrap it in a table object if (paragraph.getKeepTogether()) { Table table = new Table(1, 1); table.setOffset(0f); table.setBorder(Table.NO_BORDER); table.setWidth(100f); table.setTableFitsPage(true); Cell cell = new Cell(paragraph); cell.setBorder(Table.NO_BORDER); //patch by Matt Benson 11/01/2002 - 14:32:00 cell.setHorizontalAlignment(paragraph.alignment()); //end patch by Matt Benson table.addCell(cell); this.add(table); break; } else // we process the paragraph element.process(this); //add by Jin-Hsia Yang and blowagie paraIndent -= paragraph.indentationLeft(); //end add by Jin-Hsia Yang and blowagie // Begin removed: Bonf (Marc Schneider) 2003-07-29 // carriageReturn(); // End removed: Bonf (Marc Schneider) 2003-07-29 float spacingAfter = paragraph.spacingAfter(); if (spacingAfter != 0) { leading = spacingAfter; carriageReturn(); if (currentHeight + line.height() + leading < indentTop() - indentBottom()) { /* * Only add spacing after a paragraph if the extra * spacing fits on the page. */ Chunk space = new Chunk(" "); space.process(this); carriageReturn(); } leading = paragraph.leading(); // restore original leading } if (pageEvent != null && isParagraph) pageEvent.onParagraphEnd(writer, this, indentTop() - currentHeight); alignment = Element.ALIGN_LEFT; indentLeft -= paragraph.indentationLeft(); indentRight -= paragraph.indentationRight(); // Begin added: Bonf (Marc Schneider) 2003-07-29 carriageReturn(); // End added: Bonf (Marc Schneider) 2003-07-29 //add by Jin-Hsia Yang //end add by Jin-Hsia Yang break; } case Element.SECTION: case Element.CHAPTER: { // Chapters and Sections only differ in their constructor // so we cast both to a Section Section section = (Section) element; boolean hasTitle = section.title() != null; // if the section is a chapter, we begin a new page if (section.isChapter()) { newPage(); } // otherwise, we begin a new line else { newLine(); } if (hasTitle) { float fith = indentTop() - currentHeight; int rotation = pageSize.getRotation(); if (rotation == 90 || rotation == 180) fith = pageSize.height() - fith; PdfDestination destination = new PdfDestination(PdfDestination.FITH, fith); while (currentOutline.level() >= section.depth()) { currentOutline = currentOutline.parent(); } PdfOutline outline = new PdfOutline(currentOutline, destination, section.getBookmarkTitle(), section.isBookmarkOpen()); currentOutline = outline; } // some values are set carriageReturn(); indentLeft += section.indentationLeft(); indentRight += section.indentationRight(); PdfPageEvent pageEvent = writer.getPageEvent(); if (pageEvent != null) if (element.type() == Element.CHAPTER) pageEvent.onChapter(writer, this, indentTop() - currentHeight, section.title()); else pageEvent.onSection(writer, this, indentTop() - currentHeight, section.depth(), section.title()); // the title of the section (if any has to be printed) if (hasTitle) { isParagraph = false; add(section.title()); isParagraph = true; } indentLeft += section.indentation(); // we process the section element.process(this); // some parameters are set back to normal again indentLeft -= section.indentationLeft() + section.indentation(); indentRight -= section.indentationRight(); if (pageEvent != null) if (element.type() == Element.CHAPTER) pageEvent.onChapterEnd(writer, this, indentTop() - currentHeight); else pageEvent.onSectionEnd(writer, this, indentTop() - currentHeight); break; } case Element.LIST: { // we cast the element to a List List list = (List) element; // we adjust the document listIndentLeft += list.indentationLeft(); indentRight += list.indentationRight(); // we process the items in the list element.process(this); // some parameters are set back to normal again listIndentLeft -= list.indentationLeft(); indentRight -= list.indentationRight(); break; } case Element.LISTITEM: { // we cast the element to a ListItem ListItem listItem = (ListItem) element; float spacingBefore = listItem.spacingBefore(); if (spacingBefore != 0) { leading = spacingBefore; carriageReturn(); if (!pageEmpty) { /* * Don't add spacing before a paragraph if it's the first * on the page */ Chunk space = new Chunk(" "); space.process(this); carriageReturn(); } } // we adjust the document alignment = listItem.alignment(); listIndentLeft += listItem.indentationLeft(); indentRight += listItem.indentationRight(); leading = listItem.leading(); carriageReturn(); // we prepare the current line to be able to show us the listsymbol line.setListItem(listItem); // we process the item element.process(this); float spacingAfter = listItem.spacingAfter(); if (spacingAfter != 0) { leading = spacingAfter; carriageReturn(); if (currentHeight + line.height() + leading < indentTop() - indentBottom()) { /* * Only add spacing after a paragraph if the extra * spacing fits on the page. */ Chunk space = new Chunk(" "); space.process(this); carriageReturn(); } leading = listItem.leading(); // restore original leading } // if the last line is justified, it should be aligned to the left // if (line.hasToBeJustified()) { // line.resetAlignment(); // } // some parameters are set back to normal again carriageReturn(); listIndentLeft -= listItem.indentationLeft(); indentRight -= listItem.indentationRight(); break; } case Element.RECTANGLE: { Rectangle rectangle = (Rectangle) element; graphics.rectangle(rectangle); pageEmpty = false; break; } case Element.PTABLE: { PdfPTable ptable = (PdfPTable)element; if (ptable.size() <= ptable.getHeaderRows()) break; //nothing to do // before every table, we add a new line and flush all lines ensureNewLine(); flushLines(); addPTable(ptable); pageEmpty = false; break; } case Element.MULTI_COLUMN_TEXT: { ensureNewLine(); flushLines(); MultiColumnText multiText = (MultiColumnText) element; float height = multiText.write(writer.getDirectContent(), this, indentTop() - currentHeight); currentHeight += height; text.moveText(0, -1f* height); pageEmpty = false; break; } case Element.TABLE : { /** * This is a list of people who worked on the Table functionality. * To see who did what, please check the CVS repository: * * Leslie Baski * Matt Benson * Francesco De Milato * David Freels * Bruno Lowagie * Veerendra Namineni * Geert Poels * Tom Ring * Paulo Soares * Gerald Fehringer * Steve Appling * Karsten Klein */ PdfTable table; if (element instanceof PdfTable) { // Already pre-rendered table = (PdfTable)element; table.updateRowAdditions(); } else if (element instanceof SimpleTable) { PdfPTable ptable = ((SimpleTable)element).createPdfPTable(); if (ptable.size() <= ptable.getHeaderRows()) break; //nothing to do // before every table, we add a new line and flush all lines ensureNewLine(); flushLines(); addPTable(ptable); pageEmpty = false; break; } else if (element instanceof Table) { try { PdfPTable ptable = ((Table)element).createPdfPTable(); if (ptable.size() <= ptable.getHeaderRows()) break; //nothing to do // before every table, we add a new line and flush all lines ensureNewLine(); flushLines(); addPTable(ptable); pageEmpty = false; break; } catch(BadElementException bee) { // constructing the PdfTable // Before the table, add a blank line using offset or default leading float offset = ((Table)element).getOffset(); if (Float.isNaN(offset)) offset = leading; carriageReturn(); lines.add(new PdfLine(indentLeft(), indentRight(), alignment, offset)); currentHeight += offset; table = getPdfTable((Table)element, false); } } else { return false; } add(table, false); break; } case Element.JPEG: case Element.IMGRAW: case Element.IMGTEMPLATE: { //carriageReturn(); suggestion by Marc Campforts add((Image) element); break; } case Element.GRAPHIC: { Graphic graphic = (Graphic) element; graphic.processAttributes(indentLeft(), indentBottom(), indentRight(), indentTop(), indentTop() - currentHeight); graphics.add(graphic); pageEmpty = false; break; } default: return false; } lastElementType = element.type(); return true; } catch(Exception e) { throw new DocumentException(e); } } // methods to add Content /** * Adds an image to the document. * @param image the Image to add * @throws PdfException on error * @throws DocumentException on error */ private void add(Image image) throws PdfException, DocumentException { if (image.hasAbsolutePosition()) { graphics.addImage(image); pageEmpty = false; return; } // if there isn't enough room for the image on this page, save it for the next page if (currentHeight != 0 && indentTop() - currentHeight - image.scaledHeight() < indentBottom()) { if (!strictImageSequence && imageWait == null) { imageWait = image; return; } newPage(); if (currentHeight != 0 && indentTop() - currentHeight - image.scaledHeight() < indentBottom()) { imageWait = image; return; } } pageEmpty = false; // avoid endless loops if (image == imageWait) imageWait = null; boolean textwrap = (image.alignment() & Image.TEXTWRAP) == Image.TEXTWRAP && !((image.alignment() & Image.MIDDLE) == Image.MIDDLE); boolean underlying = (image.alignment() & Image.UNDERLYING) == Image.UNDERLYING; float diff = leading / 2; if (textwrap) { diff += leading; } float lowerleft = indentTop() - currentHeight - image.scaledHeight() -diff; float mt[] = image.matrix(); float startPosition = indentLeft() - mt[4]; if ((image.alignment() & Image.RIGHT) == Image.RIGHT) startPosition = indentRight() - image.scaledWidth() - mt[4]; if ((image.alignment() & Image.MIDDLE) == Image.MIDDLE) startPosition = indentLeft() + ((indentRight() - indentLeft() - image.scaledWidth()) / 2) - mt[4]; if (image.hasAbsoluteX()) startPosition = image.absoluteX(); if (textwrap) { if (imageEnd < 0 || imageEnd < currentHeight + image.scaledHeight() + diff) { imageEnd = currentHeight + image.scaledHeight() + diff; } if ((image.alignment() & Image.RIGHT) == Image.RIGHT) { // indentation suggested by Pelikan Stephan imageIndentRight += image.scaledWidth() + image.indentationLeft(); } else { // indentation suggested by Pelikan Stephan imageIndentLeft += image.scaledWidth() + image.indentationRight(); } } else { if ((image.alignment() & Image.RIGHT) == Image.RIGHT) startPosition -= image.indentationRight(); else if ((image.alignment() & Image.LEFT) == Image.LEFT) startPosition += image.indentationLeft(); else if ((image.alignment() & Image.MIDDLE) == Image.MIDDLE) startPosition += image.indentationLeft() - image.indentationRight(); } graphics.addImage(image, mt[0], mt[1], mt[2], mt[3], startPosition, lowerleft - mt[5]); if (!(textwrap || underlying)) { currentHeight += image.scaledHeight() + diff; flushLines(); text.moveText(0, - (image.scaledHeight() + diff)); newLine(); } } /** * Initializes a page. *

* If the footer/header is set, it is printed. * @throws DocumentException on error */ private void initPage() throws DocumentException { // initialisation of some page objects markPoint = 0; annotations = delayedAnnotations; delayedAnnotations = new ArrayList(); pageResources = new PageResources(); writer.resetContent(); // the pagenumber is incremented pageN++; // graphics and text are initialized float oldleading = leading; int oldAlignment = alignment; if (marginMirroring && (getPageNumber() & 1) == 0) { marginRight = nextMarginLeft; marginLeft = nextMarginRight; } else { marginLeft = nextMarginLeft; marginRight = nextMarginRight; } marginTop = nextMarginTop; marginBottom = nextMarginBottom; imageEnd = -1; imageIndentRight = 0; imageIndentLeft = 0; graphics = new PdfContentByte(writer); text = new PdfContentByte(writer); text.beginText(); text.moveText(left(), top()); textEmptySize = text.size(); text.reset(); text.beginText(); leading = 16; indentBottom = 0; indentTop = 0; currentHeight = 0; // backgroundcolors, etc... pageSize = nextPageSize; thisBoxSize = new HashMap(boxSize); if (pageSize.backgroundColor() != null || pageSize.hasBorders() || pageSize.borderColor() != null) { add(pageSize); } // if there is a watermark, the watermark is added if (watermark != null) { float mt[] = watermark.matrix(); graphics.addImage(watermark, mt[0], mt[1], mt[2], mt[3], watermark.offsetX() - mt[4], watermark.offsetY() - mt[5]); } // if there is a footer, the footer is added if (footer != null) { /* Added by Edgar Leonardo Prieto Perilla */ // Avoid footer identation float tmpIndentLeft = indentLeft; float tmpIndentRight = indentRight; // Begin added: Bonf (Marc Schneider) 2003-07-29 float tmpListIndentLeft = listIndentLeft; float tmpImageIndentLeft = imageIndentLeft; float tmpImageIndentRight = imageIndentRight; // End added: Bonf (Marc Schneider) 2003-07-29 indentLeft = indentRight = 0; // Begin added: Bonf (Marc Schneider) 2003-07-29 listIndentLeft = 0; imageIndentLeft = 0; imageIndentRight = 0; // End added: Bonf (Marc Schneider) 2003-07-29 /* End Added by Edgar Leonardo Prieto Perilla */ footer.setPageNumber(pageN); leading = footer.paragraph().leading(); add(footer.paragraph()); // adding the footer limits the height indentBottom = currentHeight; text.moveText(left(), indentBottom()); flushLines(); text.moveText(-left(), -bottom()); footer.setTop(bottom(currentHeight)); footer.setBottom(bottom() - (0.75f * leading)); footer.setLeft(left()); footer.setRight(right()); graphics.rectangle(footer); indentBottom = currentHeight + leading * 2; currentHeight = 0; /* Added by Edgar Leonardo Prieto Perilla */ indentLeft = tmpIndentLeft; indentRight = tmpIndentRight; // Begin added: Bonf (Marc Schneider) 2003-07-29 listIndentLeft = tmpListIndentLeft; imageIndentLeft = tmpImageIndentLeft; imageIndentRight = tmpImageIndentRight; // End added: Bonf (Marc Schneider) 2003-07-29 /* End Added by Edgar Leonardo Prieto Perilla */ } // we move to the left/top position of the page text.moveText(left(), top()); // if there is a header, the header = added if (header != null) { /* Added by Edgar Leonardo Prieto Perilla */ // Avoid header identation float tmpIndentLeft = indentLeft; float tmpIndentRight = indentRight; // Begin added: Bonf (Marc Schneider) 2003-07-29 float tmpListIndentLeft = listIndentLeft; float tmpImageIndentLeft = imageIndentLeft; float tmpImageIndentRight = imageIndentRight; // End added: Bonf (Marc Schneider) 2003-07-29 indentLeft = indentRight = 0; // Added: Bonf listIndentLeft = 0; imageIndentLeft = 0; imageIndentRight = 0; // End added: Bonf /* End Added by Edgar Leonardo Prieto Perilla */ header.setPageNumber(pageN); leading = header.paragraph().leading(); text.moveText(0, leading); add(header.paragraph()); newLine(); indentTop = currentHeight - leading; header.setTop(top() + leading); header.setBottom(indentTop() + leading * 2 / 3); header.setLeft(left()); header.setRight(right()); graphics.rectangle(header); flushLines(); currentHeight = 0; /* Added by Edgar Leonardo Prieto Perilla */ // Restore identation indentLeft = tmpIndentLeft; indentRight = tmpIndentRight; // Begin added: Bonf (Marc Schneider) 2003-07-29 listIndentLeft = tmpListIndentLeft; imageIndentLeft = tmpImageIndentLeft; imageIndentRight = tmpImageIndentRight; // End added: Bonf (Marc Schneider) 2003-07-29 /* End Added by Edgar Leonardo Prieto Perilla */ } pageEmpty = true; // if there is an image waiting to be drawn, draw it try { if (imageWait != null) { add(imageWait); imageWait = null; } } catch(Exception e) { throw new ExceptionConverter(e); } leading = oldleading; alignment = oldAlignment; carriageReturn(); PdfPageEvent pageEvent = writer.getPageEvent(); if (pageEvent != null) { if (firstPageEvent) { pageEvent.onOpenDocument(writer, this); } pageEvent.onStartPage(writer, this); } firstPageEvent = false; } /** * If the current line is not empty or null, it is added to the arraylist * of lines and a new empty line is added. * @throws DocumentException on error */ private void carriageReturn() throws DocumentException { // the arraylist with lines may not be null if (lines == null) { lines = new ArrayList(); } // If the current line is not null if (line != null) { // we check if the end of the page is reached (bugfix by Francois Gravel) if (currentHeight + line.height() + leading < indentTop() - indentBottom()) { // if so nonempty lines are added and the heigt is augmented if (line.size() > 0) { currentHeight += line.height(); lines.add(line); pageEmpty = false; } } // if the end of the line is reached, we start a new page else { newPage(); } } if (imageEnd > -1 && currentHeight > imageEnd) { imageEnd = -1; imageIndentRight = 0; imageIndentLeft = 0; } // a new current line is constructed line = new PdfLine(indentLeft(), indentRight(), alignment, leading); } /** * Adds the current line to the list of lines and also adds an empty line. * @throws DocumentException on error */ private void newLine() throws DocumentException { lastElementType = -1; carriageReturn(); if (lines != null && lines.size() > 0) { lines.add(line); currentHeight += line.height(); } line = new PdfLine(indentLeft(), indentRight(), alignment, leading); } /** * Writes all the lines to the text-object. * * @return the displacement that was caused * @throws DocumentException on error */ private float flushLines() throws DocumentException { // checks if the ArrayList with the lines is not null if (lines == null) { return 0; } //add by Jin-Hsia Yang boolean newline=false; //end add by Jin-Hsia Yang // checks if a new Line has to be made. if (line != null && line.size() > 0) { lines.add(line); line = new PdfLine(indentLeft(), indentRight(), alignment, leading); //add by Jin-Hsia Yang newline=true; //end add by Jin-Hsia Yang } // checks if the ArrayList with the lines is empty if (lines.size() == 0) { return 0; } // initialisation of some parameters Object currentValues[] = new Object[2]; PdfFont currentFont = null; float displacement = 0; PdfLine l; Float lastBaseFactor = new Float(0); currentValues[1] = lastBaseFactor; // looping over all the lines for (Iterator i = lines.iterator(); i.hasNext(); ) { // this is a line in the loop l = (PdfLine) i.next(); if(isNewpage && newline) { // fix Ken@PDI newline=false; text.moveText(l.indentLeft() - indentLeft() + listIndentLeft + paraIndent,-l.height()); } else { text.moveText(l.indentLeft() - indentLeft() + listIndentLeft, -l.height()); } // is the line preceeded by a symbol? if (l.listSymbol() != null) { ColumnText.showTextAligned(graphics, Element.ALIGN_LEFT, new Phrase(l.listSymbol()), text.getXTLM() - l.listIndent(), text.getYTLM(), 0); } currentValues[0] = currentFont; writeLineToContent(l, text, graphics, currentValues, writer.getSpaceCharRatio()); currentFont = (PdfFont)currentValues[0]; displacement += l.height(); if (indentLeft() - listIndentLeft != l.indentLeft()) { text.moveText(indentLeft() - l.indentLeft() - listIndentLeft, 0); } } lines = new ArrayList(); return displacement; } // methods to retrieve information /** * Gets the PdfInfo-object. * * @return PdfInfo */ PdfInfo getInfo() { return info; } /** * Gets the PdfCatalog-object. * * @param pages an indirect reference to this document pages * @return PdfCatalog */ PdfCatalog getCatalog(PdfIndirectReference pages) { PdfCatalog catalog; if (rootOutline.getKids().size() > 0) { catalog = new PdfCatalog(pages, rootOutline.indirectReference(), writer); } else catalog = new PdfCatalog(pages, writer); if (openActionName != null) { PdfAction action = getLocalGotoAction(openActionName); catalog.setOpenAction(action); } else if (openActionAction != null) catalog.setOpenAction(openActionAction); if (additionalActions != null) { catalog.setAdditionalActions(additionalActions); } if (pageLabels != null) catalog.setPageLabels(pageLabels); catalog.addNames(localDestinations, documentJavaScript, documentFileAttachment, writer); catalog.setViewerPreferences(viewerPreferences); if (acroForm.isValid()) { try { catalog.setAcroForm(writer.addToBody(acroForm).getIndirectReference()); } catch (IOException e) { throw new ExceptionConverter(e); } } return catalog; } // methods concerning the layout /** * Returns the bottomvalue of a Table if it were added to this document. * * @param table the table that may or may not be added to this document * @return a bottom value */ float bottom(Table table) { // where will the table begin? float h = (currentHeight > 0) ? indentTop() - currentHeight - 2f * leading : indentTop(); // constructing a PdfTable PdfTable tmp = getPdfTable(table, false); return tmp.bottom(); } /** * Checks if a PdfPTable fits the current page of the PdfDocument. * * @param table the table that has to be checked * @param margin a certain margin * @return true if the PdfPTable fits the page, false otherwise. */ boolean fitsPage(PdfPTable table, float margin) { if (!table.isLockedWidth()) { float totalWidth = (indentRight() - indentLeft()) * table.getWidthPercentage() / 100; table.setTotalWidth(totalWidth); } // ensuring that a new line has been started. ensureNewLine(); return table.getTotalHeight() <= indentTop() - currentHeight - indentBottom() - margin; } /** * Gets the current vertical page position. * @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects * for elements that do not terminate the lines they've started because those lines will get * terminated. * @return The current vertical page position. */ public float getVerticalPosition(boolean ensureNewLine) { // ensuring that a new line has been started. if (ensureNewLine) { ensureNewLine(); } return top() - currentHeight - indentTop; } /** * Ensures that a new line has been started. */ private void ensureNewLine() { try { if ((lastElementType == Element.PHRASE) || (lastElementType == Element.CHUNK)) { newLine(); flushLines(); } } catch (DocumentException ex) { throw new ExceptionConverter(ex); } } /** * Gets the indentation on the left side. * * @return a margin */ private float indentLeft() { return left(indentLeft + listIndentLeft + imageIndentLeft); } /** * Gets the indentation on the right side. * * @return a margin */ private float indentRight() { return right(indentRight + imageIndentRight); } /** * Gets the indentation on the top side. * * @return a margin */ private float indentTop() { return top(indentTop); } /** * Gets the indentation on the bottom side. * * @return a margin */ float indentBottom() { return bottom(indentBottom); } /** * Adds a named outline to the document . * @param outline the outline to be added * @param name the name of this local destination */ void addOutline(PdfOutline outline, String name) { localDestination(name, outline.getPdfDestination()); } /** * Gets the AcroForm object. * @return the PdfAcroform object of the PdfDocument */ public PdfAcroForm getAcroForm() { return acroForm; } /** * Gets the root outline. All the outlines must be created with a parent. * The first level is created with this outline. * @return the root outline */ public PdfOutline getRootOutline() { return rootOutline; } /** * Writes a text line to the document. It takes care of all the attributes. *

* Before entering the line position must have been established and the * text argument must be in text object scope (beginText()). * @param line the line to be written * @param text the PdfContentByte where the text will be written to * @param graphics the PdfContentByte where the graphics will be written to * @param currentValues the current font and extra spacing values * @param ratio * @throws DocumentException on error */ void writeLineToContent(PdfLine line, PdfContentByte text, PdfContentByte graphics, Object currentValues[], float ratio) throws DocumentException { PdfFont currentFont = (PdfFont)(currentValues[0]); float lastBaseFactor = ((Float)(currentValues[1])).floatValue(); PdfChunk chunk; int numberOfSpaces; int lineLen; boolean isJustified; float hangingCorrection = 0; float hScale = 1; float lastHScale = Float.NaN; float baseWordSpacing = 0; float baseCharacterSpacing = 0; numberOfSpaces = line.numberOfSpaces(); lineLen = line.toString().length(); // does the line need to be justified? isJustified = line.hasToBeJustified() && (numberOfSpaces != 0 || lineLen > 1); if (isJustified) { if (line.isNewlineSplit() && line.widthLeft() >= (lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1))) { if (line.isRTL()) { text.moveText(line.widthLeft() - lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1), 0); } baseWordSpacing = ratio * lastBaseFactor; baseCharacterSpacing = lastBaseFactor; } else { float width = line.widthLeft(); PdfChunk last = line.getChunk(line.size() - 1); if (last != null) { String s = last.toString(); char c; if (s.length() > 0 && hangingPunctuation.indexOf((c = s.charAt(s.length() - 1))) >= 0) { float oldWidth = width; width += last.font().width(c) * 0.4f; hangingCorrection = width - oldWidth; } } float baseFactor = width / (ratio * numberOfSpaces + lineLen - 1); baseWordSpacing = ratio * baseFactor; baseCharacterSpacing = baseFactor; lastBaseFactor = baseFactor; } } int lastChunkStroke = line.getLastStrokeChunk(); int chunkStrokeIdx = 0; float xMarker = text.getXTLM(); float baseXMarker = xMarker; float yMarker = text.getYTLM(); boolean adjustMatrix = false; // looping over all the chunks in 1 line for (Iterator j = line.iterator(); j.hasNext(); ) { chunk = (PdfChunk) j.next(); Color color = chunk.color(); hScale = 1; if (chunkStrokeIdx <= lastChunkStroke) { float width; if (isJustified) { width = chunk.getWidthCorrected(baseCharacterSpacing, baseWordSpacing); } else width = chunk.width(); if (chunk.isStroked()) { PdfChunk nextChunk = line.getChunk(chunkStrokeIdx + 1); if (chunk.isAttribute(Chunk.BACKGROUND)) { float subtract = lastBaseFactor; if (nextChunk != null && nextChunk.isAttribute(Chunk.BACKGROUND)) subtract = 0; if (nextChunk == null) subtract += hangingCorrection; float fontSize = chunk.font().size(); float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize); float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize); Object bgr[] = (Object[])chunk.getAttribute(Chunk.BACKGROUND); graphics.setColorFill((Color)bgr[0]); float extra[] = (float[])bgr[1]; graphics.rectangle(xMarker - extra[0], yMarker + descender - extra[1] + chunk.getTextRise(), width - subtract + extra[0] + extra[2], ascender - descender + extra[1] + extra[3]); graphics.fill(); graphics.setGrayFill(0); } if (chunk.isAttribute(Chunk.UNDERLINE)) { float subtract = lastBaseFactor; if (nextChunk != null && nextChunk.isAttribute(Chunk.UNDERLINE)) subtract = 0; if (nextChunk == null) subtract += hangingCorrection; Object unders[][] = (Object[][])chunk.getAttribute(Chunk.UNDERLINE); Color scolor = null; for (int k = 0; k < unders.length; ++k) { Object obj[] = unders[k]; scolor = (Color)obj[0]; float ps[] = (float[])obj[1]; if (scolor == null) scolor = color; if (scolor != null) graphics.setColorStroke(scolor); float fsize = chunk.font().size(); graphics.setLineWidth(ps[0] + fsize * ps[1]); float shift = ps[2] + fsize * ps[3]; int cap2 = (int)ps[4]; if (cap2 != 0) graphics.setLineCap(cap2); graphics.moveTo(xMarker, yMarker + shift); graphics.lineTo(xMarker + width - subtract, yMarker + shift); graphics.stroke(); if (scolor != null) graphics.resetGrayStroke(); if (cap2 != 0) graphics.setLineCap(0); } graphics.setLineWidth(1); } if (chunk.isAttribute(Chunk.ACTION)) { float subtract = lastBaseFactor; if (nextChunk != null && nextChunk.isAttribute(Chunk.ACTION)) subtract = 0; if (nextChunk == null) subtract += hangingCorrection; text.addAnnotation(new PdfAnnotation(writer, xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size(), (PdfAction)chunk.getAttribute(Chunk.ACTION))); } if (chunk.isAttribute(Chunk.REMOTEGOTO)) { float subtract = lastBaseFactor; if (nextChunk != null && nextChunk.isAttribute(Chunk.REMOTEGOTO)) subtract = 0; if (nextChunk == null) subtract += hangingCorrection; Object obj[] = (Object[])chunk.getAttribute(Chunk.REMOTEGOTO); String filename = (String)obj[0]; if (obj[1] instanceof String) remoteGoto(filename, (String)obj[1], xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size()); else remoteGoto(filename, ((Integer)obj[1]).intValue(), xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size()); } if (chunk.isAttribute(Chunk.LOCALGOTO)) { float subtract = lastBaseFactor; if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALGOTO)) subtract = 0; if (nextChunk == null) subtract += hangingCorrection; localGoto((String)chunk.getAttribute(Chunk.LOCALGOTO), xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size()); } if (chunk.isAttribute(Chunk.LOCALDESTINATION)) { float subtract = lastBaseFactor; if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALDESTINATION)) subtract = 0; if (nextChunk == null) subtract += hangingCorrection; localDestination((String)chunk.getAttribute(Chunk.LOCALDESTINATION), new PdfDestination(PdfDestination.XYZ, xMarker, yMarker + chunk.font().size(), 0)); } if (chunk.isAttribute(Chunk.GENERICTAG)) { float subtract = lastBaseFactor; if (nextChunk != null && nextChunk.isAttribute(Chunk.GENERICTAG)) subtract = 0; if (nextChunk == null) subtract += hangingCorrection; Rectangle rect = new Rectangle(xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size()); PdfPageEvent pev = writer.getPageEvent(); if (pev != null) pev.onGenericTag(writer, this, rect, (String)chunk.getAttribute(Chunk.GENERICTAG)); } if (chunk.isAttribute(Chunk.PDFANNOTATION)) { float subtract = lastBaseFactor; if (nextChunk != null && nextChunk.isAttribute(Chunk.PDFANNOTATION)) subtract = 0; if (nextChunk == null) subtract += hangingCorrection; float fontSize = chunk.font().size(); float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize); float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize); PdfAnnotation annot = PdfFormField.shallowDuplicate((PdfAnnotation)chunk.getAttribute(Chunk.PDFANNOTATION)); annot.put(PdfName.RECT, new PdfRectangle(xMarker, yMarker + descender, xMarker + width - subtract, yMarker + ascender)); text.addAnnotation(annot); } float params[] = (float[])chunk.getAttribute(Chunk.SKEW); Float hs = (Float)chunk.getAttribute(Chunk.HSCALE); if (params != null || hs != null) { float b = 0, c = 0; if (params != null) { b = params[0]; c = params[1]; } if (hs != null) hScale = hs.floatValue(); text.setTextMatrix(hScale, b, c, 1, xMarker, yMarker); } if (chunk.isImage()) { Image image = chunk.getImage(); float matrix[] = image.matrix(); matrix[Image.CX] = xMarker + chunk.getImageOffsetX() - matrix[Image.CX]; matrix[Image.CY] = yMarker + chunk.getImageOffsetY() - matrix[Image.CY]; graphics.addImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); text.moveText(xMarker + lastBaseFactor + image.scaledWidth() - text.getXTLM(), 0); } } xMarker += width; ++chunkStrokeIdx; } if (chunk.font().compareTo(currentFont) != 0) { currentFont = chunk.font(); text.setFontAndSize(currentFont.getFont(), currentFont.size()); } float rise = 0; Object textRender[] = (Object[])chunk.getAttribute(Chunk.TEXTRENDERMODE); int tr = 0; float strokeWidth = 1; Color strokeColor = null; Float fr = (Float)chunk.getAttribute(Chunk.SUBSUPSCRIPT); if (textRender != null) { tr = ((Integer)textRender[0]).intValue() & 3; if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL) text.setTextRenderingMode(tr); if (tr == PdfContentByte.TEXT_RENDER_MODE_STROKE || tr == PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE) { strokeWidth = ((Float)textRender[1]).floatValue(); if (strokeWidth != 1) text.setLineWidth(strokeWidth); strokeColor = (Color)textRender[2]; if (strokeColor == null) strokeColor = color; if (strokeColor != null) text.setColorStroke(strokeColor); } } if (fr != null) rise = fr.floatValue(); if (color != null) text.setColorFill(color); if (rise != 0) text.setTextRise(rise); if (chunk.isImage()) { adjustMatrix = true; } // If it is a CJK chunk or Unicode TTF we will have to simulate the // space adjustment. else if (isJustified && numberOfSpaces > 0 && chunk.isSpecialEncoding()) { if (hScale != lastHScale) { lastHScale = hScale; text.setWordSpacing(baseWordSpacing / hScale); text.setCharacterSpacing(baseCharacterSpacing / hScale); } String s = chunk.toString(); int idx = s.indexOf(' '); if (idx < 0) text.showText(chunk.toString()); else { float spaceCorrection = - baseWordSpacing * 1000f / chunk.font.size() / hScale; PdfTextArray textArray = new PdfTextArray(s.substring(0, idx)); int lastIdx = idx; while ((idx = s.indexOf(' ', lastIdx + 1)) >= 0) { textArray.add(spaceCorrection); textArray.add(s.substring(lastIdx, idx)); lastIdx = idx; } textArray.add(spaceCorrection); textArray.add(s.substring(lastIdx)); text.showText(textArray); } } else { if (isJustified && hScale != lastHScale) { lastHScale = hScale; text.setWordSpacing(baseWordSpacing / hScale); text.setCharacterSpacing(baseCharacterSpacing / hScale); } text.showText(chunk.toString()); } if (rise != 0) text.setTextRise(0); if (color != null) text.resetRGBColorFill(); if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL) text.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL); if (strokeColor != null) text.resetRGBColorStroke(); if (strokeWidth != 1) text.setLineWidth(1); if (chunk.isAttribute(Chunk.SKEW) || chunk.isAttribute(Chunk.HSCALE)) { adjustMatrix = true; text.setTextMatrix(xMarker, yMarker); } } if (isJustified) { text.setWordSpacing(0); text.setCharacterSpacing(0); if (line.isNewlineSplit()) lastBaseFactor = 0; } if (adjustMatrix) text.moveText(baseXMarker - text.getXTLM(), 0); currentValues[0] = currentFont; currentValues[1] = new Float(lastBaseFactor); } /** * Implements a link to other part of the document. The jump will * be made to a local destination with the same name, that must exist. * @param name the name for this link * @param llx the lower left x corner of the activation area * @param lly the lower left y corner of the activation area * @param urx the upper right x corner of the activation area * @param ury the upper right y corner of the activation area */ void localGoto(String name, float llx, float lly, float urx, float ury) { PdfAction action = getLocalGotoAction(name); annotations.add(new PdfAnnotation(writer, llx, lly, urx, ury, action)); } PdfAction getLocalGotoAction(String name) { PdfAction action; Object obj[] = (Object[])localDestinations.get(name); if (obj == null) obj = new Object[3]; if (obj[0] == null) { if (obj[1] == null) { obj[1] = writer.getPdfIndirectReference(); } action = new PdfAction((PdfIndirectReference)obj[1]); obj[0] = action; localDestinations.put(name, obj); } else { action = (PdfAction)obj[0]; } return action; } /** * The local destination to where a local goto with the same * name will jump to. * @param name the name of this local destination * @param destination the PdfDestination with the jump coordinates * @return true if the local destination was added, * false if a local destination with the same name * already existed */ boolean localDestination(String name, PdfDestination destination) { Object obj[] = (Object[])localDestinations.get(name); if (obj == null) obj = new Object[3]; if (obj[2] != null) return false; obj[2] = destination; localDestinations.put(name, obj); destination.addPage(writer.getCurrentPage()); return true; } /** * Implements a link to another document. * @param filename the filename for the remote document * @param name the name to jump to * @param llx the lower left x corner of the activation area * @param lly the lower left y corner of the activation area * @param urx the upper right x corner of the activation area * @param ury the upper right y corner of the activation area */ void remoteGoto(String filename, String name, float llx, float lly, float urx, float ury) { annotations.add(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, name))); } /** * Implements a link to another document. * @param filename the filename for the remote document * @param page the page to jump to * @param llx the lower left x corner of the activation area * @param lly the lower left y corner of the activation area * @param urx the upper right x corner of the activation area * @param ury the upper right y corner of the activation area */ void remoteGoto(String filename, int page, float llx, float lly, float urx, float ury) { writer.addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, page))); } /** Sets the viewer preferences as the sum of several constants. * @param preferences the viewer preferences * @see PdfWriter#setViewerPreferences */ public void setViewerPreferences(int preferences) { viewerPreferences |= preferences; } /** Implements an action in an area. * @param action the PdfAction * @param llx the lower left x corner of the activation area * @param lly the lower left y corner of the activation area * @param urx the upper right x corner of the activation area * @param ury the upper right y corner of the activation area */ void setAction(PdfAction action, float llx, float lly, float urx, float ury) { writer.addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action)); } void setOpenAction(String name) { openActionName = name; openActionAction = null; } void setOpenAction(PdfAction action) { openActionAction = action; openActionName = null; } void addAdditionalAction(PdfName actionType, PdfAction action) { if (additionalActions == null) { additionalActions = new PdfDictionary(); } if (action == null) additionalActions.remove(actionType); else additionalActions.put(actionType, action); if (additionalActions.size() == 0) additionalActions = null; } void setPageLabels(PdfPageLabels pageLabels) { this.pageLabels = pageLabels; } void addJavaScript(PdfAction js) { if (js.get(PdfName.JS) == null) throw new RuntimeException("Only JavaScript actions are allowed."); try { documentJavaScript.add(writer.addToBody(js).getIndirectReference()); } catch (IOException e) { throw new ExceptionConverter(e); } } void setCropBoxSize(Rectangle crop) { setBoxSize("crop", crop); } void setBoxSize(String boxName, Rectangle size) { if (size == null) boxSize.remove(boxName); else boxSize.put(boxName, new PdfRectangle(size)); } void addCalculationOrder(PdfFormField formField) { acroForm.addCalculationOrder(formField); } /** * Gives the size of a trim, art, crop or bleed box, or null if not defined. * @param boxName crop, trim, art or bleed */ Rectangle getBoxSize(String boxName) { PdfRectangle r = (PdfRectangle)thisBoxSize.get(boxName); if (r != null) return r.getRectangle(); return null; } void setSigFlags(int f) { acroForm.setSigFlags(f); } void addFormFieldRaw(PdfFormField field) { annotations.add(field); ArrayList kids = field.getKids(); if (kids != null) { for (int k = 0; k < kids.size(); ++k) addFormFieldRaw((PdfFormField)kids.get(k)); } } void addAnnotation(PdfAnnotation annot) { pageEmpty = false; if (annot.isForm()) { PdfFormField field = (PdfFormField)annot; if (field.getParent() == null) addFormFieldRaw(field); } else annotations.add(annot); } /** * Sets the display duration for the page (for presentations) * @param seconds the number of seconds to display the page */ void setDuration(int seconds) { if (seconds > 0) this.duration=seconds; else this.duration=-1; } /** * Sets the transition for the page * @param transition the PdfTransition object */ void setTransition(PdfTransition transition) { this.transition=transition; } void setPageAction(PdfName actionType, PdfAction action) { if (pageAA == null) { pageAA = new PdfDictionary(); } pageAA.put(actionType, action); } /** Getter for property strictImageSequence. * @return Value of property strictImageSequence. * */ boolean isStrictImageSequence() { return this.strictImageSequence; } /** Setter for property strictImageSequence. * @param strictImageSequence New value of property strictImageSequence. * */ void setStrictImageSequence(boolean strictImageSequence) { this.strictImageSequence = strictImageSequence; } void setPageEmpty(boolean pageEmpty) { this.pageEmpty = pageEmpty; } /** * Method added by Pelikan Stephan * @see com.lowagie.text.DocListener#clearTextWrap() */ public void clearTextWrap() throws DocumentException { super.clearTextWrap(); float tmpHeight = imageEnd - currentHeight; if (line != null) { tmpHeight += line.height(); } if ((imageEnd > -1) && (tmpHeight > 0)) { carriageReturn(); currentHeight += tmpHeight; } } ArrayList getDocumentJavaScript() { return documentJavaScript; } /** * @see com.lowagie.text.DocListener#setMarginMirroring(boolean) */ public boolean setMarginMirroring(boolean MarginMirroring) { if (writer != null && writer.isPaused()) { return false; } return super.setMarginMirroring(MarginMirroring); } void setThumbnail(Image image) throws PdfException, DocumentException { thumb = writer.getImageReference(writer.addDirectImageSimple(image)); } void addFileAttachment(String description, PdfFileSpecification fs) throws IOException { if (description == null) description = ""; fs.put(PdfName.DESC, new PdfString(description, PdfObject.TEXT_UNICODE)); if (description.length() == 0) description = "Unnamed"; String fn = PdfEncodings.convertToString(new PdfString(description, PdfObject.TEXT_UNICODE).getBytes(), null); int k = 0; while (documentFileAttachment.containsKey(fn)) { ++k; fn = PdfEncodings.convertToString(new PdfString(description + " " + k, PdfObject.TEXT_UNICODE).getBytes(), null); } documentFileAttachment.put(fn, fs.getReference()); } HashMap getDocumentFileAttachment() { return documentFileAttachment; } static PdfAnnotation convertAnnotation(PdfWriter writer, Annotation annot) throws IOException { switch(annot.annotationType()) { case Annotation.URL_NET: return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction((URL) annot.attributes().get(Annotation.URL))); case Annotation.URL_AS_STRING: return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction((String) annot.attributes().get(Annotation.FILE))); case Annotation.FILE_DEST: return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction((String) annot.attributes().get(Annotation.FILE), (String) annot.attributes().get(Annotation.DESTINATION))); case Annotation.SCREEN: boolean sparams[] = (boolean[])annot.attributes().get(Annotation.PARAMETERS); String fname = (String) annot.attributes().get(Annotation.FILE); String mimetype = (String) annot.attributes().get(Annotation.MIMETYPE); PdfFileSpecification fs; if (sparams[0]) fs = PdfFileSpecification.fileEmbedded(writer, fname, fname, null); else fs = PdfFileSpecification.fileExtern(writer, fname); PdfAnnotation ann = PdfAnnotation.createScreen(writer, new Rectangle(annot.llx(), annot.lly(), annot.urx(), annot.ury()), fname, fs, mimetype, sparams[1]); return ann; case Annotation.FILE_PAGE: return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction((String) annot.attributes().get(Annotation.FILE), ((Integer) annot.attributes().get(Annotation.PAGE)).intValue())); case Annotation.NAMED_DEST: return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction(((Integer) annot.attributes().get(Annotation.NAMED)).intValue())); case Annotation.LAUNCH: return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction((String) annot.attributes().get(Annotation.APPLICATION),(String) annot.attributes().get(Annotation.PARAMETERS),(String) annot.attributes().get(Annotation.OPERATION),(String) annot.attributes().get(Annotation.DEFAULTDIR))); default: PdfDocument doc = writer.getPdfDocument(); if (doc.line == null) return null; PdfAnnotation an = new PdfAnnotation(writer, annot.llx(doc.indentRight() - doc.line.widthLeft()), annot.lly(doc.indentTop() - doc.currentHeight), annot.urx(doc.indentRight() - doc.line.widthLeft() + 20), annot.ury(doc.indentTop() - doc.currentHeight - 20), new PdfString(annot.title()), new PdfString(annot.content())); return an; } } /** * @return an XmpMetadata byte array */ public byte[] createXmpMetadata() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { XmpWriter xmp = new XmpWriter(baos, getInfo()); xmp.close(); } catch(IOException ioe) { ioe.printStackTrace(); } return baos.toByteArray(); } int getMarkPoint() { return markPoint; } void incMarkPoint() { ++markPoint; } }