Decoration.java revision 11099:678faa7d1a6a
11558Srgrimes/*
250476Speter * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
31558Srgrimes *
412481Speter * This code is free software; you can redistribute it and/or modify it
51558Srgrimes * under the terms of the GNU General Public License version 2 only, as
641061Sbde * published by the Free Software Foundation.  Oracle designates this
774448Ssos * particular file as subject to the "Classpath" exception as provided
841061Sbde * by Oracle in the LICENSE file that accompanied this code.
939271Sphk *
1039255Sgibbs * This code is distributed in the hope that it will be useful, but WITHOUT
1138653Sgpalmer * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1238653Sgpalmer * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1355980Speter * version 2 for more details (a copy is included in the LICENSE file that
1443859Sobrien * accompanied this code).
1538653Sgpalmer *
1638653Sgpalmer * You should have received a copy of the GNU General Public License version
1738653Sgpalmer * 2 along with this work; if not, write to the Free Software Foundation,
1838653Sgpalmer * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1938653Sgpalmer *
2069800Stomsoft * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2138653Sgpalmer * or visit www.oracle.com if you need additional information or have any
2267105Sadrian * questions.
2366867Sadrian *
2438653Sgpalmer */
2538653Sgpalmer
2669800Stomsoft/*
2738653Sgpalmer * (C) Copyright IBM Corp. 1999-2003, All Rights Reserved
2838653Sgpalmer *
2956815Sshin */
3053644Sguido
3154225Sguidopackage sun.font;
3238653Sgpalmer
3354225Sguidoimport java.util.Map;
3454225Sguido
3538653Sgpalmerimport java.awt.BasicStroke;
3638653Sgpalmerimport java.awt.Color;
3738653Sgpalmerimport java.awt.Graphics2D;
3838843Sjbimport java.awt.Paint;
3938653Sgpalmerimport java.awt.RenderingHints;
4070450Sphkimport java.awt.Shape;
4138653Sgpalmerimport java.awt.Stroke;
4238653Sgpalmer
4338653Sgpalmerimport java.awt.font.TextAttribute;
4438653Sgpalmer
4567105Sadrianimport java.awt.geom.Area;
4677577Sruimport java.awt.geom.Line2D;
4738653Sgpalmerimport java.awt.geom.Rectangle2D;
4843557Ssemenuimport java.awt.geom.GeneralPath;
4977042Sruimport java.text.AttributedCharacterIterator.Attribute;
5077042Sru
5138653Sgpalmerimport static sun.font.AttributeValues.*;
5277042Sruimport static sun.font.EAttribute.*;
5377042Sru
5438653Sgpalmer/**
5544690Sbrian * This class handles underlining, strikethrough, and foreground and
5638653Sgpalmer * background styles on text.  Clients simply acquire instances
5738653Sgpalmer * of this class and hand them off to ExtendedTextLabels or GraphicComponents.
5838653Sgpalmer */
5938653Sgpalmerpublic class Decoration {
6038653Sgpalmer
6138653Sgpalmer    /**
6238653Sgpalmer     * This interface is implemented by clients that use Decoration.
6355163Sshin     * Unfortunately, interface methods have to public;  ideally these
6438653Sgpalmer     * would be package-private.
6538653Sgpalmer     */
6638653Sgpalmer    public interface Label {
6738653Sgpalmer        CoreMetrics getCoreMetrics();
6838653Sgpalmer        Rectangle2D getLogicalBounds();
6955163Sshin
7038653Sgpalmer        void handleDraw(Graphics2D g2d, float x, float y);
7138653Sgpalmer        Rectangle2D handleGetCharVisualBounds(int index);
7238653Sgpalmer        Rectangle2D handleGetVisualBounds();
7341061Sbde        Shape handleGetOutline(float x, float y);
7438653Sgpalmer    }
7546878Sobrien
7638653Sgpalmer    private Decoration() {
7738653Sgpalmer    }
7842117Ssos
7942117Ssos    /**
8010855Sjoerg     * Return a Decoration which does nothing.
8144317Sjkh     */
8252252Sbp    public static Decoration getPlainDecoration() {
8344317Sjkh
8444317Sjkh        return PLAIN;
8558235Skato    }
8658235Skato
8738458Sjb    private static final int VALUES_MASK =
8838458Sjb        AttributeValues.getMask(EFOREGROUND, EBACKGROUND, ESWAP_COLORS,
891558Srgrimes                                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