/** * * This is a small experimental class for converting between * SVG and OpenDocument .odg files. This code is not intended * to be a permanent solution for SVG-to-ODG conversion. Rather, * it is a quick-and-easy test bed for ideas which will be later * recoded into C++. * * --------------------------------------------------------------------- * * SvgOdg - A program to experiment with conversions between SVG and ODG * Copyright (C) 2006 Bob Jamison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * For more information, please write to rwjj@earthlink.net * */ /** * */ public class SvgOdg { /** * Namespace declarations */ public static final String SVG_NS = "http://www.w3.org/2000/svg"; public static final String XLINK_NS = "http://www.w3.org/1999/xlink"; public static final String ODF_NS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; public static final String ODG_NS = "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"; public static final String ODSVG_NS = "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"; DecimalFormat nrfmt; //static final double pxToCm = 0.0339; static final double pxToCm = 0.0275; static final double piToRad = 0.0174532925; BufferedWriter out; BufferedReader in; int imageNr; int styleNr; //######################################################################## //# M E S S A G E S //######################################################################## /** * */ void err(String msg) { System.out.println("SvgOdg ERROR:" + msg); } /** * */ void trace(String msg) { System.out.println("SvgOdg:" + msg); } //######################################################################## //# I N P U T / O U T P U T //######################################################################## boolean po(String s) { try { out.write(s); } catch(IOException e) { return false; } return true; } //######################################################################## //# U T I L I T Y //######################################################################## public void dumpDocument(Document doc) { String s = ""; try { TransformerFactory factory = TransformerFactory.newInstance(); Transformer trans = factory.newTransformer(); DOMSource source = new DOMSource(doc); ByteArrayOutputStream bos = new ByteArrayOutputStream(); StreamResult result = new StreamResult(bos); trans.transform(source, result); byte buf[] = bos.toByteArray(); s = new String(buf); } catch (javax.xml.transform.TransformerException e) { } trace("doc:" + s); } //######################################################################## //# I N N E R C L A S S ImageInfo //######################################################################## public class ImageInfo { String name; String newName; byte buf[]; public String getName() { return name; } public String getNewName() { return newName; } public byte[] getBuf() { return buf; } public ImageInfo(String name, String newName, byte buf[]) { this.name = name; this.name = newName; this.buf = buf; } } //######################################################################## //# I N N E R C L A S S StyleInfo //######################################################################## public class StyleInfo { String name; public String getName() { return name; } String cssStyle; public String getCssStyle() { return cssStyle; } String stroke; public String getStroke() { return stroke; } String strokeColor; public String getStrokeColor() { return strokeColor; } String strokeWidth; public String getStrokeWidth() { return strokeWidth; } String fill; public String getFill() { return fill; } String fillColor; public String getFillColor() { return fillColor; } public StyleInfo(String name, String cssStyle) { this.name = name; this.cssStyle = cssStyle; fill = "none"; stroke = "none"; } } //######################################################################## //# E N D I N N E R C L A S S E S //######################################################################## //######################################################################## //# V A R I A B L E S //######################################################################## /** * ODF content.xml file */ Document content; public Document getContent() { return content; } /** * ODF meta.xml file */ Document meta; public Document getMeta() { return meta; } /** * SVG file */ Document svg; public Document getSvg() { return svg; } /** * Loaded ODF or SVG images */ ArrayList images; public ArrayList getImages() { return images; } /** * CSS styles */ HashMap styles; public HashMap getStyles() { return styles; } //######################################################################## //# S V G T O O D F //######################################################################## class PathData { String cmd; double nr[]; PathData(String s, double buf[]) { cmd=s; nr = buf; } } double getPathNum(StringTokenizer st) { if (!st.hasMoreTokens()) return 0.0; String s = st.nextToken(); double nr = Double.parseDouble(s); return nr; } String parsePathData(String pathData, double bounds[]) { double minx = Double.MAX_VALUE; double maxx = Double.MIN_VALUE; double miny = Double.MAX_VALUE; double maxy = Double.MIN_VALUE; //trace("#### pathData:" + pathData); ArrayList data = new ArrayList(); StringTokenizer st = new StringTokenizer(pathData, " ,"); while (true) { String s = st.nextToken(); if ( s.equals("z") || s.equals("Z") ) { PathData pd = new PathData(s, new double[0]); data.add(pd); break; } else if ( s.equals("h") || s.equals("H") ) { double d[] = new double[1]; d[0] = getPathNum(st) * pxToCm; if (d[0] < minx) minx = d[0]; else if (d[0] > maxx) maxx = d[0]; PathData pd = new PathData(s, d); data.add(pd); } else if ( s.equals("v") || s.equals("V") ) { double d[] = new double[1]; d[0] = getPathNum(st) * pxToCm; if (d[0] < miny) miny = d[0]; else if (d[0] > maxy) maxy = d[0]; PathData pd = new PathData(s, d); data.add(pd); } else if ( s.equals("m") || s.equals("M") || s.equals("l") || s.equals("L") || s.equals("t") || s.equals("T") ) { double d[] = new double[2]; d[0] = getPathNum(st) * pxToCm; d[1] = getPathNum(st) * pxToCm; if (d[0] < minx) minx = d[0]; else if (d[0] > maxx) maxx = d[0]; if (d[1] < miny) miny = d[1]; else if (d[1] > maxy) maxy = d[1]; PathData pd = new PathData(s, d); data.add(pd); } else if ( s.equals("q") || s.equals("Q") || s.equals("s") || s.equals("S") ) { double d[] = new double[4]; d[0] = getPathNum(st) * pxToCm; d[1] = getPathNum(st) * pxToCm; if (d[0] < minx) minx = d[0]; else if (d[0] > maxx) maxx = d[0]; if (d[1] < miny) miny = d[1]; else if (d[1] > maxy) maxy = d[1]; d[2] = getPathNum(st) * pxToCm; d[3] = getPathNum(st) * pxToCm; if (d[2] < minx) minx = d[2]; else if (d[2] > maxx) maxx = d[2]; if (d[3] < miny) miny = d[3]; else if (d[3] > maxy) maxy = d[3]; PathData pd = new PathData(s, d); data.add(pd); } else if ( s.equals("c") || s.equals("C") ) { double d[] = new double[6]; d[0] = getPathNum(st) * pxToCm; d[1] = getPathNum(st) * pxToCm; if (d[0] < minx) minx = d[0]; else if (d[0] > maxx) maxx = d[0]; if (d[1] < miny) miny = d[1]; else if (d[1] > maxy) maxy = d[1]; d[2] = getPathNum(st) * pxToCm; d[3] = getPathNum(st) * pxToCm; if (d[2] < minx) minx = d[2]; else if (d[2] > maxx) maxx = d[2]; if (d[3] < miny) miny = d[3]; else if (d[3] > maxy) maxy = d[3]; d[4] = getPathNum(st) * pxToCm; d[5] = getPathNum(st) * pxToCm; if (d[4] < minx) minx = d[4]; else if (d[4] > maxx) maxx = d[4]; if (d[5] < miny) miny = d[5]; else if (d[5] > maxy) maxy = d[5]; PathData pd = new PathData(s, d); data.add(pd); } else if ( s.equals("a") || s.equals("A") ) { double d[] = new double[6]; d[0] = getPathNum(st) * pxToCm; d[1] = getPathNum(st) * pxToCm; if (d[0] < minx) minx = d[0]; else if (d[0] > maxx) maxx = d[0]; if (d[1] < miny) miny = d[1]; else if (d[1] > maxy) maxy = d[1]; d[2] = getPathNum(st) * piToRad;//angle d[3] = getPathNum(st) * piToRad;//angle d[4] = getPathNum(st) * pxToCm; d[5] = getPathNum(st) * pxToCm; if (d[4] < minx) minx = d[4]; else if (d[4] > maxx) maxx = d[4]; if (d[5] < miny) miny = d[5]; else if (d[5] > maxy) maxy = d[5]; PathData pd = new PathData(s, d); data.add(pd); } //trace("x:" + x + " y:" + y); } trace("minx:" + minx + " maxx:" + maxx + " miny:" + miny + " maxy:" + maxy); StringBuffer buf = new StringBuffer(); for (PathData pd : data) { buf.append(pd.cmd); buf.append(" "); for (double d:pd.nr) { buf.append(nrfmt.format(d * 1000.0)); buf.append(" "); } } bounds[0] = minx; bounds[1] = miny; bounds[2] = maxx; bounds[3] = maxy; return buf.toString(); } boolean parseTransform(String transStr, AffineTransform trans) { trace("== transform:"+ transStr); StringTokenizer st = new StringTokenizer(transStr, ")"); while (st.hasMoreTokens()) { String chunk = st.nextToken(); StringTokenizer st2 = new StringTokenizer(chunk, " ,("); if (!st2.hasMoreTokens()) continue; String name = st2.nextToken(); trace(" ++name:"+ name); if (name.equals("matrix")) { double v[] = new double[6]; for (int i=0 ; i<6 ; i++) { if (!st2.hasMoreTokens()) break; v[i] = Double.parseDouble(st2.nextToken()) * pxToCm; } AffineTransform mat = new AffineTransform(v); trans.concatenate(mat); } else if (name.equals("translate")) { double dx = 0.0; double dy = 0.0; if (!st2.hasMoreTokens()) continue; dx = Double.parseDouble(st2.nextToken()) * pxToCm; if (st2.hasMoreTokens()) dy = Double.parseDouble(st2.nextToken()) * pxToCm; trans.translate(dx, dy); } else if (name.equals("scale")) { double sx = 1.0; double sy = 1.0; if (!st2.hasMoreTokens()) continue; sx = sy = Double.parseDouble(st2.nextToken()); if (st2.hasMoreTokens()) sy = Double.parseDouble(st2.nextToken()); trans.scale(sx, sy); } else if (name.equals("rotate")) { double r = 0.0; double cx = 0.0; double cy = 0.0; if (!st2.hasMoreTokens()) continue; r = Double.parseDouble(st2.nextToken()) * piToRad; if (st2.hasMoreTokens()) { cx = Double.parseDouble(st2.nextToken()) * pxToCm; if (!st2.hasMoreTokens()) continue; cy = Double.parseDouble(st2.nextToken()) * pxToCm; trans.rotate(r, cx, cy); } else { trans.rotate(r); } } else if (name.equals("skewX")) { double angle = 0.0; if (!st2.hasMoreTokens()) continue; angle = Double.parseDouble(st2.nextToken()); trans.shear(angle, 0.0); } else if (name.equals("skewY")) { double angle = 0.0; if (!st2.hasMoreTokens()) continue; angle = Double.parseDouble(st2.nextToken()); trans.shear(0.0, angle); } } return true; } String coordToOdg(String sval) { double nr = Double.parseDouble(sval); nr = nr * pxToCm; String s = nrfmt.format(nr) + "cm"; return s; } boolean writeSvgAttributes(Element elem, AffineTransform trans) { NamedNodeMap attrs = elem.getAttributes(); String ename = elem.getLocalName(); for (int i=0 ; i\n"); NodeList children = elem.getChildNodes(); for (int i=0 ; i\n"); } else if (tagName.equals("text")) { String x = coordToOdg(elem.getAttribute("x")); String y = coordToOdg(elem.getAttribute("y")); String width = "5cm"; String height = "2cm"; String txt = elem.getTextContent(); po("\n"); po(" \n"); po(" " + txt + "\n"); po(" \n"); po("\n"); return true; } else if (tagName.equals("image")) { String x = coordToOdg(elem.getAttribute("x")); String y = coordToOdg(elem.getAttribute("y")); String width = coordToOdg(elem.getAttribute("width")); String height = coordToOdg(elem.getAttribute("height")); String imageName = elem.getAttributeNS(XLINK_NS, "href"); po("\n"); po(" \n"); po("\n"); return true; } else if (tagName.equals("path")) { double bounds[] = new double[4]; String d = elem.getAttribute("d"); String newd = parsePathData(d, bounds); double x = bounds[0]; double y = bounds[1]; double width = (bounds[2]-bounds[0]); double height = (bounds[3]-bounds[1]); po("\n"); //po(" svg:d=\"" + d + "\"/>\n"); return true; } else { //Verbatim tab mapping po("\n"); po("\n"); } return true; } boolean writeOdfContent(ZipOutputStream outs) { try { ZipEntry ze = new ZipEntry("content.xml"); outs.putNextEntry(ze); out = new BufferedWriter(new OutputStreamWriter(outs)); } catch (IOException e) { return false; } NodeList res = svg.getElementsByTagNameNS(SVG_NS, "svg"); if (res.getLength() < 1) { err("saveOdf: no root in .svg file"); return false; } Element root = (Element)res.item(0); po("\n"); po("\n"); po("\n"); po("\n"); po("\n"); po("\n"); po("\n"); po("\n"); po(" \n"); po("\n"); po("\n"); po(" \n"); po("\n"); //## Dump our style table for (StyleInfo s : styles.values()) { po("\n"); po(" \n"); po("\n"); } po("\n"); po("\n"); po("\n"); po("\n"); po("\n"); po("\n"); po("\n\n\n"); AffineTransform trans = new AffineTransform(); //trans.scale(12.0, 12.0); po("\n"); writeOdfContent(root, trans); po("\n"); po("\n\n\n"); po("\n"); po("\n"); po("\n"); po("\n"); try { out.flush(); outs.closeEntry(); } catch (IOException e) { err("writeOdfContent:" + e); return false; } return true; } boolean writeOdfMeta(ZipOutputStream outs) { try { ZipEntry ze = new ZipEntry("meta.xml"); outs.putNextEntry(ze); out = new BufferedWriter(new OutputStreamWriter(outs)); } catch (IOException e) { return false; } po("\n"); po("\n"); po("\n"); po(" Inkscape-0.43\n"); po(" clark kent\n"); po(" 2005-12-10T10:55:13\n"); po(" clark kent\n"); po(" 2005-12-10T10:56:20\n"); po(" en-US\n"); po(" 2\n"); po(" PT1M13S\n"); po(" \n"); po(" \n"); po(" \n"); po(" \n"); po(" \n"); po("\n"); po("\n"); try { out.flush(); outs.closeEntry(); } catch (IOException e) { err("writeOdfContent:" + e); return false; } return true; } boolean writeOdfManifest(ZipOutputStream outs) { try { ZipEntry ze = new ZipEntry("META-INF/manifest.xml"); outs.putNextEntry(ze); out = new BufferedWriter(new OutputStreamWriter(outs)); } catch (IOException e) { return false; } po("\n"); po("\n"); po("\n"); po(" \n"); po(" \n"); po(" \n"); po(" \n"); for (int i=0 ; i\n"); } po("\n"); try { out.flush(); outs.closeEntry(); } catch (IOException e) { err("writeOdfContent:" + e); return false; } return true; } boolean writeOdfImages(ZipOutputStream outs) { for (int i=0 ; i style.length()-2) continue; String attrName = style.substring(0, pos); String attrVal = style.substring(pos+1); trace(" =" + attrName + ':' + attrVal); if ("stroke".equals(attrName)) { si.stroke = "solid"; si.strokeColor = attrVal; } else if ("stroke-width".equals(attrName)) { si.strokeWidth = attrVal; } else if ("fill".equals(attrName)) { si.fill = "solid"; si.fillColor = attrVal; } } styles.put(css, si); return true; } boolean readSvg(InputStream ins) { //### LOAD XML try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(new EntityResolver() { public InputSource resolveEntity(String publicId, String systemId) { return new InputSource(new ByteArrayInputStream(new byte[0])); } }); Document doc = builder.parse(ins); svg = doc; } catch (javax.xml.parsers.ParserConfigurationException e) { err("making DOM parser:"+e); return false; } catch (org.xml.sax.SAXException e) { err("parsing svg document:"+e); return false; } catch (IOException e) { err("parsing svg document:"+e); return false; } //dumpDocument(svg); //### LOAD IMAGES imageNr = 0; images = new ArrayList(); NodeList res = svg.getElementsByTagNameNS(SVG_NS, "image"); for (int i=0 ; i(); res = svg.getElementsByTagName("*"); for (int i=0 ; i 0) parseCss(style); } return true; } boolean readSvg(String fileName) { try { FileInputStream fis = new FileInputStream(fileName); if (!readSvg(fis)) return false; fis.close(); } catch (IOException e) { err("opening svg file:"+e); return false; } return true; } //######################################################################## //# O D F T O S V G //######################################################################## /** * */ public boolean readOdfEntry(ZipInputStream zis, ZipEntry ent) { String fileName = ent.getName(); trace("fileName:" + fileName); if (fileName.length() < 4) return true; String ext = fileName.substring(fileName.length() - 4); trace("ext:" + ext); ArrayList arr = new ArrayList(); try { while (true) { int inb = zis.read(); if (inb < 0) break; arr.add((byte)inb); } } catch (IOException e) { return false; } byte buf[] = new byte[arr.size()]; for (int i=0 ; i(); styleNr = 0; styles = new HashMap(); ZipInputStream zis = new ZipInputStream(ins); while (true) { try { ZipEntry ent = zis.getNextEntry(); if (ent == null) break; if (!readOdfEntry(zis, ent)) return false; zis.closeEntry(); } catch (IOException e) { err("reading zip entry"); return false; } } return true; } /** * */ public boolean readOdf(String fileName) { boolean ret = true; try { FileInputStream fis = new FileInputStream(fileName); ret = readOdf(fis); fis.close(); } catch (IOException e) { err("reading " + fileName + " : " + e); ret = false; } return true; } public boolean writeSvgElement(Element elem) { String ns = elem.getNamespaceURI(); String tagName = elem.getLocalName(); trace("tag:" + tagName + " : " + ns); if (ns.equals(ODSVG_NS)) { po("<"); po(tagName); NamedNodeMap attrs = elem.getAttributes(); for (int i=0 ; i\n"); } NodeList children = elem.getChildNodes(); for (int i=0 ; i\n"); } return true; } public boolean saveSvg(String svgFileName) { trace("====== Saving images ==========="); try { for (int i=0 ; i\n"); po("\n"); writeSvgElement(page); po("\n"); try { out.close(); } catch (IOException e) { err("save:close:" + e); return false; } return true; } //######################################################################## //# C O N S T R U C T O R //######################################################################## SvgOdg() { //init, if necessary nrfmt = new DecimalFormat("#.####"); } //######################################################################## //# C O M M A N D L I N E //######################################################################## public boolean odfToSvg(String odfName, String svgName) { if (!readOdf(odfName)) return false; if (!saveSvg(svgName)) return false; return true; } public boolean svgToOdf(String svgName, String odfName) { if (!readSvg(svgName)) return false; System.out.println("ok"); if (!saveOdf(odfName)) return false; return true; } void usage() { System.out.println("usage: SvgOdf input_file.odg output_file.svg"); System.out.println(" SvgOdf input_file.svg output_file.odg"); } boolean parseArguments(String argv[]) { if (argv.length != 2) { usage(); return false; } String fileName1 = argv[0]; String fileName2 = argv[1]; if (fileName1.length()<5 || fileName2.length()<5) { System.out.println("one or more file names is too short"); usage(); return false; } String ext1 = fileName1.substring(fileName1.length()-4); String ext2 = fileName2.substring(fileName2.length()-4); //System.out.println("ext1:"+ext1+" ext2:"+ext2); //##### ODG -> SVG ##### if ((ext1.equals(".odg") || ext1.equals(".odf") || ext1.equals(".zip") ) && ext2.equals(".svg")) { if (!odfToSvg(argv[0], argv[1])) { System.out.println("Conversion from ODG to SVG failed"); return false; } } //##### SVG -> ODG ##### else if (ext1.equals(".svg") && ( ext2.equals(".odg") || ext2.equals(".odf") || ext2.equals(".zip") ) ) { if (!svgToOdf(fileName1, fileName2)) { System.out.println("Conversion from SVG to ODG failed"); return false; } } //##### none of the above ##### else { usage(); return false; } return true; } public static void main(String argv[]) { SvgOdg svgodg = new SvgOdg(); svgodg.parseArguments(argv); } } //######################################################################## //# E N D O F F I L E //########################################################################