Code

finally got transforms right. flip Y and translate
[inkscape.git] / src / extension / internal / javafx-out.cpp
1 /*\r
2  * A simple utility for exporting Inkscape svg Shapes as JavaFX paths.\r
3  *\r
4  *  For information on the JavaFX file format, see:\r
5  *      https://openjfx.dev.java.net/\r
6  *\r
7  * Authors:\r
8  *   Bob Jamison <ishmal@inkscape.org>\r
9  *\r
10  * Copyright (C) 2008 Authors\r
11  *\r
12  * Released under GNU GPL, read the file 'COPYING' for more information\r
13  */\r
14 \r
15 \r
16 #ifdef HAVE_CONFIG_H\r
17 # include <config.h>\r
18 #endif\r
19 #include "javafx-out.h"\r
20 #include <inkscape.h>\r
21 #include <inkscape_version.h>\r
22 #include <sp-path.h>\r
23 #include <style.h>\r
24 #include <display/curve.h>\r
25 #include <libnr/n-art-bpath.h>\r
26 #include <extension/system.h>\r
27 #include <2geom/pathvector.h>\r
28 #include <2geom/rect.h>\r
29 #include <2geom/bezier-curve.h>\r
30 #include <2geom/hvlinesegment.h>\r
31 #include "helper/geom.h"\r
32 #include <io/sys.h>\r
33 \r
34 #include <string>\r
35 #include <stdio.h>\r
36 #include <stdarg.h>\r
37 \r
38 \r
39 namespace Inkscape\r
40 {\r
41 namespace Extension\r
42 {\r
43 namespace Internal\r
44 {\r
45 \r
46 \r
47 \r
48 \r
49 //########################################################################\r
50 //# OUTPUT FORMATTING\r
51 //########################################################################\r
52 \r
53 \r
54 /**\r
55  * We want to control floating output format\r
56  */\r
57 static JavaFXOutput::String dstr(double d)\r
58 {\r
59     char dbuf[G_ASCII_DTOSTR_BUF_SIZE+1];\r
60     g_ascii_formatd(dbuf, G_ASCII_DTOSTR_BUF_SIZE,\r
61                   "%.8f", (gdouble)d);\r
62     JavaFXOutput::String s = dbuf;\r
63     return s;\r
64 }\r
65 \r
66 \r
67 \r
68 \r
69 /**\r
70  *  Output data to the buffer, printf()-style\r
71  */\r
72 void JavaFXOutput::out(const char *fmt, ...)\r
73 {\r
74     va_list args;\r
75     va_start(args, fmt);\r
76     gchar *output = g_strdup_vprintf(fmt, args);\r
77     va_end(args);\r
78     outbuf.append(output);\r
79     g_free(output);\r
80 }\r
81 \r
82 \r
83 /**\r
84  * Output the file header\r
85  */\r
86 bool JavaFXOutput::doHeader(const String &name)\r
87 {\r
88     time_t tim = time(NULL);\r
89     out("/*###################################################################\n");\r
90     out("### This JavaFX document was generated by Inkscape\n");\r
91     out("### http://www.inkscape.org\n");\r
92     out("### Created: %s", ctime(&tim));\r
93     out("### Version: %s\n", INKSCAPE_VERSION);\r
94     out("#####################################################################\n");\r
95     out("### NOTES:\n");\r
96     out("### ============\n");\r
97     out("### JavaFX information can be found at\n");\r
98     out("### hhttps://openjfx.dev.java.net\n");\r
99     out("###\n");\r
100     out("### If you have any problems with this output, please see the\n");\r
101     out("### Inkscape project at http://www.inkscape.org, or visit\n");\r
102     out("### the #inkscape channel on irc.freenode.net . \n");\r
103     out("###\n");\r
104     out("###################################################################*/\n");\r
105     out("\n\n");\r
106     out("/*###################################################################\n");\r
107     out("##   Exports in this file\n");\r
108     out("##==========================\n");\r
109     out("##    Shapes   : %d\n", nrShapes);\r
110     out("##    Segments : %d\n", nrSegments);\r
111     out("##    Nodes    : %d\n", nrNodes);\r
112     out("###################################################################*/\n");\r
113     out("\n\n");\r
114     out("import javafx.ui.UIElement;\n");\r
115     out("import javafx.ui.*;\n");\r
116     out("import javafx.ui.canvas.*;\n");\r
117     out("\n");\r
118     out("import java.lang.System;\n");\r
119     out("\n\n");\r
120     out("public class %s extends CompositeNode {\n", name.c_str());\r
121     out("}\n");\r
122     out("\n\n");\r
123     out("function %s.composeNode() =\n", name.c_str());\r
124     out("Group\n");\r
125     out("    {\n");\r
126     out("    content:\n");\r
127     out("        [\n");\r
128     return true;\r
129 }\r
130 \r
131 \r
132 \r
133 /**\r
134  *  Output the file footer\r
135  */\r
136 bool JavaFXOutput::doTail(const String &name)\r
137 {\r
138     int border = 25.0;\r
139     out("        ] // content\n");\r
140     out("    transform: [ translate(%s, %s), ]\n",\r
141                   dstr((-minx) + border).c_str(), dstr((-miny) + border).c_str());\r
142     out("    }; // Group\n");\r
143     out("// end function %s.composeNode()\n", name.c_str());\r
144     out("\n\n\n\n");\r
145     out("Frame {\n");\r
146     out("    title: \"Test\"\n");\r
147     out("    width: %s\n", dstr(maxx-minx + border * 2.0).c_str());\r
148     out("    height: %s\n", dstr(maxy-miny + border * 2.0).c_str());\r
149     out("    onClose: function()\n");\r
150     out("        {\n");\r
151     out("        return System.exit( 0 );\n");\r
152     out("        }\n");\r
153     out("    visible: true\n");\r
154     out("    content: Canvas {\n");\r
155     out("        content: %s{}\n", name.c_str());\r
156     out("    }\n");\r
157     out("}\n");\r
158     out("/*###################################################################\n");\r
159     out("### E N D   C L A S S    %s\n", name.c_str());\r
160     out("###################################################################*/\n");\r
161     out("\n\n");\r
162     return true;\r
163 }\r
164 \r
165 \r
166 /**\r
167  *  Output the curve data to buffer\r
168  */\r
169 bool JavaFXOutput::doCurve(SPItem *item, const String &id)\r
170 {\r
171     using Geom::X;\r
172     using Geom::Y;\r
173 \r
174     //### Get the Shape\r
175     if (!SP_IS_SHAPE(item))//Bulia's suggestion.  Allow all shapes\r
176         return true;\r
177 \r
178     SPShape *shape = SP_SHAPE(item);\r
179     SPCurve *curve = shape->curve;\r
180     if (curve->is_empty())\r
181         return true;\r
182 \r
183     nrShapes++;\r
184 \r
185     out("        /*###################################################\n");\r
186     out("        ### PATH:  %s\n", id.c_str());\r
187     out("        ###################################################*/\n");\r
188     out("        Path \n");\r
189     out("        {\n");\r
190     out("        id: \"%s\"\n", id.c_str());\r
191 \r
192     /**\r
193      * Get the fill and stroke of the shape\r
194      */\r
195     SPStyle *style = SP_OBJECT_STYLE(shape);\r
196     if (style)\r
197         {\r
198         /**\r
199          * Fill\r
200          */\r
201         if (style->fill.isColor())\r
202             {\r
203             // see color.h for how to parse SPColor\r
204             gint alpha = 0xffffffff;\r
205             guint32 rgba = style->fill.value.color.toRGBA32(alpha);\r
206             unsigned int r = SP_RGBA32_R_U(rgba);\r
207             unsigned int g = SP_RGBA32_G_U(rgba);\r
208             unsigned int b = SP_RGBA32_B_U(rgba);\r
209             unsigned int a = SP_RGBA32_A_U(rgba);\r
210             out("        fill: rgba(0x%02x, 0x%02x, 0x%02x, 0x%02x)\n",\r
211                                r, g, b, a);\r
212             }\r
213         /**\r
214          * Stroke\r
215          */\r
216         /**\r
217          *NOTE:  Things in style we can use:\r
218              * SPIPaint stroke;\r
219              * SPILength stroke_width;\r
220              * SPIEnum stroke_linecap;\r
221              * SPIEnum stroke_linejoin;\r
222              * SPIFloat stroke_miterlimit;\r
223              * NRVpathDash stroke_dash;\r
224              * unsigned stroke_dasharray_set : 1;\r
225              * unsigned stroke_dasharray_inherit : 1;\r
226              * unsigned stroke_dashoffset_set : 1;\r
227              * SPIScale24 stroke_opacity;\r
228          */\r
229         if (style->stroke_opacity.value > 0)\r
230             {\r
231             gint alpha = 0xffffffff;\r
232             guint32 rgba = style->stroke.value.color.toRGBA32(alpha);\r
233             unsigned int r = SP_RGBA32_R_U(rgba);\r
234             unsigned int g = SP_RGBA32_G_U(rgba);\r
235             unsigned int b = SP_RGBA32_B_U(rgba);\r
236             unsigned int a = SP_RGBA32_A_U(rgba);\r
237             out("        stroke: rgba(0x%02x, 0x%02x, 0x%02x, 0x%02x)\n",\r
238                                r, g, b, a);\r
239             double strokewidth = style->stroke_width.value;\r
240             out("        strokeWidth: %s\n", dstr(strokewidth).c_str());\r
241             }\r
242         }\r
243 \r
244 \r
245     // convert the path to only lineto's and cubic curveto's:\r
246     Geom::Scale yflip(1.0, -1.0);\r
247     Geom::Matrix tf = sp_item_i2d_affine(item) * yflip;\r
248     Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( curve->get_pathvector() * tf );\r
249 \r
250     //Count the NR_CURVETOs/LINETOs (including closing line segment)\r
251     guint segmentCount = 0;\r
252     for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) {\r
253         segmentCount += (*it).size();\r
254         if (it->closed())\r
255             segmentCount += 1;\r
256     }\r
257 \r
258     out("        d:\n");\r
259     out("            [\n");\r
260 \r
261     unsigned int segmentNr = 0;\r
262 \r
263     nrSegments += segmentCount;\r
264 \r
265     Geom::Rect cminmax( pathv.front().initialPoint(), pathv.front().initialPoint() ); \r
266 \r
267     /**\r
268      * For all Subpaths in the <path>\r
269      */      \r
270     for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit)\r
271         {\r
272         Geom::Point p = pit->front().initialPoint();\r
273         cminmax.expandTo(p);\r
274         out("            MoveTo {\n");\r
275         out("                x: %s\n", dstr(p[X]).c_str());\r
276         out("                y: %s\n", dstr(p[Y]).c_str());\r
277         out("                absolute: true\n");\r
278         out("                },\n");\r
279         \r
280         /**\r
281          * For all segments in the subpath\r
282          */                      \r
283         for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit)\r
284             {\r
285             //### LINE\r
286             if( dynamic_cast<Geom::LineSegment const *> (&*cit) ||\r
287                 dynamic_cast<Geom::HLineSegment const *>(&*cit) ||\r
288                 dynamic_cast<Geom::VLineSegment const *>(&*cit) )\r
289                 {\r
290                 Geom::Point p = cit->finalPoint();\r
291                 out("            LineTo {\n");\r
292                 out("                x: %s\n", dstr(p[X]).c_str());\r
293                 out("                y: %s\n", dstr(p[Y]).c_str());\r
294                 out("                absolute: true\n");\r
295                 out("                },\n");\r
296                 nrNodes++;\r
297                 }\r
298             //### BEZIER\r
299             else if(Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit))\r
300                 {\r
301                 std::vector<Geom::Point> points = cubic->points();\r
302                 Geom::Point p1 = points[1];\r
303                 Geom::Point p2 = points[2];\r
304                 Geom::Point p3 = points[3];\r
305                 out("            CurveTo {\n");\r
306                 out("                x1: %s\n", dstr(p1[X]).c_str());\r
307                 out("                y1: %s\n", dstr(p1[Y]).c_str());\r
308                 out("                x2: %s\n", dstr(p2[X]).c_str());\r
309                 out("                y2: %s\n", dstr(p2[Y]).c_str());\r
310                 out("                x3: %s\n", dstr(p3[X]).c_str());\r
311                 out("                y3: %s\n", dstr(p3[Y]).c_str());\r
312                 out("                absolute: true\n");\r
313                 out("                },\n");\r
314                 nrNodes++;\r
315                 }\r
316             else\r
317                 {\r
318                 g_error ("logical error, because pathv_to_linear_and_cubic_beziers was used");\r
319                 }\r
320             segmentNr++;\r
321             cminmax.expandTo(cit->finalPoint());\r
322             }\r
323         if (pit->closed())\r
324             {\r
325             out("            ClosePath {},\n");\r
326             }\r
327         }\r
328 \r
329     out("            ] // d\n");\r
330     out("        }, // Path\n");\r
331 \r
332                  \r
333     out("        /*###################################################\n");\r
334     out("        ### end path %s\n", id.c_str());\r
335     out("        ###################################################*/\n\n\n\n");\r
336 \r
337     double cminx = cminmax.min()[X];\r
338     double cmaxx = cminmax.max()[X];\r
339     double cminy = cminmax.min()[Y];\r
340     double cmaxy = cminmax.max()[Y];\r
341 \r
342     if (cminx < minx)\r
343         minx = cminx;\r
344     if (cmaxx > maxx)\r
345         maxx = cmaxx;\r
346     if (cminy < miny)\r
347         miny = cminy;\r
348     if (cmaxy > maxy)\r
349         maxy = cmaxy;\r
350 \r
351     return true;\r
352 }\r
353 \r
354 \r
355 /**\r
356  *  Output the curve data to buffer\r
357  */\r
358 bool JavaFXOutput::doCurvesRecursive(SPDocument *doc, Inkscape::XML::Node *node)\r
359 {\r
360     /**\r
361      * If the object is an Item, try processing it\r
362      */      \r
363     char *str  = (char *) node->attribute("id");\r
364     SPObject *reprobj = doc->getObjectByRepr(node);\r
365     if (SP_IS_ITEM(reprobj) && str)\r
366         {\r
367         SPItem *item = SP_ITEM(reprobj);\r
368         String id = str;\r
369         if (!doCurve(item, id))\r
370             return false;\r
371         }\r
372 \r
373     /**\r
374      * Descend into children\r
375      */      \r
376     for (Inkscape::XML::Node *child = node->firstChild() ; child ;\r
377               child = child->next())\r
378         {\r
379                 if (!doCurvesRecursive(doc, child))\r
380                     return false;\r
381                 }\r
382 \r
383     return true;\r
384 }\r
385 \r
386 \r
387 /**\r
388  *  Output the curve data to buffer\r
389  */\r
390 bool JavaFXOutput::doCurves(SPDocument *doc)\r
391 {\r
392 \r
393     double bignum = 1000000.0;\r
394     minx  =  bignum;\r
395     maxx  = -bignum;\r
396     miny  =  bignum;\r
397     maxy  = -bignum;\r
398 \r
399     if (!doCurvesRecursive(doc, doc->rroot))\r
400         return false;\r
401 \r
402     return true;\r
403 \r
404 }\r
405 \r
406 \r
407 \r
408 \r
409 //########################################################################\r
410 //# M A I N    O U T P U T\r
411 //########################################################################\r
412 \r
413 \r
414 \r
415 /**\r
416  *  Set values back to initial state\r
417  */\r
418 void JavaFXOutput::reset()\r
419 {\r
420     nrNodes    = 0;\r
421     nrSegments = 0;\r
422     nrShapes   = 0;\r
423     outbuf.clear();\r
424 }\r
425 \r
426 \r
427 \r
428 /**\r
429  * Saves the <paths> of an Inkscape SVG file as JavaFX spline definitions\r
430  */\r
431 bool JavaFXOutput::saveDocument(SPDocument *doc, gchar const *uri)\r
432 {\r
433     reset();\r
434 \r
435 \r
436     String name = Glib::path_get_basename(uri);\r
437     int pos = name.find('.');\r
438     if (pos > 0)\r
439         name = name.substr(0, pos);\r
440 \r
441 \r
442     //###### SAVE IN POV FORMAT TO BUFFER\r
443     //# Lets do the curves first, to get the stats\r
444     \r
445     if (!doCurves(doc))\r
446         return false;\r
447     String curveBuf = outbuf;\r
448     outbuf.clear();\r
449 \r
450     if (!doHeader(name))\r
451         return false;\r
452     \r
453     outbuf.append(curveBuf);\r
454     \r
455     if (!doTail(name))\r
456         return false;\r
457 \r
458 \r
459 \r
460 \r
461     //###### WRITE TO FILE\r
462     FILE *f = Inkscape::IO::fopen_utf8name(uri, "w");\r
463     if (!f)\r
464         {\r
465         g_warning("Could open JavaFX file '%s' for writing", uri);\r
466         return false;\r
467         }\r
468 \r
469     for (String::iterator iter = outbuf.begin() ; iter!=outbuf.end(); iter++)\r
470         {\r
471         fputc(*iter, f);\r
472         }\r
473         \r
474     fclose(f);\r
475     \r
476     return true;\r
477 }\r
478 \r
479 \r
480 \r
481 \r
482 //########################################################################\r
483 //# EXTENSION API\r
484 //########################################################################\r
485 \r
486 \r
487 \r
488 #include "clear-n_.h"\r
489 \r
490 \r
491 \r
492 /**\r
493  * API call to save document\r
494 */\r
495 void\r
496 JavaFXOutput::save(Inkscape::Extension::Output */*mod*/,\r
497                         SPDocument *doc, gchar const *uri)\r
498 {\r
499     if (!saveDocument(doc, uri))\r
500         {\r
501         g_warning("Could not save JavaFX file '%s'", uri);\r
502         }\r
503 }\r
504 \r
505 \r
506 \r
507 /**\r
508  * Make sure that we are in the database\r
509  */\r
510 bool JavaFXOutput::check (Inkscape::Extension::Extension */*module*/)\r
511 {\r
512     /* We don't need a Key\r
513     if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_JFX))\r
514         return FALSE;\r
515     */\r
516 \r
517     return true;\r
518 }\r
519 \r
520 \r
521 \r
522 /**\r
523  * This is the definition of JavaFX output.  This function just\r
524  * calls the extension system with the memory allocated XML that\r
525  * describes the data.\r
526 */\r
527 void\r
528 JavaFXOutput::init()\r
529 {\r
530     Inkscape::Extension::build_from_mem(\r
531         "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"\r
532             "<name>" N_("JavaFX Output") "</name>\n"\r
533             "<id>org.inkscape.output.jfx</id>\n"\r
534             "<output>\n"\r
535                 "<extension>.fx</extension>\n"\r
536                 "<mimetype>text/x-javafx-script</mimetype>\n"\r
537                 "<filetypename>" N_("JavaFX (*.fx)") "</filetypename>\n"\r
538                 "<filetypetooltip>" N_("JavaFX Raytracer File") "</filetypetooltip>\n"\r
539             "</output>\n"\r
540         "</inkscape-extension>",\r
541         new JavaFXOutput());\r
542 }\r
543 \r
544 \r
545 \r
546 \r
547 \r
548 }  // namespace Internal\r
549 }  // namespace Extension\r
550 }  // namespace Inkscape\r
551 \r
552 \r
553 /*\r
554   Local Variables:\r
555   mode:c++\r
556   c-file-style:"stroustrup"\r
557   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
558   indent-tabs-mode:nil\r
559   fill-column:99\r
560   End:\r
561 */\r
562 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r