source: josm/trunk/src/com/kitfox/svg/SVGUniverse.java@ 4256

Last change on this file since 4256 was 4256, checked in by bastiK, 13 years ago

see #6560 - basic svg support, includes kitfox svgsalamander, r 98, patched

File size: 21.2 KB
Line 
1/*
2 * SVGUniverse.java
3 *
4 *
5 * The Salamander Project - 2D and 3D graphics libraries in Java
6 * Copyright (C) 2004 Mark McKay
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
23 * projects can be found at http://www.kitfox.com
24 *
25 * Created on February 18, 2004, 11:43 PM
26 */
27
28package com.kitfox.svg;
29
30import com.kitfox.svg.app.beans.SVGIcon;
31import java.awt.Graphics2D;
32import java.awt.image.BufferedImage;
33import java.beans.PropertyChangeListener;
34import java.beans.PropertyChangeSupport;
35import java.io.BufferedInputStream;
36import java.io.ByteArrayInputStream;
37import java.io.ByteArrayOutputStream;
38import java.io.File;
39import java.io.IOException;
40import java.io.InputStream;
41import java.io.ObjectInputStream;
42import java.io.ObjectOutputStream;
43import java.io.Reader;
44import java.io.Serializable;
45import java.lang.ref.SoftReference;
46import java.net.MalformedURLException;
47import java.net.URI;
48import java.net.URL;
49import java.net.URLConnection;
50import java.net.URLStreamHandler;
51import java.util.HashMap;
52import java.util.Iterator;
53import java.util.zip.GZIPInputStream;
54import javax.imageio.ImageIO;
55import org.xml.sax.EntityResolver;
56import org.xml.sax.InputSource;
57import org.xml.sax.SAXException;
58import org.xml.sax.SAXParseException;
59import org.xml.sax.XMLReader;
60import org.xml.sax.helpers.XMLReaderFactory;
61
62/**
63 * Many SVG files can be loaded at one time. These files will quite likely
64 * need to reference one another. The SVG universe provides a container for
65 * all these files and the means for them to relate to each other.
66 *
67 * @author Mark McKay
68 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
69 */
70public class SVGUniverse implements Serializable
71{
72 public static final long serialVersionUID = 0;
73
74 transient private PropertyChangeSupport changes = new PropertyChangeSupport(this);
75
76 /**
77 * Maps document URIs to their loaded SVG diagrams. Note that URIs for
78 * documents loaded from URLs will reflect their URLs and URIs for documents
79 * initiated from streams will have the scheme <i>svgSalamander</i>.
80 */
81 final HashMap loadedDocs = new HashMap();
82
83 final HashMap loadedFonts = new HashMap();
84
85 final HashMap loadedImages = new HashMap();
86
87 public static final String INPUTSTREAM_SCHEME = "svgSalamander";
88
89 /**
90 * Current time in this universe. Used for resolving attributes that
91 * are influenced by track information. Time is in milliseconds. Time
92 * 0 coresponds to the time of 0 in each member diagram.
93 */
94 protected double curTime = 0.0;
95
96 private boolean verbose = false;
97
98 //Cache reader for efficiency
99 XMLReader cachedReader;
100
101 /** Creates a new instance of SVGUniverse */
102 public SVGUniverse()
103 {
104 }
105
106 public void addPropertyChangeListener(PropertyChangeListener l)
107 {
108 changes.addPropertyChangeListener(l);
109 }
110
111 public void removePropertyChangeListener(PropertyChangeListener l)
112 {
113 changes.removePropertyChangeListener(l);
114 }
115
116 /**
117 * Release all loaded SVG document from memory
118 */
119 public void clear()
120 {
121 loadedDocs.clear();
122 loadedFonts.clear();
123 loadedImages.clear();
124 }
125
126 /**
127 * Returns the current animation time in milliseconds.
128 */
129 public double getCurTime()
130 {
131 return curTime;
132 }
133
134 public void setCurTime(double curTime)
135 {
136 double oldTime = this.curTime;
137 this.curTime = curTime;
138 changes.firePropertyChange("curTime", new Double(oldTime), new Double(curTime));
139 }
140
141 /**
142 * Updates all time influenced style and presentation attributes in all SVG
143 * documents in this universe.
144 */
145 public void updateTime() throws SVGException
146 {
147 for (Iterator it = loadedDocs.values().iterator(); it.hasNext();)
148 {
149 SVGDiagram dia = (SVGDiagram)it.next();
150 dia.updateTime(curTime);
151 }
152 }
153
154 /**
155 * Called by the Font element to let the universe know that a font has been
156 * loaded and is available.
157 */
158 void registerFont(Font font)
159 {
160 loadedFonts.put(font.getFontFace().getFontFamily(), font);
161 }
162
163 public Font getDefaultFont()
164 {
165 for (Iterator it = loadedFonts.values().iterator(); it.hasNext();)
166 {
167 return (Font)it.next();
168 }
169 return null;
170 }
171
172 public Font getFont(String fontName)
173 {
174 return (Font)loadedFonts.get(fontName);
175 }
176
177 URL registerImage(URI imageURI)
178 {
179 String scheme = imageURI.getScheme();
180 if (scheme.equals("data"))
181 {
182 String path = imageURI.getRawSchemeSpecificPart();
183 int idx = path.indexOf(';');
184 String mime = path.substring(0, idx);
185 String content = path.substring(idx + 1);
186
187 if (content.startsWith("base64"))
188 {
189 content = content.substring(6);
190 try {
191 byte[] buf = new sun.misc.BASE64Decoder().decodeBuffer(content);
192 ByteArrayInputStream bais = new ByteArrayInputStream(buf);
193 BufferedImage img = ImageIO.read(bais);
194
195 URL url;
196 int urlIdx = 0;
197 while (true)
198 {
199 url = new URL("inlineImage", "localhost", "img" + urlIdx);
200 if (!loadedImages.containsKey(url))
201 {
202 break;
203 }
204 urlIdx++;
205 }
206
207 SoftReference ref = new SoftReference(img);
208 loadedImages.put(url, ref);
209
210 return url;
211 } catch (IOException ex) {
212 ex.printStackTrace();
213 }
214 }
215 return null;
216 }
217 else
218 {
219 try {
220 URL url = imageURI.toURL();
221 registerImage(url);
222 return url;
223 } catch (MalformedURLException ex) {
224 ex.printStackTrace();
225 }
226 return null;
227 }
228 }
229
230 void registerImage(URL imageURL)
231 {
232 if (loadedImages.containsKey(imageURL)) return;
233
234 SoftReference ref;
235 try
236 {
237 String fileName = imageURL.getFile();
238 if (".svg".equals(fileName.substring(fileName.length() - 4).toLowerCase()))
239 {
240 SVGIcon icon = new SVGIcon();
241 icon.setSvgURI(imageURL.toURI());
242
243 BufferedImage img = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
244 Graphics2D g = img.createGraphics();
245 icon.paintIcon(null, g, 0, 0);
246 g.dispose();
247 ref = new SoftReference(img);
248 }
249 else
250 {
251 BufferedImage img = ImageIO.read(imageURL);
252 ref = new SoftReference(img);
253 }
254 loadedImages.put(imageURL, ref);
255 }
256 catch (Exception e)
257 {
258 System.err.println("Could not load image: " + imageURL);
259 e.printStackTrace();
260 }
261 }
262
263 BufferedImage getImage(URL imageURL)
264 {
265 SoftReference ref = (SoftReference)loadedImages.get(imageURL);
266 if (ref == null) return null;
267
268 BufferedImage img = (BufferedImage)ref.get();
269 //If image was cleared from memory, reload it
270 if (img == null)
271 {
272 try
273 {
274 img = ImageIO.read(imageURL);
275 }
276 catch (Exception e)
277 { e.printStackTrace(); }
278 ref = new SoftReference(img);
279 loadedImages.put(imageURL, ref);
280 }
281
282 return img;
283 }
284
285 /**
286 * Returns the element of the document at the given URI. If the document
287 * is not already loaded, it will be.
288 */
289 public SVGElement getElement(URI path)
290 {
291 return getElement(path, true);
292 }
293
294 public SVGElement getElement(URL path)
295 {
296 try
297 {
298 URI uri = new URI(path.toString());
299 return getElement(uri, true);
300 }
301 catch (Exception e)
302 {
303 e.printStackTrace();
304 }
305 return null;
306 }
307
308 /**
309 * Looks up a href within our universe. If the href refers to a document that
310 * is not loaded, it will be loaded. The URL #target will then be checked
311 * against the SVG diagram's index and the coresponding element returned.
312 * If there is no coresponding index, null is returned.
313 */
314 public SVGElement getElement(URI path, boolean loadIfAbsent)
315 {
316 try
317 {
318 //Strip fragment from URI
319 URI xmlBase = new URI(path.getScheme(), path.getSchemeSpecificPart(), null);
320
321 SVGDiagram dia = (SVGDiagram)loadedDocs.get(xmlBase);
322 if (dia == null && loadIfAbsent)
323 {
324//System.err.println("SVGUnivserse: " + xmlBase.toString());
325//javax.swing.JOptionPane.showMessageDialog(null, xmlBase.toString());
326 URL url = xmlBase.toURL();
327
328 loadSVG(url, false);
329 dia = (SVGDiagram)loadedDocs.get(xmlBase);
330 if (dia == null) return null;
331 }
332
333 String fragment = path.getFragment();
334 return fragment == null ? dia.getRoot() : dia.getElement(fragment);
335 }
336 catch (Exception e)
337 {
338 e.printStackTrace();
339 return null;
340 }
341 }
342
343 public SVGDiagram getDiagram(URI xmlBase)
344 {
345 return getDiagram(xmlBase, true);
346 }
347
348 /**
349 * Returns the diagram that has been loaded from this root. If diagram is
350 * not already loaded, returns null.
351 */
352 public SVGDiagram getDiagram(URI xmlBase, boolean loadIfAbsent)
353 {
354 if (xmlBase == null) return null;
355
356 SVGDiagram dia = (SVGDiagram)loadedDocs.get(xmlBase);
357 if (dia != null || !loadIfAbsent) return dia;
358
359 //Load missing diagram
360 try
361 {
362 URL url;
363 if ("jar".equals(xmlBase.getScheme()) && xmlBase.getPath() != null && !xmlBase.getPath().contains("!/"))
364 {
365 //Workaround for resources stored in jars loaded by Webstart.
366 //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6753651
367 url = SVGUniverse.class.getResource("xmlBase.getPath()");
368 }
369 else
370 {
371 url = xmlBase.toURL();
372 }
373
374
375 loadSVG(url, false);
376 dia = (SVGDiagram)loadedDocs.get(xmlBase);
377 return dia;
378 }
379 catch (Exception e)
380 {
381 e.printStackTrace();
382 }
383
384 return null;
385 }
386
387 /**
388 * Wraps input stream in a BufferedInputStream. If it is detected that this
389 * input stream is GZIPped, also wraps in a GZIPInputStream for inflation.
390 *
391 * @param is Raw input stream
392 * @return Uncompressed stream of SVG data
393 * @throws java.io.IOException
394 */
395 private InputStream createDocumentInputStream(InputStream is) throws IOException
396 {
397 BufferedInputStream bin = new BufferedInputStream(is);
398 bin.mark(2);
399 int b0 = bin.read();
400 int b1 = bin.read();
401 bin.reset();
402
403 //Check for gzip magic number
404 if ((b1 << 8 | b0) == GZIPInputStream.GZIP_MAGIC)
405 {
406 GZIPInputStream iis = new GZIPInputStream(bin);
407 return iis;
408 }
409 else
410 {
411 //Plain text
412 return bin;
413 }
414 }
415
416 public URI loadSVG(URL docRoot)
417 {
418 return loadSVG(docRoot, false);
419 }
420
421 /**
422 * Loads an SVG file and all the files it references from the URL provided.
423 * If a referenced file already exists in the SVG universe, it is not
424 * reloaded.
425 * @param docRoot - URL to the location where this SVG file can be found.
426 * @param forceLoad - if true, ignore cached diagram and reload
427 * @return - The URI that refers to the loaded document
428 */
429 public URI loadSVG(URL docRoot, boolean forceLoad)
430 {
431 try
432 {
433 URI uri = new URI(docRoot.toString());
434 if (loadedDocs.containsKey(uri) && !forceLoad) return uri;
435
436 InputStream is = docRoot.openStream();
437 return loadSVG(uri, new InputSource(createDocumentInputStream(is)));
438 }
439 catch (Throwable t)
440 {
441 t.printStackTrace();
442 }
443
444 return null;
445 }
446
447
448 public URI loadSVG(InputStream is, String name) throws IOException
449 {
450 return loadSVG(is, name, false);
451 }
452
453 public URI loadSVG(InputStream is, String name, boolean forceLoad) throws IOException
454 {
455 URI uri = getStreamBuiltURI(name);
456 if (uri == null) return null;
457 if (loadedDocs.containsKey(uri) && !forceLoad) return uri;
458
459 return loadSVG(uri, new InputSource(createDocumentInputStream(is)));
460 }
461
462 public URI loadSVG(Reader reader, String name)
463 {
464 return loadSVG(reader, name, false);
465 }
466
467 /**
468 * This routine allows you to create SVG documents from data streams that
469 * may not necessarily have a URL to load from. Since every SVG document
470 * must be identified by a unique URL, Salamander provides a method to
471 * fake this for streams by defining it's own protocol - svgSalamander -
472 * for SVG documents without a formal URL.
473 *
474 * @param reader - A stream containing a valid SVG document
475 * @param name - <p>A unique name for this document. It will be used to
476 * construct a unique URI to refer to this document and perform resolution
477 * with relative URIs within this document.</p>
478 * <p>For example, a name of "/myScene" will produce the URI
479 * svgSalamander:/myScene. "/maps/canada/toronto" will produce
480 * svgSalamander:/maps/canada/toronto. If this second document then
481 * contained the href "../uk/london", it would resolve by default to
482 * svgSalamander:/maps/uk/london. That is, SVG Salamander defines the
483 * URI scheme svgSalamander for it's own internal use and uses it
484 * for uniquely identfying documents loaded by stream.</p>
485 * <p>If you need to link to documents outside of this scheme, you can
486 * either supply full hrefs (eg, href="url(http://www.kitfox.com/index.html)")
487 * or put the xml:base attribute in a tag to change the defaultbase
488 * URIs are resolved against</p>
489 * <p>If a name does not start with the character '/', it will be automatically
490 * prefixed to it.</p>
491 * @param forceLoad - if true, ignore cached diagram and reload
492 *
493 * @return - The URI that refers to the loaded document
494 */
495 public URI loadSVG(Reader reader, String name, boolean forceLoad)
496 {
497//System.err.println(url.toString());
498 //Synthesize URI for this stream
499 URI uri = getStreamBuiltURI(name);
500 if (uri == null) return null;
501 if (loadedDocs.containsKey(uri) && !forceLoad) return uri;
502
503 return loadSVG(uri, new InputSource(reader));
504 }
505
506 /**
507 * Synthesize a URI for an SVGDiagram constructed from a stream.
508 * @param name - Name given the document constructed from a stream.
509 */
510 public URI getStreamBuiltURI(String name)
511 {
512 if (name == null || name.length() == 0) return null;
513
514 if (name.charAt(0) != '/') name = '/' + name;
515
516 try
517 {
518 //Dummy URL for SVG documents built from image streams
519 return new URI(INPUTSTREAM_SCHEME, name, null);
520 }
521 catch (Exception e)
522 {
523 e.printStackTrace();
524 return null;
525 }
526 }
527
528 private XMLReader getXMLReaderCached() throws SAXException
529 {
530 if (cachedReader == null)
531 {
532 cachedReader = XMLReaderFactory.createXMLReader();
533 }
534 return cachedReader;
535 }
536
537// protected URI loadSVG(URI xmlBase, InputStream is)
538 protected URI loadSVG(URI xmlBase, InputSource is)
539 {
540 // Use an instance of ourselves as the SAX event handler
541 SVGLoader handler = new SVGLoader(xmlBase, this, verbose);
542
543 //Place this docment in the universe before it is completely loaded
544 // so that the load process can refer to references within it's current
545 // document
546//System.err.println("SVGUniverse: loading dia " + xmlBase);
547 loadedDocs.put(xmlBase, handler.getLoadedDiagram());
548
549 // Use the default (non-validating) parser
550// SAXParserFactory factory = SAXParserFactory.newInstance();
551// factory.setValidating(false);
552// factory.setNamespaceAware(true);
553
554 try
555 {
556 // Parse the input
557 XMLReader reader = getXMLReaderCached();
558 reader.setEntityResolver(
559 new EntityResolver()
560 {
561 public InputSource resolveEntity(String publicId, String systemId)
562 {
563 //Ignore all DTDs
564 return new InputSource(new ByteArrayInputStream(new byte[0]));
565 }
566 }
567 );
568 reader.setContentHandler(handler);
569 reader.parse(is);
570
571// SAXParser saxParser = factory.newSAXParser();
572// saxParser.parse(new InputSource(new BufferedReader(is)), handler);
573 return xmlBase;
574 }
575 catch (SAXParseException sex)
576 {
577 System.err.println("Error processing " + xmlBase);
578 System.err.println(sex.getMessage());
579
580 loadedDocs.remove(xmlBase);
581 return null;
582 }
583 catch (Throwable t)
584 {
585 t.printStackTrace();
586 }
587
588 return null;
589 }
590
591 public static void main(String argv[])
592 {
593 try
594 {
595 URL url = new URL("svgSalamander", "localhost", -1, "abc.svg",
596 new URLStreamHandler()
597 {
598 protected URLConnection openConnection(URL u)
599 {
600 return null;
601 }
602 }
603 );
604// URL url2 = new URL("svgSalamander", "localhost", -1, "abc.svg");
605
606 //Investigate URI resolution
607 URI uriA, uriB, uriC, uriD, uriE;
608
609 uriA = new URI("svgSalamander", "/names/mySpecialName", null);
610// uriA = new URI("http://www.kitfox.com/salamander");
611// uriA = new URI("svgSalamander://mySpecialName/grape");
612 System.err.println(uriA.toString());
613 System.err.println(uriA.getScheme());
614
615 uriB = uriA.resolve("#begin");
616 System.err.println(uriB.toString());
617
618 uriC = uriA.resolve("tree#boing");
619 System.err.println(uriC.toString());
620
621 uriC = uriA.resolve("../tree#boing");
622 System.err.println(uriC.toString());
623 }
624 catch (Exception e)
625 {
626 e.printStackTrace();
627 }
628 }
629
630 public boolean isVerbose()
631 {
632 return verbose;
633 }
634
635 public void setVerbose(boolean verbose)
636 {
637 this.verbose = verbose;
638 }
639
640 /**
641 * Uses serialization to duplicate this universe.
642 */
643 public SVGUniverse duplicate() throws IOException, ClassNotFoundException
644 {
645 ByteArrayOutputStream bs = new ByteArrayOutputStream();
646 ObjectOutputStream os = new ObjectOutputStream(bs);
647 os.writeObject(this);
648 os.close();
649
650 ByteArrayInputStream bin = new ByteArrayInputStream(bs.toByteArray());
651 ObjectInputStream is = new ObjectInputStream(bin);
652 SVGUniverse universe = (SVGUniverse)is.readObject();
653 is.close();
654
655 return universe;
656 }
657}
Note: See TracBrowser for help on using the repository browser.