BatikでSVG
Batik SVG Toolkitは、JavaでSVGを表示・処理するためのライブラリ。
とりあえず、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:'Dialog'; 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:'Dialog'; 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が表示された。