e31d06b129ad7e5cfec7d5388f439f926b91ca2c
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