001/*
002 * SVG Salamander
003 * Copyright (c) 2004, Mark McKay
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or 
007 * without modification, are permitted provided that the following
008 * conditions are met:
009 *
010 *   - Redistributions of source code must retain the above 
011 *     copyright notice, this list of conditions and the following
012 *     disclaimer.
013 *   - Redistributions in binary form must reproduce the above
014 *     copyright notice, this list of conditions and the following
015 *     disclaimer in the documentation and/or other materials 
016 *     provided with the distribution.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
029 * OF THE POSSIBILITY OF SUCH DAMAGE. 
030 * 
031 * Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
032 * projects can be found at http://www.kitfox.com
033 *
034 * Created on January 27, 2004, 2:53 PM
035 */
036
037package com.kitfox.svg.xml;
038
039import com.kitfox.svg.SVGConst;
040import java.awt.*;
041import java.io.*;
042import java.net.*;
043import java.util.logging.Level;
044import java.util.logging.Logger;
045import java.util.regex.*;
046
047/**
048 * @author Mark McKay
049 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
050 */
051public class StyleAttribute implements Serializable
052{
053    public static final long serialVersionUID = 0;
054
055    static final Pattern patternUrl = Pattern.compile("\\s*url\\((.*)\\)\\s*");
056    static final Matcher matchFpNumUnits = Pattern.compile("\\s*([-+]?((\\d*\\.\\d+)|(\\d+))([-+]?[eE]\\d+)?)\\s*(px|cm|mm|in|pc|pt|em|ex)\\s*").matcher("");
057    
058    String name;
059    String stringValue;
060
061    boolean colorCompatable = false;
062    boolean urlCompatable = false;
063
064    /** Creates a new instance of StyleAttribute */
065    public StyleAttribute()
066    {
067        this(null, null);
068    }
069    
070    public StyleAttribute(String name) 
071    {
072        this.name = name;
073        stringValue = null;
074    }
075
076    public StyleAttribute(String name, String stringValue) 
077    {
078        this.name = name;
079        this.stringValue = stringValue;
080    }
081
082    public String getName() {
083        return name;
084    }
085    
086    public StyleAttribute setName(String name)
087    {
088        this.name = name;
089        return this;
090    }
091    
092    public String getStringValue()
093    {
094        return stringValue; 
095    }
096
097    public String[] getStringList() 
098    { 
099        return XMLParseUtil.parseStringList(stringValue);
100    }
101
102    public void setStringValue(String value)
103    {
104        stringValue = value;
105    }
106
107    public boolean getBooleanValue() {
108        return stringValue.toLowerCase().equals("true");
109    }
110
111    public int getIntValue() {
112        return XMLParseUtil.findInt(stringValue);
113    }
114
115    public int[] getIntList() {
116        return XMLParseUtil.parseIntList(stringValue);
117    }
118
119    public double getDoubleValue() {
120        return XMLParseUtil.findDouble(stringValue);
121    }
122
123    public double[] getDoubleList() {
124        return XMLParseUtil.parseDoubleList(stringValue);
125    }
126
127    public float getFloatValue() {
128        return XMLParseUtil.findFloat(stringValue);
129    }
130
131    public float[] getFloatList() {
132        return XMLParseUtil.parseFloatList(stringValue);
133    }
134
135    public float getRatioValue() {
136        return (float)XMLParseUtil.parseRatio(stringValue);
137//        try { return Float.parseFloat(stringValue); }
138//        catch (Exception e) {}
139//        return 0f;
140    }
141
142    public String getUnits() {
143        matchFpNumUnits.reset(stringValue);
144        if (!matchFpNumUnits.matches()) return null;
145        return matchFpNumUnits.group(6);
146    }
147
148    public NumberWithUnits getNumberWithUnits() {
149        return XMLParseUtil.parseNumberWithUnits(stringValue);
150    }
151
152    public float getFloatValueWithUnits()
153    {
154        NumberWithUnits number = getNumberWithUnits();
155        return convertUnitsToPixels(number.getUnits(), number.getValue());
156    }
157    
158    static public float convertUnitsToPixels(int unitType, float value)
159    {
160        if (unitType == NumberWithUnits.UT_UNITLESS || unitType == NumberWithUnits.UT_PERCENT)
161        {
162            return value;
163        }
164        
165        float pixPerInch;
166        try 
167        {
168            pixPerInch = (float)Toolkit.getDefaultToolkit().getScreenResolution();
169        }
170        catch (HeadlessException ex)
171        {
172            //default to 72 dpi
173            pixPerInch = 72;
174        }
175        final float inchesPerCm = .3936f;
176
177        switch (unitType)
178        {
179            case NumberWithUnits.UT_IN:
180                return value * pixPerInch;
181            case NumberWithUnits.UT_CM:
182                return value * inchesPerCm * pixPerInch;
183            case NumberWithUnits.UT_MM:
184                return value * .1f * inchesPerCm * pixPerInch;
185            case NumberWithUnits.UT_PT:
186                return value * (1f / 72f) * pixPerInch;
187            case NumberWithUnits.UT_PC:
188                return value *  (1f / 6f) * pixPerInch;
189        }
190
191        return value;
192    }
193
194    public Color getColorValue()
195    {
196        return ColorTable.parseColor(stringValue);
197    }
198
199    public String parseURLFn()
200    {
201        Matcher matchUrl = patternUrl.matcher(stringValue);
202        if (!matchUrl.matches()) 
203        {
204            return null;
205        }
206        return matchUrl.group(1);
207    }
208
209    public URL getURLValue(URL docRoot)
210    {
211        String fragment = parseURLFn();
212        if (fragment == null) return null;
213        try {
214            return new URL(docRoot, fragment);
215        }
216        catch (Exception e)
217        {
218            Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e);
219            return null;
220        }
221    }
222
223    public URL getURLValue(URI docRoot)
224    {
225        String fragment = parseURLFn();
226        if (fragment == null) return null;
227        try {
228            URI ref = docRoot.resolve(fragment);
229            return ref.toURL();
230        }
231        catch (Exception e)
232        {
233            Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e);
234            return null;
235        }
236    }
237
238    public URI getURIValue()
239    {
240        return getURIValue(null);
241    }
242    
243    /**
244     * Parse this sytle attribute as a URL and return it in URI form resolved
245     * against the passed base.
246     *
247     * @param base - URI to resolve against.  If null, will return value without
248     * attempting to resolve it.
249     */
250    public URI getURIValue(URI base)
251    {
252        try {
253            String fragment = parseURLFn();
254            if (fragment == null) fragment = stringValue.replaceAll("\\s+", "");
255            if (fragment == null) return null;
256            
257            //======================
258            //This gets around a bug in the 1.5.0 JDK
259            if (Pattern.matches("[a-zA-Z]:!\\\\.*", fragment))
260            {
261                File file = new File(fragment);
262                return file.toURI();
263            }
264            //======================
265
266            //[scheme:]scheme-specific-part[#fragment]
267            
268            URI uriFrag = new URI(fragment);
269            if (uriFrag.isAbsolute())
270            {
271                //Has scheme
272                return uriFrag;
273            }
274        
275            if (base == null) return uriFrag;
276        
277            URI relBase = new URI(null, base.getSchemeSpecificPart(), null);
278            URI relUri;
279            if (relBase.isOpaque())
280            {
281                relUri = new URI(null, base.getSchemeSpecificPart(), uriFrag.getFragment());
282            }
283            else
284            {
285                relUri = relBase.resolve(uriFrag);
286            }
287            return new URI(base.getScheme() + ":" + relUri);
288        }
289        catch (Exception e)
290        {
291            Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e);
292            return null;
293        }
294    }
295    
296    public static void main(String[] args)
297    {
298        try
299        {
300            URI uri = new URI("jar:http://www.kitfox.com/jackal/jackal.jar!/res/doc/about.svg");
301            uri = uri.resolve("#myFragment");
302            
303            System.err.println(uri.toString());
304            
305            uri = new URI("http://www.kitfox.com/jackal/jackal.html");
306            uri = uri.resolve("#myFragment");
307            
308            System.err.println(uri.toString());
309        }
310        catch (Exception e)
311        {
312            Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e);
313        }
314    }
315}