001 /* 002 * Copyright 2002 - 2007 JEuclid, http://jeuclid.sf.net 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 /* $Id: AttributesHelper.java,v 1e57f05a780b 2009/10/28 09:06:45 max $ */ 018 019 package net.sourceforge.jeuclid.elements.support.attributes; 020 021 import java.awt.Color; 022 import java.util.HashMap; 023 import java.util.Locale; 024 import java.util.Map; 025 import java.util.StringTokenizer; 026 027 import net.sourceforge.jeuclid.Constants; 028 import net.sourceforge.jeuclid.LayoutContext; 029 import net.sourceforge.jeuclid.elements.AbstractJEuclidElement; 030 import net.sourceforge.jeuclid.elements.support.GraphicsSupport; 031 import net.sourceforge.jeuclid.elements.support.operatordict.OperatorDictionary; 032 033 import org.apache.commons.logging.Log; 034 import org.apache.commons.logging.LogFactory; 035 036 /** 037 * Class contains utility methods for working with elements attributes. 038 * 039 * @version $Revision: 1e57f05a780b $ 040 */ 041 public final class AttributesHelper { 042 043 /** 044 * Constant for "Transparent" color. 045 */ 046 public static final String COLOR_TRANSPARENT = "transparent"; 047 048 /** 049 * Width of veryverythinmath space according to 3.3.4.2. 050 */ 051 public static final String VERYVERYTHINMATHSPACE = "0.0555556em"; 052 053 /** 054 * Width of verythinmath space according to 3.3.4.2. 055 */ 056 public static final String VERYTHINMATHSPACE = "0.111111em"; 057 058 /** 059 * Width of thinmath space according to 3.3.4.2. 060 */ 061 public static final String THINMATHSPACE = "0.166667em"; 062 063 /** 064 * Width of mediummath space according to 3.3.4.2. 065 */ 066 public static final String MEDIUMMATHSPACE = "0.222222em"; 067 068 /** 069 * Width of tickmath space according to 3.3.4.2. 070 */ 071 public static final String THICKMATHSPACE = "0.277778em"; 072 073 /** 074 * Width of verytickmath space according to 3.3.4.2. 075 */ 076 public static final String VERYTHICKMATHSPACE = "0.333333em"; 077 078 /** 079 * Width of veryverytickmath space according to 3.3.4.2. 080 */ 081 public static final String VERYVERYTHICKMATHSPACE = "0.388889em"; 082 083 /** 084 * Unit for pt. 085 */ 086 public static final String PT = "pt"; 087 088 /** 089 * Infinity. Should be reasonably large. 090 */ 091 public static final String INFINITY = "9999999pt"; 092 093 private static final String ERROR_PARSING_NUMBER = "Error Parsing number: "; 094 095 private static final int HASHSHORT_ALPHA = 5; 096 097 private static final int HASHSHORT_NO_ALPHA = 4; 098 099 private static final int SHORT_INDEX_RED = 1; 100 101 private static final int SHORT_INDEX_GREEN = 2; 102 103 private static final int SHORT_INDEX_BLUE = 3; 104 105 private static final int HASHLEN_ALPHA = 9; 106 107 private static final int HASHLEN_NO_ALPHA = 7; 108 109 private static final int HEXBASE = 16; 110 111 private static final float MAX_HEXCHAR_AS_FLOAT = 15f; 112 113 private static final int MAX_BYTE = 255; 114 115 private static final float MAX_PERCENT_AS_FLOAT = 100.0f; 116 117 private static final float MAX_BYTE_AS_FLOAT = 255f; 118 119 private static final String PERCENT_SIGN = "%"; 120 121 private static final String COMMA = ","; 122 123 /** 124 * Value of EM (horizontal size). 125 * <p> 126 * Please note: This is a typical value, according to 127 * http://kb.mozillazine.org/Em_vs._ex It is not dependent on the actual 128 * font used, as it should be. 129 */ 130 private static final float EM = 0.83888888888888888888f; 131 132 /** 133 * Value of EX (vertical size). 134 * <p> 135 * Please note: This is a typical value, according to 136 * http://kb.mozillazine.org/Em_vs._ex It is not dependent on the actual 137 * font used, as it should be. 138 */ 139 private static final float EX = 0.5f; 140 141 /** 142 * Default DPI value for all Java apps. 143 */ 144 private static final float DPI = 72.0f; 145 146 private static final float PERCENT = 0.01f; 147 148 private static final float CM_PER_INCH = 2.54f; 149 150 private static final float MM_PER_INCH = AttributesHelper.CM_PER_INCH * 10.0f; 151 152 private static final float PT_PER_PC = 12.0f; 153 154 private static final Map<String, String> SIZETRANSLATIONS = new HashMap<String, String>(); 155 156 private static final Map<String, Float> RELATIVE_UNITS = new HashMap<String, Float>(); 157 158 private static final Map<String, Float> ABSOLUTE_UNITS = new HashMap<String, Float>(); 159 160 private static final Map<String, Color> COLOR_MAPPINGS = new HashMap<String, Color>(); 161 162 /** 163 * Logger for this class. 164 */ 165 private static final Log LOGGER = LogFactory 166 .getLog(AbstractJEuclidElement.class); 167 168 /** 169 * Private constructor: it's forbidden to create instance of this utility 170 * class. 171 */ 172 private AttributesHelper() { 173 } 174 175 /** 176 * Parse a size that is relative to a given size. 177 * 178 * @param sizeString 179 * sizeString to parse 180 * @param context 181 * Context to use to calculate for absolute sizes. 182 * @param relativeTo 183 * This size is relative to the given size (must be in pt). 184 * @return a size in pt. 185 */ 186 public static float parseRelativeSize(final String sizeString, 187 final LayoutContext context, final float relativeTo) { 188 if (sizeString == null) { 189 return relativeTo; 190 } 191 final String tSize = AttributesHelper.prepareSizeString(sizeString); 192 float retVal; 193 try { 194 if (tSize.length() >= 2) { 195 final int valueLen = tSize.length() - 2; 196 final String unit = tSize.substring(valueLen); 197 final float value = Float.parseFloat(tSize.substring(0, 198 valueLen)); 199 if (AttributesHelper.ABSOLUTE_UNITS.containsKey(unit)) { 200 retVal = AttributesHelper.convertSizeToPt(sizeString, 201 context, AttributesHelper.PT); 202 } else if (AttributesHelper.RELATIVE_UNITS.containsKey(unit)) { 203 retVal = value * relativeTo 204 * AttributesHelper.RELATIVE_UNITS.get(unit); 205 } else { 206 retVal = Float.parseFloat(tSize) * relativeTo; 207 } 208 } else { 209 retVal = Float.parseFloat(tSize) * relativeTo; 210 } 211 } catch (final NumberFormatException nfe) { 212 retVal = relativeTo; 213 AttributesHelper.LOGGER.warn(AttributesHelper.ERROR_PARSING_NUMBER 214 + sizeString); 215 } 216 return retVal; 217 } 218 219 /** 220 * Translates size into pt. 221 * 222 * @param sizeString 223 * string to convert 224 * @param context 225 * LayoutContext this size is relative to. This is usually the 226 * context of the parent or the element itself. 227 * @param defaultUnit 228 * default Unit to use in this context. May be px, pt, em, etc. 229 * @return Translated value of the size attribute into Point (=Java Pixels). 230 */ 231 public static float convertSizeToPt(final String sizeString, 232 final LayoutContext context, final String defaultUnit) { 233 final String tSize = AttributesHelper.prepareSizeString(sizeString); 234 if (tSize.length() == 0) { 235 return 0; 236 } 237 float retVal; 238 try { 239 final String unit; 240 final float value; 241 if (tSize.length() <= 2) { 242 unit = defaultUnit; 243 value = Float.parseFloat(tSize); 244 } else { 245 final int valueLen = tSize.length() - 2; 246 unit = tSize.substring(valueLen); 247 value = Float.parseFloat(tSize.substring(0, valueLen)); 248 } 249 if (value == 0) { 250 retVal = 0.0f; 251 } else if (AttributesHelper.RELATIVE_UNITS.containsKey(unit)) { 252 retVal = value * GraphicsSupport.getFontsizeInPoint(context) 253 * AttributesHelper.RELATIVE_UNITS.get(unit); 254 } else if (AttributesHelper.ABSOLUTE_UNITS.containsKey(unit)) { 255 retVal = value * AttributesHelper.ABSOLUTE_UNITS.get(unit); 256 } else if (defaultUnit.length() > 0) { 257 retVal = AttributesHelper.convertSizeToPt(sizeString 258 + defaultUnit, context, ""); 259 } else { 260 retVal = Float.parseFloat(tSize); 261 AttributesHelper.LOGGER.warn("Error Parsing attribute: " 262 + sizeString + " assuming " + retVal 263 + AttributesHelper.PT); 264 } 265 } catch (final NumberFormatException nfe) { 266 retVal = 1.0f; 267 AttributesHelper.LOGGER.warn(AttributesHelper.ERROR_PARSING_NUMBER 268 + sizeString + " falling back to " + retVal 269 + AttributesHelper.PT); 270 } 271 return retVal; 272 } 273 274 private static String prepareSizeString(final String sizeString) { 275 if (sizeString == null) { 276 return ""; 277 } 278 String tSize = sizeString.trim().toLowerCase(Locale.ENGLISH); 279 280 final String translatesTo = AttributesHelper.SIZETRANSLATIONS 281 .get(tSize); 282 if (translatesTo != null) { 283 tSize = translatesTo; 284 } 285 if (tSize.endsWith(AttributesHelper.PERCENT_SIGN)) { 286 // A nice trick because all other units are exactly 2 chars long. 287 tSize = new StringBuilder(tSize).append(' ').toString(); 288 } 289 return tSize; 290 } 291 292 /** 293 * Creates a color from a given string. 294 * <p> 295 * This function supports a wide variety of inputs. 296 * <ul> 297 * <li>#RGB (hex 0..f)</li> 298 * <li>#RGBA (hex 0..f)</li> 299 * <li>#RRGGBB (hex 00..ff)</li> 300 * <li>#RRGGBBAA (hex 00..ff)</li> 301 * <li>rgb(r,g,b) (0..255 or 0%..100%)</li> 302 * <li>java.awt.Color[r=r,g=g,b=b] (0..255)</li> 303 * <li>system-color(colorname)</li> 304 * <li>transparent</li> 305 * <li>colorname</li> 306 * </ul> 307 * 308 * @param value 309 * the string to parse. 310 * @return a Color representing the string if possible 311 * @param defaultValue 312 * a default color to use in case of failure. 313 */ 314 public static Color stringToColor(final String value, 315 final Color defaultValue) { 316 if (value == null) { 317 return null; 318 } 319 320 final String lowVal = value.toLowerCase(Locale.ENGLISH); 321 Color parsedColor = null; 322 323 if (AttributesHelper.COLOR_MAPPINGS.containsKey(lowVal)) { 324 parsedColor = AttributesHelper.COLOR_MAPPINGS.get(lowVal); 325 } else { 326 if (value.charAt(0) == '#') { 327 parsedColor = AttributesHelper.parseWithHash(value); 328 } else if (value.startsWith("rgb(")) { 329 parsedColor = AttributesHelper.parseAsRGB(value); 330 } else if (value.startsWith("java.awt.Color")) { 331 parsedColor = AttributesHelper.parseAsJavaAWTColor(value); 332 } 333 334 if (parsedColor == null) { 335 parsedColor = defaultValue; 336 } 337 338 if (parsedColor != null) { 339 AttributesHelper.COLOR_MAPPINGS.put(value, parsedColor); 340 } 341 } 342 return parsedColor; 343 } 344 345 /** 346 * Tries to parse the standard java.awt.Color toString output. 347 * 348 * @param value 349 * the complete line 350 * @return a color if possible 351 * @see java.awt.Color#toString() 352 */ 353 private static Color parseAsJavaAWTColor(final String value) { 354 final Color parsedColor; 355 final int poss = value.indexOf('['); 356 final int pose = value.indexOf(']'); 357 if ((poss == -1) || (pose == -1)) { 358 parsedColor = null; 359 } else { 360 parsedColor = AttributesHelper.parseCommaSeparatedString(value 361 .substring(poss + 1, pose)); 362 } 363 return parsedColor; 364 } 365 366 /** 367 * Parse a color given with the rgb() function. 368 * 369 * @param value 370 * the complete line 371 * @return a color if possible 372 */ 373 private static Color parseAsRGB(final String value) { 374 final Color parsedColor; 375 final int poss = value.indexOf('('); 376 final int pose = value.indexOf(')'); 377 if ((poss == -1) || (pose == -1)) { 378 parsedColor = null; 379 } else { 380 parsedColor = AttributesHelper.parseCommaSeparatedString(value 381 .substring(poss + 1, pose)); 382 } 383 return parsedColor; 384 } 385 386 private static Color parseCommaSeparatedString(final String value) { 387 Color parsedColor; 388 final StringTokenizer st = new StringTokenizer(value, 389 AttributesHelper.COMMA); 390 try { 391 float red = 0.0f; 392 float green = 0.0f; 393 float blue = 0.0f; 394 if (st.hasMoreTokens()) { 395 final String str = st.nextToken().trim(); 396 red = AttributesHelper.parseFloatOrPercent(str); 397 } 398 if (st.hasMoreTokens()) { 399 final String str = st.nextToken().trim(); 400 green = AttributesHelper.parseFloatOrPercent(str); 401 } 402 if (st.hasMoreTokens()) { 403 final String str = st.nextToken().trim(); 404 blue = AttributesHelper.parseFloatOrPercent(str); 405 } 406 parsedColor = new Color(red, green, blue); 407 } catch (final NumberFormatException e) { 408 AttributesHelper.LOGGER.warn(e); 409 parsedColor = null; 410 } 411 return parsedColor; 412 } 413 414 private static float parseFloatOrPercent(final String str) { 415 final float value; 416 if (str.endsWith(AttributesHelper.PERCENT_SIGN)) { 417 value = Float.parseFloat(str.substring(0, str.length() - 1)) 418 / AttributesHelper.MAX_PERCENT_AS_FLOAT; 419 } else { 420 value = Float.parseFloat(str) / AttributesHelper.MAX_BYTE_AS_FLOAT; 421 } 422 if ((value < 0.0f) || (value > 1.0f)) { 423 throw new NumberFormatException(str + " is out of Range"); 424 } 425 return value; 426 } 427 428 /** 429 * parse a color given in the #.... format. 430 * 431 * @param value 432 * the complete line 433 * @return a color if possible 434 */ 435 private static Color parseWithHash(final String value) { 436 Color parsedColor = null; 437 try { 438 final int len = value.length(); 439 if ((len >= AttributesHelper.HASHSHORT_NO_ALPHA) 440 && (len <= AttributesHelper.HASHSHORT_ALPHA)) { 441 // note: divide by 15 so F = FF = 1 and so on 442 final float red = Integer.parseInt(value.substring( 443 AttributesHelper.SHORT_INDEX_RED, 444 AttributesHelper.SHORT_INDEX_RED + 1), 445 AttributesHelper.HEXBASE) 446 / AttributesHelper.MAX_HEXCHAR_AS_FLOAT; 447 final float green = Integer.parseInt(value.substring( 448 AttributesHelper.SHORT_INDEX_GREEN, 449 AttributesHelper.SHORT_INDEX_GREEN + 1), 450 AttributesHelper.HEXBASE) 451 / AttributesHelper.MAX_HEXCHAR_AS_FLOAT; 452 final float blue = Integer.parseInt(value.substring( 453 AttributesHelper.SHORT_INDEX_BLUE, 454 AttributesHelper.SHORT_INDEX_BLUE + 1), 455 AttributesHelper.HEXBASE) 456 / AttributesHelper.MAX_HEXCHAR_AS_FLOAT; 457 float alpha = 1.0f; 458 if (len == AttributesHelper.HASHSHORT_ALPHA) { 459 alpha = Integer.parseInt(value 460 .substring(AttributesHelper.HASHSHORT_NO_ALPHA), 461 AttributesHelper.HEXBASE) 462 / AttributesHelper.MAX_HEXCHAR_AS_FLOAT; 463 } 464 parsedColor = new Color(red, green, blue, alpha); 465 } else if ((len == AttributesHelper.HASHLEN_NO_ALPHA) 466 || (len == AttributesHelper.HASHLEN_ALPHA)) { 467 final int red = Integer.parseInt(value.substring(1, 3), 468 AttributesHelper.HEXBASE); 469 final int green = Integer.parseInt(value.substring(3, 5), 470 AttributesHelper.HEXBASE); 471 final int blue = Integer.parseInt(value.substring(5, 472 AttributesHelper.HASHLEN_NO_ALPHA), 473 AttributesHelper.HEXBASE); 474 int alpha = AttributesHelper.MAX_BYTE; 475 if (len == AttributesHelper.HASHLEN_ALPHA) { 476 alpha = Integer.parseInt(value 477 .substring(AttributesHelper.HASHLEN_NO_ALPHA), 478 AttributesHelper.HEXBASE); 479 } 480 parsedColor = new Color(red, green, blue, alpha); 481 } else { 482 throw new NumberFormatException(); 483 } 484 } catch (final NumberFormatException e) { 485 return null; 486 } 487 return parsedColor; 488 } 489 490 /** 491 * Creates a re-parsable string representation of the given color. 492 * <p> 493 * First, the color will be converted into the sRGB colorspace. It will then 494 * be printed as #rrggbb, or as #rrrggbbaa if an alpha value is present. 495 * 496 * @param color 497 * the color to represent. 498 * @return a re-parsable string representadion. 499 */ 500 public static String colorTOsRGBString(final Color color) { 501 if (color == null) { 502 return AttributesHelper.COLOR_TRANSPARENT; 503 } 504 final StringBuffer sbuf = new StringBuffer(10); 505 sbuf.append('#'); 506 String s = Integer.toHexString(color.getRed()); 507 if (s.length() == 1) { 508 sbuf.append('0'); 509 } 510 sbuf.append(s); 511 s = Integer.toHexString(color.getGreen()); 512 if (s.length() == 1) { 513 sbuf.append('0'); 514 } 515 sbuf.append(s); 516 s = Integer.toHexString(color.getBlue()); 517 if (s.length() == 1) { 518 sbuf.append('0'); 519 } 520 sbuf.append(s); 521 if (color.getAlpha() != AttributesHelper.MAX_BYTE) { 522 s = Integer.toHexString(color.getAlpha()); 523 if (s.length() == 1) { 524 sbuf.append('0'); 525 } 526 sbuf.append(s); 527 } 528 return sbuf.toString(); 529 530 } 531 532 // CHECKSTYLE:OFF 533 static { 534 535 // Mostly taken from 2.4.4.2 536 AttributesHelper.SIZETRANSLATIONS.put( 537 OperatorDictionary.NAME_VERYVERYTHINMATHSPACE, 538 AttributesHelper.VERYVERYTHINMATHSPACE); 539 AttributesHelper.SIZETRANSLATIONS.put( 540 OperatorDictionary.NAME_VERYTHINMATHSPACE, 541 AttributesHelper.VERYTHINMATHSPACE); 542 AttributesHelper.SIZETRANSLATIONS.put( 543 OperatorDictionary.NAME_THINMATHSPACE, 544 AttributesHelper.THINMATHSPACE); 545 AttributesHelper.SIZETRANSLATIONS.put( 546 OperatorDictionary.NAME_MEDIUMMATHSPACE, 547 AttributesHelper.MEDIUMMATHSPACE); 548 AttributesHelper.SIZETRANSLATIONS.put( 549 OperatorDictionary.NAME_THICKMATHSPACE, 550 AttributesHelper.THICKMATHSPACE); 551 AttributesHelper.SIZETRANSLATIONS.put( 552 OperatorDictionary.NAME_VERYTHICKMATHSPACE, 553 AttributesHelper.VERYTHICKMATHSPACE); 554 AttributesHelper.SIZETRANSLATIONS.put( 555 OperatorDictionary.NAME_VERYVERYTHICKMATHSPACE, 556 AttributesHelper.VERYVERYTHICKMATHSPACE); 557 AttributesHelper.SIZETRANSLATIONS.put(OperatorDictionary.NAME_INFINITY, 558 AttributesHelper.INFINITY); 559 AttributesHelper.SIZETRANSLATIONS.put("small", "68%"); 560 AttributesHelper.SIZETRANSLATIONS.put("normal", "100%"); 561 AttributesHelper.SIZETRANSLATIONS.put("big", "147%"); 562 563 // For mfrac, as of 3.3.2.2 564 AttributesHelper.SIZETRANSLATIONS.put("thin", "0.5"); 565 AttributesHelper.SIZETRANSLATIONS.put("medium", "1"); 566 AttributesHelper.SIZETRANSLATIONS.put("thick", "2"); 567 568 AttributesHelper.SIZETRANSLATIONS.put("null", Constants.ZERO); 569 570 AttributesHelper.RELATIVE_UNITS.put("em", AttributesHelper.EM); 571 AttributesHelper.RELATIVE_UNITS.put("ex", AttributesHelper.EX); 572 AttributesHelper.RELATIVE_UNITS.put("% ", AttributesHelper.PERCENT); 573 574 AttributesHelper.ABSOLUTE_UNITS.put("px", 1.0f); 575 AttributesHelper.ABSOLUTE_UNITS.put("in", AttributesHelper.DPI); 576 AttributesHelper.ABSOLUTE_UNITS.put("cm", AttributesHelper.DPI 577 / AttributesHelper.CM_PER_INCH); 578 AttributesHelper.ABSOLUTE_UNITS.put("mm", AttributesHelper.DPI 579 / AttributesHelper.MM_PER_INCH); 580 AttributesHelper.ABSOLUTE_UNITS.put(AttributesHelper.PT, 1.0f); 581 AttributesHelper.ABSOLUTE_UNITS.put("pc", AttributesHelper.PT_PER_PC); 582 583 // Defined in 3.2.2.2 584 AttributesHelper.COLOR_MAPPINGS.put("aqua", new Color(0, 255, 255)); 585 AttributesHelper.COLOR_MAPPINGS.put("black", Color.BLACK); 586 AttributesHelper.COLOR_MAPPINGS.put("blue", Color.BLUE); 587 AttributesHelper.COLOR_MAPPINGS.put("fuchsia", new Color(255, 0, 255)); 588 AttributesHelper.COLOR_MAPPINGS.put("gray", Color.GRAY); 589 AttributesHelper.COLOR_MAPPINGS.put("green", Color.GREEN); 590 AttributesHelper.COLOR_MAPPINGS.put("lime", new Color(0, 255, 0)); 591 AttributesHelper.COLOR_MAPPINGS.put("maroon", new Color(128, 0, 0)); 592 AttributesHelper.COLOR_MAPPINGS.put("navy", new Color(0, 0, 128)); 593 AttributesHelper.COLOR_MAPPINGS.put("olive", new Color(128, 128, 0)); 594 AttributesHelper.COLOR_MAPPINGS.put("purple", new Color(128, 0, 128)); 595 AttributesHelper.COLOR_MAPPINGS.put("red", Color.RED); 596 AttributesHelper.COLOR_MAPPINGS.put("silver", new Color(192, 192, 192)); 597 AttributesHelper.COLOR_MAPPINGS.put("teal", new Color(0, 128, 128)); 598 AttributesHelper.COLOR_MAPPINGS.put("white", Color.WHITE); 599 AttributesHelper.COLOR_MAPPINGS.put("yellow", Color.YELLOW); 600 601 // Additional colors 602 AttributesHelper.COLOR_MAPPINGS.put(AttributesHelper.COLOR_TRANSPARENT, 603 null); 604 AttributesHelper.COLOR_MAPPINGS.put("null", null); 605 AttributesHelper.COLOR_MAPPINGS.put("", null); 606 } 607 // CHECKSTYLE:ON 608 }