产品手册、会议资料印成小册子,要把多页文档按骑马钉的顺序重新拼到对开纸上。桌面软件里靠手工拖拽页序,页数一多就容易拼错,换一份文档还得从头再排;放到服务端处理,文件又得先离开用户的设备。
本文介绍用 Spire.PDF for JavaScript 实现创建 PDF 小册子。它基于 WebAssembly 在浏览器端加载、拼版并保存 PDF,全过程通过虚拟文件系统(VFS)在本地读写,无需后端配合。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
将 PDF 自动拼版为双面小册子
Spire.PDF for JavaScript 提供 PdfDocument.CreateBooklet() 方法,用于把一个现成 PDF 的页面按骑马钉顺序拼到对开页上。成品纸张的宽高由参数指定,bothSides 为 true 时按双面打印排布。
具体操作步骤如下:
- 获取 Spire.PDF 的 WASM 模块,确认已就绪。
- 把待处理的 PDF 载入 VFS。
- 创建
PdfDocument对象,按两张 A4 并排的宽度与 A4 的高度调用CreateBooklet()完成拼版。 - 使用
SaveToFile()方法保存结果文件。 - 从 VFS 读出结果文件并触发浏览器下载。
自动拼版创建小册子的代码如下:
function App() {
const createBooklet = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 将待处理的 PDF 载入 VFS
const inputFileName = 'Number.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 对开页的尺寸:宽度为两张 A4 并排,高度与 A4 相同
const width = pdfModule.PdfPageSize.A4().Width * 2;
const height = pdfModule.PdfPageSize.A4().Height;
// 创建文档并按骑马钉顺序自动拼版
const doc = new pdfModule.PdfDocument();
doc.CreateBooklet({ fileName: inputFileName, width: width, height: height, bothSides: true });
// 保存结果文件
const outputFileName = '自动拼版小册子.pdf';
doc.SaveToFile(outputFileName);
doc.Close();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>创建双面小册子</h1>
<button onClick={createBooklet}>
开始拼版
</button>
</div>
);
}
export default App;
源 PDF 的页面按骑马钉顺序两两拼到对开页上:

按自定义页序手动拼版对开页
Spire.PDF for JavaScript 还提供 PdfPageBase.CreateTemplate() 方法,用于把一页的内容抽成模板,再用 PdfTemplate.Draw() 画到新页面的指定位置。每张纸上放哪两页由页序数组决定,示例按阅读顺序把连续两页并排。
具体操作步骤如下:
- 获取 Spire.PDF 的 WASM 模块,确认已就绪。
- 把待处理的 PDF 载入 VFS。
- 创建
PdfDocument对象加载源文档;再新建一个输出文档,把页面宽度设为两张 A4 并排、高度设为 A4。 - 按页序循环,用
CreateTemplate()取出每页模板,用Draw()画到对开页的左半或右半。 - 使用
SaveToFile()方法保存结果文件,并从 VFS 读出触发下载。
手动拼版对开页的代码如下:
function App() {
const createCustomBooklet = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 将待处理的 PDF 载入 VFS
const inputFileName = 'Number.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载源文档
const srcDoc = new pdfModule.PdfDocument();
srcDoc.LoadFromFile(inputFileName);
const pageCount = srcDoc.Pages.Count;
const a4 = pdfModule.PdfPageSize.A4();
// 新建输出文档:页面宽度为两张 A4 并排,高度与 A4 相同
const bookletDoc = new pdfModule.PdfDocument();
bookletDoc.PageSettings.Margins.All = 0;
bookletDoc.PageSettings.Width = a4.Width * 2;
bookletDoc.PageSettings.Height = a4.Height;
// 每张纸放连续的两页:1|2、3|4、5|6……
for (let i = 0; i < pageCount; i += 2) {
const sheet = bookletDoc.Pages.Add();
// 左半页
const leftTemplate = srcDoc.Pages.get_Item(i).CreateTemplate();
leftTemplate.Draw({ page: sheet, location: new pdfModule.PointF(0, 0) });
// 右半页,页数为奇数时最后一张纸的右侧留白
if (i + 1 < pageCount) {
const rightTemplate = srcDoc.Pages.get_Item(i + 1).CreateTemplate();
rightTemplate.Draw({ page: sheet, location: new pdfModule.PointF(a4.Width, 0) });
}
}
// 保存结果文件
const outputFileName = '自定义页序小册子.pdf';
bookletDoc.SaveToFile(outputFileName);
bookletDoc.Close();
srcDoc.Close();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>自定义页序拼版</h1>
<button onClick={createCustomBooklet}>
开始拼版
</button>
</div>
);
}
export default App;
连续两页并排到同一张纸上,6 页文档得到 3 张对开页:

常见问题
小册子用多大的纸,由什么决定
原因:CreateBooklet() 的 width 与 height 描述的是成品对开页的纸张尺寸,不是源文档单页的尺寸。本文取「A4 宽度 × 2」和「A4 高度」,得到一张 A3 大小的对开页,左半边和右半边各是一页 A4。
解决:要换成 A4 纸打印,就把纸张设为 A4 横放——宽取 A4 的高度、高取 A4 的宽度,左右两半各占一页 A5,源页会按半页缩放。
// A4 纸横放:左右各一页 A5
const width = pdfModule.PdfPageSize.A4().Height;
const height = pdfModule.PdfPageSize.A4().Width;
bothSides 参数有什么用
原因:bothSides 告诉 CreateBooklet() 成品是否按双面打印来排布。传 true 时按骑马钉的正反面成对分组,并把页数补齐到 4 的倍数;传 false 时按单面排布,不补空白页。
解决:用双面打印机并对折装订,传 true;只需要单面打印时传 false。打印方式要和拼版方式对上,否则页序会对不上。
页数不是 4 的倍数时,小册子会怎么排
原因:骑马钉每张纸要放四页(正反各两页),页数不足时,最后一张纸会出现空白位置。
解决:自动拼版由 CreateBooklet() 补齐所需的对开页;手动拼版时,循环里已经用 i + 1 < pageCount 判断右侧有没有页,落单的那一页右侧就是空白。
手动拼版的内容偏到一边,或者超出了纸面
原因:PdfTemplate.Draw() 默认按模板的原始尺寸绘制。源页比半张纸宽,右侧那页就会越过中线或跑到纸面之外。
解决:让源页尺寸与半张纸保持一致。本文的源页是 A4,对开页宽为两张 A4,落在 x = 0 和 x = A4.Width 正好对齐;源页尺寸不同,就改用 PdfCanvas.DrawTemplate(),用 size 指定半页大小,模板会缩放到这个尺寸。
sheet.Canvas.DrawTemplate({
template: rightTemplate,
location: new pdfModule.PointF(a4.Width, 0),
size: new pdfModule.SizeF(a4.Width, a4.Height)
});
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。







