/* * A simple utility for exporting Inkscape svg Shapes as JavaFX paths. * * For information on the JavaFX file format, see: * https://openjfx.dev.java.net/ * * Authors: * Bob Jamison * * Copyright (C) 2008 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ #ifdef HAVE_CONFIG_H # include #endif #include "javafx-out.h" #include #include #include #include #include #include #include #include #include <2geom/pathvector.h> #include <2geom/rect.h> #include <2geom/bezier-curve.h> #include <2geom/hvlinesegment.h> #include "helper/geom.h" #include #include #include #include namespace Inkscape { namespace Extension { namespace Internal { //######################################################################## //# M E S S A G E S //######################################################################## static void err(const char *fmt, ...) { va_list args; va_start(args, fmt); g_logv(NULL, G_LOG_LEVEL_WARNING, fmt, args); va_end(args); } //######################################################################## //# U T I L I T Y //######################################################################## /** * Got this method from Bulia, and modified it a bit. It basically * starts with this style, gets its SPObject parent, walks up the object * tree and finds all of the opacities and multiplies them. * * We use this for our "flat" object output. If the code is modified * to reflect a tree of , then this will be unneccessary. */ static double effective_opacity(const SPStyle *style) { double val = 1.0; for (SPObject const *obj = style->object; obj ; obj = obj->parent) { style = SP_OBJECT_STYLE(obj); if (!style) return val; val *= SP_SCALE24_TO_FLOAT(style->opacity.value); } return val; } //######################################################################## //# OUTPUT FORMATTING //######################################################################## /** * We want to control floating output format */ static JavaFXOutput::String dstr(double d) { char dbuf[G_ASCII_DTOSTR_BUF_SIZE+1]; g_ascii_formatd(dbuf, G_ASCII_DTOSTR_BUF_SIZE, "%.8f", (gdouble)d); JavaFXOutput::String s = dbuf; return s; } /** * Format an rgba() string */ static JavaFXOutput::String rgba(guint32 rgba) { unsigned int r = SP_RGBA32_R_U(rgba); unsigned int g = SP_RGBA32_G_U(rgba); unsigned int b = SP_RGBA32_B_U(rgba); unsigned int a = SP_RGBA32_A_U(rgba); char buf[80]; snprintf(buf, 79, "rgba(0x%02x, 0x%02x, 0x%02x, 0x%02x)", r, g, b, a); JavaFXOutput::String s = buf; return s; } /** * Format an rgba() string for a color and a 0.0-1.0 alpha */ static JavaFXOutput::String rgba(SPColor color, gdouble alpha) { return rgba(color.toRGBA32(alpha)); } /** * Output data to the buffer, printf()-style */ void JavaFXOutput::out(const char *fmt, ...) { va_list args; va_start(args, fmt); gchar *output = g_strdup_vprintf(fmt, args); va_end(args); outbuf.append(output); g_free(output); } /** * Output header data to the buffer, printf()-style */ void JavaFXOutput::fout(const char *fmt, ...) { va_list args; va_start(args, fmt); gchar *output = g_strdup_vprintf(fmt, args); va_end(args); foutbuf.append(output); g_free(output); } /** * Output the file header */ bool JavaFXOutput::doHeader() { time_t tim = time(NULL); out("/*###################################################################\n"); out("### This JavaFX document was generated by Inkscape\n"); out("### http://www.inkscape.org\n"); out("### Created: %s", ctime(&tim)); out("### Version: %s\n", INKSCAPE_VERSION); out("#####################################################################\n"); out("### NOTES:\n"); out("### ============\n"); out("### JavaFX information can be found at\n"); out("### hhttps://openjfx.dev.java.net\n"); out("###\n"); out("### If you have any problems with this output, please see the\n"); out("### Inkscape project at http://www.inkscape.org, or visit\n"); out("### the #inkscape channel on irc.freenode.net . \n"); out("###\n"); out("###################################################################*/\n"); out("\n\n"); out("/*###################################################################\n"); out("## Exports in this file\n"); out("##==========================\n"); out("## Shapes : %d\n", nrShapes); out("## Segments : %d\n", nrSegments); out("## Nodes : %d\n", nrNodes); out("###################################################################*/\n"); out("\n\n"); out("import javafx.ui.UIElement;\n"); out("import javafx.ui.*;\n"); out("import javafx.ui.canvas.*;\n"); out("\n"); out("import java.lang.System;\n"); out("\n\n"); out("public class %s extends CompositeNode {\n", name.c_str()); for (unsigned int i = 0 ; i < linearGradients.size() ; i++) { out(" public function %s(): LinearGradient;\n", linearGradients[i].c_str()); } for (unsigned int i = 0 ; i < radialGradients.size() ; i++) { out(" public function %s(): RadialGradient;\n", radialGradients[i].c_str()); } out("}\n"); out("\n\n"); outbuf.append(foutbuf); out("\n\n"); out("function %s.composeNode() =\n", name.c_str()); out("Group\n"); out(" {\n"); out(" content:\n"); out(" [\n"); return true; } /** * Output the file footer */ bool JavaFXOutput::doTail() { int border = 25.0; out(" ] // content\n"); out(" transform: [ translate(%s, %s), ]\n", dstr((-minx) + border).c_str(), dstr((-miny) + border).c_str()); out(" }; // Group\n"); out("// end function %s.composeNode()\n", name.c_str()); out("\n\n\n\n"); out("Frame {\n"); out(" title: \"Test\"\n"); out(" width: %s\n", dstr(maxx-minx + border * 2.0).c_str()); out(" height: %s\n", dstr(maxy-miny + border * 2.0).c_str()); out(" onClose: function()\n"); out(" {\n"); out(" return System.exit( 0 );\n"); out(" }\n"); out(" visible: true\n"); out(" content: Canvas {\n"); out(" content: %s{}\n", name.c_str()); out(" }\n"); out("}\n"); out("/*###################################################################\n"); out("### E N D C L A S S %s\n", name.c_str()); out("###################################################################*/\n"); out("\n\n"); return true; } /** * Output gradient information to the buffer */ bool JavaFXOutput::doGradient(SPGradient *grad, const String &id) { if (SP_IS_LINEARGRADIENT(grad)) { SPLinearGradient *g = SP_LINEARGRADIENT(grad); linearGradients.push_back(id); fout("function %s.%s() =\n", name.c_str(), id.c_str()); fout(" [\n"); fout(" LinearGradient\n"); fout(" {\n"); std::vector stops = g->vector.stops; if (stops.size() > 0) { fout(" stops:\n"); fout(" [\n"); for (unsigned int i = 0 ; icx.value).c_str()); fout(" cy: %s\n", dstr(g->cy.value).c_str()); fout(" focusX: %s\n", dstr(g->fx.value).c_str()); fout(" focusY: %s\n", dstr(g->fy.value).c_str()); fout(" radius: %s\n", dstr(g->r.value).c_str()); fout(" gradientUnits: OBJECT_BOUNDING_BOX\n"); fout(" spreadMethod: PAD\n"); std::vector stops = g->vector.stops; if (stops.size() > 0) { fout(" stops:\n"); fout(" [\n"); for (unsigned int i = 0 ; ifill; if (fill.isColor()) { // see color.h for how to parse SPColor out(" fill: %s\n", rgba(fill.value.color, SP_SCALE24_TO_FLOAT(style->fill_opacity.value)).c_str()); } else if (fill.isPaintserver()) { if (fill.value.href && fill.value.href->getURI() ) { String uri = fill.value.href->getURI()->toString(); if (uri.size()>0 && uri[0]=='#') uri = uri.substr(1); out(" fill: %s()\n", uri.c_str()); } } /** * Stroke */ /** *NOTE: Things in style we can use: * SPIPaint stroke; * SPILength stroke_width; * SPIEnum stroke_linecap; * SPIEnum stroke_linejoin; * SPIFloat stroke_miterlimit; * NRVpathDash stroke_dash; * unsigned stroke_dasharray_set : 1; * unsigned stroke_dasharray_inherit : 1; * unsigned stroke_dashoffset_set : 1; * SPIScale24 stroke_opacity; */ if (style->stroke_opacity.value > 0) { SPIPaint stroke = style->stroke; out(" stroke: %s\n", rgba(stroke.value.color, SP_SCALE24_TO_FLOAT(style->stroke_opacity.value)).c_str()); double strokewidth = style->stroke_width.value; out(" strokeWidth: %s\n", dstr(strokewidth).c_str()); } return true; } /** * Output the curve data to buffer */ bool JavaFXOutput::doCurve(SPItem *item, const String &id) { using Geom::X; using Geom::Y; //### Get the Shape if (!SP_IS_SHAPE(item))//Bulia's suggestion. Allow all shapes return true; SPShape *shape = SP_SHAPE(item); SPCurve *curve = shape->curve; if (curve->is_empty()) return true; nrShapes++; out(" /*###################################################\n"); out(" ### PATH: %s\n", id.c_str()); out(" ###################################################*/\n"); out(" Path \n"); out(" {\n"); out(" id: \"%s\"\n", id.c_str()); /** * Output the style information */ if (!doStyle(SP_OBJECT_STYLE(shape))) return false; // convert the path to only lineto's and cubic curveto's: Geom::Scale yflip(1.0, -1.0); Geom::Matrix tf = sp_item_i2d_affine(item) * yflip; Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( curve->get_pathvector() * tf ); //Count the NR_CURVETOs/LINETOs (including closing line segment) guint segmentCount = 0; for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) { segmentCount += (*it).size(); if (it->closed()) segmentCount += 1; } out(" d:\n"); out(" [\n"); unsigned int segmentNr = 0; nrSegments += segmentCount; Geom::Rect cminmax( pathv.front().initialPoint(), pathv.front().initialPoint() ); /** * For all Subpaths in the */ for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) { Geom::Point p = pit->front().initialPoint(); cminmax.expandTo(p); out(" MoveTo {\n"); out(" x: %s\n", dstr(p[X]).c_str()); out(" y: %s\n", dstr(p[Y]).c_str()); out(" absolute: true\n"); out(" },\n"); /** * For all segments in the subpath */ for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) { //### LINE if( dynamic_cast (&*cit) || dynamic_cast(&*cit) || dynamic_cast(&*cit) ) { Geom::Point p = cit->finalPoint(); out(" LineTo {\n"); out(" x: %s\n", dstr(p[X]).c_str()); out(" y: %s\n", dstr(p[Y]).c_str()); out(" absolute: true\n"); out(" },\n"); nrNodes++; } //### BEZIER else if(Geom::CubicBezier const *cubic = dynamic_cast(&*cit)) { std::vector points = cubic->points(); Geom::Point p1 = points[1]; Geom::Point p2 = points[2]; Geom::Point p3 = points[3]; out(" CurveTo {\n"); out(" x1: %s\n", dstr(p1[X]).c_str()); out(" y1: %s\n", dstr(p1[Y]).c_str()); out(" x2: %s\n", dstr(p2[X]).c_str()); out(" y2: %s\n", dstr(p2[Y]).c_str()); out(" x3: %s\n", dstr(p3[X]).c_str()); out(" y3: %s\n", dstr(p3[Y]).c_str()); out(" absolute: true\n"); out(" },\n"); nrNodes++; } else { g_error ("logical error, because pathv_to_linear_and_cubic_beziers was used"); } segmentNr++; cminmax.expandTo(cit->finalPoint()); } if (pit->closed()) { out(" ClosePath {},\n"); } } out(" ] // d\n"); out(" }, // Path\n"); out(" /*###################################################\n"); out(" ### end path %s\n", id.c_str()); out(" ###################################################*/\n\n\n\n"); double cminx = cminmax.min()[X]; double cmaxx = cminmax.max()[X]; double cminy = cminmax.min()[Y]; double cmaxy = cminmax.max()[Y]; if (cminx < minx) minx = cminx; if (cmaxx > maxx) maxx = cmaxx; if (cminy < miny) miny = cminy; if (cmaxy > maxy) maxy = cmaxy; return true; } /** * Output the curve data to buffer */ bool JavaFXOutput::doCurvesRecursive(SPDocument *doc, Inkscape::XML::Node *node) { /** * Check the type of node and process */ String id; char *idstr = (char *) node->attribute("id"); if (!idstr) { char buf[16]; sprintf(buf, "id%d", idindex++); id = buf; } else { id = idstr; } SPObject *reprobj = doc->getObjectByRepr(node); if (SP_IS_ITEM(reprobj)) { SPItem *item = SP_ITEM(reprobj); if (!doCurve(item, id)) return false; } else if (SP_IS_GRADIENT(reprobj)) { SPGradient *grad = SP_GRADIENT(reprobj); if (!doGradient(grad, id)) return false; } /** * Descend into children */ for (Inkscape::XML::Node *child = node->firstChild() ; child ; child = child->next()) { if (!doCurvesRecursive(doc, child)) return false; } return true; } /** * Output the curve data to buffer */ bool JavaFXOutput::doCurves(SPDocument *doc) { double bignum = 1000000.0; minx = bignum; maxx = -bignum; miny = bignum; maxy = -bignum; if (!doCurvesRecursive(doc, doc->rroot)) return false; return true; } //######################################################################## //# M A I N O U T P U T //######################################################################## /** * Set values back to initial state */ void JavaFXOutput::reset() { nrNodes = 0; nrSegments = 0; nrShapes = 0; idindex = 0; name.clear(); outbuf.clear(); foutbuf.clear(); linearGradients.clear(); radialGradients.clear(); } /** * Saves the of an Inkscape SVG file as JavaFX spline definitions */ bool JavaFXOutput::saveDocument(SPDocument *doc, gchar const *uri) { reset(); name = Glib::path_get_basename(uri); int pos = name.find('.'); if (pos > 0) name = name.substr(0, pos); //###### SAVE IN POV FORMAT TO BUFFER //# Lets do the curves first, to get the stats if (!doCurves(doc)) return false; String curveBuf = outbuf; outbuf.clear(); if (!doHeader()) return false; outbuf.append(curveBuf); if (!doTail()) return false; //###### WRITE TO FILE FILE *f = Inkscape::IO::fopen_utf8name(uri, "w"); if (!f) { err("Could open JavaFX file '%s' for writing", uri); return false; } for (String::iterator iter = outbuf.begin() ; iter!=outbuf.end(); iter++) { fputc(*iter, f); } fclose(f); return true; } //######################################################################## //# EXTENSION API //######################################################################## #include "clear-n_.h" /** * API call to save document */ void JavaFXOutput::save(Inkscape::Extension::Output */*mod*/, SPDocument *doc, gchar const *uri) { if (!saveDocument(doc, uri)) { g_warning("Could not save JavaFX file '%s'", uri); } } /** * Make sure that we are in the database */ bool JavaFXOutput::check (Inkscape::Extension::Extension */*module*/) { /* We don't need a Key if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_JFX)) return FALSE; */ return true; } /** * This is the definition of JavaFX output. This function just * calls the extension system with the memory allocated XML that * describes the data. */ void JavaFXOutput::init() { Inkscape::Extension::build_from_mem( "\n" "" N_("JavaFX Output") "\n" "org.inkscape.output.jfx\n" "\n" ".fx\n" "text/x-javafx-script\n" "" N_("JavaFX (*.fx)") "\n" "" N_("JavaFX Raytracer File") "\n" "\n" "", new JavaFXOutput()); } } // namespace Internal } // namespace Extension } // namespace Inkscape /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :