PDF 注释提供了一种便捷的方式,可以在不修改文档原始内容的情况下,为 PDF 添加评论、高亮、备注、链接、图章以及其他交互式元素。它们广泛应用于文档审阅、校对、协作和审批流程中。
借助 Spire.PDF for JavaScript,开发人员可以直接在 React 应用程序中通过编程方式向 PDF 文档添加多种类型的注释。本文将演示两种常用的注释类型:用于高亮指定文本的 文本标记注释,以及用于在页面上附加评论的 弹出式注释。
本文内容:
在处理 PDF 注释之前,需要先将 Spire.PDF for JavaScript 集成到 React 应用程序中。
可以通过 npm 安装所需的软件包:
npm i spire.office
然后,将所需的 JavaScript、WebAssembly 和框架文件复制到 React 项目的 public 文件夹中,以便应用程序在运行时加载这些文件。
有关详细的安装和配置说明,请参考:
如何在 React 项目中集成 Spire.PDF for JavaScript
以下示例假定 Spire.PDF 已完成配置,并且输入 PDF 文件已放置在 public 文件夹中。
文本标记注释通常用于文档审阅。开发人员可以利用它们对 PDF 中的指定文本进行高亮、下划线、删除线或其他形式的标记。
在本示例中,我们首先使用 PdfTextFinder 在 PDF 中查找指定句子。找到文本后,获取其边界矩形,并针对相应区域分别创建高亮注释。
以下 React 代码演示如何高亮 PDF 文档中的指定文本:
import React, { useEffect, useState } from 'react';
function App() {
const [ready, setReady] = useState(false);
useEffect(() => {
(async () => {
const publicUrl = process.env.PUBLIC_URL || '';
await import(/* webpackIgnore: true */ `${publicUrl}/spire.common.js`);
const spireModule = await import(/* webpackIgnore: true */ `${publicUrl}/spire.pdf.js`);
const rawModule = spireModule.default || spireModule;
window.wasmModule = typeof rawModule === 'function'
? await rawModule({ locateFile: p => p.endsWith('.wasm') ? `${publicUrl}/${p}` : p })
: rawModule;
setReady(true);
})();
}, []);
const addMarkupAnnotation = async () => {
const wasmModule = window.wasmModule.spirepdf;
const inputFileName = 'input.pdf';
const outputFileName = 'MarkupAnnotation.pdf';
await window.spire.FetchFileToVFS(inputFileName, '/', `${process.env.PUBLIC_URL || ''}/`);
const doc = new wasmModule.PdfDocument();
doc.LoadFromFile(inputFileName);
const page = doc.Pages.get_Item(0);
const finder = new wasmModule.PdfTextFinder(page);
const TARGET_TEXT = '月亮,作为夜空中最明亮的天体之一,自古以来就吸引着人类的注意。它不仅影响着地球的潮'+
'汐变化,还在人类文化、神话和科学探索中占据重要地位。';
const textFragment = finder.Find(TARGET_TEXT).get(0);
const bounds = textFragment.Bounds.toArray();
bounds.forEach((rect) => {
const annotation = new wasmModule.PdfTextMarkupAnnotation(
'Administrator',
'这是一个高亮标注。',
rect
);
annotation.TextMarkupAnnotationType =
wasmModule.PdfTextMarkupAnnotationType.Highlight;
annotation.TextMarkupColor =
new wasmModule.PdfRGBColor({
color: wasmModule.Color.get_LightYellow()
});
page.AnnotationsWidget.Add(annotation);
});
doc.SaveToFile(outputFileName);
doc.Close();
finder.Dispose();
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const url = URL.createObjectURL(
new Blob([fileArray], { type: 'application/pdf' })
);
const link = document.createElement('a');
link.href = url;
link.download = outputFileName;
link.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', padding: 30 }}>
<h1>Add Markup Annotation to PDF</h1>
<button onClick={addMarkupAnnotation} disabled={!ready}>
Add Markup Annotation
</button>
</div>
);
}
export default App;
效果图:

添加注释的过程可以分为几个主要步骤。
首先,FetchFileToVFS() 会将 input.pdf 加载到 WebAssembly 虚拟文件系统中。随后创建 PdfDocument 对象,用于加载和操作 PDF 文档。
await window.spire.FetchFileToVFS(
inputFileName,
'/',
`${process.env.PUBLIC_URL || ''}/`
);
const doc = new wasmModule.PdfDocument();
doc.LoadFromFile(inputFileName);
接下来,获取 PDF 的第一页,并将其传递给 PdfTextFinder。
const page = doc.Pages.get_Item(0);
const finder = new wasmModule.PdfTextFinder(page);
随后查找目标文本,并获取其对应的边界矩形:
const textFragment = finder.Find(TARGET_TEXT).get(0);
const bounds = textFragment.Bounds.toArray();
一个句子可能跨越多行,因此其位置可能由多个矩形组成。代码会遍历所有返回的边界矩形,并分别为每个区域创建一个 PdfTextMarkupAnnotation。
bounds.forEach((rect) => {
const annotation = new wasmModule.PdfTextMarkupAnnotation(
'Administrator',
'This is a markup annotation.',
rect
);
annotation.TextMarkupAnnotationType =
wasmModule.PdfTextMarkupAnnotationType.Highlight;
page.AnnotationsWidget.Add(annotation);
});
在本示例中,注释类型被设置为 Highlight,颜色则设置为浅黄色。
最后,将修改后的 PDF 保存到虚拟文件系统中,并转换为 Blob,从而允许浏览器下载生成的文件。
当需要在 PDF 页面的指定位置添加评论或备注时,可以使用弹出式注释。与直接标记现有文本不同,弹出式注释会在页面上创建一个注释图标,读者可以在兼容的 PDF 阅读器中与其交互。
以下示例在 PDF 的第一页添加一条评论注释:
import React, { useEffect, useState } from 'react';
function App() {
const [ready, setReady] = useState(false);
useEffect(() => {
(async () => {
const publicUrl = process.env.PUBLIC_URL || '';
await import(/* webpackIgnore: true */ `${publicUrl}/spire.common.js`);
const spireModule = await import(/* webpackIgnore: true */ `${publicUrl}/spire.pdf.js`);
const rawModule = spireModule.default || spireModule;
window.wasmModule = typeof rawModule === 'function'
? await rawModule({ locateFile: p => p.endsWith('.wasm') ? `${publicUrl}/${p}` : p })
: rawModule;
setReady(true);
})();
}, []);
const addPopupAnnotation = async () => {
const wasmModule = window.wasmModule.spirepdf;
const inputFileName = 'input.pdf';
const outputFileName = 'PopupAnnotation.pdf';
await window.spire.FetchFileToVFS(inputFileName, '/', `${process.env.PUBLIC_URL || ''}/`);
const doc = new wasmModule.PdfDocument();
doc.LoadFromFile(inputFileName);
const page = doc.Pages.get_Item(0);
const rect = new wasmModule.RectangleF({ x: 155, y: 85, width: 0, height: 0 });
const annotation = new wasmModule.PdfPopupAnnotation({
rectangle: rect,
text: '这是一个弹出式注释。',
});
annotation.Icon = wasmModule.PdfPopupIcon.Comment;
annotation.Color = new wasmModule.PdfRGBColor({ color: wasmModule.Color.get_Red() });
page.Annotations.Add(annotation);
doc.SaveToFile(outputFileName);
doc.Close();
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const url = URL.createObjectURL(new Blob([fileArray], { type: 'application/pdf' }));
const link = document.createElement('a');
link.href = url;
link.download = outputFileName;
link.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', padding: 30 }}>
<h1>Add Popup Annotation to PDF</h1>
<button onClick={addPopupAnnotation} disabled={!ready}>
Add Popup Annotation
</button>
</div>
);
}
export default App;
效果图:

加载 PDF 后,首先获取第一页:
const page = doc.Pages.get_Item(0);
随后创建一个 RectangleF 对象,用于定义弹出式注释在页面上的位置。
const rect = new wasmModule.RectangleF({
x: 155,
y: 85,
width: 0,
height: 0
});
接下来创建 PdfPopupAnnotation 对象。其中,rectangle 属性决定注释显示的位置,而 text 则定义注释中显示的评论内容。
const annotation = new wasmModule.PdfPopupAnnotation({
rectangle: rect,
text: 'This is a popup annotation.'
});
还可以自定义注释图标的样式和颜色:
annotation.Icon = wasmModule.PdfPopupIcon.Comment;
annotation.Color =
new wasmModule.PdfRGBColor({
color: wasmModule.Color.get_Red()
});
最后,通过以下代码将注释添加到页面中:
page.Annotations.Add(annotation);
随后保存修改后的 PDF,并在浏览器中下载。
虽然这两种注释都用于向 PDF 文档添加审阅信息,但它们适用于不同的使用场景。
| 注释类型 | 常见用途 | 定位方式 |
|---|---|---|
| 文本标记注释 | 高亮或标记 PDF 中已有的文本 | 根据选中文本的边界确定位置 |
| 弹出式注释 | 在指定位置添加评论或备注 | 根据页面坐标确定位置 |
文本标记注释尤其适合审阅已有内容,因为注释可以精确跟随目标文本的位置。相比之下,当评论只需要关联到页面中的某个区域,而不是特定文本片段时,弹出式注释会更加灵活。
以上示例仅介绍了两种注释类型,但在使用 Spire.PDF for JavaScript 创建其他类型的注释时,整体处理流程基本类似。
典型的注释处理流程如下:
例如,自由文本注释可以直接在页面上显示文字,图章注释可以表示“已批准”等审阅状态,而链接注释则可以将 PDF 内容连接到网页、外部文件或同一文档中的其他位置。
不同注释类型所使用的注释类和可配置属性会有所不同,但基本实现方式大体一致。
注释可以增强 PDF 文档的交互性,在文档审阅、评论、协作和审批等场景中尤其有用。
借助 Spire.PDF for JavaScript,React 应用程序可以直接在浏览器中通过编程方式创建 PDF 注释。本文演示了如何查找指定文本并添加 文本标记注释,以及如何在 PDF 页面的指定坐标处添加 弹出式注释。
通过相同的基本思路,开发人员还可以根据实际应用需求进一步实现自由文本、图章、形状、链接以及其他类型的注释。
Spire.PDF for JavaScript 支持多种注释类型,包括文本标记注释、自由文本注释、弹出式注释、图章注释、形状注释、网页链接注释、文件链接注释以及文档链接注释等。
具体使用的类和属性取决于注释类型。
可以。你可以使用 PdfTextFinder 在 PDF 中搜索指定文本,并获取对应的边界矩形。然后,可以使用这些矩形自动确定文本标记注释的位置。
这种方式非常适合自动高亮关键词、审阅术语或指定句子等场景。
一个句子可能跨越 PDF 中的多行。在这种情况下,文本查找器可能会返回多个边界矩形,分别表示同一段文本的不同部分。
为每个矩形分别创建注释,可以确保跨行的目标文本都能够被完整、准确地高亮。
可以。根据注释类型的不同,可以自定义颜色、图标、注释文本、位置、文本标记类型以及其他外观属性。
例如,本教程中的文本标记注释使用浅黄色高亮,而弹出式注释则使用红色评论图标。
会。当调用 SaveToFile() 时,这些注释会被写入输出 PDF 文件中。文件下载后,可以在支持标准 PDF 注释的 PDF 阅读器中查看这些注释。
如果您需要去除生成文档中的评估提示或解除功能限制,请联系我们获取有效期 30 天的临时许可证。
PDF 版式固定、便于分发,但其内容一经生成便不易在正文中修改。对于合同、报表、公告等文档,通常需要在不影响正文阅读的前提下标识机密等级、版权归属或“草稿”“样例”等使用状态。除了上一类常见的文字水印,使用公司 logo、印章或警示图片制作的图片水印也颇为常见:它以半透明的图片浮于内容之上,既能清晰传达品牌与状态信息,又不会破坏原文的可读性。
Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接完成 PDF 的加载、绘制与保存,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。添加图片水印通常有两种形态:一种是在页面指定位置(如页面中央)放置单张图片水印,可通过 PdfImage.FromFile 加载图片,并结合页面画布的透明度设置与 DrawImage 方法直接实现;另一种是让图片在整页重复平铺,可借助 PdfTilingBrush 平铺画刷完成。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
单张图片水印会在页面的指定位置(本示例为每页中央)放置一枚半透明的图片,适用于在文档醒目位置放置公司 logo 或警示标识。做法是:用 PdfImage.FromFile 从文件加载图片,随后在每页的画布上以 Save 保存状态、用 SetTransparency 设置透明度与混合方式,通过 DrawImage 将图片绘制到居中位置,最后用 Restore 恢复画布状态。
function App() {
const addSingleImageWatermark = 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/`);
// 将水印图片载入 VFS
const inputImageName = 'logo.png';
await window.spire.FetchFileToVFS(inputImageName, "", `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载 PDF 文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 从文件加载水印图片
let image = pdfModule.PdfImage.FromFile(inputImageName);
// 遍历文档中的所有页面
for (let i = 0; i < doc.Pages.Count; i++) {
// 获取指定页面
let page = doc.Pages.get_Item(i);
// 保存画布状态,并设置半透明(透明度 0.5)与正片叠底混合模式
page.Canvas.Save();
page.Canvas.SetTransparency({ alphaPen: 0.5, alphaBrush: 0.5, blendMode: pdfModule.PdfBlendMode.Multiply });
// 计算居中绘制位置:用页面尺寸减去图片尺寸后取一半
let position = new pdfModule.PointF(
(page.Canvas.Size.Width - image.Width) / 2,
(page.Canvas.Size.Height - image.Height) / 2
);
// 在页面中央绘制水印图片
page.Canvas.DrawImage({ image: image, point: position });
// 恢复画布之前的状态
page.Canvas.Restore();
}
// 定义输出文件名并保存文档
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>Add Single Image Watermark To PDF</h1>
<button onClick={addSingleImageWatermark}>
Generate
</button>
</div>
);
}
export default App;
添加单个图片水印后的 PDF 文档

