iText 7、htmlPDF 2 - DefaultFontProvider 的并行使用
iText 7, htmlPDF 2 - Parallel usage of DefaultFontProvider
我们正在根据 iText 7.1.2
和 htmlPDF 2.0.2
转换邮件信息。转换是在一个静态方法中完成的,该方法由并行线程为每个基于 html 的消息调用。代码看起来像这样简化(流在 finally 块中关闭):
ConverterProperties properties = new ConverterProperties();
FontProvider fontProvider = new DefaultFontProvider();
for (String font : ITEXT_FONTS) {
FontProgram fontProgram = FontProgramFactory.createFont(font);
fontProvider.addFont(fontProgram);
}
properties.setFontProvider(fontProvider);
fos = new FileOutputStream(targetFile);
HtmlConverter.convertToPdf(is, fos, properties);
for 循环用于从位于类路径中的 Noto
包中添加中文字体。在我们的环境中,我们现在有时会看到以下错误场景:
Caused by: java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3236)
at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118)
at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153)
at com.itextpdf.io.util.StreamUtil.inputStreamToArray(StreamUtil.java:212)
at com.itextpdf.html2pdf.resolver.font.DefaultFontProvider.addShippedFreeFonts(DefaultFontProvider.java:111)
at com.itextpdf.html2pdf.resolver.font.DefaultFontProvider.<init>(DefaultFontProvider.java:97)
at com.itextpdf.html2pdf.resolver.font.DefaultFontProvider.<init>(DefaultFontProvider.java:81)
问题是:
- 为每个调用创建 DefaultFontProvider 是否合法,或者应该只有一个实例(例如,因为创建成本)?
- 如果DefaultFontProvider只初始化一次->这个实例线程保存了吗?
提前致谢!
简短的回答是:请阅读文档。
问题 1 的答案
Is the creation of the DefaultFontProvider legit for every single call or should there be only one instance (e.g. because of the costs of creation)?
您应该像现在一样为每次转化创建一个新实例。字体提供程序与文档相关联,这在 base FontProvider
class 的文档中有说明。虽然如果您重复使用字体提供程序,它仍然很可能会起作用,但这不是我推荐的方式。
您正在使用的 setFontProvider
方法的文档明确说明了这一点:
Please note that FontProvider instances cannot be reused across several documents and thus as soon as you set this property, this ConverterProperties instance becomes only useful for a single HTML conversion.
问题 2 的答案
If the DefaultFontProvider is initialized only once -> is this instance thread save?
不保证线程安全。文档指导您仅使用 DefaultFontProvider
进行一次转换。
优化提示
(为了我会尝试应用它们)
- 看看
DefaultFontProvider(boolean, boolean, boolean)
构造函数。默认情况下,pdfHTML 加载标准 PDF 字体以及 pdfHTML 附带的字体。如果您确定您手动添加的字体涵盖了您在 HTML 文件中使用的所有脚本,则可以将 false
传递给所有三个构造函数参数 (new DefaultFontProvider(false, false, false)
)。但是,如果您不确定,请不要这样做,因为这可能会导致结果中缺少某些文本。或将必要的字体添加到您的 collection 以确保。
- 重用
FontProgram
个实例。它们是thread-safe,可用于许多文档的转换。但是,这是 iText 的默认行为,除了使事实明确之外,这很可能不会改善您的情况。
- 如果你可以做到
1.
,那么如果你使用 FontProvider
的另一个实现,你也可以重用 FontSet
- 首先,创建你的 DefaultFontProvider
once 并在那里添加所有字体 once,然后,通过 defaultFP.getFontSet()
获取 FontSet
实例,之后您可以创建字体提供程序通过 new FontProvider(fontSet)
设置的字体 - 每次进行转换时都必须这样做(请参阅问题 1 的答案)。
我们正在根据 iText 7.1.2
和 htmlPDF 2.0.2
转换邮件信息。转换是在一个静态方法中完成的,该方法由并行线程为每个基于 html 的消息调用。代码看起来像这样简化(流在 finally 块中关闭):
ConverterProperties properties = new ConverterProperties();
FontProvider fontProvider = new DefaultFontProvider();
for (String font : ITEXT_FONTS) {
FontProgram fontProgram = FontProgramFactory.createFont(font);
fontProvider.addFont(fontProgram);
}
properties.setFontProvider(fontProvider);
fos = new FileOutputStream(targetFile);
HtmlConverter.convertToPdf(is, fos, properties);
for 循环用于从位于类路径中的 Noto
包中添加中文字体。在我们的环境中,我们现在有时会看到以下错误场景:
Caused by: java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3236)
at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118)
at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153)
at com.itextpdf.io.util.StreamUtil.inputStreamToArray(StreamUtil.java:212)
at com.itextpdf.html2pdf.resolver.font.DefaultFontProvider.addShippedFreeFonts(DefaultFontProvider.java:111)
at com.itextpdf.html2pdf.resolver.font.DefaultFontProvider.<init>(DefaultFontProvider.java:97)
at com.itextpdf.html2pdf.resolver.font.DefaultFontProvider.<init>(DefaultFontProvider.java:81)
问题是:
- 为每个调用创建 DefaultFontProvider 是否合法,或者应该只有一个实例(例如,因为创建成本)?
- 如果DefaultFontProvider只初始化一次->这个实例线程保存了吗?
提前致谢!
简短的回答是:请阅读文档。
问题 1 的答案
Is the creation of the DefaultFontProvider legit for every single call or should there be only one instance (e.g. because of the costs of creation)?
您应该像现在一样为每次转化创建一个新实例。字体提供程序与文档相关联,这在 base FontProvider
class 的文档中有说明。虽然如果您重复使用字体提供程序,它仍然很可能会起作用,但这不是我推荐的方式。
您正在使用的 setFontProvider
方法的文档明确说明了这一点:
Please note that FontProvider instances cannot be reused across several documents and thus as soon as you set this property, this ConverterProperties instance becomes only useful for a single HTML conversion.
问题 2 的答案
If the DefaultFontProvider is initialized only once -> is this instance thread save?
不保证线程安全。文档指导您仅使用 DefaultFontProvider
进行一次转换。
优化提示
(为了我会尝试应用它们)
- 看看
DefaultFontProvider(boolean, boolean, boolean)
构造函数。默认情况下,pdfHTML 加载标准 PDF 字体以及 pdfHTML 附带的字体。如果您确定您手动添加的字体涵盖了您在 HTML 文件中使用的所有脚本,则可以将false
传递给所有三个构造函数参数 (new DefaultFontProvider(false, false, false)
)。但是,如果您不确定,请不要这样做,因为这可能会导致结果中缺少某些文本。或将必要的字体添加到您的 collection 以确保。 - 重用
FontProgram
个实例。它们是thread-safe,可用于许多文档的转换。但是,这是 iText 的默认行为,除了使事实明确之外,这很可能不会改善您的情况。 - 如果你可以做到
1.
,那么如果你使用FontProvider
的另一个实现,你也可以重用FontSet
- 首先,创建你的DefaultFontProvider
once 并在那里添加所有字体 once,然后,通过defaultFP.getFontSet()
获取FontSet
实例,之后您可以创建字体提供程序通过new FontProvider(fontSet)
设置的字体 - 每次进行转换时都必须这样做(请参阅问题 1 的答案)。