Code

e31d06b129ad7e5cfec7d5388f439f926b91ca2c
[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 <sp-linear-gradient.h>\r
24 #include <sp-radial-gradient.h>\r
25 #include <style.h>\r
26 #include <display/curve.h>\r
27 #include <svg/svg.h>\r
28 #include <extension/system.h>\r
29 #include <2geom/pathvector.h>\r
30 #include <2geom/rect.h>\r
31 #include <2geom/bezier-curve.h>\r
32 #include <2geom/hvlinesegment.h>\r
33 #include "helper/geom.h"\r
34 #include "helper/geom-curves.h"\r
35 #include <io/sys.h>\r
36 \r
37 \r
38 #include <string>\r
39 #include <stdio.h>\r
40 #include <stdarg.h>\r
41 \r
42 \r
43 namespace Inkscape\r
44 {\r
45 namespace Extension\r
46 {\r
47 namespace Internal\r
48 {\r
49 \r
50 \r
51 \r
52 \r
53 //########################################################################\r
54 //# M E S S A G E S\r
55 //########################################################################\r
56 \r
57 static void err(const char *fmt, ...)\r
58 {\r
59     va_list args;\r
60     va_start(args, fmt);\r
61     g_logv(NULL, G_LOG_LEVEL_WARNING, fmt, args);\r
62     va_end(args);\r
63 }\r
64 \r
65 \r
66 //########################################################################\r
67 //# U T I L I T Y\r
68 //########################################################################\r
69 \r
70 /**\r
71  * Got this method from Bulia, and modified it a bit.  It basically\r
72  * starts with this style, gets its SPObject parent, walks up the object\r
73  * tree and finds all of the opacities and multiplies them.\r
74  *\r
75  * We use this for our "flat" object output.  If the code is modified\r
76  * to reflect a tree of <groups>, then this will be unneccessary.\r
77  */\r
78 static double effective_opacity(const SPStyle *style)\r
79 {\r
80     double val = 1.0;\r
81     for (SPObject const *obj = style->object; obj ; obj = obj->parent)\r
82         {\r
83         style = SP_OBJECT_STYLE(obj);\r
84         if (style)\r
85             val *= SP_SCALE24_TO_FLOAT(style->opacity.value);\r
86         }\r
87     return val;\r
88 }\r
89 \r
90 //########################################################################\r
91 //# OUTPUT FORMATTING\r
92 //########################################################################\r
93 \r
94 \r
95 /**\r
96  * We want to control floating output format\r
97  */\r
98 static JavaFXOutput::String dstr(double d)\r
99 {\r
100     char dbuf[G_ASCII_DTOSTR_BUF_SIZE+1];\r
101     g_ascii_formatd(dbuf, G_ASCII_DTOSTR_BUF_SIZE,\r
102                   "%.8f", (gdouble)d);\r
103     JavaFXOutput::String s = dbuf;\r
104     return s;\r
105 }\r
106 \r
107 \r
108 \r
109 /**\r
110  * Format an rgba() string\r
111  */\r
112 static JavaFXOutput::String rgba(guint32 rgba)\r
113 {\r
114     double r = SP_RGBA32_R_F(rgba);\r
115     double g = SP_RGBA32_G_F(rgba);\r
116     double b = SP_RGBA32_B_F(rgba);\r
117     double a = SP_RGBA32_A_F(rgba);\r
118     char buf[128];\r
119     snprintf(buf, 79, "Color.color(%s, %s, %s, %s)", \r
120            dstr(r).c_str(), dstr(g).c_str(), dstr(b).c_str(), dstr(a).c_str());\r
121     JavaFXOutput::String s = buf;\r
122     return s;\r
123 }\r
124 \r
125 \r
126 /**\r
127  * Format an rgba() string for a color and a 0.0-1.0 alpha\r
128  */\r
129 static JavaFXOutput::String rgba(SPColor color, gdouble alpha)\r
130 {\r
131     return rgba(color.toRGBA32(alpha));\r
132 }\r
133 \r
134 \r
135 \r
136 \r
137 /**\r
138  *  Output data to the buffer, printf()-style\r
139  */\r
140 void JavaFXOutput::out(const char *fmt, ...)\r
141 {\r
142     va_list args;\r
143     va_start(args, fmt);\r
144     gchar *output = g_strdup_vprintf(fmt, args);\r
145     va_end(args);\r
146     outbuf.append(output);\r
147     g_free(output);\r
148 }\r
149 \r
150 /**\r
151  *  Output header data to the buffer, printf()-style\r
152  */\r
153 void JavaFXOutput::fout(const char *fmt, ...)\r
154 {\r
155     va_list args;\r
156     va_start(args, fmt);\r
157     gchar *output = g_strdup_vprintf(fmt, args);\r
158     va_end(args);\r
159     foutbuf.append(output);\r
160     g_free(output);\r
161 }\r
162 \r
163 \r
164 /**\r
165  * Output the file header\r
166  */\r
167 bool JavaFXOutput::doHeader()\r
168 {\r
169     time_t tim = time(NULL);\r
170     out("/*###################################################################\n");\r
171     out("### This JavaFX document was generated by Inkscape\n");\r
172     out("### http://www.inkscape.org\n");\r
173     out("### Created: %s", ctime(&tim));\r
174     out("### Version: %s\n", INKSCAPE_VERSION);\r
175     out("#####################################################################\n");\r
176     out("### NOTES:\n");\r
177     out("### ============\n");\r
178     out("### JavaFX information can be found at\n");\r
179     out("### hhttps://openjfx.dev.java.net\n");\r
180     out("###\n");\r
181     out("### If you have any problems with this output, please see the\n");\r
182     out("### Inkscape project at http://www.inkscape.org, or visit\n");\r
183     out("### the #inkscape channel on irc.freenode.net . \n");\r
184     out("###\n");\r
185     out("###################################################################*/\n");\r
186     out("\n\n");\r
187     out("/*###################################################################\n");\r
188     out("##   Exports in this file\n");\r
189     out("##==========================\n");\r
190     out("##    Shapes   : %d\n", nrShapes);\r
191     out("##    Nodes    : %d\n", nrNodes);\r
192     out("###################################################################*/\n");\r
193     out("\n\n");\r
194     out("import javafx.application.*;\n");\r
195     out("import javafx.scene.*;\n");\r
196     out("import javafx.scene.geometry.*;\n");\r
197     out("import javafx.scene.paint.*;\n");\r
198     out("import javafx.scene.transform.*;\n");\r
199     out("\n");\r
200     out("import java.lang.System;\n");\r
201     out("\n\n");\r
202     out("public class %s extends CustomNode {\n", name.c_str());\r
203     out("\n\n");\r
204     outbuf.append(foutbuf);\r
205     out("\n\n");\r
206     out("public function create() : Node\n");\r
207     out("{\n");\r
208     out("return Group\n");\r
209     out("    {\n");\r
210     out("    content:\n");\r
211     out("        [\n");\r
212     return true;\r
213 }\r
214 \r
215 \r
216 \r
217 /**\r
218  *  Output the file footer\r
219  */\r
220 bool JavaFXOutput::doTail()\r
221 {\r
222     int border = 25.0;\r
223     out("        ] // content\n");\r
224     out("    transform: [ Translate.translate(%s, %s), ]\n",\r
225                   dstr((-minx) + border).c_str(), dstr((-miny) + border).c_str());\r
226     out("    } // Group\n");\r
227     out("}  // end function create()\n");\r
228     out("\n\n");\r
229     out("}\n");\r
230     out("/*###################################################################\n");\r
231     out("### E N D   C L A S S    %s\n", name.c_str());\r
232     out("###################################################################*/\n");\r
233     out("\n\n\n\n");\r
234     out("\n");\r
235     out("Frame {\n");\r
236     out("    title: \"TestFrame\"\n");\r
237     out("    width: (%s).intValue()\n", dstr(maxx-minx + border * 2.0).c_str());\r
238     out("    height: (%s).intValue()\n", dstr(maxy-miny + border * 2.0).c_str());\r
239     out("    closeAction: function()\n");\r
240     out("        {\n");\r
241     out("        System.exit( 0 );\n");\r
242     out("        }\n");\r
243     out("    visible: true\n");\r
244     out("    stage: Stage {\n");\r
245     out("        content: %s{}\n", name.c_str());\r
246     out("    }\n");\r
247     out("}\n");\r
248     out("\n\n");\r
249     out("/*###################################################################\n");\r
250     out("### E N D   C L A S S    %s\n", name.c_str());\r
251     out("###################################################################*/\n");\r
252     out("\n\n");\r
253     return true;\r
254 }\r
255 \r
256 \r
257 \r
258 /**\r
259  *  Output gradient information to the buffer\r
260  */\r
261 bool JavaFXOutput::doGradient(SPGradient *grad, const String &id)\r
262 {\r
263     if (SP_IS_LINEARGRADIENT(grad))\r
264         {\r
265         SPLinearGradient *g = SP_LINEARGRADIENT(grad);\r
266         fout("function %s() : LinearGradient\n", id.c_str());\r
267         fout("{\n");\r
268         fout("    return LinearGradient\n");\r
269         fout("        {\n");\r
270         std::vector<SPGradientStop> stops = g->vector.stops;\r
271         if (stops.size() > 0)\r
272             {\r
273             fout("        stops:\n");\r
274             fout("            [\n");\r
275             for (unsigned int i = 0 ; i<stops.size() ; i++)\r
276                 {\r
277                 SPGradientStop stop = stops[i];\r
278                 fout("            Stop\n");\r
279                 fout("                {\n");\r
280                 fout("                offset: %s\n", dstr(stop.offset).c_str());\r
281                 fout("                color: %s\n", rgba(stop.color, stop.opacity).c_str());\r
282                 fout("                },\n");\r
283                 }\r
284             fout("            ]\n");\r
285             }\r
286         fout("        }\n");\r
287         fout("}\n");\r
288         fout("\n\n");\r
289         }\r
290     else if (SP_IS_RADIALGRADIENT(grad))\r
291         {\r
292         SPRadialGradient *g = SP_RADIALGRADIENT(grad);\r
293         fout("function %s() : RadialGradient\n", id.c_str());\r
294         fout("{\n");\r
295         fout("    return RadialGradient\n");\r
296         fout("        {\n");\r
297         fout("        centerX: %s\n", dstr(g->cx.value).c_str());\r
298         fout("        centerY: %s\n", dstr(g->cy.value).c_str());\r
299         fout("        focusX: %s\n", dstr(g->fx.value).c_str());\r
300         fout("        focusY: %s\n", dstr(g->fy.value).c_str());\r
301         fout("        radius: %s\n", dstr(g->r.value).c_str());\r
302         std::vector<SPGradientStop> stops = g->vector.stops;\r
303         if (stops.size() > 0)\r
304             {\r
305             fout("        stops:\n");\r
306             fout("            [\n");\r
307             for (unsigned int i = 0 ; i<stops.size() ; i++)\r
308                 {\r
309                 SPGradientStop stop = stops[i];\r
310                 fout("            Stop\n");\r
311                 fout("                {\n");\r
312                 fout("                offset: %s\n", dstr(stop.offset).c_str());\r
313                 fout("                color: %s\n", rgba(stop.color, stop.opacity).c_str());\r
314                 fout("                },\n");\r
315                 }\r
316             fout("            ]\n");\r
317             }\r
318         fout("        }\n");\r
319         fout("}\n");\r
320         fout("\n\n");\r
321         }\r
322     else\r
323         {\r
324         err("Unknown gradient type for '%s'\n", id.c_str());\r
325         return false;\r
326         }\r
327 \r
328 \r
329     return true;\r
330 }\r
331 \r
332 \r
333 \r
334 \r
335 /**\r
336  *  Output an element's style attribute\r
337  */\r
338 bool JavaFXOutput::doStyle(SPStyle *style)\r
339 {\r
340     if (!style)\r
341         return true;\r
342 \r
343     out("        opacity: %s\n", dstr(effective_opacity(style)).c_str());\r
344 \r
345     /**\r
346      * Fill\r
347      */\r
348     SPIPaint fill = style->fill;\r
349     if (fill.isColor())\r
350         {\r
351         // see color.h for how to parse SPColor\r
352         out("        fill: %s\n",\r
353             rgba(fill.value.color, SP_SCALE24_TO_FLOAT(style->fill_opacity.value)).c_str());\r
354         }\r
355     else if (fill.isPaintserver())\r
356         {\r
357         if (fill.value.href && fill.value.href->getURI() )\r
358             {\r
359             String uri = fill.value.href->getURI()->toString();\r
360             if (uri.size()>0 && uri[0]=='#')\r
361                 uri = uri.substr(1);\r
362             out("        fill: %s()\n", uri.c_str());\r
363             }\r
364         }\r
365 \r
366 \r
367     /**\r
368      * Stroke\r
369      */\r
370     /**\r
371      *NOTE:  Things in style we can use:\r
372      * SPIPaint stroke;\r
373      * SPILength stroke_width;\r
374      * SPIEnum stroke_linecap;\r
375      * SPIEnum stroke_linejoin;\r
376      * SPIFloat stroke_miterlimit;\r
377      * NRVpathDash stroke_dash;\r
378      * unsigned stroke_dasharray_set : 1;\r
379      * unsigned stroke_dasharray_inherit : 1;\r
380      * unsigned stroke_dashoffset_set : 1;\r
381      * SPIScale24 stroke_opacity;\r
382      */\r
383     if (style->stroke_opacity.value > 0)\r
384         {\r
385         SPIPaint stroke = style->stroke;\r
386         out("        stroke: %s\n",\r
387             rgba(stroke.value.color, SP_SCALE24_TO_FLOAT(style->stroke_opacity.value)).c_str());\r
388         double strokewidth = style->stroke_width.value;\r
389         out("        strokeWidth: %s\n", dstr(strokewidth).c_str());\r
390         }\r
391 \r
392     return true;\r
393 }\r
394 \r
395 \r
396 \r
397 \r
398 /**\r
399  *  Output the curve data to buffer\r
400  */\r
401 bool JavaFXOutput::doCurve(SPItem *item, const String &id)\r
402 {\r
403     using Geom::X;\r
404     using Geom::Y;\r
405 \r
406     //### Get the Shape\r
407     if (!SP_IS_SHAPE(item))//Bulia's suggestion.  Allow all shapes\r
408         return true;\r
409 \r
410     SPShape *shape = SP_SHAPE(item);\r
411     SPCurve *curve = shape->curve;\r
412     if (curve->is_empty())\r
413         return true;\r
414 \r
415     nrShapes++;\r
416 \r
417     out("        SVGPath \n");\r
418     out("        {\n");\r
419     out("        id: \"%s\"\n", id.c_str());\r
420 \r
421     /**\r
422      * Output the style information\r
423      */\r
424     if (!doStyle(SP_OBJECT_STYLE(shape)))\r
425         return false;\r
426 \r
427     // convert the path to only lineto's and cubic curveto's:\r
428     Geom::Scale yflip(1.0, -1.0);\r
429     Geom::Matrix tf = sp_item_i2d_affine(item) * yflip;\r
430     Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( curve->get_pathvector() * tf );\r
431     \r
432     //Count the NR_CURVETOs/LINETOs (including closing line segment)\r
433     nrNodes = 0;\r
434     for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) {\r
435         nrNodes += (*it).size();\r
436         if (it->closed())\r
437             nrNodes += 1;\r
438     }\r
439 \r
440     char *dataStr = sp_svg_write_path(pathv);\r
441     out("        content: \"%s\"\n", dataStr);\r
442     free(dataStr);\r
443 \r
444     Geom::Rect cminmax( pathv.front().initialPoint(), pathv.front().initialPoint() ); \r
445 \r
446     /**\r
447      * Get the Min and Max X and Y extends for the Path. \r
448      * ....For all Subpaths in the <path>\r
449      */      \r
450     for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit)\r
451         {\r
452         cminmax.expandTo(pit->front().initialPoint());\r
453         /**\r
454          * For all segments in the subpath\r
455          */                      \r
456         for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit)\r
457             {\r
458             cminmax.expandTo(cit->finalPoint());\r
459             }\r
460         }\r
461 \r
462     out("        },\n");\r
463 \r
464     double cminx = cminmax.min()[X];\r
465     double cmaxx = cminmax.max()[X];\r
466     double cminy = cminmax.min()[Y];\r
467     double cmaxy = cminmax.max()[Y];\r
468 \r
469     if (cminx < minx)\r
470         minx = cminx;\r
471     if (cmaxx > maxx)\r
472         maxx = cmaxx;\r
473     if (cminy < miny)\r
474         miny = cminy;\r
475     if (cmaxy > maxy)\r
476         maxy = cmaxy;\r
477 \r
478     return true;\r
479 }\r
480 \r
481 \r
482 /**\r
483  *  Output the tree data to buffer\r
484  */\r
485 bool JavaFXOutput::doTreeRecursive(SPDocument *doc, SPObject *obj)\r
486 {\r
487     /**\r
488      * Check the type of node and process\r
489      */\r
490     String id;\r
491     if (!obj->id)\r
492         {\r
493         char buf[16];\r
494         sprintf(buf, "id%d", idindex++);\r
495         id = buf;\r
496         }\r
497     else\r
498         {\r
499         id = obj->id;\r
500         }\r
501     if (SP_IS_ITEM(obj))\r
502         {\r
503         SPItem *item = SP_ITEM(obj);\r
504         if (!doCurve(item, id))\r
505             return false;\r
506         }\r
507     else if (SP_IS_GRADIENT(obj))\r
508         {\r
509         SPGradient *grad = SP_GRADIENT(obj);\r
510         if (!doGradient(grad, id))\r
511             return false;\r
512         }\r
513 \r
514     /**\r
515      * Descend into children\r
516      */      \r
517     for (SPObject *child = obj->firstChild() ; child ; child = child->next)\r
518         {\r
519                 if (!doTreeRecursive(doc, child))\r
520                     return false;\r
521                 }\r
522 \r
523     return true;\r
524 }\r
525 \r
526 \r
527 /**\r
528  *  Output the curve data to buffer\r
529  */\r
530 bool JavaFXOutput::doTree(SPDocument *doc)\r
531 {\r
532 \r
533     double bignum = 1000000.0;\r
534     minx  =  bignum;\r
535     maxx  = -bignum;\r
536     miny  =  bignum;\r
537     maxy  = -bignum;\r
538 \r
539     if (!doTreeRecursive(doc, doc->root))\r
540         return false;\r
541 \r
542     return true;\r
543 \r
544 }\r
545 \r
546 \r
547 \r
548 \r
549 //########################################################################\r
550 //# M A I N    O U T P U T\r
551 //########################################################################\r
552 \r
553 \r
554 \r
555 /**\r
556  *  Set values back to initial state\r
557  */\r
558 void JavaFXOutput::reset()\r
559 {\r
560     nrNodes    = 0;\r
561     nrShapes   = 0;\r
562     idindex    = 0;\r
563     name.clear();\r
564     outbuf.clear();\r
565     foutbuf.clear();\r
566 }\r
567 \r
568 \r
569 \r
570 /**\r
571  * Saves the <paths> of an Inkscape SVG file as JavaFX spline definitions\r
572  */\r
573 bool JavaFXOutput::saveDocument(SPDocument *doc, gchar const *uri)\r
574 {\r
575     reset();\r
576 \r
577 \r
578     name = Glib::path_get_basename(uri);\r
579     int pos = name.find('.');\r
580     if (pos > 0)\r
581         name = name.substr(0, pos);\r
582 \r
583 \r
584     //###### SAVE IN POV FORMAT TO BUFFER\r
585     //# Lets do the curves first, to get the stats\r
586     \r
587     if (!doTree(doc))\r
588         return false;\r
589     String curveBuf = outbuf;\r
590     outbuf.clear();\r
591 \r
592     if (!doHeader())\r
593         return false;\r
594     \r
595     outbuf.append(curveBuf);\r
596 \r
597     if (!doTail())\r
598         return false;\r
599 \r
600 \r
601 \r
602 \r
603     //###### WRITE TO FILE\r
604     FILE *f = Inkscape::IO::fopen_utf8name(uri, "w");\r
605     if (!f)\r
606         {\r
607         err("Could open JavaFX file '%s' for writing", uri);\r
608         return false;\r
609         }\r
610 \r
611     for (String::iterator iter = outbuf.begin() ; iter!=outbuf.end(); iter++)\r
612         {\r
613         fputc(*iter, f);\r
614         }\r
615         \r
616     fclose(f);\r
617     \r
618     return true;\r
619 }\r
620 \r
621 \r
622 \r
623 \r
624 //########################################################################\r
625 //# EXTENSION API\r
626 //########################################################################\r
627 \r
628 \r
629 \r
630 #include "clear-n_.h"\r
631 \r
632 \r
633 \r
634 /**\r
635  * API call to save document\r
636 */\r
637 void\r
638 JavaFXOutput::save(Inkscape::Extension::Output */*mod*/,\r
639                         SPDocument *doc, gchar const *uri)\r
640 {\r
641     if (!saveDocument(doc, uri))\r
642         {\r
643         g_warning("Could not save JavaFX file '%s'", uri);\r
644         }\r
645 }\r
646 \r
647 \r
648 \r
649 /**\r
650  * Make sure that we are in the database\r
651  */\r
652 bool JavaFXOutput::check (Inkscape::Extension::Extension */*module*/)\r
653 {\r
654     /* We don't need a Key\r
655     if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_JFX))\r
656         return FALSE;\r
657     */\r
658 \r
659     return true;\r
660 }\r
661 \r
662 \r
663 \r
664 /**\r
665  * This is the definition of JavaFX output.  This function just\r
666  * calls the extension system with the memory allocated XML that\r
667  * describes the data.\r
668 */\r
669 void\r
670 JavaFXOutput::init()\r
671 {\r
672     Inkscape::Extension::build_from_mem(\r
673         "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"\r
674             "<name>" N_("JavaFX Output") "</name>\n"\r
675             "<id>org.inkscape.output.jfx</id>\n"\r
676             "<output>\n"\r
677                 "<extension>.fx</extension>\n"\r
678                 "<mimetype>text/x-javafx-script</mimetype>\n"\r
679                 "<filetypename>" N_("JavaFX (*.fx)") "</filetypename>\n"\r
680                 "<filetypetooltip>" N_("JavaFX Raytracer File") "</filetypetooltip>\n"\r
681             "</output>\n"\r
682         "</inkscape-extension>",\r
683         new JavaFXOutput());\r
684 }\r
685 \r
686 \r
687 \r
688 \r
689 \r
690 }  // namespace Internal\r
691 }  // namespace Extension\r
692 }  // namespace Inkscape\r
693 \r
694 \r
695 /*\r
696   Local Variables:\r
697   mode:c++\r
698   c-file-style:"stroustrup"\r
699   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
700   indent-tabs-mode:nil\r
701   fill-column:99\r
702   End:\r
703 */\r
704 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r