当需要让水印铺满整页、形成浅淡的背景纹理时,可使用平铺图片水印。做法是:用 PdfImage.FromFile 加载图片,按页面尺寸通过 PdfTilingBrush 划分平铺单元,在画刷的图形上下文中以 SetTransparency 降低透明度并调用 DrawImage 在单元内绘制图片,最后以 DrawRectangle 用该画刷填充整页,即可让图片按单元行列重复、平铺覆盖整个页面。
function App() {
const addTiledImageWatermark = 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/`);
// 将水印图片载入 VFS
const inputImageName = 'logo.png';
await window.spire.FetchFileToVFS(inputImageName, "", `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载 PDF 文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 从文件加载水印图片
let image = pdfModule.PdfImage.FromFile(inputImageName);
// 遍历文档中的所有页面
for (let i = 0; i < doc.Pages.Count; i++) {
// 获取指定页面
let page = doc.Pages.get_Item(i);
// 创建平铺画刷:以页面宽度的三分之一、高度的五分之一作为平铺单元
let size = new pdfModule.SizeF({
width: page.Canvas.Size.Width / 3,
height: page.Canvas.Size.Height / 5
});
let brush = new pdfModule.PdfTilingBrush({ size: size });
// 设置水印透明度为 30%
brush.Graphics.SetTransparency({ alpha: 0.3 });
// 在平铺单元中央绘制水印图片
let point = new pdfModule.PointF(
(brush.Size.Width - image.Width) / 2,
(brush.Size.Height - image.Height) / 2
);
brush.Graphics.DrawImage({ image: image, point: point });
// 用平铺画刷填充整页矩形,使图片水印平铺覆盖整个页面
let rect = new pdfModule.RectangleF({
location: new pdfModule.PointF(0, 0),
size: page.Canvas.Size
});
page.Canvas.DrawRectangle({ brush: brush, rectangle: rect });
}
// 定义输出文件名并保存文档
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>Add Tiled Image Watermark To PDF</h1>
<button onClick={addTiledImageWatermark}>
Generate
</button>
</div>
);
}
export default App;
添加平铺图片水印后的 PDF 文档

原因:图片水印以图片形式叠加在正文之上,若不降低透明度会遮挡正文内容。
解决:绘制前用 Save 保存画布状态,再用 SetTransparency 设置透明度:其透明度由 alphaPen 与 alphaBrush 指定,取值范围为 0(完全透明)到 1(不透明);如需让水印与底色自然融合,还可通过 blendMode 指定混合模式,如 PdfBlendMode.Multiply(正片叠底)。绘制完成后用 Restore 恢复画布状态,避免影响后续绘制:
// 保存画布状态,设置半透明与正片叠底混合模式后绘制水印图片
page.Canvas.Save();
page.Canvas.SetTransparency({ alphaPen: 0.5, alphaBrush: 0.5, blendMode: pdfModule.PdfBlendMode.Multiply });
page.Canvas.DrawImage({ image: image, point: position });
page.Canvas.Restore();
原因:DrawImage 若不传入位置,会按默认坐标绘制图片,无法精确控制水印落点。
解决:DrawImage 支持传入 PointF 位置或 x、y 坐标。想让图片居中时,用页面尺寸减去图片尺寸后取一半即可得到居中坐标:
// 计算居中位置:页面尺寸减去图片尺寸后取一半
let point = new pdfModule.PointF(
(page.Canvas.Size.Width - image.Width) / 2,
(page.Canvas.Size.Height - image.Height) / 2
);
// 在指定位置绘制水印图片
page.Canvas.DrawImage({ image: image, point: point });
原因:平铺图片水印的疏密由平铺画刷 PdfTilingBrush 的 size(平铺单元尺寸)决定。
解决:平铺单元越小,图片重复越密集;单元越大则越稀疏。把 size 设为页面宽高的一定比例即可按行列平铺,例如以页面宽度的三分之一、高度的五分之一为一个单元,得到间隔适中的水印纹理。想更稀疏可调大分母(如 Width / 4),想更密集则调小分母:
// 创建平铺画刷:以页面宽度的三分之一、高度的五分之一作为平铺单元
let size = new pdfModule.SizeF({
width: page.Canvas.Size.Width / 3,
height: page.Canvas.Size.Height / 5
});
let brush = new pdfModule.PdfTilingBrush({ size: size });
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
在日常 Excel 文档处理中,文本框常用于为数据添加说明性文字、标注或提示信息——无论是为报表添加注释,还是从既有文档中提取标注内容,都离不开文本框的增删改查操作。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成这些操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍三个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
在工作表中插入文本框可以为数据提供补充说明,例如添加操作指引或注意事项。Spire.XLS for JavaScript 通过 Worksheet.TextBoxes.AddTextBox() 方法在指定位置插入文本框,随后可设置文本框的文本、对齐方式、字体和背景色,也可以用图片填充文本框。具体操作步骤如下:
Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。Workbook.Worksheets.get() 方法获取指定工作表。Worksheet.TextBoxes.AddTextBox() 方法插入第一个文本框,并设置其文本、水平垂直居中对齐、字体和背景色。Worksheet.TextBoxes.AddTextBox() 方法插入第二个文本框,并用图片填充。Workbook.SaveToFile() 方法保存文档到指定路径。下面是一个完整的代码示例,展示了在 React 中向工作表插入两个文本框——一个包含文字,一个填充图片:
function App() {
const addTextBox = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体、Excel 文件和图片载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'TextBox.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
await window.spire.FetchFileToVFS('logo.png', '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表
const sheet = workbook.Worksheets.get(0);
// 插入第一个文本框并设置位置和大小
const textBox = sheet.TextBoxes.AddTextBox(3, 2, 50, 196);
// 设置文本框文本
textBox.Text = '插入Excel文本框';
// 设置文本水平、垂直居中
textBox.HAlignment = xlsModule.CommentHAlignType.Center;
textBox.VAlignment = xlsModule.CommentVAlignType.Center;
// 设置文本框字体(加粗、白色、12号)
const font = workbook.CreateFont();
font.FontName = 'SimSun';
font.Size = 12;
font.IsBold = true;
font.Color = xlsModule.Color.get_White();
const rt = xlsModule.RichTextShape.Convert(textBox.RichText);
rt.SetFont(0, textBox.Text.length - 1, font);
// 设置文本框背景色为蓝灰色
textBox.Fill.FillType = xlsModule.ShapeFillType.SolidColor;
textBox.Fill.ForeKnownColor = xlsModule.ExcelColors.BlueGray;
// 插入第二个文本框并设置位置和大小
const textBox2 = sheet.TextBoxes.AddTextBox(6, 5, 90, 90);
// 加载图片并用图片填充文本框
textBox2.Fill.CustomPicture('logo.png');
textBox2.Fill.FillType = xlsModule.ShapeFillType.Picture;
// 设置第二个文本框的边框为 0
textBox2.Line.Weight = 0;
// 保存文档
const outputFileName = 'AddTextBox_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// 释放资源
workbook.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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>Add TextBox</h1>
<button onClick={addTextBox}>
Start
</button>
</div>
);
}
export default App;
添加文本框的结果

