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}