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