1 /**
2 * OpenDocument <drawing> input and output
3 *
4 * This is an an entry in the extensions mechanism to begin to enable
5 * the inputting and outputting of OpenDocument Format (ODF) files from
6 * within Inkscape. Although the initial implementations will be very lossy
7 * do to the differences in the models of SVG and ODF, they will hopefully
8 * improve greatly with time.
9 *
10 * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html
11 *
12 * Authors:
13 * Bob Jamison
14 *
15 * Copyright (C) 2006 Bob Jamison
16 *
17 * This library is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU Lesser General Public
19 * License as published by the Free Software Foundation; either
20 * version 2.1 of the License, or (at your option) any later version.
21 *
22 * This library is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * Lesser General Public License for more details.
26 *
27 * You should have received a copy of the GNU Lesser General Public
28 * License along with this library; if not, write to the Free Software
29 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
30 */
34 #ifdef HAVE_CONFIG_H
35 # include <config.h>
36 #endif
38 #include "odf.h"
40 //# System includes
41 #include <stdio.h>
42 #include <time.h>
43 #include <vector>
46 //# Inkscape includes
47 #include "clear-n_.h"
48 #include "inkscape.h"
49 #include <style.h>
50 #include "display/curve.h"
51 #include "libnr/n-art-bpath.h"
52 #include "extension/system.h"
54 #include "xml/repr.h"
55 #include "xml/attribute-record.h"
56 #include "sp-image.h"
57 #include "sp-path.h"
58 #include "sp-text.h"
59 #include "sp-flowtext.h"
60 #include "svg/svg.h"
61 #include "text-editing.h"
64 //# DOM-specific includes
65 #include "dom/dom.h"
66 #include "dom/util/ziptool.h"
67 #include "dom/io/domstream.h"
68 #include "dom/io/bufferstream.h"
71 //# Shorthand notation
72 typedef org::w3c::dom::DOMString DOMString;
73 typedef org::w3c::dom::io::OutputStreamWriter OutputStreamWriter;
74 typedef org::w3c::dom::io::BufferOutputStream BufferOutputStream;
79 namespace Inkscape
80 {
81 namespace Extension
82 {
83 namespace Internal
84 {
90 //########################################################################
91 //# O U T P U T
92 //########################################################################
94 static std::string getAttribute( Inkscape::XML::Node *node, char *attrName)
95 {
96 std::string val;
97 char *valstr = (char *)node->attribute(attrName);
98 if (valstr)
99 val = (const char *)valstr;
100 return val;
101 }
104 static std::string getExtension(const std::string &fname)
105 {
106 std::string ext;
108 unsigned int pos = fname.rfind('.');
109 if (pos == fname.npos)
110 {
111 ext = "";
112 }
113 else
114 {
115 ext = fname.substr(pos);
116 }
117 return ext;
118 }
120 /**
121 * Method descends into the repr tree, converting image and style info
122 * into forms compatible in ODF.
123 */
124 void
125 OdfOutput::preprocess(ZipFile &zf, Inkscape::XML::Node *node)
126 {
128 std::string nodeName = node->name();
130 if (nodeName == "image" || nodeName == "svg:image")
131 {
132 //g_message("image");
133 std::string href = getAttribute(node, "xlink:href");
134 if (href.size() > 0)
135 {
136 std::string oldName = href;
137 std::string ext = getExtension(oldName);
138 if (ext == ".jpeg")
139 ext = ".jpg";
140 if (imageTable.find(oldName) == imageTable.end())
141 {
142 char buf[64];
143 snprintf(buf, 63, "Pictures/image%d%s",
144 imageTable.size(), ext.c_str());
145 std::string newName = buf;
146 imageTable[oldName] = newName;
147 std::string comment = "old name was: ";
148 comment.append(oldName);
149 ZipEntry *ze = zf.addFile(oldName, comment);
150 if (ze)
151 {
152 ze->setFileName(newName);
153 }
154 else
155 {
156 g_warning("Could not load image file '%s'", oldName.c_str());
157 }
158 }
159 }
160 }
164 //Look for style values in the svg element
165 Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attr =
166 node->attributeList();
167 for ( ; attr ; ++attr)
168 {
169 if (!attr->key || !attr->value)
170 {
171 g_warning("null key or value in attribute");
172 continue;
173 }
174 //g_message("key:%s value:%s", g_quark_to_string(attr->key),
175 // g_quark_to_string(attr->value) );
177 std::string attrName = (const char *)g_quark_to_string(attr->key);
178 std::string attrValue = (const char *)attr->value;
179 g_message("tag:'%s' key:'%s' value:'%s'",
180 nodeName.c_str(), attrName.c_str(), attrValue.c_str() );
181 if (attrName == "style")
182 {
183 StyleInfo si(attrName, attrValue);
184 if (styleTable.find(attrValue) != styleTable.end())
185 {
186 g_message("duplicate style");
187 }
188 else
189 {
190 char buf[16];
191 snprintf(buf, 15, "style%d", styleTable.size());
192 std::string attrName = buf;
193 //Map from value-->name . Looks backwards, i know
194 styleTable[attrValue] = si;
195 g_message("mapping '%s' to '%s'",
196 attrValue.c_str(), attrName.c_str());
197 }
198 }
199 }
203 for (Inkscape::XML::Node *child = node->firstChild() ;
204 child ; child = child->next())
205 preprocess(zf, child);
206 }
210 bool OdfOutput::writeManifest(ZipFile &zf)
211 {
212 BufferOutputStream bouts;
213 OutputStreamWriter outs(bouts);
215 time_t tim;
216 time(&tim);
218 outs.printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
219 outs.printf("<!DOCTYPE manifest:manifest PUBLIC \"-//OpenOffice.org//DTD Manifest 1.0//EN\" \"Manifest.dtd\">\n");
220 outs.printf("\n");
221 outs.printf("\n");
222 outs.printf("<!--\n");
223 outs.printf("*************************************************************************\n");
224 outs.printf(" file: manifest.xml\n");
225 outs.printf(" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr>
226 outs.printf(" http://www.inkscape.org\n");
227 outs.printf("*************************************************************************\n");
228 outs.printf("-->\n");
229 outs.printf("\n");
230 outs.printf("\n");
231 outs.printf("<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">\n");
232 outs.printf(" <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.graphics\" manifest:full-path=\"/\"/>\n");
233 outs.printf(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>\n");
234 //outs.printf(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/>\n");
235 outs.printf(" <!--List our images here-->\n");
236 std::map<std::string, std::string>::iterator iter;
237 for (iter = imageTable.begin() ; iter!=imageTable.end() ; iter++)
238 {
239 std::string oldName = iter->first;
240 std::string newName = iter->second;
242 std::string ext = getExtension(oldName);
243 if (ext == ".jpeg")
244 ext = ".jpg";
245 outs.printf(" <manifest:file-entry manifest:media-type=\"");
246 if (ext == ".gif")
247 outs.printf("image/gif");
248 else if (ext == ".png")
249 outs.printf("image/png");
250 else if (ext == ".jpg")
251 outs.printf("image/jpeg");
252 outs.printf("\" manifest:full-path=\"");
253 outs.printf((char *)newName.c_str());
254 outs.printf("\"/>\n");
255 }
256 outs.printf("</manifest:manifest>\n");
258 outs.close();
260 //Make our entry
261 ZipEntry *ze = zf.newEntry("META-INF/manifest.xml", "ODF file manifest");
262 ze->setUncompressedData(bouts.getBuffer());
263 ze->finish();
265 return true;
266 }
272 bool OdfOutput::writeStyle(Writer &outs)
273 {
274 outs.printf("<office:automatic-styles>\n");
275 outs.printf("<style:style style:name=\"dp1\" style:family=\"drawing-page\"/>\n");
276 outs.printf("<style:style style:name=\"grx1\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n");
277 outs.printf(" <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");
278 outs.printf("</style:style>\n");
279 outs.printf("<style:style style:name=\"P1\" style:family=\"paragraph\">\n");
280 outs.printf(" <style:paragraph-properties fo:text-align=\"center\"/>\n");
281 outs.printf("</style:style>\n");
283 //## Dump our style table
284 /*
285 std::map<std::string, std::string>::iterator iter;
286 for (iter = styleTable.begin() ; iter != styleTable.end() ; iter++)
287 {
288 outs.printf("<style:style style:name=\"%s\"", iter->second);
289 outs.printf(" style:family=\"graphic\" style:parent-style-name=\"standard\">\n");
290 outs.printf(" <style:graphic-properties");
291 outs.printf(" draw:fill=\"" + s.getFill() + "\"");
292 if (!s.getFill().equals("none"))
293 outs.printf(" draw:fill-color=\"" + s.getFillColor() + "\"");
294 outs.printf(" draw:stroke=\"" + s.getStroke() + "\"");
295 if (!s.getStroke().equals("none"))
296 {
297 outs.printf(" svg:stroke-width=\"" + s.getStrokeWidth() + "\"");
298 outs.printf(" svg:stroke-color=\"" + s.getStrokeColor() + "\"");
299 }
300 outs.printf("/>\n");
301 outs.printf("</style:style>\n");
302 }
303 */
304 outs.printf("</office:automatic-styles>\n");
305 outs.printf("\n");
307 return true;
308 }
313 bool OdfOutput::writeTree(Writer &outs, Inkscape::XML::Node *node)
314 {
315 //# Get the SPItem, if applicable
316 SPObject *reprobj = SP_ACTIVE_DOCUMENT->getObjectByRepr(node);
317 if (!reprobj)
318 return true;
319 if (!SP_IS_ITEM(reprobj))
320 {
321 return true;
322 }
323 SPItem *item = SP_ITEM(reprobj);
325 //# Do our stuff
326 SPCurve *curve = NULL;
327 std::string tagName = "path";
328 if (SP_IS_IMAGE(item))
329 {
330 tagName = "image";
331 std::string href = getAttribute(node, "xlink:href");
332 std::map<std::string, std::string>::iterator iter = imageTable.find(href);
333 if (iter == imageTable.end())
334 {
335 g_warning("image '%s' not in table", href.c_str());
336 return false;
337 }
338 std::string newName = iter->second;
339 outs.printf("<image xlink:href=\"%s\"/>\n", newName.c_str());
340 return true;
341 }
342 else if (SP_IS_SHAPE(item))
343 {
344 curve = sp_shape_get_curve(SP_SHAPE(item));
345 }
346 else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))
347 {
348 curve = te_get_layout(item)->convertToCurves();
349 }
351 if (curve)
352 {
354 //Inkscape::XML::Node *repr = sp_repr_new("svg:path");
355 /* Transformation */
356 //repr->setAttribute("transform", SP_OBJECT_REPR(item)->attribute("transform"));
358 /* Rotation center */
359 //sp_repr_set_attr(repr, "inkscape:transform-center-x", SP_OBJECT_REPR(item)->attribute("inkscape:transform-center-x"));
360 //sp_repr_set_attr(repr, "inkscape:transform-center-y", SP_OBJECT_REPR(item)->attribute("inkscape:transform-center-y"));
362 /* Definition */
363 gchar *def_str = sp_svg_write_path(curve->bpath);
365 g_free(def_str);
366 sp_curve_unref(curve);
367 }
369 //# Iterate through the children
370 for (Inkscape::XML::Node *child = node->firstChild() ; child ; child = child->next())
371 {
372 if (!writeTree(outs, child))
373 return false;
374 }
375 return true;
376 }
381 bool OdfOutput::writeContent(ZipFile &zf, Inkscape::XML::Node *node)
382 {
383 BufferOutputStream bouts;
384 OutputStreamWriter outs(bouts);
386 time_t tim;
387 time(&tim);
389 outs.printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
390 outs.printf("\n");
391 outs.printf("\n");
392 outs.printf("<!--\n");
393 outs.printf("*************************************************************************\n");
394 outs.printf(" file: content.xml\n");
395 outs.printf(" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr>
396 outs.printf(" http://www.inkscape.org\n");
397 outs.printf("*************************************************************************\n");
398 outs.printf("-->\n");
399 outs.printf("\n");
400 outs.printf("\n");
401 outs.printf("<office:document-content\n");
402 outs.printf(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n");
403 outs.printf(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n");
404 outs.printf(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n");
405 outs.printf(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n");
406 outs.printf(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n");
407 outs.printf(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n");
408 outs.printf(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
409 outs.printf(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
410 outs.printf(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n");
411 outs.printf(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n");
412 outs.printf(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n");
413 outs.printf(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n");
414 outs.printf(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n");
415 outs.printf(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n");
416 outs.printf(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n");
417 outs.printf(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n");
418 outs.printf(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n");
419 outs.printf(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n");
420 outs.printf(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n");
421 outs.printf(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n");
422 outs.printf(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n");
423 outs.printf(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n");
424 outs.printf(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n");
425 outs.printf(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
426 outs.printf(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n");
427 outs.printf(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n");
428 outs.printf(" office:version=\"1.0\">\n");
429 outs.printf("\n");
430 outs.printf("\n");
431 outs.printf("<office:scripts/>\n");
432 outs.printf("\n");
433 outs.printf("\n");
434 //AffineTransform trans = new AffineTransform();
435 //trans.scale(12.0, 12.0);
436 outs.printf("<!-- ######### CONVERSION FROM SVG STARTS ######## -->\n");
437 outs.printf("<!--\n");
438 outs.printf("*************************************************************************\n");
439 outs.printf(" S T Y L E S\n");
440 outs.printf(" Style entries have been pulled from the svg style and\n");
441 outs.printf(" representation attributes in the SVG tree. The tree elements\n");
442 outs.printf(" then refer to them by name, in the ODF manner\n");
443 outs.printf("*************************************************************************\n");
444 outs.printf("-->\n");
445 outs.printf("\n");
446 outs.printf("\n");
448 if (!writeStyle(outs))
449 {
450 g_warning("Failed to write styles");
451 return false;
452 }
454 outs.printf("\n");
455 outs.printf("\n");
456 outs.printf("\n");
457 outs.printf("\n");
458 outs.printf("<!--\n");
459 outs.printf("*************************************************************************\n");
460 outs.printf(" D R A W I N G\n");
461 outs.printf(" This section is the heart of SVG-ODF conversion. We are\n");
462 outs.printf(" starting with simple conversions, and will slowly evolve\n");
463 outs.printf(" into a 'smarter' translation as time progresses. Any help\n");
464 outs.printf(" in improving .odg export is welcome.\n");
465 outs.printf("*************************************************************************\n");
466 outs.printf("-->\n");
467 outs.printf("\n");
468 outs.printf("\n");
469 outs.printf("<office:body>\n");
470 outs.printf("<office:drawing>\n");
471 outs.printf("<draw:page draw:name=\"page1\" draw:style-name=\"dp1\" draw:master-page-name=\"Default\">\n");
472 outs.printf("\n");
473 outs.printf("\n");
475 if (!writeTree(outs, node))
476 {
477 g_warning("Failed to convert SVG tree");
478 return false;
479 }
481 outs.printf("\n");
482 outs.printf("\n");
484 outs.printf("</draw:page>\n");
485 outs.printf("</office:drawing>\n");
487 outs.printf("\n");
488 outs.printf("\n");
489 outs.printf("<!-- ######### CONVERSION FROM SVG ENDS ######## -->\n");
490 outs.printf("\n");
491 outs.printf("\n");
493 outs.printf("</office:body>\n");
494 outs.printf("</office:document-content>\n");
495 outs.printf("\n");
496 outs.printf("\n");
497 outs.printf("\n");
498 outs.printf("<!--\n");
499 outs.printf("*************************************************************************\n");
500 outs.printf(" E N D O F F I L E\n");
501 outs.printf(" Have a nice day -- ishmal\n");
502 outs.printf("*************************************************************************\n");
503 outs.printf("-->\n");
504 outs.printf("\n");
505 outs.printf("\n");
509 //Make our entry
510 ZipEntry *ze = zf.newEntry("content.xml", "ODF master content file");
511 ze->setUncompressedData(bouts.getBuffer());
512 ze->finish();
514 return true;
515 }
520 /**
521 * Descends into the SVG tree, mapping things to ODF when appropriate
522 */
523 void
524 OdfOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *uri)
525 {
526 ZipFile zf;
527 styleTable.clear();
528 imageTable.clear();
529 preprocess(zf, doc->rroot);
530 if (!writeManifest(zf))
531 {
532 g_warning("Failed to write manifest");
533 return;
534 }
535 if (!writeContent(zf, doc->rroot))
536 {
537 g_warning("Failed to write content");
538 return;
539 }
540 if (!zf.writeFile(uri))
541 {
542 return;
543 }
544 }
547 /**
548 * This is the definition of PovRay output. This function just
549 * calls the extension system with the memory allocated XML that
550 * describes the data.
551 */
552 void
553 OdfOutput::init()
554 {
555 Inkscape::Extension::build_from_mem(
556 "<inkscape-extension>\n"
557 "<name>" N_("OpenDocument Drawing Output") "</name>\n"
558 "<id>org.inkscape.output.odf</id>\n"
559 "<output>\n"
560 "<extension>.odg</extension>\n"
561 "<mimetype>text/x-povray-script</mimetype>\n"
562 "<filetypename>" N_("OpenDocument drawing (*.odg)") "</filetypename>\n"
563 "<filetypetooltip>" N_("OpenDocument drawing file") "</filetypetooltip>\n"
564 "</output>\n"
565 "</inkscape-extension>",
566 new OdfOutput());
567 }
569 /**
570 * Make sure that we are in the database
571 */
572 bool
573 OdfOutput::check (Inkscape::Extension::Extension *module)
574 {
575 /* We don't need a Key
576 if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_POV))
577 return FALSE;
578 */
580 return TRUE;
581 }
585 //########################################################################
586 //# I N P U T
587 //########################################################################
591 //#######################
592 //# L A T E R !!! :-)
593 //#######################
607 } //namespace Internal
608 } //namespace Extension
609 } //namespace Inkscape
612 //########################################################################
613 //# E N D O F F I L E
614 //########################################################################
616 /*
617 Local Variables:
618 mode:c++
619 c-file-style:"stroustrup"
620 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
621 indent-tabs-mode:nil
622 fill-column:99
623 End:
624 */
625 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :