1 /*
2 * $Id: Phrase.java 3942 2009-05-28 18:14:10Z blowagie $
3 *
4 * Copyright 1999, 2000, 2001, 2002 by 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;
51
52 import java.util.ArrayList;
53 import java.util.Collection;
54 import java.util.Iterator;
55
56 import com.lowagie.text.pdf.HyphenationEvent;
57
58 /**
59 * A <CODE>Phrase</CODE> is a series of <CODE>Chunk</CODE>s.
60 * <P>
61 * A <CODE>Phrase</CODE> has a main <CODE>Font</CODE>, but some chunks
62 * within the phrase can have a <CODE>Font</CODE> that differs from the
63 * main <CODE>Font</CODE>. All the <CODE>Chunk</CODE>s in a <CODE>Phrase</CODE>
64 * have the same <CODE>leading</CODE>.
65 * <P>
66 * Example:
67 * <BLOCKQUOTE><PRE>
68 * // When no parameters are passed, the default leading = 16
69 * <STRONG>Phrase phrase0 = new Phrase();</STRONG>
70 * <STRONG>Phrase phrase1 = new Phrase("this is a phrase");</STRONG>
71 * // In this example the leading is passed as a parameter
72 * <STRONG>Phrase phrase2 = new Phrase(16, "this is a phrase with leading 16");</STRONG>
73 * // When a Font is passed (explicitly or embedded in a chunk), the default leading = 1.5 * size of the font
74 * <STRONG>Phrase phrase3 = new Phrase("this is a phrase with a red, normal font Courier, size 12", FontFactory.getFont(FontFactory.COURIER, 12, Font.NORMAL, new Color(255, 0, 0)));</STRONG>
75 * <STRONG>Phrase phrase4 = new Phrase(new Chunk("this is a phrase"));</STRONG>
76 * <STRONG>Phrase phrase5 = new Phrase(18, new Chunk("this is a phrase", FontFactory.getFont(FontFactory.HELVETICA, 16, Font.BOLD, new Color(255, 0, 0)));</STRONG>
77 * </PRE></BLOCKQUOTE>
78 *
79 * @see Element
80 * @see Chunk
81 * @see Paragraph
82 * @see Anchor
83 */
84
85 public class Phrase extends ArrayList implements TextElementArray {
86
87 // constants
88 private static final long serialVersionUID = 2643594602455068231L;
89
90 // membervariables
91 /** This is the leading of this phrase. */
92 protected float leading = Float.NaN;
93
94 /** This is the font of this phrase. */
95 protected Font font;
96
97 /** Null, unless the Phrase has to be hyphenated.
98 * @since 2.1.2
99 */
100 protected HyphenationEvent hyphenation = null;
101
102 // constructors
103
104 /**
105 * Constructs a <CODE>Phrase</CODE> without specifying a leading.
106 */
107 public Phrase() {
108 this(16);
109 }
110
111 /**
112 * Copy constructor for <CODE>Phrase</CODE>.
113 */
114 public Phrase(Phrase phrase) {
115 super();
116 this.addAll(phrase);
117 leading = phrase.getLeading();
118 font = phrase.getFont();
119 setHyphenation(phrase.getHyphenation());
120 }
121
122 /**
123 * Constructs a <CODE>Phrase</CODE> with a certain leading.
124 *
125 * @param leading the leading
126 */
127 public Phrase(float leading) {
128 this.leading = leading;
129 font = new Font();
130 }
131
132 /**
133 * Constructs a <CODE>Phrase</CODE> with a certain <CODE>Chunk</CODE>.
134 *
135 * @param chunk a <CODE>Chunk</CODE>
136 */
137 public Phrase(Chunk chunk) {
138 super.add(chunk);
139 font = chunk.getFont();
140 setHyphenation(chunk.getHyphenation());
141 }
142
143 /**
144 * Constructs a <CODE>Phrase</CODE> with a certain <CODE>Chunk</CODE>
145 * and a certain leading.
146 *
147 * @param leading the leading
148 * @param chunk a <CODE>Chunk</CODE>
149 */
150 public Phrase(float leading, Chunk chunk) {
151 this.leading = leading;
152 super.add(chunk);
153 font = chunk.getFont();
154 setHyphenation(chunk.getHyphenation());
155 }
156
157 /**
158 * Constructs a <CODE>Phrase</CODE> with a certain <CODE>String</CODE>.
159 *
160 * @param string a <CODE>String</CODE>
161 */
162 public Phrase(String string) {
163 this(Float.NaN, string, new Font());
164 }
165
166 /**
167 * Constructs a <CODE>Phrase</CODE> with a certain <CODE>String</CODE> and a certain <CODE>Font</CODE>.
168 *
169 * @param string a <CODE>String</CODE>
170 * @param font a <CODE>Font</CODE>
171 */
172 public Phrase(String string, Font font) {
173 this(Float.NaN, string, font);
174 }
175
176 /**
177 * Constructs a <CODE>Phrase</CODE> with a certain leading and a certain <CODE>String</CODE>.
178 *
179 * @param leading the leading
180 * @param string a <CODE>String</CODE>
181 */
182 public Phrase(float leading, String string) {
183 this(leading, string, new Font());
184 }
185
186 /**
187 * Constructs a <CODE>Phrase</CODE> with a certain leading, a certain <CODE>String</CODE>
188 * and a certain <CODE>Font</CODE>.
189 *
190 * @param leading the leading
191 * @param string a <CODE>String</CODE>
192 * @param font a <CODE>Font</CODE>
193 */
194 public Phrase(float leading, String string, Font font) {
195 this.leading = leading;
196 this.font = font;
197 /* bugfix by August Detlefsen */
198 if (string != null && string.length() != 0) {
199 super.add(new Chunk(string, font));
200 }
201 }
202
203 // implementation of the Element-methods
204
205 /**
206 * Processes the element by adding it (or the different parts) to an
207 * <CODE>ElementListener</CODE>.
208 *
209 * @param listener an <CODE>ElementListener</CODE>
210 * @return <CODE>true</CODE> if the element was processed successfully
211 */
212 public boolean process(ElementListener listener) {
213 try {
214 for (Iterator i = iterator(); i.hasNext(); ) {
215 listener.add((Element) i.next());
216 }
217 return true;
218 }
219 catch(DocumentException de) {
220 return false;
221 }
222 }
223
224 /**
225 * Gets the type of the text element.
226 *
227 * @return a type
228 */
229 public int type() {
230 return Element.PHRASE;
231 }
232
233 /**
234 * Gets all the chunks in this element.
235 *
236 * @return an <CODE>ArrayList</CODE>
237 */
238 public ArrayList getChunks() {
239 ArrayList tmp = new ArrayList();
240 for (Iterator i = iterator(); i.hasNext(); ) {
241 tmp.addAll(((Element) i.next()).getChunks());
242 }
243 return tmp;
244 }
245
246 /**
247 * @see com.lowagie.text.Element#isContent()
248 * @since iText 2.0.8
249 */
250 public boolean isContent() {
251 return true;
252 }
253
254 /**
255 * @see com.lowagie.text.Element#isNestable()
256 * @since iText 2.0.8
257 */
258 public boolean isNestable() {
259 return true;
260 }
261
262 // overriding some of the ArrayList-methods
263
264 /**
265 * Adds a <CODE>Chunk</CODE>, an <CODE>Anchor</CODE> or another <CODE>Phrase</CODE>
266 * to this <CODE>Phrase</CODE>.
267 *
268 * @param index index at which the specified element is to be inserted
269 * @param o an object of type <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or <CODE>Phrase</CODE>
270 * @throws ClassCastException when you try to add something that isn't a <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or <CODE>Phrase</CODE>
271 */
272 public void add(int index, Object o) {
273 if (o == null) return;
274 try {
275 Element element = (Element) o;
276 if (element.type() == Element.CHUNK) {
277 Chunk chunk = (Chunk) element;
278 if (!font.isStandardFont()) {
279 chunk.setFont(font.difference(chunk.getFont()));
280 }
281 if (hyphenation != null && chunk.getHyphenation() == null && !chunk.isEmpty()) {
282 chunk.setHyphenation(hyphenation);
283 }
284 super.add(index, chunk);
285 }
286 else if (element.type() == Element.PHRASE ||
287 element.type() == Element.ANCHOR ||
288 element.type() == Element.ANNOTATION ||
289 element.type() == Element.TABLE || // line added by David Freels
290 element.type() == Element.YMARK ||
291 element.type() == Element.MARKED) {
292 super.add(index, element);
293 }
294 else {
295 throw new ClassCastException(String.valueOf(element.type()));
296 }
297 }
298 catch(ClassCastException cce) {
299 throw new ClassCastException("Insertion of illegal Element: " + cce.getMessage());
300 }
301 }
302
303 /**
304 * Adds a <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or another <CODE>Phrase</CODE>
305 * to this <CODE>Phrase</CODE>.
306 *
307 * @param o an object of type <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or <CODE>Phrase</CODE>
308 * @return a boolean
309 * @throws ClassCastException when you try to add something that isn't a <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or <CODE>Phrase</CODE>
310 */
311 public boolean add(Object o) {
312 if (o == null) return false;
313 if (o instanceof String) {
314 return super.add(new Chunk((String) o, font));
315 }
316 if (o instanceof RtfElementInterface) {
317 return super.add(o);
318 }
319 try {
320 Element element = (Element) o;
321 switch(element.type()) {
322 case Element.CHUNK:
323 return addChunk((Chunk) o);
324 case Element.PHRASE:
325 case Element.PARAGRAPH:
326 Phrase phrase = (Phrase) o;
327 boolean success = true;
328 Element e;
329 for (Iterator i = phrase.iterator(); i.hasNext(); ) {
330 e = (Element) i.next();
331 if (e instanceof Chunk) {
332 success &= addChunk((Chunk)e);
333 }
334 else {
335 success &= this.add(e);
336 }
337 }
338 return success;
339 case Element.MARKED:
340 case Element.ANCHOR:
341 case Element.ANNOTATION:
342 case Element.TABLE: // case added by David Freels
343 case Element.PTABLE: // case added by mr. Karen Vardanyan
344 // This will only work for PDF!!! Not for RTF/HTML
345 case Element.LIST:
346 case Element.YMARK:
347 return super.add(o);
348 default:
349 throw new ClassCastException(String.valueOf(element.type()));
350 }
351 }
352 catch(ClassCastException cce) {
353 throw new ClassCastException("Insertion of illegal Element: " + cce.getMessage());
354 }
355 }
356
357 /**
358 * Adds a collection of <CODE>Chunk</CODE>s
359 * to this <CODE>Phrase</CODE>.
360 *
361 * @param collection a collection of <CODE>Chunk</CODE>s, <CODE>Anchor</CODE>s and <CODE>Phrase</CODE>s.
362 * @return <CODE>true</CODE> if the action succeeded, <CODE>false</CODE> if not.
363 * @throws ClassCastException when you try to add something that isn't a <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or <CODE>Phrase</CODE>
364 */
365 public boolean addAll(Collection collection) {
366 for (Iterator iterator = collection.iterator(); iterator.hasNext(); ) {
367 this.add(iterator.next());
368 }
369 return true;
370 }
371
372 /**
373 * Adds a Chunk.
374 * <p>
375 * This method is a hack to solve a problem I had with phrases that were split between chunks
376 * in the wrong place.
377 * @param chunk a Chunk to add to the Phrase
378 * @return true if adding the Chunk succeeded
379 */
380 protected boolean addChunk(Chunk chunk) {
381 Font f = chunk.getFont();
382 String c = chunk.getContent();
383 if (font != null && !font.isStandardFont()) {
384 f = font.difference(chunk.getFont());
385 }
386 if (size() > 0 && !chunk.hasAttributes()) {
387 try {
388 Chunk previous = (Chunk) get(size() - 1);
389 if (!previous.hasAttributes()
390 && (f == null
391 || f.compareTo(previous.getFont()) == 0)
392 && !"".equals(previous.getContent().trim())
393 && !"".equals(c.trim())) {
394 previous.append(c);
395 return true;
396 }
397 }
398 catch(ClassCastException cce) {
399 }
400 }
401 Chunk newChunk = new Chunk(c, f);
402 newChunk.setAttributes(chunk.getAttributes());
403 if (hyphenation != null && newChunk.getHyphenation() == null && !newChunk.isEmpty()) {
404 newChunk.setHyphenation(hyphenation);
405 }
406 return super.add(newChunk);
407 }
408
409 /**
410 * Adds a <CODE>Object</CODE> to the <CODE>Paragraph</CODE>.
411 *
412 * @param object the object to add.
413 */
414 protected void addSpecial(Object object) {
415 super.add(object);
416 }
417
418 // other methods that change the member variables
419
420 /**
421 * Sets the leading of this phrase.
422 *
423 * @param leading the new leading
424 */
425
426 public void setLeading(float leading) {
427 this.leading = leading;
428 }
429
430 /**
431 * Sets the main font of this phrase.
432 * @param font the new font
433 */
434 public void setFont(Font font) {
435 this.font = font;
436 }
437
438 // methods to retrieve information
439
440 /**
441 * Gets the leading of this phrase.
442 *
443 * @return the linespacing
444 */
445 public float getLeading() {
446 if (Float.isNaN(leading) && font != null) {
447 return font.getCalculatedLeading(1.5f);
448 }
449 return leading;
450 }
451
452 /**
453 * Checks you if the leading of this phrase is defined.
454 *
455 * @return true if the leading is defined
456 */
457 public boolean hasLeading() {
458 if (Float.isNaN(leading)) {
459 return false;
460 }
461 return true;
462 }
463
464 /**
465 * Gets the font of the first <CODE>Chunk</CODE> that appears in this <CODE>Phrase</CODE>.
466 *
467 * @return a <CODE>Font</CODE>
468 */
469 public Font getFont() {
470 return font;
471 }
472
473 /**
474 * Returns the content as a String object.
475 * This method differs from toString because toString will return an ArrayList with the toString value of the Chunks in this Phrase.
476 */
477 public String getContent() {
478 StringBuffer buf = new StringBuffer();
479 for (Iterator i = getChunks().iterator(); i.hasNext(); ) {
480 buf.append(i.next().toString());
481 }
482 return buf.toString();
483 }
484
485 /**
486 * Checks is this <CODE>Phrase</CODE> contains no or 1 empty <CODE>Chunk</CODE>.
487 *
488 * @return <CODE>false</CODE> if the <CODE>Phrase</CODE>
489 * contains more than one or more non-empty<CODE>Chunk</CODE>s.
490 */
491 public boolean isEmpty() {
492 switch(size()) {
493 case 0:
494 return true;
495 case 1:
496 Element element = (Element) get(0);
497 if (element.type() == Element.CHUNK && ((Chunk) element).isEmpty()) {
498 return true;
499 }
500 return false;
501 default:
502 return false;
503 }
504 }
505
506 /**
507 * Getter for the hyphenation settings.
508 * @return a HyphenationEvent
509 * @since 2.1.2
510 */
511 public HyphenationEvent getHyphenation() {
512 return hyphenation;
513 }
514
515 /**
516 * Setter for the hyphenation.
517 * @param hyphenation a HyphenationEvent instance
518 * @since 2.1.2
519 */
520 public void setHyphenation(HyphenationEvent hyphenation) {
521 this.hyphenation = hyphenation;
522 }
523
524 // kept for historical reasons; people should use FontSelector
525 // eligible for deprecation, but the methods are mentioned in the book p277.
526
527 /**
528 * Constructs a Phrase that can be used in the static getInstance() method.
529 * @param dummy a dummy parameter
530 */
531 private Phrase(boolean dummy) {
532 }
533
534 /**
535 * Gets a special kind of Phrase that changes some characters into corresponding symbols.
536 * @param string
537 * @return a newly constructed Phrase
538 */
539 public static final Phrase getInstance(String string) {
540 return getInstance(16, string, new Font());
541 }
542
543 /**
544 * Gets a special kind of Phrase that changes some characters into corresponding symbols.
545 * @param leading
546 * @param string
547 * @return a newly constructed Phrase
548 */
549 public static final Phrase getInstance(int leading, String string) {
550 return getInstance(leading, string, new Font());
551 }
552
553 /**
554 * Gets a special kind of Phrase that changes some characters into corresponding symbols.
555 * @param leading
556 * @param string
557 * @param font
558 * @return a newly constructed Phrase
559 */
560 public static final Phrase getInstance(int leading, String string, Font font) {
561 Phrase p = new Phrase(true);
562 p.setLeading(leading);
563 p.font = font;
564 if (font.getFamily() != Font.SYMBOL && font.getFamily() != Font.ZAPFDINGBATS && font.getBaseFont() == null) {
565 int index;
566 while((index = SpecialSymbol.index(string)) > -1) {
567 if (index > 0) {
568 String firstPart = string.substring(0, index);
569 ((ArrayList)p).add(new Chunk(firstPart, font));
570 string = string.substring(index);
571 }
572 Font symbol = new Font(Font.SYMBOL, font.getSize(), font.getStyle(), font.getColor());
573 StringBuffer buf = new StringBuffer();
574 buf.append(SpecialSymbol.getCorrespondingSymbol(string.charAt(0)));
575 string = string.substring(1);
576 while (SpecialSymbol.index(string) == 0) {
577 buf.append(SpecialSymbol.getCorrespondingSymbol(string.charAt(0)));
578 string = string.substring(1);
579 }
580 ((ArrayList)p).add(new Chunk(buf.toString(), symbol));
581 }
582 }
583 if (string != null && string.length() != 0) {
584 ((ArrayList)p).add(new Chunk(string, font));
585 }
586 return p;
587 }
588
589 }