在 PDF 文档中添加图形是很多业务场景中的常见需求:用线条和方框标注重点区域、给表格外的内容加一个醒目的边框、用填充色块区分不同板块,或者在图纸、报表上叠加饼形、椭圆等示意图形。如果每次都依赖设计软件手工绘制,不仅效率低下,也难以批量处理。借助 Spire.PDF for JavaScript 的绘图能力,可以在浏览器端直接向 PDF 页面写入各种形状,把标注和示意工作交给程序自动完成。
Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接完成 PDF 的加载、编辑与保存,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。在 PDF 上绘制形状的核心对象是页面的绘图画布 PdfPage.Canvas:它提供了 DrawLine、DrawPie、DrawRectangle、DrawEllipse 等方法。
本文介绍四个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
在 PDF 页面中绘制线条
绘制线条时可以指定颜色与粗细,也能选择实线或虚线——虚实由 DashStyle 和 DashPattern 控制。
function App() {
const drawLines = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 创建 PDF 文档并添加一个空白页面
let doc = new pdfModule.PdfDocument();
let page = doc.Pages.Add();
// 保存当前图形状态
let state = page.Canvas.Save();
// 创建红色画笔,用于绘制线条
let pen = new pdfModule.PdfPen({
pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Red() }),
width: 2,
});
// 线的起始点坐标与长度
let x = 30.0;
let y = 50.0;
let width = 300.0;
// 绘制一条实线
page.Canvas.DrawLine({ pen: pen, x1: x, y1: y, x2: x + width, y2: y });
// 设置虚线样式与虚线间隔
pen.DashStyle = pdfModule.PdfDashStyle.Dash;
pen.DashPattern = [3.0, 2.0];
// 绘制一条虚线
page.Canvas.DrawLine({ pen: pen, x1: x, y1: y + 60.0, x2: x + width, y2: y + 60.0 });
// 恢复图形状态
page.Canvas.Restore({ state: state });
// 定义输出文件名并保存文档
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>Draw Lines in PDF</h1>
<button onClick={drawLines}>
Draw
</button>
</div>
);
}
export default App;
在 PDF 页面上绘制一条实线和一条虚线后的效果

在 PDF 页面中绘制饼形
饼形用于表达占比,外接矩形决定位置和大小,startAngle 与 sweepAngle 决定扇形开口的角度。
function App() {
const drawPie = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 创建 PDF 文档并添加一个空白页面
let doc = new pdfModule.PdfDocument();
let page = doc.Pages.Add();
// 保存当前图形状态
let state = page.Canvas.Save();
// 创建深红色画笔
let pen = new pdfModule.PdfPen({
pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_DarkRed() }),
width: 2,
});
// 绘制第一个饼形
page.Canvas.DrawPie({ pen: pen, x: 10.0, y: 30.0, width: 130.0, height: 130.0, startAngle: 360.0, sweepAngle: 300.0 });
// 绘制第二个饼形
page.Canvas.DrawPie({ pen: pen, x: 160.0, y: 30.0, width: 130.0, height: 130.0, startAngle: 360.0, sweepAngle: 330.0 });
// 绘制第三个饼形
page.Canvas.DrawPie({ pen: pen, x: 320.0, y: 30.0, width: 130.0, height: 130.0, startAngle: 360.0, sweepAngle: 360.0 });
// 恢复图形状态
page.Canvas.Restore({ state: state });
// 定义输出文件名并保存文档
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>Draw a Pie in PDF</h1>
<button onClick={drawPie}>
Draw
</button>
</div>
);
}
export default App;
在 PDF 页面上绘制三个饼形后的效果

