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