001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 */ 028 029package org.jfree.chart.text; 030 031import java.awt.Color; 032import java.awt.Font; 033import java.awt.FontMetrics; 034import java.awt.Graphics2D; 035import java.awt.Paint; 036import java.awt.font.LineMetrics; 037import java.awt.geom.Rectangle2D; 038import java.io.IOException; 039import java.io.ObjectInputStream; 040import java.io.ObjectOutputStream; 041import java.io.Serializable; 042import java.util.Objects; 043import org.jfree.chart.HashUtils; 044import org.jfree.chart.ui.Size2D; 045import org.jfree.chart.ui.TextAnchor; 046import org.jfree.chart.util.PaintUtils; 047import org.jfree.chart.util.SerialUtils; 048 049/** 050 * A text item, with an associated font, that fits on a single line (see 051 * {@link TextLine}). Instances of the class are immutable. 052 */ 053public class TextFragment implements Serializable { 054 055 /** For serialization. */ 056 private static final long serialVersionUID = 4465945952903143262L; 057 058 /** The default font. */ 059 public static final Font DEFAULT_FONT = new Font("Serif", Font.PLAIN, 12); 060 061 /** The default text color. */ 062 public static final Paint DEFAULT_PAINT = Color.BLACK; 063 064 /** The text. */ 065 private String text; 066 067 /** The font. */ 068 private Font font; 069 070 /** The text color. */ 071 private transient Paint paint; 072 073 /** 074 * The baseline offset (can be used to simulate subscripts and 075 * superscripts). 076 */ 077 private float baselineOffset; 078 079 /** 080 * Creates a new text fragment. 081 * 082 * @param text the text ({@code null} not permitted). 083 */ 084 public TextFragment(String text) { 085 this(text, DEFAULT_FONT, DEFAULT_PAINT); 086 } 087 088 /** 089 * Creates a new text fragment. 090 * 091 * @param text the text ({@code null} not permitted). 092 * @param font the font ({@code null} not permitted). 093 */ 094 public TextFragment(String text, Font font) { 095 this(text, font, DEFAULT_PAINT); 096 } 097 098 /** 099 * Creates a new text fragment. 100 * 101 * @param text the text ({@code null} not permitted). 102 * @param font the font ({@code null} not permitted). 103 * @param paint the text color ({@code null} not permitted). 104 */ 105 public TextFragment(String text, Font font, Paint paint) { 106 this(text, font, paint, 0.0f); 107 } 108 109 /** 110 * Creates a new text fragment. 111 * 112 * @param text the text ({@code null} not permitted). 113 * @param font the font ({@code null} not permitted). 114 * @param paint the text color ({@code null} not permitted). 115 * @param baselineOffset the baseline offset. 116 */ 117 public TextFragment(String text, Font font, Paint paint, 118 float baselineOffset) { 119 if (text == null) { 120 throw new IllegalArgumentException("Null 'text' argument."); 121 } 122 if (font == null) { 123 throw new IllegalArgumentException("Null 'font' argument."); 124 } 125 if (paint == null) { 126 throw new IllegalArgumentException("Null 'paint' argument."); 127 } 128 this.text = text; 129 this.font = font; 130 this.paint = paint; 131 this.baselineOffset = baselineOffset; 132 } 133 134 /** 135 * Returns the text. 136 * 137 * @return The text (possibly {@code null}). 138 */ 139 public String getText() { 140 return this.text; 141 } 142 143 /** 144 * Returns the font. 145 * 146 * @return The font (never {@code null}). 147 */ 148 public Font getFont() { 149 return this.font; 150 } 151 152 /** 153 * Returns the text paint. 154 * 155 * @return The text paint (never {@code null}). 156 */ 157 public Paint getPaint() { 158 return this.paint; 159 } 160 161 /** 162 * Returns the baseline offset. 163 * 164 * @return The baseline offset. 165 */ 166 public float getBaselineOffset() { 167 return this.baselineOffset; 168 } 169 170 /** 171 * Draws the text fragment. 172 * 173 * @param g2 the graphics device. 174 * @param anchorX the x-coordinate of the anchor point. 175 * @param anchorY the y-coordinate of the anchor point. 176 * @param anchor the location of the text that is aligned to the anchor 177 * point. 178 * @param rotateX the x-coordinate of the rotation point. 179 * @param rotateY the y-coordinate of the rotation point. 180 * @param angle the angle. 181 */ 182 public void draw(Graphics2D g2, float anchorX, float anchorY, 183 TextAnchor anchor, float rotateX, float rotateY, double angle) { 184 g2.setFont(this.font); 185 g2.setPaint(this.paint); 186 TextUtils.drawRotatedString(this.text, g2, anchorX, anchorY 187 + this.baselineOffset, anchor, angle, rotateX, rotateY); 188 } 189 190 /** 191 * Calculates the dimensions of the text fragment. 192 * 193 * @param g2 the graphics device. 194 * 195 * @return The width and height of the text. 196 */ 197 public Size2D calculateDimensions(Graphics2D g2) { 198 FontMetrics fm = g2.getFontMetrics(this.font); 199 Rectangle2D bounds = TextUtils.getTextBounds(this.text, g2, fm); 200 Size2D result = new Size2D(bounds.getWidth(), bounds.getHeight()); 201 return result; 202 } 203 204 /** 205 * Calculates the vertical offset between the baseline and the specified 206 * text anchor. 207 * 208 * @param g2 the graphics device. 209 * @param anchor the anchor. 210 * 211 * @return the offset. 212 */ 213 public float calculateBaselineOffset(Graphics2D g2, TextAnchor anchor) { 214 float result = 0.0f; 215 FontMetrics fm = g2.getFontMetrics(this.font); 216 LineMetrics lm = fm.getLineMetrics("ABCxyz", g2); 217 if (anchor.isTop()) { 218 result = lm.getAscent(); 219 } 220 else if (anchor.isHalfAscent()) { 221 result = lm.getAscent() / 2.0f; 222 } 223 else if (anchor.isVerticalCenter()) { 224 result = lm.getAscent() / 2.0f - lm.getDescent() / 2.0f; 225 } 226 else if (anchor.isBottom()) { 227 result = -lm.getDescent() - lm.getLeading(); 228 } 229 return result; 230 } 231 232 /** 233 * Tests this instance for equality with an arbitrary object. 234 * 235 * @param obj the object to test against ({@code null} permitted). 236 * 237 * @return A boolean. 238 */ 239 @Override 240 public boolean equals(Object obj) { 241 if (obj == this) 242 { 243 return true; 244 } 245 if (!(obj instanceof TextFragment)) { 246 return false; 247 } 248 TextFragment tf = (TextFragment) obj; 249 if (!Objects.equals(this.text, tf.text)) { 250 return false; 251 } 252 if (!Objects.equals(this.font, tf.font)) { 253 return false; 254 } 255 if (!PaintUtils.equal(this.paint, tf.paint)) { 256 return false; 257 } 258 if (Float.floatToIntBits(this.baselineOffset) != 259 Float.floatToIntBits(tf.baselineOffset)) { 260 return false; 261 } 262 return true; 263 } 264 265 266 /** 267 * Returns a hash code for this object. 268 * 269 * @return A hash code. 270 */ 271 @Override 272 public int hashCode() { 273 int hash = 7; 274 hash = 83 * hash + Objects.hashCode(this.text); 275 hash = 83 * hash + Objects.hashCode(this.font); 276 hash = 83 * hash + HashUtils.hashCodeForPaint(this.paint); 277 hash = 83 * hash + Float.floatToIntBits(this.baselineOffset); 278 return hash; 279 } 280 281 /** 282 * Provides serialization support. 283 * 284 * @param stream the output stream. 285 * 286 * @throws IOException if there is an I/O error. 287 */ 288 private void writeObject(ObjectOutputStream stream) throws IOException { 289 stream.defaultWriteObject(); 290 SerialUtils.writePaint(this.paint, stream); 291 } 292 293 /** 294 * Provides serialization support. 295 * 296 * @param stream the input stream. 297 * 298 * @throws IOException if there is an I/O error. 299 * @throws ClassNotFoundException if there is a classpath problem. 300 */ 301 private void readObject(ObjectInputStream stream) throws IOException, 302 ClassNotFoundException { 303 stream.defaultReadObject(); 304 this.paint = SerialUtils.readPaint(stream); 305 } 306 307} 308