PDF 生成模板编辑器
开源可视化 PDF 生成引擎,支持自定义模板,并提供面向开发者的 API,灵活构建文档生成流程。
1.3.0 版本新增 CEmailConverter,支持 EML/MIME 邮件和 Outlook MSG 文件。输入可以是文件路径、File 对象、字节数组或输入流;输出可以是 HTML 资源包,或包含邮件正文及可选附件列表的 PDF。
请使用版本匹配的 JAR 和 native 库。运行以下 PDF 示例前,应按应用许可证密钥使用有效许可证初始化 SDK。仅解析邮件或导出 HTML 包不需要许可证,但仍须加载 native 库;只使用 HTML 功能的应用可通过 System.load() 按绝对路径加载匹配的 native 库。
CEmailFormat 提供 EMAIL_FORMAT_AUTO、EMAIL_FORMAT_EML 和 EMAIL_FORMAT_MSG。AUTO 根据 Compound File Binary 文件头识别 MSG,其余输入按 EML 解析,不依赖文件扩展名。二进制 MSG 应直接以文件、字节或流传入,不要先转成 Java 字符串。
import CPDFGeneration.CEmailConverter;
import CPDFGeneration.CEmailDiagnostic;
import CPDFGeneration.CEmailResult;
try (CEmailResult result = CEmailConverter.ConvertToPdf("message.eml", "message.pdf")) {
if (!result.isSuccess()) {
for (int i = 0; i < result.GetDiagnosticCount(); i++) {
CEmailDiagnostic diagnostic = result.GetDiagnostic(i);
System.err.println(diagnostic.code + ": " + diagnostic.detail);
}
throw new IllegalStateException("Email conversion failed: " + result.GetStatus(),
result.GetLastCallbackException());
}
System.out.println("Converted format: " + result.GetFormat());
}将 message.eml 换为 MSG 路径,即可转换 MSG。输出 PDF 必须尚不存在:路径输出会保留已有文件;失败时会删除本次操作创建的部分文件。无论成功或失败,都应关闭返回的 CEmailResult。EMAIL_SUCCESS 和 EMAIL_SUCCESS_WITH_WARNINGS 都表示成功,isSuccess() 会同时接受这两种状态。
PDF 包含选定的邮件正文和启用时的附件列表,不会嵌入附件文件,也不会把附件内容转换成额外页面。PDF 中不保留附件下载链接;需要附件文件时,应导出 HTML 包或读取附件数据。
先通过 ConvertToHtml() 解析一次,再根据保留的内容导出离线 HTML 包或生成 PDF。下面的示例同时查询主正文来源,并设置自定义页边距。
import CPDFGeneration.CConverterProperties;
import CPDFGeneration.CEmailBodySource;
import CPDFGeneration.CEmailConverter;
import CPDFGeneration.CEmailResult;
import CPDFGeneration.CEmailStatus;
CConverterProperties properties = new CConverterProperties();
try {
properties.setPageMargins(48, 40, 48, 40);
try (CEmailResult result = CEmailConverter.ConvertToHtml("message.msg")) {
if (!result.isSuccess()) {
throw new IllegalStateException("Email parsing failed: " + result.GetStatus());
}
CEmailBodySource body = result.GetSelectedBodySource();
if (body.status.isSuccess()) {
System.out.println(body.mediaType + " / " + body.origin);
}
CEmailStatus exported = result.ExportHtml("mail-package");
if (!exported.isSuccess()) {
throw new IllegalStateException("HTML export failed: " + exported);
}
CEmailStatus written = result.WritePdf("message.pdf", properties);
if (!written.isSuccess()) {
throw new IllegalStateException("PDF write failed: " + written,
result.GetLastCallbackException());
}
}
} finally {
properties.release();
}HTML 包建议使用新的输出目录。ExportHtml() 导出 index.html、正文资源和启用的附件文件,不覆盖已有输出文件。失败时会清理本次导出创建的文件,并保留调用方原有数据。GetHtml() 只返回 UTF-8 HTML 字符串,其中可能包含相对资源路径;需要完整离线资源包时,应使用 ExportHtml()。
必须检查每次导出或写入返回的状态。GetStatus() 表示最近一次操作,可能替换最初的转换状态。新的 native 输出操作会根据初始 HTML 转换状态重新计算状态和诊断;此前取得的 Java 快照仍然有效。
PDF、授权或写入失败后,已经生成成功的 HTML、资源和附件可以保留,用于再次导出或重试 PDF。输入解析失败则不保证存在 HTML。GetSelectedBodySource().status 仅表示正文来源查询的状态,不修改结果对象的操作状态。该接口只标识选定的主正文,不枚举所有显示的 multipart 片段;来源 ID 是结果内部的不透明标识。 返回的 CEmailBodySource 包含 bodyId、sourceId、mediaType 和 origin;媒体类型为 text/plain 或 text/html,来源类别为 original、recovered、fallback 或 generated。
PDF 属性传 null 时,邮件转换默认使用 A4,上下边距为 32 CSS px,左右为 40 CSS px。传入属性对象后,将使用其页面大小、页边距(包括 0)、字体提供者和页眉页脚设置;操作不会修改调用方的属性或 base URI。HTML 预览的内边距不会叠加到 PDF 页边距上。打印配置详见页面边距和页眉与页脚。
选项传 null 时使用默认值,也可以创建 CEmailOptions。转换开始时会复制选项;复制期间不要并发修改同一个对象。
| 字段 | 默认值 | 说明 |
|---|---|---|
flags | 7 | 显示邮件头、显示附件列表并启用附件导出。 |
maxInputBytes | 100 MiB | 原始输入的最大字节数。 |
maxDecodedBytes | 256 MiB | 解码内容的字节预算。 |
maxHtmlBytes | 64 MiB | 生成 HTML 的字节预算。 |
cancellationCallback | null | 可选 CEmailCancelCallback,返回 true 请求取消。 |
CEmailFlags 可通过按位 OR 组合:
| 标志 | 作用 |
|---|---|
EMAIL_INCLUDE_HEADERS | 显示邮件头摘要,默认开启。 |
EMAIL_INCLUDE_ATTACHMENT_LIST | 显示附件列表,默认开启。 |
EMAIL_EXPORT_ATTACHMENTS | 随 HTML 包导出可用的附件文件,默认开启。 |
EMAIL_INLINE_RESOURCES | 在生成的 HTML 中内联正文资源。 |
EMAIL_RECOVER_EML | 对不规范 EML 启用有限恢复;应通过诊断检查降级或跳过的内容。 |
EMAIL_INCLUDE_BCC | 显示默认隐藏的 Bcc。 |
import CPDFGeneration.CEmailConverter;
import CPDFGeneration.CEmailFlags;
import CPDFGeneration.CEmailFormat;
import CPDFGeneration.CEmailOptions;
import CPDFGeneration.CEmailResult;
import java.util.concurrent.atomic.AtomicBoolean;
CEmailOptions options = new CEmailOptions();
options.flags |= CEmailFlags.EMAIL_INLINE_RESOURCES;
options.maxInputBytes = 20L * 1024 * 1024;
AtomicBoolean cancelled = new AtomicBoolean(false);
options.cancellationCallback = cancelled::get;
try (CEmailResult result = CEmailConverter.ConvertToHtml(
"message.eml", CEmailFormat.EMAIL_FORMAT_AUTO, options)) {
if (!result.isSuccess()) {
throw new IllegalStateException("Email parsing failed: " + result.GetStatus(),
result.GetLastCallbackException());
}
System.out.println("HTML characters: " + result.GetHtml().length());
}字节预算必须为非负数;0 表示真实的零上限,不代表无限。负预算在传给转换器时抛出 IllegalArgumentException。未知标志位返回 EMAIL_ERROR_INVALID_PARAMETER。即使没有超过配置的字节预算,仍可能触发解析器内部的结构限制。
取消采用协作式检查,发生在阶段之间和回调 I/O 中,不会中断正在执行的解析或排版操作,也不提供严格的超时保证。回调在当前调用 Java 线程上同步执行,应快速返回。其他线程可将示例中的 cancelled 标志设为 true 来请求取消。
结果对象会持有回调,后续 ExportHtml() 和 WritePdf() 仍使用该回调,直到结果关闭。修改原选项对象不会替换已保留的回调。回调异常会被视为取消,并保存在 GetLastCallbackException() 中。回调内不要重入 SDK、关闭正在使用的结果、释放其属性,或同步等待另一项 PDF 任务。
使用输入流时,应提供从当前位置开始要读取的邮件字节数,不要把 InputStream.available() 当成邮件长度。文件输入可使用 Files.size(),并确保读取期间输入不发生变化。
import CPDFGeneration.CEmailConverter;
import CPDFGeneration.CEmailResult;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
Path input = Paths.get("message.eml");
Path output = Paths.get("message-stream.pdf");
long length = Files.size(input);
try (InputStream source = Files.newInputStream(input);
OutputStream destination = Files.newOutputStream(output,
StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
CEmailResult result = CEmailConverter.ConvertToPdf(source, length, destination)) {
if (!result.isSuccess()) {
throw new IllegalStateException("Stream conversion failed: " + result.GetStatus(),
result.GetLastCallbackException());
}
}短读会循环补足。提前结束会返回 EMAIL_ERROR_READ,并在结果中保留原始 EOFException。SDK 不关闭或 flush 调用方的流;上面的 try-with-resources 由调用方负责关闭。失败或取消后,应丢弃部分输出,包括调用方为输出流自行创建的文件。
完全使用内存进行转换:
import CPDFGeneration.CEmailConverter;
import CPDFGeneration.CEmailResult;
import java.io.ByteArrayOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
byte[] email = Files.readAllBytes(Paths.get("message.msg"));
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (CEmailResult result = CEmailConverter.ConvertToPdf(email, output)) {
if (!result.isSuccess()) {
throw new IllegalStateException("Memory conversion failed: " + result.GetStatus(),
result.GetLastCallbackException());
}
byte[] pdf = output.toByteArray();
System.out.println("PDF bytes: " + pdf.length);
}内存输入和输出会占用 Java 堆空间。较大的邮件建议使用文件或流输入,只有需要附件内容时才复制附件字节。
下列重载组都提供简化方法和带显式格式、选项的完整方法;PDF 完整方法还接受 CConverterProperties:
| 操作 | 输入 | 输出 | 完整方法的附加参数 |
|---|---|---|---|
ConvertToHtml | String 路径或 File | CEmailResult | CEmailFormat、CEmailOptions |
ConvertToHtml | byte[] | CEmailResult | CEmailFormat、CEmailOptions |
ConvertToHtml | InputStream、long length | CEmailResult | CEmailFormat、CEmailOptions |
ConvertToPdf | String 路径 | String PDF 路径 | CEmailFormat、CEmailOptions、CConverterProperties |
ConvertToPdf | File | File PDF 输出 | CEmailFormat、CEmailOptions、CConverterProperties |
ConvertToPdf | byte[] | OutputStream | CEmailFormat、CEmailOptions、CConverterProperties |
ConvertToPdf | InputStream、long length | OutputStream | CEmailFormat、CEmailOptions、CConverterProperties |
简化方法使用 AUTO 格式及默认选项;PDF 简化方法还使用邮件的默认页面属性。所有转换方法均返回需要关闭的自有 CEmailResult。
| 结果方法 | 用途 |
|---|---|
GetStatus() / isSuccess() | 最近一次转换或 native 输出操作的状态。 |
GetFormat() | 解析器报告的输入格式。 |
GetPdfError() | PDF 或授权失败时的底层 CHtmlConverter.CErrorCode。 |
GetHtml() | HTML 字符串;未生成 HTML 时为空字符串。 |
GetResourceCount() / GetResource(index) | 正文资源数量及元数据、字节快照。 |
GetAttachmentCount() / GetAttachment(index) | 附件数量及元数据、字节快照。 |
GetDiagnosticCount() / GetDiagnostic(index) | 错误或警告诊断,包括代码、来源 ID、详情和降级处理。 |
GetSelectedBodySource() | 主正文来源的查询状态、ID、媒体类型和来源类别。 |
GetLastCallbackException() | 保留的 Java 流或取消回调异常;没有时为 null。 |
ExportHtml(String / File) | 导出离线 HTML 包。 |
WritePdf(String / File / OutputStream [, properties]) | 从保留的 HTML 生成或重试 PDF。 |
GetResource() 返回 CEmailResource,GetAttachment() 返回 CEmailAttachment,GetDiagnostic() 返回 CEmailDiagnostic。资源和附件提供媒体类型、相对路径、字节数等元数据。附件还提供 id、originalName、displayName、hasData、hidden、referencedByBody 和 nestedMessage。只有元数据的附件满足 hasData == false 和 getData() == null,与合法的零字节附件不同。
集合索引从 0 开始,越界时抛出 IndexOutOfBoundsException。元数据及字节快照归 Java 所有,结果关闭后仍然有效。每次 getData() 还会返回一个新的独立字节数组,因此较大的内容会额外占用堆空间。
CEmailStatus | 数值 | 含义 |
|---|---|---|
EMAIL_SUCCESS | 0 | 无警告的成功。 |
EMAIL_SUCCESS_WITH_WARNINGS | 1 | 成功,但有警告或降级内容。 |
EMAIL_ERROR_INVALID_PARAMETER | -1 | 参数、格式或选项无效。 |
EMAIL_ERROR_READ | -2 | 读取输入失败。 |
EMAIL_ERROR_PARSE | -3 | 邮件解析失败。 |
EMAIL_ERROR_HTML | -4 | HTML 生成失败。 |
EMAIL_ERROR_WRITE | -5 | 输出写入失败。 |
EMAIL_ERROR_PDF | -6 | PDF 渲染失败。 |
EMAIL_ERROR_LICENSE | -7 | PDF 授权检查失败。 |
EMAIL_ERROR_LIMIT | -8 | 超出字节预算或内部结构限制。 |
EMAIL_ERROR_CANCELLED | -9 | 已请求取消。 |
EMAIL_ERROR_OUT_OF_MEMORY | -10 | native 内存分配失败。 |
EMAIL_ERROR_INTERNAL | -11 | 内部错误或未知的 native 状态。 |
一般转换错误通过状态返回。负预算、路径含 NUL 等 Java 参数验证错误可能抛出 IllegalArgumentException;Java 或 JNI 内存分配失败仍可能抛出 OutOfMemoryError。流与取消回调异常保存在 GetLastCallbackException() 中,下一次 native 输出操作会替换该值。Java 参数验证失败不会改变前一次操作的状态。
CEmailResult 都应使用 try-with-resources。close() 和 release() 可重复调用;关闭后的 native 查询或输出会抛出 IllegalStateException,仍可调用 isClosed()。release() 使用同一锁;已释放的属性不能再次传给邮件 API。从发布包的 samples 目录运行:
cd EmailConvertTest
bash RunTest.sh示例直接读取 samples/TestFiles/email 中固定的 EML/MSG 文件,将 HTML 包、带页眉页脚的 PDF、嵌套邮件 PDF 和流转换 PDF 输出到新的 samples/out/email-example-* 目录。Linux 脚本会从 PATH 对应的 Java 安装中定位并预加载 libjsig.so,保持 JVM 信号处理器兼容性。在线授权缓存的后台刷新仍默认开启。
PrintOptionsTest 演示页边距和页眉页脚配置;FamilyHeaderFooterTest 使用 TestFiles/family_html/family_all_pages.html 演示封面跳页及页码设置。