In other words, I'm trying to do something with JavaFX like what Batik allows you to do with Swing.
I want to be able to capture the appearance of an arbitrary node in my JavaFX UI, much like Node.snapshot() does, except that I need my image in a vector format like SVG, not a raster image. (And inserting a raster snapshot of my node into an SVG image is not good enough; it needs to be a proper, scalable vector image.)
This is a long-term project, so I'm even willing to go as far as implementing my own GraphicsContext, or whatever the equivalent is in JavaFX's retained mode API.
Does anyone know if there is a way to do this? Is what I'm hoping to do even possible in JavaFX?
I started to write a JavaFx Node to SVG converter which "extends" the ShapeConverter from Gerrit Grunwald which only converts Shape geometries:
https://github.com/stefaneidelloth/JavaFxNodeToSvg
... and stopped it after a certain amount of frustration.
Please feel free to improve it, add features and fix bugs.
The converter works for simple Node examples but fails for advanced examples like a Chart. My failed converter might serve you as a starting point. The current state is illustrated by the following figures. The left side shows the original JavaFx node and the right side shows the resulting svg output.
Simple node example (works):
Chart example (does not work):
The corresponging svg files are available here:
https://github.com/stefaneidelloth/JavaFxNodeToSvg/tree/master/output
Some further notes
In addition to the positioning problems that are illustrated by the Chart example above, some further issues have to be considered:
JavaFx provides more css functionality than the SVG standard elements. For example a (rectangular) JavaFx Region can have individual line styles for each of the four border lines. Such a Region can not simply be converted to a SVG rect. Instead, the four border lines of the Region need to be drawn individually. Furthermore, each end of such a border line can have two individual edge radii: a vertical radius and a horizontal radius. In order to convert the "four" border lines to corresponding SVG lines ... it might be necessary to further split the border line: draw two rounded parts and a straight part for each of the four border lines. Therefore, in some cases there might be 12 SVG path elements to draw the border of a single JavaFx Region. In addition, the background fill of the Region can have different radii than the border of the Region. Drawing the background of the Region might requires some more SVG elements. Therefore, even the conversion of a "rectangular Region" can get quite complex.
Please also note that JavaFx nodes might be animated. For example the opacity of a line is 0 at the beginning and fades to another value after a few milliseconds.
FadeTransition ft = new FadeTransition(Duration.millis(250), borderline);
ft.setToValue(0);
ft.play();
Therefore it only makes sense to convert Nodes where animations are disabled or to wait until the Nodes are in a stable state.
I would be very happy if the conversion of JavaFx Charts works one day, so that I can use JavaFx plotting with SVG export in one of my projects.
I decided to stop the development of the converter for the time being and to investigate the plotting and SVG export with JavaScript libraries (d3) instead. If that strategy turns out to be even worse, I might come back to the JavaFxNodeToSvgConverter.
Edit
The plotting with d3.js works very well and I decided not to use JavaFx for the purpose of plotting/svg creation. https://github.com/stefaneidelloth/javafx-d3
There is a simple JavaFX shape to SVG string converter, it will only convert basic shapes without css applied, not arbitrary nodes, but perhaps that might be all you need.
There is an open bug request in JFX JIRA at
https://javafx-jira.kenai.com/browse/RT-38559
(registration required; you can vote for the issue then). It currently says
Consider for a future version.
And is marked for version 9.
The idea is that if you are able to convert the JavaFX Nodes tree structure to a series of Graphics2D orders, then you can use Batik which has a Graphics2D driver.
The thing is that converting the JavaFX tree structure to Graphics2D orders is not as difficult as you might think (even if you process the CSS properties of the Nodes).
Some readers suggested that I should include some code, and not just the link to the library and pictures of it working. It is not so easy to do, because even if it is not so difficult to do, it still has 5000 lines of code.
How to perform the conversion:
- you must have a Graphics2D to convert to SVG (for example the Batik SVGGraphics2D class);
- iterate through the JavaFX structure;
- for each Node in the structure, first convert the current javaFX transforms on this Node to an AffineTransform, and apply the transformation to the Node (you must do it in a Stack to be sure to revert to the initial AffineTransform at the end of the Node);
- and for each Node, you have to transform to the appropriate Graphics2D orders.
Note that you don't need to support a lot of Node types, mainly:
- Regions (controls are a special type of Region which can have an associated Graphics)
- Groups
- Shapes (Text, Rectangle, etc...)
- ImageView for images
You may also need to take care of the Node Clipping to apply the associated Clipping in the Graphics2D.
Also you will have to take care of the CSS properties of the Node.
For all its worth, the library I used (which apllies this algorithm) is here: http://sourceforge.net/projects/jfxconverter/