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