BatikでSVG

 Batik SVG Toolkitは、JavaSVGを表示・処理するためのライブラリ。
 とりあえず、SVGビュアーを書いてみる。

 ■準備
 Batik SVG Toolkitからライブラリをダウンロードし、クラスパスを設定する。

 ■ソースコード
 とりあえず、こんな感じで書いてみた。

package test;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.print.PrinterException;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.Result;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
import org.apache.batik.dom.util.DOMUtilities;
import org.apache.batik.svggen.DOMTreeManager;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.svggen.SVGGraphics2DIOException;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.JSVGScrollPane;
import org.apache.batik.swing.gvt.AbstractPanInteractor;
import org.apache.batik.swing.gvt.AbstractZoomInteractor;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.apache.batik.swing.gvt.GVTTreeRendererListener;
import org.apache.batik.swing.gvt.Interactor;
import org.apache.batik.swing.svg.GVTTreeBuilderEvent;
import org.apache.batik.swing.svg.GVTTreeBuilderListener;
import org.apache.batik.swing.svg.SVGDocumentLoaderEvent;
import org.apache.batik.swing.svg.SVGDocumentLoaderListener;
import org.apache.batik.swing.svg.SVGUserAgent;
import org.apache.batik.swing.svg.SVGUserAgentAdapter;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.print.PrintTranscoder;
import org.apache.batik.util.XMLResourceDescriptor;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.svg.SVGDocument;


public class TestSVGCanvas extends JSVGCanvas {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private Timer timer;
	public static final String PRINT_OUT_ACTION="Print";
	private boolean enableButton1PanInteractor=false;
	private boolean enableButton1ZoomInteractor=false;
	
	protected Interactor panInteractor2 = new AbstractPanInteractor() {
		
    	public boolean startInteraction(InputEvent ie) {
    		int mods = ie.getModifiers();
            return
                ie.getID() == MouseEvent.MOUSE_PRESSED &&
                (mods & InputEvent.BUTTON1_MASK) != 0;
    	}
	};
	
    protected Interactor zoomInteractor2 = new AbstractZoomInteractor() {
        public boolean startInteraction(InputEvent ie) {
            int mods = ie.getModifiers();
            return
            ie.getID() == MouseEvent.MOUSE_PRESSED &&
            (mods & InputEvent.BUTTON1_MASK) != 0;
        }
    };
	
	public TestSVGCanvas() {
		super();
		init();
	}
	
	public TestSVGCanvas(SVGUserAgent arg0, boolean arg1, boolean arg2) {
		super(arg0, arg1, arg2);
		init();
	}

	public DOMTreeManager getDOMTreeManager(){
		SVGGraphics2D s2g=(SVGGraphics2D )this.getGraphics();
		return s2g.getDOMTreeManager();
	}
	
	/*
	 * zoom: BUTTON1 + CTRL Key
	 * realtime zoom:BUTTON3 + SHIFT Key
	 * translation:BUTTON1 + SHIFT Key
	 * rotation:BUTTON3 + CTRL Key
	 * reset:CTRL+SHIFT+BUTTON3
	 * 
	 */
	private void init(){
		timer=new Timer();
		super.addMouseWheelListener(new WheelZooming());
		SVGProgressDialog spd=new SVGProgressDialog(new JFrame());
		addSVGDocumentLoaderListener(spd);
		addGVTTreeBuilderListener(spd);
		addGVTTreeRendererListener(spd);
		getActionMap().put(PRINT_OUT_ACTION, new PrintAction());
	}
	
	
	
	public void saveSVGDocument(File f){
		try {
			SVGDocument svg=getSVGDocument();
			if(svg==null)throw new RuntimeException("SVGDocument is null.");
			DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();	
			Document doc0 = docBuilder.newDocument();
			DOMImplementation domImpl =doc0.getImplementation();
			Document doc=DOMUtilities.deepCloneDocument(svg, domImpl);
			TransformerFactory  factory = TransformerFactory.newInstance();
			Transformer transformer = factory.newTransformer();
			Source source = new DOMSource(doc);
					Result result = new StreamResult(f.toURI().toString());
			        transformer.transform(source, result);
	    }catch(TransformerFactoryConfigurationError e){
	    }catch(TransformerConfigurationException e){
	    }catch(TransformerException e) {
	    }catch(Exception e){
	    }
	}
	
