Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.library / org.gvsig.ui / src / main / java / org / gvsig / gui / awt / text / RotatedTextUtils.java @ 42836

History | View | Annotate | Download (38 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2015 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
package org.gvsig.gui.awt.text;
25

    
26

    
27
import java.awt.Graphics2D;
28
import java.awt.font.TextLayout;
29
import java.awt.geom.AffineTransform;
30
import java.awt.geom.NoninvertibleTransformException;
31
import java.awt.geom.Point2D;
32

    
33
/**
34
 * <p>A convenience class to easily draw rotated text which is positioned on
35
 * a specific side of the rotation point (TOP, BOTTOM, LEFT or RIGHT).
36
 * The text can be anchored by its central point or by the text corner.</p>
37
 * 
38
 * <p>The following diagrams illustrate the behaviour of each positioning and
39
 * anchor point in relation to the rotation point:</p>
40
 * <pre>
41
 * top center:         top corner:
42
 *        o                     o
43
 *       l                     l
44
 *      l                     l
45
 *     e                     e
46
 *    h                     h
47
 *      .                   . 
48
 * 
49
 * 
50
 * right center:      right corner:
51
 *                          o 
52
 *                         l 
53
 *        o               l
54
 *       l               e
55
 *   .  l              .h 
56
 *     e
57
 *    h
58
 * </pre>
59
 * 
60
 * <p>The  class provides 2 separate families of methods:
61
 * <ul>
62
 * <li><strong>draw methods</strong>, which rotate the graphics, draw the rotated text and
63
 * restore the graphics transformation</li>
64
 * <li><strong>getPosition methods</strong>, which are used to get the position of the
65
 * rotated text on an already rotated graphics (faster when drawing several
66
 * rotated texts using the same rotation angle). Note that this family of
67
 * methods deals with coordinates in 2 different coordinate spaces
68
 * (the original, non-rotated space and the rotated space). The origin point
69
 * coordinates has to be referred to the non-rotated space, while the returned
70
 * position is referred to the rotated space.</li>
71
 * </ul>
72
 *
73
 * Generally speaking the <code>draw</code> family of methods can be considered a simpler, higher level
74
 * API, while the <code>getPosition</code> family is conceptually more complex but faster for some
75
 * scenarios.
76
 * </p>
77
 * 
78
 * <p>
79
 * Example of <strong>draw</strong> method usage:
80
 * <pre>
81
 * // draw coordinates axis
82
 * Graphics2D g = ...
83
 * Point2D origin1 = new Point2D.Double(100, 200);
84
 * int lenght = 100;
85
 * g.drawLine((int)origin1.getX()-length, (int)origin1.getY(), (int)origin1.getX()+length, (int)origin1.getY());
86
 * g.drawLine((int)origin1.getX(), (int)origin1.getY()-length, (int)origin1.getX(), (int)origin1.getY()+length);
87
 * // draw the rotated text
88
 * RotatedTextUtils.draw(origin1, g, "Hello world", angle, RotatedTextUtils.PLACEMENT_BOTTOM, RotatedTextUtils.ANCHOR_CORNER);
89
 * </pre>
90
 * </p>
91
 * 
92
 * 
93
 * <p>
94
 * Example of <strong>getPosition</strong> method usage:
95
 * <pre>
96
 * // draw coordinates axis
97
 * Graphics2D g = ...
98
 * AffineTransform defaultAt = g.getTransform();
99
 * Point2D origin1 = new Point2D.Double(100, 200);
100
 * Point2D origin2 = new Point2D.Double(200, 200);
101
 * int lenght = 100;
102
 * g.drawLine((int)origin1.getX()-length, (int)origin1.getY(), (int)origin1.getX()+length, (int)origin1.getY());
103
 * g.drawLine((int)origin1.getX(), (int)origin1.getY()-length, (int)origin1.getX(), (int)origin1.getY()+length);
104
 * // draw the rotated text
105
 * AffineTransform finalTransform = g.getTransform();
106
 * finalTransform.rotate(angle);
107
 * g.setTransform(finalTransform);
108
 * TextLayout text = new TextLayout("Hello world", g.getFont(), g.getFontRenderContext());
109
 * Point2D p = RotatedTextUtils.getPosition(origin1, angle, text, RotatedTextUtils.PLACEMENT_BOTTOM, RotatedTextUtils.ANCHOR_CORNER);
110
 * text.draw(g, (float)p.getX(), (float)p.getY());
111
 * // faster than RotatedTextUtils.draw if we are writing the same rotated text at different points
112
 * p = RotatedTextUtils.getPosition(origin2, angle, text, RotatedTextUtils.PLACEMENT_BOTTOM, RotatedTextUtils.ANCHOR_CORNER);
113
 * text.draw(g, (float)p.getX(), (float)p.getY());
114
 * g.setTransform(defaultAt);
115
 * </pre>
116
 * </p>
117
 * 
118
 * @author Cesar Martinez Izquierdo <cmartinez@scolab.es>
119
 *
120
 */
121
public class RotatedTextUtils {
122
        /**
123
         * PI/2
124
         */
125
        private static final double PI_HALF = Math.PI/2;
126
        /**
127
         * 3*PI/2
128
         */
129
        private static final double PI_HALF3 = 3*Math.PI/2;
130
        /**
131
         * 2*PI
132
         */
133
        private static final double PI_HALF4 = 2*Math.PI;
134
        
135
        /**
136
         * Anchor a corner of the text on the rotation center. In this way
137
         * a corner (left or right) of the text string will be aligned
138
         * with the rotation point.
139
         * 
140
         * The corner (left or right) to anchor will be automatically selected
141
         * depending on the rotation angle (choosing the corner which is closer
142
         * to the rotation center)
143
         */
144
        public static final int ANCHOR_CORNER = 0;
145
        /**
146
         * Anchor the center of the text on the rotation center. In this way
147
         * the center of the text string will be aligned with the rotation point.
148
         */
149
        public static final int ANCHOR_CENTER = 1;
150
        
151
        /**
152
         * Place the text on the top of the rotation point, meaning that no part
153
         * of the text is under the rotation point. 
154
         */
155
        public static final int PLACEMENT_TOP = 0;
156
        /**
157
         * Place the text bellow the rotation point, meaning that no part
158
         * of the text is over the rotation point. 
159
         */
160
        public static final int PLACEMENT_BOTTOM = 1;
161
        /**
162
         * Place the text on the left of the rotation point, meaning that no part
163
         * of the text is on the right the rotation point. 
164
         */
165
        public static final int PLACEMENT_LEFT = 2;
166
        /**
167
         * Place the text on the right of the rotation point, meaning that no part
168
         * of the text is on the left the rotation point. 
169
         */
170
        public static final int PLACEMENT_RIGHT = 3;
171
        
172
    /**
173
     * Draws rotated text which is positioned on
174
     * a specific side of the rotation point (TOP, BOTTOM, LEFT or RIGHT).
175
     * The text can be anchored by its central point or by the text corner.
176
     * 
177
     * @param origin        The rotation center point
178
     * @param g                        The target Graphics2D
179
     * @param strText         The text to draw .Use the Graphics2D options (font,
180
     * color, etc) to style the text before calling this method.
181
     * @param angle                The rotation angle, in radians. The angle should be comprised
182
     * in the [0, 2*PI[ range, result is otherwise unexpected
183
     * (a convenience method is provided to normalize it:
184
     *  {@link RotatedTextUtils#normalizeAngle(double)})
185
     * @param relativePosition The position of the text compared with the origin point.
186
     *  See {@link #PLACEMENT_TOP}, {@link #PLACEMENT_LEFT}, {@link #PLACEMENT_RIGHT} and
187
     *  {@value #PLACEMENT_BOTTOM}.
188
     * @param anchor         Whether the center of the label should be aligned with the
189
     * point ({@link #ANCHOR_CENTER}) or a corner of the label should be used
190
     * ({@link #ANCHOR_CORNER}).
191
     */
192
        public static void draw(Point2D origin, Graphics2D g, String strText, double angle, int relativePosition, int anchor) {
193
                AffineTransform defaultAt = g.getTransform();
194

    
195
                // affine transform containing the rotation plus the previous graphics transformations (if any)
196
                AffineTransform finalAt = g.getTransform();
197
                finalAt.rotate(angle);
198
                g.setTransform(finalAt);
199

    
200
                TextLayout text = new TextLayout(strText, g.getFont(), g.getFontRenderContext());
201
                Point2D position = null;
202

    
203
                if (anchor==ANCHOR_CORNER) {
204
                        switch (relativePosition) {
205
                        case PLACEMENT_RIGHT:
206
                                position = getPositionRightCorner(origin, text, angle);
207
                                break;
208
                        case PLACEMENT_BOTTOM:
209
                                position = getPositionBottomCorner(origin, text, angle);
210
                                break;
211
                        case PLACEMENT_LEFT:
212
                                position = getPositionLeftCorner(origin, text, angle);
213
                                break;
214
                        case PLACEMENT_TOP:
215
                        default:
216
                                position = getPositionTopCorner(origin, text, angle);
217
                                break;
218
                        }
219
                }
220
                else {
221
                        switch (relativePosition) {
222
                        case PLACEMENT_RIGHT:
223
                                position = getPositionRightCenter(origin, text, angle);
224
                                break;
225
                        case PLACEMENT_BOTTOM:
226
                                position = getPositionBottomCenter(origin, text, angle);
227
                                break;
228
                        case PLACEMENT_LEFT:
229
                                position = getPositionLeftCenter(origin, text, angle);
230
                                break;
231
                        case PLACEMENT_TOP:
232
                        default:
233
                                position = getPositionTopCenter(origin, text, angle);
234
                                break;
235
                        }
236
                }
237
                text.draw(g, (float)position.getX(), (float)position.getY());
238
                g.setTransform(defaultAt);
239
    }
240
    
241
    /**
242
     * <p>Gets the position in which the text should be drawn according to the
243
     * provided origin point, angle, align and anchor.</p>
244
     * 
245
     * <p>You may consider using
246
     * the higher level draw methods (e.g. {@link #draw(Point2D, Graphics2D, String, double, int, int)},
247
     * {@link #drawTopCenter(Point2D, Graphics2D, String, double)}, etc) if
248
     * you are drawing a single label, as this method makes some assumptions
249
     * for getting maximum performance when drawing several texts using the
250
     * same rotation. In particular, this method assumes that the target
251
     * Graphics2D has been rotated using the provided angle and the text
252
     * has been laid out for this rotated target Graphics2D.</p> 
253
         * 
254
         * <p>This method deals with coordinates in 2 different coordinate spaces
255
     * (the original, non-rotated space and the rotated space). The origin point
256
     * coordinates has to be referred to the non-rotated space, while the returned
257
     * position is referred to the rotated space.</p>
258
     * 
259
     * @param origin The point used as the center of the rotation
260
     * @param text   The text to be positioned, which has to be prepared for
261
     * a rotated graphics, matching the rotation angle 
262
     * @param angle The rotation angle, in radians. The angle should be comprised
263
     * in the [0, 2*PI[ range, result is otherwise unexpected
264
     * (a convenience method is provided to normalize it:
265
     *  {@link RotatedTextUtils#normalizeAngle(double)}
266
     * @param relativePosition The position of the text compared with the origin point.
267
     *  See {@link #PLACEMENT_TOP}, {@link #PLACEMENT_LEFT}, {@link #PLACEMENT_RIGHT} and
268
     *  {@value #PLACEMENT_BOTTOM}.
269
     * @param anchor Whether the center of the label should be aligned with the
270
     * point ({@link #ANCHOR_CENTER}) or a corner of the label should be used
271
     * ({@link #ANCHOR_CORNER}).
272
     * @return The position in which the text should be drawn.
273
     */
274
    public static Point2D getPosition(Point2D origin, TextLayout text, double angle, int relativePosition, int anchor) {
275
            if (anchor==ANCHOR_CORNER) {
276
                    switch (relativePosition) {
277
                    case PLACEMENT_RIGHT:
278
                            return getPositionRightCorner(origin, text, angle);
279
                    case PLACEMENT_BOTTOM:
280
                            return getPositionBottomCorner(origin, text, angle);
281
                    case PLACEMENT_LEFT:
282
                            return getPositionLeftCorner(origin, text, angle);
283
                    case PLACEMENT_TOP:
284
                    default:
285
                            return getPositionTopCorner(origin, text, angle);
286
                    }
287
            }
288
            else {
289
                    switch (relativePosition) {
290
                    case PLACEMENT_RIGHT:
291
                            return getPositionRightCenter(origin, text, angle);
292
                    case PLACEMENT_BOTTOM:
293
                            return getPositionBottomCenter(origin, text, angle);
294
                    case PLACEMENT_LEFT:
295
                            return getPositionLeftCenter(origin, text, angle);
296
                    case PLACEMENT_TOP:
297
                    default:
298
                            return getPositionTopCenter(origin, text, angle);
299
                    }
300
            }
301
    }
302

    
303
    /**
304
     * <p>Gets the position in which the text should be drawn according to the
305
     * provided origin point and angle, placing the text at the top of the
306
     * point and using a corner of the text as anchor.</p>
307
     * 
308
     * <p>This method deals with coordinates in 2 different coordinate spaces
309
     * (the original, non-rotated space and the rotated space). The origin point
310
     * coordinates has to be referred to the non-rotated space, while the returned
311
     * position is referred to the rotated space.</p>
312
     *  
313
     * @param origin The center point of the rotation
314
     * @param text   The text to be drawn, created for the rotated Graphics2D
315
     * @param angle  The rotation angle, in radians. Angle is assumed to be normalized (see
316
     * {@link #normalizeAngle(double)}) 
317
     * @return The position in which the text should be drawn, referenced to the rotated
318
     * coordinate space
319
     * 
320
     * @throws NoninvertibleTransformException
321
     * @see {@link RotatedTextUtils#getPosition(Point2D, double, TextLayout, int, int)}
322
     * @see RotatedTextUtils#PLACEMENT_TOP
323
     * @see RotatedTextUtils#ANCHOR_CORNER
324
     */
325
    public static Point2D getPositionTopCorner(Point2D origin, TextLayout text, double angle) {
326
            double height = text.getBounds().getHeight();
327
            double descent = text.getBounds().getHeight()+text.getBounds().getY();
328
            double width = text.getAdvance();
329
            double yOffset;
330
            double xOffset;
331
            
332
                double correctedOriginX = origin.getX()+getRotatedDescent2(descent, angle);
333
                double correctedOriginY = origin.getY()-getRotatedDescent1(descent, angle);
334

    
335
            if (angle>0.0d && angle<PI_HALF) { // first quadrant
336
                    xOffset = -getRotatedWidth1(width, angle)-getRotatedWidth2(height, angle)/2.0d;
337
                    yOffset = -getRotatedHeight1(width, angle);
338
            }
339
            else if (angle<0.1d) { // when is 0
340
                    xOffset = 0.0d;
341
                    yOffset = 0.0d;
342
            }
343
            else if (angle>PI_HALF3) {  // fourth quadrant
344
                    xOffset = getRotatedWidth2(height, angle)/2.0d;
345
                    yOffset = 0.0d;
346
            }
347
            else if (angle<Math.PI) { // second quadrant
348
                    xOffset = getRotatedWidth1(width, angle)-getRotatedWidth2(height, angle)/2.0d;
349
                    yOffset = -(getRotatedHeight1(width, angle)+getRotatedHeight2(height, angle));
350
            }
351
            else { // third quadrant
352
                    xOffset = getRotatedWidth2(height, angle)/2.0d; 
353
                    yOffset = -getRotatedHeight2(height, angle);
354
            }
355
            Point2D result = new Point2D.Double(correctedOriginX+xOffset, correctedOriginY+yOffset);
356
            // Transform the calculated drawing point from the non-rotated coordinate space
357
            // to the final (rotated) coordinate space.
358
            // All the above calculations have been made using the non-rotated coordinate space
359
            AffineTransform at = AffineTransform.getRotateInstance(angle);
360
            try {
361
                        at.inverseTransform(result, result);
362
                } catch (NoninvertibleTransformException e) {
363
                        // can't happen: rotation always has inverste tranform
364
                }
365
        return result;
366
    }
367

    
368
    /**
369
     * <p>Gets the position in which the text should be drawn according to the
370
     * provided origin point and angle, placing the text at the top of the
371
     * point and using the center of the text as anchor.</p>
372
     * 
373
     * <p>This method deals with coordinates in 2 different coordinate spaces
374
     * (the original, non-rotated space and the rotated space). The origin point
375
     * coordinates has to be referred to the non-rotated space, while the returned
376
     * position is referred to the rotated space.</p>
377
     *  
378
     * @param origin The center point of the rotation
379
     * @param text   The text to be drawn, created for the rotated Graphics2D
380
     * @param angle  The rotation angle, in radians. Angle is assumed to be normalized (see
381
     * {@link #normalizeAngle(double)}) 
382
     * @return The position in which the text should be drawn, referenced to the rotated
383
     * coordinate space
384
     * 
385
     * @throws NoninvertibleTransformException
386
     * @see {@link RotatedTextUtils#getPosition(Point2D, double, TextLayout, int, int)}
387
     * @see RotatedTextUtils#PLACEMENT_TOP
388
     * @see RotatedTextUtils#ANCHOR_CENTER
389
     */
390
    public static Point2D getPositionTopCenter(Point2D origin, TextLayout text, double angle) {
391
            double height = text.getBounds().getHeight();
392
            double descent = text.getBounds().getHeight()+text.getBounds().getY();
393
            double width = text.getAdvance();
394
            double yOffset;
395
            double xOffset;
396
            
397
                double correctedOriginX = origin.getX()+getRotatedDescent2(descent, angle);
398
                double correctedOriginY = origin.getY()-getRotatedDescent1(descent, angle);
399

    
400
            if (angle>0.0d && angle<PI_HALF) { // first quadrant
401
                    xOffset = -(getRotatedWidth1(width, angle)+getRotatedWidth2(height, angle))/2.0d;
402
                    yOffset = -getRotatedHeight1(width, angle);
403
            }
404
            else if (angle<0.1d) { // when is 0
405
                    xOffset = -width/2.0d;
406
                    yOffset = 0.0d;
407
            }
408
            else if (angle>PI_HALF3) {  // fourth quadrant
409
                    xOffset = (getRotatedWidth2(height, angle)-getRotatedWidth1(width, angle))/2.0d;
410
                    yOffset = 0.0d;
411
            }
412
            else if (angle<Math.PI) { // second quadrant
413
                    xOffset = (getRotatedWidth1(width, angle)-getRotatedWidth2(height, angle))/2.0d;
414
                    yOffset = -(getRotatedHeight1(width, angle)+getRotatedHeight2(height, angle));
415
            }
416
            else { // third quadrant
417
                    xOffset = (getRotatedWidth1(width, angle) + getRotatedWidth2(height, angle))/2.0d; 
418
                    yOffset = -getRotatedHeight2(height, angle);
419
            }
420
            Point2D result = new Point2D.Double(correctedOriginX+xOffset, correctedOriginY+yOffset);
421
            // Transform the calculated drawing point from the non-rotated coordinate space
422
            // to the final (rotated) coordinate space.
423
            // All the above calculations have been made using the non-rotated coordinate space
424
            AffineTransform at = AffineTransform.getRotateInstance(angle);
425
            try {
426
                        at.inverseTransform(result, result);
427
                } catch (NoninvertibleTransformException e) {
428
                        // can't happen: rotation always has inverste tranform
429
                }
430
        return result;
431
    }
432

    
433

    
434
    /**
435
     * <p>Gets the position in which the text should be drawn according to the
436
     * provided origin point and angle, placing the text at the right of the
437
     * point and using a corner of the text as anchor.</p>
438
     * 
439
     * <p>This method deals with coordinates in 2 different coordinate spaces
440
     * (the original, non-rotated space and the rotated space). The origin point
441
     * coordinates has to be referred to the non-rotated space, while the returned
442
     * position is referred to the rotated space.</p>
443
     *  
444
     * @param origin The center point of the rotation
445
     * @param text   The text to be drawn, created for the rotated Graphics2D
446
     * @param angle  The rotation angle, in radians. Angle is assumed to be normalized (see
447
     * {@link #normalizeAngle(double)}) 
448
     * @return The position in which the text should be drawn, referenced to the rotated
449
     * coordinate space
450
     * 
451
     * @throws NoninvertibleTransformException
452
     * @see {@link RotatedTextUtils#getPosition(Point2D, double, TextLayout, int, int)}
453
     * @see RotatedTextUtils#PLACEMENT_RIGHT
454
     * @see RotatedTextUtils#ANCHOR_CORNER
455
     */
456
    public static Point2D getPositionRightCorner(Point2D origin, TextLayout text, double angle) {
457
            double height = text.getBounds().getHeight();
458
            double descent = text.getBounds().getHeight()+text.getBounds().getY();
459
            double width = text.getAdvance();
460
            double yOffset;
461
            double xOffset;
462
                double correctedOriginX = origin.getX()+getRotatedDescent2(descent, angle);
463
                double correctedOriginY = origin.getY()-getRotatedDescent1(descent, angle);
464
                
465
            if (angle>0.0d && angle<PI_HALF) { // first quadrant
466
                    xOffset = 0.0d;
467
                    yOffset = getRotatedHeight2(height, angle)/2.0d;
468
            }
469
            else if (angle<0.1d) { // when is 0
470
                    xOffset = 0.0d;
471
                    yOffset = height/2.0d;
472
            }
473
            else if (angle>PI_HALF3) {  // fourth quadrant
474
                    xOffset = getRotatedWidth2(height, angle);
475
                    yOffset = getRotatedHeight2(height, angle)/2.0d;
476
            }
477
            else if (angle<Math.PI) { // second quadrant
478
                    xOffset = getRotatedWidth1(width, angle);
479
                    yOffset = -getRotatedHeight1(width, angle)-(getRotatedHeight2(height, angle))/2.0d;
480
            }
481
            else { // third quadrant
482
                    xOffset = getRotatedWidth1(width, angle) + getRotatedWidth2(height, angle);
483
                    yOffset = -getRotatedHeight2(height, angle)/2.0d +getRotatedHeight1(width, angle);
484
            }
485
            Point2D result = new Point2D.Double(correctedOriginX+xOffset, correctedOriginY+yOffset);
486
            // Transform the calculated drawing point from the non-rotated coordinate space
487
            // to the final (rotated) coordinate space.
488
            // All the above calculations have been made using the non-rotated coordinate space
489
            AffineTransform at = AffineTransform.getRotateInstance(angle);
490
            try {
491
                        at.inverseTransform(result, result);
492
                } catch (NoninvertibleTransformException e) {
493
                        // can't happen: rotation always has inverste tranform
494
                }
495
            return result;
496
    }
497

    
498
    /**
499
     * <p>Gets the position in which the text should be drawn according to the
500
     * provided origin point and angle, placing the text at the right of the
501
     * point and using the center of the text as anchor.</p>
502
     * 
503
     * <p>This method deals with coordinates in 2 different coordinate spaces
504
     * (the original, non-rotated space and the rotated space). The origin point
505
     * coordinates has to be referred to the non-rotated space, while the returned
506
     * position is referred to the rotated space.</p>
507
     *  
508
     * @param origin The center point of the rotation
509
     * @param text   The text to be drawn, created for the rotated Graphics2D
510
     * @param angle  The rotation angle, in radians. Angle is assumed to be normalized (see
511
     * {@link #normalizeAngle(double)}) 
512
     * @return The position in which the text should be drawn, referenced to the rotated
513
     * coordinate space
514
     * 
515
     * @throws NoninvertibleTransformException
516
     * @see {@link RotatedTextUtils#getPosition(Point2D, double, TextLayout, int, int)}
517
     * @see RotatedTextUtils#PLACEMENT_RIGHT
518
     * @see RotatedTextUtils#ANCHOR_CENTER
519
     */
520
    public static Point2D getPositionRightCenter(Point2D origin, TextLayout text, double angle) {
521
            double height = text.getBounds().getHeight();
522
            double descent = text.getBounds().getHeight()+text.getBounds().getY();
523
            double width = text.getAdvance();
524
            double yOffset;
525
            double xOffset;
526
            
527
                double correctedOriginX = origin.getX()+getRotatedDescent2(descent, angle);
528
                double correctedOriginY = origin.getY()-getRotatedDescent1(descent, angle);
529
                
530
            if (angle>0.0d && angle<PI_HALF) { // first quadrant
531
                    xOffset = 0.0;
532
                    yOffset = (getRotatedHeight2(height, angle)-getRotatedHeight1(width, angle))/2.0d;
533
            }
534
            else if (angle<0.1d) { // when is 0
535
                    xOffset = 0.0;
536
                    yOffset = height/2.0d;
537
            }
538
            else if (angle>PI_HALF3) {  // fourth quadrant
539
                    xOffset = (getRotatedWidth2(height, angle));
540
                    yOffset = (getRotatedHeight1(width, angle)+getRotatedHeight2(height, angle))/2.0d;
541
            }
542
            else if (angle<Math.PI) { // second quadrant
543
                    xOffset = getRotatedWidth1(width, angle);
544
                    yOffset = -(getRotatedHeight2(height, angle)+getRotatedHeight1(width, angle))/2.0d;
545
            }
546
            else { // third quadrant
547
                    xOffset = getRotatedWidth1(width, angle)+getRotatedWidth2(height, angle); 
548
                    yOffset = (getRotatedHeight1(width, angle)-getRotatedHeight2(height, angle))/2.0d;
549
            }
550
            Point2D result = new Point2D.Double(correctedOriginX+xOffset, correctedOriginY+yOffset);
551
            // Transform the calculated drawing point from the non-rotated coordinate space
552
            // to the final (rotated) coordinate space.
553
            // All the above calculations have been made using the non-rotated coordinate space
554
            AffineTransform at = AffineTransform.getRotateInstance(angle);
555
            try {
556
                        at.inverseTransform(result, result);
557
                } catch (NoninvertibleTransformException e) {
558
                        // can't happen: rotation always has inverste tranform
559
                }
560
            return result;
561
    }
562

    
563
    /**
564
     * <p>Gets the position in which the text should be drawn according to the
565
     * provided origin point and angle, placing the text at the bottom of the
566
     * point and using a corner of the text as anchor.</p>
567
     * 
568
     * <p>This method deals with coordinates in 2 different coordinate spaces
569
     * (the original, non-rotated space and the rotated space). The origin point
570
     * coordinates has to be referred to the non-rotated space, while the returned
571
     * position is referred to the rotated space.</p>
572
     *  
573
     * @param origin The center point of the rotation
574
     * @param text   The text to be drawn, created for the rotated Graphics2D
575
     * @param angle  The rotation angle, in radians. Angle is assumed to be normalized (see
576
     * {@link #normalizeAngle(double)}) 
577
     * @return The position in which the text should be drawn, referenced to the rotated
578
     * coordinate space
579
     * 
580
     * @throws NoninvertibleTransformException
581
     * @see {@link RotatedTextUtils#getPosition(Point2D, double, TextLayout, int, int)}
582
     * @see RotatedTextUtils#PLACEMENT_BOTTOM
583
     * @see RotatedTextUtils#ANCHOR_CORNER
584
     */
585
    public static Point2D getPositionBottomCorner(Point2D origin, TextLayout text, double angle) {
586
            double height = text.getBounds().getHeight();
587
            double descent = text.getBounds().getHeight()+text.getBounds().getY();
588
            double width = text.getAdvance();
589
            double yOffset;
590
            double xOffset;
591
                double correctedOriginX = origin.getX()+getRotatedDescent2(descent, angle);
592
                double correctedOriginY = origin.getY()-getRotatedDescent1(descent, angle);
593
                
594
            if (angle>0.0d && angle<=PI_HALF) { // first quadrant
595
                    xOffset = -getRotatedWidth2(height, angle)/2.0d;
596
                    yOffset = getRotatedHeight2(height, angle);
597
            }
598
            else if (angle<0.1d) { // when is 0
599
                    xOffset = 0.0d;
600
                    yOffset = height;
601
            }
602
            else if (angle>=PI_HALF3) {  // fourth quadrant
603
                    xOffset = getRotatedWidth2(height, angle)/2.0d-getRotatedWidth1(width, angle);
604
                    yOffset = getRotatedHeight1(width, angle)+getRotatedHeight2(height, angle);
605

    
606
            }
607
            else if (angle<Math.PI) { // second quadrant
608
                    xOffset = -getRotatedWidth2(height, angle)/2.0d;
609
                    yOffset = 0.0d;
610
            }
611
            else { // third quadrant
612
                    xOffset = getRotatedWidth1(width, angle) + getRotatedWidth2(height, angle)/2.0d; 
613
                    yOffset = getRotatedHeight1(width, angle);
614
            }
615
            Point2D result = new Point2D.Double(correctedOriginX+xOffset, correctedOriginY+yOffset);
616
            // Transform the calculated drawing point from the non-rotated coordinate space
617
            // to the final (rotated) coordinate space.
618
            // All the above calculations have been made using the non-rotated coordinate space
619
            AffineTransform at = AffineTransform.getRotateInstance(angle);
620
            try {
621
                        at.inverseTransform(result, result);
622
                } catch (NoninvertibleTransformException e) {
623
                        // can't happen: rotation always has inverste tranform
624
                }
625
            return result;
626
    }
627

    
628
    /**
629
     * <p>Gets the position in which the text should be drawn according to the
630
     * provided origin point and angle, placing the text at the bottom of the
631
     * point and using the center of the text as anchor.</p>
632
     * 
633
     * <p>This method deals with coordinates in 2 different coordinate spaces
634
     * (the original, non-rotated space and the rotated space). The origin point
635
     * coordinates has to be referred to the non-rotated space, while the returned
636
     * position is referred to the rotated space.</p>
637
     *  
638
     * @param origin The center point of the rotation
639
     * @param text   The text to be drawn, created for the rotated Graphics2D
640
     * @param angle  The rotation angle, in radians. Angle is assumed to be normalized (see
641
     * {@link #normalizeAngle(double)}) 
642
     * @return The position in which the text should be drawn, referenced to the rotated
643
     * coordinate space
644
     * 
645
     * @throws NoninvertibleTransformException
646
     * @see {@link RotatedTextUtils#getPosition(Point2D, double, TextLayout, int, int)}
647
     * @see RotatedTextUtils#PLACEMENT_BOTTOM
648
     * @see RotatedTextUtils#ANCHOR_CENTER
649
     */
650
    public static Point2D getPositionBottomCenter(Point2D origin, TextLayout text, double angle) {
651
            double height = text.getBounds().getHeight();
652
            double descent = text.getBounds().getHeight()+text.getBounds().getY();
653
            double width = text.getAdvance();
654
            double yOffset;
655
            double xOffset;
656
                double correctedOriginX = origin.getX()+getRotatedDescent2(descent, angle);
657
                double correctedOriginY = origin.getY()-getRotatedDescent1(descent, angle);
658

    
659
            if (angle>0.0d && angle<PI_HALF) { // first quadrant
660
                    xOffset = -(getRotatedWidth1(width, angle)+getRotatedWidth2(height, angle))/2.0d;
661
                    yOffset = getRotatedHeight2(height, angle);
662
            }
663
            else if (angle<0.1d) { // when is 0
664
                    xOffset = -width/2.0d;
665
                    yOffset = height;
666
            }
667
            else if (angle>PI_HALF3) {  // fourth quadrant
668
                    xOffset = (getRotatedWidth2(height, angle)-getRotatedWidth1(width, angle))/2.0d;
669
                    yOffset = getRotatedHeight1(width, angle)+getRotatedHeight2(height, angle);
670
            }
671
            else if (angle<Math.PI) { // second quadrant
672
                    xOffset = (getRotatedWidth1(width, angle)-getRotatedWidth2(height, angle))/2.0d;
673
                    yOffset = 0.0d;
674
            }
675
            else { // third quadrant
676
                    xOffset = (getRotatedWidth1(width, angle) + getRotatedWidth2(height, angle))/2.0d; 
677
                    yOffset = getRotatedHeight1(width, angle);
678
            }
679
            Point2D result = new Point2D.Double(correctedOriginX+xOffset, correctedOriginY+yOffset);
680
            // Transform the calculated drawing point from the non-rotated coordinate space
681
            // to the final (rotated) coordinate space.
682
            // All the above calculations have been made using the non-rotated coordinate space
683
            AffineTransform at = AffineTransform.getRotateInstance(angle);
684
            try {
685
                        at.inverseTransform(result, result);
686
                } catch (NoninvertibleTransformException e) {
687
                        // can't happen: rotation always has inverste tranform
688
                }
689
            return result;
690
    }
691

    
692

    
693
    /**
694
     * <p>Gets the position in which the text should be drawn according to the
695
     * provided origin point and angle, placing the text at the left of the
696
     * point and using a corner of the text as anchor.</p>
697
     * 
698
     * <p>This method deals with coordinates in 2 different coordinate spaces
699
     * (the original, non-rotated space and the rotated space). The origin point
700
     * coordinates has to be referred to the non-rotated space, while the returned
701
     * position is referred to the rotated space.</p>
702
     *  
703
     * @param origin The center point of the rotation
704
     * @param text   The text to be drawn, created for the rotated Graphics2D
705
     * @param angle  The rotation angle, in radians. Angle is assumed to be normalized (see
706
     * {@link #normalizeAngle(double)}) 
707
     * @return The position in which the text should be drawn, referenced to the rotated
708
     * coordinate space
709
     * 
710
     * @throws NoninvertibleTransformException
711
     * @see {@link RotatedTextUtils#getPosition(Point2D, double, TextLayout, int, int)}
712
     * @see RotatedTextUtils#PLACEMENT_LEFT
713
     * @see RotatedTextUtils#ANCHOR_CORNER
714
     */
715
    public static Point2D getPositionLeftCorner(Point2D origin, TextLayout text, double angle) {
716
            double height = text.getBounds().getHeight();
717
            double descent = text.getBounds().getHeight()+text.getBounds().getY();
718
            double width = text.getAdvance();
719
            double yOffset;
720
            double xOffset;
721
                double correctedOriginX = origin.getX()+getRotatedDescent2(descent, angle);
722
                double correctedOriginY = origin.getY()-getRotatedDescent1(descent, angle);
723
                
724
            if (angle>0.0d && angle<PI_HALF) { // first quadrant
725
                    xOffset = -(getRotatedWidth1(width, angle)+getRotatedWidth2(height, angle));
726
                    yOffset = getRotatedHeight2(height, angle)/2.0d -getRotatedHeight1(width, angle);
727
            }
728
            else if (angle<0.1d) { // when is 0
729
                    xOffset = -width;
730
                    yOffset = height/2.0d;
731
            }
732
            else if (angle>PI_HALF3) {  // fourth quadrant
733
                    xOffset = (-getRotatedWidth1(width, angle));
734
                    yOffset = getRotatedHeight1(width, angle)+(getRotatedHeight2(height, angle))/2.0d;
735
            }
736
            else if (angle<Math.PI) { // second quadrant
737
                    xOffset = -(getRotatedWidth2(height, angle));
738
                    yOffset = -getRotatedHeight2(height, angle)/2.0d;
739
            }
740
            else { // third quadrant
741
                    xOffset = 0.0d; 
742
                    yOffset = -getRotatedHeight2(height, angle)/2.0d;
743
            }
744
            Point2D result = new Point2D.Double(correctedOriginX+xOffset, correctedOriginY+yOffset);
745
            // Transform the calculated drawing point from the non-rotated coordinate space
746
            // to the final (rotated) coordinate space.
747
            // All the above calculations have been made using the non-rotated coordinate space
748
            AffineTransform at = AffineTransform.getRotateInstance(angle);
749
            try {
750
                        at.inverseTransform(result, result);
751
                } catch (NoninvertibleTransformException e) {
752
                        // can't happen: rotation always has inverste tranform
753
                }
754
            return result;
755
    }
756
    /**
757
     * <p>Gets the position in which the text should be drawn according to the
758
     * provided origin point and angle, placing the text at the left of the
759
     * point and using the center of the text as anchor.</p>
760
     * 
761
     * <p>This method deals with coordinates in 2 different coordinate spaces
762
     * (the original, non-rotated space and the rotated space). The origin point
763
     * coordinates has to be referred to the non-rotated space, while the returned
764
     * position is referred to the rotated space.</p>
765
     *  
766
     * @param origin The center point of the rotation
767
     * @param text   The text to be drawn, created for the rotated Graphics2D
768
     * @param angle  The rotation angle, in radians. Angle is assumed to be normalized (see
769
     * {@link #normalizeAngle(double)}) 
770
     * @return The position in which the text should be drawn, referenced to the rotated
771
     * coordinate space
772
     * 
773
     * @throws NoninvertibleTransformException
774
     * @see {@link RotatedTextUtils#getPosition(Point2D, double, TextLayout, int, int)}
775
     * @see RotatedTextUtils#PLACEMENT_LEFT
776
     * @see RotatedTextUtils#ANCHOR_CENTER
777
     */
778
    public static Point2D getPositionLeftCenter(Point2D origin, TextLayout text, double angle) {
779
            double height = text.getBounds().getHeight();
780
            double descent = text.getBounds().getHeight()+text.getBounds().getY();
781
            double width = text.getAdvance();
782
            double yOffset;
783
            double xOffset;
784
                double correctedOriginX = origin.getX()+getRotatedDescent2(descent, angle);
785
                double correctedOriginY = origin.getY()-getRotatedDescent1(descent, angle);
786
                
787
            if (angle>0.0d && angle<PI_HALF) { // first quadrant
788
                    xOffset = -(getRotatedWidth1(width, angle)+getRotatedWidth2(height, angle));
789
                    yOffset = (getRotatedHeight2(height, angle)-getRotatedHeight1(width, angle))/2.0d;
790
            }
791
            else if (angle<0.1d) { // when is 0
792
                    xOffset = -width;
793
                    yOffset = height/2.0d;
794
            }
795
            else if (angle>PI_HALF3) {  // fourth quadrant
796
                    xOffset = (-getRotatedWidth1(width, angle));
797
                    yOffset = (getRotatedHeight1(width, angle)+getRotatedHeight2(height, angle))/2.0d;
798
            }
799
            else if (angle<Math.PI) { // second quadrant
800
                    xOffset = -(getRotatedWidth2(height, angle));
801
                    yOffset = -(getRotatedHeight2(height, angle)+getRotatedHeight1(width, angle))/2.0d;
802
            }
803
            else { // third quadrant
804
                    xOffset = 0.0d; 
805
                    yOffset = (getRotatedHeight1(width, angle)-getRotatedHeight2(height, angle))/2.0d;
806
            }
807
            Point2D result = new Point2D.Double(correctedOriginX+xOffset, correctedOriginY+yOffset);
808
            // Transform the calculated drawing point from the non-rotated coordinate space
809
            // to the final (rotated) coordinate space.
810
            // All the above calculations have been made using the non-rotated coordinate space
811
            AffineTransform at = AffineTransform.getRotateInstance(angle);
812
            try {
813
                        at.inverseTransform(result, result);
814
                } catch (NoninvertibleTransformException e) {
815
                        // can't happen: rotation always has inverste tranform
816
                }
817
            return result;
818
    }
819
    
820
    /**
821
     * Draws the provided text rotated by angle radians using location as rotation center
822
     * without any positioning or anchoring adjustments.
823
     * Use the Graphics2D options (font, color, etc) to style the text before calling
824
     * this method.
825
     * 
826
     * @param location The rotation center
827
     * @param g        The Graphics2D on which the text should be drawn
828
     * @param strText  The text to be drawn
829
     * @param angle    The rotation angle, in radians
830
     */
831
    public static void drawRotated(Point2D location, Graphics2D g, String strText, double angle) {
832
            AffineTransform defaultAt = g.getTransform();
833
            AffineTransform at = AffineTransform.getRotateInstance(angle);
834
        g.setTransform(at);
835
        TextLayout text = new TextLayout(strText, g.getFont(), g.getFontRenderContext());
836
        Point2D result = new Point2D.Double(location.getX(), location.getY());
837
            try {
838
                        at.inverseTransform(result, result);
839
                } catch (NoninvertibleTransformException e) {
840
                        // can't happen: rotation always has inverste tranform
841
                }
842
        text.draw(g, (float)result.getX(), (float)result.getY());
843
        g.setTransform(defaultAt);
844
    }
845

    
846
        /**
847
         * Normalizes an angle, in radians. A normalized angle
848
         * is an angle contained in the range [0, 2*PI[.
849
         * 
850
         * @param angle The angle to normalize, in radians
851
         * @return Normalized angled, in radians
852
         */
853
        public static double normalizeAngle(double angle) {
854
                double module = angle%(PI_HALF4);
855
                if (module>=0) {
856
                        return module;
857
                }
858
                else {
859
                        return (angle + PI_HALF4);
860
                }
861
        }
862
    
863
    private static double getRotatedHeight1(double width, double angle) {
864
            return Math.abs(width*Math.sin(angle));
865
    }
866
    
867
    private static double getRotatedHeight2(double height, double angle) {
868
            return Math.abs(height*Math.cos(angle));
869
    }
870
    
871
    private static double getRotatedWidth1(double width, double angle) {
872
            return Math.abs(width*Math.cos(angle));
873
    }
874
    
875
    private static double getRotatedWidth2(double height, double angle) {
876
            return Math.abs(height*Math.sin(angle));
877
    }
878
    
879
    private static double getRotatedDescent1(double descent, double angle) {
880
            return descent*Math.sin(angle+PI_HALF);
881
    }
882
    
883
    private static double getRotatedDescent2(double descent, double angle) {
884
            return descent*Math.sin(angle);
885
    }
886
    
887
    
888
    private static double getRotatedOffsetX1(double baseOffsetX, double angle) {
889
            return Math.abs(baseOffsetX*Math.cos(angle+PI_HALF));
890
    }
891
    
892
    private static double getRotatedOffsetX2(double baseOffsetX, double angle) {
893
            return Math.abs(baseOffsetX*Math.sin(angle));
894
    }
895
}
896

    
897