Save This Page
Home » iText-src-2.1.3 » com.lowagie » text » pdf » [javadoc | source]
    1   /*
    2    * $Id: ColumnText.java 3373 2008-05-12 16:21:24Z xlv $
    3    *
    4    * Copyright 2001, 2002 by Paulo Soares.
    5    *
    6    * The contents of this file are subject to the Mozilla Public License Version 1.1
    7    * (the "License"); you may not use this file except in compliance with the License.
    8    * You may obtain a copy of the License at http://www.mozilla.org/MPL/
    9    *
   10    * Software distributed under the License is distributed on an "AS IS" basis,
   11    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
   12    * for the specific language governing rights and limitations under the License.
   13    *
   14    * The Original Code is 'iText, a free JAVA-PDF library'.
   15    *
   16    * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
   17    * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
   18    * All Rights Reserved.
   19    * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
   20    * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
   21    *
   22    * Contributor(s): all the names of the contributors are added in the source code
   23    * where applicable.
   24    *
   25    * Alternatively, the contents of this file may be used under the terms of the
   26    * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
   27    * provisions of LGPL are applicable instead of those above.  If you wish to
   28    * allow use of your version of this file only under the terms of the LGPL
   29    * License and not to allow others to use your version of this file under
   30    * the MPL, indicate your decision by deleting the provisions above and
   31    * replace them with the notice and other provisions required by the LGPL.
   32    * If you do not delete the provisions above, a recipient may use your version
   33    * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
   34    *
   35    * This library is free software; you can redistribute it and/or modify it
   36    * under the terms of the MPL as stated above or under the terms of the GNU
   37    * Library General Public License as published by the Free Software Foundation;
   38    * either version 2 of the License, or any later version.
   39    *
   40    * This library is distributed in the hope that it will be useful, but WITHOUT
   41    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   42    * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
   43    * details.
   44    *
   45    * If you didn't download this code from the following link, you should check if
   46    * you aren't using an obsolete version:
   47    * http://www.lowagie.com/iText/
   48    */
   49   
   50   package com.lowagie.text.pdf;
   51   import java.util.ArrayList;
   52   import java.util.Iterator;
   53   import java.util.LinkedList;
   54   import java.util.Stack;
   55   
   56   import com.lowagie.text.Chunk;
   57   import com.lowagie.text.DocumentException;
   58   import com.lowagie.text.Element;
   59   import com.lowagie.text.ExceptionConverter;
   60   import com.lowagie.text.Image;
   61   import com.lowagie.text.ListItem;
   62   import com.lowagie.text.Paragraph;
   63   import com.lowagie.text.Phrase;
   64   import com.lowagie.text.SimpleTable;
   65   import com.lowagie.text.pdf.draw.DrawInterface;
   66   
   67   /**
   68    * Formats text in a columnwise form. The text is bound
   69    * on the left and on the right by a sequence of lines. This allows the column
   70    * to have any shape, not only rectangular.
   71    * <P>
   72    * Several parameters can be set like the first paragraph line indent and
   73    * extra space between paragraphs.
   74    * <P>
   75    * A call to the method <CODE>go</CODE> will return one of the following
   76    * situations: the column ended or the text ended.
   77    * <P>
   78    * I the column ended, a new column definition can be loaded with the method
   79    * <CODE>setColumns</CODE> and the method <CODE>go</CODE> can be called again.
   80    * <P>
   81    * If the text ended, more text can be loaded with <CODE>addText</CODE>
   82    * and the method <CODE>go</CODE> can be called again.<BR>
   83    * The only limitation is that one or more complete paragraphs must be loaded
   84    * each time.
   85    * <P>
   86    * Full bidirectional reordering is supported. If the run direction is
   87    * <CODE>PdfWriter.RUN_DIRECTION_RTL</CODE> the meaning of the horizontal
   88    * alignments and margins is mirrored.
   89    * @author Paulo Soares (psoares@consiste.pt)
   90    */
   91   
   92   public class ColumnText {
   93       /** Eliminate the arabic vowels */    
   94       public static final int AR_NOVOWEL = ArabicLigaturizer.ar_novowel;
   95       /** Compose the tashkeel in the ligatures. */    
   96       public static final int AR_COMPOSEDTASHKEEL = ArabicLigaturizer.ar_composedtashkeel;
   97       /** Do some extra double ligatures. */    
   98       public static final int AR_LIG = ArabicLigaturizer.ar_lig;
   99       /**
  100        * Digit shaping option: Replace European digits (U+0030...U+0039) by Arabic-Indic digits.
  101        */
  102       public static final int DIGITS_EN2AN = ArabicLigaturizer.DIGITS_EN2AN;
  103       
  104       /**
  105        * Digit shaping option: Replace Arabic-Indic digits by European digits (U+0030...U+0039).
  106        */
  107       public static final int DIGITS_AN2EN = ArabicLigaturizer.DIGITS_AN2EN;
  108       
  109       /**
  110        * Digit shaping option:
  111        * Replace European digits (U+0030...U+0039) by Arabic-Indic digits
  112        * if the most recent strongly directional character
  113        * is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
  114        * The initial state at the start of the text is assumed to be not an Arabic,
  115        * letter, so European digits at the start of the text will not change.
  116        * Compare to DIGITS_ALEN2AN_INIT_AL.
  117        */
  118       public static final int DIGITS_EN2AN_INIT_LR = ArabicLigaturizer.DIGITS_EN2AN_INIT_LR;
  119       
  120       /**
  121        * Digit shaping option:
  122        * Replace European digits (U+0030...U+0039) by Arabic-Indic digits
  123        * if the most recent strongly directional character
  124        * is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
  125        * The initial state at the start of the text is assumed to be an Arabic,
  126        * letter, so European digits at the start of the text will change.
  127        * Compare to DIGITS_ALEN2AN_INT_LR.
  128        */
  129       public static final int DIGITS_EN2AN_INIT_AL = ArabicLigaturizer.DIGITS_EN2AN_INIT_AL;
  130       
  131       /**
  132        * Digit type option: Use Arabic-Indic digits (U+0660...U+0669).
  133        */
  134       public static final int DIGIT_TYPE_AN = ArabicLigaturizer.DIGIT_TYPE_AN;
  135       
  136       /**
  137        * Digit type option: Use Eastern (Extended) Arabic-Indic digits (U+06f0...U+06f9).
  138        */
  139       public static final int DIGIT_TYPE_AN_EXTENDED = ArabicLigaturizer.DIGIT_TYPE_AN_EXTENDED;
  140       
  141       protected int runDirection = PdfWriter.RUN_DIRECTION_DEFAULT;
  142       
  143       /** the space char ratio */
  144       public static final float GLOBAL_SPACE_CHAR_RATIO = 0;
  145       
  146       /** Initial value of the status. */
  147       public static final int START_COLUMN = 0;
  148       
  149       /** Signals that there is no more text available. */
  150       public static final int NO_MORE_TEXT = 1;
  151       
  152       /** Signals that there is no more column. */
  153       public static final int NO_MORE_COLUMN = 2;
  154       
  155       /** The column is valid. */
  156       protected static final int LINE_STATUS_OK = 0;
  157       
  158       /** The line is out the column limits. */
  159       protected static final int LINE_STATUS_OFFLIMITS = 1;
  160       
  161       /** The line cannot fit this column position. */
  162       protected static final int LINE_STATUS_NOLINE = 2;
  163       
  164       /** Upper bound of the column. */
  165       protected float maxY;
  166       
  167       /** Lower bound of the column. */
  168       protected float minY;
  169       
  170       protected float leftX;
  171       
  172       protected float rightX;
  173       
  174       /** The column alignment. Default is left alignment. */
  175       protected int alignment = Element.ALIGN_LEFT;
  176       
  177       /** The left column bound. */
  178       protected ArrayList leftWall;
  179       
  180       /** The right column bound. */
  181       protected ArrayList rightWall;
  182       
  183       /** The chunks that form the text. */
  184   //    protected ArrayList chunks = new ArrayList();
  185       protected BidiLine bidiLine;
  186       
  187       /** The current y line location. Text will be written at this line minus the leading. */
  188       protected float yLine;
  189       
  190       /** The leading for the current line. */
  191       protected float currentLeading = 16;
  192       
  193       /** The fixed text leading. */
  194       protected float fixedLeading = 16;
  195       
  196       /** The text leading that is multiplied by the biggest font size in the line. */
  197       protected float multipliedLeading = 0;
  198       
  199       /** The <CODE>PdfContent</CODE> where the text will be written to. */
  200       protected PdfContentByte canvas;
  201       
  202       protected PdfContentByte[] canvases;
  203       
  204       /** The line status when trying to fit a line to a column. */
  205       protected int lineStatus;
  206       
  207       /** The first paragraph line indent. */
  208       protected float indent = 0;
  209       
  210       /** The following paragraph lines indent. */
  211       protected float followingIndent = 0;
  212       
  213       /** The right paragraph lines indent. */
  214       protected float rightIndent = 0;
  215       
  216       /** The extra space between paragraphs. */
  217       protected float extraParagraphSpace = 0;
  218       
  219       /** The width of the line when the column is defined as a simple rectangle. */
  220       protected float rectangularWidth = -1;
  221       
  222       protected boolean rectangularMode = false;
  223       /** Holds value of property spaceCharRatio. */
  224       private float spaceCharRatio = GLOBAL_SPACE_CHAR_RATIO;
  225   
  226       private boolean lastWasNewline = true;
  227       
  228       /** Holds value of property linesWritten. */
  229       private int linesWritten;
  230       
  231       private float firstLineY;
  232       private boolean firstLineYDone = false;
  233       
  234       /** Holds value of property arabicOptions. */
  235       private int arabicOptions = 0;
  236       
  237       protected float descender;
  238       
  239       protected boolean composite = false;
  240       
  241       protected ColumnText compositeColumn;
  242       
  243       protected LinkedList compositeElements;
  244       
  245       protected int listIdx = 0;
  246       
  247       private boolean splittedRow;
  248       
  249       protected Phrase waitPhrase;
  250       
  251       /** if true, first line height is adjusted so that the max ascender touches the top */
  252       private boolean useAscender = false;
  253   
  254       /**
  255        * Creates a <CODE>ColumnText</CODE>.
  256        * @param canvas the place where the text will be written to. Can
  257        * be a template.
  258        */
  259       public ColumnText(PdfContentByte canvas) {
  260           this.canvas = canvas;
  261       }
  262       
  263       /** Creates an independent duplicated of the instance <CODE>org</CODE>.
  264        * @param org the original <CODE>ColumnText</CODE>
  265        * @return the duplicated
  266        */    
  267       public static ColumnText duplicate(ColumnText org) {
  268           ColumnText ct = new ColumnText(null);
  269           ct.setACopy(org);
  270           return ct;
  271       }
  272       
  273       /** Makes this instance an independent copy of <CODE>org</CODE>.
  274        * @param org the original <CODE>ColumnText</CODE>
  275        * @return itself
  276        */    
  277       public ColumnText setACopy(ColumnText org) {
  278           setSimpleVars(org);
  279           if (org.bidiLine != null)
  280               bidiLine = new BidiLine(org.bidiLine);
  281           return this;
  282       }
  283       
  284       protected void setSimpleVars(ColumnText org) {
  285           maxY = org.maxY;
  286           minY = org.minY;
  287           alignment = org.alignment;
  288           leftWall = null;
  289           if (org.leftWall != null)
  290               leftWall = new ArrayList(org.leftWall);
  291           rightWall = null;
  292           if (org.rightWall != null)
  293               rightWall = new ArrayList(org.rightWall);
  294           yLine = org.yLine;
  295           currentLeading = org.currentLeading;
  296           fixedLeading = org.fixedLeading;
  297           multipliedLeading = org.multipliedLeading;
  298           canvas = org.canvas;
  299           canvases = org.canvases;
  300           lineStatus = org.lineStatus;
  301           indent = org.indent;
  302           followingIndent = org.followingIndent;
  303           rightIndent = org.rightIndent;
  304           extraParagraphSpace = org.extraParagraphSpace;
  305           rectangularWidth = org.rectangularWidth;
  306           rectangularMode = org.rectangularMode;
  307           spaceCharRatio = org.spaceCharRatio;
  308           lastWasNewline = org.lastWasNewline;
  309           linesWritten = org.linesWritten;
  310           arabicOptions = org.arabicOptions;
  311           runDirection = org.runDirection;
  312           descender = org.descender;
  313           composite = org.composite;
  314           splittedRow = org.splittedRow;
  315           if (org.composite) {
  316               compositeElements = new LinkedList(org.compositeElements);
  317               if (splittedRow) {
  318                   PdfPTable table = (PdfPTable)compositeElements.getFirst();
  319                   compositeElements.set(0, new PdfPTable(table));
  320               }
  321               if (org.compositeColumn != null)
  322                   compositeColumn = duplicate(org.compositeColumn);
  323           }
  324           listIdx = org.listIdx;
  325           firstLineY = org.firstLineY;
  326           leftX = org.leftX;
  327           rightX = org.rightX;
  328           firstLineYDone = org.firstLineYDone;
  329           waitPhrase = org.waitPhrase;
  330           useAscender = org.useAscender;
  331           filledWidth = org.filledWidth;
  332           adjustFirstLine = org.adjustFirstLine;
  333       }
  334       
  335       private void addWaitingPhrase() {
  336           if (bidiLine == null && waitPhrase != null) {
  337               bidiLine = new BidiLine();
  338               for (Iterator j = waitPhrase.getChunks().iterator(); j.hasNext();) {
  339                   bidiLine.addChunk(new PdfChunk((Chunk)j.next(), null));
  340               }
  341               waitPhrase = null;
  342           }
  343       }
  344       
  345       /**
  346        * Adds a <CODE>Phrase</CODE> to the current text array.
  347        * Will not have any effect if addElement() was called before.
  348        * @param phrase the text
  349        */
  350       public void addText(Phrase phrase) {
  351           if (phrase == null || composite)
  352               return;
  353           addWaitingPhrase();
  354           if (bidiLine == null) {
  355               waitPhrase = phrase;
  356               return;
  357           }
  358           for (Iterator j = phrase.getChunks().iterator(); j.hasNext();) {
  359               bidiLine.addChunk(new PdfChunk((Chunk)j.next(), null));
  360           }
  361       }
  362       
  363       /**
  364        * Replaces the current text array with this <CODE>Phrase</CODE>.
  365        * Anything added previously with addElement() is lost.
  366        * @param phrase the text
  367        */
  368       public void setText(Phrase phrase) {
  369           bidiLine = null;
  370           composite = false;
  371           compositeColumn = null;
  372           compositeElements = null;
  373           listIdx = 0;
  374           splittedRow = false;
  375           waitPhrase = phrase;
  376       }
  377       
  378       /**
  379        * Adds a <CODE>Chunk</CODE> to the current text array.
  380        * Will not have any effect if addElement() was called before.
  381        * @param chunk the text
  382        */
  383       public void addText(Chunk chunk) {
  384           if (chunk == null || composite)
  385               return;
  386           addText(new Phrase(chunk));
  387       }
  388       
  389       /**
  390        * Adds an element. Elements supported are <CODE>Paragraph</CODE>,
  391        * <CODE>List</CODE>, <CODE>PdfPTable</CODE>, <CODE>Image</CODE> and
  392        * <CODE>Graphic</CODE>.
  393        * <p>
  394        * It removes all the text placed with <CODE>addText()</CODE>.
  395        * @param element the <CODE>Element</CODE>
  396        */    
  397       public void addElement(Element element) {
  398           if (element == null)
  399               return;
  400           if (element instanceof Image) {
  401               Image img = (Image)element;
  402               PdfPTable t = new PdfPTable(1);
  403               float w = img.getWidthPercentage();
  404               if (w == 0) {
  405                   t.setTotalWidth(img.getScaledWidth());
  406                   t.setLockedWidth(true);
  407               }
  408               else
  409                   t.setWidthPercentage(w);
  410               t.setSpacingAfter(img.getSpacingAfter());
  411               t.setSpacingBefore(img.getSpacingBefore());
  412               switch (img.getAlignment()) {
  413                   case Image.LEFT:
  414                       t.setHorizontalAlignment(Element.ALIGN_LEFT);
  415                       break;
  416                   case Image.RIGHT:
  417                       t.setHorizontalAlignment(Element.ALIGN_RIGHT);
  418                       break;
  419                   default:
  420                       t.setHorizontalAlignment(Element.ALIGN_CENTER);
  421                       break;
  422               }
  423               PdfPCell c = new PdfPCell(img, true);
  424               c.setPadding(0);
  425               c.setBorder(img.getBorder());
  426               c.setBorderColor(img.getBorderColor());
  427               c.setBorderWidth(img.getBorderWidth());
  428               c.setBackgroundColor(img.getBackgroundColor());
  429               t.addCell(c);
  430               element = t;
  431           }
  432           if (element.type() == Element.CHUNK) {
  433           	element = new Paragraph((Chunk)element);
  434           }
  435           else if (element.type() == Element.PHRASE) {
  436           	element = new Paragraph((Phrase)element);
  437           }
  438           if (element instanceof SimpleTable) {
  439           	try {
  440   				element = ((SimpleTable)element).createPdfPTable();
  441   			} catch (DocumentException e) {
  442   				throw new IllegalArgumentException("Element not allowed.");
  443   			}
  444           }
  445           else if (element.type() != Element.PARAGRAPH && element.type() != Element.LIST && element.type() != Element.PTABLE && element.type() != Element.YMARK)
  446               throw new IllegalArgumentException("Element not allowed.");
  447           if (!composite) {
  448               composite = true;
  449               compositeElements = new LinkedList();
  450               bidiLine = null;
  451               waitPhrase = null;
  452           }
  453           compositeElements.add(element);
  454       }
  455       
  456       /**
  457        * Converts a sequence of lines representing one of the column bounds into
  458        * an internal format.
  459        * <p>
  460        * Each array element will contain a <CODE>float[4]</CODE> representing
  461        * the line x = ax + b.
  462        * @param cLine the column array
  463        * @return the converted array
  464        */
  465       protected ArrayList convertColumn(float cLine[]) {
  466           if (cLine.length < 4)
  467               throw new RuntimeException("No valid column line found.");
  468           ArrayList cc = new ArrayList();
  469           for (int k = 0; k < cLine.length - 2; k += 2) {
  470               float x1 = cLine[k];
  471               float y1 = cLine[k + 1];
  472               float x2 = cLine[k + 2];
  473               float y2 = cLine[k + 3];
  474               if (y1 == y2)
  475                   continue;
  476               // x = ay + b
  477               float a = (x1 - x2) / (y1 - y2);
  478               float b = x1 - a * y1;
  479               float r[] = new float[4];
  480               r[0] = Math.min(y1, y2);
  481               r[1] = Math.max(y1, y2);
  482               r[2] = a;
  483               r[3] = b;
  484               cc.add(r);
  485               maxY = Math.max(maxY, r[1]);
  486               minY = Math.min(minY, r[0]);
  487           }
  488           if (cc.isEmpty())
  489               throw new RuntimeException("No valid column line found.");
  490           return cc;
  491       }
  492       
  493       /**
  494        * Finds the intersection between the <CODE>yLine</CODE> and the column. It will
  495        * set the <CODE>lineStatus</CODE> appropriately.
  496        * @param wall the column to intersect
  497        * @return the x coordinate of the intersection
  498        */
  499       protected float findLimitsPoint(ArrayList wall) {
  500           lineStatus = LINE_STATUS_OK;
  501           if (yLine < minY || yLine > maxY) {
  502               lineStatus = LINE_STATUS_OFFLIMITS;
  503               return 0;
  504           }
  505           for (int k = 0; k < wall.size(); ++k) {
  506               float r[] = (float[])wall.get(k);
  507               if (yLine < r[0] || yLine > r[1])
  508                   continue;
  509               return r[2] * yLine + r[3];
  510           }
  511           lineStatus = LINE_STATUS_NOLINE;
  512           return 0;
  513       }
  514       
  515       /**
  516        * Finds the intersection between the <CODE>yLine</CODE> and the two
  517        * column bounds. It will set the <CODE>lineStatus</CODE> appropriately.
  518        * @return a <CODE>float[2]</CODE>with the x coordinates of the intersection
  519        */
  520       protected float[] findLimitsOneLine() {
  521           float x1 = findLimitsPoint(leftWall);
  522           if (lineStatus == LINE_STATUS_OFFLIMITS || lineStatus == LINE_STATUS_NOLINE)
  523               return null;
  524           float x2 = findLimitsPoint(rightWall);
  525           if (lineStatus == LINE_STATUS_NOLINE)
  526               return null;
  527           return new float[]{x1, x2};
  528       }
  529       
  530       /**
  531        * Finds the intersection between the <CODE>yLine</CODE>,
  532        * the <CODE>yLine-leading</CODE>and the two
  533        * column bounds. It will set the <CODE>lineStatus</CODE> appropriately.
  534        * @return a <CODE>float[4]</CODE>with the x coordinates of the intersection
  535        */
  536       protected float[] findLimitsTwoLines() {
  537           boolean repeat = false;
  538           for (;;) {
  539               if (repeat && currentLeading == 0)
  540                   return null;
  541               repeat = true;
  542               float x1[] = findLimitsOneLine();
  543               if (lineStatus == LINE_STATUS_OFFLIMITS)
  544                   return null;
  545               yLine -= currentLeading;
  546               if (lineStatus == LINE_STATUS_NOLINE) {
  547                   continue;
  548               }
  549               float x2[] = findLimitsOneLine();
  550               if (lineStatus == LINE_STATUS_OFFLIMITS)
  551                   return null;
  552               if (lineStatus == LINE_STATUS_NOLINE) {
  553                   yLine -= currentLeading;
  554                   continue;
  555               }
  556               if (x1[0] >= x2[1] || x2[0] >= x1[1])
  557                   continue;
  558               return new float[]{x1[0], x1[1], x2[0], x2[1]};
  559           }
  560       }
  561       
  562       /**
  563        * Sets the columns bounds. Each column bound is described by a
  564        * <CODE>float[]</CODE> with the line points [x1,y1,x2,y2,...].
  565        * The array must have at least 4 elements.
  566        * @param leftLine the left column bound
  567        * @param rightLine the right column bound
  568        */
  569       public void setColumns(float leftLine[], float rightLine[]) {
  570           maxY = -10e20f;
  571           minY = 10e20f;
  572           rightWall = convertColumn(rightLine);
  573           leftWall = convertColumn(leftLine);
  574           rectangularWidth = -1;
  575           rectangularMode = false;
  576       }
  577       
  578       /**
  579        * Simplified method for rectangular columns.
  580        * @param phrase a <CODE>Phrase</CODE>
  581        * @param llx the lower left x corner
  582        * @param lly the lower left y corner
  583        * @param urx the upper right x corner
  584        * @param ury the upper right y corner
  585        * @param leading the leading
  586        * @param alignment the column alignment
  587        */
  588       public void setSimpleColumn(Phrase phrase, float llx, float lly, float urx, float ury, float leading, int alignment) {
  589           addText(phrase);
  590           setSimpleColumn(llx, lly, urx, ury, leading, alignment);
  591       }
  592       
  593       /**
  594        * Simplified method for rectangular columns.
  595        * @param llx the lower left x corner
  596        * @param lly the lower left y corner
  597        * @param urx the upper right x corner
  598        * @param ury the upper right y corner
  599        * @param leading the leading
  600        * @param alignment the column alignment
  601        */
  602       public void setSimpleColumn(float llx, float lly, float urx, float ury, float leading, int alignment) {
  603           setLeading(leading);
  604           this.alignment = alignment;
  605           setSimpleColumn(llx, lly, urx, ury);
  606       }
  607       
  608       /**
  609        * Simplified method for rectangular columns.
  610        * @param llx
  611        * @param lly
  612        * @param urx
  613        * @param ury
  614        */
  615       public void setSimpleColumn(float llx, float lly, float urx, float ury) {
  616           leftX = Math.min(llx, urx);
  617           maxY = Math.max(lly, ury);
  618           minY = Math.min(lly, ury);
  619           rightX = Math.max(llx, urx);
  620           yLine = maxY;
  621           rectangularWidth = rightX - leftX;
  622           if (rectangularWidth < 0)
  623               rectangularWidth = 0;
  624           rectangularMode = true;
  625       }
  626       /**
  627        * Sets the leading to fixed
  628        * @param leading the leading
  629        */
  630       public void setLeading(float leading) {
  631           fixedLeading = leading;
  632           multipliedLeading = 0;
  633       }
  634       
  635       /**
  636        * Sets the leading fixed and variable. The resultant leading will be
  637        * fixedLeading+multipliedLeading*maxFontSize where maxFontSize is the
  638        * size of the biggest font in the line.
  639        * @param fixedLeading the fixed leading
  640        * @param multipliedLeading the variable leading
  641        */
  642       public void setLeading(float fixedLeading, float multipliedLeading) {
  643           this.fixedLeading = fixedLeading;
  644           this.multipliedLeading = multipliedLeading;
  645       }
  646       
  647       /**
  648        * Gets the fixed leading
  649        * @return the leading
  650        */
  651       public float getLeading() {
  652           return fixedLeading;
  653       }
  654       
  655       /**
  656        * Gets the variable leading
  657        * @return the leading
  658        */
  659       public float getMultipliedLeading() {
  660           return multipliedLeading;
  661       }
  662       
  663       /**
  664        * Sets the yLine. The line will be written to yLine-leading.
  665        * @param yLine the yLine
  666        */
  667       public void setYLine(float yLine) {
  668           this.yLine = yLine;
  669       }
  670       
  671       /**
  672        * Gets the yLine.
  673        * @return the yLine
  674        */
  675       public float getYLine() {
  676           return yLine;
  677       }
  678       
  679       /**
  680        * Sets the alignment.
  681        * @param alignment the alignment
  682        */
  683       public void setAlignment(int alignment) {
  684           this.alignment = alignment;
  685       }
  686       
  687       /**
  688        * Gets the alignment.
  689        * @return the alignment
  690        */
  691       public int getAlignment() {
  692           return alignment;
  693       }
  694       
  695       /**
  696        * Sets the first paragraph line indent.
  697        * @param indent the indent
  698        */
  699       public void setIndent(float indent) {
  700           this.indent = indent;
  701           lastWasNewline = true;
  702       }
  703       
  704       /**
  705        * Gets the first paragraph line indent.
  706        * @return the indent
  707        */
  708       public float getIndent() {
  709           return indent;
  710       }
  711       
  712       /**
  713        * Sets the following paragraph lines indent.
  714        * @param indent the indent
  715        */
  716       public void setFollowingIndent(float indent) {
  717           this.followingIndent = indent;
  718           lastWasNewline = true;
  719       }
  720       
  721       /**
  722        * Gets the following paragraph lines indent.
  723        * @return the indent
  724        */
  725       public float getFollowingIndent() {
  726           return followingIndent;
  727       }
  728       
  729       /**
  730        * Sets the right paragraph lines indent.
  731        * @param indent the indent
  732        */
  733       public void setRightIndent(float indent) {
  734           this.rightIndent = indent;
  735           lastWasNewline = true;
  736       }
  737       
  738       /**
  739        * Gets the right paragraph lines indent.
  740        * @return the indent
  741        */
  742       public float getRightIndent() {
  743           return rightIndent;
  744       }
  745       
  746       /**
  747        * Outputs the lines to the document. It is equivalent to <CODE>go(false)</CODE>.
  748        * @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
  749        * and/or <CODE>NO_MORE_COLUMN</CODE>
  750        * @throws DocumentException on error
  751        */
  752       public int go() throws DocumentException {
  753           return go(false);
  754       }
  755       
  756       /**
  757        * Outputs the lines to the document. The output can be simulated.
  758        * @param simulate <CODE>true</CODE> to simulate the writing to the document
  759        * @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
  760        * and/or <CODE>NO_MORE_COLUMN</CODE>
  761        * @throws DocumentException on error
  762        */
  763       public int go(boolean simulate) throws DocumentException {
  764           if (composite)
  765               return goComposite(simulate);
  766           addWaitingPhrase();
  767           if (bidiLine == null)
  768               return NO_MORE_TEXT;
  769           descender = 0;
  770           linesWritten = 0;
  771           boolean dirty = false;
  772           float ratio = spaceCharRatio;
  773           Object currentValues[] = new Object[2];
  774           PdfFont currentFont = null;
  775           Float lastBaseFactor = new Float(0);
  776           currentValues[1] = lastBaseFactor;
  777           PdfDocument pdf = null;
  778           PdfContentByte graphics = null;
  779           PdfContentByte text = null;
  780           firstLineY = Float.NaN;
  781           int localRunDirection = PdfWriter.RUN_DIRECTION_NO_BIDI;
  782           if (runDirection != PdfWriter.RUN_DIRECTION_DEFAULT)
  783               localRunDirection = runDirection;
  784           if (canvas != null) {
  785               graphics = canvas;
  786               pdf = canvas.getPdfDocument();
  787               text = canvas.getDuplicate();
  788           }
  789           else if (!simulate)
  790               throw new NullPointerException("ColumnText.go with simulate==false and text==null.");
  791           if (!simulate) {
  792               if (ratio == GLOBAL_SPACE_CHAR_RATIO)
  793                   ratio = text.getPdfWriter().getSpaceCharRatio();
  794               else if (ratio < 0.001f)
  795                   ratio = 0.001f;
  796           }
  797           float firstIndent = 0;
  798           
  799           int status = 0;
  800           if (rectangularMode) {
  801               for (;;) {
  802                   firstIndent = (lastWasNewline ? indent : followingIndent);
  803                   if (rectangularWidth <= firstIndent + rightIndent) {
  804                       status = NO_MORE_COLUMN;
  805                       if (bidiLine.isEmpty())
  806                           status |= NO_MORE_TEXT;
  807                       break;
  808                   }
  809                   if (bidiLine.isEmpty()) {
  810                       status = NO_MORE_TEXT;
  811                       break;
  812                   }
  813                   PdfLine line = bidiLine.processLine(leftX, rectangularWidth - firstIndent - rightIndent, alignment, localRunDirection, arabicOptions);
  814                   if (line == null) {
  815                       status = NO_MORE_TEXT;
  816                       break;
  817                   }
  818                   float maxSize = line.getMaxSizeSimple();
  819                   if (isUseAscender() && Float.isNaN(firstLineY)) {
  820                       currentLeading = line.getAscender();
  821                   }
  822                   else {
  823                       currentLeading = fixedLeading + maxSize * multipliedLeading;
  824                   }
  825                   if (yLine > maxY || yLine - currentLeading < minY ) {
  826                       status = NO_MORE_COLUMN;
  827                       bidiLine.restore();
  828                       break;
  829                   }
  830                   yLine -= currentLeading;
  831                   if (!simulate && !dirty) {
  832                       text.beginText();
  833                       dirty = true;
  834                   }
  835                   if (Float.isNaN(firstLineY)) {
  836                       firstLineY = yLine;
  837                   }
  838                   updateFilledWidth(rectangularWidth - line.widthLeft());
  839                   if (!simulate) {
  840                       currentValues[0] = currentFont;
  841                       text.setTextMatrix(leftX + (line.isRTL() ? rightIndent : firstIndent) + line.indentLeft(), yLine);
  842                       pdf.writeLineToContent(line, text, graphics, currentValues, ratio);
  843                       currentFont = (PdfFont)currentValues[0];
  844                   }
  845                   lastWasNewline = line.isNewlineSplit();
  846                   yLine -= line.isNewlineSplit() ? extraParagraphSpace : 0;
  847                   ++linesWritten;
  848                   descender = line.getDescender();
  849               }
  850           }
  851           else {
  852               currentLeading = fixedLeading;
  853               for (;;) {
  854                   firstIndent = (lastWasNewline ? indent : followingIndent);
  855                   float yTemp = yLine;
  856                   float xx[] = findLimitsTwoLines();
  857                   if (xx == null) {
  858                       status = NO_MORE_COLUMN;
  859                       if (bidiLine.isEmpty())
  860                           status |= NO_MORE_TEXT;
  861                       yLine = yTemp;
  862                       break;
  863                   }
  864                   if (bidiLine.isEmpty()) {
  865                       status = NO_MORE_TEXT;
  866                       yLine = yTemp;
  867                       break;
  868                   }
  869                   float x1 = Math.max(xx[0], xx[2]);
  870                   float x2 = Math.min(xx[1], xx[3]);
  871                   if (x2 - x1 <= firstIndent + rightIndent)
  872                       continue;
  873                   if (!simulate && !dirty) {
  874                       text.beginText();
  875                       dirty = true;
  876                   }
  877                   PdfLine line = bidiLine.processLine(x1, x2 - x1 - firstIndent - rightIndent, alignment, localRunDirection, arabicOptions);
  878                   if (line == null) {
  879                       status = NO_MORE_TEXT;
  880                       yLine = yTemp;
  881                       break;
  882                   }
  883                   if (!simulate) {
  884                       currentValues[0] = currentFont;
  885                       text.setTextMatrix(x1 + (line.isRTL() ? rightIndent : firstIndent) + line.indentLeft(), yLine);
  886                       pdf.writeLineToContent(line, text, graphics, currentValues, ratio);
  887                       currentFont = (PdfFont)currentValues[0];
  888                   }
  889                   lastWasNewline = line.isNewlineSplit();
  890                   yLine -= line.isNewlineSplit() ? extraParagraphSpace : 0;
  891                   ++linesWritten;
  892                   descender = line.getDescender();
  893               }
  894           }
  895           if (dirty) {
  896               text.endText();
  897               canvas.add(text);
  898           }
  899           return status;
  900       }
  901       
  902       /**
  903        * Sets the extra space between paragraphs.
  904        * @return the extra space between paragraphs
  905        */
  906       public float getExtraParagraphSpace() {
  907           return extraParagraphSpace;
  908       }
  909       
  910       /**
  911        * Sets the extra space between paragraphs.
  912        * @param extraParagraphSpace the extra space between paragraphs
  913        */
  914       public void setExtraParagraphSpace(float extraParagraphSpace) {
  915           this.extraParagraphSpace = extraParagraphSpace;
  916       }
  917       
  918       /**
  919        * Clears the chunk array. A call to <CODE>go()</CODE> will always return
  920        * NO_MORE_TEXT.
  921        */
  922       public void clearChunks() {
  923           if (bidiLine != null)
  924               bidiLine.clearChunks();
  925       }
  926       
  927       /** Gets the space/character extra spacing ratio for
  928        * fully justified text.
  929        * @return the space/character extra spacing ratio
  930        */    
  931       public float getSpaceCharRatio() {
  932           return spaceCharRatio;
  933       }
  934       
  935       /** Sets the ratio between the extra word spacing and the extra character spacing
  936        * when the text is fully justified.
  937        * Extra word spacing will grow <CODE>spaceCharRatio</CODE> times more than extra character spacing.
  938        * If the ratio is <CODE>PdfWriter.NO_SPACE_CHAR_RATIO</CODE> then the extra character spacing
  939        * will be zero.
  940        * @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing
  941        */
  942       public void setSpaceCharRatio(float spaceCharRatio) {
  943           this.spaceCharRatio = spaceCharRatio;
  944       }
  945   
  946       /** Sets the run direction. 
  947        * @param runDirection the run direction
  948        */    
  949       public void setRunDirection(int runDirection) {
  950           if (runDirection < PdfWriter.RUN_DIRECTION_DEFAULT || runDirection > PdfWriter.RUN_DIRECTION_RTL)
  951               throw new RuntimeException("Invalid run direction: " + runDirection);
  952           this.runDirection = runDirection;
  953       }
  954       
  955       /** Gets the run direction.
  956        * @return the run direction
  957        */    
  958       public int getRunDirection() {
  959           return runDirection;
  960       }
  961       
  962       /** Gets the number of lines written.
  963        * @return the number of lines written
  964        */
  965       public int getLinesWritten() {
  966           return this.linesWritten;
  967       }
  968       
  969       /** Gets the arabic shaping options.
  970        * @return the arabic shaping options
  971        */
  972       public int getArabicOptions() {
  973           return this.arabicOptions;
  974       }
  975       
  976       /** Sets the arabic shaping options. The option can be AR_NOVOWEL,
  977        * AR_COMPOSEDTASHKEEL and AR_LIG.
  978        * @param arabicOptions the arabic shaping options
  979        */
  980       public void setArabicOptions(int arabicOptions) {
  981           this.arabicOptions = arabicOptions;
  982       }
  983       
  984       /** Gets the biggest descender value of the last line written.
  985        * @return the biggest descender value of the last line written
  986        */    
  987       public float getDescender() {
  988           return descender;
  989       }
  990       
  991       /** Gets the width that the line will occupy after writing.
  992        * Only the width of the first line is returned.
  993        * @param phrase the <CODE>Phrase</CODE> containing the line
  994        * @param runDirection the run direction
  995        * @param arabicOptions the options for the arabic shaping
  996        * @return the width of the line
  997        */    
  998       public static float getWidth(Phrase phrase, int runDirection, int arabicOptions) {
  999           ColumnText ct = new ColumnText(null);
 1000           ct.addText(phrase);
 1001           ct.addWaitingPhrase();
 1002           PdfLine line = ct.bidiLine.processLine(0, 20000, Element.ALIGN_LEFT, runDirection, arabicOptions);
 1003           if (line == null)
 1004               return 0;
 1005           else
 1006               return 20000 - line.widthLeft();
 1007       }
 1008       
 1009       /** Gets the width that the line will occupy after writing.
 1010        * Only the width of the first line is returned.
 1011        * @param phrase the <CODE>Phrase</CODE> containing the line
 1012        * @return the width of the line
 1013        */    
 1014       public static float getWidth(Phrase phrase) {
 1015           return getWidth(phrase, PdfWriter.RUN_DIRECTION_NO_BIDI, 0);
 1016       }
 1017       
 1018       /** Shows a line of text. Only the first line is written.
 1019        * @param canvas where the text is to be written to
 1020        * @param alignment the alignment. It is not influenced by the run direction
 1021        * @param phrase the <CODE>Phrase</CODE> with the text
 1022        * @param x the x reference position
 1023        * @param y the y reference position
 1024        * @param rotation the rotation to be applied in degrees counterclockwise
 1025        * @param runDirection the run direction
 1026        * @param arabicOptions the options for the arabic shaping
 1027        */    
 1028       public static void showTextAligned(PdfContentByte canvas, int alignment, Phrase phrase, float x, float y, float rotation, int runDirection, int arabicOptions) {
 1029           if (alignment != Element.ALIGN_LEFT && alignment != Element.ALIGN_CENTER
 1030               && alignment != Element.ALIGN_RIGHT)
 1031               alignment = Element.ALIGN_LEFT;
 1032           canvas.saveState();
 1033           ColumnText ct = new ColumnText(canvas);
 1034           if (rotation == 0) {
 1035               if (alignment == Element.ALIGN_LEFT)
 1036                   ct.setSimpleColumn(phrase, x, y - 1, 20000 + x, y + 2, 2, alignment);
 1037               else if (alignment == Element.ALIGN_RIGHT)
 1038                   ct.setSimpleColumn(phrase, x-20000, y-1, x, y+2, 2, alignment);
 1039               else
 1040                   ct.setSimpleColumn(phrase, x-20000, y-1, x+20000, y+2, 2, alignment);
 1041           }
 1042           else {
 1043               double alpha = rotation * Math.PI / 180.0;
 1044               float cos = (float)Math.cos(alpha);
 1045               float sin = (float)Math.sin(alpha);
 1046               canvas.concatCTM(cos, sin, -sin, cos, x, y);
 1047               if (alignment == Element.ALIGN_LEFT)
 1048                   ct.setSimpleColumn(phrase, 0, -1, 20000, 2, 2, alignment);
 1049               else if (alignment == Element.ALIGN_RIGHT)
 1050                   ct.setSimpleColumn(phrase, -20000, -1, 0, 2, 2, alignment);
 1051               else
 1052                   ct.setSimpleColumn(phrase, -20000, -1, 20000, 2, 2, alignment);
 1053           }
 1054           if (runDirection == PdfWriter.RUN_DIRECTION_RTL) {
 1055               if (alignment == Element.ALIGN_LEFT)
 1056                   alignment = Element.ALIGN_RIGHT;
 1057               else if (alignment == Element.ALIGN_RIGHT)
 1058                   alignment = Element.ALIGN_LEFT;
 1059           }
 1060           ct.setAlignment(alignment);
 1061           ct.setArabicOptions(arabicOptions);
 1062           ct.setRunDirection(runDirection);
 1063           try {
 1064               ct.go();
 1065           }
 1066           catch (DocumentException e) {
 1067               throw new ExceptionConverter(e);
 1068           }
 1069           canvas.restoreState();
 1070       }
 1071   
 1072       /** Shows a line of text. Only the first line is written.
 1073        * @param canvas where the text is to be written to
 1074        * @param alignment the alignment
 1075        * @param phrase the <CODE>Phrase</CODE> with the text
 1076        * @param x the x reference position
 1077        * @param y the y reference position
 1078        * @param rotation the rotation to be applied in degrees counterclockwise
 1079        */    
 1080       public static void showTextAligned(PdfContentByte canvas, int alignment, Phrase phrase, float x, float y, float rotation) {
 1081           showTextAligned(canvas, alignment, phrase, x, y, rotation, PdfWriter.RUN_DIRECTION_NO_BIDI, 0);
 1082       }
 1083   
 1084       protected int goComposite(boolean simulate) throws DocumentException {
 1085           if (!rectangularMode)
 1086               throw new DocumentException("Irregular columns are not supported in composite mode.");
 1087           linesWritten = 0;
 1088           descender = 0;
 1089           boolean firstPass = adjustFirstLine;
 1090           main_loop:
 1091           while (true) {
 1092               if (compositeElements.isEmpty())
 1093                   return NO_MORE_TEXT;
 1094               Element element = (Element)compositeElements.getFirst();
 1095               if (element.type() == Element.PARAGRAPH) {
 1096                   Paragraph para = (Paragraph)element;
 1097                   int status = 0;
 1098                   for (int keep = 0; keep < 2; ++keep) {
 1099                       float lastY = yLine;
 1100                       boolean createHere = false;
 1101                       if (compositeColumn == null) {
 1102                           compositeColumn = new ColumnText(canvas);
 1103                           compositeColumn.setUseAscender(firstPass ? useAscender : false);
 1104                           compositeColumn.setAlignment(para.getAlignment());
 1105                           compositeColumn.setIndent(para.getIndentationLeft() + para.getFirstLineIndent());
 1106                           compositeColumn.setExtraParagraphSpace(para.getExtraParagraphSpace());
 1107                           compositeColumn.setFollowingIndent(para.getIndentationLeft());
 1108                           compositeColumn.setRightIndent(para.getIndentationRight());
 1109                           compositeColumn.setLeading(para.getLeading(), para.getMultipliedLeading());
 1110                           compositeColumn.setRunDirection(runDirection);
 1111                           compositeColumn.setArabicOptions(arabicOptions);
 1112                           compositeColumn.setSpaceCharRatio(spaceCharRatio);
 1113                           compositeColumn.addText(para);
 1114                           if (!firstPass) {
 1115                               yLine -= para.spacingBefore();
 1116                           }
 1117                           createHere = true;
 1118                       }
 1119                       compositeColumn.leftX = leftX;
 1120                       compositeColumn.rightX = rightX;
 1121                       compositeColumn.yLine = yLine;
 1122                       compositeColumn.rectangularWidth = rectangularWidth;
 1123                       compositeColumn.rectangularMode = rectangularMode;
 1124                       compositeColumn.minY = minY;
 1125                       compositeColumn.maxY = maxY;
 1126                       boolean keepCandidate = (para.getKeepTogether() && createHere && !firstPass);
 1127                       status = compositeColumn.go(simulate || (keepCandidate && keep == 0));
 1128                       updateFilledWidth(compositeColumn.filledWidth);
 1129                       if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
 1130                           compositeColumn = null;
 1131                           yLine = lastY;
 1132                           return NO_MORE_COLUMN;
 1133                       }
 1134                       if (simulate || !keepCandidate)
 1135                           break;
 1136                       if (keep == 0) {
 1137                           compositeColumn = null;
 1138                           yLine = lastY;
 1139                       }
 1140                   }
 1141                   firstPass = false;
 1142                   yLine = compositeColumn.yLine;
 1143                   linesWritten += compositeColumn.linesWritten;
 1144                   descender = compositeColumn.descender;
 1145                   if ((status & NO_MORE_TEXT) != 0) {
 1146                       compositeColumn = null;
 1147                       compositeElements.removeFirst();
 1148                       yLine -= para.spacingAfter();
 1149                   }
 1150                   if ((status & NO_MORE_COLUMN) != 0) {
 1151                       return NO_MORE_COLUMN;
 1152                   }
 1153               }
 1154               else if (element.type() == Element.LIST) {
 1155                   com.lowagie.text.List list = (com.lowagie.text.List)element;
 1156                   ArrayList items = list.getItems();
 1157                   ListItem item = null;
 1158                   float listIndentation = list.getIndentationLeft();
 1159                   int count = 0;
 1160                   Stack stack = new Stack();
 1161                   for (int k = 0; k < items.size(); ++k) {
 1162                       Object obj = items.get(k);
 1163                       if (obj instanceof ListItem) {
 1164                           if (count == listIdx) {
 1165                               item = (ListItem)obj;
 1166                               break;
 1167                           }
 1168                           else ++count;
 1169                       }
 1170                       else if (obj instanceof com.lowagie.text.List) {
 1171                           stack.push(new Object[]{list, new Integer(k), new Float(listIndentation)});
 1172                           list = (com.lowagie.text.List)obj;
 1173                           items = list.getItems();
 1174                           listIndentation += list.getIndentationLeft();
 1175                           k = -1;
 1176                           continue;
 1177                       }
 1178                       if (k == items.size() - 1) {
 1179                           if (!stack.isEmpty()) {
 1180                               Object objs[] = (Object[])stack.pop();
 1181                               list = (com.lowagie.text.List)objs[0];
 1182                               items = list.getItems();
 1183                               k = ((Integer)objs[1]).intValue();
 1184                               listIndentation = ((Float)objs[2]).floatValue();
 1185                           }
 1186                       }
 1187                   }
 1188                   int status = 0;
 1189                   for (int keep = 0; keep < 2; ++keep) {
 1190                       float lastY = yLine;
 1191                       boolean createHere = false;
 1192                       if (compositeColumn == null) {
 1193                           if (item == null) {
 1194                               listIdx = 0;
 1195                               compositeElements.removeFirst();
 1196                               continue main_loop;
 1197                           }
 1198                           compositeColumn = new ColumnText(canvas);
 1199                           compositeColumn.setUseAscender(firstPass ? useAscender : false);
 1200                           compositeColumn.setAlignment(item.getAlignment());
 1201                           compositeColumn.setIndent(item.getIndentationLeft() + listIndentation + item.getFirstLineIndent());
 1202                           compositeColumn.setExtraParagraphSpace(item.getExtraParagraphSpace());
 1203                           compositeColumn.setFollowingIndent(compositeColumn.getIndent());
 1204                           compositeColumn.setRightIndent(item.getIndentationRight() + list.getIndentationRight());
 1205                           compositeColumn.setLeading(item.getLeading(), item.getMultipliedLeading());
 1206                           compositeColumn.setRunDirection(runDirection);
 1207                           compositeColumn.setArabicOptions(arabicOptions);
 1208                           compositeColumn.setSpaceCharRatio(spaceCharRatio);
 1209                           compositeColumn.addText(item);
 1210                           if (!firstPass) {
 1211                               yLine -= item.spacingBefore();
 1212                           }
 1213                           createHere = true;
 1214                       }
 1215                       compositeColumn.leftX = leftX;
 1216                       compositeColumn.rightX = rightX;
 1217                       compositeColumn.yLine = yLine;
 1218                       compositeColumn.rectangularWidth = rectangularWidth;
 1219                       compositeColumn.rectangularMode = rectangularMode;
 1220                       compositeColumn.minY = minY;
 1221                       compositeColumn.maxY = maxY;
 1222                       boolean keepCandidate = (item.getKeepTogether() && createHere && !firstPass);
 1223                       status = compositeColumn.go(simulate || (keepCandidate && keep == 0));
 1224                       updateFilledWidth(compositeColumn.filledWidth);
 1225                       if ((status & NO_MORE_TEXT) == 0 && keepCandidate) {
 1226                           compositeColumn = null;
 1227                           yLine = lastY;
 1228                           return NO_MORE_COLUMN;
 1229                       }
 1230                       if (simulate || !keepCandidate)
 1231                           break;
 1232                       if (keep == 0) {
 1233                           compositeColumn = null;
 1234                           yLine = lastY;
 1235                       }
 1236                   }
 1237                   firstPass = false;
 1238                   yLine = compositeColumn.yLine;
 1239                   linesWritten += compositeColumn.linesWritten;
 1240                   descender = compositeColumn.descender;
 1241                   if (!Float.isNaN(compositeColumn.firstLineY) && !compositeColumn.firstLineYDone) {
 1242                       if (!simulate)
 1243                           showTextAligned(canvas, Element.ALIGN_LEFT, new Phrase(item.getListSymbol()), compositeColumn.leftX + listIndentation, compositeColumn.firstLineY, 0);
 1244                       compositeColumn.firstLineYDone = true;
 1245                   }
 1246                   if ((status & NO_MORE_TEXT) != 0) {
 1247                       compositeColumn = null;
 1248                       ++listIdx;
 1249                       yLine -= item.spacingAfter();
 1250                   }
 1251                   if ((status & NO_MORE_COLUMN) != 0) {
 1252                       return NO_MORE_COLUMN;
 1253                   }
 1254               }
 1255               else if (element.type() == Element.PTABLE) {
 1256               	// don't write anything in the current column if there's no more space available
 1257                   if (yLine < minY || yLine > maxY)
 1258                       return NO_MORE_COLUMN;
 1259                   
 1260                   // get the PdfPTable element
 1261                   PdfPTable table = (PdfPTable)element;
 1262                   
 1263                   // we ignore tables without a body
 1264                   if (table.size() <= table.getHeaderRows()) {
 1265                       compositeElements.removeFirst();
 1266                       continue;
 1267                   }
 1268                   
 1269                   // offsets
 1270                   float yTemp = yLine;
 1271                   if (!firstPass && listIdx == 0) {
 1272                       yTemp -= table.spacingBefore();
 1273                   }
 1274                   float yLineWrite = yTemp;
 1275                   
 1276                   // don't write anything in the current column if there's no more space available
 1277                   if (yTemp < minY || yTemp > maxY) {
 1278                       return NO_MORE_COLUMN;
 1279                   }
 1280                   
 1281                   // coordinates
 1282                   currentLeading = 0;
 1283                   float x1 = leftX;
 1284                   float tableWidth;
 1285                   if (table.isLockedWidth()) {
 1286                       tableWidth = table.getTotalWidth();
 1287                       updateFilledWidth(tableWidth);
 1288                   }
 1289                   else {
 1290                       tableWidth = rectangularWidth * table.getWidthPercentage() / 100f;
 1291                       table.setTotalWidth(tableWidth);
 1292                   }
 1293                   
 1294                   // how many header rows are real header rows; how many are footer rows?
 1295                   int headerRows = table.getHeaderRows();
 1296                   int footerRows = table.getFooterRows();
 1297                   if (footerRows > headerRows)
 1298                       footerRows = headerRows;
 1299                   int realHeaderRows = headerRows - footerRows;
 1300                   float headerHeight = table.getHeaderHeight();
 1301                   float footerHeight = table.getFooterHeight();
 1302   
 1303                   // make sure the header and footer fit on the page
 1304                   boolean skipHeader = (!firstPass && table.isSkipFirstHeader() && listIdx <= headerRows);
 1305                   if (!skipHeader) {
 1306                       yTemp -= headerHeight;
 1307                       if (yTemp < minY || yTemp > maxY) {
 1308                           if (firstPass) {
 1309                               compositeElements.removeFirst();
 1310                               continue;
 1311                           }
 1312                           return NO_MORE_COLUMN;
 1313                       }
 1314                   }
 1315                   
 1316                   // how many real rows (not header or footer rows) fit on a page?
 1317                   int k;
 1318                   if (listIdx < headerRows) {
 1319                       listIdx = headerRows;
 1320                   }
 1321                   if (!table.isComplete()) {
 1322                   	yTemp -= footerHeight;
 1323                   }
 1324                   for (k = listIdx; k < table.size(); ++k) {
 1325                       float rowHeight = table.getRowHeight(k);
 1326                       if (yTemp - rowHeight < minY)
 1327                           break;
 1328                       yTemp -= rowHeight;
 1329                   }
 1330                   if (!table.isComplete()) {
 1331                   	yTemp += footerHeight;
 1332                   }
 1333                   // either k is the first row that doesn't fit on the page (break);
 1334                   if (k < table.size()) {
 1335                   	if (table.isSplitRows() && (!table.isSplitLate() || (k == listIdx && firstPass))) {
 1336                           if (!splittedRow) {
 1337                               splittedRow = true;
 1338                               table = new PdfPTable(table);
 1339                               compositeElements.set(0, table);
 1340                               ArrayList rows = table.getRows();
 1341                               for (int i = headerRows; i < listIdx; ++i)
 1342                                   rows.set(i, null);
 1343                           }
 1344                           float h = yTemp - minY;
 1345                           PdfPRow newRow = table.getRow(k).splitRow(h);
 1346                           if (newRow == null) {
 1347                               if (k == listIdx) {
 1348                                   return NO_MORE_COLUMN;
 1349                               }
 1350                           }
 1351                           else {
 1352                               yTemp = minY;
 1353                               table.getRows().add(++k, newRow);
 1354                           }
 1355                       }
 1356                       else if (!table.isSplitRows() && k == listIdx && firstPass) {
 1357                           compositeElements.removeFirst();
 1358                           splittedRow = false;
 1359                           continue;
 1360                       }
 1361                       else if (k == listIdx && !firstPass && (!table.isSplitRows() || table.isSplitLate()) && (table.getFooterRows() == 0 || table.isComplete())) {
 1362                           return NO_MORE_COLUMN;
 1363                       }
 1364                   }
 1365                   // or k is the number of rows in the table (for loop was done).
 1366                   firstPass = false;
 1367                   // we draw the table (for real now)
 1368                   if (!simulate) {
 1369                   	// set the alignment
 1370                       switch (table.getHorizontalAlignment()) {
 1371                           case Element.ALIGN_LEFT:
 1372                               break;
 1373                           case Element.ALIGN_RIGHT:
 1374                               x1 += rectangularWidth - tableWidth;
 1375                               break;
 1376                           default:
 1377                               x1 += (rectangularWidth - tableWidth) / 2f;
 1378                       }
 1379                       // copy the rows that fit on the page in a new table nt
 1380                       PdfPTable nt = PdfPTable.shallowCopy(table);
 1381                       ArrayList rows = table.getRows();
 1382                       ArrayList sub = nt.getRows();
 1383                       
 1384                       // first we add the real header rows (if necessary)
 1385                       if (!skipHeader) {
 1386                           for (int j = 0; j < realHeaderRows; ++j) {
 1387                           	PdfPRow headerRow = (PdfPRow)rows.get(j);
 1388                               sub.add(headerRow);
 1389                           }
 1390                       }
 1391                       else {
 1392                           nt.setHeaderRows(footerRows);
 1393                       }
 1394                       // then we add the real content
 1395                       for (int j = listIdx; j < k; ++j) {
 1396                           sub.add(rows.get(j));
 1397                       }
 1398                       // if k < table.size(), we must indicate that the new table is complete;
 1399                       // otherwise no footers will be added (because iText thinks the table continues on the same page)
 1400                       if (k < table.size()) {
 1401                       	nt.setComplete(true);
 1402                       }
 1403                       // we add the footer rows if necessary (not for incomplete tables)
 1404                       for (int j = 0; j < footerRows && nt.isComplete(); ++j) {
 1405                           sub.add(rows.get(j + realHeaderRows));
 1406                       }
 1407   
 1408                       // we need a correction if the last row needs to be extended
 1409                       float rowHeight = 0;
 1410                       if (table.isExtendLastRow()) {
 1411                           PdfPRow last = (PdfPRow)sub.get(sub.size() - 1 - footerRows);
 1412                           rowHeight = last.getMaxHeights();
 1413                           last.setMaxHeights(yTemp - minY + rowHeight);
 1414                           yTemp = minY;
 1415                       }
 1416                       
 1417                       // now we render the rows of the new table
 1418                       if (canvases != null)
 1419                           nt.writeSelectedRows(0, -1, x1, yLineWrite, canvases);
 1420                       else
 1421                           nt.writeSelectedRows(0, -1, x1, yLineWrite, canvas);
 1422                       if (table.isExtendLastRow()) {
 1423                           PdfPRow last = (PdfPRow)sub.get(sub.size() - 1 - footerRows);
 1424                           last.setMaxHeights(rowHeight);
 1425                       }
 1426                   }
 1427                   else if (table.isExtendLastRow() && minY > PdfPRow.BOTTOM_LIMIT) {
 1428                       yTemp = minY;
 1429                   }
 1430                   yLine = yTemp;
 1431                   if (!(skipHeader || table.isComplete())) {
 1432                   	yLine += footerHeight;
 1433                   }
 1434                   if (k >= table.size()) {
 1435                       yLine -= table.spacingAfter();
 1436                       compositeElements.removeFirst();
 1437                       splittedRow = false;
 1438                       listIdx = 0;
 1439                   }
 1440                   else {
 1441                       if (splittedRow) {
 1442                           ArrayList rows = table.getRows();
 1443                           for (int i = listIdx; i < k; ++i)
 1444                               rows.set(i, null);
 1445                       }
 1446                       listIdx = k;
 1447                       return NO_MORE_COLUMN;
 1448                   }
 1449               }
 1450               else if (element.type() == Element.YMARK) {
 1451                   if (!simulate) {
 1452                       DrawInterface zh = (DrawInterface)element;
 1453                       zh.draw(canvas, leftX, minY, rightX, maxY, yLine);
 1454                   }
 1455                   compositeElements.removeFirst();
 1456               }
 1457               else
 1458                   compositeElements.removeFirst();
 1459           }
 1460       }
 1461       
 1462       /**
 1463        * Gets the canvas.
 1464        * @return a PdfContentByte.
 1465        */
 1466       public PdfContentByte getCanvas() {
 1467           return canvas;
 1468       }
 1469       
 1470       /**
 1471        * Sets the canvas.
 1472        * @param canvas
 1473        */
 1474       public void setCanvas(PdfContentByte canvas) {
 1475           this.canvas = canvas;
 1476           this.canvases = null;
 1477           if (compositeColumn != null)
 1478               compositeColumn.setCanvas(canvas);
 1479       }
 1480       
 1481       /**
 1482        * Sets the canvases.
 1483        * @param canvases
 1484        */
 1485       public void setCanvases(PdfContentByte[] canvases) {
 1486           this.canvases = canvases;
 1487           this.canvas = canvases[PdfPTable.TEXTCANVAS];
 1488           if (compositeColumn != null)
 1489               compositeColumn.setCanvases(canvases);
 1490       }
 1491       
 1492       /**
 1493        * Gets the canvases.
 1494        * @return an array of PdfContentByte
 1495        */
 1496       public PdfContentByte[] getCanvases() {
 1497           return canvases;
 1498       }
 1499       
 1500       /**
 1501        * Checks if the element has a height of 0.
 1502        * @return true or false
 1503        * @since 2.1.2
 1504        */
 1505       public boolean zeroHeightElement() {
 1506           return composite && !compositeElements.isEmpty() && ((Element)compositeElements.getFirst()).type() == Element.YMARK;
 1507       }
 1508       
 1509       /**
 1510        * Checks if UseAscender is enabled/disabled.
 1511        * @return true is the adjustment of the first line height is based on max ascender.
 1512        */
 1513       public boolean isUseAscender() {
 1514           return useAscender;
 1515       }
 1516   
 1517       /**
 1518        * Enables/Disables adjustment of first line height based on max ascender.
 1519        * @param use enable adjustment if true
 1520        */
 1521       public void setUseAscender(boolean use) {
 1522           useAscender = use;
 1523       }
 1524       
 1525       /**
 1526        * Checks the status variable and looks if there's still some text.
 1527        */
 1528       public static boolean hasMoreText(int status) {
 1529       	return (status & ColumnText.NO_MORE_TEXT) == 0;
 1530       }
 1531   
 1532       /**
 1533        * Holds value of property filledWidth.
 1534        */
 1535       private float filledWidth;
 1536   
 1537       /**
 1538        * Gets the real width used by the largest line.
 1539        * @return the real width used by the largest line
 1540        */
 1541       public float getFilledWidth() {
 1542   
 1543           return this.filledWidth;
 1544       }
 1545   
 1546       /**
 1547        * Sets the real width used by the largest line. Only used to set it
 1548        * to zero to start another measurement.
 1549        * @param filledWidth the real width used by the largest line
 1550        */
 1551       public void setFilledWidth(float filledWidth) {
 1552   
 1553           this.filledWidth = filledWidth;
 1554       }
 1555       
 1556       /**
 1557        * Replaces the <CODE>filledWidth</CODE> if greater than the existing one.
 1558        * @param w the new <CODE>filledWidth</CODE> if greater than the existing one
 1559        */
 1560       public void updateFilledWidth(float w) {
 1561           if (w > filledWidth)
 1562               filledWidth = w;
 1563       }
 1564   
 1565       private boolean adjustFirstLine = true;
 1566   
 1567       /**
 1568        * Gets the first line adjustment property.
 1569        * @return the first line adjustment property.
 1570        */
 1571       public boolean isAdjustFirstLine() {
 1572           return this.adjustFirstLine;
 1573       }
 1574   
 1575       /**
 1576        * Sets the first line adjustment. Some objects have properties, like spacing before, that
 1577        * behave differently if the object is the first to be written after go() or not. The first line adjustment is 
 1578        * <CODE>true</CODE> by default but can be changed if several objects are to be placed one
 1579        * after the other in the same column calling go() several times.
 1580        * @param adjustFirstLine <CODE>true</CODE> to adjust the first line, <CODE>false</CODE> otherwise
 1581        */
 1582       public void setAdjustFirstLine(boolean adjustFirstLine) {
 1583           this.adjustFirstLine = adjustFirstLine;
 1584       }
 1585   }

Save This Page
Home » iText-src-2.1.3 » com.lowagie » text » pdf » [javadoc | source]