	public void resetTransform(){
		this.getActionMap().get(JSVGCanvas.RESET_TRANSFORM_ACTION).actionPerformed(
			new ActionEvent(this,0,JSVGCanvas.RESET_TRANSFORM_ACTION)
		);
	}
	
	private class WheelZooming implements MouseWheelListener{
		boolean isFinished=true;
		TimerTask task;

		void updateWheelTransform(){
			if(!isFinished){
		        AffineTransform pt =  getPaintingTransform();
		        if (pt != null) {
		            AffineTransform rt = (AffineTransform)getRenderingTransform().clone();
		            rt.preConcatenate(pt);
		            setRenderingTransform(rt);
		        }
		        isFinished =true;
		        task=null;
			}
		}
		
		void taskStart(){
			if(task!=null)task.cancel();
			task=new TimerTask(){
				@Override
				public void run() {
					updateWheelTransform();
				}
			};
			timer.schedule(task, 500);
		}
		
		public void mouseWheelMoved(MouseWheelEvent arg0) {
			try{
				if(arg0.getModifiers()==MouseWheelEvent.SHIFT_MASK)return;
				Rectangle rec=getBounds();
				double cx=rec.getCenterX();
				double cy=rec.getCenterY();
	            AffineTransform at = AffineTransform.getTranslateInstance(cx,cy);
	            if(arg0.getWheelRotation()>0){
	            	if((at.getScaleX()*1.05)<Double.MAX_VALUE){
	    				at.scale(1.05, 1.05);
	    				at.translate(-cx,-cy);
	            	}
				}else{
	            	if((at.getScaleX()*0.95)>Double.MIN_VALUE){
	            		at.scale(0.95, 0.95);
	            		at.translate(-cx,-cy);
	            	}
				}
				if(isFinished){
					setPaintingTransform(null);
					isFinished=false;
				}else{
					at.concatenate(getPaintingTransform());
				}
				setPaintingTransform(at);
				taskStart();
			}catch(NullPointerException ne){}
		}
	}
	
	private class SVGProgressDialog extends JDialog implements
	SVGDocumentLoaderListener,GVTTreeBuilderListener,GVTTreeRendererListener {

		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		private JLabel label;
		private boolean isRend;
		private boolean isLoading;
		private AnimeIcon icon;

		private SVGProgressDialog(JFrame arg0) throws HeadlessException {
			super(arg0,false);
			super.setTitle("Progress");
			super.setAlwaysOnTop(true);
			super.getContentPane().setLayout(new FlowLayout());
			super.setResizable(false);
			label=new JLabel("");
			label.setHorizontalAlignment(JLabel.CENTER);
			icon=new AnimeIcon();
			super.getContentPane().add(icon);
			super.getContentPane().add(label);
			super.setSize(120,60);
		}

		public void gvtRenderingCancelled(GVTTreeRendererEvent arg0) {}

		public void gvtRenderingCompleted(GVTTreeRendererEvent arg0) {
			isRend=false;
			label.setText("Rendering Done.");
		    super.setVisible(false);
			isLoading=false;
			icon.animationStop();
		}

		public void gvtRenderingFailed(GVTTreeRendererEvent arg0) {}

		public void gvtRenderingPrepare(GVTTreeRendererEvent arg0) {
			isRend=true;
			TimerTask task=new TimerTask(){
				@Override
				public void run() {
					if(isRend){
						label.setText("Now Rendering...");
				        setLocationRelativeTo(getParent());
				        if(isRend)setVisible(true);
				        icon.animationStart();
					}
				}
			};
			if(isLoading){
				timer.schedule(task,0);
			}else{
				timer.schedule(task,1000);
			}
		}

		public void gvtRenderingStarted(GVTTreeRendererEvent arg0) {}
		public void gvtBuildCancelled(GVTTreeBuilderEvent arg0) {}

		public void gvtBuildCompleted(GVTTreeBuilderEvent arg0) {
		    label.setText("Building Done.");
		    super.setVisible(false);
			icon.animationStop();
		}

		public void gvtBuildFailed(GVTTreeBuilderEvent arg0) {}

		public void gvtBuildStarted(GVTTreeBuilderEvent arg0) {
		    label.setText("Now Building...");
		    super.setLocationRelativeTo(this.getParent());
		    super.setVisible(true);
		    icon.animationStart();
		}

		public void documentLoadingStarted(SVGDocumentLoaderEvent arg0) {
			isLoading=true;
			label.setText("Now Loading...");
		    super.setLocationRelativeTo(this.getParent());
		    super.setVisible(true);
		    icon.animationStart();
		}

		public void documentLoadingCompleted(SVGDocumentLoaderEvent arg0) {
		    label.setText("Document Loaded.");
		    super.setVisible(false);
		    icon.animationStop();
		}

		public void documentLoadingCancelled(SVGDocumentLoaderEvent arg0) {}
		public void documentLoadingFailed(SVGDocumentLoaderEvent arg0) {}
	}
	
	public class PrintAction extends AbstractAction {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		public PrintAction() {}
		public void actionPerformed(ActionEvent e) {
			if (svgDocument != null) {
				final SVGDocument doc = svgDocument;
				new Thread() {
					public void run(){
						String uri = doc.getURL();
						String fragment = getFragmentIdentifier();
						if (fragment != null) {
							uri += "#"+fragment;
						}
						PrintTranscoder pt = new PrintTranscoder();
						pt.addTranscodingHint(PrintTranscoder.KEY_XML_PARSER_CLASSNAME,
								XMLResourceDescriptor.getXMLParserClassName());
						pt.addTranscodingHint(PrintTranscoder.KEY_SHOW_PAGE_DIALOG,
								Boolean.TRUE);
						pt.addTranscodingHint(PrintTranscoder.KEY_SHOW_PRINTER_DIALOG,
								Boolean.TRUE);
						pt.transcode(new TranscoderInput(uri), null);
						try {
							pt.print();
						} catch (PrinterException ex) {
							userAgent.displayError(ex);
						}
					}
				}.start();
			}
		}
	}

	public boolean isEnableButton1PanInteractor() {
		return enableButton1PanInteractor;
	}

	public boolean isEnableButton1ZoomInteractor() {
		return enableButton1ZoomInteractor;
	}
	
	@SuppressWarnings(value="all")
	public void setEnableButton1PanInteractor(boolean enableButton1PanInteractor) {
		if(this.enableButton1PanInteractor!=enableButton1PanInteractor){
			this.enableButton1PanInteractor=enableButton1PanInteractor;
			if(this.enableButton1PanInteractor){
				if(enableButton1ZoomInteractor)setEnableButton1ZoomInteractor(false);
				this.getInteractors().add(this.panInteractor2);
			}else{
				this.getInteractors().remove(this.panInteractor2);
			}
		}
	}
	
	@SuppressWarnings(value="all")
	public void setEnableButton1ZoomInteractor(boolean enableButton1ZoomInteractor) {
		if(this.enableButton1ZoomInteractor!=enableButton1ZoomInteractor){
			this.enableButton1ZoomInteractor=enableButton1ZoomInteractor;
			if(this.enableButton1ZoomInteractor){
				if(enableButton1PanInteractor)setEnableButton1PanInteractor(false);
				this.getInteractors().add(this.zoomInteractor2);
			}else{
				this.getInteractors().remove(this.zoomInteractor2);
			}
		}
	}
	
