You are viewing a plain text version of this content. The canonical link for it is here.
Posted to batik-users@xmlgraphics.apache.org by Alan Deikman <Al...@znyx.com> on 2009/03/28 01:35:15 UTC

Merging one document with another in a JSVGCanvas

A few weeks ago I was painting one or more JSVGCanvas objects on top of 
another to build a display with multiple document roots.  This works 
pretty well by using the JContainer.add() method, then you can use Swing 
methods to size and position the child JSVGCanvas.  However it has some 
drawbacks -- the deal breaker for me was reconciling the coordinate 
systems and CTMs of both the parent and child objects so that the child 
showed up where I wanted it to in the right scale.   Further, the 
kbd/mouse interactors for both objects remain completely separate.

So I learned how to do the following:

      SVGDocument ourDoc = getSVGDocument();
      SVGSVGElement root = ourDoc.getRootElement();

      // now get another document and import it

      SVGDocument doc = otherCanvas.getSVGDocument();
      SVGSVGElement panelRoot = doc.getRootElement();
      SVGOMSVGElement node = (SVGOMSVGElement)
                     ourDoc.importNode(panelRoot, true);

      // now add it to the display

      root.appendChild(node);
         
So far so good.  The next step is what I need help with, which is to 
scale, translate, and possibly rotate the sub-document in "node" to a 
fixed place in "root."   I have the target position as follows:

      SVGGraphicsElement position = (SVGGraphicsElement) 
ourDoc.getElementByID("xyzzy");
      SVGRect targetRect = position.getBBox();

So what is the most efficient way to get "node" to appear exactly on top 
of "targetRect?"   I have gotten hopelessly confused over the various 
viewports, viewboxes, and transforms involved. 

I may also need to rotate node by 90 degrees.  Since node is an SVG 
element, it won't take a transform, so do I need to encapsulate it 
inside a G element?

Much thanks for any help on this.

-- 
Alan Deikman
ZNYX Networks


---------------------------------------------------------------------
To unsubscribe, e-mail: batik-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: batik-users-help@xmlgraphics.apache.org


Re: Merging one document with another in a JSVGCanvas

Posted by Helder Magalhães <he...@gmail.com>.
Hi Alan,


> However it has some drawbacks --
> the deal breaker for me was reconciling the coordinate systems and CTMs of
> both the parent and child objects so that the child showed up where I wanted
> it to in the right scale.   Further, the kbd/mouse interactors for both
> objects remain completely separate.

Yes, I recall a few related mailing list threads recently, with
complex math and quite a bit of code to make it work all together...
;-)


> So I learned how to do the following:
[code snippet]

So "the following" seems to be embedding a document within the other.
Generally it seems a good idea. :-)


> So what is the most efficient way to get "node" to appear exactly on top of
> "targetRect?"   I have gotten hopelessly confused over the various
> viewports, viewboxes, and transforms involved.
> I may also need to rotate node by 90 degrees.  Since node is an SVG element,
> it won't take a transform, so do I need to encapsulate it inside a G
> element?

Well, for a start I'd propose a slightly different approach, intending
to be more general:
 * Create a parent document to hold all the imported documents ("container");
 * Create a svg container [1] for each document being imported;
 * Adjust the dimension and aspect ratio [2] of the child documents.

The proposal would result in something like (untested code snippet):

<svg xmlns="http://www.w3.org/2000/svg" id="container">
  <svg preserveAspectRatio="xMaxYMax" width="$width" height="$height"
id="document1">
    <!-- document 1 contents -->
  </svg>
  <svg preserveAspectRatio="xMaxYMax" width="$width" height="$height"
id="document2">
    <!-- document 2 contents -->
  </svg>
  <!-- ... document N ... -->
</svg>

Note that "$width" and "$height" are variables to be replaced with
actual values which should be coherent with the target dimension.


