使用 iTextPdf FontFactory 从 /WEB-INF/resources/fonts/foobar.ttf 加载字体

Load Font from /WEB-INF/resources/fonts/foobar.ttf with iTextPdf FontFactory

这个问题几乎说明了一切。我得到以下异常:

ExceptionConverter: java.io.IOException: /fonts/CALIBRI.TTF not found as file or resource.
Caused By: java.io.IOException: /fonts/CALIBRI.TTF not found as file or resource.
    at com.itextpdf.text.io.RandomAccessSourceFactory.createByReadingToMemory(RandomAccessSourceFactory.java:263)
    at com.itextpdf.text.io.RandomAccessSourceFactory.createBestSource(RandomAccessSourceFactory.java:173)
    at com.itextpdf.text.pdf.RandomAccessFileOrArray.<init>(RandomAccessFileOrArray.java:148)
    at com.itextpdf.text.pdf.TrueTypeFont.process(TrueTypeFont.java:641)
    at com.itextpdf.text.pdf.TrueTypeFont.<init>(TrueTypeFont.java:375)
    at com.itextpdf.text.pdf.BaseFont.getAllFontNames(BaseFont.java:1229)
    at com.itextpdf.text.FontFactoryImp.register(FontFactoryImp.java:446)
    at com.itextpdf.text.FontFactory.register(FontFactory.java:341)
    at bean.createPdf.makePdf(createPdf.java:358)
    at bean.auswahl.buttonProbenbegleitschein(auswahl.java:188)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.sun.el.parser.AstValue.invoke(AstValue.java:254)
    at com.sun.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:302)
    at com.sun.faces.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:105)
    at javax.faces.event.MethodExpressionActionListener.processAction(MethodExpressionActionListener.java:148)
    at javax.faces.event.ActionEvent.processListener(ActionEvent.java:88)
    at org.apache.myfaces.trinidad.component.UIXComponentBase.broadcast(UIXComponentBase.java:1113)
    at org.apache.myfaces.trinidad.component.UIXCommand.broadcast(UIXCommand.java:179)
    at org.apache.myfaces.trinidad.component.UIXComponent.broadcastInContext(UIXComponent.java:364)
    at oracle.adf.view.rich.event.ProxyEvent.broadcastWrappedEvent(ProxyEvent.java:72)
    at oracle.adf.view.rich.component.fragment.UIXRegion.broadcast(UIXRegion.java:124)
    at org.apache.myfaces.trinidad.component.UIXComponent.broadcastInContext(UIXComponent.java:364)
    at org.apache.myfaces.trinidad.component.WrapperEvent.broadcastWrappedEvent(WrapperEvent.java:82)
    at oracle.adf.view.rich.component.fragment.ContextSwitchingComponent.run(ContextSwitchingComponent.java:168)
    at oracle.adf.view.rich.component.fragment.ContextSwitchingComponent._processPhase(ContextSwitchingComponent.java:510)
    at oracle.adf.view.rich.component.fragment.ContextSwitchingComponent.broadcast(ContextSwitchingComponent.java:171)
    at org.apache.myfaces.trinidad.component.UIXComponent.broadcastInContext(UIXComponent.java:364)
    at org.apache.myfaces.trinidad.component.WrapperEvent.broadcastWrappedEvent(WrapperEvent.java:82)
    at oracle.adf.view.rich.component.fragment.UIXInclude.broadcast(UIXInclude.java:111)
    at org.apache.myfaces.trinidad.component.UIXComponent.broadcastInContext(UIXComponent.java:364)
    at org.apache.myfaces.trinidad.component.WrapperEvent.broadcastWrappedEvent(WrapperEvent.java:82)
    at oracle.adf.view.rich.component.fragment.ContextSwitchingComponent.run(ContextSwitchingComponent.java:168)
    at oracle.adf.view.rich.component.fragment.ContextSwitchingComponent._processPhase(ContextSwitchingComponent.java:510)
    at oracle.adf.view.rich.component.fragment.ContextSwitchingComponent.broadcast(ContextSwitchingComponent.java:171)
    at oracle.adf.view.rich.component.fragment.UIXInclude.broadcast(UIXInclude.java:115)
    at javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:794)
    at javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:1259)
    at oracle.adfinternal.view.faces.lifecycle.LifecycleImpl._invokeApplication(LifecycleImpl.java:1074)
    at oracle.adfinternal.view.faces.lifecycle.LifecycleImpl._executePhase(LifecycleImpl.java:402)
    at oracle.adfinternal.view.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:225)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:593)
    at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:280)
    at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:254)
    at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:136)
    at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:346)
    at weblogic.servlet.internal.TailFilter.doFilter(TailFilter.java:25)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:79)
    at oracle.adf.model.servlet.ADFBindingFilter.doFilter(ADFBindingFilter.java:192)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:79)
    at oracle.adfinternal.view.faces.webapp.rich.RegistrationFilter.doFilter(RegistrationFilter.java:105)
    at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl$FilterListChain.doFilter(TrinidadFilterImpl.java:502)
    at oracle.adfinternal.view.faces.activedata.AdsFilter.doFilter(AdsFilter.java:60)
    at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl$FilterListChain.doFilter(TrinidadFilterImpl.java:502)
    at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl._doFilterImpl(TrinidadFilterImpl.java:327)
    at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl.doFilter(TrinidadFilterImpl.java:229)
    at org.apache.myfaces.trinidad.webapp.TrinidadFilter.doFilter(TrinidadFilter.java:92)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:79)
    at de.lkvsh.rdv.portal.authlib.servlet.PortalFilter.doFilter(PortalFilter.java:116)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:79)
    at oracle.security.jps.ee.http.JpsAbsFilter.run(JpsAbsFilter.java:137)
    at java.security.AccessController.doPrivileged(Native Method)
    at oracle.security.jps.util.JpsSubject.doAsPrivileged(JpsSubject.java:315)
    at oracle.security.jps.ee.util.JpsPlatformUtil.runJaasMode(JpsPlatformUtil.java:460)
    at oracle.security.jps.ee.http.JpsAbsFilter.runJaasMode(JpsAbsFilter.java:120)
    at oracle.security.jps.ee.http.JpsAbsFilter.doFilter(JpsAbsFilter.java:217)
    at oracle.security.jps.ee.http.JpsFilter.doFilter(JpsFilter.java:81)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:79)
    at oracle.dms.servlet.DMSServletFilter.doFilter(DMSServletFilter.java:220)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:79)
    at weblogic.servlet.internal.RequestEventsFilter.doFilter(RequestEventsFilter.java:27)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:79)
    at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.wrapRun(WebAppServletContext.java:3436)
    at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3402)
    at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
    at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:120)
    at weblogic.servlet.provider.WlsSubjectHandle.run(WlsSubjectHandle.java:57)
    at weblogic.servlet.internal.WebAppServletContext.doSecuredExecute(WebAppServletContext.java:2285)
    at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2201)
    at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2179)
    at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1572)
    at weblogic.servlet.provider.ContainerSupportProviderImpl$WlsRequestExecutor.run(ContainerSupportProviderImpl.java:255)
    at weblogic.work.ExecuteThread.execute(ExecuteThread.java:311)
    at weblogic.work.ExecuteThread.run(ExecuteThread.java:263)

