Revision 42050

View differences:

trunk/org.gvsig.desktop/org.gvsig.desktop.library/org.gvsig.ui/src/main/java/org/gvsig/gui/awt/text/RotatedTextUtils.java
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 angle The rotation angle, in radians. The angle should be comprised
261
     * in the [0, 2*PI[ range, result is otherwise unexpected
262
     * (a convenience method is provided to normalize it:
263
     *  {@link RotatedTextUtils#normalizeAngle(double)}
264
     * @param text   The text to be positioned, which has to be prepared for
265
     * a rotated graphics, matching the rotation angle 
266
     * @param at    An affine transform matching the rotation angle 
267
     * @param relativePosition The position of the text compared with the origin point.
268
     *  See {@link #PLACEMENT_TOP}, {@link #PLACEMENT_LEFT}, {@link #PLACEMENT_RIGHT} and
269
     *  {@value #PLACEMENT_BOTTOM}.
270
     * @param anchor Whether the center of the label should be aligned with the
271
     * point ({@link #ANCHOR_CENTER}) or a corner of the label should be used
272
     * ({@link #ANCHOR_CORNER}).
273
     * @return The position in which the text should be drawn.
274
     * 
275
     * @throws NoninvertibleTransformException
276
     */
277
    public static Point2D getPosition(Point2D origin, double angle, TextLayout text, int relativePosition, int anchor)  throws NoninvertibleTransformException {
278
    	if (anchor==ANCHOR_CORNER) {
279
    		switch (relativePosition) {
280
    		case PLACEMENT_RIGHT:
281
    			return getPositionRightCorner(origin, text, angle);
282
    		case PLACEMENT_BOTTOM:
283
    			return getPositionBottomCorner(origin, text, angle);
284
    		case PLACEMENT_LEFT:
285
    			return getPositionLeftCorner(origin, text, angle);
286
    		case PLACEMENT_TOP:
287
    		default:
288
    			return getPositionTopCorner(origin, text, angle);
289
    		}
290
    	}
291
    	else {
292
    		switch (relativePosition) {
293
    		case PLACEMENT_RIGHT:
294
    			return getPositionRightCenter(origin, text, angle);
295
    		case PLACEMENT_BOTTOM:
296
    			return getPositionBottomCenter(origin, text, angle);
297
    		case PLACEMENT_LEFT:
298
    			return getPositionLeftCenter(origin, text, angle);
299
    		case PLACEMENT_TOP:
300
    		default:
301
    			return getPositionTopCenter(origin, text, angle);
302
    		}
303
    	}
304
    }
305

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

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

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

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

  
436

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

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

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

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

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

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

  
695

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

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

  
900

  

Also available in: Unified diff