Search code examples
javapdfsvgxsl-foapache-fop

Can't draw SVG in PDF with Apache FOP - Unknown formatting object "{}svg" encountered (a child of fo:instream-foreign-object}


I created the following code for a simple checkbox in a table using XSL-FO:

Java code

Element svg = document.createElement("svg");
svg.setAttribute("width", "12pt");
svg.setAttribute("height", "12pt");
svg.setAttribute("viewBox", "0 0 24 24");

Element rect = document.createElement("rect");
rect.setAttribute("x", "2");
rect.setAttribute("y", "2");
rect.setAttribute("width", "20");
rect.setAttribute("height", "20");
rect.setAttribute("fill", "none");
rect.setAttribute("stroke", "black");
svg.appendChild(rect);
<fo:table-cell border="solid 1px black" font-family="Arial" font-size="12pt" padding="2pt">
 <fo:block>
  <fo:inline>Checkbox: </fo:inline>
  <fo:inline>
   <fo:instream-foreign-object>
    <svg height="12pt" viewBox="0 0 24 24" width="12pt">
     <rect fill="none" height="20" stroke="black" width="20" x="2" y="2"/>
    </svg>
   </fo:instream-foreign-object>
  </fo:inline>
 </fo:block>
</fo:table-cell>

However, when I try to convert it to PDF, I get the following warning: Unknown formatting object "{}svg" encountered (a child of fo:instream-foreign-object}

What could be causing this error?

I tried adding SVG properties to the configuration of FOP and using the fo:svg tag, but it still does not work properly.


Solution

  • FOP needs to know the kind of XML you feed it. In this case there are two XML namespaces in play; the Formatting Objects http://www.w3.org/1999/XSL/Format and the SVG http://www.w3.org/2000/svg.

    There are different ways to declare the namespace. And you can see them in this example PDF. In the following example the SVG namespace is just declared in the svg element, and then FOP knows that all the children are of the same namespace (unless another namespace in declared).

    <?xml version="1.0" encoding="UTF-8"?>
    <root xmlns="http://www.w3.org/1999/XSL/Format">
      <layout-master-set>
        <simple-page-master margin="10mm" page-width="210mm" page-height="297mm" master-name="simple">
          <region-body region-name="simple-body" margin-bottom="20mm" margin-top="20mm" />
        </simple-page-master>
      </layout-master-set>
      <page-sequence master-reference="simple">
        <flow flow-name="simple-body">
          <block>Hello World!</block>
          <block>
            <instream-foreign-object>
              <svg xmlns="http://www.w3.org/2000/svg" height="12pt" viewBox="0 0 24 24" width="12pt">
                <rect fill="none" height="20" stroke="black" width="20" x="2" y="2"/>
              </svg>
            </instream-foreign-object>
          </block>
        </flow>
      </page-sequence>
    </root>
    

    In Java it is probably fine if you just use the createElementNS with the SVG namespace, and Java will find out how to declare the SVG namespace in the XML document.

    As an XSL-FO example you add the namespace would be XSL http://www.w3.org/1999/XSL/Transform to the mix, either by letting the XSL or the FO namespace be the primary. In this case I think that it makes sense to have FO as the primary, because the output of the XSL Transformation is a FO document (with some SVG embedded in it).

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0"
      xmlns="http://www.w3.org/1999/XSL/Format"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:svg="http://www.w3.org/2000/svg">
    
      <xsl:template match="/">
        <root>
          <layout-master-set>
            <simple-page-master margin="10mm" page-width="210mm" page-height="297mm" master-name="simple">
              <region-body region-name="simple-body" margin-bottom="20mm" margin-top="20mm" />
            </simple-page-master>
          </layout-master-set>
          <page-sequence master-reference="simple">
            <flow flow-name="simple-body">
              <block>Hello World!</block>
              <block>
                <instream-foreign-object>
                  <svg:svg height="12pt" viewBox="0 0 24 24" width="12pt">
                    <svg:rect fill="none" height="20" stroke="black" width="20" x="2" y="2"/>
                  </svg:svg>
              </instream-foreign-object>
            </block>
          </flow>
        </page-sequence>
      </root>
    </xsl:template>
    
    </xsl:stylesheet>