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 = (Paint) foreground; 161 bgPaint = (Paint) 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 }