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