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 }