KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > lowagie > text > pdf > MultiColumnText


1 /*
2  * $Id: MultiColumnText.java 2441 2006-10-27 17:24:01Z xlv $
3  * $Name$
4  *
5  * Copyright 2004 Steve Appling
6  *
7  * The contents of this file are subject to the Mozilla Public License Version 1.1
8  * (the "License"); you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
10  *
11  * Software distributed under the License is distributed on an "AS IS" basis,
12  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13  * for the specific language governing rights and limitations under the License.
14  *
15  * The Original Code is 'iText, a free JAVA-PDF library'.
16  *
17  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
18  * the Initial Developer are Copyright (C) 1999-2005 by Bruno Lowagie.
19  * All Rights Reserved.
20  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
21  * are Copyright (C) 2000-2005 by Paulo Soares. All Rights Reserved.
22  *
23  * Contributor(s): all the names of the contributors are added in the source code
24  * where applicable.
25  *
26  * Alternatively, the contents of this file may be used under the terms of the
27  * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
28  * provisions of LGPL are applicable instead of those above. If you wish to
29  * allow use of your version of this file only under the terms of the LGPL
30  * License and not to allow others to use your version of this file under
31  * the MPL, indicate your decision by deleting the provisions above and
32  * replace them with the notice and other provisions required by the LGPL.
33  * If you do not delete the provisions above, a recipient may use your version
34  * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
35  *
36  * This library is free software; you can redistribute it and/or modify it
37  * under the terms of the MPL as stated above or under the terms of the GNU
38  * Library General Public License as published by the Free Software Foundation;
39  * either version 2 of the License, or any later version.
40  *
41  * This library is distributed in the hope that it will be useful, but WITHOUT
42  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
43  * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
44  * details.
45  *
46  * If you didn't download this code from the following link, you should check if
47  * you aren't using an obsolete version:
48  * http://www.lowagie.com/iText/
49  */

50
51 package com.lowagie.text.pdf;
52
53 import java.util.ArrayList JavaDoc;
54
55 import com.lowagie.text.Chunk;
56 import com.lowagie.text.DocumentException;
57 import com.lowagie.text.Element;
58 import com.lowagie.text.ElementListener;
59 import com.lowagie.text.Phrase;
60 import com.lowagie.text.Rectangle;
61
62 /**
63  * Formats content into one or more columns bounded by a
64  * rectangle. The columns may be simple rectangles or
65  * more complicated shapes. Add all of the columns before
66  * adding content. Column continuation is supported. A MultiColumnText object may be added to
67  * a document using <CODE>Document.add</CODE>.
68  * @author Steve Appling
69  */

