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.Font; 032import java.awt.Graphics2D; 033import java.awt.Paint; 034import java.awt.Shape; 035import java.awt.geom.Rectangle2D; 036import java.io.Serializable; 037import java.util.ArrayList; 038import java.util.Collections; 039import java.util.List; 040import org.jfree.chart.api.HorizontalAlignment; 041import org.jfree.chart.block.Size2D; 042import org.jfree.chart.internal.Args; 043import org.jfree.chart.internal.ShapeUtils; 044 045/** 046 * A list of {@link TextLine} objects that form a block of text. 047 * 048 * @see TextUtils#createTextBlock(String, Font, Paint) 049 */ 050public class TextBlock implements Serializable { 051 052 /** For serialization. */ 053 private static final long serialVersionUID = -4333175719424385526L; 054 055 /** Storage for the lines of text. */ 056 private List<TextLine> lines; 057 058 /** The alignment of the lines. */ 059 private HorizontalAlignment lineAlignment; 060 061 /** 062 * Creates a new empty text block. 063 */ 064 public TextBlock() { 065 this.lines = new ArrayList<>(); 066 this.lineAlignment = HorizontalAlignment.CENTER; 067 } 068 069 /** 070 * Returns the alignment of the lines of text within the block. 071 * 072 * @return The alignment (never {@code null}). 073 */ 074 public HorizontalAlignment getLineAlignment() { 075 return this.lineAlignment; 076 } 077 078 /** 079 * Sets the alignment of the lines of text within the block. 080 * 081 * @param alignment the alignment ({@code null} not permitted). 082 */ 083 public void setLineAlignment(HorizontalAlignment alignment) { 084 Args.nullNotPermitted(alignment, "alignment"); 085 this.lineAlignment = alignment; 086 } 087 088 /** 089 * Adds a line of text that will be displayed using the specified font and 090 * color. 091 * 092 * @param text the text ({@code null} not permitted). 093 * @param font the font ({@code null} not permitted). 094 * @param paint the paint ({@code null} not permitted). 095 */ 096 public void addLine(String text, Font font, Paint paint) { 097 addLine(new TextLine(text, font, paint)); 098 } 099 100 /** 101 * Adds a {@link TextLine} to the block. 102 * 103 * @param line the line. 104 */ 105 public void addLine(TextLine line) { 106 this.lines.add(line); 107 } 108 109 /** 110 * Returns the last line in the block. 111 * 112 * @return The last line in the block. 113 */ 114 public TextLine getLastLine() { 115 TextLine last = null; 116 final int index = this.lines.size() - 1; 117 if (index >= 0) { 118 last = this.lines.get(index); 119 } 120 return last; 121 } 122 123 /** 124 * Returns an unmodifiable list containing the lines for the text block. 125 * 126 * @return A list of {@link TextLine} objects. 127 */ 128 public List<TextLine> getLines() { 129 return Collections.unmodifiableList(this.lines); 130 } 131 132 /** 133 * Returns the width and height of the text block. 134 * 135 * @param g2 the graphics device. 136 * 137 * @return The width and height. 138 */ 139 public Size2D calculateDimensions(Graphics2D g2) { 140 double width = 0.0; 141 double height = 0.0; 142 for (TextLine line : this.lines) { 143 final Size2D dimension = line.calculateDimensions(g2); 144 width = Math.max(width, dimension.getWidth()); 145 height = height + dimension.getHeight(); 146 } 147 return new Size2D(width, height); 148 } 149 150 /** 151 * Returns the bounds of the text block. 152 * 153 * @param g2 the graphics device ({@code null} not permitted). 154 * @param anchorX the x-coordinate for the anchor point. 155 * @param anchorY the y-coordinate for the anchor point. 156 * @param anchor the text block anchor ({@code null} not permitted). 157 * @param rotateX the x-coordinate for the rotation point. 158 * @param rotateY the y-coordinate for the rotation point. 159 * @param angle the rotation angle. 160 * 161 * @return The bounds. 162 */ 163 public Shape calculateBounds(Graphics2D g2, float anchorX, float anchorY, 164 TextBlockAnchor anchor, float rotateX, float rotateY, double angle) { 165 Size2D d = calculateDimensions(g2); 166 float[] offsets = calculateOffsets(anchor, d.getWidth(), d.getHeight()); 167 Rectangle2D bounds = new Rectangle2D.Double(anchorX + offsets[0], 168 anchorY + offsets[1], d.getWidth(), d.getHeight()); 169 Shape rotatedBounds = ShapeUtils.rotateShape(bounds, angle, rotateX, 170 rotateY); 171 return rotatedBounds; 172 } 173 174 /** 175 * Draws the text block at a specific location. 176 * 177 * @param g2 the graphics device. 178 * @param x the x-coordinate for the anchor point. 179 * @param y the y-coordinate for the anchor point. 180 * @param anchor the anchor point. 181 */ 182 public void draw(Graphics2D g2, float x, float y, TextBlockAnchor anchor) { 183 draw(g2, x, y, anchor, 0.0f, 0.0f, 0.0); 184 } 185 186 /** 187 * Draws the text block, aligning it with the specified anchor point and 188 * rotating it about the specified rotation point. 189 * 190 * @param g2 the graphics device. 191 * @param anchorX the x-coordinate for the anchor point. 192 * @param anchorY the y-coordinate for the anchor point. 193 * @param anchor the point on the text block that is aligned to the 194 * anchor point. 195 * @param rotateX the x-coordinate for the rotation point. 196 * @param rotateY the x-coordinate for the rotation point. 197 * @param angle the rotation (in radians). 198 */ 199 public void draw(Graphics2D g2, float anchorX, float anchorY, 200 TextBlockAnchor anchor, float rotateX, float rotateY, double angle) { 201 202 Size2D d = calculateDimensions(g2); 203 float[] offsets = calculateOffsets(anchor, d.getWidth(), 204 d.getHeight()); 205 float yCursor = 0.0f; 206 for (TextLine line : this.lines) { 207 Size2D dimension = line.calculateDimensions(g2); 208 float lineOffset = 0.0f; 209 if (this.lineAlignment == HorizontalAlignment.CENTER) { 210 lineOffset = (float) (d.getWidth() - dimension.getWidth()) 211 / 2.0f; 212 } else if (this.lineAlignment == HorizontalAlignment.RIGHT) { 213 lineOffset = (float) (d.getWidth() - dimension.getWidth()); 214 } 215 line.draw(g2, anchorX + offsets[0] + lineOffset, 216 anchorY + offsets[1] + yCursor, TextAnchor.TOP_LEFT, 217 rotateX, rotateY, angle); 218 yCursor = yCursor + (float) dimension.getHeight(); 219 } 220 221 } 222 223 /** 224 * Calculates the x and y offsets required to align the text block with the 225 * specified anchor point. This assumes that the top left of the text 226 * block is at (0.0, 0.0). 227 * 228 * @param anchor the anchor position. 229 * @param width the width of the text block. 230 * @param height the height of the text block. 231 * 232 * @return The offsets (float[0] = x offset, float[1] = y offset). 233 */ 234 private float[] calculateOffsets(TextBlockAnchor anchor, double width, 235 double height) { 236 float[] result = new float[2]; 237 float xAdj = 0.0f; 238 float yAdj = 0.0f; 239 240 if (anchor == TextBlockAnchor.TOP_CENTER 241 || anchor == TextBlockAnchor.CENTER 242 || anchor == TextBlockAnchor.BOTTOM_CENTER) { 243 244 xAdj = (float) -width / 2.0f; 245 246 } else if (anchor == TextBlockAnchor.TOP_RIGHT 247 || anchor == TextBlockAnchor.CENTER_RIGHT 248 || anchor == TextBlockAnchor.BOTTOM_RIGHT) { 249 250 xAdj = (float) -width; 251 252 } 253 254 if (anchor == TextBlockAnchor.TOP_LEFT 255 || anchor == TextBlockAnchor.TOP_CENTER 256 || anchor == TextBlockAnchor.TOP_RIGHT) { 257 258 yAdj = 0.0f; 259 260 } else if (anchor == TextBlockAnchor.CENTER_LEFT 261 || anchor == TextBlockAnchor.CENTER 262 || anchor == TextBlockAnchor.CENTER_RIGHT) { 263 264 yAdj = (float) -height / 2.0f; 265 266 } else if (anchor == TextBlockAnchor.BOTTOM_LEFT 267 || anchor == TextBlockAnchor.BOTTOM_CENTER 268 || anchor == TextBlockAnchor.BOTTOM_RIGHT) { 269 270 yAdj = (float) -height; 271 272 } 273 result[0] = xAdj; 274 result[1] = yAdj; 275 return result; 276 } 277 278 /** 279 * Tests this object for equality with an arbitrary object. 280 * 281 * @param obj the object to test against ({@code null} permitted). 282 * 283 * @return A boolean. 284 */ 285 @Override 286 public boolean equals(Object obj) { 287 if (obj == this) { 288 return true; 289 } 290 if (obj instanceof TextBlock) { 291 TextBlock block = (TextBlock) obj; 292 return this.lines.equals(block.lines); 293 } 294 return false; 295 } 296 297 /** 298 * Returns a hash code for this object. 299 * 300 * @return A hash code. 301 */ 302 @Override 303 public int hashCode() { 304 return (this.lines != null ? this.lines.hashCode() : 0); 305 } 306} 307