001: /*
002: * $Id: Phrase.java 2904 2007-08-30 17:18:07Z psoares33 $
003: * $Name: $
004: *
005: * Copyright 1999, 2000, 2001, 2002 by Bruno Lowagie.
006: *
007: * The contents of this file are subject to the Mozilla Public License Version 1.1
008: * (the "License"); you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
013: * for the specific language governing rights and limitations under the License.
014: *
015: * The Original Code is 'iText, a free JAVA-PDF library'.
016: *
017: * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
018: * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
019: * All Rights Reserved.
020: * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
021: * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
022: *
023: * Contributor(s): all the names of the contributors are added in the source code
024: * where applicable.
025: *
026: * Alternatively, the contents of this file may be used under the terms of the
027: * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
028: * provisions of LGPL are applicable instead of those above. If you wish to
029: * allow use of your version of this file only under the terms of the LGPL
030: * License and not to allow others to use your version of this file under
031: * the MPL, indicate your decision by deleting the provisions above and
032: * replace them with the notice and other provisions required by the LGPL.
033: * If you do not delete the provisions above, a recipient may use your version
034: * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
035: *
036: * This library is free software; you can redistribute it and/or modify it
037: * under the terms of the MPL as stated above or under the terms of the GNU
038: * Library General Public License as published by the Free Software Foundation;
039: * either version 2 of the License, or any later version.
040: *
041: * This library is distributed in the hope that it will be useful, but WITHOUT
042: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
043: * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
044: * details.
045: *
046: * If you didn't download this code from the following link, you should check if
047: * you aren't using an obsolete version:
048: * http://www.lowagie.com/iText/
049: */
050:
051: package com.lowagie.text;
052:
053: import java.util.ArrayList;
054: import java.util.Collection;
055: import java.util.Iterator;
056: import java.util.Properties;
057:
058: /**
059: * A <CODE>Phrase</CODE> is a series of <CODE>Chunk</CODE>s.
060: * <P>
061: * A <CODE>Phrase</CODE> has a main <CODE>Font</CODE>, but some chunks
062: * within the phrase can have a <CODE>Font</CODE> that differs from the
063: * main <CODE>Font</CODE>. All the <CODE>Chunk</CODE>s in a <CODE>Phrase</CODE>
064: * have the same <CODE>leading</CODE>.
065: * <P>
066: * Example:
067: * <BLOCKQUOTE><PRE>
068: * // When no parameters are passed, the default leading = 16
069: * <STRONG>Phrase phrase0 = new Phrase();</STRONG>
070: * <STRONG>Phrase phrase1 = new Phrase("this is a phrase");</STRONG>
071: * // In this example the leading is passed as a parameter
072: * <STRONG>Phrase phrase2 = new Phrase(16, "this is a phrase with leading 16");</STRONG>
073: * // When a Font is passed (explicitely or embedded in a chunk), the default leading = 1.5 * size of the font
074: * <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>
075: * <STRONG>Phrase phrase4 = new Phrase(new Chunk("this is a phrase"));</STRONG>
076: * <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>
077: * </PRE></BLOCKQUOTE>
078: *
079: * @see Element
080: * @see Chunk
081: * @see Paragraph
082: * @see Anchor
083: */
084:
085: public class Phrase extends ArrayList implements TextElementArray {
086:
087: // constants
088: private static final long serialVersionUID = 2643594602455068231L;
089:
090: // membervariables
091: /** This is the leading of this phrase. */
092: protected float leading = Float.NaN;
093:
094: /** This is the font of this phrase. */
095: protected Font font;
096:
097: // constructors
098:
099: /**
100: * Constructs a <CODE>Phrase</CODE> without specifying a leading.
101: */
102: public Phrase() {
103: this (16);
104: }
105:
106: /**
107: * Copy constructor for <CODE>Phrase</CODE>.
108: */
109: public Phrase(Phrase phrase) {
110: super (phrase);
111: leading = phrase.getLeading();
112: font = phrase.getFont();
113: }
114:
115: /**
116: * Constructs a <CODE>Phrase</CODE> with a certain leading.
117: *
118: * @param leading the leading
119: */
120: public Phrase(float leading) {
121: this .leading = leading;
122: font = new Font();
123: }
124:
125: /**
126: * Constructs a <CODE>Phrase</CODE> with a certain <CODE>Chunk</CODE>.
127: *
128: * @param chunk a <CODE>Chunk</CODE>
129: */
130: public Phrase(Chunk chunk) {
131: super .add(chunk);
132: font = chunk.getFont();
133: }
134:
135: /**
136: * Constructs a <CODE>Phrase</CODE> with a certain <CODE>Chunk</CODE>
137: * and a certain leading.
138: *
139: * @param leading the leading
140: * @param chunk a <CODE>Chunk</CODE>
141: */
142: public Phrase(float leading, Chunk chunk) {
143: this .leading = leading;
144: super .add(chunk);
145: font = chunk.getFont();
146: }
147:
148: /**
149: * Constructs a <CODE>Phrase</CODE> with a certain <CODE>String</CODE>.
150: *
151: * @param string a <CODE>String</CODE>
152: */
153: public Phrase(String string) {
154: this (Float.NaN, string, new Font());
155: }
156:
157: /**
158: * Constructs a <CODE>Phrase</CODE> with a certain <CODE>String</CODE> and a certain <CODE>Font</CODE>.
159: *
160: * @param string a <CODE>String</CODE>
161: * @param font a <CODE>Font</CODE>
162: */
163: public Phrase(String string, Font font) {
164: this (Float.NaN, string, font);
165: }
166:
167: /**
168: * Constructs a <CODE>Phrase</CODE> with a certain leading and a certain <CODE>String</CODE>.
169: *
170: * @param leading the leading
171: * @param string a <CODE>String</CODE>
172: */
173: public Phrase(float leading, String string) {
174: this (leading, string, new Font());
175: }
176:
177: /**
178: * Constructs a <CODE>Phrase</CODE> with a certain leading, a certain <CODE>String</CODE>
179: * and a certain <CODE>Font</CODE>.
180: *
181: * @param leading the leading
182: * @param string a <CODE>String</CODE>
183: * @param font a <CODE>Font</CODE>
184: */
185: public Phrase(float leading, String string, Font font) {
186: this .leading = leading;
187: this .font = font;
188: /* bugfix by August Detlefsen */
189: if (string != null && string.length() != 0) {
190: super .add(new Chunk(string, font));
191: }
192: }
193:
194: // implementation of the Element-methods
195:
196: /**
197: * Processes the element by adding it (or the different parts) to an
198: * <CODE>ElementListener</CODE>.
199: *
200: * @param listener an <CODE>ElementListener</CODE>
201: * @return <CODE>true</CODE> if the element was processed successfully
202: */
203: public boolean process(ElementListener listener) {
204: try {
205: for (Iterator i = iterator(); i.hasNext();) {
206: listener.add((Element) i.next());
207: }
208: return true;
209: } catch (DocumentException de) {
210: return false;
211: }
212: }
213:
214: /**
215: * Gets the type of the text element.
216: *
217: * @return a type
218: */
219: public int type() {
220: return Element.PHRASE;
221: }
222:
223: /**
224: * Gets all the chunks in this element.
225: *
226: * @return an <CODE>ArrayList</CODE>
227: */
228: public ArrayList getChunks() {
229: ArrayList tmp = new ArrayList();
230: for (Iterator i = iterator(); i.hasNext();) {
231: tmp.addAll(((Element) i.next()).getChunks());
232: }
233: return tmp;
234: }
235:
236: // overriding some of the ArrayList-methods
237:
238: /**
239: * Adds a <CODE>Chunk</CODE>, an <CODE>Anchor</CODE> or another <CODE>Phrase</CODE>
240: * to this <CODE>Phrase</CODE>.
241: *
242: * @param index index at which the specified element is to be inserted
243: * @param o an object of type <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or <CODE>Phrase</CODE>
244: * @throws ClassCastException when you try to add something that isn't a <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or <CODE>Phrase</CODE>
245: */
246: public void add(int index, Object o) {
247: if (o == null)
248: return;
249: try {
250: Element element = (Element) o;
251: if (element.type() == Element.CHUNK) {
252: Chunk chunk = (Chunk) element;
253: if (!font.isStandardFont()) {
254: chunk.setFont(font.difference(chunk.getFont()));
255: }
256: super .add(index, chunk);
257: } else if (element.type() == Element.PHRASE
258: || element.type() == Element.ANCHOR
259: || element.type() == Element.ANNOTATION
260: || element.type() == Element.TABLE || // line added by David Freels
261: element.type() == Element.MARKED) {
262: super .add(index, element);
263: } else {
264: throw new ClassCastException(String.valueOf(element
265: .type()));
266: }
267: } catch (ClassCastException cce) {
268: throw new ClassCastException(
269: "Insertion of illegal Element: " + cce.getMessage());
270: }
271: }
272:
273: /**
274: * Adds a <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or another <CODE>Phrase</CODE>
275: * to this <CODE>Phrase</CODE>.
276: *
277: * @param o an object of type <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or <CODE>Phrase</CODE>
278: * @return a boolean
279: * @throws ClassCastException when you try to add something that isn't a <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or <CODE>Phrase</CODE>
280: */
281: public boolean add(Object o) {
282: if (o == null)
283: return false;
284: if (o instanceof String) {
285: return super .add(new Chunk((String) o, font));
286: }
287: try {
288: Element element = (Element) o;
289: switch (element.type()) {
290: case Element.CHUNK:
291: return addChunk((Chunk) o);
292: case Element.PHRASE:
293: case Element.PARAGRAPH:
294: Phrase phrase = (Phrase) o;
295: boolean success = true;
296: Element e;
297: for (Iterator i = phrase.iterator(); i.hasNext();) {
298: e = (Element) i.next();
299: if (e instanceof Chunk) {
300: success &= addChunk((Chunk) e);
301: } else {
302: success &= this .add(e);
303: }
304: }
305: return success;
306: case Element.MARKED:
307: case Element.ANCHOR:
308: case Element.ANNOTATION:
309: case Element.TABLE: // case added by David Freels
310: case Element.PTABLE: // case added by mr. Karen Vardanyan
311: // This will only work for PDF!!! Not for RTF/HTML
312: case Element.LIST:
313: return super .add(o);
314: default:
315: throw new ClassCastException(String.valueOf(element
316: .type()));
317: }
318: } catch (ClassCastException cce) {
319: throw new ClassCastException(
320: "Insertion of illegal Element: " + cce.getMessage());
321: }
322: }
323:
324: /**
325: * Adds a collection of <CODE>Chunk</CODE>s
326: * to this <CODE>Phrase</CODE>.
327: *
328: * @param collection a collection of <CODE>Chunk</CODE>s, <CODE>Anchor</CODE>s and <CODE>Phrase</CODE>s.
329: * @return <CODE>true</CODE> if the action succeeded, <CODE>false</CODE> if not.
330: * @throws ClassCastException when you try to add something that isn't a <CODE>Chunk</CODE>, <CODE>Anchor</CODE> or <CODE>Phrase</CODE>
331: */
332: public boolean addAll(Collection collection) {
333: for (Iterator iterator = collection.iterator(); iterator
334: .hasNext();) {
335: this .add(iterator.next());
336: }
337: return true;
338: }
339:
340: /**
341: * Adds a Chunk.
342: * <p>
343: * This method is a hack to solve a problem I had with phrases that were split between chunks
344: * in the wrong place.
345: * @param chunk a Chunk to add to the Phrase
346: * @return true if adding the Chunk succeeded
347: */
348: protected boolean addChunk(Chunk chunk) {
349: Font f = chunk.getFont();
350: String c = chunk.getContent();
351: if (!font.isStandardFont()) {
352: f = font.difference(chunk.getFont());
353: }
354: if (size() > 0 && !chunk.hasAttributes()) {
355: try {
356: Chunk previous = (Chunk) get(size() - 1);
357: if (!previous.hasAttributes()
358: && previous.getFont().compareTo(f) == 0
359: && !"".equals(previous.getContent().trim())
360: && !"".equals(c.trim())) {
361: previous.append(c);
362: return true;
363: }
364: } catch (ClassCastException cce) {
365: }
366: }
367: Chunk newChunk = new Chunk(c, f);
368: newChunk.setAttributes(chunk.getAttributes());
369: return super .add(newChunk);
370: }
371:
372: /**
373: * Adds a <CODE>Object</CODE> to the <CODE>Paragraph</CODE>.
374: *
375: * @param object the object to add.
376: */
377: protected void addSpecial(Object object) {
378: super .add(object);
379: }
380:
381: // other methods that change the member variables
382:
383: /**
384: * Sets the leading of this phrase.
385: *
386: * @param leading the new leading
387: */
388:
389: public void setLeading(float leading) {
390: this .leading = leading;
391: }
392:
393: /**
394: * Sets the main font of this phrase.
395: * @param font the new font
396: */
397: public void setFont(Font font) {
398: this .font = font;
399: }
400:
401: // methods to retrieve information
402:
403: /**
404: * Gets the leading of this phrase.
405: *
406: * @return the linespacing
407: */
408: public float getLeading() {
409: if (Float.isNaN(leading)) {
410: return font.getCalculatedLeading(1.5f);
411: }
412: return leading;
413: }
414:
415: /**
416: * Checks you if the leading of this phrase is defined.
417: *
418: * @return true if the leading is defined
419: */
420: public boolean hasLeading() {
421: if (Float.isNaN(leading)) {
422: return false;
423: }
424: return true;
425: }
426:
427: /**
428: * Gets the font of the first <CODE>Chunk</CODE> that appears in this <CODE>Phrase</CODE>.
429: *
430: * @return a <CODE>Font</CODE>
431: */
432: public Font getFont() {
433: return font;
434: }
435:
436: /**
437: * Returns the content as a String object.
438: * This method differs from toString because toString will return an ArrayList with the toString value of the Chunks in this Phrase.
439: */
440: public String getContent() {
441: StringBuffer buf = new StringBuffer();
442: for (Iterator i = getChunks().iterator(); i.hasNext();) {
443: buf.append(i.next().toString());
444: }
445: return buf.toString();
446: }
447:
448: /**
449: * Checks is this <CODE>Phrase</CODE> contains no or 1 empty <CODE>Chunk</CODE>.
450: *
451: * @return <CODE>false</CODE> if the <CODE>Phrase</CODE>
452: * contains more than one or more non-empty<CODE>Chunk</CODE>s.
453: */
454: public boolean isEmpty() {
455: switch (size()) {
456: case 0:
457: return true;
458: case 1:
459: Element element = (Element) get(0);
460: if (element.type() == Element.CHUNK
461: && ((Chunk) element).isEmpty()) {
462: return true;
463: }
464: return false;
465: default:
466: return false;
467: }
468: }
469:
470: // kept for historical reasons; people should use FontSelector
471: // eligible for deprecation, but the methods are mentioned in the book p277.
472:
473: /**
474: * Constructs a Phrase that can be used in the static getInstance() method.
475: * @param dummy a dummy parameter
476: */
477: private Phrase(boolean dummy) {
478: }
479:
480: /**
481: * Gets a special kind of Phrase that changes some characters into corresponding symbols.
482: * @param string
483: * @return a newly constructed Phrase
484: */
485: public static final Phrase getInstance(String string) {
486: return getInstance(16, string, new Font());
487: }
488:
489: /**
490: * Gets a special kind of Phrase that changes some characters into corresponding symbols.
491: * @param leading
492: * @param string
493: * @return a newly constructed Phrase
494: */
495: public static final Phrase getInstance(int leading, String string) {
496: return getInstance(leading, string, new Font());
497: }
498:
499: /**
500: * Gets a special kind of Phrase that changes some characters into corresponding symbols.
501: * @param leading
502: * @param string
503: * @param font
504: * @return a newly constructed Phrase
505: */
506: public static final Phrase getInstance(int leading, String string,
507: Font font) {
508: Phrase p = new Phrase(true);
509: p.setLeading(leading);
510: p.font = font;
511: if (font.getFamily() != Font.SYMBOL
512: && font.getFamily() != Font.ZAPFDINGBATS
513: && font.getBaseFont() == null) {
514: int index;
515: while ((index = SpecialSymbol.index(string)) > -1) {
516: if (index > 0) {
517: String firstPart = string.substring(0, index);
518: ((ArrayList) p).add(new Chunk(firstPart, font));
519: string = string.substring(index);
520: }
521: Font symbol = new Font(Font.SYMBOL, font.getSize(),
522: font.getStyle(), font.getColor());
523: StringBuffer buf = new StringBuffer();
524: buf.append(SpecialSymbol.getCorrespondingSymbol(string
525: .charAt(0)));
526: string = string.substring(1);
527: while (SpecialSymbol.index(string) == 0) {
528: buf.append(SpecialSymbol
529: .getCorrespondingSymbol(string.charAt(0)));
530: string = string.substring(1);
531: }
532: ((ArrayList) p).add(new Chunk(buf.toString(), symbol));
533: }
534: }
535: if (string != null && string.length() != 0) {
536: ((ArrayList) p).add(new Chunk(string, font));
537: }
538: return p;
539: }
540:
541: // deprecated constructor and methods
542:
543: /**
544: * Returns a <CODE>Phrase</CODE> that has been constructed taking in account
545: * the value of some <VAR>attributes</VAR>.
546: *
547: * @param attributes Some attributes
548: */
549: public Phrase(Properties attributes) {
550: this (com.lowagie.text.factories.ElementFactory
551: .getPhrase(attributes));
552: }
553:
554: /**
555: * Gets the font of the first <CODE>Chunk</CODE> that appears in this <CODE>Phrase</CODE>.
556: *
557: * @return a <CODE>Font</CODE>
558: * @deprecated Use {@link #getFont()} instead
559: */
560: public Font font() {
561: return getFont();
562: }
563:
564: /**
565: * Gets the leading of this phrase.
566: *
567: * @return the linespacing
568: * @deprecated Use {@link #getLeading()} instead
569: */
570: public float leading() {
571: return getLeading();
572: }
573:
574: /**
575: * Checks you if the leading of this phrase is defined.
576: *
577: * @return true if the leading is defined
578: * @deprecated Use {@link #hasLeading()} instead
579: */
580: public boolean leadingDefined() {
581: return hasLeading();
582: }
583:
584: /**
585: * Returns the content as a String object.
586: * This method differs from toString because toString will return an ArrayList with the toString value of the Chunks in this Phrase.
587: * @deprecated Use {@link #getContent()} instead
588: */
589: public String content() {
590: return getContent();
591: }
592: }
|