70 public class MultiColumnText implements Element {
71
72     /** special constant for automatic calculation of height */
73     public static final float AUTOMATIC = -1f;
74
75     /**
76      * total desiredHeight of columns. If <CODE>AUTOMATIC</CODE>, this means fill pages until done.
77      * This may be larger than one page
78      */

79     private float desiredHeight;
80
81     /**
82      * total height of element written out so far
83      */

84     private float totalHeight;
85
86     /**
87      * true if all the text could not be written out due to height restriction
88      */

89     private boolean overflow;
90
91     /**
92      * Top of the columns - y position on starting page.
93      * If <CODE>AUTOMATIC</CODE>, it means current y position when added to document
94      */

95     private float top;
96
97     /**
98      * used to store the y position of the bottom of the page
99      */

100     private float pageBottom;
101
102     /**
103      * ColumnText object used to do all the real work. This same object is used for all columns
104      */

105     private ColumnText columnText;
106
107     /**
108      * Array of <CODE>ColumnDef</CODE> objects used to define the columns
109      */

110     private ArrayList JavaDoc columnDefs;
111
112     /**
113      * true if all columns are simple (rectangular)
114      */

115     private boolean simple = true;
116
117     private int currentColumn = 0;
118     
119     private float nextY = AUTOMATIC;
120     
121     private boolean columnsRightToLeft = false;
122     
123     private PdfDocument document;
124     /**
125      * Default constructor. Sets height to <CODE>AUTOMATIC</CODE>.
126      * Columns will repeat on each page as necessary to accomodate content length.
127      */

128     public MultiColumnText() {
129         this(AUTOMATIC);
130     }
131
132     /**
133      * Construct a MultiColumnText container of the specified height.
134      * If height is <CODE>AUTOMATIC</CODE>, fill complete pages until done.
135      * If a specific height is used, it may span one or more pages.
136      *
137      * @param height
138      */

139     public MultiColumnText(float height) {
140         columnDefs = new ArrayList JavaDoc();
141         desiredHeight = height;
142         top = AUTOMATIC;
143         // canvas will be set later
144
columnText = new ColumnText(null);
145         totalHeight = 0f;
146     }
147
148     /**
149      * Construct a MultiColumnText container of the specified height
150      * starting at the specified Y position.
151      *
152      * @param height
153      * @param top
154      */

155     public MultiColumnText(float top, float height) {
156         columnDefs = new ArrayList JavaDoc();
157         desiredHeight = height;
158         this.top = top;
159         nextY = top;
160         // canvas will be set later
161
columnText = new ColumnText(null);
162         totalHeight = 0f;
163     }
164     
165     /**
166      * Indicates that all of the text did not fit in the
167      * specified height. Note that isOverflow will return
168      * false before the MultiColumnText object has been
169      * added to the document. It will always be false if
170      * the height is AUTOMATIC.
171      *
172      * @return true if there is still space left in the column
173      */

174     public boolean isOverflow() {
175         return overflow;
176     }
177
178     /**
179      * Copy the parameters from the specified ColumnText to use
180      * when rendering. Parameters like <CODE>setArabicOptions</CODE>
181      * must be set in this way.
182      *
183      * @param sourceColumn
184      */

185     public void useColumnParams(ColumnText sourceColumn) {
186         // note that canvas will be overwritten later
187
columnText.setSimpleVars(sourceColumn);
188     }
189
190     /**
191      * Add a new column. The parameters are limits for each column
192      * wall in the format of a sequence of points (x1,y1,x2,y2,...).
193      *
194      * @param left limits for left column
195      * @param right limits for right column
196      */

197     public void addColumn(float[] left, float[] right) {
198         ColumnDef nextDef = new ColumnDef(left, right);
199         simple = nextDef.isSimple();
200         columnDefs.add(nextDef);
201     }
202
203     /**
204      * Add a simple rectangular column with specified left
205      * and right x position boundaries.
206      *
207      * @param left left boundary
208      * @param right right boundary
209      */

210     public void addSimpleColumn(float left, float right) {
211         ColumnDef newCol = new ColumnDef(left, right);
212         columnDefs.add(newCol);
213     }
214
215     /**
216      * Add the specified number of evenly spaced rectangular columns.
217      * Columns will be seperated by the specified gutterWidth.
218      *
219      * @param left left boundary of first column
220      * @param right right boundary of last column
221      * @param gutterWidth width of gutter spacing between columns
222      * @param numColumns number of columns to add
223      */

224     public void addRegularColumns(float left, float right, float gutterWidth, int numColumns) {
225         float currX = left;
226         float width = right - left;
227         float colWidth = (width - (gutterWidth * (numColumns - 1))) / numColumns;
228         for (int i = 0; i < numColumns; i++) {
229             addSimpleColumn(currX, currX + colWidth);
230             currX += colWidth + gutterWidth;
231         }
232     }
233
234     /**
235      * Add an element to be rendered in a column.
236      * Note that you can only add a <CODE>Phrase</CODE>
237      * or a <CODE>Chunk</CODE> if the columns are
238      * not all simple. This is an underlying restriction in
239      * {@link com.lowagie.text.pdf.ColumnText}
240      *
241      * @param element element to add
242      * @throws DocumentException if element can't be added
243      */

244     public void addElement(Element element) throws DocumentException {
245         if (simple) {
246             columnText.addElement(element);
247         } else if (element instanceof Phrase) {
248             columnText.addText((Phrase) element);
249         } else if (element instanceof Chunk) {
250             columnText.addText((Chunk) element);
251         } else {
252             throw new DocumentException("Can't add " + element.getClass() + " to MultiColumnText with complex columns");
253         }
254     }
255
256
257     /**
258      * Write out the columns. After writing, use
259      * {@link #isOverflow()} to see if all text was written.
260      * @param canvas PdfContentByte to write with
261      * @param document document to write to (only used to get page limit info)
262      * @param documentY starting y position to begin writing at
263      * @return the current height (y position) after writing the columns
264      * @throws DocumentException on error
265      */

266     public float write(PdfContentByte canvas, PdfDocument document, float documentY) throws DocumentException {
267         this.document = document;
268         columnText.setCanvas(canvas);
269         if (columnDefs.isEmpty()) {
270             throw new DocumentException("MultiColumnText has no columns");
271         }
272         overflow = false;
273         pageBottom = document.bottom();
274         float currentHeight = 0;
275         boolean done = false;
276         try {
277             while (!done) {
278                 if (nextY == AUTOMATIC) {
279                     nextY = document.getVerticalPosition(true); // RS - 07/07/2005 - - Get current doc writing position for top of columns on new page.
280
}
281                 if (top == AUTOMATIC) {
282                     top = document.getVerticalPosition(true); // RS - 07/07/2005 - Get current doc writing position for top of columns on new page.
283
}
284
285                 ColumnDef currentDef = (ColumnDef) columnDefs.get(getCurrentColumn());
286                 columnText.setYLine(top);
287
288                 float[] left = currentDef.resolvePositions(Rectangle.LEFT);
289                 float[] right = currentDef.resolvePositions(Rectangle.RIGHT);
290                 if (document.isMarginMirroring() && document.getPageNumber() % 2 == 0){
291                     float delta = document.rightMargin() - document.left();
292                     left = (float[])left.clone();
293                     right = (float[])right.clone();
294                     for (int i = 0; i < left.length; i += 2) {
295                         left[i] -= delta;
296                     }
297                     for (int i = 0; i < right.length; i += 2) {
298                         right[i] -= delta;
299                     }
300                 }
301                 
302                 currentHeight = Math.max(currentHeight, getHeight(left, right));
303
304                 if (currentDef.isSimple()) {
305                     columnText.setSimpleColumn(left[2], left[3], right[0], right[1]);
306                 } else {
307                     columnText.setColumns(left, right);
308                 }
309
310                 int result = columnText.go();
311                 if ((result & ColumnText.NO_MORE_TEXT) != 0) {
312                     done = true;
313                     top = columnText.getYLine();
314                 } else if (shiftCurrentColumn()) {
315                     top = nextY;
316                 } else { // check if we are done because of height
317
totalHeight += currentHeight;
318                     if ((desiredHeight != AUTOMATIC) && (totalHeight >= desiredHeight)) {
319                         overflow = true;
320                         break;
321                     } else { // need to start new page and reset the columns
322
documentY = nextY;
323                         newPage();
324                         currentHeight = 0;
325                     }
326                 }
327             }
328         } catch (DocumentException ex) {
329             ex.printStackTrace();
330             throw ex;
331         }
332         if (desiredHeight == AUTOMATIC && columnDefs.size() == 1) {
333             currentHeight = documentY - columnText.getYLine();
334         }
335         return currentHeight;
336     }
337
338     private void newPage() throws DocumentException {
339         resetCurrentColumn();
340         if (desiredHeight == AUTOMATIC) {
341             top = nextY = AUTOMATIC;
342         }
343         else {
344             top = nextY;
345         }
346         totalHeight = 0;
347         if (document != null) {
348             document.newPage();
349         }
350     }
351     
352     /**
353      * Figure out the height of a column from the border extents
354      *
355      * @param left left border
356      * @param right right border
357      * @return height
358      */

359     private float getHeight(float[] left, float[] right) {
360         float max = Float.MIN_VALUE;
361         float min = Float.MAX_VALUE;
362         for (int i = 0; i < left.length; i += 2) {
363             min = Math.min(min, left[i + 1]);
364             max = Math.max(max, left[i + 1]);
365         }
366         for (int i = 0; i < right.length; i += 2) {
367             min = Math.min(min, right[i + 1]);
368             max = Math.max(max, right[i + 1]);
369         }
370         return max - min;
371     }
372
373
374     /**
375      * Processes the element by adding it to an
376      * <CODE>ElementListener</CODE>.
377      *
378      * @param listener an <CODE>ElementListener</CODE>
379      * @return <CODE>true</CODE> if the element was processed successfully
380      */

381     public boolean process(ElementListener listener) {
382         try {
383             return listener.add(this);
384         } catch (DocumentException de) {
385             return false;
386         }
387     }
388
389     /**
390      * Gets the type of the text element.
391      *
392      * @return a type
393      */

394
395     public int type() {
396         return Element.MULTI_COLUMN_TEXT;
397     }
398
399     /**
400      * Returns null - not used
401      *
402      * @return null
403      */

404
405     public ArrayList JavaDoc getChunks() {
406         return null;
407     }
408
409     /**
410      * Calculates the appropriate y position for the bottom
411      * of the columns on this page.
412      *
413      * @return the y position of the bottom of the columns
414      */

415     private float getColumnBottom() {
416         if (desiredHeight == AUTOMATIC) {
417             return pageBottom;
418         } else {
419             return Math.max(top - (desiredHeight - totalHeight), pageBottom);
420         }
421     }
422
423     /**
424      * Moves the text insertion point to the beginning of the next column, issuing a page break if
425      * needed.
426      * @throws DocumentException on error
427      */

428     public void nextColumn() throws DocumentException {
429         currentColumn = (currentColumn + 1) % columnDefs.size();
430         top = nextY;
431         if (currentColumn == 0) {
432             newPage();
433         }
434     }
435
436     /**
437      * Gets the current column.
438      * @return the current column
439      */

440     public int getCurrentColumn() {
441         if (columnsRightToLeft) {
442             return (columnDefs.size() - currentColumn - 1);
443         }
444         return currentColumn;
445     }
446     
447     /**
448      * Resets the current column.
449      */

450     public void resetCurrentColumn() {
451         currentColumn = 0;
452     }
453     
454     /**
455      * Shifts the current column.
456      * @return true if the currentcolumn has changed
457      */

458     public boolean shiftCurrentColumn() {
459         if (currentColumn + 1 < columnDefs.size()) {
460             currentColumn++;
461             return true;
462         }
463         return false;
464     }
465     
466     /**
467      * Sets the direction of the columns.
468      * @param direction true = right2left; false = left2right
469      */

470     public void setColumnsRightToLeft(boolean direction) {
471         columnsRightToLeft = direction;
472     }
473     
474     /** Sets the ratio between the extra word spacing and the extra character spacing
475      * when the text is fully justified.
476      * Extra word spacing will grow <CODE>spaceCharRatio</CODE> times more than extra character spacing.
477      * If the ratio is <CODE>PdfWriter.NO_SPACE_CHAR_RATIO</CODE> then the extra character spacing
478      * will be zero.
479      * @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing
480      */

481     public void setSpaceCharRatio(float spaceCharRatio) {
482         columnText.setSpaceCharRatio(spaceCharRatio);
483     }
484
485     /** Sets the run direction.
486      * @param runDirection the run direction
487      */

488     public void setRunDirection(int runDirection) {
489         columnText.setRunDirection(runDirection);
490     }
491     
492     /** Sets the arabic shaping options. The option can be AR_NOVOWEL,
493      * AR_COMPOSEDTASHKEEL and AR_LIG.
494      * @param arabicOptions the arabic shaping options
495      */

496     public void setArabicOptions(int arabicOptions) {
497         columnText.setArabicOptions(arabicOptions);
498     }
499     
500     /** Sets the default alignment
501      * @param alignment the default alignment
502      */

503     public void setAlignment(int alignment) {
504         columnText.setAlignment(alignment);
505     }
506     
507     /**
508      * Inner class used to define a column
509      */

510     private class ColumnDef {
511         private float[] left;
512         private float[] right;
513
514         ColumnDef(float[] newLeft, float[] newRight) {
515             left = newLeft;
516             right = newRight;
517         }
518
519         ColumnDef(float leftPosition, float rightPosition) {
520             left = new float[4];
521             left[0] = leftPosition; // x1
522
left[1] = top; // y1
523
left[2] = leftPosition; // x2
524
if (desiredHeight == AUTOMATIC || top == AUTOMATIC) {
525                 left[3] = AUTOMATIC;
526             } else {
527                 left[3] = top - desiredHeight;
528             }
529
530             right = new float[4];
531             right[0] = rightPosition; // x1
532
right[1] = top; // y1
533
right[2] = rightPosition; // x2
534
if (desiredHeight == AUTOMATIC || top == AUTOMATIC) {
535                 right[3] = AUTOMATIC;
536             } else {
537                 right[3] = top - desiredHeight;
538             }
539         }
540
541         /**
542          * Resolves the positions for the specified side of the column
543          * into real numbers once the top of the column is known.
544          *
545          * @param side either <CODE>Rectangle.LEFT</CODE>
546          * or <CODE>Rectangle.RIGHT</CODE>
547          * @return the array of floats for the side
548          */

549         float[] resolvePositions(int side) {
550             if (side == Rectangle.LEFT) {
551                 return resolvePositions(left);
552             } else {
553                 return resolvePositions(right);
554             }
555         }
556
557         private float[] resolvePositions(float[] positions) {
558             if (!isSimple()) {
559                 return positions;
560             }
561             if (top == AUTOMATIC) {
562                 // this is bad - must be programmer error
563
throw new RuntimeException JavaDoc("resolvePositions called with top=AUTOMATIC (-1). " +
564                         "Top position must be set befure lines can be resolved");
565             }
566             positions[1] = top;
567             positions[3] = getColumnBottom();
568             return positions;
569         }
570
571         /**
572          * Checks if column definition is a simple rectangle
573          * @return true if it is a simple column
574          */

575         private boolean isSimple() {
576             return (left.length == 4 && right.length == 4) && (left[0] == left[2] && right[0] == right[2]);
577         }
578
579     }
580 }
581
Popular Tags