mgarin/weblaf

SVG support based on SVG Salamander and Batik

mgarin opened this issue · 5 comments

Provide additional tools to simplify SVG graphics usage within components.

Here is a set of planned features:

  • Convenient implementation of Swing Icon for the SVG graphics

  • Easy way to retrieve BufferedImage of specific size from loaded SVG image

  • SVG support based on SVG Salamander free open-source library

  • SVG support based on Apache Batik free open-source free library

  • Separate svg module - would contain base API for providing SVG graphics loading support and won't be useful without a second module containing the actual implementation

  • Separate svg-salamander module - would contain SVG Salamander implementation

  • Separate svg-batik module - would contain Apache Batik implementation

Since this will certainly involve additional 3rd-party library or libraries - it should be placed into separate module. It will work perfectly along with other v1.3.0 module changes: #336

Some part of this enhancement request will be available with v1.2.9 release as SVG icons are quite convenient since they can be re-used for skins with different color schemes.

I have updated the main issue text with more information on planned stuff.

At this point it is mostly about adding Apache Batik support and separating all implementations from the API I've got in the IconManager. That should help keeping code clean of any SVG library-related mess and to allow developers to choose which library they want SVG graphics to be loaded and modified with.

I should generally say that I'd prefer using SVG Salamander as it is lightweight and has a really convenient API, but I've encountered quite a few issues with multiple SVG files and sometimes it is hard to say what exactly goes wrong.

On the other hand Apache Batik has extensive support for almost all SVG features (which you would probably never use anyway, but still) and does succeed at loading almost all SVG files. It also has slightly higher performance at painting more complex images. But the amount of dependencies it drags along is atrocious and it is the reason why I didn't go for it initially.

Overall - there is no best solution, but there are options and I want to allow WebLaF users to have those options (and maybe more in the future as well?) instead of hardcoding a single solution I prefer.

Sciss commented

As a side note, I once dragged Batik as a dependency in a project, and it was a nightmare; it transitively depends on zillions of other libraries which are not even consistently compatible to each other; I also found Batik's Java2D rendering extremely ugly, but perhaps I wasn't investing enough energy to understand how to tune it. For me, that project was a no-go, a good example of a failed humongous Java project that is just a big pile of spaghetti.

As a side note, I once dragged Batik as a dependency in a project, and it was a nightmare; it transitively depends on zillions of other libraries which are not even consistently compatible to each other;

Couldn't agree more. If not the dependencies I would've gone with Batik from the beginning.

I also found Batik's Java2D rendering extremely ugly, but perhaps I wasn't investing enough energy to understand how to tune it

I didn't find a good way to use Batik first time I tried it either and I don't consider their JSVGCanvas or JSVGComponent as a good solution at all - those are basically inconvenient/useless on practice.

After a bit more research though I found a way to do everything I did with SVG Salamander using Batik. Here is a short example of how to create WebLaF's SvgIcon analogue:

public class BatikSvgIcon implements Icon
{
    private final SVGDocument svgDocument;
    private final GraphicsNode svgIcon;

    public BatikSvgIcon ( URL url ) throws Exception
    {
        final String xmlParser = XMLResourceDescriptor.getXMLParserClassName ();
        final SAXSVGDocumentFactory df = new SAXSVGDocumentFactory ( xmlParser );
        svgDocument = df.createSVGDocument ( url.toString () );

        final UserAgent userAgent = new UserAgentAdapter ();
        final DocumentLoader loader = new DocumentLoader ( userAgent );
        final BridgeContext ctx = new BridgeContext ( userAgent, loader );
        ctx.setDynamicState ( BridgeContext.DYNAMIC );
        final GVTBuilder builder = new GVTBuilder ();
        svgIcon = builder.build ( ctx, svgDocument );
    }

    public void paintIcon ( Component c, Graphics g, int x, int y )
    {
        paintSvgIcon ( ( Graphics2D ) g, x, y );
    }

    private void paintSvgIcon ( Graphics2D g, int x, int y )
    {
        g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
        AffineTransform transform = new AffineTransform ( 1.0, 0.0, 0.0, 1.0, x, y );
        svgIcon.setTransform ( transform );
        svgIcon.paint ( g );
    }

    public int getIconWidth ()
    {
        return ( int ) svgIcon.getGeometryBounds ().getWidth ();
    }

    public int getIconHeight ()
    {
        return ( int ) svgIcon.getGeometryBounds ().getHeight ();
    }
}

You can also modify SVG structure through svgDocument in a similar way you can do it using SVG Salamander and changes are reflected upon the next paint operation.

Batik's API is not too bad if you get into it, I guess it just became overly complicated after all the features that been added there.

And well, there is one good reason to choose Batik over other options - performance. Painting operations mostly take twice less time than with other libraries I've tried. Not like its a huge difference when we're talking about 4ms vs 2ms, but unfortunately it all adds up and slows down Swing UI due to its single-thread nature.

In either case, I was thinking to add Batik implementation in a separate module, so you can always throw that one away safely if you don't want to drag all the dependencies and use SVG Salamander implementation (or even write your own if needed based on WebLaF's API).