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 * AttrStringUtils.java 029 * -------------------- 030 * (C) Copyright 2013-2022, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.chart.util; 038 039import org.jfree.chart.internal.Args; 040import java.awt.Graphics2D; 041import java.awt.font.TextLayout; 042import java.awt.geom.AffineTransform; 043import java.awt.geom.Rectangle2D; 044import java.text.AttributedString; 045import org.jfree.chart.text.TextAnchor; 046 047/** 048 * Some {@code AttributedString} utilities. 049 * 050 * @since 1.0.16 051 */ 052public class AttrStringUtils { 053 054 private AttrStringUtils() { 055 // no need to instantiate this class 056 } 057 058 /** 059 * Returns the bounds for the attributed string. 060 * 061 * @param text the attributed string ({@code null} not permitted). 062 * @param g2 the graphics target ({@code null} not permitted). 063 * 064 * @return The bounds (never {@code null}). 065 * 066 * @since 1.0.18 067 */ 068 public static Rectangle2D getTextBounds(AttributedString text, 069 Graphics2D g2) { 070 TextLayout tl = new TextLayout(text.getIterator(), 071 g2.getFontRenderContext()); 072 return tl.getBounds(); 073 } 074 075 /** 076 * Draws the attributed string at {@code (x, y)}, rotated by the 077 * specified angle about {@code (x, y)}. 078 * 079 * @param text the attributed string ({@code null} not permitted). 080 * @param g2 the graphics output target. 081 * @param angle the angle. 082 * @param x the x-coordinate. 083 * @param y the y-coordinate. 084 * 085 * @since 1.0.16 086 */ 087 public static void drawRotatedString(AttributedString text, Graphics2D g2, 088 double angle, float x, float y) { 089 drawRotatedString(text, g2, x, y, angle, x, y); 090 } 091 092 /** 093 * Draws the attributed string at {@code (textX, textY)}, rotated by 094 * the specified angle about {@code (rotateX, rotateY)}. 095 * 096 * @param text the attributed string ({@code null} not permitted). 097 * @param g2 the graphics output target. 098 * @param textX the x-coordinate for the text. 099 * @param textY the y-coordinate for the text. 100 * @param angle the rotation angle (in radians). 101 * @param rotateX the x-coordinate for the rotation point. 102 * @param rotateY the y-coordinate for the rotation point. 103 * 104 * @since 1.0.16 105 */ 106 public static void drawRotatedString(AttributedString text, Graphics2D g2, 107 float textX, float textY, double angle, float rotateX, 108 float rotateY) { 109 Args.nullNotPermitted(text, "text"); 110 111 AffineTransform saved = g2.getTransform(); 112 AffineTransform rotate = AffineTransform.getRotateInstance(angle, 113 rotateX, rotateY); 114 g2.transform(rotate); 115 TextLayout tl = new TextLayout(text.getIterator(), 116 g2.getFontRenderContext()); 117 tl.draw(g2, textX, textY); 118 119 g2.setTransform(saved); 120 } 121 122 /** 123 * Draws the string anchored to {@code (x, y)}, rotated by the 124 * specified angle about {@code (rotationX, rotationY)}. 125 * 126 * @param text the text ({@code null} not permitted). 127 * @param g2 the graphics target. 128 * @param x the x-coordinate for the text location. 129 * @param y the y-coordinate for the text location. 130 * @param textAnchor the text anchor point. 131 * @param angle the rotation (in radians). 132 * @param rotationX the x-coordinate for the rotation point. 133 * @param rotationY the y-coordinate for the rotation point. 134 * 135 * @since 1.0.16 136 */ 137 public static void drawRotatedString(AttributedString text, Graphics2D g2, 138 float x, float y, TextAnchor textAnchor, 139 double angle, float rotationX, float rotationY) { 140 Args.nullNotPermitted(text, "text"); 141 float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text, textAnchor, 142 null); 143 drawRotatedString(text, g2, x + textAdj[0], y + textAdj[1], angle, 144 rotationX, rotationY); 145 } 146 147 /** 148 * Draws a rotated string. 149 * 150 * @param text the text to draw. 151 * @param g2 the graphics target. 152 * @param x the x-coordinate for the text location. 153 * @param y the y-coordinate for the text location. 154 * @param textAnchor the text anchor point. 155 * @param angle the rotation (in radians). 156 * @param rotationAnchor the rotation anchor point. 157 * 158 * @since 1.0.16 159 */ 160 public static void drawRotatedString(AttributedString text, Graphics2D g2, 161 float x, float y, TextAnchor textAnchor, 162 double angle, TextAnchor rotationAnchor) { 163 Args.nullNotPermitted(text, "text"); 164 float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text, textAnchor, 165 null); 166 float[] rotateAdj = deriveRotationAnchorOffsets(g2, text, 167 rotationAnchor); 168 drawRotatedString(text, g2, x + textAdj[0], y + textAdj[1], 169 angle, x + textAdj[0] + rotateAdj[0], 170 y + textAdj[1] + rotateAdj[1]); 171 } 172 173 private static float[] deriveTextBoundsAnchorOffsets(Graphics2D g2, 174 AttributedString text, TextAnchor anchor, Rectangle2D textBounds) { 175 176 TextLayout layout = new TextLayout(text.getIterator(), g2.getFontRenderContext()); 177 Rectangle2D bounds = layout.getBounds(); 178 179 float[] result = new float[3]; 180 float ascent = layout.getAscent(); 181 result[2] = -ascent; 182 float halfAscent = ascent / 2.0f; 183 float descent = layout.getDescent(); 184 float leading = layout.getLeading(); 185 float xAdj = 0.0f; 186 float yAdj = 0.0f; 187 188 if (isHorizontalCenter(anchor)) { 189 xAdj = (float) -bounds.getWidth() / 2.0f; 190 } 191 else if (isHorizontalRight(anchor)) { 192 xAdj = (float) -bounds.getWidth(); 193 } 194 195 if (isTop(anchor)) { 196 //yAdj = -descent - leading + (float) bounds.getHeight(); 197 yAdj = (float) bounds.getHeight(); 198 } 199 else if (isHalfAscent(anchor)) { 200 yAdj = halfAscent; 201 } 202 else if (isHalfHeight(anchor)) { 203 yAdj = -descent - leading + (float) (bounds.getHeight() / 2.0); 204 } 205 else if (isBaseline(anchor)) { 206 yAdj = 0.0f; 207 } 208 else if (isBottom(anchor)) { 209 yAdj = -descent - leading; 210 } 211 if (textBounds != null) { 212 textBounds.setRect(bounds); 213 } 214 result[0] = xAdj; 215 result[1] = yAdj; 216 return result; 217 } 218 219 /** 220 * A utility method that calculates the rotation anchor offsets for a 221 * string. These offsets are relative to the text starting coordinate 222 * (BASELINE_LEFT). 223 * 224 * @param g2 the graphics device. 225 * @param text the text. 226 * @param anchor the anchor point. 227 * 228 * @return The offsets. 229 */ 230 private static float[] deriveRotationAnchorOffsets(Graphics2D g2, 231 AttributedString text, TextAnchor anchor) { 232 233 float[] result = new float[2]; 234 235 TextLayout layout = new TextLayout(text.getIterator(), 236 g2.getFontRenderContext()); 237 Rectangle2D bounds = layout.getBounds(); 238 float ascent = layout.getAscent(); 239 float halfAscent = ascent / 2.0f; 240 float descent = layout.getDescent(); 241 float leading = layout.getLeading(); 242 float xAdj = 0.0f; 243 float yAdj = 0.0f; 244 245 if (isHorizontalLeft(anchor)) { 246 xAdj = 0.0f; 247 } 248 else if (isHorizontalCenter(anchor)) { 249 xAdj = (float) bounds.getWidth() / 2.0f; 250 } 251 else if (isHorizontalRight(anchor)) { 252 xAdj = (float) bounds.getWidth(); 253 } 254 255 if (isTop(anchor)) { 256 yAdj = descent + leading - (float) bounds.getHeight(); 257 } 258 else if (isHalfHeight(anchor)) { 259 yAdj = descent + leading - (float) (bounds.getHeight() / 2.0); 260 } 261 else if (isHalfAscent(anchor)) { 262 yAdj = -halfAscent; 263 } 264 else if (isBaseline(anchor)) { 265 yAdj = 0.0f; 266 } 267 else if (isBottom(anchor)) { 268 yAdj = descent + leading; 269 } 270 result[0] = xAdj; 271 result[1] = yAdj; 272 return result; 273 274 } 275 276 private static boolean isTop(TextAnchor anchor) { 277 return anchor.equals(TextAnchor.TOP_LEFT) 278 || anchor.equals(TextAnchor.TOP_CENTER) 279 || anchor.equals(TextAnchor.TOP_RIGHT); 280 } 281 282 private static boolean isBaseline(TextAnchor anchor) { 283 return anchor.equals(TextAnchor.BASELINE_LEFT) 284 || anchor.equals(TextAnchor.BASELINE_CENTER) 285 || anchor.equals(TextAnchor.BASELINE_RIGHT); 286 } 287 288 private static boolean isHalfAscent(TextAnchor anchor) { 289 return anchor.equals(TextAnchor.HALF_ASCENT_LEFT) 290 || anchor.equals(TextAnchor.HALF_ASCENT_CENTER) 291 || anchor.equals(TextAnchor.HALF_ASCENT_RIGHT); 292 } 293 294 private static boolean isHalfHeight(TextAnchor anchor) { 295 return anchor.equals(TextAnchor.CENTER_LEFT) 296 || anchor.equals(TextAnchor.CENTER) 297 || anchor.equals(TextAnchor.CENTER_RIGHT); 298 } 299 300 private static boolean isBottom(TextAnchor anchor) { 301 return anchor.equals(TextAnchor.BOTTOM_LEFT) 302 || anchor.equals(TextAnchor.BOTTOM_CENTER) 303 || anchor.equals(TextAnchor.BOTTOM_RIGHT); 304 } 305 306 private static boolean isHorizontalLeft(TextAnchor anchor) { 307 return anchor.equals(TextAnchor.TOP_LEFT) 308 || anchor.equals(TextAnchor.CENTER_LEFT) 309 || anchor.equals(TextAnchor.HALF_ASCENT_LEFT) 310 || anchor.equals(TextAnchor.BASELINE_LEFT) 311 || anchor.equals(TextAnchor.BOTTOM_LEFT); 312 } 313 314 private static boolean isHorizontalCenter(TextAnchor anchor) { 315 return anchor.equals(TextAnchor.TOP_CENTER) 316 || anchor.equals(TextAnchor.CENTER) 317 || anchor.equals(TextAnchor.HALF_ASCENT_CENTER) 318 || anchor.equals(TextAnchor.BASELINE_CENTER) 319 || anchor.equals(TextAnchor.BOTTOM_CENTER); 320 } 321 322 private static boolean isHorizontalRight(TextAnchor anchor) { 323 return anchor.equals(TextAnchor.TOP_RIGHT) 324 || anchor.equals(TextAnchor.CENTER_RIGHT) 325 || anchor.equals(TextAnchor.HALF_ASCENT_RIGHT) 326 || anchor.equals(TextAnchor.BASELINE_RIGHT) 327 || anchor.equals(TextAnchor.BOTTOM_RIGHT); 328 } 329}