当需要汇总或复用既有文档中的标注信息时,可以通过遍历文本框并提取其中的文本内容和填充图片来实现。Spire.XLS for JavaScript 通过 Worksheet.TextBoxes.Count 获取文本框数量,配合 Worksheet.TextBoxes.get() 方法遍历每个文本框:读取其 Text 属性得到文本内容,通过 Fill.FillType 判断填充类型,并用 Fill.Picture 属性提取填充图片,最终将提取结果分别保存为 txt 文件和 png 图片文件。具体操作步骤如下:
Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。Workbook.Worksheets.get() 方法获取指定工作表。Worksheet.TextBoxes.Count 和 Worksheet.TextBoxes.get() 方法遍历 TextBoxes 集合中的每个文本框。Text 属性,收集其中的文本内容。Fill.Picture 属性获取填充图片,并保存为 png 文件。下面是一个完整的代码示例,展示了在 React 中提取文本框中的文本和图片:
function App() {
const extractTextAndImage = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'TextBox.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表
const sheet = workbook.Worksheets.get(0);
// 遍历工作表中的所有文本框,提取文本与图片
const textLines = [];
const pictureFiles = [];
for (let i = sheet.TextBoxes.Count - 1; i >= 0; i--) {
const shape = sheet.TextBoxes.get(i);
// 提取文本框中的文本
if (shape.Text) {
textLines.push(shape.Text);
}
// 提取文本框中的填充图片
if (shape.Fill.FillType === xlsModule.ShapeFillType.Picture) {
const picture = shape.Fill.Picture;
const imageFile = 'ExtractedImage' + i + '.png';
picture.Save(imageFile);
pictureFiles.push(imageFile);
}
}
// 将提取的文本保存为 txt 文件
const textFile = 'ExtractedText.txt';
window.dotnetRuntime.Module.FS.writeFile(textFile, textLines.join('\r\n'));
// 释放资源
workbook.Dispose();
// 下载提取的文本文件
const txtArray = window.dotnetRuntime.Module.FS.readFile(textFile);
const txtBlob = new Blob([txtArray], { type: 'text/plain' });
const txtUrl = URL.createObjectURL(txtBlob);
const txtAnchor = document.createElement('a');
txtAnchor.href = txtUrl;
txtAnchor.download = textFile;
txtAnchor.click();
URL.revokeObjectURL(txtUrl);
// 下载提取的图片文件
for (const imageFile of pictureFiles) {
const imageArray = window.dotnetRuntime.Module.FS.readFile(imageFile);
const imageBlob = new Blob([imageArray], { type: 'application/png' });
const imageUrl = URL.createObjectURL(imageBlob);
const imageAnchor = document.createElement('a');
imageAnchor.href = imageUrl;
imageAnchor.download = imageFile;
imageAnchor.click();
URL.revokeObjectURL(imageUrl);
}
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Extract Text And Image From TextBox</h1>
<button onClick={extractTextAndImage}>
Start
</button>
</div>
);
}
export default App;
提取文本框中的文本和图片结果

当文档中的标注信息不再需要时,可以将其删除以保持工作表整洁。Spire.XLS for JavaScript 通过 Worksheet.TextBoxes.RemoveAt() 方法按索引删除指定的文本框。具体操作步骤如下:
Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。Workbook.Worksheets.get() 方法获取指定工作表。Worksheet.TextBoxes.RemoveAt() 方法删除指定索引的文本框。Workbook.SaveToFile() 方法保存文档到指定路径。下面是一个完整的代码示例,展示了在 React 中删除工作表内的文本框:
function App() {
const removeTextBox = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'TextBox.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表
const sheet = workbook.Worksheets.get(0);
// 删除第一个文本框
sheet.TextBoxes.RemoveAt(0);
// 保存文档
const outputFileName = 'RemoveTextBox_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// 释放资源
workbook.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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>Remove TextBox</h1>
<button onClick={removeTextBox}>
Start
</button>
</div>
);
}
export default App;
删除文本框的结果

原因:AddTextBox() 方法中指定的行、列坐标越界,或未将字体文件载入 VFS,导致文本框中的文字无法正常渲染。
解决:确认行、列坐标在工作表范围内,并确保在使用前已通过 FetchFileToVFS() 加载所需字体,例如:
await window.spire.FetchFileToVFS(
'simsun.ttc', '/Library/Fonts/', '/'
);
原因:直接访问 Fill.Picture 属性仅适用于使用图片填充的文本框。若文本框未设置图片填充(例如使用纯色填充),访问该属性会抛出异常。
解决:在访问 Fill.Picture 前先判断文本框的 Fill.FillType 是否为 Picture,确认是图片填充后再获取图片并调用 Save() 方法保存。
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
在 Excel 文档处理中,公式和函数是最核心的能力之一——无论是求和、求平均,还是日期与三角运算,公式都能让数据处理自动化、高效化。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成公式与函数的插入和读取,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
Spire.XLS for JavaScript 提供的 Worksheet.Range.get() 方法返回的单元格 Range 对象的 Formula 属性可用于向 Excel 工作表中的指定单元格添加公式或函数。添加公式和函数到 Excel 工作表的主要操作步骤如下:
Workbook 的对象。Workbook.Worksheets.get() 方法获取指定的工作表。Range.Formula 属性将公式和函数添加到工作表的指定单元格中。Workbook.SaveToFile() 方法保存工作簿。下面是一个完整的代码示例,展示了在 React 中向 Excel 工作表插入数学运算、日期函数、三角函数、平均值函数和求和函数:
function App() {
const insertFormulasAndFunctions = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 创建 Workbook 对象
const workbook = new xlsModule.Workbook();
// 获取第一个工作表
const sheet = workbook.Worksheets.get(0);
// 声明两个变量:currentRow 和 currentFormula
let currentRow = 1;
let currentFormula = "";
// 设置列宽
sheet.SetColumnWidth(1, 32);
sheet.SetColumnWidth(2, 16);
// 在单元格中写入数据
sheet.Range.get({ row: currentRow, column: 1 }).Value = "测试数据";
sheet.Range.get({ row: currentRow, column: 2 }).NumberValue = 1;
sheet.Range.get({ row: currentRow, column: 3 }).NumberValue = 2;
sheet.Range.get({ row: currentRow, column: 4 }).NumberValue = 3;
sheet.Range.get({ row: currentRow, column: 5 }).NumberValue = 4;
sheet.Range.get({ row: currentRow, column: 6 }).NumberValue = 5;
currentRow += 2;
sheet.Range.get({ row: currentRow, column: 1 }).Value = "公式或函数";
sheet.Range.get({ row: currentRow, column: 2 }).Value = "结果";
// 设置单元格格式
let range = sheet.Range.get({ row: currentRow, column: 1, lastRow: currentRow, lastColumn: 2 });
range.Style.Font.FontName = "黑体";
range.Style.KnownColor = xlsModule.ExcelColors.LightGreen;
range.Style.FillPattern = xlsModule.ExcelPatternType.Solid;
range.Style.Borders.get(xlsModule.BordersLineType.EdgeBottom).LineStyle = xlsModule.LineStyleType.Medium;
range.Style.Font.IsBold = true;
// 数学运算
currentFormula = "=1/2+3*4";
currentRow += 1;
sheet.Range.get({ row: currentRow, column: 1 }).NumberFormat = "@";
sheet.Range.get({ row: currentRow, column: 1 }).Text = currentFormula;
sheet.Range.get({ row: currentRow, column: 2 }).Formula = currentFormula;
// 日期函数
currentFormula = "=TODAY()";
currentRow += 1;
sheet.Range.get({ row: currentRow, column: 1 }).NumberFormat = "@";
sheet.Range.get({ row: currentRow, column: 1 }).Text = currentFormula;
sheet.Range.get({ row: currentRow, column: 2 }).Formula = currentFormula;
sheet.Range.get({ row: currentRow, column: 2 }).Style.NumberFormat = "YYYY/MM/DD";
// 三角函数
currentFormula = "=SIN(PI()/6)";
currentRow += 1;
sheet.Range.get({ row: currentRow, column: 1 }).NumberFormat = "@";
sheet.Range.get({ row: currentRow, column: 1 }).Text = currentFormula;
sheet.Range.get({ row: currentRow, column: 2 }).Formula = currentFormula;
// 平均值函数
currentFormula = "=AVERAGE(B1:F1)";
currentRow += 1;
sheet.Range.get({ row: currentRow, column: 1 }).NumberFormat = "@";
sheet.Range.get({ row: currentRow, column: 1 }).Text = currentFormula;
sheet.Range.get({ row: currentRow, column: 2 }).Formula = currentFormula;
// 求和函数
currentFormula = "=SUM(B1:F1)";
currentRow += 1;
sheet.Range.get({ row: currentRow, column: 1 }).NumberFormat = "@";
sheet.Range.get({ row: currentRow, column: 1 }).Text = currentFormula;
sheet.Range.get({ row: currentRow, column: 2 }).Formula = currentFormula;
// 保存工作簿
const outputFileName = 'InsertFormulasAndFunctions_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// 释放资源
workbook.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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>Insert Formulas and Functions</h1>
<button onClick={insertFormulasAndFunctions}>
Start
</button>
</div>
);
}
export default App;
插入公式和函数到 Excel 工作表结果

