1 /**
2 *
3 * This is a small experimental class for converting between
4 * SVG and OpenDocument .odg files. This code is not intended
5 * to be a permanent solution for SVG-to-ODG conversion. Rather,
6 * it is a quick-and-easy test bed for ideas which will be later
7 * recoded into C++.
8 *
9 * ---------------------------------------------------------------------
10 *
11 * SvgOdg - A program to experiment with conversions between SVG and ODG
12 * Copyright (C) 2006 Bob Jamison
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software Foundation,
26 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27 *
28 * For more information, please write to rwjj@earthlink.net
29 *
30 */
33 /**
34 *
35 */
36 public class SvgOdg
37 {
41 /**
42 * Namespace declarations
43 */
44 public static final String SVG_NS =
45 "http://www.w3.org/2000/svg";
46 public static final String XLINK_NS =
47 "http://www.w3.org/1999/xlink";
48 public static final String ODF_NS =
49 "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
50 public static final String ODG_NS =
51 "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0";
52 public static final String ODSVG_NS =
53 "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0";
56 DecimalFormat nrfmt;
57 //static final double pxToCm = 0.0339;
58 static final double pxToCm = 0.0275;
59 static final double piToRad = 0.0174532925;
60 BufferedWriter out;
61 BufferedReader in;
62 int imageNr;
63 int styleNr;
65 //########################################################################
66 //# M E S S A G E S
67 //########################################################################
69 /**
70 *
71 */
72 void err(String msg)
73 {
74 System.out.println("SvgOdg ERROR:" + msg);
75 }
77 /**
78 *
79 */
80 void trace(String msg)
81 {
82 System.out.println("SvgOdg:" + msg);
83 }
88 //########################################################################
89 //# I N P U T / O U T P U T
90 //########################################################################
92 boolean po(String s)
93 {
94 try
95 {
96 out.write(s);
97 }
98 catch(IOException e)
99 {
100 return false;
101 }
102 return true;
103 }
105 //########################################################################
106 //# U T I L I T Y
107 //########################################################################
109 public void dumpDocument(Document doc)
110 {
111 String s = "";
112 try
113 {
114 TransformerFactory factory = TransformerFactory.newInstance();
115 Transformer trans = factory.newTransformer();
116 DOMSource source = new DOMSource(doc);
117 ByteArrayOutputStream bos = new ByteArrayOutputStream();
118 StreamResult result = new StreamResult(bos);
119 trans.transform(source, result);
120 byte buf[] = bos.toByteArray();
121 s = new String(buf);
122 }
123 catch (javax.xml.transform.TransformerException e)
124 {
125 }
126 trace("doc:" + s);
127 }
130 //########################################################################
131 //# I N N E R C L A S S ImageInfo
132 //########################################################################
133 public class ImageInfo
134 {
135 String name;
136 String newName;
137 byte buf[];
139 public String getName()
140 {
141 return name;
142 }
144 public String getNewName()
145 {
146 return newName;
147 }
150 public byte[] getBuf()
151 {
152 return buf;
153 }
155 public ImageInfo(String name, String newName, byte buf[])
156 {
157 this.name = name;
158 this.name = newName;
159 this.buf = buf;
160 }
162 }
163 //########################################################################
164 //# I N N E R C L A S S StyleInfo
165 //########################################################################
166 public class StyleInfo
167 {
169 String name;
170 public String getName()
171 {
172 return name;
173 }
175 String cssStyle;
176 public String getCssStyle()
177 {
178 return cssStyle;
179 }
181 String stroke;
182 public String getStroke()
183 {
184 return stroke;
185 }
187 String strokeColor;
188 public String getStrokeColor()
189 {
190 return strokeColor;
191 }
193 String strokeWidth;
194 public String getStrokeWidth()
195 {
196 return strokeWidth;
197 }
199 String fill;
200 public String getFill()
201 {
202 return fill;
203 }
205 String fillColor;
206 public String getFillColor()
207 {
208 return fillColor;
209 }
211 public StyleInfo(String name, String cssStyle)
212 {
213 this.name = name;
214 this.cssStyle = cssStyle;
215 fill = "none";
216 stroke = "none";
217 }
219 }
220 //########################################################################
221 //# E N D I N N E R C L A S S E S
222 //########################################################################
227 //########################################################################
228 //# V A R I A B L E S
229 //########################################################################
231 /**
232 * ODF content.xml file
233 */
234 Document content;
235 public Document getContent()
236 {
237 return content;
238 }
240 /**
241 * ODF meta.xml file
242 */
243 Document meta;
244 public Document getMeta()
245 {
246 return meta;
247 }
249 /**
250 * SVG file
251 */
252 Document svg;
253 public Document getSvg()
254 {
255 return svg;
256 }
258 /**
259 * Loaded ODF or SVG images
260 */
261 ArrayList<ImageInfo> images;
262 public ArrayList<ImageInfo> getImages()
263 {
264 return images;
265 }
267 /**
268 * CSS styles
269 */
270 HashMap<String, StyleInfo> styles;
271 public HashMap<String, StyleInfo> getStyles()
272 {
273 return styles;
274 }
280 //########################################################################
281 //# S V G T O O D F
282 //########################################################################
284 class PathData
285 {
286 String cmd;
287 double nr[];
288 PathData(String s, double buf[])
289 {
290 cmd=s; nr = buf;
291 }
292 }
294 double getPathNum(StringTokenizer st)
295 {
296 if (!st.hasMoreTokens())
297 return 0.0;
298 String s = st.nextToken();
299 double nr = Double.parseDouble(s);
300 return nr;
301 }
303 String parsePathData(String pathData, double bounds[])
304 {
305 double minx = Double.MAX_VALUE;
306 double maxx = Double.MIN_VALUE;
307 double miny = Double.MAX_VALUE;
308 double maxy = Double.MIN_VALUE;
309 //trace("#### pathData:" + pathData);
310 ArrayList<PathData> data = new ArrayList<PathData>();
311 StringTokenizer st = new StringTokenizer(pathData, " ,");
312 while (true)
313 {
314 String s = st.nextToken();
315 if ( s.equals("z") || s.equals("Z") )
316 {
317 PathData pd = new PathData(s, new double[0]);
318 data.add(pd);
319 break;
320 }
321 else if ( s.equals("h") || s.equals("H") )
322 {
323 double d[] = new double[1];
324 d[0] = getPathNum(st) * pxToCm;
325 if (d[0] < minx) minx = d[0];
326 else if (d[0] > maxx) maxx = d[0];
327 PathData pd = new PathData(s, d);
328 data.add(pd);
329 }
330 else if ( s.equals("v") || s.equals("V") )
331 {
332 double d[] = new double[1];
333 d[0] = getPathNum(st) * pxToCm;
334 if (d[0] < miny) miny = d[0];
335 else if (d[0] > maxy) maxy = d[0];
336 PathData pd = new PathData(s, d);
337 data.add(pd);
338 }
339 else if ( s.equals("m") || s.equals("M") ||
340 s.equals("l") || s.equals("L") ||
341 s.equals("t") || s.equals("T") )
342 {
343 double d[] = new double[2];
344 d[0] = getPathNum(st) * pxToCm;
345 d[1] = getPathNum(st) * pxToCm;
346 if (d[0] < minx) minx = d[0];
347 else if (d[0] > maxx) maxx = d[0];
348 if (d[1] < miny) miny = d[1];
349 else if (d[1] > maxy) maxy = d[1];
350 PathData pd = new PathData(s, d);
351 data.add(pd);
352 }
353 else if ( s.equals("q") || s.equals("Q") ||
354 s.equals("s") || s.equals("S") )
355 {
356 double d[] = new double[4];
357 d[0] = getPathNum(st) * pxToCm;
358 d[1] = getPathNum(st) * pxToCm;
359 if (d[0] < minx) minx = d[0];
360 else if (d[0] > maxx) maxx = d[0];
361 if (d[1] < miny) miny = d[1];
362 else if (d[1] > maxy) maxy = d[1];
363 d[2] = getPathNum(st) * pxToCm;
364 d[3] = getPathNum(st) * pxToCm;
365 if (d[2] < minx) minx = d[2];
366 else if (d[2] > maxx) maxx = d[2];
367 if (d[3] < miny) miny = d[3];
368 else if (d[3] > maxy) maxy = d[3];
369 PathData pd = new PathData(s, d);
370 data.add(pd);
371 }
372 else if ( s.equals("c") || s.equals("C") )
373 {
374 double d[] = new double[6];
375 d[0] = getPathNum(st) * pxToCm;
376 d[1] = getPathNum(st) * pxToCm;
377 if (d[0] < minx) minx = d[0];
378 else if (d[0] > maxx) maxx = d[0];
379 if (d[1] < miny) miny = d[1];
380 else if (d[1] > maxy) maxy = d[1];
381 d[2] = getPathNum(st) * pxToCm;
382 d[3] = getPathNum(st) * pxToCm;
383 if (d[2] < minx) minx = d[2];
384 else if (d[2] > maxx) maxx = d[2];
385 if (d[3] < miny) miny = d[3];
386 else if (d[3] > maxy) maxy = d[3];
387 d[4] = getPathNum(st) * pxToCm;
388 d[5] = getPathNum(st) * pxToCm;
389 if (d[4] < minx) minx = d[4];
390 else if (d[4] > maxx) maxx = d[4];
391 if (d[5] < miny) miny = d[5];
392 else if (d[5] > maxy) maxy = d[5];
393 PathData pd = new PathData(s, d);
394 data.add(pd);
395 }
396 else if ( s.equals("a") || s.equals("A") )
397 {
398 double d[] = new double[6];
399 d[0] = getPathNum(st) * pxToCm;
400 d[1] = getPathNum(st) * pxToCm;
401 if (d[0] < minx) minx = d[0];
402 else if (d[0] > maxx) maxx = d[0];
403 if (d[1] < miny) miny = d[1];
404 else if (d[1] > maxy) maxy = d[1];
405 d[2] = getPathNum(st) * piToRad;//angle
406 d[3] = getPathNum(st) * piToRad;//angle
407 d[4] = getPathNum(st) * pxToCm;
408 d[5] = getPathNum(st) * pxToCm;
409 if (d[4] < minx) minx = d[4];
410 else if (d[4] > maxx) maxx = d[4];
411 if (d[5] < miny) miny = d[5];
412 else if (d[5] > maxy) maxy = d[5];
413 PathData pd = new PathData(s, d);
414 data.add(pd);
415 }
416 //trace("x:" + x + " y:" + y);
417 }
419 trace("minx:" + minx + " maxx:" + maxx +
420 " miny:" + miny + " maxy:" + maxy);
422 StringBuffer buf = new StringBuffer();
423 for (PathData pd : data)
424 {
425 buf.append(pd.cmd);
426 buf.append(" ");
427 for (double d:pd.nr)
428 {
429 buf.append(nrfmt.format(d * 1000.0));
430 buf.append(" ");
431 }
432 }
434 bounds[0] = minx;
435 bounds[1] = miny;
436 bounds[2] = maxx;
437 bounds[3] = maxy;
439 return buf.toString();
440 }
444 boolean parseTransform(String transStr, AffineTransform trans)
445 {
446 trace("== transform:"+ transStr);
447 StringTokenizer st = new StringTokenizer(transStr, ")");
448 while (st.hasMoreTokens())
449 {
450 String chunk = st.nextToken();
451 StringTokenizer st2 = new StringTokenizer(chunk, " ,(");
452 if (!st2.hasMoreTokens())
453 continue;
454 String name = st2.nextToken();
455 trace(" ++name:"+ name);
456 if (name.equals("matrix"))
457 {
458 double v[] = new double[6];
459 for (int i=0 ; i<6 ; i++)
460 {
461 if (!st2.hasMoreTokens())
462 break;
463 v[i] = Double.parseDouble(st2.nextToken()) * pxToCm;
464 }
465 AffineTransform mat = new AffineTransform(v);
466 trans.concatenate(mat);
467 }
468 else if (name.equals("translate"))
469 {
470 double dx = 0.0;
471 double dy = 0.0;
472 if (!st2.hasMoreTokens())
473 continue;
474 dx = Double.parseDouble(st2.nextToken()) * pxToCm;
475 if (st2.hasMoreTokens())
476 dy = Double.parseDouble(st2.nextToken()) * pxToCm;
477 trans.translate(dx, dy);
478 }
479 else if (name.equals("scale"))
480 {
481 double sx = 1.0;
482 double sy = 1.0;
483 if (!st2.hasMoreTokens())
484 continue;
485 sx = sy = Double.parseDouble(st2.nextToken());
486 if (st2.hasMoreTokens())
487 sy = Double.parseDouble(st2.nextToken());
488 trans.scale(sx, sy);
489 }
490 else if (name.equals("rotate"))
491 {
492 double r = 0.0;
493 double cx = 0.0;
494 double cy = 0.0;
495 if (!st2.hasMoreTokens())
496 continue;
497 r = Double.parseDouble(st2.nextToken()) * piToRad;
498 if (st2.hasMoreTokens())
499 {
500 cx = Double.parseDouble(st2.nextToken()) * pxToCm;
501 if (!st2.hasMoreTokens())
502 continue;
503 cy = Double.parseDouble(st2.nextToken()) * pxToCm;
504 trans.rotate(r, cx, cy);
505 }
506 else
507 {
508 trans.rotate(r);
509 }
510 }
511 else if (name.equals("skewX"))
512 {
513 double angle = 0.0;
514 if (!st2.hasMoreTokens())
515 continue;
516 angle = Double.parseDouble(st2.nextToken());
517 trans.shear(angle, 0.0);
518 }
519 else if (name.equals("skewY"))
520 {
521 double angle = 0.0;
522 if (!st2.hasMoreTokens())
523 continue;
524 angle = Double.parseDouble(st2.nextToken());
525 trans.shear(0.0, angle);
526 }
527 }
528 return true;
529 }
533 String coordToOdg(String sval)
534 {
535 double nr = Double.parseDouble(sval);
536 nr = nr * pxToCm;
537 String s = nrfmt.format(nr) + "cm";
538 return s;
539 }
542 boolean writeSvgAttributes(Element elem, AffineTransform trans)
543 {
544 NamedNodeMap attrs = elem.getAttributes();
545 String ename = elem.getLocalName();
546 for (int i=0 ; i<attrs.getLength() ; i++)
547 {
548 Attr attr = (Attr)attrs.item(i);
549 String aname = attr.getName();
550 String aval = attr.getValue();
551 if (aname.startsWith("xmlns"))
552 continue;
553 else if (aname.equals("d"))//already handled
554 continue;
555 else if (aname.startsWith("transform"))
556 {
557 parseTransform(aval, trans);
558 continue;
559 }
560 else if (aname.equals("style"))
561 {
562 StyleInfo style = styles.get(aval);
563 if (style != null)
564 {
565 po(" draw:style-name=\"");
566 po(style.getName());
567 po("\"");
568 }
569 continue;
570 }
571 if (aname.equals("x") || aname.equals("y") ||
572 aname.equals("width") || aname.equals("height"))
573 {
574 aval = coordToOdg(aval);
575 }
576 if ("id".equals(aname))
577 po(" ");
578 else if ("transform".equals(aname))
579 po(" draw:");
580 else
581 po(" svg:");
582 po(aname);
583 po("=\"");
584 po(aval);
585 po("\"");
586 }
588 //Output the current transform
589 if (!trans.isIdentity() &&
590 !(
591 ename.equals("g") ||
592 ename.equals("defs") ||
593 ename.equals("metadata")
594 )
595 )
596 {
597 double v[] = new double[6];
598 trans.getMatrix(v);
599 po(" draw:transform=\"matrix(" +
600 nrfmt.format(v[0]) + "," +
601 nrfmt.format(v[1]) + "," +
602 nrfmt.format(v[2]) + "," +
603 nrfmt.format(v[3]) + "," +
604 nrfmt.format(v[4]) + "," +
605 nrfmt.format(v[5]) + ")\"");
606 }
607 return true;
608 }
610 public boolean writeOdfContent(Element elem, AffineTransform trans)
611 {
612 String ns = elem.getNamespaceURI();
613 String tagName = elem.getLocalName();
614 //trace("ns:" + ns + " tagName:" + tagName);
615 if (!ns.equals(SVG_NS))
616 return true;
617 if (tagName.equals("svg"))
618 {
619 NodeList children = elem.getChildNodes();
620 for (int i=0 ; i<children.getLength() ; i++)
621 {
622 Node n = children.item(i);
623 if (n.getNodeType() == Node.ELEMENT_NODE)
624 if (!writeOdfContent((Element)n,
625 (AffineTransform)trans.clone()))
626 return false;
627 }
628 }
629 else if (tagName.equals("g"))
630 {
631 //String transform = elem.getAttribute("transform");
632 po("<draw:g");
633 writeSvgAttributes(elem, trans); po(">\n");
634 NodeList children = elem.getChildNodes();
635 for (int i=0 ; i<children.getLength() ; i++)
636 {
637 Node n = children.item(i);
638 if (n.getNodeType() == Node.ELEMENT_NODE)
639 if (!writeOdfContent((Element)n,
640 (AffineTransform)trans.clone()))
641 return false;
642 }
643 po("</draw:g>\n");
644 }
645 else if (tagName.equals("text"))
646 {
647 String x = coordToOdg(elem.getAttribute("x"));
648 String y = coordToOdg(elem.getAttribute("y"));
649 String width = "5cm";
650 String height = "2cm";
651 String txt = elem.getTextContent();
652 po("<draw:frame draw:style-name=\"grx1\" draw:layer=\"layout\" ");
653 po("svg:x=\"" + x + "\" svg:y=\"" + y + "\" ");
654 po("svg:width=\"" + width + "\" svg:height=\"" + height + "\"");
655 po(">\n");
656 po(" <draw:text-box draw:auto-grow-height=\"true\" draw:auto-grow-width=\"true\">\n");
657 po(" <text:p text:style-name=\"P1\"> " + txt + "</text:p>\n");
658 po(" </draw:text-box>\n");
659 po("</draw:frame>\n");
660 return true;
661 }
662 else if (tagName.equals("image"))
663 {
664 String x = coordToOdg(elem.getAttribute("x"));
665 String y = coordToOdg(elem.getAttribute("y"));
666 String width = coordToOdg(elem.getAttribute("width"));
667 String height = coordToOdg(elem.getAttribute("height"));
668 String imageName = elem.getAttributeNS(XLINK_NS, "href");
669 po("<draw:frame draw:style-name=\"grx1\" draw:layer=\"layout\" ");
670 po("svg:x=\"" + x + "\" svg:y=\"" + y + "\" ");
671 po("svg:width=\"" + width + "\" svg:height=\"" + height + "\">\n");
672 po(" <draw:image xlink:href=\"Pictures/" + imageName + "\" xlink:type=\"simple\" xlink:show=\"embed\" xlink:actuate=\"onLoad\"><text:p/></draw:image>\n");
673 po("</draw:frame>\n");
674 return true;
675 }
676 else if (tagName.equals("path"))
677 {
678 double bounds[] = new double[4];
679 String d = elem.getAttribute("d");
680 String newd = parsePathData(d, bounds);
681 double x = bounds[0];
682 double y = bounds[1];
683 double width = (bounds[2]-bounds[0]);
684 double height = (bounds[3]-bounds[1]);
685 po("<draw:path draw:layer=\"layout\" \n");
686 po(" svg:x=\"" + nrfmt.format(x) + "cm\" ");
687 po("svg:y=\"" + nrfmt.format(y) + "cm\" ");
688 po("svg:width=\"" + nrfmt.format(width) + "cm\" ");
689 po("svg:height=\"" + nrfmt.format(height) + "cm\" ");
690 po("svg:viewBox=\"0.0 0.0 " +
691 nrfmt.format(bounds[2] * 1000.0) + " " +
692 nrfmt.format(bounds[3] * 1000.0) + "\"\n");
693 po(" svg:d=\"" + newd + "\"\n ");
695 writeSvgAttributes(elem, trans); po("/>\n");
696 //po(" svg:d=\"" + d + "\"/>\n");
697 return true;
698 }
700 else
701 {
702 //Verbatim tab mapping
703 po("<draw:"); po(tagName);
704 writeSvgAttributes(elem, trans); po(">\n");
705 po("</draw:"); po(tagName); po(">\n");
706 }
708 return true;
709 }
712 boolean writeOdfContent(ZipOutputStream outs)
713 {
714 try
715 {
716 ZipEntry ze = new ZipEntry("content.xml");
717 outs.putNextEntry(ze);
718 out = new BufferedWriter(new OutputStreamWriter(outs));
719 }
720 catch (IOException e)
721 {
722 return false;
723 }
725 NodeList res = svg.getElementsByTagNameNS(SVG_NS, "svg");
726 if (res.getLength() < 1)
727 {
728 err("saveOdf: no <svg> root in .svg file");
729 return false;
730 }
731 Element root = (Element)res.item(0);
734 po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
735 po("<office:document-content\n");
736 po(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n");
737 po(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n");
738 po(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n");
739 po(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n");
740 po(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n");
741 po(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n");
742 po(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
743 po(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
744 po(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n");
745 po(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n");
746 po(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n");
747 po(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n");
748 po(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n");
749 po(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n");
750 po(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n");
751 po(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n");
752 po(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n");
753 po(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n");
754 po(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n");
755 po(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n");
756 po(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n");
757 po(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n");
758 po(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n");
759 po(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
760 po(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n");
761 po(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n");
762 po(" office:version=\"1.0\">\n");
763 po("\n");
764 po("\n");
765 po("<office:scripts/>\n");
766 po("<office:automatic-styles>\n");
767 po("<style:style style:name=\"dp1\" style:family=\"drawing-page\"/>\n");
768 po("<style:style style:name=\"grx1\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n");
769 po(" <style:graphic-properties draw:stroke=\"none\" draw:fill=\"solid\" draw:textarea-horizontal-align=\"center\" draw:textarea-vertical-align=\"middle\" draw:color-mode=\"standard\" draw:luminance=\"0%\" draw:contrast=\"0%\" draw:gamma=\"100%\" draw:red=\"0%\" draw:green=\"0%\" draw:blue=\"0%\" fo:clip=\"rect(0cm 0cm 0cm 0cm)\" draw:image-opacity=\"100%\" style:mirror=\"none\"/>\n");
770 po("</style:style>\n");
771 po("<style:style style:name=\"P1\" style:family=\"paragraph\">\n");
772 po(" <style:paragraph-properties fo:text-align=\"center\"/>\n");
773 po("</style:style>\n");
775 //## Dump our style table
776 for (StyleInfo s : styles.values())
777 {
778 po("<style:style style:name=\"" + s.getName() + "\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n");
779 po(" <style:graphic-properties");
780 po(" draw:fill=\"" + s.getFill() + "\"");
781 if (!s.getFill().equals("none"))
782 po(" draw:fill-color=\"" + s.getFillColor() + "\"");
783 po(" draw:stroke=\"" + s.getStroke() + "\"");
784 if (!s.getStroke().equals("none"))
785 {
786 po(" svg:stroke-width=\"" + s.getStrokeWidth() + "\"");
787 po(" svg:stroke-color=\"" + s.getStrokeColor() + "\"");
788 }
789 po("/>\n");
790 po("</style:style>\n");
791 }
792 po("</office:automatic-styles>\n");
793 po("\n");
794 po("\n");
795 po("<office:body>\n");
796 po("<office:drawing>\n");
797 po("<draw:page draw:name=\"page1\" draw:style-name=\"dp1\" draw:master-page-name=\"Default\">\n");
798 po("\n\n\n");
799 AffineTransform trans = new AffineTransform();
800 //trans.scale(12.0, 12.0);
801 po("<!-- ######### CONVERSION FROM SVG STARTS ######## -->\n");
802 writeOdfContent(root, trans);
803 po("<!-- ######### CONVERSION FROM SVG ENDS ######## -->\n");
804 po("\n\n\n");
806 po("</draw:page>\n");
807 po("</office:drawing>\n");
808 po("</office:body>\n");
809 po("</office:document-content>\n");
812 try
813 {
814 out.flush();
815 outs.closeEntry();
816 }
817 catch (IOException e)
818 {
819 err("writeOdfContent:" + e);
820 return false;
821 }
822 return true;
823 }
825 boolean writeOdfMeta(ZipOutputStream outs)
826 {
827 try
828 {
829 ZipEntry ze = new ZipEntry("meta.xml");
830 outs.putNextEntry(ze);
831 out = new BufferedWriter(new OutputStreamWriter(outs));
832 }
833 catch (IOException e)
834 {
835 return false;
836 }
838 po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
839 po("<office:document-meta\n");
840 po(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n");
841 po(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
842 po(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
843 po(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n");
844 po(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n");
845 po(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n");
846 po(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n");
847 po(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n");
848 po(" office:version=\"1.0\">\n");
849 po("<office:meta>\n");
850 po(" <meta:generator>Inkscape-0.43</meta:generator>\n");
851 po(" <meta:initial-creator>clark kent</meta:initial-creator>\n");
852 po(" <meta:creation-date>2005-12-10T10:55:13</meta:creation-date>\n");
853 po(" <dc:creator>clark kent</dc:creator>\n");
854 po(" <dc:date>2005-12-10T10:56:20</dc:date>\n");
855 po(" <dc:language>en-US</dc:language>\n");
856 po(" <meta:editing-cycles>2</meta:editing-cycles>\n");
857 po(" <meta:editing-duration>PT1M13S</meta:editing-duration>\n");
858 po(" <meta:user-defined meta:name=\"Info 1\"/>\n");
859 po(" <meta:user-defined meta:name=\"Info 2\"/>\n");
860 po(" <meta:user-defined meta:name=\"Info 3\"/>\n");
861 po(" <meta:user-defined meta:name=\"Info 4\"/>\n");
862 po(" <meta:document-statistic meta:object-count=\"2\"/>\n");
863 po("</office:meta>\n");
864 po("</office:document-meta>\n");
867 try
868 {
869 out.flush();
870 outs.closeEntry();
871 }
872 catch (IOException e)
873 {
874 err("writeOdfContent:" + e);
875 return false;
876 }
877 return true;
878 }
881 boolean writeOdfManifest(ZipOutputStream outs)
882 {
883 try
884 {
885 ZipEntry ze = new ZipEntry("META-INF/manifest.xml");
886 outs.putNextEntry(ze);
887 out = new BufferedWriter(new OutputStreamWriter(outs));
888 }
889 catch (IOException e)
890 {
891 return false;
892 }
894 po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
895 po("<!DOCTYPE manifest:manifest PUBLIC \"-//OpenOffice.org//DTD Manifest 1.0//EN\" \"Manifest.dtd\">\n");
896 po("<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">\n");
897 po(" <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.graphics\" manifest:full-path=\"/\"/>\n");
898 po(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>\n");
899 po(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/>\n");
900 po(" <!--List our images here-->\n");
901 for (int i=0 ; i<images.size() ; i++)
902 {
903 ImageInfo ie = images.get(i);
904 String fname = ie.getName();
905 if (fname.length() < 5)
906 {
907 err("image file name too short:" + fname);
908 return false;
909 }
910 String ext = fname.substring(fname.length() - 4);
911 po(" <manifest:file-entry manifest:media-type=\"");
912 if (ext.equals(".gif"))
913 po("image/gif");
914 else if (ext.equals(".png"))
915 po("image/png");
916 else if (ext.equals(".jpg") || ext.equals(".jpeg"))
917 po("image/jpeg");
918 po("\" manifest:full-path=\"");
919 po(fname);
920 po("\"/>\n");
921 }
922 po("</manifest:manifest>\n");
924 try
925 {
926 out.flush();
927 outs.closeEntry();
928 }
929 catch (IOException e)
930 {
931 err("writeOdfContent:" + e);
932 return false;
933 }
934 return true;
935 }
937 boolean writeOdfImages(ZipOutputStream outs)
938 {
939 for (int i=0 ; i<images.size() ; i++)
940 {
941 ImageInfo ie = images.get(i);
942 try
943 {
944 String iname = "Pictures/" + ie.getName();
945 ZipEntry ze = new ZipEntry(iname);
946 outs.putNextEntry(ze);
947 outs.write(ie.getBuf());
948 outs.closeEntry();
949 }
950 catch (IOException e)
951 {
952 err("writing images:" + e);
953 return false;
954 }
955 }
956 return true;
957 }
959 /**
960 *
961 */
962 public boolean writeOdf(OutputStream outs)
963 {
964 try
965 {
966 ZipOutputStream zos = new ZipOutputStream(outs);
967 if (!writeOdfContent(zos))
968 return false;
969 if (!writeOdfManifest(zos))
970 return false;
971 if (!writeOdfMeta(zos))
972 return false;
973 if (!writeOdfImages(zos))
974 return false;
975 //if (!writeOdfStyles(zos))
976 // return false;
977 zos.close();
978 }
979 catch (IOException e)
980 {
981 err("closing ODF zip output stream:" + e);
982 return false;
983 }
984 return true;
985 }
989 /**
990 *
991 */
992 public boolean saveOdf(String fileName)
993 {
994 boolean ret = true;
995 try
996 {
997 FileOutputStream fos = new FileOutputStream(fileName);
998 ret = writeOdf(fos);
999 fos.close();
1000 }
1001 catch (IOException e)
1002 {
1003 err("writing odf " + fileName + " : " + e);
1004 return false;
1005 }
1006 return ret;
1007 }
1011 boolean parseCss(String css)
1012 {
1013 trace("##### STYLE ### :" + css);
1014 String name = "gr" + styleNr;
1015 styleNr++;
1016 StyleInfo si = new StyleInfo(name, css);
1017 StringTokenizer st = new StringTokenizer(css, ";");
1018 while (st.hasMoreTokens())
1019 {
1020 String style = st.nextToken();
1021 //trace(" " + style);
1022 int pos = style.indexOf(':');
1023 if (pos < 1 || pos > style.length()-2)
1024 continue;
1025 String attrName = style.substring(0, pos);
1026 String attrVal = style.substring(pos+1);
1027 trace(" =" + attrName + ':' + attrVal);
1028 if ("stroke".equals(attrName))
1029 {
1030 si.stroke = "solid";
1031 si.strokeColor = attrVal;
1032 }
1033 else if ("stroke-width".equals(attrName))
1034 {
1035 si.strokeWidth = attrVal;
1036 }
1037 else if ("fill".equals(attrName))
1038 {
1039 si.fill = "solid";
1040 si.fillColor = attrVal;
1041 }
1042 }
1043 styles.put(css, si);
1044 return true;
1045 }
1047 boolean readSvg(InputStream ins)
1048 {
1049 //### LOAD XML
1050 try
1051 {
1052 DocumentBuilderFactory factory =
1053 DocumentBuilderFactory.newInstance();
1054 factory.setNamespaceAware(true);
1055 DocumentBuilder builder =
1056 factory.newDocumentBuilder();
1057 builder.setEntityResolver(new EntityResolver()
1058 {
1059 public InputSource resolveEntity(String publicId, String systemId)
1060 {
1061 return new InputSource(new ByteArrayInputStream(new byte[0]));
1062 }
1063 });
1064 Document doc = builder.parse(ins);
1065 svg = doc;
1066 }
1067 catch (javax.xml.parsers.ParserConfigurationException e)
1068 {
1069 err("making DOM parser:"+e);
1070 return false;
1071 }
1072 catch (org.xml.sax.SAXException e)
1073 {
1074 err("parsing svg document:"+e);
1075 return false;
1076 }
1077 catch (IOException e)
1078 {
1079 err("parsing svg document:"+e);
1080 return false;
1081 }
1082 //dumpDocument(svg);
1084 //### LOAD IMAGES
1085 imageNr = 0;
1086 images = new ArrayList<ImageInfo>();
1087 NodeList res = svg.getElementsByTagNameNS(SVG_NS, "image");
1088 for (int i=0 ; i<res.getLength() ; i++)
1089 {
1090 Element elem = (Element) res.item(i);
1091 String fileName = elem.getAttributeNS(XLINK_NS, "href");
1092 if (fileName == null)
1093 {
1094 err("No xlink:href pointer to image data for image");
1095 return false;
1096 }
1097 File f = new File(fileName);
1098 if (!f.exists())
1099 {
1100 err("image '" + fileName + "' does not exist");
1101 return false;
1102 }
1103 int bufSize = (int)f.length();
1104 byte buf[] = new byte[bufSize];
1105 int pos = 0;
1106 try
1107 {
1108 FileInputStream fis = new FileInputStream(fileName);
1109 while (pos < bufSize)
1110 {
1111 int len = fis.read(buf, pos, bufSize - pos);
1112 if (len < 0)
1113 break;
1114 pos += len;
1115 }
1116 fis.close();
1117 }
1118 catch (IOException e)
1119 {
1120 err("reading image '" + fileName + "' :" + e);
1121 return false;
1122 }
1123 if (pos != bufSize)
1124 {
1125 err("reading image entry. expected " +
1126 bufSize + ", found " + pos + " bytes.");
1127 return false;
1128 }
1129 ImageInfo ie = new ImageInfo(fileName, fileName, buf);
1130 images.add(ie);
1131 }
1133 //### Parse styles
1134 styleNr = 0;
1135 styles = new HashMap<String, StyleInfo>();
1136 res = svg.getElementsByTagName("*");
1137 for (int i=0 ; i<res.getLength() ; i++)
1138 {
1139 Element elem = (Element) res.item(i);
1140 trace("elem:"+ elem.getNodeName());
1141 String style = elem.getAttribute("style");
1142 if (style != null && style.length() > 0)
1143 parseCss(style);
1144 }
1146 return true;
1147 }
1149 boolean readSvg(String fileName)
1150 {
1151 try
1152 {
1153 FileInputStream fis = new FileInputStream(fileName);
1154 if (!readSvg(fis))
1155 return false;
1156 fis.close();
1157 }
1158 catch (IOException e)
1159 {
1160 err("opening svg file:"+e);
1161 return false;
1162 }
1163 return true;
1164 }
1167 //########################################################################
1168 //# O D F T O S V G
1169 //########################################################################
1171 /**
1172 *
1173 */
1174 public boolean readOdfEntry(ZipInputStream zis, ZipEntry ent)
1175 {
1176 String fileName = ent.getName();
1177 trace("fileName:" + fileName);
1178 if (fileName.length() < 4)
1179 return true;
1180 String ext = fileName.substring(fileName.length() - 4);
1181 trace("ext:" + ext);
1182 ArrayList<Byte> arr = new ArrayList<Byte>();
1183 try
1184 {
1185 while (true)
1186 {
1187 int inb = zis.read();
1188 if (inb < 0)
1189 break;
1190 arr.add((byte)inb);
1191 }
1192 }
1193 catch (IOException e)
1194 {
1195 return false;
1196 }
1197 byte buf[] = new byte[arr.size()];
1198 for (int i=0 ; i<buf.length ; i++)
1199 buf[i] = arr.get(i);
1200 trace("bufsize:" + buf.length);
1202 if (ext.equals(".xml"))
1203 {
1204 try
1205 {
1206 DocumentBuilderFactory factory =
1207 DocumentBuilderFactory.newInstance();
1208 factory.setNamespaceAware(true);
1209 DocumentBuilder builder =
1210 factory.newDocumentBuilder();
1211 builder.setEntityResolver(new EntityResolver()
1212 {
1213 public InputSource resolveEntity(String publicId, String systemId)
1214 {
1215 return new InputSource(new ByteArrayInputStream(new byte[0]));
1216 }
1217 });
1218 //trace("doc:"+new String(buf));
1219 Document doc = builder.parse(new ByteArrayInputStream(buf));
1220 if (fileName.equals("content.xml"))
1221 content = doc;
1222 else if (fileName.equals("meta.xml"))
1223 meta = doc;
1224 else if (fileName.equals("styles.xml"))
1225 {
1226 //styles = doc;
1227 }
1228 }
1229 catch (javax.xml.parsers.ParserConfigurationException e)
1230 {
1231 err("making DOM parser:"+e);
1232 return false;
1233 }
1234 catch (org.xml.sax.SAXException e)
1235 {
1236 err("parsing document:"+e);
1237 return false;
1238 }
1239 catch (IOException e)
1240 {
1241 err("parsing document:"+e);
1242 return false;
1243 }
1244 }
1245 else if (ext.equals(".png") ||
1246 ext.equals(".gif") ||
1247 ext.equals(".jpg") ||
1248 ext.equals(".jpeg") )
1249 {
1250 String imageName = "image" + imageNr + ext;
1251 imageNr++;
1252 ImageInfo ie = new ImageInfo(fileName, imageName, buf);
1253 trace("added image '" + imageName + "'. " + buf.length +" bytes.");
1254 images.add(ie);
1256 }
1257 dumpDocument(content);
1258 return true;
1259 }
1262 /**
1263 *
1264 */
1265 public boolean readOdf(InputStream ins)
1266 {
1267 imageNr = 0;
1268 images = new ArrayList<ImageInfo>();
1269 styleNr = 0;
1270 styles = new HashMap<String, StyleInfo>();
1271 ZipInputStream zis = new ZipInputStream(ins);
1272 while (true)
1273 {
1274 try
1275 {
1276 ZipEntry ent = zis.getNextEntry();
1277 if (ent == null)
1278 break;
1279 if (!readOdfEntry(zis, ent))
1280 return false;
1281 zis.closeEntry();
1282 }
1283 catch (IOException e)
1284 {
1285 err("reading zip entry");
1286 return false;
1287 }
1288 }
1290 return true;
1291 }
1294 /**
1295 *
1296 */
1297 public boolean readOdf(String fileName)
1298 {
1299 boolean ret = true;
1300 try
1301 {
1302 FileInputStream fis = new FileInputStream(fileName);
1303 ret = readOdf(fis);
1304 fis.close();
1305 }
1306 catch (IOException e)
1307 {
1308 err("reading " + fileName + " : " + e);
1309 ret = false;
1310 }
1311 return true;
1312 }
1317 public boolean writeSvgElement(Element elem)
1318 {
1319 String ns = elem.getNamespaceURI();
1320 String tagName = elem.getLocalName();
1321 trace("tag:" + tagName + " : " + ns);
1322 if (ns.equals(ODSVG_NS))
1323 {
1324 po("<"); po(tagName);
1325 NamedNodeMap attrs = elem.getAttributes();
1326 for (int i=0 ; i<attrs.getLength() ; i++)
1327 {
1328 Attr attr = (Attr)attrs.item(i);
1329 String aname = attr.getName();
1330 String aval = attr.getValue();
1331 //Replace image name
1332 if ("xlink:href".equals(aname) && "image".equals(tagName))
1333 {
1334 for (int j=0 ; j<images.size() ; j++)
1335 {
1336 ImageInfo ie = images.get(i);
1337 if (aval.equals(ie.getName()))
1338 aval = ie.getNewName();
1339 }
1340 }
1341 po(" ");
1342 po(aname);
1343 po("=\"");
1344 po(aval);
1345 po("\"");
1346 }
1347 po(">\n");
1348 }
1349 NodeList children = elem.getChildNodes();
1350 for (int i=0 ; i<children.getLength() ; i++)
1351 {
1352 Node n = children.item(i);
1353 if (n.getNodeType() == Node.ELEMENT_NODE)
1354 if (!writeSvgElement((Element)n))
1355 return false;
1356 }
1357 if (ns.equals(ODSVG_NS))
1358 {
1359 po("</"); po(tagName); po(">\n");
1360 }
1361 return true;
1362 }
1365 public boolean saveSvg(String svgFileName)
1366 {
1367 trace("====== Saving images ===========");
1368 try
1369 {
1370 for (int i=0 ; i<images.size() ; i++)
1371 {
1372 ImageInfo entry = images.get(i);
1373 trace("saving:" + entry.name);
1374 FileOutputStream fos = new FileOutputStream(entry.name);
1375 fos.write(entry.buf);
1376 fos.close();
1377 }
1378 }
1379 catch (IOException e)
1380 {
1381 err("saveAsSVG:" + e);
1382 return false;
1383 }
1385 try
1386 {
1387 out = new BufferedWriter(new FileWriter(svgFileName));
1388 }
1389 catch (IOException e)
1390 {
1391 err("save:" + e);
1392 return false;
1393 }
1395 if (content == null)
1396 {
1397 err("no content in odf");
1398 return false;
1399 }
1401 NodeList res = content.getElementsByTagNameNS(ODF_NS, "drawing");
1402 if (res.getLength() < 1)
1403 {
1404 err("save: no drawing in document");
1405 return false;
1406 }
1407 Element root = (Element)res.item(0);
1408 trace("NS:"+root.getNamespaceURI());
1410 res = root.getElementsByTagNameNS(ODG_NS, "page");
1411 if (res.getLength() < 1)
1412 {
1413 err("save: no page in drawing");
1414 return false;
1415 }
1416 Element page = (Element)res.item(0);
1418 po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1419 po("<svg\n");
1420 po(" xmlns:svg=\"http://www.w3.org/2000/svg\"\n");
1421 po(" xmlns=\"http://www.w3.org/2000/svg\"\n");
1422 po(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
1423 po(" version=\"1.0\"\n");
1424 po(" >\n");
1426 writeSvgElement(page);
1428 po("</svg>\n");
1430 try
1431 {
1432 out.close();
1433 }
1434 catch (IOException e)
1435 {
1436 err("save:close:" + e);
1437 return false;
1438 }
1439 return true;
1440 }
1444 //########################################################################
1445 //# C O N S T R U C T O R
1446 //########################################################################
1448 SvgOdg()
1449 {
1450 //init, if necessary
1451 nrfmt = new DecimalFormat("#.####");
1452 }
1455 //########################################################################
1456 //# C O M M A N D L I N E
1457 //########################################################################
1459 public boolean odfToSvg(String odfName, String svgName)
1460 {
1461 if (!readOdf(odfName))
1462 return false;
1463 if (!saveSvg(svgName))
1464 return false;
1465 return true;
1466 }
1468 public boolean svgToOdf(String svgName, String odfName)
1469 {
1470 if (!readSvg(svgName))
1471 return false;
1472 System.out.println("ok");
1473 if (!saveOdf(odfName))
1474 return false;
1475 return true;
1476 }
1478 void usage()
1479 {
1480 System.out.println("usage: SvgOdf input_file.odg output_file.svg");
1481 System.out.println(" SvgOdf input_file.svg output_file.odg");
1482 }
1485 boolean parseArguments(String argv[])
1486 {
1487 if (argv.length != 2)
1488 {
1489 usage();
1490 return false;
1491 }
1493 String fileName1 = argv[0];
1494 String fileName2 = argv[1];
1496 if (fileName1.length()<5 || fileName2.length()<5)
1497 {
1498 System.out.println("one or more file names is too short");
1499 usage();
1500 return false;
1501 }
1503 String ext1 = fileName1.substring(fileName1.length()-4);
1504 String ext2 = fileName2.substring(fileName2.length()-4);
1505 //System.out.println("ext1:"+ext1+" ext2:"+ext2);
1507 //##### ODG -> SVG #####
1508 if ((ext1.equals(".odg") || ext1.equals(".odf") || ext1.equals(".zip") ) &&
1509 ext2.equals(".svg"))
1510 {
1511 if (!odfToSvg(argv[0], argv[1]))
1512 {
1513 System.out.println("Conversion from ODG to SVG failed");
1514 return false;
1515 }
1516 }
1518 //##### SVG -> ODG #####
1519 else if (ext1.equals(".svg") &&
1520 ( ext2.equals(".odg") || ext2.equals(".odf") || ext2.equals(".zip") ) )
1521 {
1522 if (!svgToOdf(fileName1, fileName2))
1523 {
1524 System.out.println("Conversion from SVG to ODG failed");
1525 return false;
1526 }
1527 }
1529 //##### none of the above #####
1530 else
1531 {
1532 usage();
1533 return false;
1534 }
1535 return true;
1536 }
1539 public static void main(String argv[])
1540 {
1541 SvgOdg svgodg = new SvgOdg();
1542 svgodg.parseArguments(argv);
1543 }
1546 }
1548 //########################################################################
1549 //# E N D O F F I L E
1550 //########################################################################