	public void initCanvas(String w,String h,String v){
		StringBuffer buf=new StringBuffer();
		buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		buf.append("<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.0//EN' 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd'>");
		buf.append("<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
		buf.append(" style=\"fill-opacity:1; color-rendering:auto; color-interpolation:auto; text-rendering:auto; stroke:black; stroke-linecap:square; stroke-miterlimit:10; shape-rendering:auto; stroke-opacity:1; fill:black; stroke-dasharray:none; font-weight:normal; stroke-width:1; font-family:&apos;Dialog&apos;; font-style:normal; stroke-linejoin:miter; font-size:12; stroke-dashoffset:0; image-rendering:auto;\"");
		buf.append(" xmlns=\"http://www.w3.org/2000/svg\"");
		buf.append(" xml:space=\"preserve\"");
		buf.append(" width=\""+w+"\"");
		buf.append(" height=\""+h+"\"");
		buf.append(" viewBox=\""+v+"\"></svg>");
		String svg=buf.toString();
		String parser = XMLResourceDescriptor.getXMLParserClassName();
    	try {
    		ByteArrayOutputStream bs=new ByteArrayOutputStream();
    		BufferedOutputStream bos = new BufferedOutputStream(bs);
       	    Writer out = new OutputStreamWriter(bos, "UTF-8");
       	    out.write(svg);
       	    out.close();
    	    ByteArrayInputStream bis=new ByteArrayInputStream(bs.toByteArray());
    		InputStreamReader isr = new InputStreamReader (bis);
     	    SAXSVGDocumentFactory factory=new SAXSVGDocumentFactory(parser);
    	    SVGDocument doc=factory.createSVGDocument(null,isr);
   	    	setSVGDocument(doc);
    	} catch (UnsupportedEncodingException ue){
    		ue.printStackTrace();
    	} catch (SVGGraphics2DIOException se){
    		se.printStackTrace();
    	} catch (IOException ioe){
    		ioe.printStackTrace();
    	}
	}
	
	public void initCanvas(Rectangle2D rec){
		StringBuffer buf=new StringBuffer();
		buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		buf.append("<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.0//EN' 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd'>");
		buf.append("<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
		buf.append(" style=\"fill-opacity:1; color-rendering:auto; color-interpolation:auto; text-rendering:auto; stroke:black; stroke-linecap:square; stroke-miterlimit:10; shape-rendering:auto; stroke-opacity:1; fill:black; stroke-dasharray:none; font-weight:normal; stroke-width:1; font-family:&apos;Dialog&apos;; font-style:normal; stroke-linejoin:miter; font-size:12; stroke-dashoffset:0; image-rendering:auto;\"");
		buf.append(" xmlns=\"http://www.w3.org/2000/svg\"");
		buf.append(" xml:space=\"preserve\"");
		buf.append(" width=\""+Integer.toString((int)rec.getWidth())+"\"");
		buf.append(" height=\""+Integer.toString((int)rec.getHeight())+"\"");
		buf.append(" viewBox=\""+Integer.toString((int)rec.getX())+" "+Integer.toString((int)rec.getY())+" "+
					Integer.toString((int)rec.getWidth())+" "+Integer.toString((int)rec.getHeight())+"\"></svg>");
		String svg=buf.toString();
		String parser = XMLResourceDescriptor.getXMLParserClassName();
    	try {
    		ByteArrayOutputStream bs=new ByteArrayOutputStream();
    		BufferedOutputStream bos = new BufferedOutputStream(bs);
       	    Writer out = new OutputStreamWriter(bos, "UTF-8");
       	    out.write(svg);
       	    out.close();
    	    ByteArrayInputStream bis=new ByteArrayInputStream(bs.toByteArray());
    		InputStreamReader isr = new InputStreamReader (bis);
     	    SAXSVGDocumentFactory factory=new SAXSVGDocumentFactory(parser);
    	    SVGDocument doc=factory.createSVGDocument(null,isr);
   	    	setSVGDocument(doc);
    	} catch (UnsupportedEncodingException ue){
    		ue.printStackTrace();
    	} catch (SVGGraphics2DIOException se){
    		se.printStackTrace();
    	} catch (IOException ioe){
    		ioe.printStackTrace();
    	}
	}

	private class AnimeIcon extends JComponent implements Icon,ActionListener {
	    /**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		private boolean flag = false;
	    private final javax.swing.Timer animator;
	    private final Vector<Shape> list = new Vector<Shape>();
	    
	    public void animationStart() {
	        flag = true;
	        animator.start();
	    }
	    
	    public void animationStop() {
	        flag = false;
	        animator.stop();
	    }
	    
	    public AnimeIcon() {
	        super();
	        animator = new javax.swing.Timer(100, this);
	        double r  = 2.0d;
	        double sx = 1.0d;
	        double sy = 1.0d;
	        list.addElement(new Ellipse2D.Double(sx+3*r, sy+0*r, 2*r, 2*r));
	        list.addElement(new Ellipse2D.Double(sx+5*r, sy+1*r, 2*r, 2*r));
	        list.addElement(new Ellipse2D.Double(sx+6*r, sy+3*r, 2*r, 2*r));
	        list.addElement(new Ellipse2D.Double(sx+5*r, sy+5*r, 2*r, 2*r));
	        list.addElement(new Ellipse2D.Double(sx+3*r, sy+6*r, 2*r, 2*r));
	        list.addElement(new Ellipse2D.Double(sx+1*r, sy+5*r, 2*r, 2*r));
	        list.addElement(new Ellipse2D.Double(sx+0*r, sy+3*r, 2*r, 2*r));
	        list.addElement(new Ellipse2D.Double(sx+1*r, sy+1*r, 2*r, 2*r));
	        int iw = (int)(r*8)+(int)(sx*2);
	        int ih = (int)(r*8)+(int)(sy*2);
	        setPreferredSize(new Dimension(iw, ih));
	    }
	    
	    public boolean isRunning(){
	    	return flag;
	    }
	    
	    public void paintComponent(Graphics g) {
	        Graphics2D g2d = (Graphics2D) g;
	        g2d.setColor(getBackground());
	        g2d.fillRect(0, 0, getWidth(), getHeight());
	        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
	        Iterator<Shape> it = list.iterator();
	        if(flag) {
	            float alpha = 0.1f;
	            while(it.hasNext()) {
	                g2d.setColor(new Color(0.5f,0.5f,0.5f,alpha));
	                g2d.fill((Shape)it.next());
	                alpha = alpha+0.1f;
	            }
	        }else{
	            while(it.hasNext()) {
	                g2d.setColor(new Color(0.6f,0.6f,0.6f));
	                g2d.fill((Shape)it.next());
	            }
	        }
	    }
	    
	    public void actionPerformed(ActionEvent e) {
	        list.addElement(list.remove(0));
	        repaint();
	    }


		public int getIconHeight() {
			return 16;
		}

		public int getIconWidth() {
			return 16;
		}

		public void paintIcon(Component arg0, Graphics arg1, int arg2, int arg3) {
			SwingUtilities.paintComponent(arg1, this, (Container)arg0, new Rectangle(arg2,arg3,16,16));
		}
	   
	}
	
	public static void main(String[] args){
		JFrame f=new JFrame();
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		f.getContentPane().setLayout(new BorderLayout());
		final TestSVGCanvas canvas=new TestSVGCanvas(new SVGUserAgentAdapter(),true,true);
		JSVGScrollPane sp=new JSVGScrollPane(canvas);
		sp.setBorder(BorderFactory.createLoweredBevelBorder());
		f.getContentPane().add(sp,BorderLayout.CENTER);
		JMenuBar menu=new JMenuBar();
		JMenu file=new JMenu("File");
		file.setMnemonic('F');
		menu.add(file);
		f.setJMenuBar(menu);
		JMenuItem open=new JMenuItem("Open");
		open.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent arg0) {
				JFileChooser chooser=new JFileChooser();
				int ret = chooser.showDialog(null, "ファイル選択");
				if (ret != JFileChooser.APPROVE_OPTION) {return;}
				File f=chooser.getSelectedFile();
				try{
					canvas.setURI(f.toURI().toURL().toExternalForm());
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		});
		file.add(open);
		JMenuItem out=new JMenuItem("Output");
		out.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent arg0) {
				JFileChooser chooser=new JFileChooser();
				int ret = chooser.showDialog(null, "ファイル選択");
				if (ret != JFileChooser.APPROVE_OPTION) {return;}
				File f=chooser.getSelectedFile();
				canvas.saveSVGDocument(f);
			}
		});
		file.add(out);
		f.setSize(800,600);
		f.setVisible(true);
	}
}

 ■結果
 とりあえず、SVGが表示された。