把产品手册的封面补到项目说明前面、把报价单的第 2 到第 3 页并进合同、把几份报告合成一份汇总,说到底都是把页面从一份 PDF 搬到另一份 PDF 里。桌面软件的做法是开两个窗口来回拖拽,拖错一次就得重来;页数一多,搬过去的顺序也容易乱。还有一类麻烦是尺寸对不上:封面是 A5、目标文档是 A4,整页搬过去会露出一圈空白。
Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接加载、修改与保存 PDF 文档,复制过程全部在本地完成,通过虚拟文件系统(VFS)读写文件,无需后端配合。
本文介绍四个核心功能点,前三个搬的是整页,源页的尺寸、旋转与页边距都原样带过来;第四个只取页面内容,画到多大的页面上由你决定。
整页搬运
内容复制(模板)
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
复制单页并插入到指定位置
Spire.PDF for JavaScript 提供 PdfDocument.InsertPage 方法,用于把另一份文档里的某一页复制到当前文档,并指定它落在当前文档的第几位,不传落点下标时,追加到末尾。
function App() {
const copyPageAtPosition = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 把源文档与目标文档都载入 VFS
const sourceFileName = '源文档.pdf';
const targetFileName = '目标文档.pdf';
await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 分别载入两份文档
const sourceDoc = new pdfModule.PdfDocument();
sourceDoc.LoadFromFile(sourceFileName);
const targetDoc = new pdfModule.PdfDocument();
targetDoc.LoadFromFile(targetFileName);
// 把源文档的第 1 页复制到目标文档的最前面
// pageIndex 取自源文档,resultPageIndex 是复制结果在目标文档中的落点
targetDoc.InsertPage({ ldDoc: sourceDoc, pageIndex: 0, resultPageIndex: 0 });
// 保存结果文档
const outputFileName = '复制指定页面.pdf';
targetDoc.SaveToFile(outputFileName);
sourceDoc.Close();
targetDoc.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={copyPageAtPosition}>
开始复制
</button>
</div>
);
}
export default App;
resultPageIndex是四个功能点里唯一能控制落点的地方:传 0 插到首位、传 1 插到第 2 位,传当前页数则等于追加到末尾。
源文档的第 1 页排在目标文档最前面,文档由 2 页变为 3 页:

一次复制多页到文档末尾
Spire.PDF for JavaScript 还提供 PdfDocument.InsertPageRange 方法,用于把源文档里一段连续的页复制过来。它接收源文档和起点、终点两个下标,没有落点参数,复制结果一律追加到当前文档末尾。
function App() {
const appendPageRange = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 把源文档与目标文档都载入 VFS
const sourceFileName = '源文档.pdf';
const targetFileName = '目标文档.pdf';
await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 分别载入两份文档
const sourceDoc = new pdfModule.PdfDocument();
sourceDoc.LoadFromFile(sourceFileName);
const targetDoc = new pdfModule.PdfDocument();
targetDoc.LoadFromFile(targetFileName);
// 把源文档的第 2 页到第 3 页追加到目标文档末尾
// 注意:这里是位置参数,不是对象;endIndex 含端点
targetDoc.InsertPageRange(sourceDoc, 1, 2);
// 保存结果文档
const outputFileName = '复制页面范围.pdf';
targetDoc.SaveToFile(outputFileName);
sourceDoc.Close();
targetDoc.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={appendPageRange}>
复制第 2-3 页
</button>
</div>
);
}
export default App;
源文档的第 2、3 页追加后文档为 4 页:

复制整份文档的全部页面
整份文档都要搬时不必先算下标,PdfDocument.AppendPage 接收源文档对象,按原顺序把它的全部页追加到当前文档末尾。合并几份文档、给报告加附件这类场景,把手上的文档依次传进去就行。
function App() {
const appendWholeDocument = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 把源文档与目标文档都载入 VFS
const sourceFileName = '源文档.pdf';
const targetFileName = '目标文档.pdf';
await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 分别载入两份文档
const sourceDoc = new pdfModule.PdfDocument();
sourceDoc.LoadFromFile(sourceFileName);
const targetDoc = new pdfModule.PdfDocument();
targetDoc.LoadFromFile(targetFileName);
// 整份文档都要复制时用 AppendPage,按原顺序追加全部页
targetDoc.AppendPage({ doc: sourceDoc });
// 保存结果文档
const outputFileName = '复制全部页面.pdf';
targetDoc.SaveToFile(outputFileName);
sourceDoc.Close();
targetDoc.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={appendWholeDocument}>
开始复制
</button>
</div>
);
}
export default App;
源文档全部 4 页追加后文档为 6 页:

