001 /* ======================================================================== 002 * JCommon : a free general purpose class library for the Java(tm) platform 003 * ======================================================================== 004 * 005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jcommon/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------- 028 * TextLine.java 029 * ------------- 030 * (C) Copyright 2003-2005, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: TextLine.java,v 1.11 2005/10/18 13:17:16 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 07-Nov-2003 : Version 1 (DG); 040 * 22-Dec-2003 : Added workaround for Java bug 4245442 (DG); 041 * 29-Jan-2004 : Added new constructor (DG); 042 * 22-Mar-2004 : Added equals() method and implemented Serializable (DG); 043 * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D 044 * because of JDK bug 4976448 which persists on JDK 1.3.1 (DG); 045 * 03-Sep-2004 : Added a method to remove a fragment (DG); 046 * 08-Jul-2005 : Fixed bug in calculateBaselineOffset() (DG); 047 * 048 */ 049 050 package org.jfree.text; 051 052 import java.awt.Font; 053 import java.awt.Graphics2D; 054 import java.awt.Paint; 055 import java.io.Serializable; 056 import java.util.Iterator; 057 import java.util.List; 058 059 import org.jfree.ui.Size2D; 060 import org.jfree.ui.TextAnchor; 061 import org.jfree.util.Log; 062 import org.jfree.util.LogContext; 063 064 /** 065 * A sequence of {@link TextFragment} objects that together form a line of 066 * text. A sequence of text lines is managed by the {@link TextBlock} class. 067 * 068 * @author David Gilbert 069 */ 070 public class TextLine implements Serializable { 071 072 /** For serialization. */ 073 private static final long serialVersionUID = 7100085690160465444L; 074 075 /** Storage for the text fragments that make up the line. */ 076 private List fragments; 077 078 /** Access to logging facilities. */ 079 protected static final LogContext logger 080 = Log.createContext(TextLine.class); 081 082 /** 083 * Creates a new empty line. 084 */ 085 public TextLine() { 086 this.fragments = new java.util.ArrayList(); 087 } 088 089 /** 090 * Creates a new text line using the default font. 091 * 092 * @param text the text (<code>null</code> not permitted). 093 */ 094 public TextLine(final String text) { 095 this(text, TextFragment.DEFAULT_FONT); 096 } 097 098 /** 099 * Creates a new text line. 100 * 101 * @param text the text (<code>null</code> not permitted). 102 * @param font the text font (<code>null</code> not permitted). 103 */ 104 public TextLine(final String text, final Font font) { 105 this.fragments = new java.util.ArrayList(); 106 final TextFragment fragment = new TextFragment(text, font); 107 this.fragments.add(fragment); 108 } 109 110 /** 111 * Creates a new text line. 112 * 113 * @param text the text (<code>null</code> not permitted). 114 * @param font the text font (<code>null</code> not permitted). 115 * @param paint the text color (<code>null</code> not permitted). 116 */ 117 public TextLine(final String text, final Font font, final Paint paint) { 118 if (text == null) { 119 throw new IllegalArgumentException("Null 'text' argument."); 120 } 121 if (font == null) { 122 throw new IllegalArgumentException("Null 'font' argument."); 123 } 124 if (paint == null) { 125 throw new IllegalArgumentException("Null 'paint' argument."); 126 } 127 this.fragments = new java.util.ArrayList(); 128 final TextFragment fragment = new TextFragment(text, font, paint); 129 this.fragments.add(fragment); 130 } 131 132 /** 133 * Adds a text fragment to the text line. 134 * 135 * @param fragment the text fragment (<code>null</code> not permitted). 136 */ 137 public void addFragment(final TextFragment fragment) { 138 this.fragments.add(fragment); 139 } 140 141 /** 142 * Removes a fragment from the line. 143 * 144 * @param fragment the fragment to remove. 145 */ 146 public void removeFragment(final TextFragment fragment) { 147 this.fragments.remove(fragment); 148 } 149 150 /** 151 * Draws the text line. 152 * 153 * @param g2 the graphics device. 154 * @param anchorX the x-coordinate for the anchor point. 155 * @param anchorY the y-coordinate for the anchor point. 156 * @param anchor the point on the text line that is aligned to the anchor 157 * point. 158 * @param rotateX the x-coordinate for the rotation point. 159 * @param rotateY the y-coordinate for the rotation point. 160 * @param angle the rotation angle (in radians). 161 */ 162 public void draw(final Graphics2D g2, 163 final float anchorX, final float anchorY, 164 final TextAnchor anchor, 165 final float rotateX, final float rotateY, 166 final double angle) { 167 168 float x = anchorX; 169 final float yOffset = calculateBaselineOffset(g2, anchor); 170 final Iterator iterator = this.fragments.iterator(); 171 while (iterator.hasNext()) { 172 final TextFragment fragment = (TextFragment) iterator.next(); 173 final Size2D d = fragment.calculateDimensions(g2); 174 fragment.draw( 175 g2, x, anchorY + yOffset, TextAnchor.BASELINE_LEFT, 176 rotateX, rotateY, angle 177 ); 178 x = x + (float) d.getWidth(); 179 } 180 181 } 182 183 /** 184 * Calculates the width and height of the text line. 185 * 186 * @param g2 the graphics device. 187 * 188 * @return The width and height. 189 */ 190 public Size2D calculateDimensions(final Graphics2D g2) { 191 double width = 0.0; 192 double height = 0.0; 193 final Iterator iterator = this.fragments.iterator(); 194 while (iterator.hasNext()) { 195 final TextFragment fragment = (TextFragment) iterator.next(); 196 final Size2D dimension = fragment.calculateDimensions(g2); 197 width = width + dimension.getWidth(); 198 height = Math.max(height, dimension.getHeight()); 199 if (logger.isDebugEnabled()) { 200 logger.debug("width = " + width + ", height = " + height); 201 } 202 } 203 return new Size2D(width, height); 204 } 205 206 /** 207 * Returns the first text fragment in the line. 208 * 209 * @return The first text fragment in the line. 210 */ 211 public TextFragment getFirstTextFragment() { 212 TextFragment result = null; 213 if (this.fragments.size() > 0) { 214 result = (TextFragment) this.fragments.get(0); 215 } 216 return result; 217 } 218 219 /** 220 * Returns the last text fragment in the line. 221 * 222 * @return The last text fragment in the line. 223 */ 224 public TextFragment getLastTextFragment() { 225 TextFragment result = null; 226 if (this.fragments.size() > 0) { 227 result = (TextFragment) this.fragments.get(this.fragments.size() 228 - 1); 229 } 230 return result; 231 } 232 233 /** 234 * Calculate the offsets required to translate from the specified anchor 235 * position to the left baseline position. 236 * 237 * @param g2 the graphics device. 238 * @param anchor the anchor position. 239 * 240 * @return The offsets. 241 */ 242 private float calculateBaselineOffset(final Graphics2D g2, 243 final TextAnchor anchor) { 244 float result = 0.0f; 245 Iterator iterator = this.fragments.iterator(); 246 while (iterator.hasNext()) { 247 TextFragment fragment = (TextFragment) iterator.next(); 248 result = Math.max(result, 249 fragment.calculateBaselineOffset(g2, anchor)); 250 } 251 return result; 252 } 253 254 /** 255 * Tests this object for equality with an arbitrary object. 256 * 257 * @param obj the object to test against (<code>null</code> permitted). 258 * 259 * @return A boolean. 260 */ 261 public boolean equals(final Object obj) { 262 if (obj == null) { 263 return false; 264 } 265 if (obj == this) { 266 return true; 267 } 268 if (obj instanceof TextLine) { 269 final TextLine line = (TextLine) obj; 270 return this.fragments.equals(line.fragments); 271 } 272 return false; 273 } 274 275 /** 276 * Returns a hash code for this object. 277 * 278 * @return A hash code. 279 */ 280 public int hashCode() { 281 return (this.fragments != null ? this.fragments.hashCode() : 0); 282 } 283 284 }