001/*
002 * $Id: VerticalText.java 4827 2011-05-02 22:34:16Z mstorer $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045import java.util.ArrayList;
046import java.util.Iterator;
047
048import com.itextpdf.text.BaseColor;
049import com.itextpdf.text.Chunk;
050import com.itextpdf.text.Element;
051import com.itextpdf.text.Phrase;
052import com.itextpdf.text.error_messages.MessageLocalization;
053
054/** Writes text vertically. Note that the naming is done according
055 * to horizontal text although it refers to vertical text.
056 * A line with the alignment Element.LEFT_ALIGN will actually
057 * be top aligned.
058 */
059public class VerticalText {
060
061/** Signals that there are no more text available. */
062    public static final int NO_MORE_TEXT = 1;
063
064/** Signals that there is no more column. */
065    public static final int NO_MORE_COLUMN = 2;
066
067/** The chunks that form the text. */
068    protected ArrayList<PdfChunk> chunks = new ArrayList<PdfChunk>();
069
070    /** The <CODE>PdfContent</CODE> where the text will be written to. */
071    protected PdfContentByte text;
072
073    /** The column alignment. Default is left alignment. */
074    protected int alignment = Element.ALIGN_LEFT;
075
076    /** Marks the chunks to be eliminated when the line is written. */
077    protected int currentChunkMarker = -1;
078
079    /** The chunk created by the splitting. */
080    protected PdfChunk currentStandbyChunk;
081
082    /** The chunk created by the splitting. */
083    protected String splittedChunkText;
084
085    /** The leading
086     */
087    protected float leading;
088
089    /** The X coordinate.
090     */
091    protected float startX;
092
093    /** The Y coordinate.
094     */
095    protected float startY;
096
097    /** The maximum number of vertical lines.
098     */
099    protected int maxLines;
100
101    /** The height of the text.
102     */
103    protected float height;
104
105    /** Creates new VerticalText
106     * @param text the place where the text will be written to. Can
107     * be a template.
108     */
109    public VerticalText(PdfContentByte text) {
110        this.text = text;
111    }
112
113    /**
114     * Adds a <CODE>Phrase</CODE> to the current text array.
115     * @param phrase the text
116     */
117    public void addText(Phrase phrase) {
118        for (Chunk c: phrase.getChunks()) {
119            chunks.add(new PdfChunk(c, null));
120        }
121    }
122
123    /**
124     * Adds a <CODE>Chunk</CODE> to the current text array.
125     * @param chunk the text
126     */
127    public void addText(Chunk chunk) {
128        chunks.add(new PdfChunk(chunk, null));
129    }
130
131    /** Sets the layout.
132     * @param startX the top right X line position
133     * @param startY the top right Y line position
134     * @param height the height of the lines
135     * @param maxLines the maximum number of lines
136     * @param leading the separation between the lines
137     */
138    public void setVerticalLayout(float startX, float startY, float height, int maxLines, float leading) {
139        this.startX = startX;
140        this.startY = startY;
141        this.height = height;
142        this.maxLines = maxLines;
143        setLeading(leading);
144    }
145
146    /** Sets the separation between the vertical lines.
147     * @param leading the vertical line separation
148     */
149    public void setLeading(float leading) {
150        this.leading = leading;
151    }
152
153    /** Gets the separation between the vertical lines.
154     * @return the vertical line separation
155     */
156    public float getLeading() {
157        return leading;
158    }
159
160    /**
161     * Creates a line from the chunk array.
162     * @param width the width of the line
163     * @return the line or null if no more chunks
164     */
165    protected PdfLine createLine(float width) {
166        if (chunks.isEmpty())
167            return null;
168        splittedChunkText = null;
169        currentStandbyChunk = null;
170        PdfLine line = new PdfLine(0, width, alignment, 0);
171        String total;
172        for (currentChunkMarker = 0; currentChunkMarker < chunks.size(); ++currentChunkMarker) {
173            PdfChunk original = chunks.get(currentChunkMarker);
174            total = original.toString();
175            currentStandbyChunk = line.add(original);
176            if (currentStandbyChunk != null) {
177                splittedChunkText = original.toString();
178                original.setValue(total);
179                return line;
180            }
181        }
182        return line;
183    }
184
185    /**
186     * Normalizes the list of chunks when the line is accepted.
187     */
188    protected void shortenChunkArray() {
189        if (currentChunkMarker < 0)
190            return;
191        if (currentChunkMarker >= chunks.size()) {
192            chunks.clear();
193            return;
194        }
195        PdfChunk split = chunks.get(currentChunkMarker);
196        split.setValue(splittedChunkText);
197        chunks.set(currentChunkMarker, currentStandbyChunk);
198        for (int j = currentChunkMarker - 1; j >= 0; --j)
199            chunks.remove(j);
200    }
201
202    /**
203     * Outputs the lines to the document. It is equivalent to <CODE>go(false)</CODE>.
204     * @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
205     * and/or <CODE>NO_MORE_COLUMN</CODE>
206     */
207    public int go() {
208        return go(false);
209    }
210
211    /**
212     * Outputs the lines to the document. The output can be simulated.
213     * @param simulate <CODE>true</CODE> to simulate the writing to the document
214     * @return returns the result of the operation. It can be <CODE>NO_MORE_TEXT</CODE>
215     * and/or <CODE>NO_MORE_COLUMN</CODE>
216     */
217    public int go(boolean simulate) {
218        boolean dirty = false;
219        PdfContentByte graphics = null;
220        if (text != null) {
221            graphics = text.getDuplicate();
222        }
223        else if (!simulate)
224            throw new NullPointerException(MessageLocalization.getComposedMessage("verticaltext.go.with.simulate.eq.eq.false.and.text.eq.eq.null"));
225        int status = 0;
226        for (;;) {
227            if (maxLines <= 0) {
228                status = NO_MORE_COLUMN;
229                if (chunks.isEmpty())
230                    status |= NO_MORE_TEXT;
231                break;
232            }
233            if (chunks.isEmpty()) {
234                status = NO_MORE_TEXT;
235                break;
236            }
237            PdfLine line = createLine(height);
238            if (!simulate && !dirty) {
239                text.beginText();
240                dirty = true;
241            }
242            shortenChunkArray();
243            if (!simulate) {
244                text.setTextMatrix(startX, startY - line.indentLeft());
245                writeLine(line, text, graphics);
246            }
247            --maxLines;
248            startX -= leading;
249        }
250        if (dirty) {
251            text.endText();
252            text.add(graphics);
253        }
254        return status;
255    }
256
257    private Float curCharSpace = 0f;
258
259    void writeLine(PdfLine line, PdfContentByte text, PdfContentByte graphics) {
260        PdfFont currentFont = null;
261        PdfChunk chunk;
262        for (Iterator<PdfChunk> j = line.iterator(); j.hasNext(); ) {
263            chunk = j.next();
264
265            if (chunk.font().compareTo(currentFont) != 0) {
266                currentFont = chunk.font();
267                text.setFontAndSize(currentFont.getFont(), currentFont.size());
268            }
269            BaseColor color = chunk.color();
270            Float charSpace = (Float)chunk.getAttribute(Chunk.CHAR_SPACING);
271            // no char space setting means "leave it as is".
272            if (charSpace != null && !curCharSpace.equals(charSpace)) {
273                curCharSpace = charSpace.floatValue();
274                text.setCharacterSpacing(curCharSpace);
275            }
276            if (color != null)
277                text.setColorFill(color);
278            
279            text.showText(chunk.toString());
280            
281            if (color != null)
282                text.resetRGBColorFill();
283        }
284    }
285
286    /** Sets the new text origin.
287     * @param startX the X coordinate
288     * @param startY the Y coordinate
289     */
290    public void setOrigin(float startX, float startY) {
291        this.startX = startX;
292        this.startY = startY;
293    }
294
295    /** Gets the X coordinate where the next line will be written. This value will change
296     * after each call to <code>go()</code>.
297     * @return  the X coordinate
298     */
299    public float getOriginX() {
300        return startX;
301    }
302
303    /** Gets the Y coordinate where the next line will be written.
304     * @return  the Y coordinate
305     */
306    public float getOriginY() {
307        return startY;
308    }
309
310    /** Gets the maximum number of available lines. This value will change
311     * after each call to <code>go()</code>.
312     * @return Value of property maxLines.
313     */
314    public int getMaxLines() {
315        return maxLines;
316    }
317
318    /** Sets the maximum number of lines.
319     * @param maxLines the maximum number of lines
320     */
321    public void setMaxLines(int maxLines) {
322        this.maxLines = maxLines;
323    }
324
325    /** Gets the height of the line
326     * @return the height
327     */
328    public float getHeight() {
329        return height;
330    }
331
332    /** Sets the height of the line
333     * @param height the new height
334     */
335    public void setHeight(float height) {
336        this.height = height;
337    }
338
339    /**
340     * Sets the alignment.
341     * @param alignment the alignment
342     */
343    public void setAlignment(int alignment) {
344        this.alignment = alignment;
345    }
346
347    /**
348     * Gets the alignment.
349     * @return the alignment
350     */
351    public int getAlignment() {
352        return alignment;
353    }
354}