要读取 Excel 工作表中的公式和函数,需要循环遍历工作表中的所有已使用单元格,之后利用单元格的 HasFormula 属性找到包含公式或函数的单元格,然后用 Range.Formula 属性获取这些单元格中的公式或函数。详细操作步骤如下:
Workbook 的对象。Workbook.LoadFromFile() 方法载入 Excel 工作簿。Workbook.Worksheets.get() 方法获取第一个工作表。HasFormula 属性检测一个单元格是否包含公式或函数。如果有,则使用 Range.RangeAddressLocal 属性和 Range.Formula 属性获取单元格名及其中的公式或函数,并输出获取的内容。下面是一个完整的代码示例,展示了在 React 中遍历工作表并读取其中的公式和函数:
function App() {
const readFormulasAndFunctions = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'FormulasAndFunctions.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 创建 Workbook 对象
const workbook = new xlsModule.Workbook();
// 载入 Excel 工作簿
workbook.LoadFromFile({ fileName: inputFileName });
// 获取该工作簿的第一个工作表
const sheet = workbook.Worksheets.get(0);
// 获取该工作表已使用的单元格范围
const usedRange = sheet.AllocatedRange;
// 创建输出工作簿
const output = new xlsModule.Workbook();
const outSheet = output.Worksheets.get(0);
let outRow = 1;
// 循环遍历已使用的单元格
for (const cell of usedRange.Cells) {
// 判断单元格是否有公式或函数
if (cell.HasFormula) {
// 获取单元格名
const cellname = cell.RangeAddressLocal;
// 获取单元格中的公式或函数
const formula = cell.Formula;
// 写入读取到的单元格名和公式
outSheet.Range.get({ row: outRow, column: 1 }).Value = "单元格" + cellname + "包含:" + formula;
outRow += 1;
}
}
// 设置输出列宽,确保文本完整显示
outSheet.SetColumnWidth(1, 45);
// 保存输出工作簿
const outputFileName = 'ReadFormulasAndFunctions_output.xlsx';
output.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// 释放资源
output.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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>Read Formulas and Functions</h1>
<button onClick={readFormulasAndFunctions}>
Start
</button>
</div>
);
}
export default App;
读取 Excel 工作表中的公式和函数结果

原因:目标单元格中的公式实际是以文本形式写入的(用 Text/Value 属性而非 Formula 属性),HasFormula 只对真正的公式返回 true。
解决:插入时确认使用 Range.Formula 属性;否则读取前需先把文本重新赋值为公式。
原因:Formula 属性返回的是单元格中的公式字符串,而 FormulaNumberValue 属性返回的是公式计算后的数值结果,两者返回的内容不同。
解决:需要公式字符串时使用 cell.Formula,需要公式计算后的数值结果时使用 cell.FormulaNumberValue,根据实际需求选择对应的属性。
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
Spire.PDF 12.9.1 现已发布。该版本修复了 XPS 转 PDF 时缩放图片丢失的问题,并优化了 PDF 转 SVG 的转换耗时。详情如下。
问题修复:
调整:
在日常 Excel 表格处理中,对行或列进行分组可以将明细数据折叠起来,只显示汇总信息,从而让大型表格更加简洁、便于阅读。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成分组与取消分组操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
对行或列进行分组后,可以将分组内的明细数据折叠起来,只保留需要的汇总行或汇总列,让工作表更清爽。Spire.XLS for JavaScript 通过 GroupByRows() 方法组合行、通过 GroupByColumns() 方法组合列。具体操作步骤如下:
Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。Workbook.Worksheets.get() 方法获取指定工作表。Worksheet.GroupByRows() 方法组合行。Worksheet.GroupByColumns() 方法组合列。Workbook.SaveToFile() 方法保存文档到指定路径。下面是一个完整的代码示例,展示了在 React 中设置行或列分组:
function App() {
const groupRowsAndColumns = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'GroupRowsAndColumns.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表
const sheet = workbook.Worksheets.get(0);
// 设置行数据分组
sheet.GroupByRows(6, 10, false);
sheet.GroupByRows(14, 16, false);
// 设置列数据分组
sheet.GroupByColumns(2, 7, false);
// 保存文档
const outputFileName = 'GroupRowsAndColumns_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// 释放资源
workbook.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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>Group Rows And Columns</h1>
<button onClick={groupRowsAndColumns}>
Start
</button>
</div>
);
}
export default App;
设置行或列分组后,被分组区域左侧或上方会出现分组标记,点击标记即可折叠或展开明细数据。

当不再需要分组结构时,可以取消已有的分组,使所有行和列恢复为普通显示。Spire.XLS for JavaScript 通过 UngroupByRows() 方法取消行组合、通过 UngroupByColumns() 方法取消列组合。具体操作步骤如下:
Workbook 对象,并使用 LoadFromFile() 方法加载包含分组的 Excel 文档。Workbook.Worksheets.get() 方法获取指定工作表。Worksheet.UngroupByRows() 方法取消行组合。Worksheet.UngroupByColumns() 方法取消列组合。Workbook.SaveToFile() 方法保存文档到指定路径。下面是一个完整的代码示例,展示了在 React 中取消设置行或列分组:
function App() {
const ungroupRowsAndColumns = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'GroupRowsAndColumns.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表
const sheet = workbook.Worksheets.get(0);
// 取消行数据组合
sheet.UngroupByRows(6, 10);
sheet.UngroupByRows(14, 16);
// 取消列数据组合
sheet.UngroupByColumns(2, 7);
// 保存文档
const outputFileName = 'UngroupRowsAndColumns_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// 释放资源
workbook.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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>Ungroup Rows And Columns</h1>
<button onClick={ungroupRowsAndColumns}>
Start
</button>
</div>
);
}
export default App;
取消分组后,行或列的分组标记消失,数据恢复为未分组的普通显示。

