openhtmltopdf
openhtmltopdf copied to clipboard
SVG TextPath causes StringIndexOutOfBoundsException
Hello,
We are trying to render a SVG with a textPath element in a PDF, but we keep getting the following error: java.lang.StringIndexOutOfBoundsException: String index out of range: 0
Note: The problem is caused by the SVG file, so I am not 100% sure it should be an issue here or on batik. Any other tested SVG works, the issue only comes up when using the textPath
-element.
Note 2: We develop in Kotlin, but we do not believe the issue is caused by that.
We tried textPath without attributes and/or different attributes. We tried to move the referring path
-element to the parent, but all to no avail.
Fix or workaround would be appreciated.
com.openhtmltopdf.load INFO:: TIME: parse stylesheets 47ms
com.openhtmltopdf.match INFO:: media = print
com.openhtmltopdf.match INFO:: Matcher created with 160 selectors
com.openhtmltopdf.general INFO:: Using fast-mode renderer. Prepare to fly.
java.lang.StringIndexOutOfBoundsException: String index out of range: 0
at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:47)
at java.base/java.lang.String.charAt(String.java:693)
at org.apache.batik.bridge.URIResolver.getNode(URIResolver.java:99)
at org.apache.batik.bridge.BridgeContext.getReferencedNode(BridgeContext.java:760)
at org.apache.batik.bridge.BridgeContext.getReferencedElement(BridgeContext.java:804)
at org.apache.batik.bridge.SVGTextPathElementBridge.createTextPath(SVGTextPathElementBridge.java:70)
at org.apache.batik.bridge.SVGTextElementBridge.fillAttributedStringBuffer(SVGTextElementBridge.java:943)
at org.apache.batik.bridge.SVGTextElementBridge.buildAttributedString(SVGTextElementBridge.java:850)
at org.apache.batik.bridge.SVGTextElementBridge.computeLaidoutText(SVGTextElementBridge.java:630)
at org.apache.batik.bridge.SVGTextElementBridge.buildGraphicsNode(SVGTextElementBridge.java:286)
at org.apache.batik.bridge.GVTBuilder.buildGraphicsNode(GVTBuilder.java:224)
at org.apache.batik.bridge.GVTBuilder.buildComposite(GVTBuilder.java:171)
at org.apache.batik.bridge.GVTBuilder.build(GVTBuilder.java:82)
at org.apache.batik.transcoder.SVGAbstractTranscoder.transcode(SVGAbstractTranscoder.java:208)
at com.openhtmltopdf.svgsupport.PDFTranscoder.transcode(PDFTranscoder.java:278)
at org.apache.batik.transcoder.XMLAbstractTranscoder.transcode(XMLAbstractTranscoder.java:142)
at org.apache.batik.transcoder.SVGAbstractTranscoder.transcode(SVGAbstractTranscoder.java:156)
at com.openhtmltopdf.svgsupport.BatikSVGImage.drawSVG(BatikSVGImage.java:231)
at com.openhtmltopdf.pdfboxout.PdfBoxSVGReplacedElement.paint(PdfBoxSVGReplacedElement.java:70)
at com.openhtmltopdf.pdfboxout.PdfBoxFastOutputDevice.paintReplacedElement(PdfBoxFastOutputDevice.java:275)
at com.openhtmltopdf.render.displaylist.DisplayListPainter.paintReplacedElement(DisplayListPainter.java:169)
at com.openhtmltopdf.render.displaylist.DisplayListPainter.paintReplacedElements(DisplayListPainter.java:151)
at com.openhtmltopdf.render.displaylist.DisplayListPainter.paint(DisplayListPainter.java:268)
at com.openhtmltopdf.pdfboxout.PdfBoxRenderer.paintPageFast(PdfBoxRenderer.java:936)
at com.openhtmltopdf.pdfboxout.PdfBoxRenderer.writePDFFast(PdfBoxRenderer.java:628)
at com.openhtmltopdf.pdfboxout.PdfBoxRenderer.createPdfFast(PdfBoxRenderer.java:564)
at com.openhtmltopdf.pdfboxout.PdfBoxRenderer.createPDF(PdfBoxRenderer.java:490)
at com.openhtmltopdf.pdfboxout.PdfBoxRenderer.createPDF(PdfBoxRenderer.java:427)
at com.openhtmltopdf.pdfboxout.PdfBoxRenderer.createPDF(PdfBoxRenderer.java:409)
at com.openhtmltopdf.pdfboxout.PdfRendererBuilder.run(PdfRendererBuilder.java:46)
at nl.companyname.customername.controller.ReportController.downloadReport(ReportController.kt:169)
at nl.companyname.customername.controller.ReportController$$FastClassBySpringCGLIB$$f9dac0a1.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.apache.shiro.spring.security.interceptor.AopAllianceAnnotationsAuthorizingMethodInterceptor$1.proceed(AopAllianceAnnotationsAuthorizingMethodInterceptor.java:82)
at org.apache.shiro.authz.aop.AuthorizingMethodInterceptor.invoke(AuthorizingMethodInterceptor.java:39)
at org.apache.shiro.spring.security.interceptor.AopAllianceAnnotationsAuthorizingMethodInterceptor.invoke(AopAllianceAnnotationsAuthorizingMethodInterceptor.java:115)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)
at nl.companyname.customername.controller.ReportController$$EnhancerBySpringCGLIB$$6f581fb6.downloadReport(<generated>)
at nl.companyname.customername.controller.ReportController$$FastClassBySpringCGLIB$$f9dac0a1.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.apache.shiro.spring.security.interceptor.AopAllianceAnnotationsAuthorizingMethodInterceptor$1.proceed(AopAllianceAnnotationsAuthorizingMethodInterceptor.java:82)
at org.apache.shiro.authz.aop.AuthorizingMethodInterceptor.invoke(AuthorizingMethodInterceptor.java:39)
at org.apache.shiro.spring.security.interceptor.AopAllianceAnnotationsAuthorizingMethodInterceptor.invoke(AopAllianceAnnotationsAuthorizingMethodInterceptor.java:115)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)
at nl.companyname.customername.controller.ReportController$$EnhancerBySpringCGLIB$$87b8a28a.downloadReport(<generated>)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:450)
at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387)
at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1726)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:834)
We tried our own SVG (piece of it below) and the one from Mozille, found here: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/textPath
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="600"
height="600" style="position: absolute;">
<g transform="translate(300,300)">
<g class="slice">
<path d="M-33.26240508564892,-69.07525016745126A76.66666666666667,76.66666666666667,0,0,1,-1.4083438190194563e-14,-76.66666666666667L-4.592425496802574e-15,-25A25,25,0,0,0,-10.846436440972475,-22.52453809808193Z"
style="fill: rgb(57, 59, 121); stroke: grey;"> </path>
<text transform="translate(-19.506421979199633,-85.46896481187684)" dy=".35em" text-anchor="middle"
font-size="12px" font-family="arial" fill="black">2
</text>
<path id="path0"
d="M-110.63365169791925,-229.7502886004357A255,255,0,0,1,-4.684274006738626e-14,-255L-4.133182947122317e-14,-225A225,225,0,0,0,-97.61792796875227,-202.72084288273737Z"
style="fill: rgb(57, 59, 121); stroke: grey;"> </path>
<text letter-spacing="1.5" dy="1.3em" font-size="8px" font-family="arial" fill="white">
<textPath startOffset="5px" xlink:href="#path0">Wil sturen</textPath>
</text>
</g>
</g>
</svg>
Our code is as follows:
@PostMapping("/report/download/{id}", produces = [MediaType.APPLICATION_PDF_VALUE])
fun downloadReportChangedForIssue() : ResponseEntity<ByteArray> {
val document = getHtmlDocument()
val byteArrayOutputStream = ByteArrayOutputStream()
try {
val builder = PdfRendererBuilder()
builder.withW3cDocument(document, "/")
builder.toStream(byteArrayOutputStream)
builder.useSVGDrawer(BatikSVGDrawer())
builder.run()
val headers = HttpHeaders()
headers.add("Content-Disposition", "inline; filename=test.pdf")
headers.add("Content-Type", "application/pdf")
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.headers(headers)
.body(byteArrayOutputStream.toByteArray())
} catch(error: Exception) {
System.err.println(error.printStackTrace())
} finally {
byteArrayOutputStream.close()
}
throw RuntimeException("Error")
}
private fun getHtmlDocument(base64Img: String): Document {
val simpleSvg = "<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
"\n" +
" <!-- to hide the path, it is usually wrapped in a <defs> element -->\n" +
" <!-- <defs> -->\n" +
" <path id=\"MyPath\" fill=\"none\" stroke=\"red\"\n" +
" d=\"M10,90 Q90,90 90,45 Q90,10 50,10 Q10,10 10,40 Q10,70 45,70 Q70,70 75,50\" />\n" +
" <!-- </defs> -->\n" +
"\n" +
" <text>\n" +
" <textPath href=\"#MyPath\">\n" +
" Quick brown fox jumps over the lazy dog.\n" +
" </textPath>\n" +
" </text>\n" +
"\n" +
"</svg>"
val jSoupDocument = Jsoup.parse(
"<html><body><h1>Report</h1>" +
simpleSvg +
"<p>Hello World</p></body></html>", "", Parser.xmlParser()
)
val w3CDom = W3CDom();
return w3CDom.fromJsoup(jSoupDocument)
}
Extra info
pom.xml
<properties>
<java.version>11</java.version>
<kotlin.version>1.5.30</kotlin.version>
<jacoco.plugin.version>0.8.7</jacoco.plugin.version>
<logstash-logback-encoder.version>6.3</logstash-logback-encoder.version>
<openhtml.version>1.0.10</openhtml.version>
</properties>
<dependencies>
<!-- PDF GENERATION -->
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-core</artifactId>
<version>${openhtml.version}</version>
</dependency>
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-pdfbox</artifactId>
<version>${openhtml.version}</version>
</dependency>
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-java2d</artifactId>
<version>${openhtml.version}</version>
</dependency>
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-slf4j</artifactId>
<version>${openhtml.version}</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version>
</dependency>
<dependency>
<!-- SVG support plugin. -->
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-svg-support</artifactId>
<version>${openhtml.version}</version>
</dependency>
</dependencies>