用页面模板复制页面内容
Spire.PDF for JavaScript 还提供 PdfPageBase.CreateTemplate 方法,用于把一页的内容取成一个 PdfTemplate,再由 Canvas.DrawTemplate 画到新建的页面上。前面三个方法搬的是整页,新页尺寸跟着源页走;模板取的是内容,画到多大的页面、画在什么位置、画几次都由你定,同一份模板可以反复使用。
function App() {
const copyPageWithTemplate = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 把待处理的 PDF 文件载入 VFS
const inputFileName = '源文档.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 载入文档
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 取出要复用的那一页,转成模板:内容取一次,后面可以画到多页上
const sourcePage = doc.Pages.get_Item(0);
const template = sourcePage.CreateTemplate();
// 第 1 处:在第 2 位插入一个 A4 新页,尺寸与源页不同,内容缩放到 297.6 x 421.6 画在 (80, 80)
const page1 = doc.Pages.Insert(1, new pdfModule.SizeF(595.0, 842.0), new pdfModule.PdfMargins({ margin: 0.0 }));
page1.Canvas.DrawTemplate(template, new pdfModule.PointF(80.0, 80.0), new pdfModule.SizeF(297.6, 421.6));
// 第 2 处:再插入一个 A4 新页,同一份模板缩得更小,画在右下角
const page2 = doc.Pages.Insert(2, new pdfModule.SizeF(595.0, 842.0), new pdfModule.PdfMargins({ margin: 0.0 }));
page2.Canvas.DrawTemplate(template, new pdfModule.PointF(320.0, 460.0), new pdfModule.SizeF(200.0, 283.3));
// 保存结果文档
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={copyPageWithTemplate}>
开始复制
</button>
</div>
);
}
export default App;
DrawTemplate 的第三个参数不传时,模板按原坐标绘制,不缩放——新页比源页大,内容就只占页面一角。新页的尺寸与页边距都由 Pages.Insert 决定,示例里四边页边距取 0,模板的绘制起点才是页面的左上角。
源文档第 1 页的内容被缩放到两个 A4 新页上,文档由 4 页变为 6 页:

常见问题
用 new PdfMargins(0.0) 建页面时报 Arg_NullReferenceException
原因:PdfMargins 的构造函数把单个数字参数当成内部句柄处理,new pdfModule.PdfMargins(0.0) 拿到的不是一个页边距对象——读它的 Left、Top 会抛 Arg_NullReferenceException,拿它去建页面也不会得到期望的页边距。
解决:用对象形式传页边距,四边都是 0 时写 { margin: 0.0 }:
// 四边页边距都为 0
const margins = new pdfModule.PdfMargins({ margin: 0.0 });
// 也可以四边分别指定
const custom = new pdfModule.PdfMargins({ left: 20.0, top: 20.0, right: 20.0, bottom: 20.0 });
复制页面时报下标越界或区间逆序
原因:页面下标从 0 开始,endIndex 含端点,所以合法区间是 0 到 Pages.Count - 1。超出这个范围会抛 Index out of range,startIndex 大于 endIndex 则抛 The start index is greater then the end index.。
解决:用 Pages.Count 兜住上界再传进去:
// 想复制第 2 到第 4 页:start = 1、end = 3,用页数兜住上界
const start = 1;
const end = Math.min(3, sourceDoc.Pages.Count - 1);
targetDoc.InsertPageRange(sourceDoc, start, end);
复制后旋转过的页面方向不对
原因:CreateTemplate() 取的是页面内容,页面的旋转角(/Rotate)不在模板里。源页带旋转角时,模板的坐标系与目标页对不上——直接 DrawTemplate 画到新页上,内容会落到可见区域之外,副本的 Rotation 也是 0。
解决:源页带旋转角时改用整页复制,内容和旋转角会一起带过去:
// 整页复制:旋转角随页面一起过来
targetDoc.InsertPage({ ldDoc: sourceDoc, pageIndex: 0, resultPageIndex: 1 });
如果必须用模板法,就先把源页的旋转角临时归零再取模板,复制完把角度补回源页和副本:
const rotation = sourcePage.Rotation.value;
// 临时归零,模板才会按页面的真实坐标导出
sourcePage.Rotation = 0;
const newPage = doc.Pages.Insert(1, sourcePage.Size, new pdfModule.PdfMargins({ margin: 0.0 }));
newPage.Canvas.DrawTemplate(sourcePage.CreateTemplate(), new pdfModule.PointF(0.0, 0.0));
// 源页还原,副本补上同样的角度
sourcePage.Rotation = rotation;
newPage.Rotation = rotation;
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。