字体文件的物理位置是/WEB-INF/resources/fonts/CALIBRI.TTF 据我所知,iTextPDf FontFactory.

无法在资源中找到它

java.io.IOException: /fonts/CALIBRI.TTF not found as file or resource.

我认为 iText 库直接在 WEB-INF 文件夹下而不是 /WEB-INF/resources 下寻找字体。

基于source code of iText's RandomAccessSourceFactory#createBestSource(), the FontFactory#register()只能取代表有效(相对)磁盘文件系统路径的路径,或资源URL(例如HTTPURL或classpath URL),不是 WAR 资源路径。您实际上提供了 WAR 资源路径,您通常只能将其传递给 ServletContext#getResource()getResourceAsStream()getRealPath()getRequestDispatcher()

有几种方法可以让它工作:

  1. 借助ServletContext#getRealPath():

    将WAR资源路径转换为绝对磁盘文件系统路径
    String path = getServletContext().getRealPath("/WEB-INF/resources/fonts/foobar.ttf");
    FontFactory.register(path);
    

    需要注意的是,此方法非常不可移植且依赖于环境。其中,当服务器配置为将 WAR 直接扩展到内存而不是本地磁盘文件系统或使用虚拟文件系统时,这将失败。例如,默认情况下,它可能适用于 Tomcat,但可能不适用于 WebLogic。您的堆栈跟踪确认您正在使用 WebLogic。 getRealPath() 默认总是 return null.

    的可能性很大
  2. 将字体文件移出 /WEB-INF 文件夹,以便可以通过 HTTP URL 公开访问。前提是您可以将整个 /resources 文件夹向上移动一层,在 /WEB-INF 之外,并且字体可以通过 http://localhost:8080/context/resources/fonts/foobar.ttf 访问(首先在普通的网络浏览器中进行测试以确保),然后只需提供正是那条路。

    String path = "http://localhost:8080/context/resources/fonts/foobar.ttf";
    FontFactory.register(path);
    

    如有必要,您可以动态构造 http://localhost:8080/context(基础 URL),如下所示:

    String url = request.getRequestURL().toString();
    String baseURL = url.substring(0, url.length() - request.getRequestURI().length()) + request.getContextPath();
    String path = baseURL + "/resources/fonts/foobar.ttf";
    FontFactory.register(path);
    

    需要注意的是,默认情况下,世界上任何人都可以下载它,即使只是猜测 URL。


  3. 将字体文件移动到 class 路径。 IE。确保它最终在所有 Java class 文件和 class 资源文件(例如属性文件)中的 /WEB-INF/classes 中结束。假设包名只是 fonts。如果您使用的是 Maven,只需将其放入 /main/resources/fonts/foobar.ttf。如果您使用的 "plain vanilla" Web 项目只有一个 src 文件夹,您可以在其中放置所有 Java 包和 class 源文件,然后创建一个包 fonts 并将 foobar.ttf 文件放在那里。然后你可以提供 /fonts/foobar.ttf.

    的路径
    String path = "/fonts/foobar.ttf";
    FontFactory.register(path);
    

    这显然是首选方式。


  4. 如果 none 以上选项适用,最后的办法是在 File#createTempFile() in container managed temporary folder, grabbing the WAR resource contents by ServletContext#getResourceAsStream() 的帮助下创建一个临时文件,将其写入临时文件并最终提供绝对值临时文件路径。

    String tempDir = (String) getServletContext().getAttribute(ServletContext.TEMPDIR);
    File tempFile = File.createTempFile("foobar-", ".ttf", new File(tempDir));
    InputStream input = getServletContext().getResourceAsStream("/WEB-INF/resources/fonts/foobar.ttf");
    Files.copy(input, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
    String path = tempFile.getAbsolutePath();
    FontFactory.register(path);
    

注意:以上答案假定代码放在 "plain" Java EE servlet class 中。如果您使用的是 JSF——正如堆栈跟踪所确认的那样——您可以通过 ExternalContext#getContext(), the HttpServletRequest by ExternalContext#getRequest(), the ServletContext attributes by ExternalContext#getApplicationMap(), the real path by ExternalContext#getRealPath(), and the resource stream by ExternalContext#getResourceAsStream().

获得 ServletContext