原因:GroupByRows() 和 GroupByColumns() 方法的第三个参数 isCollapsed 设置为 false,分组默认以展开状态显示。
解决:将该参数设置为 true,保存后分组将以折叠状态显示:
sheet.GroupByRows(6, 10, true);
原因:UngroupByRows() 和 UngroupByColumns() 方法仅取消指定范围内行或列的分组。如果这些行或列同时属于更高层级的分组,更高层级的分组符号仍然会保留。
解决:确认取消分组时传入的行列范围与设置分组时保持一致;若存在嵌套分组,可多次调用取消方法逐层取消:
sheet.UngroupByRows(6, 10);
sheet.UngroupByRows(14, 16);
sheet.UngroupByColumns(2, 7);
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
PDF 版式固定、便于分发,但其内容一经生成便不易在正文中修改。对于合同、报表、公告等文档,通常需要在不影响正文阅读的前提下标识机密等级、版权归属或“草稿”“样例”等使用状态。文本水印是解决这一问题的常见手段:它以半透明的文字浮于内容之上,既能清楚传达信息,又不破坏原文的可读性。
Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接完成 PDF 的加载、绘制与保存,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。添加文本水印通常有两种形态:一种是在每页中央沿对角线放置一行水印文字,可通过页面画布的透明度与坐标系变换直接实现;另一种是让文字在整页重复平铺,可借助 PdfTilingBrush 平铺画刷完成。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
单行文本水印会在每页中央生成一行斜向文字,适用于密级或版权标识。做法是:用中文字体的 PdfTrueTypeFont 搭配 MeasureString 测量文字尺寸并计算居中偏移,再逐页通过 SetTransparency、TranslateTransform、RotateTransform 设置透明度、旋转坐标系,最后用 DrawString 绘制水印文字。
function App() {
const addSingleLineTextWatermark = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 将支持中文的 TrueType 字体载入 VFS
await window.spire.FetchFileToVFS('ARIAL UNICODE MS.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);
// 创建 TrueType 字体:粗体、40 号
let trueTypeFont = new pdfModule.PdfTrueTypeFont({
fontFamily: 'Arial Unicode MS',
size: 40,
style: pdfModule.PdfFontStyle.Bold,
unicode: true
});
// 创建水印笔刷并指定水印文本
let brush = pdfModule.PdfBrushes.get_DarkGray();
const text = '机密文档,不可外泄';
// 测量水印文本的尺寸
let textSize = trueTypeFont.MeasureString({ text: text });
// 计算两个偏移量,用于确定坐标系平移量,使水印沿对角线居中
let offset1 = (textSize.Width * Math.sqrt(2)) / 4;
let offset2 = (textSize.Height * Math.sqrt(2)) / 4;
let format = new pdfModule.PdfStringFormat({ alignment: pdfModule.PdfTextAlignment.Left });
// 遍历文档中的所有页面
for (let i = 0; i < doc.Pages.Count; i++) {
// 获取指定页面
let page = doc.Pages.get_Item(i);
// 设置页面透明度
page.Canvas.SetTransparency(0.8);
// 将坐标系平移到页面中央并补偿文本尺寸带来的偏移
page.Canvas.TranslateTransform(
page.Canvas.ClientSize.Width / 2 - offset1 - offset2,
page.Canvas.ClientSize.Height / 2 + offset1 - offset2
);
// 逆时针旋转坐标系 45 度
page.Canvas.RotateTransform({ angle: -45 });
// 在页面上绘制水印文本
page.Canvas.DrawString({ s: text, font: trueTypeFont, brush: brush, x: 0, y: 0, 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>Add Single-line Text Watermark To PDF</h1>
<button onClick={addSingleLineTextWatermark}>
Generate
</button>
</div>
);
}
export default App;
添加单行文本水印后的 PDF 文档

当需要让水印铺满整页时,可使用多行文本水印。做法是:用 PdfTilingBrush 按页面尺寸划分平铺单元,在单元内以 SetTransparency、RotateTransform 调整透明度与角度并用 DrawString 绘制文字,最后以 DrawRectangle 将画刷填充整页即可。
function App() {
const addMultilineTextWatermark = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 将支持中文的 TrueType 字体载入 VFS
await window.spire.FetchFileToVFS('ARIAL UNICODE MS.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);
// 创建平铺画刷:以页面的一半宽度、三分之一高度作为平铺单元
let size = new pdfModule.SizeF({
width: page.Canvas.ClientSize.Width / 2,
height: page.Canvas.ClientSize.Height / 3
});
let brush = new pdfModule.PdfTilingBrush({ size: size });
// 设置水印透明度为 30%
brush.Graphics.SetTransparency(0.3);
// 保存画刷当前状态,并将坐标系平移、旋转,使水印呈斜向排列
brush.Graphics.Save();
brush.Graphics.TranslateTransform(brush.Size.Width / 2, brush.Size.Height / 2);
brush.Graphics.RotateTransform({ angle: -45 });
// 绘制平铺水印
let format = new pdfModule.PdfStringFormat({ alignment: pdfModule.PdfTextAlignment.Center });
// 创建 TrueType 字体:粗体、25 号
let trueTypeFont = new pdfModule.PdfTrueTypeFont({
fontFamily: 'Arial Unicode MS',
size: 25,
style: pdfModule.PdfFontStyle.Bold,
unicode: true
});
// 绘制水印
brush.Graphics.DrawString({
s: "机密文档",
font: trueTypeFont,
brush: pdfModule.PdfBrushes.get_DarkRed(),
x: 0,
y: -18,
format: format
});
// 恢复画刷之前的状态,并还原为不透明
brush.Graphics.Restore();
brush.Graphics.SetTransparency({ alpha: 1 });
// 用平铺画刷填充整页矩形,使水印文字平铺覆盖整个页面
let rect = new pdfModule.RectangleF({
location: new pdfModule.PointF(0, 0),
size: page.Canvas.ClientSize
});
page.Canvas.DrawRectangle({ brush: brush, rectangle: rect });
// 定义输出文件名并保存文档
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>Add Multiline Text Watermark To PDF</h1>
<button onClick={addMultilineTextWatermark}>
Generate
</button>
</div>
);
}
export default App;
添加多行文本水印后的 PDF 文档

原因:DrawString 需要显式指定绘制文本所用的字体与画刷。
解决:英文字符可使用 PdfFont 配合内置的 PdfFontFamily(如 Helvetica),通过 PdfBrushes 的静态颜色属性指定水印颜色;需要中文等多语言文字或加粗等样式时,应先将对应的 TrueType 字体载入 VFS,再使用 PdfTrueTypeFont:
// 将中文字体载入 VFS
await window.spire.FetchFileToVFS('SIMSUN.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 创建中文字体与水印画刷
let font = new pdfModule.PdfTrueTypeFont({
fontFamily: '宋体',
size: 24,
style: pdfModule.PdfFontStyle.Regular,
unicode: true
});
let brush = new pdfModule.PdfSolidBrush({
pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_DarkRed() })
});
// 在页面上绘制中文水印(示例中绘制于页面画布)
page.Canvas.DrawString({ s: '机密文件', font: font, brush: brush, x: 0, y: 0, format: format });
原因:单行文本水印示例中已通过页面循环将水印应用到每一页,而多行文本水印示例仅通过 doc.Pages.get_Item(0) 作用于第一页。
解决:需要让多行水印也覆盖整篇文档时,将平铺画刷的创建与页面填充放入页面循环即可:
for (let i = 0; i < doc.Pages.Count; i++) {
let page = doc.Pages.get_Item(i);
// 创建平铺画刷并设置透明度、旋转与文字
let size = new pdfModule.SizeF({
width: page.Canvas.ClientSize.Width / 2,
height: page.Canvas.ClientSize.Height / 3
});
let brush = new pdfModule.PdfTilingBrush({ size: size });
// …… 设置透明度、旋转并绘制水印文字 ……
// 用平铺画刷填充当前页面
page.Canvas.DrawRectangle({
brush: brush,
rectangle: new pdfModule.RectangleF({ location: new pdfModule.PointF(0, 0), size: page.Canvas.ClientSize })
});
}
原因:透明度过高或过低会影响水印的观感,旋转角度决定水印文字的方向。
解决:通过 SetTransparency 设置透明度,取值范围为 0(完全透明)到 1(不透明);通过 RotateTransform 控制坐标系旋转角度,负值表示逆时针旋转。单行示例将透明度设为 0.8、旋转 -45 度,多行平铺示例在平铺画刷的图形上下文中做了相同设置:
// 单行水印:设置页面透明度并旋转页面画布
page.Canvas.SetTransparency(0.8);
page.Canvas.RotateTransform({ angle: -45 });
// 多行平铺水印:在平铺画刷的图形上下文中设置透明度并旋转
brush.Graphics.SetTransparency(0.3);
brush.Graphics.RotateTransform({ angle: -45 });
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
批注(Comments)是 Excel 中用于对单元格内容进行补充说明的重要工具,常用于数据审核、协作备注等场景。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成批注的添加、读取、编辑与删除操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍几个常用功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
添加批注同时可以携带作者信息,便于识别批注的来源。
function App() {
const addCommentWithAuthor = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'CommentsSample.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表
const sheet = workbook.Worksheets.get(0);
// 获取要添加批注的单元格
const range = sheet.Range.get('C1');
// 设置作者和批注内容
const author = 'E-iceblue';
const text = '这是一个示例,展示如何添加带可编辑作者属性的批注。';
// 向单元格添加批注并设置属性
const comment = range.AddComment();
comment.Width = 200;
comment.IsVisible = true;
comment.Text = author + ':\n' + text;
// 为批注中的作者名称设置字体样式
const font = workbook.CreateFont();
font.FontName = 'Arial';
font.KnownColor = xlsModule.ExcelColors.Black;
font.IsBold = true;
comment.RichText.SetFont(0, author.length, font);
// 保存工作簿
const outputFileName = 'AddCommentWithAuthor_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// 释放资源
workbook.Dispose();
// 从 VFS 读取生成的文件并触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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>Add Comment With Author</h1>
<button onClick={addCommentWithAuthor}>Start</button>
</div>
);
}
export default App;
运行后,单元格 C1 上会出现一个包含作者名称和批注文本的批注。

通过 CellRange.Comment 属性可以读取单元格上的批注。
function App() {
const readComment = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将 Excel 文件载入 VFS
const inputFileName = 'CommentsSample.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表
const sheet = workbook.Worksheets.get(0);
// 获取批注文本
const builder = [];
builder.push(sheet.Range.get('A1').Comment.Text + '\n\t');
builder.push(sheet.Range.get('A2').Comment.Text);
// 将批注内容保存为 txt 文件
const outputFileName = 'ReadComment_output.txt';
window.dotnetRuntime.Module.FS.writeFile(outputFileName, builder.join('\n'));
workbook.Dispose();
// 从 VFS 读取生成的文件并触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/plain' });
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>Read Comment</h1>
<button onClick={readComment}>Start</button>
</div>
);
}
export default App;
读取A1,A2单元格的批注内容

