引言
在 Web 应用中处理文档格式转换一直是前端开发中的难点。传统方案通常依赖服务端接口,但这也带来了额外的服务器成本和网络延迟。随着 WebAssembly 技术的成熟,如今我们可以在浏览器端直接完成 RTF 到 PDF 的转换,本文将分享一种基于 WASM 的实现方案。
环境准备与安装配置
在开始编码之前,需要完成以下准备工作:
1. 安装依赖包
通过 npm 安装文档处理库:
bash
npm i spire.office
该包包含了多种文档格式的处理能力,本文仅使用其中的 RTF 转 PDF 功能。
2. 部署 WASM 资源文件
安装完成后,需要将必要的运行时文件复制到项目的前端静态目录(如 public/)中,包括:
spire.doc.jsspire.common.jsSpire.Doc.Wasm.zipSpire.Common.Wasm.zip_framework/
将这些文件复制到 public/ 目录后,示例目录结构如下:
public/
├── spire.doc.js
├── spire.common.js
├── Spire.Doc.Wasm.zip
├── Spire.Common.Wasm.zip
├── _framework/
│ └── ...
└── static/
├── font/ # 存放字体文件(如 times.ttf 等)
└── data/ # 存放待转换的 RTF 样本文件(可选)
3. 准备字体文件
为确保 PDF 中的文字正确渲染,需要将所需的 TrueType 字体文件放置在静态目录中(如上例的 public/static/font/)。本文示例使用 Times New Roman 系列字体,你也可以根据实际文档内容替换为其他字体。
注意:字体文件需遵循相应授权协议,请确保您有权在应用中使用这些字体。
完成上述准备后,即可开始编写转换逻辑。
技术选型与架构
本方案采用 WebAssembly(WASM) 技术,将成熟的文档处理库编译为 WASM 模块,在浏览器中运行。整体架构如下:
- 加载层:使用动态 import 异步加载 WASM 模块
- 虚拟文件系统(VFS):在浏览器内存中模拟文件系统,供 WASM 模块读写
- 转换引擎:基于 WASM 的文档处理核心,负责解析 RTF 并生成 PDF
- 输出层:从 VFS 读取生成的 PDF 并触发下载
flowchart TB A用户触发转换 --> B加载 WASM 模块 B --> C初始化虚拟文件系统 VFS C --> D加载字体文件到 VFS D --> E加载 RTF 文件到 VFS E --> FDocument.LoadFromFile 解析 RTF F --> GDocument.SaveToFile 生成 PDF G --> H从 VFS 读取 PDF 数据 H --> I创建 Blob 并触发下载 I --> J清理资源
核心代码解析
1. WASM 模块加载
使用 React Hooks 管理模块的加载状态,通过动态 import 实现按需加载:
javascript
useEffect(() => {
(async () => {
try {
const publicUrl = process.env.PUBLIC_URL || '';
// 动态导入 WASM 模块
const spireModule = await import(
/* webpackIgnore: true */ `${publicUrl}/spire.doc.js`
);
const rawModule = spireModule.default || spireModule;
// 初始化 WASM 实例,指定 .wasm 文件的位置
window.wasmModule = typeof rawModule === 'function'
? await rawModule({
locateFile: p => p.endsWith('.wasm')
? `${publicUrl}/${p}`
: p
})
: rawModule;
setWasmModule(window.wasmModule);
} catch (error) {
console.error('Failed to load WASM module:', error);
}
})();
}, []);
这里的关键是 locateFile 函数,它告诉 WASM 运行时从哪里加载 .wasm 二进制文件。/* webpackIgnore: true */ 注释确保 Webpack 不会尝试解析这个动态路径。
2. 虚拟文件系统(VFS)
WASM 模块运行在沙箱环境中,无法直接访问宿主操作系统的文件系统。因此需要在 WASM 的内存中构建一个虚拟文件系统(VFS),将所需的文件写入其中:
javascript
// 加载字体文件到 VFS
await window.spire.FetchFileToVFS(
'times.ttf', // 文件名
'/Library/Fonts/', // VFS 中的目标路径
`${publicUrl}/static/font/` // 宿主环境中的资源路径
);
await window.spire.FetchFileToVFS('timesbd.ttf', '/Library/Fonts/', `${publicUrl}/static/font/`);
await window.spire.FetchFileToVFS('timesbi.ttf', '/Library/Fonts/', `${publicUrl}/static/font/`);
await window.spire.FetchFileToVFS('timesi.ttf', '/Library/Fonts/', `${publicUrl}/static/font/`);
字体文件对于 PDF 生成至关重要,它确保了输出文档中的文字能够正确渲染。
3. 文档加载与转换
javascript
const convertRtfToPdf = async () => {
const wasmModule = window.wasmModule.spiredoc;
if (!wasmModule) return;
// 将输入文件写入 VFS
await window.spire.FetchFileToVFS(
'input.rtf',
'',
`${process.env.PUBLIC_URL}/static/data/`
);
// 创建 Document 实例
const doc = new wasmModule.Document();
// 从 VFS 加载 RTF 文件
doc.LoadFromFile('input.rtf');
// 保存为 PDF 格式
const outputFileName = 'RtfToPdf.pdf';
doc.SaveToFile({
fileName: outputFileName,
fileFormat: wasmModule.FileFormat.PDF
});
// 从 VFS 读取生成的 PDF
const pdfData = window.dotnetRuntime.Module.FS.readFile(outputFileName);
// 创建 Blob 并下载
const blob = new Blob([pdfData], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
// 清理
URL.revokeObjectURL(url);
doc.Dispose();
};
4. 资源管理与清理
WASM 模块分配的内存需要手动释放,否则可能导致内存泄漏:
javascript
// 清理 VFS 中的文件
window.dotnetRuntime.Module.FS.unlink('input.rtf');
window.dotnetRuntime.Module.FS.unlink('RtfToPdf.pdf');
// 释放 Document 对象
doc.Dispose();
兼容性与注意事项
浏览器兼容性
该方案依赖 WebAssembly 和 Blob URL 特性,适用于所有现代浏览器(Chrome、Firefox、Safari、Edge)。对于需要兼容 IE 等老旧浏览器的场景,仍需服务端转换方案兜底。
跨域问题
如果字体或 RTF 文件存放在不同的域名下,需要确保服务器配置了正确的 CORS 头。
内存限制
WASM 模块在浏览器中运行时受限于浏览器标签页的内存限制(通常约为 2GB)。对于超大文档,建议分页处理或限制单次转换的文件大小。
总结
通过 WebAssembly 技术,我们成功在浏览器端实现了 RTF 到 PDF 的转换,避免了服务端依赖,降低了架构复杂度。这种方案的核心优势在于:
- 低延迟:所有处理在本地完成,无需网络请求
- 低成本:无需维护转换服务器
- 高隐私:用户文档无需上传到第三方服务
当然,这种方案也有其适用边界:对于超大文档或复杂排版,可能需要结合服务端方案共同使用。选择何种方案,需要根据具体的业务场景和技术约束来权衡。