/** * Copyright (c) 2008 Borland Software Corporation * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Dmitry Stadnik - initial API and implementation */ package org.eclipse.gmf.runtime.lite.svg; import java.awt.RenderingHints; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.io.IOException; import java.util.WeakHashMap; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.dom.svg.SAXSVGDocumentFactory; import org.apache.batik.util.XMLResourceDescriptor; import org.eclipse.draw2d.Figure; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.gmf.internal.runtime.lite.svg.Activator; import org.eclipse.gmf.internal.runtime.lite.svg.InferringNamespaceContext; import org.eclipse.gmf.internal.runtime.lite.svg.SimpleImageTranscoder; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.widgets.Display; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; public class SVGFigure extends Figure { private String uri; private boolean failedToLoadDocument, specifyCanvasWidth = true, specifyCanvasHeight = true; private SimpleImageTranscoder transcoder; private static WeakHashMap<String, Document> documentsMap = new WeakHashMap<String, Document>(); public final String getURI() { return uri; } public final void setURI(String uri) { setURI(uri, true); } public void setURI(String uri, boolean loadOnDemand) { this.uri = uri; transcoder = null; failedToLoadDocument = false; if (loadOnDemand) { loadDocument(); } } private void loadDocument() { transcoder = null; failedToLoadDocument = true; if (uri == null) { return; } String parser = XMLResourceDescriptor.getXMLParserClassName(); SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(parser); try { Document document; if (documentsMap.containsKey(uri)) document = documentsMap.get(uri); else { document = factory.createDocument(uri); documentsMap.put(uri, document); } transcoder = new SimpleImageTranscoder(document); failedToLoadDocument = false; } catch (IOException e) { Activator.logError("Error loading SVG file", e); } } protected final Document getDocument() { if (failedToLoadDocument) { return null; } if (transcoder == null) { loadDocument(); } return transcoder == null ? null : transcoder.getDocument(); } /** * Returns true if document was loaded without errors; tries to load document if needed. */ public final boolean checkContentAvailable() { return getDocument() != null; } private XPath getXPath() { XPath xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(new InferringNamespaceContext(getDocument().getDocumentElement())); return xpath; } /** * Executes XPath query over the SVG document. */ protected final NodeList getNodes(String query) { Document document = getDocument(); if (document != null) { try { return (NodeList) getXPath().evaluate(query, document, XPathConstants.NODESET); } catch (XPathExpressionException e) { throw new RuntimeException(e); } } return null; } /** * Reads color value from the document. */ protected Color getColor(Element element, String attributeName) { if (getDocument() == null || getDocument() != element.getOwnerDocument()) { return null; } Color color = null; // Make sure that CSSEngine is available. BridgeContext ctx = transcoder.initCSSEngine(); try { color = SVGUtils.toSWTColor(element, attributeName); } finally { if (ctx != null) { ctx.dispose(); } } return color; } @Override protected void paintFigure(Graphics graphics) { super.paintFigure(graphics); Document document = getDocument(); if (document == null) { return; } Image image = null; try { Rectangle r = getClientArea(); transcoder.setCanvasSize(specifyCanvasWidth ? r.width : -1, specifyCanvasHeight ? r.height : -1); updateRenderingHints(graphics); BufferedImage awtImage = transcoder.getBufferedImage(); if (awtImage != null) { image = toSWT(Display.getCurrent(), awtImage); graphics.drawImage(image, r.x, r.y); } } finally { if (image != null) { image.dispose(); } } } private void updateRenderingHints(Graphics graphics) { { int aa = SWT.DEFAULT; try { aa = graphics.getAntialias(); } catch (Exception e) { // not supported } Object aaHint; if (aa == SWT.ON) { aaHint = RenderingHints.VALUE_ANTIALIAS_ON; } else if (aa == SWT.OFF) { aaHint = RenderingHints.VALUE_ANTIALIAS_OFF; } else { aaHint = RenderingHints.VALUE_ANTIALIAS_DEFAULT; } if (transcoder.getRenderingHints().get(RenderingHints.KEY_ANTIALIASING) != aaHint) { transcoder.getRenderingHints().put(RenderingHints.KEY_ANTIALIASING, aaHint); transcoder.contentChanged(); } } { int aa = SWT.DEFAULT; try { aa = graphics.getTextAntialias(); } catch (Exception e) { // not supported } Object aaHint; if (aa == SWT.ON) { aaHint = RenderingHints.VALUE_TEXT_ANTIALIAS_ON; } else if (aa == SWT.OFF) { aaHint = RenderingHints.VALUE_TEXT_ANTIALIAS_OFF; } else { aaHint = RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT; } if (transcoder.getRenderingHints().get(RenderingHints.KEY_TEXT_ANTIALIASING) != aaHint) { transcoder.getRenderingHints().put(RenderingHints.KEY_TEXT_ANTIALIASING, aaHint); transcoder.contentChanged(); } } } /** * Converts an AWT based buffered image into an SWT <code>Image</code>. This will always return an <code>Image</code> that * has 24 bit depth regardless of the type of AWT buffered image that is passed into the method. * * @param awtImage the {@link java.awt.image.BufferedImage} to be converted to an <code>Image</code> * @return an <code>Image</code> that represents the same image data as the AWT <code>BufferedImage</code> type. */ private static org.eclipse.swt.graphics.Image toSWT(Device device, BufferedImage awtImage) { // We can force bitdepth to be 24 bit because BufferedImage getRGB // allows us to always retrieve 24 bit data regardless of source color depth. PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF); ImageData swtImageData = new ImageData(awtImage.getWidth(), awtImage.getHeight(), 24, palette); // Ensure scansize is aligned on 32 bit. int scansize = (((awtImage.getWidth() * 3) + 3) * 4) / 4; WritableRaster alphaRaster = awtImage.getAlphaRaster(); byte[] alphaBytes = new byte[awtImage.getWidth()]; for (int y = 0; y < awtImage.getHeight(); y++) { int[] buff = awtImage.getRGB(0, y, awtImage.getWidth(), 1, null, 0, scansize); swtImageData.setPixels(0, y, awtImage.getWidth(), buff, 0); if (alphaRaster != null) { int[] alpha = alphaRaster.getPixels(0, y, awtImage.getWidth(), 1, (int[]) null); for (int i = 0; i < awtImage.getWidth(); i++) { alphaBytes[i] = (byte) alpha[i]; } swtImageData.setAlphas(0, y, awtImage.getWidth(), alphaBytes, 0); } } return new org.eclipse.swt.graphics.Image(device, swtImageData); } public final Rectangle2D getAreaOfInterest() { getDocument(); return transcoder == null ? null : transcoder.getCanvasAreaOfInterest(); } public void setAreaOfInterest(Rectangle2D value) { getDocument(); if (transcoder != null) { transcoder.setCanvasAreaOfInterest(value); } repaint(); } public final boolean isSpecifyCanvasWidth() { return specifyCanvasWidth; } public void setSpecifyCanvasWidth(boolean specifyCanvasWidth) { this.specifyCanvasWidth = specifyCanvasWidth; contentChanged(); } public final boolean isSpecifyCanvasHeight() { return specifyCanvasHeight; } public void setSpecifyCanvasHeight(boolean specifyCanvasHeight) { this.specifyCanvasHeight = specifyCanvasHeight; contentChanged(); } /** * Should be called when SVG document has been changed. It will be re-rendered and figure will be repainted. */ public void contentChanged() { getDocument(); if (transcoder != null) { transcoder.contentChanged(); } repaint(); } }