产品改名、术语调整、模板里的公司名写错,这类改动落到 PDF 上就很别扭:手上没有源文档时,只能把整页导成图片再压字,或者在阅读器里删掉原文字、手动补一个新的。合同、说明书这类成文文档尤其如此,同一个名字可能分散在正文、项目符号列表和文末说明里,逐处手改既慢又容易漏。
Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端加载、修改与保存 PDF 文档,改名只需要定位文本再写回,文档通过虚拟文件系统(VFS)读写,不经过后端。本文的四节分别处理替换第一处、替换全部(附带换字色、匹配方式与替换范围三个开关)、用背景色覆盖原词,以及画矩形盖住原文再重绘新词;样例里新旧名字等长,替换后排版不变。
本文介绍四个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
替换第一处匹配
Spire.PDF for JavaScript 提供 PdfTextReplacer 类,用于把页面上的文本换成另一段文本。它按页构造,ReplaceText 只替换找到的第一处,返回值是实际替换掉的处数(这里为 1),页面上剩下的同名文本保持原样。
function App() {
const replaceFirstMatch = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 字体载入 VFS
await window.spire.FetchFileToVFS('MSYH.TTC', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 将待处理的 PDF 文件载入 VFS
const inputFileName = '产品介绍.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载 PDF 文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 取第一页,替换范围就是这一页
let page = doc.Pages.get_Item(0);
// 创建文本替换器,只替换第一处匹配
const replacer = new pdfModule.PdfTextReplacer(page);
replacer.ReplaceText('星辰云盘', '星云网盘');
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={replaceFirstMatch}>
开始替换
</button>
</div>
);
}
export default App;
只替换了第一处:

替换全部匹配
Spire.PDF for JavaScript 还提供 PdfTextReplacer.ReplaceAllText(),用于把整页里所有匹配的文本一次换掉。它的入参与 ReplaceText 相同,同样返回替换掉的处数。它还带一个三参重载 ReplaceAllText(旧, 新, 颜色),替换的同时可以把字色一并改掉。
function App() {
const replaceAllMatches = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 字体载入 VFS
await window.spire.FetchFileToVFS('MSYH.TTC', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 将待处理的 PDF 文件载入 VFS
const inputFileName = '产品介绍.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载 PDF 文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
let page = doc.Pages.get_Item(0);
// 第三个参数传颜色,替换的同时把新词改成红色
const replacer = new pdfModule.PdfTextReplacer(page);
replacer.ReplaceAllText('星辰云盘', '星云网盘', pdfModule.Color.get_Red());
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={replaceAllMatches}>
开始替换
</button>
</div>
);
}
export default App;
旧名被全部替换、新名同时改成红色:

除了换字色,
PdfTextReplacer.Options上还有两个开关。ReplaceType决定「什么才算匹配」:默认只认完全相同的字符串,可以切到IgnoreCase(忽略大小写)、WholeWord(整词)或Regex(正则)。SetReplacementArea则把替换限制在一个矩形里,矩形可以先让PdfTextFinder找出来:
const replacer = new pdfModule.PdfTextReplacer(page);
// 忽略大小写
replacer.Options.ReplaceType = pdfModule.ReplaceActionType.IgnoreCase;
// 把替换限制在矩形内
const finds = new pdfModule.PdfTextFinder(page).Find('星辰云盘');
replacer.Options.SetReplacementArea(finds.get(0).Bounds[0]);
// 设置完再调用
const count = replacer.ReplaceAllText('星辰云盘', '星云网盘');
覆盖式替换
Spire.PDF for JavaScript 还提供 PdfTextFragment.ApplyRecoverString(),用于把找到的文本就地覆盖掉:先用一个背景色盖住原文字,再把新文本写在同一个位置。它省掉了自己算坐标、自己重绘的步骤,代价是只能指定背景色,新字的字体与前景色沿用原文字;背景色取白色时看不出接缝。需要连字体和字色一起换时,见下一节自己重绘的写法。
function App() {
const replaceByCovering = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 把文档用到的字体载入 VFS
await window.spire.FetchFileToVFS('MSYH.TTC', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 将待处理的 PDF 文件载入 VFS
const inputFileName = '产品介绍.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载 PDF 文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
let page = doc.Pages.get_Item(0);
// 先用查找器拿到目标文本的位置
const finder = new pdfModule.PdfTextFinder(page);
const finds = finder.Find('星辰云盘');
// 逐处覆盖:第二个参数是背景色,第三个参数 true 表示按 Unicode 写入新文本
for (let i = 0; i < finds.length; i++) {
finds.get(i).ApplyRecoverString('星云网盘', pdfModule.Color.get_White(), 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={replaceByCovering}>
开始替换
</button>
</div>
);
}
export default App;
原文被白色背景盖住、新词写在原位置的文档:

绘制矩形覆盖并重绘新文本
前面三节动的都是文本层的内容。这一节换个思路:用 PdfTextFinder 拿到关键字位置,先画一个白底矩形把原文字盖住,再在矩形里写上新的文字。相比 ApplyRecoverString,多出来的自由度是可以自己指定字体、字号和字色。
function App() {
const replaceByDrawing = 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 对象并加载 PDF 文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
let page = doc.Pages.get_Item(0);
// 先用查找器拿到目标文本的位置
const finder = new pdfModule.PdfTextFinder(page);
const finds = finder.Find('星辰云盘');
// 覆盖用的白底矩形,以及新词的字体与字色
const white = pdfModule.PdfBrushes.get_White();
const FONT_SIZE = 9;
const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: FONT_SIZE });
const brush = new pdfModule.PdfSolidBrush({ pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_DarkBlue() }) });
// LineLimit置false,避免文本在框内被裁切
const format = new pdfModule.PdfStringFormat();
format.LineLimit = false;
// 设置文本的对齐方式
format.Alignment = pdfModule.PdfTextAlignment.Center;
format.LineAlignment = pdfModule.PdfVerticalAlignment.Middle;
for (let i = 0; i < finds.length; i++) {
const rec = finds.get(i).Bounds[0];
// 先用白底矩形把原文字盖住
page.Canvas.DrawRectangle({ brush: white, rectangle: rec });
// 新词直接写进命中框
page.Canvas.DrawString({ s: '星云网盘', font: font, brush: brush, layoutRectangle: rec, format: format });
}
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={replaceByDrawing}>
开始替换
</button>
</div>
);
}
export default App;
白底矩形盖住原文、新词直接写在命中框内的文档:

常见问题
替换后旧名字还能被搜索、复制到
原因:ApplyRecoverString 属于视觉层操作——它在原文字上盖一层背景色再把新词画上去,原来的文本对象仍然留在文本层,所以阅读器里搜索、复制拿到的还是旧名字。实测覆盖式替换后旧词出现次数不变,新词多出 4 处。
解决:需要内容层面真正替换时改用 PdfTextReplacer,它的产物里旧词从文本层消失、新词进入文本层,实测旧词 4 处降到 0、新词由 1 处增至 5 处:
// 内容层替换:旧文本不再残留
const replacer = new pdfModule.PdfTextReplacer(page);
replacer.ReplaceAllText('星辰云盘', '星云网盘');
照官方示例写「白色矩形 + DrawString 重绘」,白底出来了但新文字不显示
原因:官方示例的思路和本文第四节一致——用 PdfTextFinder 找到位置,先画一个白色矩形盖住原文字,再用 Canvas.DrawString 把新文本画上去。白底矩形正常,但重绘这一步有一个坑:
DrawString的矩形重载受PdfStringFormat.LineLimit控制,而它默认为true,会把文本裁到框内;命中框又紧贴字形,高度如果有 11 磅(正好等于字号),装不下一整行行高,于是整行被裁光——页面和文本层都不会有新字。
解决:传一个关掉 LineLimit 的 PdfStringFormat,命中框就能原样当排版框用(第四节即此写法)。
const format = new pdfModule.PdfStringFormat();
format.LineLimit = false;
page.Canvas.DrawString({ s: '星云网盘', font: font, brush: brush, layoutRectangle: rec, format: format });
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。









