1 /*
2 * $Id: Rectangle.java 3742 2009-03-03 16:42:09Z blowagie $
3 *
4 * Copyright 1999, 2000, 2001, 2002 by Bruno Lowagie.
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * (the "License"); you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the License.
13 *
14 * The Original Code is 'iText, a free JAVA-PDF library'.
15 *
16 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
17 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
18 * All Rights Reserved.
19 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
20 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
21 *
22 * Contributor(s): all the names of the contributors are added in the source code
23 * where applicable.
24 *
25 * Alternatively, the contents of this file may be used under the terms of the
26 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
27 * provisions of LGPL are applicable instead of those above. If you wish to
28 * allow use of your version of this file only under the terms of the LGPL
29 * License and not to allow others to use your version of this file under
30 * the MPL, indicate your decision by deleting the provisions above and
31 * replace them with the notice and other provisions required by the LGPL.
32 * If you do not delete the provisions above, a recipient may use your version
33 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
34 *
35 * This library is free software; you can redistribute it and/or modify it
36 * under the terms of the MPL as stated above or under the terms of the GNU
37 * Library General Public License as published by the Free Software Foundation;
38 * either version 2 of the License, or any later version.
39 *
40 * This library is distributed in the hope that it will be useful, but WITHOUT
41 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
42 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
43 * details.
44 *
45 * If you didn't download this code from the following link, you should check if
46 * you aren't using an obsolete version:
47 * http://www.lowagie.com/iText/
48 */
49
50 package com.lowagie.text;
51
52 import java.awt.Color;
53 import java.util.ArrayList;
54
55 import com.lowagie.text.pdf.GrayColor;
56
57 /**
58 * A <CODE>Rectangle</CODE> is the representation of a geometric figure.
59 *
60 * Rectangles support constant width borders using
61 * {@link #setBorderWidth(float)}and {@link #setBorder(int)}.
62 * They also support borders that vary in width/color on each side using
63 * methods like {@link #setBorderWidthLeft(float)}or
64 * {@link #setBorderColorLeft(java.awt.Color)}.
65 *
66 * @see Element
67 * @see Table
68 * @see Cell
69 * @see HeaderFooter
70 */
71 public class Rectangle implements Element {
72
73 // CONSTANTS:
74
75 /** This is the value that will be used as <VAR>undefined </VAR>. */
76 public static final int UNDEFINED = -1;
77
78 /** This represents one side of the border of the <CODE>Rectangle</CODE>. */
79 public static final int TOP = 1;
80
81 /** This represents one side of the border of the <CODE>Rectangle</CODE>. */
82 public static final int BOTTOM = 2;
83
84 /** This represents one side of the border of the <CODE>Rectangle</CODE>. */
85 public static final int LEFT = 4;
86
87 /** This represents one side of the border of the <CODE>Rectangle</CODE>. */
88 public static final int RIGHT = 8;
89
90 /** This represents a rectangle without borders. */
91 public static final int NO_BORDER = 0;
92
93 /** This represents a type of border. */
94 public static final int BOX = TOP + BOTTOM + LEFT + RIGHT;
95
96 // MEMBER VARIABLES:
97
98 /** the lower left x-coordinate. */
99 protected float llx;
100
101 /** the lower left y-coordinate. */
102 protected float lly;
103
104 /** the upper right x-coordinate. */
105 protected float urx;
106
107 /** the upper right y-coordinate. */
108 protected float ury;
109
110 /** The rotation of the Rectangle */
111 protected int rotation = 0;
112
113 /** This is the color of the background of this rectangle. */
114 protected Color backgroundColor = null;
115
116 /** This represents the status of the 4 sides of the rectangle. */
117 protected int border = UNDEFINED;
118
119 /** Whether variable width/color borders are used. */
120 protected boolean useVariableBorders = false;
121
122 /** This is the width of the border around this rectangle. */
123 protected float borderWidth = UNDEFINED;
124
125 /** The width of the left border of this rectangle. */
126 protected float borderWidthLeft = UNDEFINED;
127
128 /** The width of the right border of this rectangle. */
129 protected float borderWidthRight = UNDEFINED;
130
131 /** The width of the top border of this rectangle. */
132 protected float borderWidthTop = UNDEFINED;
133
134 /** The width of the bottom border of this rectangle. */
135 protected float borderWidthBottom = UNDEFINED;
136
137 /** The color of the border of this rectangle. */
138 protected Color borderColor = null;
139
140 /** The color of the left border of this rectangle. */
141 protected Color borderColorLeft = null;
142
143 /** The color of the right border of this rectangle. */
144 protected Color borderColorRight = null;
145
146 /** The color of the top border of this rectangle. */
147 protected Color borderColorTop = null;
148
149 /** The color of the bottom border of this rectangle. */
150 protected Color borderColorBottom = null;
151
152 // CONSTRUCTORS:
153
154 /**
155 * Constructs a <CODE>Rectangle</CODE> -object.
156 *
157 * @param llx lower left x
158 * @param lly lower left y
159 * @param urx upper right x
160 * @param ury upper right y
161 */
162 public Rectangle(float llx, float lly, float urx, float ury) {
163 this.llx = llx;
164 this.lly = lly;
165 this.urx = urx;
166 this.ury = ury;
167 }
168
169 /**
170 * Constructs a <CODE>Rectangle</CODE> -object starting from the origin
171 * (0, 0).
172 *
173 * @param urx upper right x
174 * @param ury upper right y
175 */
176 public Rectangle(float urx, float ury) {
177 this(0, 0, urx, ury);
178 }
179
180 /**
181 * Constructs a <CODE>Rectangle</CODE> -object.
182 *
183 * @param rect another <CODE>Rectangle</CODE>
184 */
185 public Rectangle(Rectangle rect) {
186 this(rect.llx, rect.lly, rect.urx, rect.ury);
187 cloneNonPositionParameters(rect);
188 }
189
190 // IMPLEMENTATION OF THE ELEMENT INTERFACE:e
191
192 /**
193 * Processes the element by adding it (or the different parts) to an
194 * <CODE>ElementListener</CODE>.
195 *
196 * @param listener an <CODE>ElementListener</CODE>
197 * @return <CODE>true</CODE> if the element was processed successfully
198 */
199 public boolean process(ElementListener listener) {
200 try {
201 return listener.add(this);
202 }
203 catch (DocumentException de) {
204 return false;
205 }
206 }
207
208 /**
209 * Gets the type of the text element.
210 *
211 * @return a type
212 */
213 public int type() {
214 return Element.RECTANGLE;
215 }
216
217 /**
218 * Gets all the chunks in this element.
219 *
220 * @return an <CODE>ArrayList</CODE>
221 */
222 public ArrayList getChunks() {
223 return new ArrayList();
224 }
225
226 /**
227 * @see com.lowagie.text.Element#isContent()
228 * @since iText 2.0.8
229 */
230 public boolean isContent() {
231 return true;
232 }
233
234 /**
235 * @see com.lowagie.text.Element#isNestable()
236 * @since iText 2.0.8
237 */
238 public boolean isNestable() {
239 return false;
240 }
241
242 // METHODS TO GET/SET THE DIMENSIONS:
243
244 /**
245 * Sets the lower left x-coordinate.
246 *
247 * @param llx the new value
248 */
249 public void setLeft(float llx) {
250 this.llx = llx;
251 }
252
253 /**
254 * Returns the lower left x-coordinate.
255 *
256 * @return the lower left x-coordinate
257 */
258 public float getLeft() {
259 return llx;
260 }
261
262 /**
263 * Returns the lower left x-coordinate, considering a given margin.
264 *
265 * @param margin a margin
266 * @return the lower left x-coordinate
267 */
268 public float getLeft(float margin) {
269 return llx + margin;
270 }
271
272 /**
273 * Sets the upper right x-coordinate.
274 *
275 * @param urx the new value
276 */
277 public void setRight(float urx) {
278 this.urx = urx;
279 }
280
281 /**
282 * Returns the upper right x-coordinate.
283 *
284 * @return the upper right x-coordinate
285 */
286 public float getRight() {
287 return urx;
288 }
289
290 /**
291 * Returns the upper right x-coordinate, considering a given margin.
292 *
293 * @param margin a margin
294 * @return the upper right x-coordinate
295 */
296 public float getRight(float margin) {
297 return urx - margin;
298 }
299
300 /**
301 * Returns the width of the rectangle.
302 *
303 * @return the width
304 */
305 public float getWidth() {
306 return urx - llx;
307 }
308
309 /**
310 * Sets the upper right y-coordinate.
311 *
312 * @param ury the new value
313 */
314 public void setTop(float ury) {
315 this.ury = ury;
316 }
317
318 /**
319 * Returns the upper right y-coordinate.
320 *
321 * @return the upper right y-coordinate
322 */
323 public float getTop() {
324 return ury;
325 }
326
327 /**
328 * Returns the upper right y-coordinate, considering a given margin.
329 *
330 * @param margin a margin
331 * @return the upper right y-coordinate
332 */
333 public float getTop(float margin) {
334 return ury - margin;
335 }
336
337 /**
338 * Sets the lower left y-coordinate.
339 *
340 * @param lly the new value
341 */
342 public void setBottom(float lly) {
343 this.lly = lly;
344 }
345
346 /**
347 * Returns the lower left y-coordinate.
348 *
349 * @return the lower left y-coordinate
350 */
351 public float getBottom() {
352 return lly;
353 }
354
355 /**
356 * Returns the lower left y-coordinate, considering a given margin.
357 *
358 * @param margin a margin
359 * @return the lower left y-coordinate
360 */
361 public float getBottom(float margin) {
362 return lly + margin;
363 }
364
365 /**
366 * Returns the height of the rectangle.
367 *
368 * @return the height
369 */
370 public float getHeight() {
371 return ury - lly;
372 }
373
374 /**
375 * Normalizes the rectangle.
376 * Switches lower left with upper right if necessary.
377 */
378 public void normalize() {
379 if (llx > urx) {
380 float a = llx;
381 llx = urx;
382 urx = a;
383 }
384 if (lly > ury) {
385 float a = lly;
386 lly = ury;
387 ury = a;
388 }
389 }
390
391 // METHODS TO GET/SET THE ROTATION:
392
393 /**
394 * Gets the rotation of the rectangle
395 *
396 * @return a rotation value
397 */
398 public int getRotation() {
399 return rotation;
400 }
401
402 /**
403 * Rotates the rectangle.
404 * Swaps the values of llx and lly and of urx and ury.
405 *
406 * @return the rotated <CODE>Rectangle</CODE>
407 */
408 public Rectangle rotate() {
409 Rectangle rect = new Rectangle(lly, llx, ury, urx);
410 rect.rotation = rotation + 90;
411 rect.rotation %= 360;
412 return rect;
413 }
414
415 // METHODS TO GET/SET THE BACKGROUND COLOR:
416
417 /**
418 * Gets the backgroundcolor.
419 *
420 * @return a <CODE>Color</CODE>
421 */
422 public Color getBackgroundColor() {
423 return backgroundColor;
424 }
425
426 /**
427 * Sets the backgroundcolor of the rectangle.
428 *
429 * @param backgroundColor a <CODE>Color</CODE>
430 */
431
432 public void setBackgroundColor(Color backgroundColor) {
433 this.backgroundColor = backgroundColor;
434 }
435
436 /**
437 * Gets the grayscale.
438 *
439 * @return the grayscale color of the background
440 * or 0 if the background has no grayscale color.
441 */
442 public float getGrayFill() {
443 if (backgroundColor instanceof GrayColor)
444 return ((GrayColor)backgroundColor).getGray();
445 return 0;
446 }
447
448 /**
449 * Sets the the background color to a grayscale value.
450 *
451 * @param value the new grayscale value
452 */
453 public void setGrayFill(float value) {
454 backgroundColor = new GrayColor(value);
455 }
456
457 // METHODS TO GET/SET THE BORDER:
458
459 /**
460 * Returns the exact type of the border.
461 *
462 * @return a value
463 */
464 public int getBorder() {
465 return border;
466 }
467
468 /**
469 * Indicates whether some type of border is set.
470 *
471 * @return a boolean
472 */
473 public boolean hasBorders() {
474 switch (border) {
475 case UNDEFINED:
476 case NO_BORDER:
477 return false;
478 default:
479 return borderWidth > 0 || borderWidthLeft > 0
480 || borderWidthRight > 0 || borderWidthTop > 0 || borderWidthBottom > 0;
481 }
482 }
483
484 /**
485 * Indicates whether the specified type of border is set.
486 *
487 * @param type the type of border
488 * @return a boolean
489 */
490 public boolean hasBorder(int type) {
491 if (border == UNDEFINED)
492 return false;
493 return (border & type) == type;
494 }
495
496 /**
497 * Enables/Disables the border on the specified sides.
498 * The border is specified as an integer bitwise combination of
499 * the constants: <CODE>LEFT, RIGHT, TOP, BOTTOM</CODE>.
500 *
501 * @see #enableBorderSide(int)
502 * @see #disableBorderSide(int)
503 * @param border the new value
504 */
505 public void setBorder(int border) {
506 this.border = border;
507 }
508
509 /**
510 * Indicates whether variable width borders are being used.
511 * Returns true if <CODE>setBorderWidthLeft, setBorderWidthRight,
512 * setBorderWidthTop, or setBorderWidthBottom</CODE> has been called.
513 *
514 * @return true if variable width borders are in use
515 */
516 public boolean isUseVariableBorders() {
517 return useVariableBorders;
518 }
519
520 /**
521 * Sets a parameter indicating if the rectangle has variable borders
522 *
523 * @param useVariableBorders indication if the rectangle has variable borders
524 */
525 public void setUseVariableBorders(boolean useVariableBorders) {
526 this.useVariableBorders = useVariableBorders;
527 }
528
529 /**
530 * Enables the border on the specified side.
531 *
532 * @param side the side to enable.
533 * One of <CODE>LEFT, RIGHT, TOP, BOTTOM</CODE>
534 */
535 public void enableBorderSide(int side) {
536 if (border == UNDEFINED)
537 border = 0;
538 border |= side;
539 }
540
541 /**
542 * Disables the border on the specified side.
543 *
544 * @param side the side to disable.
545 * One of <CODE>LEFT, RIGHT, TOP, BOTTOM</CODE>
546 */
547 public void disableBorderSide(int side) {
548 if (border == UNDEFINED)
549 border = 0;
550 border &= ~side;
551 }
552
553 // METHODS TO GET/SET THE BORDER WIDTH:
554
555 /**
556 * Gets the borderwidth.
557 *
558 * @return a value
559 */
560 public float getBorderWidth() {
561 return borderWidth;
562 }
563
564 /**
565 * Sets the borderwidth of the table.
566 *
567 * @param borderWidth the new value
568 */
569 public void setBorderWidth(float borderWidth) {
570 this.borderWidth = borderWidth;
571 }
572
573 /**
574 * Helper function returning the border width of a specific side.
575 *
576 * @param variableWidthValue a variable width (could be undefined)
577 * @param side the border you want to check
578 * @return the variableWidthValue if not undefined, otherwise the borderWidth
579 */
580 private float getVariableBorderWidth(float variableWidthValue, int side) {
581 if ((border & side) != 0)
582 return variableWidthValue != UNDEFINED ? variableWidthValue : borderWidth;
583 return 0;
584 }
585
586 /**
587 * Helper function updating the border flag for a side
588 * based on the specified width.
589 * A width of 0 will disable the border on that side.
590 * Any other width enables it.
591 *
592 * @param width width of border
593 * @param side border side constant
594 */
595 private void updateBorderBasedOnWidth(float width, int side) {
596 useVariableBorders = true;
597 if (width > 0)
598 enableBorderSide(side);
599 else
600 disableBorderSide(side);
601 }
602
603 /**
604 * Gets the width of the left border.
605 *
606 * @return a width
607 */
608 public float getBorderWidthLeft() {
609 return getVariableBorderWidth(borderWidthLeft, LEFT);
610 }
611
612 /**
613 * Sets the width of the left border.
614 *
615 * @param borderWidthLeft a width
616 */
617 public void setBorderWidthLeft(float borderWidthLeft) {
618 this.borderWidthLeft = borderWidthLeft;
619 updateBorderBasedOnWidth(borderWidthLeft, LEFT);
620 }
621
622 /**
623 * Gets the width of the right border.
624 *
625 * @return a width
626 */
627 public float getBorderWidthRight() {
628 return getVariableBorderWidth(borderWidthRight, RIGHT);
629 }
630
631 /**
632 * Sets the width of the right border.
633 *
634 * @param borderWidthRight a width
635 */
636 public void setBorderWidthRight(float borderWidthRight) {
637 this.borderWidthRight = borderWidthRight;
638 updateBorderBasedOnWidth(borderWidthRight, RIGHT);
639 }
640
641 /**
642 * Gets the width of the top border.
643 *
644 * @return a width
645 */
646 public float getBorderWidthTop() {
647 return getVariableBorderWidth(borderWidthTop, TOP);
648 }
649
650 /**
651 * Sets the width of the top border.
652 *
653 * @param borderWidthTop a width
654 */
655 public void setBorderWidthTop(float borderWidthTop) {
656 this.borderWidthTop = borderWidthTop;
657 updateBorderBasedOnWidth(borderWidthTop, TOP);
658 }
659
660 /**
661 * Gets the width of the bottom border.
662 *
663 * @return a width
664 */
665 public float getBorderWidthBottom() {
666 return getVariableBorderWidth(borderWidthBottom, BOTTOM);
667 }
668
669 /**
670 * Sets the width of the bottom border.
671 *
672 * @param borderWidthBottom a width
673 */
674 public void setBorderWidthBottom(float borderWidthBottom) {
675 this.borderWidthBottom = borderWidthBottom;
676 updateBorderBasedOnWidth(borderWidthBottom, BOTTOM);
677 }
678
679 // METHODS TO GET/SET THE BORDER COLOR:
680
681 /**
682 * Gets the color of the border.
683 *
684 * @return a <CODE>Color</CODE>
685 */
686 public Color getBorderColor() {
687 return borderColor;
688 }
689
690 /**
691 * Sets the color of the border.
692 *
693 * @param borderColor a <CODE>Color</CODE>
694 */
695 public void setBorderColor(Color borderColor) {
696 this.borderColor = borderColor;
697 }
698
699 /**
700 * Gets the color of the left border.
701 *
702 * @return a <CODE>Color</CODE>
703 */
704 public Color getBorderColorLeft() {
705 if (borderColorLeft == null)
706 return borderColor;
707 return borderColorLeft;
708 }
709
710 /**
711 * Sets the color of the left border.
712 *
713 * @param borderColorLeft a <CODE>Color</CODE>
714 */
715 public void setBorderColorLeft(Color borderColorLeft) {
716 this.borderColorLeft = borderColorLeft;
717 }
718
719 /**
720 * Gets the color of the right border.
721 *
722 * @return a <CODE>Color</CODE>
723 */
724 public Color getBorderColorRight() {
725 if (borderColorRight == null)
726 return borderColor;
727 return borderColorRight;
728 }
729
730 /**
731 * Sets the color of the right border.
732 *
733 * @param borderColorRight a <CODE>Color</CODE>
734 */
735 public void setBorderColorRight(Color borderColorRight) {
736 this.borderColorRight = borderColorRight;
737 }
738
739 /**
740 * Gets the color of the top border.
741 *
742 * @return a <CODE>Color</CODE>
743 */
744 public Color getBorderColorTop() {
745 if (borderColorTop == null)
746 return borderColor;
747 return borderColorTop;
748 }
749
750 /**
751 * Sets the color of the top border.
752 *
753 * @param borderColorTop a <CODE>Color</CODE>
754 */
755 public void setBorderColorTop(Color borderColorTop) {
756 this.borderColorTop = borderColorTop;
757 }
758
759 /**
760 * Gets the color of the bottom border.
761 *
762 * @return a <CODE>Color</CODE>
763 */
764 public Color getBorderColorBottom() {
765 if (borderColorBottom == null)
766 return borderColor;
767 return borderColorBottom;
768 }
769
770 /**
771 * Sets the color of the bottom border.
772 *
773 * @param borderColorBottom a <CODE>Color</CODE>
774 */
775 public void setBorderColorBottom(Color borderColorBottom) {
776 this.borderColorBottom = borderColorBottom;
777 }
778
779 // SPECIAL METHODS:
780
781 /**
782 * Gets a Rectangle that is altered to fit on the page.
783 *
784 * @param top the top position
785 * @param bottom the bottom position
786 * @return a <CODE>Rectangle</CODE>
787 */
788 public Rectangle rectangle(float top, float bottom) {
789 Rectangle tmp = new Rectangle(this);
790 if (getTop() > top) {
791 tmp.setTop(top);
792 tmp.disableBorderSide(TOP);
793 }
794 if (getBottom() < bottom) {
795 tmp.setBottom(bottom);
796 tmp.disableBorderSide(BOTTOM);
797 }
798 return tmp;
799 }
800
801 /**
802 * Copies each of the parameters, except the position, from a
803 * <CODE>Rectangle</CODE> object
804 *
805 * @param rect <CODE>Rectangle</CODE> to copy from
806 */
807 public void cloneNonPositionParameters(Rectangle rect) {
808 this.rotation = rect.rotation;
809 this.backgroundColor = rect.backgroundColor;
810 this.border = rect.border;
811 this.useVariableBorders = rect.useVariableBorders;
812 this.borderWidth = rect.borderWidth;
813 this.borderWidthLeft = rect.borderWidthLeft;
814 this.borderWidthRight = rect.borderWidthRight;
815 this.borderWidthTop = rect.borderWidthTop;
816 this.borderWidthBottom = rect.borderWidthBottom;
817 this.borderColor = rect.borderColor;
818 this.borderColorLeft = rect.borderColorLeft;
819 this.borderColorRight = rect.borderColorRight;
820 this.borderColorTop = rect.borderColorTop;
821 this.borderColorBottom = rect.borderColorBottom;
822 }
823
824 /**
825 * Copies each of the parameters, except the position, from a
826 * <CODE>Rectangle</CODE> object if the value is set there
827 *
828 * @param rect <CODE>Rectangle</CODE> to copy from
829 */
830 public void softCloneNonPositionParameters(Rectangle rect) {
831 if (rect.rotation != 0)
832 this.rotation = rect.rotation;
833 if (rect.backgroundColor != null)
834 this.backgroundColor = rect.backgroundColor;
835 if (rect.border != UNDEFINED)
836 this.border = rect.border;
837 if (useVariableBorders)
838 this.useVariableBorders = rect.useVariableBorders;
839 if (rect.borderWidth != UNDEFINED)
840 this.borderWidth = rect.borderWidth;
841 if (rect.borderWidthLeft != UNDEFINED)
842 this.borderWidthLeft = rect.borderWidthLeft;
843 if (rect.borderWidthRight != UNDEFINED)
844 this.borderWidthRight = rect.borderWidthRight;
845 if (rect.borderWidthTop != UNDEFINED)
846 this.borderWidthTop = rect.borderWidthTop;
847 if (rect.borderWidthBottom != UNDEFINED)
848 this.borderWidthBottom = rect.borderWidthBottom;
849 if (rect.borderColor != null)
850 this.borderColor = rect.borderColor;
851 if (rect.borderColorLeft != null)
852 this.borderColorLeft = rect.borderColorLeft;
853 if (rect.borderColorRight != null)
854 this.borderColorRight = rect.borderColorRight;
855 if (rect.borderColorTop != null)
856 this.borderColorTop = rect.borderColorTop;
857 if (rect.borderColorBottom != null)
858 this.borderColorBottom = rect.borderColorBottom;
859 }
860
861 /**
862 * @return a String representation of the rectangle
863 * @see java.lang.Object#toString()
864 */
865 public String toString() {
866 StringBuffer buf = new StringBuffer("Rectangle: ");
867 buf.append(getWidth());
868 buf.append('x');
869 buf.append(getHeight());
870 buf.append(" (rot: ");
871 buf.append(rotation);
872 buf.append(" degrees)");
873 return buf.toString();
874 }
875
876 }