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.io.Serializable; 035import java.util.ArrayList; 036import java.util.List; 037import org.jfree.chart.block.Size2D; 038import org.jfree.chart.internal.Args; 039 040/** 041 * A sequence of {@link TextFragment} objects that together form a line of 042 * text. A sequence of text lines is managed by the {@link TextBlock} class. 043 */ 044public class TextLine implements Serializable { 045 046 /** For serialization. */ 047 private static final long serialVersionUID = 7100085690160465444L; 048 049 /** Storage for the text fragments that make up the line. */ 050 private List<TextFragment> fragments; 051 052 /** 053 * Creates a new empty line. 054 */ 055 public TextLine() { 056 this.fragments = new ArrayList<>(); 057 } 058 059 /** 060 * Creates a new text line using the default font. 061 * 062 * @param text the text ({@code null} not permitted). 063 */ 064 public TextLine(String text) { 065 this(text, TextFragment.DEFAULT_FONT); 066 } 067 068 /** 069 * Creates a new text line. 070 * 071 * @param text the text ({@code null} not permitted). 072 * @param font the text font ({@code null} not permitted). 073 */ 074 public TextLine(String text, Font font) { 075 this.fragments = new java.util.ArrayList<>(); 076 final TextFragment fragment = new TextFragment(text, font); 077 this.fragments.add(fragment); 078 } 079 080 /** 081 * Creates a new text line. 082 * 083 * @param text the text ({@code null} not permitted). 084 * @param font the text font ({@code null} not permitted). 085 * @param paint the text color ({@code null} not permitted). 086 */ 087 public TextLine(String text, Font font, Paint paint) { 088 Args.nullNotPermitted(text, "text"); 089 Args.nullNotPermitted(font, "font"); 090 Args.nullNotPermitted(paint, "paint"); 091 this.fragments = new ArrayList<>(); 092 final TextFragment fragment = new TextFragment(text, font, paint); 093 this.fragments.add(fragment); 094 } 095 096 /** 097 * Adds a text fragment to the text line. 098 * 099 * @param fragment the text fragment ({@code null} not permitted). 100 */ 101 public void addFragment(TextFragment fragment) { 102 Args.nullNotPermitted(fragment, "fragment"); 103 this.fragments.add(fragment); 104 } 105 106 /** 107 * Removes a fragment from the line. 108 * 109 * @param fragment the fragment to remove. 110 */ 111 public void removeFragment(TextFragment fragment) { 112 this.fragments.remove(fragment); 113 } 114 115 /** 116 * Draws the text line. 117 * 118 * @param g2 the graphics device. 119 * @param anchorX the x-coordinate for the anchor point. 120 * @param anchorY the y-coordinate for the anchor point. 121 * @param anchor the point on the text line that is aligned to the anchor 122 * point. 123 * @param rotateX the x-coordinate for the rotation point. 124 * @param rotateY the y-coordinate for the rotation point. 125 * @param angle the rotation angle (in radians). 126 */ 127 public void draw(Graphics2D g2, float anchorX, float anchorY, 128 TextAnchor anchor, float rotateX, float rotateY, double angle) { 129 130 Size2D dim = calculateDimensions(g2); 131 float xAdj = 0.0f; 132 if (anchor.isHorizontalCenter()) { 133 xAdj = (float) -dim.getWidth() / 2.0f; 134 } 135 else if (anchor.isRight()) { 136 xAdj = (float) -dim.getWidth(); 137 } 138 float x = anchorX + xAdj; 139 final float yOffset = calculateBaselineOffset(g2, anchor); 140 for (TextFragment fragment : this.fragments) { 141 final Size2D d = fragment.calculateDimensions(g2); 142 fragment.draw(g2, x, anchorY + yOffset, TextAnchor.BASELINE_LEFT, 143 rotateX, rotateY, angle); 144 x = x + (float) d.getWidth(); 145 } 146 147 } 148 149 /** 150 * Calculates the width and height of the text line. 151 * 152 * @param g2 the graphics device. 153 * 154 * @return The width and height. 155 */ 156 public Size2D calculateDimensions(Graphics2D g2) { 157 double width = 0.0; 158 double height = 0.0; 159 for (TextFragment fragment : this.fragments) { 160 final Size2D dimension = fragment.calculateDimensions(g2); 161 width = width + dimension.getWidth(); 162 height = Math.max(height, dimension.getHeight()); 163 } 164 return new Size2D(width, height); 165 } 166 167 /** 168 * Returns the first text fragment in the line. 169 * 170 * @return The first text fragment in the line. 171 */ 172 public TextFragment getFirstTextFragment() { 173 TextFragment result = null; 174 if (this.fragments.size() > 0) { 175 result = this.fragments.get(0); 176 } 177 return result; 178 } 179 180 /** 181 * Returns the last text fragment in the line. 182 * 183 * @return The last text fragment in the line. 184 */ 185 public TextFragment getLastTextFragment() { 186 TextFragment result = null; 187 if (this.fragments.size() > 0) { 188 result = this.fragments.get(this.fragments.size() - 1); 189 } 190 return result; 191 } 192 193 /** 194 * Calculate the offsets required to translate from the specified anchor 195 * position to the left baseline position. 196 * 197 * @param g2 the graphics device. 198 * @param anchor the anchor position. 199 * 200 * @return The offsets. 201 */ 202 private float calculateBaselineOffset(Graphics2D g2, TextAnchor anchor) { 203 float result = 0.0f; 204 for (TextFragment fragment : this.fragments) { 205 result = Math.max(result, 206 fragment.calculateBaselineOffset(g2, anchor)); 207 } 208 return result; 209 } 210 211 /** 212 * Tests this object for equality with an arbitrary object. 213 * 214 * @param obj the object to test against ({@code null} permitted). 215 * 216 * @return A boolean. 217 */ 218 @Override 219 public boolean equals(Object obj) { 220 if (obj == null) { 221 return false; 222 } 223 if (obj == this) { 224 return true; 225 } 226 if (obj instanceof TextLine) { 227 final TextLine line = (TextLine) obj; 228 return this.fragments.equals(line.fragments); 229 } 230 return false; 231 } 232 233 /** 234 * Returns a hash code for this object. 235 * 236 * @return A hash code. 237 */ 238 @Override 239 public int hashCode() { 240 return (this.fragments != null ? this.fragments.hashCode() : 0); 241 } 242 243}