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 }