blackears / svgSalamander

155 stars 56 forks source link

Is there a way to clone SVGIcon with it's current diagram into new SVGUniverse? #48

Closed mgarin closed 4 years ago

mgarin commented 4 years ago

A bit tricky question here - is there a good way to clone/copy an SVGIcon instance with it's current diagram (with all modifications potentially made in runtime) into new SVGUniverse instance?

I know I can load the same SVG icon from it's URI again in a different SVGUniverse and configure it separately, but I really need to preserve any changes made to it's diagram in runtime.

Let me elaborate a little bit -

  1. Let's say I have some small SVG icon - leaf.svg:

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
    <path d="m31.604 4.203c-3.461-2.623-8.787-4.189-14.247-4.189-6.754 0-12.257 2.358-15.1 6.469-1.335 1.931-2.073 4.217-2.194 6.796-.108 2.296.278 4.835 1.146 7.567 2.965-8.887 11.244-15.847 20.79-15.847 0 0-8.932 2.351-14.548 9.631-.003.004-.078.097-.207.272-1.128 1.509-2.111 3.224-2.846 5.166-1.246 2.963-2.4 7.03-2.4 11.931h4c0 0-.607-3.819.449-8.212 1.747.236 3.308.353 4.714.353 3.677 0 6.293-.796 8.231-2.504 1.736-1.531 2.694-3.587 3.707-5.764 1.548-3.325 3.302-7.094 8.395-10.01.292-.167.48-.468.502-.804s-.126-.659-.394-.862z" />
    </svg>
  2. I'm using it in runtime as an Icon for a label:

    public class SvgExample
    {
    public static void main ( final String[] args )
    {
        SwingUtilities.invokeLater ( new Runnable ()
        {
            @Override
            public void run ()
            {
                // SVG icon
                final SVGIcon icon = new SVGIcon ();
                icon.setSvgURI ( new File ( "C:\\leaf.svg" ).toURI () );
                icon.setPreferredSize ( new Dimension ( 32, 32 ) );
                icon.setAntiAlias ( true );
    
                // Displaying it
                final JFrame frame = new JFrame ( "SVG Example" );
                final JLabel iconLabel = new JLabel ( icon );
                iconLabel.setBorder ( BorderFactory.createEmptyBorder ( 50, 50, 50, 50 ) );
                frame.add ( iconLabel );
                frame.setDefaultCloseOperation ( WindowConstants.EXIT_ON_CLOSE );
                frame.pack ();
                frame.setLocationRelativeTo ( null );
                frame.setVisible ( true );
            }
        } );
    }
    }

A simple frame:

image

  1. Now let's say I modify it's diagram anyhow:
    try
    {
    final SVGDiagram diagram = icon.getSvgUniverse ().getDiagram ( icon.getSvgURI () );
    diagram.getRoot ().addAttribute ( "fill", AnimationElement.AT_XML, "#FF0000" );
    }
    catch ( final SVGElementException e )
    {
    e.printStackTrace ();
    }

Simple fill attribute with red color:

image

  1. Now is the tricky part, I need to create a copy of this red icon and adjust it further - for instance to add some more adjustments to it:
    try
    {
    final SVGDiagram diagram = icon.getSvgUniverse ().getDiagram ( icon.getSvgURI () );
    diagram.getRoot ().addAttribute ( "stroke", AnimationElement.AT_XML, "#0000FF" );
    }
    catch ( final SVGElementException e )
    {
    e.printStackTrace ();
    }

    Problem is - if I do more adjustments - they will affect all other SVGIcons with the same URI within the same SVGUniverse. And if I load new SVGIcon from that URI into new SVGUniverse - I'll end up losing the red fill attribute I've added earlier.

I didn't really find any existing way to clone/copy SVGIcon or it's SVGDiagram from one SVGUniverse to another one.

I have some tools for recursive cloning of data & reflection access that can help me with that, but it will be quite a hack & there are also a lot of cross-references to SVGUniverse in different classes which will also get copied by those tools which is problem for that approach.

blackears commented 4 years ago

The libray doesn't currently have a way to do that. If you want to duplicate bits of the SVG, you'll need to manipulate the tree of nodes directly. The SVGDiagram structure is pretty straight forward, so you might be able to get away with reflection for the inner nodes. You could also walk the tree manually and make copies of the nodes you encounter.

mgarin commented 4 years ago

Thanks for the answer!

I tried copying SVGRoot element only & replacing it in the SVGDiagram through setRoot ( ... ) method, but it seem to be breaking something - root is fully copied, but when icon is painted it's simply empty.

At the same time - copying the whole SVGIcon along with it's SVGUniverse seem to work just fine, so I'll stick to this approach for now. That of course forces me to use separate SVGUniverse for each SVGIcon so I don't copy a bunch of other unnecessary stuff along whenever icon copy is created.

It is quite a "barbaric" approach, but it doesn't seem to have too much of an overhead to really matter and it also makes using SVGIcon generally more simple as I had a lot of extra workarounds to make these icons use correct SVGUniverses before.

I can post a link to the implementation later on (once I push it to GitHub) if you're curious, but generally I simply used my Clone implementation to copy SVGIcon and it does seem to work correctly (although I have a custom SvgIcon extending SVGIcon in my code for convenience).


And just a small general question - why was the current approach with SVGUniverse chosen? Was there some big benefit of having the same SVGUniverse reused for different SVGIcons?

blackears commented 4 years ago

The SVGUniverse allows multiple documents to have references to each other, so the <use> tag can reference other documents.

If all you need to do is duplicate your leaf object, you could create an empty diagram and then build a tree in it that just contains a copy of the path element with your graphic. But then again, the SVGUniverse is pretty light weight so there won't be much penalty to having multiples of them.

mgarin commented 4 years ago

The SVGUniverse allows multiple documents to have references to each other, so the tag can reference other documents.

You mean <use> references between multiple diagrams or within one diagram?

If all you need to do is duplicate your leaf object, you could create an empty diagram and then build a tree in it that just contains a copy of the path element with your graphic.

Pretty much, but "building a tree" in new empty diagram will have approximately the same complexity as just cloning the whole SVGIcon with it's universe and diagram since there is no sraightforward way to just copy all SVG elements from existing tree into the new one.

Also as you mentioned - SVGUniverse is indeed pretty light weight, so there is not much point to overcomplicate the code. And, in the first place, all my SVGIcons are already using separate SVGUniverses due to various customizations done in runtime, so it won't be changing much for me.

blackears commented 4 years ago

Each SVGDiagram is meant to co-respond to a single SVG file. Since SVG files can refer to elements in other files, the SVGUniverse acts as a manager class to hold a group of them and allow those inter document links to be followed.

Anyway, you seem to have found a solution that works for you, so I'll consider this closed for now.

mgarin commented 4 years ago

I understood the relation of 1 SVG file = 1 Diagram, was just wondering about the <use> as I've never seen it reference anything outside of SVG own tree.

Either way, yes, I found a somewhat working solution. Thanks for the responses!