1
votes

I am writing an application which is to create a HTML document which contains a number of embedded SVGs.

The application has the following characteristics:

  1. It must run headless in a server environment

  2. It uses existing code which manipulates the DOM in order to create the SVGs and other items in the document

  3. The existing code uses the getBBox() method in order to determine sizes of various dynamically created elements, so as to calculate the layout of the SVGs.

  4. As additional information, although I do not think it affects the question: the SVG generation code is written in Javascript and uses the D3 library. I am using node-java to facilitate this. However, the problem I am currently experiencing is easily reproduced in a pure Java SSCE (see below).

I am trying to use Apache Batik to provide the SVG DOM implementation.

My problem is that I have so far been unable to get the getBBox() to return a non-null value in the case where an SVG element is embedded in a larger XML document.

Here is an example that illustrates the problem:

import org.w3c.dom.*;
import org.w3c.dom.svg.*;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.GVTBuilder;

class BatikDomEmbedded {

    public static void main(String [ ] args) {

        DOMImplementation impl = SVGDOMImplementation.getDOMImplementation(); 
        SVGDocument doc = (SVGDocument) impl.createDocument(null, "html", null);

        Element html = doc.getDocumentElement();
        Element body = doc.createElement("body");
        html.appendChild(body);

        Element svg = doc.createElementNS(SVGDOMImplementation.SVG_NAMESPACE_URI, "svg"); 
        svg.setAttributeNS(null, "width", "500");
        svg.setAttributeNS(null, "height", "100");
        body.appendChild(svg);

        UserAgent userAgent = new UserAgentAdapter(); 
        DocumentLoader loader    = new DocumentLoader(userAgent); 
        BridgeContext ctx       = new BridgeContext(userAgent, loader); 
        ctx.setDynamicState(BridgeContext.DYNAMIC); 
        GVTBuilder builder   = new GVTBuilder(); 
        builder.build(ctx, svg); 

        Element rect = doc.createElementNS(SVGDOMImplementation.SVG_NAMESPACE_URI, "rect");
        rect.setAttributeNS(null, "x", "100"); 
        rect.setAttributeNS(null, "y", "100"); 
        rect.setAttributeNS(null, "width", "150"); 
        rect.setAttributeNS(null, "height", "200"); 
        svg.appendChild(rect); 

        SVGRect bbox = ((SVGLocatable)rect).getBBox(); 

        System.out.println("X: " + bbox.getX() + "\nY: " + bbox.getY() + 
            "\nHeight: " + bbox.getHeight() + "\nWidth: " + bbox.getWidth());     
    }       

}

Running this code results in a null pointer exception when dereferencing bbox.

If I alter the code slightly to make the document a standalone SVG document then it works OK.

The chief difference between these scenarios (other than that the SVG element is not the root document element) is that in the working case, the call to builder.build(ctx, ...); supplies an SVGDocument as the second argument, but in the non-working case it is an SVGElement (of type 'svg').

Is there any way to get getBBox() working in this case where the svg element is embedded in a larger document (and in fact there may be multiple SVGs in this document)?

1

1 Answers

0
votes
val impl: DOMImplementation = SVGDOMImplementation.getDOMImplementation()
val doc = impl.createDocument(SVGConstants.SVG_NAMESPACE_URI, "svg", null) as SVGDocument

val userAgent: UserAgent = UserAgentAdapter()
val loader = DocumentLoader(userAgent)
val ctx = BridgeContext(userAgent, loader)
ctx.setDynamicState(BridgeContext.DYNAMIC)
val builder = GVTBuilder()
builder.build(ctx, doc)

val rect: Element = doc.createElementNS(SVGDOMImplementation.SVG_NAMESPACE_URI, "rect")
rect.setAttributeNS(null, "x", "100")
rect.setAttributeNS(null, "y", "100")
rect.setAttributeNS(null, "width", "150")
rect.setAttributeNS(null, "height", "200")
doc.documentElement.appendChild(rect)

val bbox = (rect as SVGLocatable).bBox

println("X: " + bbox.x + "\nY: " + bbox.y +
        "\nHeight: " + bbox.height + "\nWidth: " + bbox.width)

try this code.

first, always input svg namespace_url when create document or element.

second, don't set qualifiedName to "html" in SvgDocument. if you want to create html document, use HtmlDocument.

p.s this code is kotlin.