Thing is, generally, the overlay approach may have a caveat: if the
proportions of the documents are not the same, the final look can be
weird. In the sample above, the documents are stretch to the target
dimension (aspect ratio isn't preserved), although you may try other
approaches (see the specification [2] for a nice sample).


> Much thanks for any help on this.

Hope this helps,
 Helder Magalhães


[1] http://www.w3.org/TR/SVG11/intro.html#TermContainerElement
[2] http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute

---------------------------------------------------------------------
To unsubscribe, e-mail: batik-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: batik-users-help@xmlgraphics.apache.org


Re: Merging one document with another in a JSVGCanvas

Posted by Alan Deikman <Al...@znyx.com>.
thomas.deweese@kodak.com wrote:
>
>    Right.  The easiest way to map things is to get the bbox of
> 'node' (same as position above). 
Tom,

I was trying to avoid this, because at the time that 'node' is imported, 
the GVT tree isn't computed so node.getBBox() returns null.  I did 
figure out how to get it computed as follows:

           UserAgentAdapter agent = new UserAgentAdapter();
           BridgeContext ctx = new BridgeContext(agent);
           GVTBuilder builder = new GVTBuilder();
           GraphicsNode rootGN = builder.build(ctx, ourDoc);

And then all the bboxes are available.  Question: does invoking a 
GVTBuilder() at this point have any bad side effects or is there another 
way of doing this?  Remember I am doing this inside the UpdateManager's 
thread.

So far I have discovered that I can get the translation done by just 
setting the viewport on the SVG element as follows:

           node.setAttribute("x", Double.toString(position.getX()));
           node.setAttribute("y", Double.toString(position.getY()));
           node.setAttribute("width", Double.toString(position.getWidth()));
           node.setAttribute("height", 
Double.toString(position.getHeight()));

This way I don't need the bbox of 'node'.   However, once I need 
rotation it all gets screwed up both in terms of position and scale.   
I'm still working on that but I wanted to follow up on your helpful post 
with what I had so far.


-- 
Alan Deikman



Re: Merging one document with another in a JSVGCanvas -- one solution

Posted by Alan Deikman <Al...@znyx.com>.
Helder Magalhães wrote:
> I'm not particularly interested, but yes: sharing a/the solution (or a
> possible workaround, etc.), even if just a summary of it, is generally
> welcome [1]... ;-)
>   
OK.   I do have an outstanding question about my own code, but it is an 
SVG question and not anything to do with Batik.  The question is what is 
the role of the viewBox attribute in the imported node?  I found that if 
I do not set it to the viewport parameters this doesn't work.

        SVGDocument ourDoc = getSVGDocument();
        SVGSVGElement root = ourDoc.getRootElement();

        SVGDocument doc = /* the document to import */ ;

        // import the root SVG element

        SVGSVGElement panelRoot = doc.getRootElement();
        SVGOMSVGElement node = (SVGOMSVGElement)
                                ourDoc.importNode(panelRoot, true);

        // make sure we are using the right namespace

        String ns = root.getNamespaceURI();

        // discover the rect where we want the new graphic to go

        SVGRect position = /* where we want node to appear */;

        // the original panel width is used to calculate the scale
        // factor.  We assume that the aspect ratio of node and root are 
the same

        float originalPanelWidth = node.getWidth().getBaseVal().getValue();

        // set the viewport

        node.setAttribute("x", Double.toString(position.getX()));
        node.setAttribute("y", Double.toString(position.getY()));
        node.setAttribute("width", Double.toString(position.getWidth()));
        node.setAttribute("height", Double.toString(position.getHeight()));

        // set the viewbox to the same as the view port

        node.setAttribute("viewBox", String.format("%f %f %f %f",
                       position.getX(), position.getY(),
                       position.getWidth(), position.getHeight()));

        // create a g element and transfer all of the svg element's
        // elements to it, then make the g element the only child
        // of the original g element

        SVGOMGElement g = (SVGOMGElement)
                            ourDoc.createElementNS(ns, "g");
        Node child;
        while ((child = node.getFirstElementChild()) != null)
            g.appendChild(child);
        node.appendChild(g);

        // move and scale to the position without rotation

        if (!isRotationNeeded()) {
            g.setAttribute("transform", String.format(
                    "translate(%f, %f) scale(%f)",
                    position.getX(), position.getY(),
                    position.getWidth() / originalPanelWidth));
        }

        // or rotate 90 degrees if needed

        else {
            g.setAttribute("transform", String.format(
                    "rotate(90) translate(%f, %f) scale(%f)",
                    0.0,
                    0 - position.getX() - position.getWidth(),
                    position.getHeight() / originalPanelWidth));
        }

        // attach to the new SVG element to our root and we are done

        root.appendChild(node);
       
