在Batik将SVG转为GraphicsNode时如何使用自定义字体?
问题:Batik转换含自定义字体的SVG为GraphicsNode时出现空指针异常
场景描述
在Java中使用Apache Batik和OpenPDF库将SVG嵌入PDF,流程为先创建GraphicsNode,再转换为ImgTemplate用于PDF文档。但当SVG使用资源文件夹中的自定义字体时,出现空指针异常,不含自定义字体的SVG转换正常。
初始转换代码
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); }
首次报错信息
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使用了未初始化srcs元素的构造方法导致。
修改后尝试注册字体的代码
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); }
com.custom.FontFactory返回已从资源加载的Set<Font>,但仍出现新的空指针异常:
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)
直接通过文件路径注册字体也无效果。
解决方案
Batik处理自定义字体需要通过自定义UserAgent指定字体查找逻辑,仅注册到系统GraphicsEnvironment无法被Batik的桥接上下文识别。以下是可行步骤:
1. 实现自定义UserAgentAdapter
继承UserAgentAdapter,重写资源解析和字体解析方法,让Batik能正确加载自定义字体:
public class CustomUserAgent extends UserAgentAdapter { public CustomUserAgent() {} @Override public String resolveURI(String uri) { // 处理SVG中引用的相对路径字体文件,比如"fonts/MyCustomFont.ttf" if (uri.startsWith("fonts/")) { try { return getClass().getResource("/" + uri).toURI().toString(); } catch (URISyntaxException e) { throw new RuntimeException("解析字体URI失败", e); } } return super.resolveURI(uri); } @Override public FontFamilyResolver getFontFamilyResolver() { return familyName -> { // 手动加载自定义字体并返回Batik所需的FontFamily实例 try (InputStream fontStream = getClass().getResourceAsStream("/fonts/MyCustomFont.ttf")) { Font font = Font.createFont(Font.TRUETYPE_FONT, fontStream); // 可选:注册到系统,确保其他场景也能访问该字体 GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font); return new FontFamily(familyName, new Font[]{font}); } catch (IOException | FontFormatException e) { throw new RuntimeException("加载自定义字体失败", e); } }; } }
2. 修改转换代码使用自定义UserAgent
替换原有UserAgentAdapter为自定义实现,确保Batik处理字体时调用自定义逻辑:
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))); // 使用自定义UserAgent final UserAgent userAgent = new CustomUserAgent(); 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); }
3. 确保SVG字体引用正确
SVG中font-family名称需与自定义字体的名称一致,示例:
<style> @font-face { font-family: 'MyCustomFont'; src: url('fonts/MyCustomFont.ttf') format('truetype'); } .custom-text { font-family: 'MyCustomFont'; } </style>
关键说明
- Batik的字体解析逻辑依赖
UserAgent提供的资源定位和字体解析能力,仅注册到系统无法被Batik桥接上下文识别。 - 自定义
FontFamilyResolver直接返回Batik所需的FontFamily实例,避免了默认逻辑中可能出现的空指针问题。
内容的提问来源于stack exchange,提问作者King Midas
相关产品推荐
相关产品推荐

