2
votes

I am generating a PDF using Apache FOP 2.1, JAXB, and XSLT 1.0. The template has some embedded SVGs. The PDF renders without any issues, but FO is throwing errors that are cluttering up my logs.

Setup:

XslProcessor.java

public XslProcessor(File f) {
    try {
        URIResolverAdapter uriResolverAdapter = new URIResolverAdapter(new UserAgentUriResolver());
        FopFactoryBuilder builder = new FopFactoryBuilder(URI.create("/"), uriResolverAdapter);
        DefaultConfigurationBuilder defaultConfigurationBuilder = new DefaultConfigurationBuilder();
        builder.setConfiguration(defaultConfigurationBuilder.buildFromFile("config/pdf/fop.xconf"));
        fopFactory = builder.build();
        transformerFactory = TransformerFactory.newInstance();
        transformFile = f;
    } catch (IOException | SAXException | ConfigurationException e) {
        throw new RuntimeException(e);
    }
}

public synchronized void process(InputStream is, OutputStream os) {
    try {
        try(BufferedOutputStream bufferedOut = new BufferedOutputStream(os)) {
            Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, bufferedOut);
            Source xslt = new StreamSource(transformFile);
            Transformer transformer = transformerFactory.newTransformer(xslt);
            Source source = new StreamSource(is);
            Result result = new SAXResult(fop.getDefaultHandler());
            transformer.transform(source, result);
        }
        os.flush();
        os.close();
    } catch (IOException | FOPException | TransformerException e) {
        throw new RuntimeException(e);
    }
}

UserAgentUriResolver.java

public class UserAgentUriResolver implements URIResolver {

private static final String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.86 Safari/537.36";
private static final String REFERRER = "https://www.google.com";

@Override
public Source resolve(String href, String base) throws TransformerException {
    try {
        URL url = new URL(href);
        URLConnection connection = url.openConnection();
        connection.setRequestProperty("User-Agent", USER_AGENT);
        connection.setRequestProperty("Referrer", REFERRER);
        return new StreamSource(connection.getInputStream());
    } catch (IOException e) {
        return new StreamSource(new ByteArrayInputStream(new byte[0]));
    }
}
}

Sample template:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:fo="http://www.w3.org/1999/XSL/Format">

    <xsl:template match="/">
        <fo:root>
            <fo:layout-master-set>
                <fo:simple-page-master master-name="page"
                                       page-height="29.7cm"
                                       page-width="21cm">
                    <fo:region-body/>
                    <fo:region-after extent="1.25cm"/>
                </fo:simple-page-master>
                <fo:page-sequence-master master-name="all">
                    <fo:repeatable-page-master-alternatives>
                        <fo:conditional-page-master-reference master-reference="page"/>
                    </fo:repeatable-page-master-alternatives>
                </fo:page-sequence-master>
            </fo:layout-master-set>
            <fo:page-sequence master-reference="all">
                <fo:static-content flow-name="xsl-region-after">
                    <fo:block>
                        <fo:instream-foreign-object>
                            <!-- SVG here! -->
                            <svg xmlns="http://www.w3.org/2000/svg" xml:base="http://www.example.com/">
                                <rect height="20" width="100%"  fill="#ababab"></rect>
                            </svg>
                        </fo:instream-foreign-object>
                    </fo:block>
                </fo:static-content>
                <fo:flow flow-name="xsl-region-body">
                    <fo:block>
                        <xsl:apply-templates/>
                    </fo:block>
                </fo:flow>
            </fo:page-sequence>
        </fo:root>
    </xsl:template>
</xsl:stylesheet>

Once for every svg in the template, I get

org.apache.fop.fo.FONode: Could not set base URL for svg

java.lang.IllegalArgumentException: URI is not absolute at java.net.URI.toURL(URI.java:1088) ~[na:1.8.0_91] at org.apache.fop.fo.extensions.svg.SVGElement.getDimension(SVGElement.java:77) ~[fop-2.1.jar:na] at org.apache.fop.fo.flow.InstreamForeignObject.prepareIntrinsicSize(InstreamForeignObject.java:112) [fop-2.1.jar:na] at org.apache.fop.fo.flow.InstreamForeignObject.getIntrinsicWidth(InstreamForeignObject.java:125) [fop-2.1.jar:na] at org.apache.fop.layoutmgr.inline.AbstractGraphicsLayoutManager.getInlineArea(AbstractGraphicsLayoutManager.java:60) [fop-2.1.jar:na] at ...

2
Have no idea whether this is any solution but that is terrible code. You are creating the object and then running a test to see if you want anything in it (ok) or nothing (violation of the standard), Why is your test not surrounding whether you create instream-foreign-object? - Kevin Brown
Could you edit your question to include an example of resulting svg with all the attributes? I just tried to process an fo file derived from your code with FOP 2.1 and it gives no warning at all, so I suspect there is something wrong in some other attribute. - lfurini
Possibly not(@someValue) returns false, leading to a structure like <fo:instream-foreign-object/> which may cause an issue in some engines. Not sure the error above, never tested it. - Kevin Brown
@KevinBrown I tested your hypothesis: an empty fo:instream-foreign-object processed by FOP 2.1 causes a validation error (org.apache.fop.fo.ValidationException: "fo:instream-foreign-object" is missing child elements. Required content model: one (1) non-XSL namespace child), so there should be something else wrong in the OP's created svg trees. - lfurini
@KevinBrown The code is simplified/obfuscated. The fo:instream-foreign-object is not empty. - nmatte

2 Answers

1
votes

Omitting or changing the default base URI for resolving resource URIs should help.(see EnvironmentProfile) If the base URI is set, an absolute URI is required, e.g.: file:///.

An absolute URI specifies a scheme; a URI that is not absolute is said to be relative. URIs are also classified according to whether they are opaque or hierarchical. java.net. URI

You can set this URI programmatically. In my code it looks like this:

new FopFactoryBuilder(URI.create("file:///"), uriResolverAdapter);

Background

In the class org.apache.fop.fo.extensions.svg.SVGElement the base java.net.URI is transformed to an URL

URI baseUri = getUserAgent().getResourceResolver().getBaseURI();
if (baseUri != null) {
    SVGOMDocument svgdoc = (SVGOMDocument)doc;
    svgdoc.setURLObject(baseUri.toURL());
}

And the method URI::toURL is checking for the scheme. If the scheme is missed, an IllegalArgumentException("URI is not absolute") is thrown.

0
votes

Try adding an xml:base attribute to the <svg> element.

xml:base (https://www.w3.org/TR/xmlbase/) is newer than the XSL 1.1 Recommendation. That shouldn't matter since you'd be adding it to the non-XSL <svg> element anyway, and xml:base is in SVG 1.1: https://www.w3.org/TR/SVG/struct.html#Core.attrib

Why FOP doesn't use the base-uri of the FO document as the base-uri for the SVG is a mystery, so this might or might not work.