| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.batik.bridge; |
| |
| import java.awt.RenderingHints; |
| import java.awt.Shape; |
| import java.awt.color.ColorSpace; |
| import java.awt.color.ICC_Profile; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Rectangle2D; |
| import java.io.BufferedInputStream; |
| import java.io.InputStream; |
| import java.io.InterruptedIOException; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.batik.anim.dom.AbstractSVGAnimatedLength; |
| import org.apache.batik.anim.dom.AnimatedLiveAttributeValue; |
| import org.apache.batik.anim.dom.SVGOMAnimatedPreserveAspectRatio; |
| import org.apache.batik.anim.dom.SVGOMDocument; |
| import org.apache.batik.anim.dom.SVGOMElement; |
| import org.apache.batik.css.engine.CSSEngine; |
| import org.apache.batik.css.engine.SVGCSSEngine; |
| import org.apache.batik.dom.AbstractNode; |
| import org.apache.batik.dom.events.DOMMouseEvent; |
| import org.apache.batik.dom.events.NodeEventTarget; |
| import org.apache.batik.dom.svg.LiveAttributeException; |
| import org.apache.batik.ext.awt.image.renderable.ClipRable8Bit; |
| import org.apache.batik.ext.awt.image.renderable.Filter; |
| import org.apache.batik.ext.awt.image.spi.BrokenLinkProvider; |
| import org.apache.batik.ext.awt.image.spi.ImageTagRegistry; |
| import org.apache.batik.gvt.CanvasGraphicsNode; |
| import org.apache.batik.gvt.CompositeGraphicsNode; |
| import org.apache.batik.gvt.GraphicsNode; |
| import org.apache.batik.gvt.ImageNode; |
| import org.apache.batik.gvt.RasterImageNode; |
| import org.apache.batik.gvt.ShapeNode; |
| import org.apache.batik.util.HaltingThread; |
| import org.apache.batik.util.MimeTypeConstants; |
| import org.apache.batik.util.ParsedURL; |
| import org.apache.batik.util.XMLConstants; |
| |
| import org.apache.xmlgraphics.java2d.color.ICCColorSpaceWithIntent; |
| import org.apache.xmlgraphics.java2d.color.RenderingIntent; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.events.DocumentEvent; |
| import org.w3c.dom.events.Event; |
| import org.w3c.dom.events.EventListener; |
| import org.w3c.dom.events.EventTarget; |
| import org.w3c.dom.svg.SVGDocument; |
| import org.w3c.dom.svg.SVGImageElement; |
| import org.w3c.dom.svg.SVGSVGElement; |
| |
| /** |
| * Bridge class for the <image> element. |
| * |
| * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a> |
| * @version $Id$ |
| */ |
| public class SVGImageElementBridge extends AbstractGraphicsNodeBridge { |
| |
| protected SVGDocument imgDocument; |
| protected EventListener listener = null; |
| protected BridgeContext subCtx = null; |
| protected boolean hitCheckChildren = false; |
| /** |
| * Constructs a new bridge for the <image> element. |
| */ |
| public SVGImageElementBridge() {} |
| |
| /** |
| * Returns 'image'. |
| */ |
| public String getLocalName() { |
| return SVG_IMAGE_TAG; |
| } |
| |
| /** |
| * Returns a new instance of this bridge. |
| */ |
| public Bridge getInstance() { |
| return new SVGImageElementBridge(); |
| } |
| |
| /** |
| * Creates a graphics node using the specified BridgeContext and for the |
| * specified element. |
| * |
| * @param ctx the bridge context to use |
| * @param e the element that describes the graphics node to build |
| * @return a graphics node that represents the specified element |
| */ |
| public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) { |
| ImageNode imageNode = (ImageNode)super.createGraphicsNode(ctx, e); |
| if (imageNode == null) { |
| return null; |
| } |
| |
| associateSVGContext(ctx, e, imageNode); |
| |
| hitCheckChildren = false; |
| GraphicsNode node = buildImageGraphicsNode(ctx,e); |
| |
| if (node == null) { |
| SVGImageElement ie = (SVGImageElement) e; |
| String uriStr = ie.getHref().getAnimVal(); |
| throw new BridgeException(ctx, e, ERR_URI_IMAGE_INVALID, |
| new Object[] {uriStr}); |
| } |
| |
| imageNode.setImage(node); |
| imageNode.setHitCheckChildren(hitCheckChildren); |
| |
| // 'image-rendering' and 'color-rendering' |
| RenderingHints hints = null; |
| hints = CSSUtilities.convertImageRendering(e, hints); |
| hints = CSSUtilities.convertColorRendering(e, hints); |
| if (hints != null) |
| imageNode.setRenderingHints(hints); |
| |
| return imageNode; |
| } |
| |
| /** |
| * Create a Graphics node according to the |
| * resource pointed by the href : RasterImageNode |
| * for bitmaps, CompositeGraphicsNode for svg files. |
| * |
| * @param ctx : the bridge context to use |
| * @param e the element that describes the graphics node to build |
| * |
| * @return the graphic node that represent the resource |
| * pointed by the reference |
| */ |
| protected GraphicsNode buildImageGraphicsNode |
| (BridgeContext ctx, Element e){ |
| |
| SVGImageElement ie = (SVGImageElement) e; |
| |
| // 'xlink:href' attribute - required |
| String uriStr = ie.getHref().getAnimVal(); |
| if (uriStr.length() == 0) { |
| throw new BridgeException(ctx, e, ERR_ATTRIBUTE_MISSING, |
| new Object[] {"xlink:href"}); |
| } |
| if (uriStr.indexOf('#') != -1) { |
| throw new BridgeException(ctx, e, ERR_ATTRIBUTE_VALUE_MALFORMED, |
| new Object[] {"xlink:href", uriStr}); |
| } |
| |
| // Build the URL. |
| String baseURI = AbstractNode.getBaseURI(e); |
| ParsedURL purl; |
| if (baseURI == null) { |
| purl = new ParsedURL(uriStr); |
| } else { |
| purl = new ParsedURL(baseURI, uriStr); |
| } |
| |
| return createImageGraphicsNode(ctx, e, purl); |
| } |
| |
| protected GraphicsNode createImageGraphicsNode(BridgeContext ctx, |
| Element e, |
| ParsedURL purl) { |
| Rectangle2D bounds = getImageBounds(ctx, e); |
| if ((bounds.getWidth() == 0) || (bounds.getHeight() == 0)) { |
| ShapeNode sn = new ShapeNode(); |
| sn.setShape(bounds); |
| return sn; |
| } |
| |
| SVGDocument svgDoc = (SVGDocument)e.getOwnerDocument(); |
| String docURL = svgDoc.getURL(); |
| ParsedURL pDocURL = null; |
| if (docURL != null) |
| pDocURL = new ParsedURL(docURL); |
| |
| UserAgent userAgent = ctx.getUserAgent(); |
| |
| try { |
| userAgent.checkLoadExternalResource(purl, pDocURL); |
| } catch (SecurityException secEx ) { |
| throw new BridgeException(ctx, e, secEx, ERR_URI_UNSECURE, |
| new Object[] {purl}); |
| } |
| |
| DocumentLoader loader = ctx.getDocumentLoader(); |
| ImageTagRegistry reg = ImageTagRegistry.getRegistry(); |
| ICCColorSpaceWithIntent colorspace = extractColorSpace(e, ctx); |
| { |
| /** |
| * Before we open the URL we see if we have the |
| * URL already cached and parsed |
| */ |
| try { |
| /* Check the document loader cache */ |
| Document doc = loader.checkCache(purl.toString()); |
| if (doc != null) { |
| imgDocument = (SVGDocument)doc; |
| return createSVGImageNode(ctx, e, imgDocument); |
| } |
| } catch (BridgeException ex) { |
| throw ex; |
| } catch (Exception ex) { |
| /* Nothing to do */ |
| } |
| |
| /* Check the ImageTagRegistry Cache */ |
| Filter img = reg.checkCache(purl, colorspace); |
| if (img != null) { |
| return createRasterImageNode(ctx, e, img, purl); |
| } |
| } |
| |
| /* The Protected Stream ensures that the stream doesn't |
| * get closed unless we want it to. It is also based on |
| * a Buffered Reader so in general we can mark the start |
| * and reset rather than reopening the stream. Finally |
| * it hides the mark/reset methods so only we get to |
| * use them. |
| */ |
| ProtectedStream reference = null; |
| try { |
| reference = openStream(e, purl); |
| } catch (SecurityException secEx ) { |
| throw new BridgeException(ctx, e, secEx, ERR_URI_UNSECURE, |
| new Object[] {purl}); |
| } catch (IOException ioe) { |
| return createBrokenImageNode(ctx, e, purl.toString(), |
| ioe.getLocalizedMessage()); |
| } |
| |
| { |
| /** |
| * First see if we can id the file as a Raster via magic |
| * number. This is probably the fastest mechanism. |
| * We tell the registry what the source purl is but we |
| * tell it not to open that url. |
| */ |
| Filter img = reg.readURL(reference, purl, colorspace, |
| false, false); |
| if (img != null) { |
| try { |
| reference.tie(); |
| } catch (IOException ioe) { |
| // This would be from a close, Let it slide... |
| } |
| // It's a bouncing baby Raster... |
| return createRasterImageNode(ctx, e, img, purl); |
| } |
| } |
| |
| try { |
| // Reset the stream for next try. |
| reference.retry(); |
| } catch (IOException ioe) { |
| reference.release(); |
| reference = null; |
| try { |
| // Couldn't reset stream so reopen it. |
| reference = openStream(e, purl); |
| } catch (IOException ioe2) { |
| // Since we already opened the stream this is unlikely. |
| return createBrokenImageNode(ctx, e, purl.toString(), |
| ioe2.getLocalizedMessage()); |
| } |
| } |
| |
| try { |
| /** |
| * Next see if it's an XML document. |
| */ |
| Document doc = loader.loadDocument(purl.toString(), reference); |
| reference.release(); |
| imgDocument = (SVGDocument)doc; |
| return createSVGImageNode(ctx, e, imgDocument); |
| } catch (BridgeException ex) { |
| reference.release(); |
| throw ex; |
| } catch (SecurityException secEx ) { |
| reference.release(); |
| throw new BridgeException(ctx, e, secEx, ERR_URI_UNSECURE, |
| new Object[] {purl}); |
| } catch (InterruptedIOException iioe) { |
| reference.release(); |
| if (HaltingThread.hasBeenHalted()) |
| throw new InterruptedBridgeException(); |
| |
| } catch (InterruptedBridgeException ibe) { |
| reference.release(); |
| throw ibe; |
| } catch (Exception ex) { |
| /* Do nothing drop out... */ |
| // ex.printStackTrace(); |
| } |
| |
| try { |
| reference.retry(); |
| } catch (IOException ioe) { |
| reference.release(); |
| reference = null; |
| try { |
| // Couldn't reset stream so reopen it. |
| reference = openStream(e, purl); |
| } catch (IOException ioe2) { |
| return createBrokenImageNode(ctx, e, purl.toString(), |
| ioe2.getLocalizedMessage()); |
| } |
| } |
| |
| try { |
| // Finally try to load the image as a raster image (JPG or |
| // PNG) allowing the registry to open the url (so the |
| // JDK readers can be checked). |
| Filter img = reg.readURL(reference, purl, colorspace, |
| true, true); |
| if (img != null) { |
| // It's a bouncing baby Raster... |
| return createRasterImageNode(ctx, e, img, purl); |
| } |
| } finally { |
| reference.release(); |
| } |
| return null; |
| } |
| |
| public static class ProtectedStream extends BufferedInputStream { |
| static final int BUFFER_SIZE = 8192; |
| ProtectedStream(InputStream is) { |
| super(is, BUFFER_SIZE); |
| super.mark(BUFFER_SIZE); // Remember start |
| } |
| ProtectedStream(InputStream is, int size) { |
| super(is, size); |
| super.mark(size); // Remember start |
| } |
| |
| public boolean markSupported() { |
| return false; |
| } |
| public void mark(int sz){ |
| } |
| public void reset() throws IOException { |
| throw new IOException("Reset unsupported"); |
| } |
| |
| public synchronized void retry() throws IOException { |
| super.reset(); |
| wasClosed = false; |
| isTied = false; |
| } |
| |
| public synchronized void close() throws IOException { |
| wasClosed = true; |
| if (isTied) { |
| super.close(); |
| // System.err.println("Closing stream - from close"); |
| } |
| } |
| |
| /** |
| * Let stream know that it is perminately tied to one |
| * image decoder. This means that it can allow that |
| * decoder to close the stream. |
| */ |
| public synchronized void tie() throws IOException { |
| isTied = true; |
| if (wasClosed) { |
| super.close(); |
| // System.err.println("Closing stream - from tie"); |
| } |
| } |
| |
| /** |
| * Close the stream. |
| */ |
| public void release() { |
| try { |
| super.close(); |
| // System.err.println("Closing stream - from release"); |
| } catch (IOException ioe) { |
| // Like Duh! what would you do close it again? |
| } |
| } |
| boolean wasClosed = false; |
| boolean isTied = false; |
| } |
| |
| protected ProtectedStream openStream(Element e, ParsedURL purl) |
| throws IOException { |
| List mimeTypes = new ArrayList |
| (ImageTagRegistry.getRegistry().getRegisteredMimeTypes()); |
| mimeTypes.addAll(MimeTypeConstants.MIME_TYPES_SVG_LIST); |
| InputStream reference = purl.openStream(mimeTypes.iterator()); |
| return new ProtectedStream(reference); |
| } |
| |
| /** |
| * Creates an <code>ImageNode</code>. |
| */ |
| protected GraphicsNode instantiateGraphicsNode() { |
| return new ImageNode(); |
| } |
| |
| /** |
| * Returns false as image is not a container. |
| */ |
| public boolean isComposite() { |
| return false; |
| } |
| |
| // dynamic support |
| |
| /** |
| * This method is invoked during the build phase if the document |
| * is dynamic. The responsability of this method is to ensure that |
| * any dynamic modifications of the element this bridge is |
| * dedicated to, happen on its associated GVT product. |
| */ |
| protected void initializeDynamicSupport(BridgeContext ctx, |
| Element e, |
| GraphicsNode node) { |
| if (!ctx.isInteractive()) |
| return; |
| |
| // Bind the nodes for interactive and dynamic |
| // HACK due to the way images are represented in GVT |
| ctx.bind(e, node); |
| |
| if (ctx.isDynamic()) { |
| // Only do this for dynamic not interactive. |
| this.e = e; |
| this.node = node; |
| this.ctx = ctx; |
| ((SVGOMElement)e).setSVGContext(this); |
| } |
| } |
| |
| // BridgeUpdateHandler implementation ////////////////////////////////// |
| |
| /** |
| * Invoked when the animated value of an animatable attribute has changed. |
| */ |
| public void handleAnimatedAttributeChanged |
| (AnimatedLiveAttributeValue alav) { |
| try { |
| String ns = alav.getNamespaceURI(); |
| String ln = alav.getLocalName(); |
| if (ns == null) { |
| if (ln.equals(SVG_X_ATTRIBUTE) |
| || ln.equals(SVG_Y_ATTRIBUTE)) { |
| updateImageBounds(); |
| return; |
| } else if (ln.equals(SVG_WIDTH_ATTRIBUTE) |
| || ln.equals(SVG_HEIGHT_ATTRIBUTE)) { |
| SVGImageElement ie = (SVGImageElement) e; |
| ImageNode imageNode = (ImageNode) node; |
| AbstractSVGAnimatedLength _attr; |
| if (ln.charAt(0) == 'w') { |
| _attr = (AbstractSVGAnimatedLength) ie.getWidth(); |
| } else { |
| _attr = (AbstractSVGAnimatedLength) ie.getHeight(); |
| } |
| float val = _attr.getCheckedValue(); |
| if (val == 0 || imageNode.getImage() instanceof ShapeNode) { |
| rebuildImageNode(); |
| } else { |
| updateImageBounds(); |
| } |
| return; |
| } else if (ln.equals(SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE)) { |
| updateImageBounds(); |
| return; |
| } |
| } else if (ns.equals(XLINK_NAMESPACE_URI) |
| && ln.equals(XLINK_HREF_ATTRIBUTE)) { |
| rebuildImageNode(); |
| return; |
| } |
| } catch (LiveAttributeException ex) { |
| throw new BridgeException(ctx, ex); |
| } |
| super.handleAnimatedAttributeChanged(alav); |
| } |
| |
| protected void updateImageBounds() { |
| //retrieve the new bounds of the image tag |
| Rectangle2D bounds = getImageBounds(ctx, e); |
| GraphicsNode imageNode = ((ImageNode)node).getImage(); |
| float[] vb = null; |
| if (imageNode instanceof RasterImageNode) { |
| //Raster image |
| Rectangle2D imgBounds = |
| ((RasterImageNode)imageNode).getImageBounds(); |
| // create the implicit viewBox for the raster |
| // image. The viewBox for a raster image is the size |
| // of the image |
| vb = new float[4]; |
| vb[0] = 0; // x |
| vb[1] = 0; // y |
| vb[2] = (float)imgBounds.getWidth(); // width |
| vb[3] = (float)imgBounds.getHeight(); // height |
| } else { |
| if (imgDocument != null) { |
| Element svgElement = imgDocument.getRootElement(); |
| String viewBox = svgElement.getAttributeNS |
| (null, SVG_VIEW_BOX_ATTRIBUTE); |
| vb = ViewBox.parseViewBoxAttribute(e, viewBox, ctx); |
| } |
| } |
| if (imageNode != null) { |
| // handles the 'preserveAspectRatio', 'overflow' and |
| // 'clip' and sets the appropriate AffineTransform to |
| // the image node |
| initializeViewport(ctx, e, imageNode, vb, bounds); |
| } |
| |
| } |
| |
| protected void rebuildImageNode() { |
| // Reference copy of the imgDocument |
| if ((imgDocument != null) && (listener != null)) { |
| NodeEventTarget tgt = (NodeEventTarget)imgDocument.getRootElement(); |
| |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_CLICK, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYDOWN, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYPRESS, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYUP, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEDOWN, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEMOVE, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEOUT, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEOVER, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEUP, |
| listener, false); |
| listener = null; |
| } |
| |
| if (imgDocument != null) { |
| SVGSVGElement svgElement = imgDocument.getRootElement(); |
| disposeTree(svgElement); |
| } |
| |
| imgDocument = null; |
| subCtx = null; |
| |
| //update of the reference of the image. |
| GraphicsNode inode = buildImageGraphicsNode(ctx,e); |
| |
| ImageNode imgNode = (ImageNode)node; |
| imgNode.setImage(inode); |
| |
| if (inode == null) { |
| SVGImageElement ie = (SVGImageElement) e; |
| String uriStr = ie.getHref().getAnimVal(); |
| throw new BridgeException(ctx, e, ERR_URI_IMAGE_INVALID, |
| new Object[] {uriStr}); |
| } |
| } |
| |
| /** |
| * Invoked for each CSS property that has changed. |
| */ |
| protected void handleCSSPropertyChanged(int property) { |
| switch(property) { |
| case SVGCSSEngine.IMAGE_RENDERING_INDEX: |
| case SVGCSSEngine.COLOR_INTERPOLATION_INDEX: |
| RenderingHints hints = CSSUtilities.convertImageRendering(e, null); |
| hints = CSSUtilities.convertColorRendering(e, hints); |
| if (hints != null) { |
| node.setRenderingHints(hints); |
| } |
| break; |
| default: |
| super.handleCSSPropertyChanged(property); |
| } |
| } |
| |
| // convenient methods ////////////////////////////////////////////////// |
| |
| /** |
| * Returns a GraphicsNode that represents an raster image in JPEG or PNG |
| * format. |
| * |
| * @param ctx the bridge context |
| * @param e the image element |
| * @param img the image to use in creating the graphics node |
| */ |
| protected GraphicsNode createRasterImageNode(BridgeContext ctx, |
| Element e, |
| Filter img, |
| ParsedURL purl) { |
| Rectangle2D bounds = getImageBounds(ctx, e); |
| if ((bounds.getWidth() == 0) || (bounds.getHeight() == 0)) { |
| ShapeNode sn = new ShapeNode(); |
| sn.setShape(bounds); |
| return sn; |
| } |
| |
| if (BrokenLinkProvider.hasBrokenLinkProperty(img)) { |
| Object o=img.getProperty(BrokenLinkProvider.BROKEN_LINK_PROPERTY); |
| String msg = "unknown"; |
| if (o instanceof String) |
| msg = (String)o; |
| SVGDocument doc = ctx.getUserAgent().getBrokenLinkDocument |
| (e, purl.toString(), msg); |
| return createSVGImageNode(ctx, e, doc); |
| } |
| |
| RasterImageNode node = new RasterImageNode(); |
| node.setImage(img); |
| Rectangle2D imgBounds = img.getBounds2D(); |
| |
| // create the implicit viewBox for the raster image. The viewBox for a |
| // raster image is the size of the image |
| float [] vb = new float[4]; |
| vb[0] = 0; // x |
| vb[1] = 0; // y |
| vb[2] = (float)imgBounds.getWidth(); // width |
| vb[3] = (float)imgBounds.getHeight(); // height |
| |
| // handles the 'preserveAspectRatio', 'overflow' and 'clip' and sets the |
| // appropriate AffineTransform to the image node |
| initializeViewport(ctx, e, node, vb, bounds); |
| |
| return node; |
| } |
| |
| /** |
| * Returns a GraphicsNode that represents a svg document as an image. |
| * |
| * @param ctx the bridge context |
| * @param e the image element |
| * @param imgDocument the SVG document that represents the image |
| */ |
| protected GraphicsNode createSVGImageNode(BridgeContext ctx, |
| Element e, |
| SVGDocument imgDocument) { |
| CSSEngine eng = ((SVGOMDocument)imgDocument).getCSSEngine(); |
| subCtx = ctx.createSubBridgeContext((SVGOMDocument)imgDocument); |
| |
| CompositeGraphicsNode result = new CompositeGraphicsNode(); |
| // handles the 'preserveAspectRatio', 'overflow' and 'clip' and |
| // sets the appropriate AffineTransform to the image node |
| Rectangle2D bounds = getImageBounds(ctx, e); |
| |
| if ((bounds.getWidth() == 0) || (bounds.getHeight() == 0)) { |
| ShapeNode sn = new ShapeNode(); |
| sn.setShape(bounds); |
| result.getChildren().add(sn); |
| return result; |
| } |
| |
| Rectangle2D r = CSSUtilities.convertEnableBackground(e); |
| if (r != null) { |
| result.setBackgroundEnable(r); |
| } |
| |
| SVGSVGElement svgElement = imgDocument.getRootElement(); |
| CanvasGraphicsNode node; |
| node = (CanvasGraphicsNode)subCtx.getGVTBuilder().build |
| (subCtx, svgElement); |
| |
| if ((eng == null) && ctx.isInteractive()) { |
| // If we "created" this document then add listerns. |
| subCtx.addUIEventListeners(imgDocument); |
| } |
| |
| // HACK: remove the clip set by the SVGSVGElement as the overflow |
| // and clip properties must be ignored. The clip will be set later |
| // using the overflow and clip of the <image> element. |
| node.setClip(null); |
| // HACK: remove the viewingTransform set by the SVGSVGElement |
| // as the viewBox must be ignored. The viewingTransform will |
| // be set later using the width/height of the image element. |
| node.setViewingTransform(new AffineTransform()); |
| result.getChildren().add(node); |
| |
| // create the implicit viewBox for the SVG image. The viewBox for a |
| // SVG image is the viewBox of the outermost SVG element of the SVG file |
| // XXX Use animated value of 'viewBox' here? |
| String viewBox = |
| svgElement.getAttributeNS(null, SVG_VIEW_BOX_ATTRIBUTE); |
| float[] vb = ViewBox.parseViewBoxAttribute(e, viewBox, ctx); |
| |
| initializeViewport(ctx, e, result, vb, bounds); |
| |
| // add a listener on the outermost svg element of the SVG image. |
| // if an event occured inside the SVG image document, send it |
| // to the <image> element (inside the original document). |
| if (ctx.isInteractive()) { |
| listener = new ForwardEventListener(svgElement, e); |
| NodeEventTarget tgt = (NodeEventTarget)svgElement; |
| |
| tgt.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_CLICK, |
| listener, false, null); |
| subCtx.storeEventListenerNS |
| (tgt, XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_CLICK, |
| listener, false); |
| |
| tgt.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYDOWN, |
| listener, false, null); |
| subCtx.storeEventListenerNS |
| (tgt, XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYDOWN, |
| listener, false); |
| |
| tgt.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYPRESS, |
| listener, false, null); |
| subCtx.storeEventListenerNS |
| (tgt, XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYPRESS, |
| listener, false); |
| |
| tgt.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYUP, |
| listener, false, null); |
| subCtx.storeEventListenerNS |
| (tgt, XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYUP, |
| listener, false); |
| |
| tgt.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEDOWN, |
| listener, false, null); |
| subCtx.storeEventListenerNS |
| (tgt, XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEDOWN, |
| listener, false); |
| |
| tgt.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEMOVE, |
| listener, false, null); |
| subCtx.storeEventListenerNS |
| (tgt, XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEMOVE, |
| listener, false); |
| |
| tgt.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEOUT, |
| listener, false, null); |
| subCtx.storeEventListenerNS |
| (tgt, XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEOUT, |
| listener, false); |
| |
| tgt.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEOVER, |
| listener, false, null); |
| subCtx.storeEventListenerNS |
| (tgt, XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEOVER, |
| listener, false); |
| |
| tgt.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEUP, |
| listener, false, null); |
| subCtx.storeEventListenerNS |
| (tgt, XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEUP, |
| listener, false); |
| } |
| |
| return result; |
| } |
| |
| public void dispose() { |
| if ((imgDocument != null) && (listener != null)) { |
| NodeEventTarget tgt = (NodeEventTarget)imgDocument.getRootElement(); |
| |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_CLICK, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYDOWN, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYPRESS, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_KEYUP, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEDOWN, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEMOVE, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEOUT, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEOVER, |
| listener, false); |
| tgt.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, SVG_EVENT_MOUSEUP, |
| listener, false); |
| listener = null; |
| } |
| |
| if (imgDocument != null) { |
| SVGSVGElement svgElement = imgDocument.getRootElement(); |
| disposeTree(svgElement); |
| imgDocument = null; |
| subCtx = null; |
| } |
| super.dispose(); |
| |
| } |
| /** |
| * A simple DOM listener to forward events from the SVG image document to |
| * the original document. |
| */ |
| protected static class ForwardEventListener implements EventListener { |
| |
| /** |
| * The root element of the SVG image. |
| */ |
| protected Element svgElement; |
| |
| /** |
| * The image element. |
| */ |
| protected Element imgElement; |
| |
| /** |
| * Constructs a new <code>ForwardEventListener</code> |
| */ |
| public ForwardEventListener(Element svgElement, Element imgElement) { |
| this.svgElement = svgElement; |
| this.imgElement = imgElement; |
| } |
| |
| public void handleEvent(Event e) { |
| DOMMouseEvent evt = (DOMMouseEvent) e; |
| DOMMouseEvent newMouseEvent = (DOMMouseEvent) |
| // DOM Level 2 6.5 cast from Document to DocumentEvent is ok |
| ((DocumentEvent)imgElement.getOwnerDocument()).createEvent("MouseEvents"); |
| |
| newMouseEvent.initMouseEventNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| evt.getType(), |
| evt.getBubbles(), |
| evt.getCancelable(), |
| evt.getView(), |
| evt.getDetail(), |
| evt.getScreenX(), |
| evt.getScreenY(), |
| evt.getClientX(), |
| evt.getClientY(), |
| evt.getButton(), |
| (EventTarget)imgElement, |
| evt.getModifiersString()); |
| ((EventTarget)imgElement).dispatchEvent(newMouseEvent); |
| } |
| } |
| |
| /** |
| * Initializes according to the specified element, the specified graphics |
| * node with the specified bounds. This method takes into account the |
| * 'viewBox', 'preserveAspectRatio', and 'clip' properties. According to |
| * those properties, a AffineTransform and a clip is set. |
| * |
| * @param ctx the bridge context |
| * @param e the image element that defines the properties |
| * @param node the graphics node |
| * @param vb the implicit viewBox definition |
| * @param bounds the bounds of the image element |
| */ |
| protected static void initializeViewport(BridgeContext ctx, |
| Element e, |
| GraphicsNode node, |
| float[] vb, |
| Rectangle2D bounds) { |
| |
| float x = (float)bounds.getX(); |
| float y = (float)bounds.getY(); |
| float w = (float)bounds.getWidth(); |
| float h = (float)bounds.getHeight(); |
| |
| try { |
| SVGImageElement ie = (SVGImageElement) e; |
| SVGOMAnimatedPreserveAspectRatio _par = |
| (SVGOMAnimatedPreserveAspectRatio) ie.getPreserveAspectRatio(); |
| _par.check(); |
| |
| AffineTransform at = ViewBox.getPreserveAspectRatioTransform |
| (e, vb, w, h, _par, ctx); |
| at.preConcatenate(AffineTransform.getTranslateInstance(x, y)); |
| node.setTransform(at); |
| |
| // 'overflow' and 'clip' |
| Shape clip = null; |
| if (CSSUtilities.convertOverflow(e)) { // overflow:hidden |
| float [] offsets = CSSUtilities.convertClip(e); |
| if (offsets == null) { // clip:auto |
| clip = new Rectangle2D.Float(x, y, w, h); |
| } else { // clip:rect(<x> <y> <w> <h>) |
| // offsets[0] = top |
| // offsets[1] = right |
| // offsets[2] = bottom |
| // offsets[3] = left |
| clip = new Rectangle2D.Float(x+offsets[3], |
| y+offsets[0], |
| w-offsets[1]-offsets[3], |
| h-offsets[2]-offsets[0]); |
| } |
| } |
| |
| if (clip != null) { |
| try { |
| at = at.createInverse(); // clip in user space |
| Filter filter = node.getGraphicsNodeRable(true); |
| clip = at.createTransformedShape(clip); |
| node.setClip(new ClipRable8Bit(filter, clip)); |
| } catch (java.awt.geom.NoninvertibleTransformException ex) {} |
| } |
| } catch (LiveAttributeException ex) { |
| throw new BridgeException(ctx, ex); |
| } |
| } |
| |
| /** |
| * Analyzes the color-profile property and builds an ICCColorSpaceExt |
| * object from it. |
| * |
| * @param element the element with the color-profile property |
| * @param ctx the bridge context |
| */ |
| protected static ICCColorSpaceWithIntent extractColorSpace(Element element, |
| BridgeContext ctx) { |
| |
| String colorProfileProperty = CSSUtilities.getComputedStyle |
| (element, SVGCSSEngine.COLOR_PROFILE_INDEX).getStringValue(); |
| |
| // The only cases that need special handling are 'sRGB' and 'name' |
| ICCColorSpaceWithIntent colorSpace = null; |
| if (CSS_SRGB_VALUE.equalsIgnoreCase(colorProfileProperty)) { |
| |
| colorSpace = new ICCColorSpaceWithIntent |
| (ICC_Profile.getInstance(ColorSpace.CS_sRGB), |
| RenderingIntent.AUTO, "sRGB", null); |
| |
| } else if (!CSS_AUTO_VALUE.equalsIgnoreCase(colorProfileProperty) |
| && !"".equalsIgnoreCase(colorProfileProperty)){ |
| |
| // The value is neither 'sRGB' nor 'auto': it is a profile name. |
| SVGColorProfileElementBridge profileBridge = |
| (SVGColorProfileElementBridge) ctx.getBridge |
| (SVG_NAMESPACE_URI, SVG_COLOR_PROFILE_TAG); |
| if (profileBridge != null) { |
| colorSpace = profileBridge.createICCColorSpaceWithIntent |
| (ctx, element, colorProfileProperty); |
| |
| } |
| } |
| return colorSpace; |
| } |
| |
| /** |
| * Returns the bounds of the specified image element. |
| * |
| * @param ctx the bridge context |
| * @param element the image element |
| */ |
| protected static Rectangle2D getImageBounds(BridgeContext ctx, |
| Element element) { |
| try { |
| SVGImageElement ie = (SVGImageElement) element; |
| |
| // 'x' attribute - default is 0 |
| AbstractSVGAnimatedLength _x = |
| (AbstractSVGAnimatedLength) ie.getX(); |
| float x = _x.getCheckedValue(); |
| |
| // 'y' attribute - default is 0 |
| AbstractSVGAnimatedLength _y = |
| (AbstractSVGAnimatedLength) ie.getY(); |
| float y = _y.getCheckedValue(); |
| |
| // 'width' attribute - required |
| AbstractSVGAnimatedLength _width = |
| (AbstractSVGAnimatedLength) ie.getWidth(); |
| float w = _width.getCheckedValue(); |
| |
| // 'height' attribute - required |
| AbstractSVGAnimatedLength _height = |
| (AbstractSVGAnimatedLength) ie.getHeight(); |
| float h = _height.getCheckedValue(); |
| |
| return new Rectangle2D.Float(x, y, w, h); |
| } catch (LiveAttributeException ex) { |
| throw new BridgeException(ctx, ex); |
| } |
| } |
| |
| GraphicsNode createBrokenImageNode |
| (BridgeContext ctx, Element e, String uri, String message) { |
| SVGDocument doc = ctx.getUserAgent().getBrokenLinkDocument |
| (e, uri, Messages.formatMessage(URI_IMAGE_ERROR, |
| new Object[] { message } )); |
| return createSVGImageNode(ctx, e, doc); |
| } |
| |
| |
| static SVGBrokenLinkProvider brokenLinkProvider |
| = new SVGBrokenLinkProvider(); |
| static { |
| ImageTagRegistry.setBrokenLinkProvider(brokenLinkProvider); |
| } |
| } |