Search code examples
javafont-facebatikapache-batik

Use custom fonts when converting SVG to GraphicNode in Batik


I am trying to embed a SVG into a PDF using both Apache Batik and OpenPDF libraries in Java.

For this purpose, following a tutorial, I need to create a GraphicsNode, that later will be converted into a ImgTemplate to be use on the PDF document.

My code is:

    private GraphicsNode convert(String svgCode) throws IOException {
        final SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName());
        final SVGDocument svgDocument = factory.createSVGDocument("", new ByteArrayInputStream(svgCode.getBytes(StandardCharsets.UTF_8)));

        final UserAgent userAgent = new UserAgentAdapter();
        final DocumentLoader loader = new DocumentLoader(userAgent);

        final BridgeContext context = new BridgeContext(userAgent, loader);
        context.setDynamicState(BridgeContext.DYNAMIC);

        final GVTBuilder builder = new GVTBuilder();
        return builder.build(context, svgDocument);
    }

As the SVG code is using a Font that is on my resources folder, I get a:

java.lang.NullPointerException: Cannot invoke "java.util.List.iterator()" because "this.srcs" is null

    at org.apache.batik.bridge.FontFace.getFontFamily(FontFace.java:98)
    at org.apache.batik.bridge.CSSFontFace.getFontFamily(CSSFontFace.java:176)
    at org.apache.batik.bridge.SVGFontUtilities.getFontFamily(SVGFontUtilities.java:144)

FontFace has two constructors and is using when debuggin:

    protected FontFace(String familyName) {
        super(familyName);
    }

That is not initializing the srcs element. And therefore I get a NPE.

Maybe I need to register the fonts in some way. Then I have added some extra lines to the code:

    private GraphicsNode convert(String svgCode) throws IOException {
        //Create org.w3c.dom.svg.SVGDocument
        final SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName());
        final SVGDocument svgDocument = factory.createSVGDocument("", new ByteArrayInputStream(svgCode.getBytes(StandardCharsets.UTF_8)));

        //Create a org.apache.batik.gvt.GraphicsNode from the org.w3c.dom.svg.SVGDocument
        final UserAgent userAgent = new UserAgentAdapter();
        final DocumentLoader loader = new DocumentLoader(userAgent);

        // Notice, that you should use org.apache.batik.bridge.svg12.SVG12BridgeContext.SVG12BridgeContext for the svg version 1.2
        final BridgeContext context = new SVG12BridgeContext(userAgent, loader);
        context.setDynamicState(BridgeContext.DYNAMIC);

        //Register fonts
        final GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        final Set<Font> loadedFonts = com.custom.FontFactory.getLoadedFonts();
        loadedFonts.forEach(graphicsEnvironment::registerFont);

        final GVTBuilder builder = new GVTBuilder();
        return builder.build(context, svgDocument);
    }

My com.custom.FontFactory returns a Set<Font> that are already loaded from resources.

That I am not sure if it is the correct way or not for registering fonts. But seems that have some effect, as now I get a:

java.lang.NullPointerException: Cannot invoke "java.util.Collection.toArray()" because "c" is null

    at java.base/java.util.LinkedList.addAll(LinkedList.java:419)
    at java.base/java.util.LinkedList.addAll(LinkedList.java:398)
    at java.base/java.util.LinkedList.<init>(LinkedList.java:130)
    at org.apache.batik.bridge.FontFace.createFontFace(FontFace.java:78)

That is a different error on a different place. I have also tried to directly register the fonts as follows:

graphicsEnvironment.registerFont(Font.createFont(Font.TRUETYPE_FONT,
                        new File("<pathToTheResource>"))));

But there is no difference with the last error.

Then the question is.. how can I user a custom fonts on Batik for converting a SVG to a GraphicNode?

If I convert a SVG without any embedded font, it is working fine. Then I am assuming that the issue is related to the fonts used.


Solution

  • Ok. Seems that the issue was not related to the fonts that I want to register with GraphicsEnvironment, but with the fonts that are embedded on the svg. Ia have followed this recommendation to have fonts inside the SVG. For this purpose, I create a font-face with a base64 codified font in the src field.

    Seems that Batik does not like this content on the src. But, as I am embedding the fonts using GraphicsEnvironment, I do not need the embeded font on the SVG.

    TL;DR Embed the fonts inside the SVG xml when you are using the SVG on a webpage, but do not do it when using Batik to make conversions to PNG, JPEG, or any other rendering.