通过 Comments.get(0) 按索引获取批注,然后修改其文本内容。
function App() {
const editComment = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'CommentsSample.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表
const sheet = workbook.Worksheets.get(0);
// 获取第一个批注
const comment = sheet.Comments.get(0);
// 编辑批注内容
comment.Text = '该批注已由 Spire.XLS 编辑。';
// 保存工作簿
const outputFileName = 'EditExcelComment_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// 释放资源
workbook.Dispose();
// 从 VFS 读取生成的文件并触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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>Edit Excel Comment</h1>
<button onClick={editComment}>Start</button>
</div>
);
}
export default App;
编辑A1的批注内容

通过 Comments.Clear 方法可以删除工作表中的所有批注。
function App() {
const removeComment = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'CommentsSample.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表的全部批注
const comments = workbook.Worksheets.get(0).Comments;
// 清除批注,也可以使用 comments.RemoveAt(0) 按索引进行删除
comments.Clear();
// 保存工作簿
const outputFileName = 'RemoveComment_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// 释放资源
workbook.Dispose();
// 从 VFS 读取生成的文件并触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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>Remove Comment</h1>
<button onClick={removeComment}>Start</button>
</div>
);
}
export default App;
删除批注后

原因:添加批注后未设置 IsVisible 属性为 true,批注默认处于隐藏状态。
解决:添加批注后设置 comment.IsVisible = true,即可使批注在工作表中显示。
原因:目标单元格上不存在批注,或者使用了错误的单元格引用。
解决:确认目标单元格已添加批注,并通过 sheet.Range.get('A1').Comment 等方式访问批注内容。
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
我们很高兴地宣布 Spire.Doc 14.9.1 版本正式发布。该版本新增强大的 Word 文档主题管理功能,同时优化图表样式与文档导出的字体嵌入相关配置。开发者现在可通过代码完整自定义、复制和编辑 Word 文档主题,统一多份文件的视觉风格。
此外,两项影响文档转换与页面删除的关键漏洞已完成修复,有效提升整体文档处理稳定性。详细更新内容如下。
新功能:
// Create a new document and apply the theme
Document doc = new Document();
// Configure a custom theme
Theme theme = doc.Theme;
theme.MajorFonts.Latin = "Courier New"; // Set the heading font to Courier New
theme.MinorFonts.Latin = "Agency FB"; // Set the body font to Agency FB
ThemeColors colors = theme.Colors;
colors.Dark1 = Color.MidnightBlue;
colors.Light1 = Color.PaleGreen;
colors.Dark2 = Color.Indigo;
colors.Light2 = Color.Khaki;
colors.Accent1 = Color.OrangeRed;
colors.Accent2 = Color.LightSalmon;
colors.Accent3 = Color.Yellow;
colors.Accent4 = Color.Gold;
colors.Accent5 = Color.BlueViolet;
colors.Accent6 = Color.DarkViolet;
colors.Hyperlink = Color.Black;
colors.FollowedHyperlink = Color.Gray;
Section section = doc.AddSection();
// Create a heading and apply MajorFonts and the Accent1 color
Paragraph heading = section.AddParagraph();
heading.AppendText("Document Theme Test");
heading.ApplyStyle(BuiltinStyle.Heading1);
// Link the heading style to MajorFonts
ParagraphStyle heading1Style = (ParagraphStyle)doc.Styles["Heading 1"];
heading1Style.CharacterFormat.ThemeFont = ThemeFont.Major; // Use the Major theme font (heading font)
heading1Style.CharacterFormat.ThemeColor = ThemeColor.Accent1; // Use the Accent1 theme color
// Create a body paragraph and apply MinorFonts
Paragraph body = section.AddParagraph();
body.AppendText("This document uses a custom theme created programmatically.");
// Link the Normal style to MinorFonts
ParagraphStyle normalStyle = (ParagraphStyle)doc.Styles["Normal"];
normalStyle.CharacterFormat.ThemeFont = ThemeFont.Minor; // Use the Minor theme font (body font)
normalStyle.CharacterFormat.ThemeColor = ThemeColor.Text1; // Use the default text color
// Add a test paragraph to verify the theme color
Paragraph colorTest = section.AddParagraph();
TextRange textRange = colorTest.AppendText("Accent1 Color Test");
textRange.CharacterFormat.ThemeColor = ThemeColor.Accent1; // Use the Accent1 theme color directly
doc.SaveToFile(outputFile, FileFormat.Docx);
doc.Close();
// Load the source document and get its theme
Document sourceDoc = new Document();
sourceDoc.LoadFromFile("Theme.docx");
Theme sourceTheme = sourceDoc.Theme;
// Create a destination document
Document newDoc = new Document();
// Copy the source document's theme (theme fonts and colors) to the destination document
sourceDoc.CloneThemesTo(newDoc);
newDoc.SaveToFile("CopyTheme.docx", FileFormat.Docx);
Document doc = new Document();
// Set the font faces of the document theme
doc.Theme.MinorFonts.Latin = "Algerian";
doc.Theme.MinorFonts.EastAsian = "Aharoni";
doc.Theme.MinorFonts.ComplexScript = "Andalus";
// Set the theme font and theme color of the style's character format
CharacterFormat font = ((ParagraphStyle)doc.Styles["Normal"]).CharacterFormat;
font.ThemeFont = ThemeFont.Minor;
font.ThemeColor = ThemeColor.Accent2;
// Get the theme font and theme color
ThemeFont themeFont = font.ThemeFont;
string fontName = font.FontName;
Color textColor = font.TextColor;
doc.SaveToFile("ThemeAttributes.docx", FileFormat.Docx);
doc.setEmbedFontsInFile(true);
doc.setSaveSubsetFonts(true);
// Create a document and add a column chart
Document doc = new Document();
Section section = doc.AddSection();
ShapeObject shape = section.AddParagraph().AppendChart(ChartType.Column, 500, 300);
Chart chart = shape.Chart;
chart.Series.Clear();
chart.Series.Add("Sales", new string[] { "Q1", "Q2", "Q3", "Q4" }, new double[] { 120, 90, 160, 200 });
// Chart area - solid fill
chart.Format.Fill.FillType = FillType.Solid;
chart.Format.Fill.Color = Color.OrangeRed;
chart.Format.Fill.Transparency = 0.3;
// Data points of the first series - linear gradient fill
Fill seriesFill = chart.Series[0].DataPoints.Format.Fill;
seriesFill.FillType = FillType.Shade;
seriesFill.GradientType = GradientFillType.Linear;
seriesFill.SetGradientDirection(GradientFillDirection.LinearUp);
seriesFill.GradientStops.Add(new GradientStop(0, Color.White));
seriesFill.GradientStops.Add(new GradientStop(1, Color.OrangeRed));
// Data labels - enable and use a solid fill
chart.Series[0].HasDataLabels = true;
chart.Series[0].DataLabels.Format.Fill.FillType = FillType.Solid;
chart.Series[0].DataLabels.Format.Fill.Color = Color.LightYellow;
doc.SaveToFile("ChartFill.docx", FileFormat.Docx);
// Create a document and add a line chart
Document doc = new Document();
Section section = doc.AddSection();
ShapeObject shape = section.AddParagraph().AppendChart(ChartType.Line, 500, 300);
Chart chart = shape.Chart;
chart.Series.Clear();
chart.Series.Add("Trend", new string[] { "Cat1", "Cat2", "Cat3", "Cat4" }, new double[] { 4.3, 2.4, 3.1, 5.2 });
// Chart area - border weight, dash style and solid color
chart.Format.Stroke.Weight = 2;
chart.Format.Stroke.DashStyle = DashStyle.Dash;
chart.Format.Stroke.Fill.FillType = FillType.Solid;
chart.Format.Stroke.Fill.Color = Color.Blue;
// Data series border - set through its data points
chart.Series[0].DataPoints.Format.Stroke.Fill.FillType = FillType.Solid;
chart.Series[0].DataPoints.Format.Stroke.Fill.Color = Color.Red;
chart.Series[0].DataPoints.Format.Stroke.Weight = 1.5;
// Start and end arrows on the axes
chart.AxisX.Format.Stroke.StartArrowType = ArrowType.Arrow;
chart.AxisX.Format.Stroke.EndArrowType = ArrowType.Arrow;
chart.AxisY.Format.Stroke.StartArrowType = ArrowType.Arrow;
chart.AxisY.Format.Stroke.EndArrowType = ArrowType.Arrow;
doc.SaveToFile("ChartStroke.docx", FileFormat.Docx);
问题修复:
在制作报表时,为单元格设置背景颜色可以突出标题和重点数据,为工作表设置背景图片则能让整张报表更有辨识度。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成这两类设置,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
为单元格设置背景颜色可以突出显示表头、重要数据或特定区域。Spire.XLS for JavaScript 通过 CellRange.Style.Color 属性为单元格或单元格区域设置背景颜色,支持丰富的内置颜色。具体操作步骤如下:
Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。Workbook.Worksheets.get() 方法获取指定工作表。CellRange.Style.Color 属性为指定单元格区域设置背景颜色。Workbook.SaveToFile() 方法保存文档到指定路径。下面是一个完整的代码示例,展示了在 React 中为单元格区域设置背景颜色:
function App() {
const setBackgroundColor = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'SetBackgroundColor.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表
const sheet = workbook.Worksheets.get(0);
// 将表头行设置为黄色背景
sheet.Range.get("A1:E1").Style.Color = xlsModule.Color.get_Yellow();
// 将前两行数据设置为浅蓝色背景
sheet.Range.get("A2:E2").Style.Color = xlsModule.Color.get_LightSkyBlue();
sheet.Range.get("A3:E3").Style.Color = xlsModule.Color.get_LightSkyBlue();
// 保存文档
const outputFileName = 'SetBackgroundColor_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// 释放资源
workbook.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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>Set Cell Background Color</h1>
<button onClick={setBackgroundColor}>
Start
</button>
</div>
);
}
export default App;
设置背景颜色后,表头行显示为黄色背景,前两行数据显示为浅蓝色背景,可以直观地区分不同区域的单元格。

