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