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 }