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 {
  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 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: ").append(fgPaint);
 435             if (bgPaint != null) sb.append(" bgPaint: ").append(bgPaint);
 436             if (swapColors) sb.append(" swapColors: true");
 437             if (strikethrough) sb.append(" strikethrough: true");
 438             if (stdUnderline != null) sb.append(" stdUnderline: ").append(stdUnderline);
 439             if (imUnderline != null) sb.append(" imUnderline: ").append(imUnderline);
 440             sb.append("]");
 441             return sb.toString();
 442         }
 443     }
 444 }