在 PDF 页面中绘制矩形
矩形既能只描边,也能填充。填充除纯色(PdfSolidBrush)外,还支持线性渐变(PdfLinearGradientBrush)与径向渐变(PdfRadialGradientBrush)。
function App() {
const drawRectangle = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 创建 PDF 文档并添加一个空白页面
let doc = new pdfModule.PdfDocument();
let page = doc.Pages.Add();
// 保存当前图形状态
let state = page.Canvas.Save();
// 创建黑色画笔
let pen = new pdfModule.PdfPen({
pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Black() }),
width: 1,
});
// 用画笔绘制一个矩形的轮廓
page.Canvas.DrawRectangle({
pen: pen,
rectangle: new pdfModule.RectangleF({
location: new pdfModule.PointF(20.0, 30.0),
size: new pdfModule.SizeF({ width: 150.0, height: 120.0 }),
}),
});
// 创建一个线性渐变刷子对象
let linearGradientBrush = new pdfModule.PdfLinearGradientBrush({
point1: new pdfModule.PointF(200.0, 30.0),
point2: new pdfModule.PointF(350.0, 150.0),
color1: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Green() }),
color2: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Red() }),
});
// 用线性渐变刷子绘制一个填充式矩形
page.Canvas.DrawRectangle({
brush: linearGradientBrush,
rectangle: new pdfModule.RectangleF({
location: new pdfModule.PointF(200.0, 30.0),
size: new pdfModule.SizeF({ width: 150.0, height: 120.0 }),
}),
});
// 创建一个径向渐变刷子对象
let radialGradientBrush = new pdfModule.PdfRadialGradientBrush({
centreStart: new pdfModule.PointF(380.0, 30.0),
radiusStart: 150.0,
centreEnd: new pdfModule.PointF(530.0, 150.0),
radiusEnd: 150.0,
colorStart: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Orange() }),
colorEnd: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Blue() }),
});
// 用径向渐变刷子绘制一个填充式矩形
page.Canvas.DrawRectangle({
brush: radialGradientBrush,
rectangle: new pdfModule.RectangleF({
location: new pdfModule.PointF(380.0, 30.0),
size: new pdfModule.SizeF({ width: 150.0, height: 120.0 }),
}),
});
// 恢复图形状态
page.Canvas.Restore({ state: state });
// 定义输出文件名并保存文档
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>Draw a Rectangle in PDF</h1>
<button onClick={drawRectangle}>
Draw
</button>
</div>
);
}
export default App;
在 PDF 页面上绘制矩形轮廓与渐变填充矩形后的效果

在 PDF 页面中绘制椭圆形
椭圆同样支持描边与填充,轮廓用 PdfPen,填充用 PdfSolidBrush,也可以直接取用 PdfPens 预设的画笔。
function App() {
const drawEllipse = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 创建 PDF 文档并添加一个空白页面
let doc = new pdfModule.PdfDocument();
let page = doc.Pages.Add();
// 保存当前图形状态
let state = page.Canvas.Save();
// 创建 CadetBlue 画笔
let pen = pdfModule.PdfPens.get_CadetBlue();
// 绘制椭圆形状轮廓
page.Canvas.DrawEllipse({ pen: pen, x: 50.0, y: 30.0, width: 120.0, height: 100.0 });
// 创建填充刷子对象
let brush = new pdfModule.PdfSolidBrush({
pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_CadetBlue() }),
});
// 绘制填充的椭圆形状
page.Canvas.DrawEllipse({ brush: brush, x: 180.0, y: 30.0, width: 120.0, height: 100.0 });
// 恢复图形状态
page.Canvas.Restore({ state: state });
// 定义输出文件名并保存文档
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>Draw an Ellipse in PDF</h1>
<button onClick={drawEllipse}>
Draw
</button>
</div>
);
}
export default App;
在 PDF 页面上绘制椭圆轮廓与填充椭圆后的效果

常见问题
绘制的形状为什么出现在页面边缘或超出可视范围
原因:DrawLine、DrawPie、DrawRectangle、DrawEllipse 使用的坐标原点位于页面左下角,x 轴向右、y 轴向上,单位为点(point)。如果直接照搬屏幕坐标(原点在左上角),绘制出的形状位置就会与预期相反或超出页面。
解决:按页面左下角为原点来换算坐标。可以先读取页面尺寸再做布局,例如使用 PdfPage.Size 得到页面宽高,再据此计算形状的位置:
// 获取页面尺寸,按左下角为原点计算坐标
let size = page.Size;
let x = size.Width / 4;
let y = size.Height / 3;
page.Canvas.DrawRectangle({ pen: pen, x: x, y: y, width: 200, height: 120 });
绘制后页面原有内容发生变化或错位
原因:绘图时会修改画布的当前变换与图形状态。若在绘制前用 ScaleTransform、TranslateTransform 等改变了坐标系,或在绘制后没有还原状态,就会影响后续内容。
解决:成对使用 Canvas.Save 与 Canvas.Restore,把绘制操作包裹在两者之间,确保绘制完成后画布状态还原到绘制前的水平:
// 绘制前保存状态
let state = page.Canvas.Save();
// ……执行绘制……
// 绘制后恢复状态
page.Canvas.Restore({ state: state });
为什么保存的 PDF 里看不到绘制的形状
原因:形状是绘制在画布对象上的,如果绘制之后没有调用 doc.SaveToFile 把文档写回文件,或者输出的文件与读取下载的不是同一个文件名,就会导致看到的仍是原始内容。
解决:确认绘制完成后调用 doc.SaveToFile(outputFileName) 保存,并用同一个 outputFileName 从虚拟文件系统读取下载:
// 保存文档到指定文件名
doc.SaveToFile(outputFileName);
doc.Close();
// 用同一个文件名从 VFS 读取,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。







