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}