I hope someone finds this useful even though there are probably more 
elegant ways of getting this done.

-- 
Alan Deikman



---------------------------------------------------------------------
To unsubscribe, e-mail: batik-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: batik-users-help@xmlgraphics.apache.org


Re: Merging one document with another in a JSVGCanvas

Posted by Helder Magalhães <he...@gmail.com>.
Hi Alan,

> If anyone is interested in the solution I
> can post it here.

I'm not particularly interested, but yes: sharing a/the solution (or a
possible workaround, etc.), even if just a summary of it, is generally
welcome [1]... ;-)

Regards,
 Helder

[1] http://www.catb.org/~esr/faqs/smart-questions.html#followup

---------------------------------------------------------------------
To unsubscribe, e-mail: batik-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: batik-users-help@xmlgraphics.apache.org


Re: Merging one document with another in a JSVGCanvas

Posted by Alan Deikman <Al...@znyx.com>.
thomas.deweese@kodak.com wrote:
>
>    Right.  The easiest way to map things is to get the bbox of
> 'node' (same as position above).  Then you can map the center of
> the node bbox to 0,0:
I managed to solve this problem a few weekends back but haven't posted 
about it because it turns out to be much more an SVG exercise than 
anything to do with Batik specifically.   It works without having to 
compute the bounding box of the imported node first.   If anyone is 
interested in the solution I can post it here.   Thanks to everyone for 
the help.

-- 
Alan Deikman



Re: Merging one document with another in a JSVGCanvas

Posted by th...@kodak.com.
Hi Alan,

Alan Deikman <Al...@znyx.com> wrote on 03/27/2009 08:35:15 PM:

> So far so good.  The next step is what I need help with, which is to 
> scale, translate, and possibly rotate the sub-document in "node" to a 
> fixed place in "root."   I have the target position as follows:
> 
>       SVGGraphicsElement position = (SVGGraphicsElement) 
> ourDoc.getElementByID("xyzzy");
>       SVGRect targetRect = position.getBBox();
> 
> So what is the most efficient way to get "node" to appear exactly on top 

> of "targetRect?"   I have gotten hopelessly confused over the various 
> viewports, viewboxes, and transforms involved. 

> I may also need to rotate node by 90 degrees.  Since node is an SVG 
> element, it won't take a transform, so do I need to encapsulate it 
> inside a G element?

   Right.  The easiest way to map things is to get the bbox of 
'node' (same as position above).  Then you can map the center of
the node bbox to 0,0:
 translate(-nodeRect.X+nodeRect.Width/2, -nodeRect.Y+nodeRect.Height/2)

   Then scale the width and height of 'node' to match
targetRect (take into account rotation if needed):

  scale(targetRect.Width/nodeRect.Width, 
targetRect.Height/nodeRect.Height)

   Then translate it back to the center of the targetRect.

         translate(targetRect.X+targetRect.Width/2, 
targetRect.Y+targetRect.Height/2)


   They need to appear in 'reverse order' (IIRC) so:

<g transform="translate(targetRect.X+targetRect.Width/2, 
targetRect.Y+targetRect.Height/2);
                  scale(targetRect.Width/nodeRect.Width, 
targetRect.Height/nodeRect.Height);
                  translate(-nodeRect.X+nodeRect.Width/2, 
-nodeRect.Y+nodeRect.Height/2);">