合同、审批单定稿之后常要盖一枚「已核准」的图章,或在页角留一行时间戳,标记这份文件由谁在什么时候确认过。桌面软件做这件事要一枚枚手动盖,文件一多就很吃力;把文档传到服务端处理,又等于让文件离开了本地。
Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端加载、修改与保存 PDF 文档,图章在本地生成,通过虚拟文件系统(VFS)读写,无需后端配合。它把图章当作一类注释处理:PdfRubberStampAnnotation 决定图章在页面上的位置与大小,外观则由 PdfAppearance 挂上的 PdfTemplate 决定——在模板上画图片就是图像章,画文字就是文字章。
本文介绍三个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
添加图像图章
Spire.PDF for JavaScript 提供 PdfRubberStampAnnotation 类,用于在页面的指定位置放置一枚图章。它只框定图章的位置与大小,外观由 PdfAppearance 决定。
function App() {
const addImageStamp = 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 stampImageName = '印章.png';
await window.spire.FetchFileToVFS(stampImageName, "", `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 获取第一页
let page = doc.Pages.get_Item(0);
// 载入印章图片
let image = pdfModule.PdfImage.FromFile(stampImageName);
// 把图片缩到 120 磅
const maxSize = 120;
const scale = Math.min(maxSize / image.Width, maxSize / image.Height, 1);
const stampWidth = image.Width * scale;
const stampHeight = image.Height * scale;
// 创建与图章等大的模板,并把图片铺满模板
let template = new pdfModule.PdfTemplate({ width: stampWidth, height: stampHeight });
template.Graphics.DrawImage({ image: image, x: 0, y: 0, width: stampWidth, height: stampHeight });
// 在页面右下角框出图章的位置,矩形与模板等大
let rect = new pdfModule.RectangleF({
location: new pdfModule.PointF(page.ActualSize.Width - stampWidth - 40, page.ActualSize.Height - stampHeight - 40),
size: new pdfModule.SizeF({ width: stampWidth, height: stampHeight })
});
// 创建图章注释,并把模板设成它的外观
let stamp = new pdfModule.PdfRubberStampAnnotation({ rectangle: rect });
let appearance = new pdfModule.PdfAppearance(stamp);
appearance.Normal = template;
stamp.Appearance = appearance;
// 将图章注释添加到页面的注释集合中
page.AnnotationsWidget.Add(stamp);
// 保存文档
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={addImageStamp}>
开始添加
</button>
</div>
);
}
export default App;
在页面右下角盖上图像图章后的 PDF 文档:

添加文字图章
Spire.PDF for JavaScript 还提供 PdfPath、PdfPen 与 PdfTrueTypeFont,用于把文字画成图章。PdfPath 用四段圆弧拼出圆角矩形,DrawPath 把它描边成外框,再用 DrawString 在框内写上文字,得到的是一枚带边框的文字图章。
function App() {
const addTextStamp = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 将含中文字形的字体载入 VFS 的字体目录
await window.spire.FetchFileToVFS('SIMSUN.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 将待处理的 PDF 载入 VFS
const inputFileName = '示例文档.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 获取第一页
let page = doc.Pages.get_Item(0);
// 创建承载图章的模板
let template = new pdfModule.PdfTemplate({ width: 125, height: 55 });
// 用 VFS 中的字体文件构造字体,并准备画笔
let font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: 12, style: pdfModule.PdfFontStyle.Bold });
let brush = new pdfModule.PdfSolidBrush({ pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_DarkRed() }) });
let pen = new pdfModule.PdfPen({ brush: brush });
// 用四段圆弧加一条直线拼出圆角矩形路径
const radius = 20;
let path = new pdfModule.PdfPath();
path.AddArc({ x: 0, y: 0, width: radius, height: radius, startAngle: 180, sweepAngle: 90 });
path.AddArc({ x: template.Width - radius, y: 0, width: radius, height: radius, startAngle: 270, sweepAngle: 90 });
path.AddArc({ x: template.Width - radius, y: template.Height - radius, width: radius, height: radius, startAngle: 0, sweepAngle: 90 });
path.AddArc({ x: 0, y: template.Height - radius, width: radius, height: radius, startAngle: 90, sweepAngle: 90 });
path.AddLine({ x1: 0, y1: template.Height - radius, x2: 0, y2: radius / 2 });
// 把路径描边成图章的外框
template.Graphics.DrawPath({ pen: pen, path: path });
// 在外框内写上两行文字
template.Graphics.DrawString({ s: '已核准', font: font, brush: brush, point: new pdfModule.PointF(40, 10) });
template.Graphics.DrawString({ s: '市场营销部', font: font, brush: brush, point: new pdfModule.PointF(30, 32) });
// 在页面右下角框出图章的位置,矩形与模板等大
let rect = new pdfModule.RectangleF({
location: new pdfModule.PointF(page.ActualSize.Width - template.Width - 40, page.ActualSize.Height - template.Height - 40),
size: template.Size
});
// 创建图章注释,并把模板设成它的外观
let stamp = new pdfModule.PdfRubberStampAnnotation({ rectangle: rect });
let appearance = new pdfModule.PdfAppearance(stamp);
appearance.Normal = template;
stamp.Appearance = appearance;
// 将图章注释添加到页面的注释集合中
page.AnnotationsWidget.Add(stamp);
// 保存文档
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={addTextStamp}>
开始添加
</button>
</div>
);
}
export default App;
带圆角边框与「已核准」文字的文字图章:

添加日期时间图章
Spire.PDF for JavaScript 还提供 PdfTemplate,用于把任意内容渲染成图章外观。下面的代码演示了在模板上画一行当前时间:字符串用浏览器原生的 Date 生成,DrawString 接受的就是一段普通字符串,图章内容便会随生成时机变化。
function App() {
const addDateTimeStamp = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 将含中文字形的字体载入 VFS 的字体目录
await window.spire.FetchFileToVFS('SIMSUN.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 将待处理的 PDF 载入 VFS
const inputFileName = '示例文档.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 获取第一页
let page = doc.Pages.get_Item(0);
// 获取当前日期时间
const now = new Date();
const pad = (value) => String(value).padStart(2, '0');
let timeString = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
// 创建模板,宽高按文字长度预留
let template = new pdfModule.PdfTemplate({ width: 150, height: 16 });
// 准备字体与画笔
let font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: 12 });
let brush = new pdfModule.PdfSolidBrush({ pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Blue() }) });
// 把时间文字画到模板上
template.Graphics.DrawString({ s: timeString, font: font, brush: brush, point: new pdfModule.PointF(0, 0) });
// 在页面右下角框出图章的位置,矩形与模板等大
let rect = new pdfModule.RectangleF({
location: new pdfModule.PointF(page.ActualSize.Width - template.Width - 20, page.ActualSize.Height - template.Height - 20),
size: template.Size
});
// 创建图章注释,并把模板设成它的外观
let stamp = new pdfModule.PdfRubberStampAnnotation({ rectangle: rect });
let appearance = new pdfModule.PdfAppearance(stamp);
appearance.Normal = template;
stamp.Appearance = appearance;
// 将图章注释添加到页面的注释集合中
page.AnnotationsWidget.Add(stamp);
// 保存文档
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={addDateTimeStamp}>
开始添加
</button>
</div>
);
}
export default App;
页角记录了当前日期时间的图章:

常见问题
图章盖得太大,位置也对不上
原因:图章在页面上的位置和大小由 PdfRubberStampAnnotation 的矩形决定,而外观又铺满这个矩形。模板尺寸与矩形尺寸不一致时,外观会被拉伸;矩形坐标若直接套用图片的像素宽高而不缩放,图章还会盖过正文甚至越出页面。
解决:先算好目标显示尺寸,让模板与矩形共用同一组宽高,再按页面尺寸定位:
// 模板与矩形用同一组宽高
let template = new pdfModule.PdfTemplate({ width: stampWidth, height: stampHeight });
let rect = new pdfModule.RectangleF({
location: new pdfModule.PointF(page.ActualSize.Width - stampWidth - 40, page.ActualSize.Height - stampHeight - 40),
size: template.Size
});
文字图章里的中文显示成空心方框
原因:PdfTrueTypeFont 通过 fontFile 取字体文件,若该文件不含中文字形(如 ARIAL.TTF 这类西文字体),中文取不到字形,就渲染成一串方框。
解决:换用含中文字形的字体,并先把文件载入虚拟文件系统的 /Library/Fonts/:
await window.spire.FetchFileToVFS('SIMSUN.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: 12 });
日期时间图章的时间格式怎么自定义
原因:DrawString 只画传入的字符串,格式完全由生成字符串的代码决定;toLocaleString 之类的写法又依赖浏览器的区域设置,换一个环境输出可能就不一样。
解决:需要固定格式时,用 Date 的取值方法自己拼:
const now = new Date();
const pad = (value) => String(value).padStart(2, '0');
const timeString = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}`;
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。









