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