View Javadoc

1   /*
2    * Copyright 2002 - 2007 JEuclid, http://jeuclid.sf.net
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  /* $Id: AttributesHelper.java,v 1e57f05a780b 2009/10/28 09:06:45 max $ */
18  
19  package net.sourceforge.jeuclid.elements.support.attributes;
20  
21  import java.awt.Color;
22  import java.util.HashMap;
23  import java.util.Locale;
24  import java.util.Map;
25  import java.util.StringTokenizer;
26  
27  import net.sourceforge.jeuclid.Constants;
28  import net.sourceforge.jeuclid.LayoutContext;
29  import net.sourceforge.jeuclid.elements.AbstractJEuclidElement;
30  import net.sourceforge.jeuclid.elements.support.GraphicsSupport;
31  import net.sourceforge.jeuclid.elements.support.operatordict.OperatorDictionary;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  
36  /**
37   * Class contains utility methods for working with elements attributes.
38   * 
39   * @version $Revision: 1e57f05a780b $
40   */
41  public final class AttributesHelper {
42  
43      /**
44       * Constant for "Transparent" color.
45       */
46      public static final String COLOR_TRANSPARENT = "transparent";
47  
48      /**
49       * Width of veryverythinmath space according to 3.3.4.2.
50       */
51      public static final String VERYVERYTHINMATHSPACE = "0.0555556em";
52  
53      /**
54       * Width of verythinmath space according to 3.3.4.2.
55       */
56      public static final String VERYTHINMATHSPACE = "0.111111em";
57  
58      /**
59       * Width of thinmath space according to 3.3.4.2.
60       */
61      public static final String THINMATHSPACE = "0.166667em";
62  
63      /**
64       * Width of mediummath space according to 3.3.4.2.
65       */
66      public static final String MEDIUMMATHSPACE = "0.222222em";
67  
68      /**
69       * Width of tickmath space according to 3.3.4.2.
70       */
71      public static final String THICKMATHSPACE = "0.277778em";
72  
73      /**
74       * Width of verytickmath space according to 3.3.4.2.
75       */
76      public static final String VERYTHICKMATHSPACE = "0.333333em";
77  
78      /**
79       * Width of veryverytickmath space according to 3.3.4.2.
80       */
81      public static final String VERYVERYTHICKMATHSPACE = "0.388889em";
82  
83      /**
84       * Unit for pt.
85       */
86      public static final String PT = "pt";
87  
88      /**
89       * Infinity. Should be reasonably large.
90       */
91      public static final String INFINITY = "9999999pt";
92  
93      private static final String ERROR_PARSING_NUMBER = "Error Parsing number: ";
94  
95      private static final int HASHSHORT_ALPHA = 5;
96  
97      private static final int HASHSHORT_NO_ALPHA = 4;
98  
99      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 }