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 * TextUtils.java
029 * --------------
030 * (C) Copyright 2014, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes
036 * -------
037 * 30-Jun-2014 : Version 1 (DG);
038 *
039 */
040
041package org.jfree.chart.util;
042
043import java.awt.Font;
044import java.awt.FontMetrics;
045import java.awt.Graphics2D;
046import java.awt.font.FontRenderContext;
047import java.awt.font.LineMetrics;
048import java.awt.geom.Rectangle2D;
049import org.jfree.ui.TextAnchor;
050
051/**
052 * Text utility functions.
053 * 
054 * @since 1.0.18
055 */
056public class TextUtils {
057    
058    /**
059     * Draws a string such that the specified anchor point is aligned to the
060     * given <code>(x, y)</code> location, and returns a bounding rectangle 
061     * for the text.
062     *
063     * @param text  the text.
064     * @param g2  the graphics device.
065     * @param x  the x coordinate (Java 2D).
066     * @param y  the y coordinate (Java 2D).
067     * @param anchor  the anchor location.
068     *
069     * @return The text bounds (adjusted for the text position).
070     */
071    public static Rectangle2D drawAlignedString(String text,
072            Graphics2D g2, float x, float y, TextAnchor anchor) {
073
074        Rectangle2D textBounds = new Rectangle2D.Double();
075        float[] adjust = deriveTextBoundsAnchorOffsets(g2, text, anchor,
076                textBounds);
077        // adjust text bounds to match string position
078        textBounds.setRect(x + adjust[0], y + adjust[1] + adjust[2],
079            textBounds.getWidth(), textBounds.getHeight());
080        g2.drawString(text, x + adjust[0], y + adjust[1]);
081        return textBounds;
082    }
083
084    /**
085     * Returns the bounds of an aligned string.
086     * 
087     * @param text  the string (<code>null</code> not permitted).
088     * @param g2  the graphics target (<code>null</code> not permitted).
089     * @param x  the x-coordinate.
090     * @param y  the y-coordinate.
091     * @param anchor  the anchor point that will be aligned to 
092     *     <code>(x, y)</code> (<code>null</code> not permitted).
093     * 
094     * @return The text bounds (never <code>null</code>).
095     * 
096     * @since 1.3
097     */
098    public static Rectangle2D calcAlignedStringBounds(String text,
099            Graphics2D g2, float x, float y, TextAnchor anchor) {
100
101        Rectangle2D textBounds = new Rectangle2D.Double();
102        float[] adjust = deriveTextBoundsAnchorOffsets(g2, text, anchor,
103                textBounds);
104        // adjust text bounds to match string position
105        textBounds.setRect(x + adjust[0], y + adjust[1] + adjust[2],
106            textBounds.getWidth(), textBounds.getHeight());
107        return textBounds;
108    }
109    
110    /**
111     * A utility method that calculates the anchor offsets for a string.
112     * Normally, the (x, y) coordinate for drawing text is a point on the
113     * baseline at the left of the text string.  If you add these offsets to
114     * (x, y) and draw the string, then the anchor point should coincide with
115     * the (x, y) point.
116     *
117     * @param g2  the graphics device (not <code>null</code>).
118     * @param text  the text.
119     * @param anchor  the anchor point.
120     *
121     * @return  The offsets.
122     */
123    private static float[] deriveTextBoundsAnchorOffsets(Graphics2D g2,
124            String text, TextAnchor anchor) {
125
126        float[] result = new float[2];
127        FontRenderContext frc = g2.getFontRenderContext();
128        Font f = g2.getFont();
129        FontMetrics fm = g2.getFontMetrics(f);
130        Rectangle2D bounds = getTextBounds(text, fm);
131        LineMetrics metrics = f.getLineMetrics(text, frc);
132        float ascent = metrics.getAscent();
133        float halfAscent = ascent / 2.0f;
134        float descent = metrics.getDescent();
135        float leading = metrics.getLeading();
136        float xAdj = 0.0f;
137        float yAdj = 0.0f;
138
139        if (anchor.isHorizontalCenter()) {
140            xAdj = (float) -bounds.getWidth() / 2.0f;
141        }
142        else if (anchor.isRight()) {
143            xAdj = (float) -bounds.getWidth();
144        }
145
146        if (anchor.isTop()) {
147            yAdj = -descent - leading + (float) bounds.getHeight();
148        }
149        else if (anchor.isHalfAscent()) {
150            yAdj = halfAscent;
151        }
152        else if (anchor.isVerticalCenter()) {
153            yAdj = -descent - leading + (float) (bounds.getHeight() / 2.0);
154        }
155        else if (anchor.isBaseline()) {
156            yAdj = 0.0f;
157        }
158        else if (anchor.isBottom()) {
159            yAdj = -metrics.getDescent() - metrics.getLeading();
160        }
161        result[0] = xAdj;
162        result[1] = yAdj;
163        return result;
164
165    }
166
167    /**
168     * A utility method that calculates the anchor offsets for a string.
169     * Normally, the (x, y) coordinate for drawing text is a point on the
170     * baseline at the left of the text string.  If you add these offsets to
171     * (x, y) and draw the string, then the anchor point should coincide with
172     * the (x, y) point.
173     *
174     * @param g2  the graphics device (not <code>null</code>).
175     * @param text  the text.
176     * @param anchor  the anchor point.
177     * @param textBounds  the text bounds (if not <code>null</code>, this
178     *                    object will be updated by this method to match the
179     *                    string bounds).
180     *
181     * @return  The offsets.
182     */
183    private static float[] deriveTextBoundsAnchorOffsets(Graphics2D g2,
184            String text, TextAnchor anchor, Rectangle2D textBounds) {
185
186        float[] result = new float[3];
187        FontRenderContext frc = g2.getFontRenderContext();
188        Font f = g2.getFont();
189        FontMetrics fm = g2.getFontMetrics(f);
190        Rectangle2D bounds = getTextBounds(text, fm);
191        LineMetrics metrics = f.getLineMetrics(text, frc);
192        float ascent = metrics.getAscent();
193        result[2] = -ascent;
194        float halfAscent = ascent / 2.0f;
195        float descent = metrics.getDescent();
196        float leading = metrics.getLeading();
197        float xAdj = 0.0f;
198        float yAdj = 0.0f;
199
200        if (anchor.isHorizontalCenter()) {
201            xAdj = (float) -bounds.getWidth() / 2.0f;
202        }
203        else if (anchor.isRight()) {
204            xAdj = (float) -bounds.getWidth();
205        }
206
207        if (anchor.isTop()) {
208            yAdj = -descent - leading + (float) bounds.getHeight();
209        }
210        else if (anchor.isHalfAscent()) {
211            yAdj = halfAscent;
212        }
213        else if (anchor.isHorizontalCenter()) {
214            yAdj = -descent - leading + (float) (bounds.getHeight() / 2.0);
215        }
216        else if (anchor.isBaseline()) {
217            yAdj = 0.0f;
218        }
219        else if (anchor.isBottom()) {
220            yAdj = -metrics.getDescent() - metrics.getLeading();
221        }
222        if (textBounds != null) {
223            textBounds.setRect(bounds);
224        }
225        result[0] = xAdj;
226        result[1] = yAdj;
227        return result;
228    }
229    
230    /**
231     * Returns the bounds for the specified text.  The supplied text is
232     * assumed to be on a single line (no carriage return or newline 
233     * characters).
234     *
235     * @param text  the text (<code>null</code> not permitted).
236     * @param fm  the font metrics (<code>null</code> not permitted).
237     *
238     * @return The text bounds.
239     */
240    public static Rectangle2D getTextBounds(String text, FontMetrics fm) {
241        return getTextBounds(text, 0.0, 0.0, fm);
242    }
243    
244    /**
245     * Returns the bounds for the specified text when it is drawn with the 
246     * left-baseline aligned to the point <code>(x, y)</code>.
247     * 
248     * @param text  the text (<code>null</code> not permitted).
249     * @param x  the x-coordinate.
250     * @param y  the y-coordinate.
251     * @param fm  the font metrics (<code>null</code> not permitted).
252     * 
253     * @return The bounding rectangle (never <code>null</code>). 
254     */
255    public static Rectangle2D getTextBounds(String text, double x, double y,
256            FontMetrics fm) {
257        ParamChecks.nullNotPermitted(text, "text");
258        ParamChecks.nullNotPermitted(fm, "fm");
259        double width = fm.stringWidth(text);
260        double height = fm.getHeight();
261        return new Rectangle2D.Double(x, y - fm.getAscent(), width, height);
262        
263    }
264}