1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3  *
4  * This code is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License version 2 only, as
6  * published by the Free Software Foundation.  Oracle designates this
7  * particular file as subject to the "Classpath" exception as provided
8  * by Oracle in the LICENSE file that accompanied this code.
9  *
10  * This code is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13  * version 2 for more details (a copy is included in the LICENSE file that
14  * accompanied this code).
15  *
16  * You should have received a copy of the GNU General Public License version
17  * 2 along with this work; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19  *
20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21  * or visit www.oracle.com if you need additional information or have any
22  * questions.
23  *
24  */
25 
26 /*
27  * (C) Copyright IBM Corp. 1999-2003, All Rights Reserved
28  *
29  */
30 
31 package sun.font;
32 
33 import java.util.Map;
34 
35 import java.awt.BasicStroke;
36 import java.awt.Color;
37 import java.awt.Graphics2D;
38 import java.awt.Paint;
39 import java.awt.RenderingHints;
40 import java.awt.Shape;
41 import java.awt.Stroke;
42 
43 import java.awt.font.TextAttribute;
44 
45 import java.awt.geom.Area;
46 import java.awt.geom.Line2D;
47 import java.awt.geom.Rectangle2D;
48 import java.awt.geom.GeneralPath;
49 import java.text.AttributedCharacterIterator.Attribute;
50 
51 import static sun.font.AttributeValues.*;
52 import static sun.font.EAttribute.*;
53 
54 /**
55  * This class handles underlining, strikethrough, and foreground and
56  * background styles on text.  Clients simply acquire instances
57  * of this class and hand them off to ExtendedTextLabels or GraphicComponents.
58  */
59 public class Decoration {
60 
61     /**
62      * This interface is implemented by clients that use Decoration.
63      * Unfortunately, interface methods have to public;  ideally these
64      * would be package-private.
65      */
66     public interface Label {
getCoreMetrics()67         CoreMetrics getCoreMetrics();
getLogicalBounds()68         Rectangle2D getLogicalBounds();
69 
handleDraw(Graphics2D g2d, float x, float y)70         void handleDraw(Graphics2D g2d, float x, float y);
handleGetCharVisualBounds(int index)71         Rectangle2D handleGetCharVisualBounds(int index);
handleGetVisualBounds()72         Rectangle2D handleGetVisualBounds();
handleGetOutline(float x, float y)73         Shape handleGetOutline(float x, float y);
74     }
75 
Decoration()76     private Decoration() {
77     }
78 
79     /**
80      * Return a Decoration which does nothing.
81      */
getPlainDecoration()82     public static Decoration getPlainDecoration() {
83 
84         return PLAIN;
85     }
86 
87     private static final int VALUES_MASK =
88         AttributeValues.getMask(EFOREGROUND, EBACKGROUND, ESWAP_COLORS,
89                                 ESTRIKETHROUGH, EUNDERLINE, EINPUT_METHOD_HIGHLIGHT,
90                                 EINPUT_METHOD_UNDERLINE);
91 
getDecoration(AttributeValues values)92     public static Decoration getDecoration(AttributeValues values) {
93         if (values == null || !values.anyDefined(VALUES_MASK)) {
94             return PLAIN;
95         }
96 
97         values = values.applyIMHighlight();
98 
99         return new DecorationImpl(values.getForeground(),
100                                   values.getBackground(),
101                                   values.getSwapColors(),
102                                   values.getStrikethrough(),
103                                   Underline.getUnderline(values.getUnderline()),
104                                   Underline.getUnderline(values.getInputMethodUnderline()));
105     }
106 
107     /**
108      * Return a Decoration appropriate for the given Map.
109      * @param attributes the Map used to determine the Decoration
110      */
getDecoration(Map<? extends Attribute, ?> attributes)111     public static Decoration getDecoration(Map<? extends Attribute, ?> attributes) {
112         if (attributes == null) {
113             return PLAIN;
114         }
115         return getDecoration(AttributeValues.fromMap(attributes));
116     }
117 
drawTextAndDecorations(Label label, Graphics2D g2d, float x, float y)118     public void drawTextAndDecorations(Label label,
119                                 Graphics2D g2d,
120                                 float x,
121                                 float y) {
122 
123         label.handleDraw(g2d, x, y);
124     }
125 
getVisualBounds(Label label)126     public Rectangle2D getVisualBounds(Label label) {
127 
128         return label.handleGetVisualBounds();
129     }
130 
getCharVisualBounds(Label label, int index)131     public Rectangle2D getCharVisualBounds(Label label, int index) {
132 
133         return label.handleGetCharVisualBounds(index);
134     }
135 
getOutline(Label label, float x, float y)136     Shape getOutline(Label label,
137                      float x,
138                      float y) {
139 
140         return label.handleGetOutline(x, y);
141     }
142 
143     private static final Decoration PLAIN = new Decoration();
144 
145     private static final class DecorationImpl extends Decoration {
146 
147         private Paint fgPaint = null;
148         private Paint bgPaint = null;
149         private boolean swapColors = false;
150         private boolean strikethrough = false;
151         private Underline stdUnderline = null; // underline from TextAttribute.UNDERLINE_ON
152         private Underline imUnderline = null; // input method underline
153 
DecorationImpl(Paint foreground, Paint background, boolean swapColors, boolean strikethrough, Underline stdUnderline, Underline imUnderline)154         DecorationImpl(Paint foreground,
155                        Paint background,
156                        boolean swapColors,
157                        boolean strikethrough,
158                        Underline stdUnderline,
159                        Underline imUnderline) {
160 
161             fgPaint = foreground;
162             bgPaint = background;
163 
164             this.swapColors = swapColors;
165             this.strikethrough = strikethrough;
166 
167             this.stdUnderline = stdUnderline;
168             this.imUnderline = imUnderline;
169         }
170 
areEqual(Object lhs, Object rhs)171         private static boolean areEqual(Object lhs, Object rhs) {
172 
173             if (lhs == null) {
174                 return rhs == null;
175             }
176             else {
177                 return lhs.equals(rhs);
178             }
179         }
180 
equals(Object rhs)181         public boolean equals(Object rhs) {
182 
183             if (rhs == this) {
184                 return true;
185             }
186             if (rhs == null) {
187                 return false;
188             }
189 
190             DecorationImpl other = null;
191             try {
192                 other = (DecorationImpl) rhs;
193             }
194             catch(ClassCastException e) {
195                 return false;
196             }
197 
198             if (!(swapColors == other.swapColors &&
199                         strikethrough == other.strikethrough)) {
200                 return false;
201             }
202 
203             if (!areEqual(stdUnderline, other.stdUnderline)) {
204                 return false;
205             }
206             if (!areEqual(fgPaint, other.fgPaint)) {
207                 return false;
208             }
209             if (!areEqual(bgPaint, other.bgPaint)) {
210                 return false;
211             }
212             return areEqual(imUnderline, other.imUnderline);
213         }
214 
hashCode()215         public int hashCode() {
216 
217             int hc = 1;
218             if (strikethrough) {
219                 hc |= 2;
220             }
221             if (swapColors) {
222                 hc |= 4;
223             }
224             if (stdUnderline != null) {
225                 hc += stdUnderline.hashCode();
226             }
227             return hc;
228         }
229 
230         /**
231         * Return the bottom of the Rectangle which encloses pixels
232         * drawn by underlines.
233         */
getUnderlineMaxY(CoreMetrics cm)234         private float getUnderlineMaxY(CoreMetrics cm) {
235 
236             float maxY = 0;
237             if (stdUnderline != null) {
238 
239                 float ulBottom = cm.underlineOffset;
240                 ulBottom += stdUnderline.getLowerDrawLimit(cm.underlineThickness);
241                 maxY = Math.max(maxY, ulBottom);
242             }
243 
244             if (imUnderline != null) {
245 
246                 float ulBottom = cm.underlineOffset;
247                 ulBottom += imUnderline.getLowerDrawLimit(cm.underlineThickness);
248                 maxY = Math.max(maxY, ulBottom);
249             }
250 
251             return maxY;
252         }
253 
drawTextAndEmbellishments(Label label, Graphics2D g2d, float x, float y)254         private void drawTextAndEmbellishments(Label label,
255                                                Graphics2D g2d,
256                                                float x,
257                                                float y) {
258 
259             label.handleDraw(g2d, x, y);
260 
261             if (!strikethrough && stdUnderline == null && imUnderline == null) {
262                 return;
263             }
264 
265             float x1 = x;
266             float x2 = x1 + (float)label.getLogicalBounds().getWidth();
267 
268             CoreMetrics cm = label.getCoreMetrics();
269             if (strikethrough) {
270                 Stroke savedStroke = g2d.getStroke();
271                 g2d.setStroke(new BasicStroke(cm.strikethroughThickness,
272                                               BasicStroke.CAP_BUTT,
273                                               BasicStroke.JOIN_MITER));
274                 float strikeY = y + cm.strikethroughOffset;
275                 g2d.draw(new Line2D.Float(x1, strikeY, x2, strikeY));
276                 g2d.setStroke(savedStroke);
277             }
278 
279             float ulOffset = cm.underlineOffset;
280             float ulThickness = cm.underlineThickness;
281 
282             if (stdUnderline != null) {
283                 stdUnderline.drawUnderline(g2d, ulThickness, x1, x2, y + ulOffset);
284             }
285 
286             if (imUnderline != null) {
287                 imUnderline.drawUnderline(g2d, ulThickness, x1, x2, y + ulOffset);
288             }
289         }
290 
drawTextAndDecorations(Label label, Graphics2D g2d, float x, float y)291         public void drawTextAndDecorations(Label label,
292                                     Graphics2D g2d,
293                                     float x,
294                                     float y) {
295 
296             if (fgPaint == null && bgPaint == null && swapColors == false) {
297                 drawTextAndEmbellishments(label, g2d, x, y);
298             }
299             else {
300                 Paint savedPaint = g2d.getPaint();
301                 Paint foreground, background;
302 
303                 if (swapColors) {
304                     background = fgPaint==null? savedPaint : fgPaint;
305                     if (bgPaint == null) {
306                         if (background instanceof Color) {
307                             Color bg = (Color)background;
308                             // 30/59/11 is standard weights, tweaked a bit
309                             int brightness = 33 * bg.getRed()
310                                 + 53 * bg.getGreen()
311                                 + 14 * bg.getBlue();
312                             foreground = brightness > 18500 ? Color.BLACK : Color.WHITE;
313                         } else {
314                             foreground = Color.WHITE;
315                         }
316                     } else {
317                         foreground = bgPaint;
318                     }
319                 }
320                 else {
321                     foreground = fgPaint==null? savedPaint : fgPaint;
322                     background = bgPaint;
323                 }
324 
325                 if (background != null) {
326 
327                     Rectangle2D bgArea = label.getLogicalBounds();
328                     bgArea = new Rectangle2D.Float(x + (float)bgArea.getX(),
329                                                 y + (float)bgArea.getY(),
330                                                 (float)bgArea.getWidth(),
331                                                 (float)bgArea.getHeight());
332 
333                     g2d.setPaint(background);
334                     g2d.fill(bgArea);
335                 }
336 
337                 g2d.setPaint(foreground);
338                 drawTextAndEmbellishments(label, g2d, x, y);
339                 g2d.setPaint(savedPaint);
340             }
341         }
342 
getVisualBounds(Label label)343         public Rectangle2D getVisualBounds(Label label) {
344 
345             Rectangle2D visBounds = label.handleGetVisualBounds();
346 
347             if (swapColors || bgPaint != null || strikethrough
348                         || stdUnderline != null || imUnderline != null) {
349 
350                 float minX = 0;
351                 Rectangle2D lb = label.getLogicalBounds();
352 
353                 float minY = 0, maxY = 0;
354 
355                 if (swapColors || bgPaint != null) {
356 
357                     minY = (float)lb.getY();
358                     maxY = minY + (float)lb.getHeight();
359                 }
360 
361                 maxY = Math.max(maxY, getUnderlineMaxY(label.getCoreMetrics()));
362 
363                 Rectangle2D ab = new Rectangle2D.Float(minX, minY, (float)lb.getWidth(), maxY-minY);
364                 visBounds.add(ab);
365             }
366 
367             return visBounds;
368         }
369 
getOutline(Label label, float x, float y)370         Shape getOutline(Label label,
371                          float x,
372                          float y) {
373 
374             if (!strikethrough && stdUnderline == null && imUnderline == null) {
375                 return label.handleGetOutline(x, y);
376             }
377 
378             CoreMetrics cm = label.getCoreMetrics();
379 
380             // NOTE:  The performace of the following code may
381             // be very poor.
382             float ulThickness = cm.underlineThickness;
383             float ulOffset = cm.underlineOffset;
384 
385             Rectangle2D lb = label.getLogicalBounds();
386             float x1 = x;
387             float x2 = x1 + (float)lb.getWidth();
388 
389             Area area = null;
390 
391             if (stdUnderline != null) {
392                 Shape ul = stdUnderline.getUnderlineShape(ulThickness,
393                                                           x1, x2, y+ulOffset);
394                 area = new Area(ul);
395             }
396 
397             if (strikethrough) {
398                 Stroke stStroke = new BasicStroke(cm.strikethroughThickness,
399                                                   BasicStroke.CAP_BUTT,
400                                                   BasicStroke.JOIN_MITER);
401                 float shiftY = y + cm.strikethroughOffset;
402                 Line2D line = new Line2D.Float(x1, shiftY, x2, shiftY);
403                 Area slArea = new Area(stStroke.createStrokedShape(line));
404                 if(area == null) {
405                     area = slArea;
406                 } else {
407                     area.add(slArea);
408                 }
409             }
410 
411             if (imUnderline != null) {
412                 Shape ul = imUnderline.getUnderlineShape(ulThickness,
413                                                          x1, x2, y+ulOffset);
414                 Area ulArea = new Area(ul);
415                 if (area == null) {
416                     area = ulArea;
417                 }
418                 else {
419                     area.add(ulArea);
420                 }
421             }
422 
423             // area won't be null here, since at least one underline exists.
424             area.add(new Area(label.handleGetOutline(x, y)));
425 
426             return new GeneralPath(area);
427         }
428 
429 
toString()430         public String toString() {
431             StringBuilder sb = new StringBuilder();
432             sb.append(super.toString());
433             sb.append("[");
434             if (fgPaint != null) sb.append("fgPaint: " + fgPaint);
435             if (bgPaint != null) sb.append(" bgPaint: " + bgPaint);
436             if (swapColors) sb.append(" swapColors: true");
437             if (strikethrough) sb.append(" strikethrough: true");
438             if (stdUnderline != null) sb.append(" stdUnderline: " + stdUnderline);
439             if (imUnderline != null) sb.append(" imUnderline: " + imUnderline);
440             sb.append("]");
441             return sb.toString();
442         }
443     }
444 }
445