除了为单元格设置背景颜色,还可以为整个工作表设置背景图片,让报表更具辨识度。Spire.XLS for JavaScript 通过 Worksheet.PageSetup.BackgroundImage 属性将图片设置为工作表背景。具体操作步骤如下:
Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。Workbook.Worksheets.get() 方法获取指定工作表。Stream 对象读取要作为背景的图片文件。Worksheet.PageSetup.BackgroundImage 属性将图片设置为工作表背景。Workbook.SaveToFile() 方法保存文档到指定路径。下面是一个完整的代码示例,展示了在 React 中为工作表设置背景图片:
function App() {
const setBackgroundImage = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体、图片和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const backgroundImageName = 'Background.png';
await window.spire.FetchFileToVFS(backgroundImageName, '', `${process.env.PUBLIC_URL}data/`);
const inputFileName = 'SetBackgroundColor.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表
const sheet = workbook.Worksheets.get(0);
// 以流的形式打开图片
const bm = new xlsModule.Stream(backgroundImageName);
// 将图片设置为工作表背景
sheet.PageSetup.BackgroundImage = bm;
// 保存文档
const outputFileName = 'SetBackgroundImage_output.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
// 释放资源
workbook.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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>Set Worksheet Background Image</h1>
<button onClick={setBackgroundImage}>
Start
</button>
</div>
);
}
export default App;
设置背景图片后,图片会作为工作表背景铺满在工作表后方,单元格内容和数据仍清晰显示在图片之上。

原因:Style.Color 属性设置的是单元格的背景(填充)颜色,而不是字体颜色。如果设置的颜色被其他样式覆盖,或者没有正确设置填充模式,颜色可能无法正常显示。
解决:直接为单元格区域设置颜色即可,例如 sheet.Range.get("A1:E1").Style.Color = xlsModule.Color.get_Yellow();。如果希望使用带图案的填充,可以结合 Style.Interior.FillPattern 和 Style.Interior.Gradient 一起使用。
原因:工作表背景图片始终显示在单元格内容的后方,仅作为背景装饰,不会被数据遮挡或遮挡数据。
解决:这是正常的显示层级关系。如果需要让图片显示在数据之上,请使用 Worksheet.Pictures.Add() 方法在工作表中插入浮动图片,而不是设置工作表背景。
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。