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