1 /*
2  * $Id: PdfLine.java 3994 2009-06-24 13:08:04Z blowagie $
3  *
4  * Copyright 1999, 2000, 2001, 2002 Bruno Lowagie
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
52 import java.util.ArrayList;
53 import java.util.Iterator;
54
55 import com.lowagie.text.Chunk;
56 import com.lowagie.text.Element;
57 import com.lowagie.text.ListItem;
58
59 /**
60  * <CODE>PdfLine</CODE> defines an array with <CODE>PdfChunk</CODE>-objects
61  * that fit into 1 line.
62  */

63
64 public class PdfLine {
65     
66     // membervariables
67     
68     /** The arraylist containing the chunks. */
69     protected ArrayList line;
70     
71     /** The left indentation of the line. */
72     protected float left;
73     
74     /** The width of the line. */
75     protected float width;
76     
77     /** The alignment of the line. */
78     protected int alignment;
79     
80     /** The height of the line. */
81     protected float height;
82     
83     /** The listsymbol (if necessary). */
84     protected Chunk listSymbol = null;
85     
86     /** The listsymbol (if necessary). */
87     protected float symbolIndent;
88     
89     /** <CODE>true</CODE> if the chunk splitting was caused by a newline. */
90     protected boolean newlineSplit = false;
91     
92     /** The original width. */
93     protected float originalWidth;
94     
95     protected boolean isRTL = false;
96     
97     // constructors
98     
99     /**
100      * Constructs a new <CODE>PdfLine</CODE>-object.
101      *
102      * @param    left        the limit of the line at the left
103      * @param    right        the limit of the line at the right
104      * @param    alignment    the alignment of the line
105      * @param    height        the height of the line
106      */

107     
108     PdfLine(float left, float right, int alignment, float height) {
109         this.left = left;
110         this.width = right - left;
111         this.originalWidth = this.width;
112         this.alignment = alignment;
113         this.height = height;
114         this.line = new ArrayList();
115     }
116     
117     /**
118      * Creates a PdfLine object.
119      * @param left                the left offset
120      * @param originalWidth        the original width of the line
121      * @param remainingWidth    bigger than 0 if the line isn't completely filled
122      * @param alignment            the alignment of the line
123      * @param newlineSplit        was the line splitted (or does the paragraph end with this line)
124      * @param line                an array of PdfChunk objects
125      * @param isRTL                do you have to read the line from Right to Left?
126      */

127     PdfLine(float left, float originalWidth, float remainingWidth, int alignment, boolean newlineSplit, ArrayList line, boolean isRTL) {
128         this.left = left;
129         this.originalWidth = originalWidth;
130         this.width = remainingWidth;
131         this.alignment = alignment;
132         this.line = line;
133         this.newlineSplit = newlineSplit;
134         this.isRTL = isRTL;
135     }
136     
137     // methods
138     
139     /**
140      * Adds a <CODE>PdfChunk</CODE> to the <CODE>PdfLine</CODE>.
141      *
142      * @param        chunk        the <CODE>PdfChunk</CODE> to add
143      * @return        <CODE>null</CODE> if the chunk could be added completely; if not
144      *                a <CODE>PdfChunk</CODE> containing the part of the chunk that could
145      *                not be added is returned
146      */

147     
148     PdfChunk add(PdfChunk chunk) {
149         // nothing happens if the chunk is null.
150         if (chunk == null || chunk.toString().equals("")) {
151             return null;
152         }
153         
154         // we split the chunk to be added
155         PdfChunk overflow = chunk.split(width);
156         newlineSplit = (chunk.isNewlineSplit() || overflow == null);
157         //        if (chunk.isNewlineSplit() && alignment == Element.ALIGN_JUSTIFIED)
158         //            alignment = Element.ALIGN_LEFT;
159         if (chunk.isTab()) {
160             Object[] tab = (Object[])chunk.getAttribute(Chunk.TAB);
161             float tabPosition = ((Float)tab[1]).floatValue();
162             boolean newline = ((Boolean)tab[2]).booleanValue();
163             if (newline && tabPosition < originalWidth - width) {
164                 return chunk;
165             }
166             width = originalWidth - tabPosition;
167             chunk.adjustLeft(left);
168             addToLine(chunk);
169         }
170         // if the length of the chunk > 0 we add it to the line
171         else if (chunk.length() > 0 || chunk.isImage()) {
172             if (overflow != null)
173                 chunk.trimLastSpace();
174             width -= chunk.width();
175             addToLine(chunk);
176         }
177         // if the length == 0 and there were no other chunks added to the line yet,
178         // we risk to end up in an endless loop trying endlessly to add the same chunk
179         else if (line.size() < 1) {
180             chunk = overflow;
181             overflow = chunk.truncate(width);
182             width -= chunk.width();
183             if (chunk.length() > 0) {
184                 addToLine(chunk);
185                 return overflow;
186             }
187             // if the chunk couldn't even be truncated, we add everything, so be it
188             else {
189                 if (overflow != null)
190                     addToLine(overflow);
191                 return null;
192             }
193         }
194         else {
195             width += ((PdfChunk)(line.get(line.size() - 1))).trimLastSpace();
196         }
197         return overflow;
198     }
199     
200     private void addToLine(PdfChunk chunk) {
201         if (chunk.changeLeading && chunk.isImage()) {
202             float f = chunk.getImage().getScaledHeight() + chunk.getImageOffsetY() + chunk.getImage().getBorderWidthTop();
203             if (f > height) height = f;
204         }
205         line.add(chunk);
206     }
207     
208     // methods to retrieve information
209     
210     /**
211      * Returns the number of chunks in the line.
212      *
213      * @return    a value
214      */

215     
216     public int size() {
217         return line.size();
218     }
219     
220     /**
221      * Returns an iterator of <CODE>PdfChunk</CODE>s.
222      *
223      * @return    an <CODE>Iterator</CODE>
224      */

225     
226     public Iterator iterator() {
227         return line.iterator();
228     }
229     
230     /**
231      * Returns the height of the line.
232      *
233      * @return    a value
234      */

235     
236     float height() {
237         return height;
238     }
239     
240     /**
241      * Returns the left indentation of the line taking the alignment of the line into account.
242      *
243      * @return    a value
244      */

245     
246     float indentLeft() {
247         if (isRTL) {
248             switch (alignment) {
249                 case Element.ALIGN_LEFT:
250                     return left + width;
251                 case Element.ALIGN_CENTER:
252                     return left + (width / 2f);
253                 default:
254                     return left;
255             }
256         }
257         else if (this.getSeparatorCount() == 0) {
258             switch (alignment) {
259                 case Element.ALIGN_RIGHT:
260                     return left + width;
261                 case Element.ALIGN_CENTER:
262                     return left + (width / 2f);
263             }
264         }
265         return left;
266     }
267     
268     /**
269      * Checks if this line has to be justified.
270      *
271      * @return    <CODE>true</CODE> if the alignment equals <VAR>ALIGN_JUSTIFIED</VAR> and there is some width left.
272      */

273     
274     public boolean hasToBeJustified() {
275         return ((alignment == Element.ALIGN_JUSTIFIED || alignment == Element.ALIGN_JUSTIFIED_ALL) && width != 0);
276     }
277     
278     /**
279      * Resets the alignment of this line.
280      * <P>
281      * The alignment of the last line of for instance a <CODE>Paragraph</CODE>
282      * that has to be justified, has to be reset to <VAR>ALIGN_LEFT</VAR>.
283      */

284     
285     public void resetAlignment() {
286         if (alignment == Element.ALIGN_JUSTIFIED) {
287             alignment = Element.ALIGN_LEFT;
288         }
289     }
290     
291     /** Adds extra indentation to the left (for Paragraph.setFirstLineIndent). */
292     void setExtraIndent(float extra) {
293         left += extra;
294         width -= extra;
295     }
296     
297     /**
298      * Returns the width that is left, after a maximum of characters is added to the line.
299      *
300      * @return    a value
301      */

302     
303     float widthLeft() {
304         return width;
305     }
306     
307     /**
308      * Returns the number of space-characters in this line.
309      *
310      * @return    a value
311      */

312     
313     int numberOfSpaces() {
314         String string = toString();
315         int length = string.length();
316         int numberOfSpaces = 0;
317         for (int i = 0; i < length; i++) {
318             if (string.charAt(i) == ' ') {
319                 numberOfSpaces++;
320             }
321         }
322         return numberOfSpaces;
323     }
324     
325     /**
326      * Sets the listsymbol of this line.
327      * <P>
328      * This is only necessary for the first line of a <CODE>ListItem</CODE>.
329      *
330      * @param listItem the list symbol
331      */

332     
333     public void setListItem(ListItem listItem) {
334         this.listSymbol = listItem.getListSymbol();
335         this.symbolIndent = listItem.getIndentationLeft();
336     }
337     
338     /**
339      * Returns the listsymbol of this line.
340      *
341      * @return    a <CODE>PdfChunk</CODE> if the line has a listsymbol; <CODE>null</CODE> otherwise
342      */

343     
344     public Chunk listSymbol() {
345         return listSymbol;
346     }
347     
348     /**
349      * Return the indentation needed to show the listsymbol.
350      *
351      * @return    a value
352      */

353     
354     public float listIndent() {
355         return symbolIndent;
356     }
357     
358     /**
359      * Get the string representation of what is in this line.
360      *
361      * @return    a <CODE>String</CODE>
362      */

363     
364     public String toString() {
365         StringBuffer tmp = new StringBuffer();
366         for (Iterator i = line.iterator(); i.hasNext(); ) {
367             tmp.append(((PdfChunk) i.next()).toString());
368         }
369         return tmp.toString();
370     }
371     
372     /**
373      * Returns the length of a line in UTF32 characters
374      * @return    the length in UTF32 characters
375      * @since    2.1.2
376      */

377     public int GetLineLengthUtf32() {
378         int total = 0;
379         for (Iterator i = line.iterator(); i.hasNext();) {
380             total += ((PdfChunk)i.next()).lengthUtf32();
381         }
382         return total;
383     }
384     
385     /**
386      * Checks if a newline caused the line split.
387      * @return <CODE>true</CODE> if a newline caused the line split
388      */

389     public boolean isNewlineSplit() {
390         return newlineSplit && (alignment != Element.ALIGN_JUSTIFIED_ALL);
391     }
392     
393     /**
394      * Gets the index of the last <CODE>PdfChunk</CODE> with metric attributes
395      * @return the last <CODE>PdfChunk</CODE> with metric attributes
396      */

397     public int getLastStrokeChunk() {
398         int lastIdx = line.size() - 1;
399         for (; lastIdx >= 0; --lastIdx) {
400             PdfChunk chunk = (PdfChunk)line.get(lastIdx);
401             if (chunk.isStroked())
402                 break;
403         }
404         return lastIdx;
405     }
406     
407     /**
408      * Gets a <CODE>PdfChunk</CODE> by index.
409      * @param idx the index
410      * @return the <CODE>PdfChunk</CODE> or null if beyond the array
411      */

412     public PdfChunk getChunk(int idx) {
413         if (idx < 0 || idx >= line.size())
414             return null;
415         return (PdfChunk)line.get(idx);
416     }
417     
418     /**
419      * Gets the original width of the line.
420      * @return the original width of the line
421      */

422     public float getOriginalWidth() {
423         return originalWidth;
424     }
425     
426     /*
427      * Gets the maximum size of all the fonts used in this line
428      * including images.
429      * @return maximum size of all the fonts used in this line
430      float getMaxSizeSimple() {
431         float maxSize = 0;
432         PdfChunk chunk;
433         for (int k = 0; k < line.size(); ++k) {
434             chunk = (PdfChunk)line.get(k);
435             if (!chunk.isImage()) {
436                 maxSize = Math.max(chunk.font().size(), maxSize);
437             }
438             else {
439                 maxSize = Math.max(chunk.getImage().getScaledHeight() + chunk.getImageOffsetY() , maxSize);
440             }
441         }
442         return maxSize;
443     }*/

444     
445     /**
446      * Gets the difference between the "normal" leading and the maximum
447      * size (for instance when there are images in the chunk).
448      * @return    an extra leading for images
449      * @since    2.1.5
450      */

451     float[] getMaxSize() {
452         float normal_leading = 0;
453         float image_leading = -10000;
454         PdfChunk chunk;
455         for (int k = 0; k < line.size(); ++k) {
456             chunk = (PdfChunk)line.get(k);
457             if (!chunk.isImage()) {
458                 normal_leading = Math.max(chunk.font().size(), normal_leading);
459             }
460             else {
461                 image_leading = Math.max(chunk.getImage().getScaledHeight() + chunk.getImageOffsetY(), image_leading);
462             }
463         }
464         return new float[]{normal_leading, image_leading};
465     }
466     
467     boolean isRTL() {
468         return isRTL;
469     }
470     
471     /**
472      * Gets the number of separators in the line.
473      * @return    the number of separators in the line
474      * @since    2.1.2
475      */

476     int getSeparatorCount() {
477         int s = 0;
478         PdfChunk ck;
479         for (Iterator i = line.iterator(); i.hasNext(); ) {
480             ck = (PdfChunk)i.next();
481             if (ck.isTab()) {
482                 return 0;
483             }
484             if (ck.isHorizontalSeparator()) {
485                 s++;
486             }
487         }
488         return s;
489     }
490     
491     /**
492      * Gets a width corrected with a charSpacing and wordSpacing.
493      * @param charSpacing
494      * @param wordSpacing
495      * @return a corrected width
496      */

497     public float getWidthCorrected(float charSpacing, float wordSpacing) {
498         float total = 0;
499         for (int k = 0; k < line.size(); ++k) {
500             PdfChunk ck = (PdfChunk)line.get(k);
501             total += ck.getWidthCorrected(charSpacing, wordSpacing);
502         }
503         return total;
504     }
505     
506 /**
507  * Gets the maximum size of the ascender for all the fonts used
508  * in this line.
509  * @return maximum size of all the ascenders used in this line
510  */

511    public float getAscender() {
512        float ascender = 0;
513        for (int k = 0; k < line.size(); ++k) {
514            PdfChunk ck = (PdfChunk)line.get(k);
515            if (ck.isImage())
516                ascender = Math.max(ascender, ck.getImage().getScaledHeight() + ck.getImageOffsetY());
517            else {
518                PdfFont font = ck.font();
519                ascender = Math.max(ascender, font.getFont().getFontDescriptor(BaseFont.ASCENT, font.size()));
520            }
521        }
522        return ascender;
523    }
524
525 /**
526  * Gets the biggest descender for all the fonts used 
527  * in this line.  Note that this is a negative number.
528  * @return maximum size of all the ascenders used in this line
529  */

530     public float getDescender() {
531         float descender = 0;
532         for (int k = 0; k < line.size(); ++k) {
533             PdfChunk ck = (PdfChunk)line.get(k);
534             if (ck.isImage())
535                 descender = Math.min(descender, ck.getImageOffsetY());
536             else {
537                 PdfFont font = ck.font();
538                 descender = Math.min(descender, font.getFont().getFontDescriptor(BaseFont.DESCENT, font.size()));
539             }
540         }
541         return descender;
542     }
543 }