Decoration.java revision 11099:678faa7d1a6a
11558Srgrimes/* 250476Speter * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 31558Srgrimes * 412481Speter * This code is free software; you can redistribute it and/or modify it 51558Srgrimes * under the terms of the GNU General Public License version 2 only, as 641061Sbde * published by the Free Software Foundation. Oracle designates this 774448Ssos * particular file as subject to the "Classpath" exception as provided 841061Sbde * by Oracle in the LICENSE file that accompanied this code. 939271Sphk * 1039255Sgibbs * This code is distributed in the hope that it will be useful, but WITHOUT 1138653Sgpalmer * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1238653Sgpalmer * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1355980Speter * version 2 for more details (a copy is included in the LICENSE file that 1443859Sobrien * accompanied this code). 1538653Sgpalmer * 1638653Sgpalmer * You should have received a copy of the GNU General Public License version 1738653Sgpalmer * 2 along with this work; if not, write to the Free Software Foundation, 1838653Sgpalmer * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1938653Sgpalmer * 2069800Stomsoft * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2138653Sgpalmer * or visit www.oracle.com if you need additional information or have any 2267105Sadrian * questions. 2366867Sadrian * 2438653Sgpalmer */ 2538653Sgpalmer 2669800Stomsoft/* 2738653Sgpalmer * (C) Copyright IBM Corp. 1999-2003, All Rights Reserved 2838653Sgpalmer * 2956815Sshin */ 3053644Sguido 3154225Sguidopackage sun.font; 3238653Sgpalmer 3354225Sguidoimport java.util.Map; 3454225Sguido 3538653Sgpalmerimport java.awt.BasicStroke; 3638653Sgpalmerimport java.awt.Color; 3738653Sgpalmerimport java.awt.Graphics2D; 3838843Sjbimport java.awt.Paint; 3938653Sgpalmerimport java.awt.RenderingHints; 4070450Sphkimport java.awt.Shape; 4138653Sgpalmerimport java.awt.Stroke; 4238653Sgpalmer 4338653Sgpalmerimport java.awt.font.TextAttribute; 4438653Sgpalmer 4567105Sadrianimport java.awt.geom.Area; 4677577Sruimport java.awt.geom.Line2D; 4738653Sgpalmerimport java.awt.geom.Rectangle2D; 4843557Ssemenuimport java.awt.geom.GeneralPath; 4977042Sruimport java.text.AttributedCharacterIterator.Attribute; 5077042Sru 5138653Sgpalmerimport static sun.font.AttributeValues.*; 5277042Sruimport static sun.font.EAttribute.*; 5377042Sru 5438653Sgpalmer/** 5544690Sbrian * This class handles underlining, strikethrough, and foreground and 5638653Sgpalmer * background styles on text. Clients simply acquire instances 5738653Sgpalmer * of this class and hand them off to ExtendedTextLabels or GraphicComponents. 5838653Sgpalmer */ 5938653Sgpalmerpublic class Decoration { 6038653Sgpalmer 6138653Sgpalmer /** 6238653Sgpalmer * This interface is implemented by clients that use Decoration. 6355163Sshin * Unfortunately, interface methods have to public; ideally these 6438653Sgpalmer * would be package-private. 6538653Sgpalmer */ 6638653Sgpalmer public interface Label { 6738653Sgpalmer CoreMetrics getCoreMetrics(); 6838653Sgpalmer Rectangle2D getLogicalBounds(); 6955163Sshin 7038653Sgpalmer void handleDraw(Graphics2D g2d, float x, float y); 7138653Sgpalmer Rectangle2D handleGetCharVisualBounds(int index); 7238653Sgpalmer Rectangle2D handleGetVisualBounds(); 7341061Sbde Shape handleGetOutline(float x, float y); 7438653Sgpalmer } 7546878Sobrien 7638653Sgpalmer private Decoration() { 7738653Sgpalmer } 7842117Ssos 7942117Ssos /** 8010855Sjoerg * Return a Decoration which does nothing. 8144317Sjkh */ 8252252Sbp public static Decoration getPlainDecoration() { 8344317Sjkh 8444317Sjkh return PLAIN; 8558235Skato } 8658235Skato 8738458Sjb private static final int VALUES_MASK = 8838458Sjb AttributeValues.getMask(EFOREGROUND, EBACKGROUND, ESWAP_COLORS, 891558Srgrimes 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 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: " + 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