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}