From 6025b6016517c6d898d8957d1d7e03ba71431912 Mon Sep 17 00:00:00 2001 From: tknall Date: Fri, 1 Dec 2006 12:20:24 +0000 Subject: Initial import of release 2.2. git-svn-id: https://joinup.ec.europa.eu/svn/pdf-as/trunk@4 7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c --- .../java/com/lowagie/text/pdf/MultiColumnText.java | 575 +++++++++++++++++++++ 1 file changed, 575 insertions(+) create mode 100644 src/main/java/com/lowagie/text/pdf/MultiColumnText.java (limited to 'src/main/java/com/lowagie/text/pdf/MultiColumnText.java') diff --git a/src/main/java/com/lowagie/text/pdf/MultiColumnText.java b/src/main/java/com/lowagie/text/pdf/MultiColumnText.java new file mode 100644 index 0000000..4de2069 --- /dev/null +++ b/src/main/java/com/lowagie/text/pdf/MultiColumnText.java @@ -0,0 +1,575 @@ +/* + * $Id: MultiColumnText.java,v 1.19 2006/03/20 12:11:46 blowagie Exp $ + * $Name: $ + * + * Copyright 2004 Steve Appling + * + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is 'iText, a free JAVA-PDF library'. + * + * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by + * the Initial Developer are Copyright (C) 1999-2005 by Bruno Lowagie. + * All Rights Reserved. + * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer + * are Copyright (C) 2000-2005 by Paulo Soares. All Rights Reserved. + * + * Contributor(s): all the names of the contributors are added in the source code + * where applicable. + * + * Alternatively, the contents of this file may be used under the terms of the + * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the + * provisions of LGPL are applicable instead of those above. If you wish to + * allow use of your version of this file only under the terms of the LGPL + * License and not to allow others to use your version of this file under + * the MPL, indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by the LGPL. + * If you do not delete the provisions above, a recipient may use your version + * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MPL as stated above or under the terms of the GNU + * Library General Public License as published by the Free Software Foundation; + * either version 2 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more + * details. + * + * If you didn't download this code from the following link, you should check if + * you aren't using an obsolete version: + * http://www.lowagie.com/iText/ + */ + +package com.lowagie.text.pdf; + +import com.lowagie.text.*; + +import java.util.ArrayList; + +/** + * Formats content into one or more columns bounded by a + * rectangle. The columns may be simple rectangles or + * more complicated shapes. Add all of the columns before + * adding content. Column continuation is supported. A MultiColumnText object may be added to + * a document using Document.add. + * @author Steve Appling + */ +public class MultiColumnText implements Element { + + /** special constant for automatic calculation of height */ + public static final float AUTOMATIC = -1f; + + /** + * total desiredHeight of columns. If AUTOMATIC, this means fill pages until done. + * This may be larger than one page + */ + private float desiredHeight; + + /** + * total height of element written out so far + */ + private float totalHeight; + + /** + * true if all the text could not be written out due to height restriction + */ + private boolean overflow; + + /** + * Top of the columns - y position on starting page. + * If AUTOMATIC, it means current y position when added to document + */ + private float top; + + /** + * used to store the y position of the bottom of the page + */ + private float pageBottom; + + /** + * ColumnText object used to do all the real work. This same object is used for all columns + */ + private ColumnText columnText; + + /** + * Array of ColumnDef objects used to define the columns + */ + private ArrayList columnDefs; + + /** + * true if all columns are simple (rectangular) + */ + private boolean simple = true; + + private int currentColumn = 0; + + private float nextY = AUTOMATIC; + + private boolean columnsRightToLeft = false; + + private PdfDocument document; + /** + * Default constructor. Sets height to AUTOMATIC. + * Columns will repeat on each page as necessary to accomodate content length. + */ + public MultiColumnText() { + this(AUTOMATIC); + } + + /** + * Construct a MultiColumnText container of the specified height. + * If height is AUTOMATIC, fill complete pages until done. + * If a specific height is used, it may span one or more pages. + * + * @param height + */ + public MultiColumnText(float height) { + columnDefs = new ArrayList(); + desiredHeight = height; + top = AUTOMATIC; + // canvas will be set later + columnText = new ColumnText(null); + totalHeight = 0f; + } + + /** + * Construct a MultiColumnText container of the specified height + * starting at the specified Y position. + * + * @param height + * @param top + */ + public MultiColumnText(float top, float height) { + columnDefs = new ArrayList(); + desiredHeight = height; + this.top = top; + nextY = top; + // canvas will be set later + columnText = new ColumnText(null); + totalHeight = 0f; + } + + /** + * Indicates that all of the text did not fit in the + * specified height. Note that isOverflow will return + * false before the MultiColumnText object has been + * added to the document. It will always be false if + * the height is AUTOMATIC. + * + * @return true if there is still space left in the column + */ + public boolean isOverflow() { + return overflow; + } + + /** + * Copy the parameters from the specified ColumnText to use + * when rendering. Parameters like setArabicOptions + * must be set in this way. + * + * @param sourceColumn + */ + public void useColumnParams(ColumnText sourceColumn) { + // note that canvas will be overwritten later + columnText.setSimpleVars(sourceColumn); + } + + /** + * Add a new column. The parameters are limits for each column + * wall in the format of a sequence of points (x1,y1,x2,y2,...). + * + * @param left limits for left column + * @param right limits for right column + */ + public void addColumn(float[] left, float[] right) { + ColumnDef nextDef = new ColumnDef(left, right); + simple = nextDef.isSimple(); + columnDefs.add(nextDef); + } + + /** + * Add a simple rectangular column with specified left + * and right x position boundaries. + * + * @param left left boundary + * @param right right boundary + */ + public void addSimpleColumn(float left, float right) { + ColumnDef newCol = new ColumnDef(left, right); + columnDefs.add(newCol); + } + + /** + * Add the specified number of evenly spaced rectangular columns. + * Columns will be seperated by the specified gutterWidth. + * + * @param left left boundary of first column + * @param right right boundary of last column + * @param gutterWidth width of gutter spacing between columns + * @param numColumns number of columns to add + */ + public void addRegularColumns(float left, float right, float gutterWidth, int numColumns) { + float currX = left; + float width = right - left; + float colWidth = (width - (gutterWidth * (numColumns - 1))) / numColumns; + for (int i = 0; i < numColumns; i++) { + addSimpleColumn(currX, currX + colWidth); + currX += colWidth + gutterWidth; + } + } + + /** + * Add an element to be rendered in a column. + * Note that you can only add a Phrase + * or a Chunk if the columns are + * not all simple. This is an underlying restriction in + * {@link com.lowagie.text.pdf.ColumnText} + * + * @param element element to add + * @throws DocumentException if element can't be added + */ + public void addElement(Element element) throws DocumentException { + if (simple) { + columnText.addElement(element); + } else if (element instanceof Phrase) { + columnText.addText((Phrase) element); + } else if (element instanceof Chunk) { + columnText.addText((Chunk) element); + } else { + throw new DocumentException("Can't add " + element.getClass() + " to MultiColumnText with complex columns"); + } + } + + + /** + * Write out the columns. After writing, use + * {@link #isOverflow()} to see if all text was written. + * @param canvas PdfContentByte to write with + * @param document document to write to (only used to get page limit info) + * @param documentY starting y position to begin writing at + * @return the current height (y position) after writing the columns + * @throws DocumentException on error + */ + public float write(PdfContentByte canvas, PdfDocument document, float documentY) throws DocumentException { + this.document = document; + columnText.setCanvas(canvas); + if (columnDefs.size() == 0) { + throw new DocumentException("MultiColumnText has no columns"); + } + overflow = false; + pageBottom = document.bottom(); + float currentHeight = 0; + boolean done = false; + try { + while (!done) { + if (nextY == AUTOMATIC) { + nextY = document.getVerticalPosition(true); // RS - 07/07/2005 - - Get current doc writing position for top of columns on new page. + } + if (top == AUTOMATIC) { + top = document.getVerticalPosition(true); // RS - 07/07/2005 - Get current doc writing position for top of columns on new page. + } + + ColumnDef currentDef = (ColumnDef) columnDefs.get(getCurrentColumn()); + columnText.setYLine(top); + + float[] left = currentDef.resolvePositions(Rectangle.LEFT); + float[] right = currentDef.resolvePositions(Rectangle.RIGHT); + if (document.isMarginMirroring() && document.getPageNumber() % 2 == 0){ + float delta = document.rightMargin() - document.left(); + left = (float[])left.clone(); + right = (float[])right.clone(); + for (int i = 0; i < left.length; i += 2) { + left[i] -= delta; + } + for (int i = 0; i < right.length; i += 2) { + right[i] -= delta; + } + } + + currentHeight = Math.max(currentHeight, getHeight(left, right)); + + if (currentDef.isSimple()) { + columnText.setSimpleColumn(left[2], left[3], right[0], right[1]); + } else { + columnText.setColumns(left, right); + } + + int result = columnText.go(); + if ((result & ColumnText.NO_MORE_TEXT) != 0) { + done = true; + top = columnText.getYLine(); + } else if (shiftCurrentColumn()) { + top = nextY; + } else { // check if we are done because of height + totalHeight += currentHeight; + if ((desiredHeight != AUTOMATIC) && (totalHeight >= desiredHeight)) { + overflow = true; + break; + } else { // need to start new page and reset the columns + documentY = nextY; + newPage(); + currentHeight = 0; + } + } + } + } catch (DocumentException ex) { + ex.printStackTrace(); + throw ex; + } + if (desiredHeight == AUTOMATIC && columnDefs.size() == 1) { + currentHeight = documentY - columnText.getYLine(); + } + return currentHeight; + } + + private void newPage() throws DocumentException { + resetCurrentColumn(); + if (desiredHeight == AUTOMATIC) { + top = nextY = AUTOMATIC; + } + else { + top = nextY; + } + totalHeight = 0; + if (document != null) { + document.newPage(); + } + } + + /** + * Figure out the height of a column from the border extents + * + * @param left left border + * @param right right border + * @return height + */ + private float getHeight(float[] left, float[] right) { + float max = Float.MIN_VALUE; + float min = Float.MAX_VALUE; + for (int i = 0; i < left.length; i += 2) { + min = Math.min(min, left[i + 1]); + max = Math.max(max, left[i + 1]); + } + for (int i = 0; i < right.length; i += 2) { + min = Math.min(min, right[i + 1]); + max = Math.max(max, right[i + 1]); + } + return max - min; + } + + + /** + * Processes the element by adding it to an + * ElementListener. + * + * @param listener an ElementListener + * @return true if the element was processed successfully + */ + public boolean process(ElementListener listener) { + try { + return listener.add(this); + } catch (DocumentException de) { + return false; + } + } + + /** + * Gets the type of the text element. + * + * @return a type + */ + + public int type() { + return Element.MULTI_COLUMN_TEXT; + } + + /** + * Returns null - not used + * + * @return null + */ + + public ArrayList getChunks() { + return null; + } + + /** + * Calculates the appropriate y position for the bottom + * of the columns on this page. + * + * @return the y position of the bottom of the columns + */ + private float getColumnBottom() { + if (desiredHeight == AUTOMATIC) { + return pageBottom; + } else { + return Math.max(top - (desiredHeight - totalHeight), pageBottom); + } + } + + /** + * Moves the text insertion point to the beginning of the next column, issuing a page break if + * needed. + * @throws DocumentException on error + */ + public void nextColumn() throws DocumentException { + currentColumn = (currentColumn + 1) % columnDefs.size(); + top = nextY; + if (currentColumn == 0) { + newPage(); + } + } + + /** + * Gets the current column. + * @return the current column + */ + public int getCurrentColumn() { + if (columnsRightToLeft) { + return (columnDefs.size() - currentColumn - 1); + } + return currentColumn; + } + + /** + * Resets the current column. + */ + public void resetCurrentColumn() { + currentColumn = 0; + } + + /** + * Shifts the current column. + * @return true if the currentcolumn has changed + */ + public boolean shiftCurrentColumn() { + if (currentColumn + 1 < columnDefs.size()) { + currentColumn++; + return true; + } + return false; + } + + /** + * Sets the direction of the columns. + * @param direction true = right2left; false = left2right + */ + public void setColumnsRightToLeft(boolean direction) { + columnsRightToLeft = direction; + } + + /** Sets the ratio between the extra word spacing and the extra character spacing + * when the text is fully justified. + * Extra word spacing will grow spaceCharRatio times more than extra character spacing. + * If the ratio is PdfWriter.NO_SPACE_CHAR_RATIO then the extra character spacing + * will be zero. + * @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing + */ + public void setSpaceCharRatio(float spaceCharRatio) { + columnText.setSpaceCharRatio(spaceCharRatio); + } + + /** Sets the run direction. + * @param runDirection the run direction + */ + public void setRunDirection(int runDirection) { + columnText.setRunDirection(runDirection); + } + + /** Sets the arabic shaping options. The option can be AR_NOVOWEL, + * AR_COMPOSEDTASHKEEL and AR_LIG. + * @param arabicOptions the arabic shaping options + */ + public void setArabicOptions(int arabicOptions) { + columnText.setArabicOptions(arabicOptions); + } + + /** Sets the default alignment + * @param alignment the default alignment + */ + public void setAlignment(int alignment) { + columnText.setAlignment(alignment); + } + + /** + * Inner class used to define a column + */ + private class ColumnDef { + private float[] left; + private float[] right; + + ColumnDef(float[] newLeft, float[] newRight) { + left = newLeft; + right = newRight; + } + + ColumnDef(float leftPosition, float rightPosition) { + left = new float[4]; + left[0] = leftPosition; // x1 + left[1] = top; // y1 + left[2] = leftPosition; // x2 + if (desiredHeight == AUTOMATIC || top == AUTOMATIC) { + left[3] = AUTOMATIC; + } else { + left[3] = top - desiredHeight; + } + + right = new float[4]; + right[0] = rightPosition; // x1 + right[1] = top; // y1 + right[2] = rightPosition; // x2 + if (desiredHeight == AUTOMATIC || top == AUTOMATIC) { + right[3] = AUTOMATIC; + } else { + right[3] = top - desiredHeight; + } + } + + /** + * Resolves the positions for the specified side of the column + * into real numbers once the top of the column is known. + * + * @param side either Rectangle.LEFT + * or Rectangle.RIGHT + * @return the array of floats for the side + */ + float[] resolvePositions(int side) { + if (side == Rectangle.LEFT) { + return resolvePositions(left); + } else { + return resolvePositions(right); + } + } + + private float[] resolvePositions(float[] positions) { + if (!isSimple()) { + return positions; + } + if (top == AUTOMATIC) { + // this is bad - must be programmer error + throw new RuntimeException("resolvePositions called with top=AUTOMATIC (-1). " + + "Top position must be set befure lines can be resolved"); + } + positions[1] = top; + positions[3] = getColumnBottom(); + return positions; + } + + /** + * Checks if column definition is a simple rectangle + * @return true if it is a simple column + */ + private boolean isSimple() { + return (left.length == 4 && right.length == 4) && (left[0] == left[2] && right[0] == right[2]); + } + + } +} -- cgit v1.2.3