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
31package sun.font;
32
33import java.util.Map;
34
35import java.awt.BasicStroke;
36import java.awt.Color;
37import java.awt.Graphics2D;
38import java.awt.Paint;
39import java.awt.RenderingHints;
40import java.awt.Shape;
41import java.awt.Stroke;
42
43import java.awt.font.TextAttribute;
44
45import java.awt.geom.Area;
46import java.awt.geom.Line2D;
47import java.awt.geom.Rectangle2D;
48import java.awt.geom.GeneralPath;
49import java.text.AttributedCharacterIterator.Attribute;
50
51import static sun.font.AttributeValues.*;
52import 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 */
59public 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 {
67        CoreMetrics getCoreMetrics();
68        Rectangle2D getLogicalBounds();
69
70        void handleDraw(Graphics2D g2d, float x, float y);
71        Rectangle2D handleGetCharVisualBounds(int index);
72        Rectangle2D handleGetVisualBounds();
73        Shape handleGetOutline(float x, float y);
74    }
75
76    private Decoration() {
77    }
78
79    /**
80     * Return a Decoration which does nothing.
81     */
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
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     */
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
118    public void drawTextAndDecorations(Label label,
119                                Graphics2D g2d,
120                                float x,
121                                float y) {
122
123        label.handleDraw(g2d, x, y);
124    }
125
126    public Rectangle2D getVisualBounds(Label label) {
127
128        return label.handleGetVisualBounds();
129    }
130
131    public Rectangle2D getCharVisualBounds(Label label, int index) {
132
133        return label.handleGetCharVisualBounds(index);
134    }
135
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
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
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
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
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        */
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
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
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
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
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
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