给 PDF 补文字往往排在生成流程的收尾:单据编号、审核意见、说明批注,或者压在图上一层浅色大字。几页文档手动敲一下还好,等文字要跟着数据走——编号逐份变化、说明斜排在页角、浅色字压在图表上——手工排版就吃力了。
本文介绍用 Spire.PDF for JavaScript 在 PDF 页面中绘制文本,包括渐变填充的文字、在矩形框内排版的文字,以及旋转变形与半透明的文字。它基于 WebAssembly 在浏览器端直接创建与保存 PDF 文档,全过程在本地完成,通过虚拟文件系统(VFS)读写文件,无需后端配合。
本文介绍四个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
绘制颜色渐变的文本
文字的颜色来自 DrawString 的填充刷子,换上一把渐变刷子 PdfLinearGradientBrush,字就会沿指定方向从一种颜色过渡到另一种——方向由 mode 决定,起止范围由刷子上的 rect 圈定。
function App() {
const drawGradientText = 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 文档并添加一个空白页面
const doc = new pdfModule.PdfDocument();
const page = doc.Pages.Add();
const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: 24 });
const text = '颜色渐变的文本';
// 量出这行文字的宽度,让渐变的起止范围与文字等宽
const textWidth = font.MeasureString({ text: text }).Width;
// 横向渐变:从红色过渡到蓝色
const gradient = new pdfModule.PdfLinearGradientBrush({
rect: new pdfModule.RectangleF({ x: 40, y: 90, width: textWidth, height: 40 }),
color1: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Red() }),
color2: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Blue() }),
mode: pdfModule.PdfLinearGradientMode.Horizontal,
});
// 文字落点与渐变矩形的左端对齐,红蓝两色完整扫过整行
page.Canvas.DrawString({ s: text, font: font, brush: gradient, x: 40, y: 110 });
// 定义输出文件名并保存文档
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={drawGradientText}>
开始绘制
</button>
</div>
);
}
export default App;
渐变矩形与文字等宽时,红色到蓝色完整扫过整行文字:

绘制在矩形框内排版的文本
DrawString 的落点除了给一对坐标,也可以给一个矩形框 layoutRectangle:文字以框宽为准自动折行,不用自己算每一行在哪里断开。再配 PdfStringFormat 的 alignment 与 lineAlignment,可以更灵活的控制文本的对齐效果。
function App() {
const drawTextInRectangle = 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 文档并添加一个空白页面
const doc = new pdfModule.PdfDocument();
const page = doc.Pages.Add();
const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: 14 });
const brush = new pdfModule.PdfSolidBrush({ pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Black() }) });
const borderPen = new pdfModule.PdfPen({ pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_LightGray() }), width: 1 });
const text = '这是一段比较长的说明文字,交给矩形框之后会按框的宽度自动折行,不需要自己计算每一行到哪里断开。';
// 左框:默认靠左折行,文字从框的左上角排起
const leftBox = new pdfModule.RectangleF({ x: 40, y: 80, width: 200, height: 100 });
page.Canvas.DrawRectangle({ pen: borderPen, rectangle: leftBox });
page.Canvas.DrawString({ s: text, font: font, brush: brush, layoutRectangle: leftBox });
// 右框:同样的文字,在框内水平居中并垂直居中
const rightBox = new pdfModule.RectangleF({ x: 300, y: 80, width: 200, height: 100 });
page.Canvas.DrawRectangle({ pen: borderPen, rectangle: rightBox });
const center = new pdfModule.PdfStringFormat({
alignment: pdfModule.PdfTextAlignment.Center,
lineAlignment: pdfModule.PdfVerticalAlignment.Middle,
});
page.Canvas.DrawString({ s: text, font: font, brush: brush, layoutRectangle: rightBox, format: center });
// 定义输出文件名并保存文档
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={drawTextInRectangle}>
开始绘制
</button>
</div>
);
}
export default App;
同一段文字在 200 磅宽的框内自动折行,右框另加了水平与垂直居中:

绘制旋转与变形的文本
文字的旋转与变形不靠字体参数,而是先把画布转过去,画布变换常用的四个方法:
| 接口 | 作用 | 参数与单位 |
|---|---|---|
TranslateTransform(dx, dy) |
把画布原点平移到目标位置 | 位移量,单位磅 |
RotateTransform({ angle }) |
绕画布原点旋转 | 角度;正值在本画布上为顺时针 |
SkewTransform(angleX, angleY) |
让坐标轴倾斜,文字沿斜线排布 | 倾斜角度;(-20, 0) 时整行右端抬高 |
ScaleTransform(scaleX, scaleY) |
按倍数缩放画布 | 两个方向的缩放倍数;(1, 0.6) 纵向压到 0.6 倍 |
四种变换作用在同一段样例文字上的效果(灰为变换前、红为变换后,圆点为落点):

function App() {
const drawTransformedText = 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 文档并添加一个空白页面
const doc = new pdfModule.PdfDocument();
const page = doc.Pages.Add();
const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: 16 });
const brush = new pdfModule.PdfSolidBrush({ pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_SteelBlue() }) });
// 平移:只把原点移到落点,文字保持水平地整体挪过去
let state = page.Canvas.Save();
page.Canvas.TranslateTransform(60, 110);
page.Canvas.DrawString({ s: '平移后的文本', font: font, brush: brush, x: 0, y: 0 });
page.Canvas.Restore({ state: state });
// 旋转:把原点挪到落点,再转过 30°
state = page.Canvas.Save();
page.Canvas.TranslateTransform(120, 210);
page.Canvas.RotateTransform({ angle: 30 });
page.Canvas.DrawString({ s: '旋转 30° 的文本', font: font, brush: brush, x: 0, y: 0 });
page.Canvas.Restore({ state: state });
// 倾斜:横向切变 -20°,整行右端抬高
state = page.Canvas.Save();
page.Canvas.TranslateTransform(60, 430);
page.Canvas.SkewTransform(-20, 0);
page.Canvas.DrawString({ s: '横向倾斜的文本', font: font, brush: brush, x: 0, y: 0 });
page.Canvas.Restore({ state: state });
// 变形:纵向缩到 0.6 倍,字被压扁
state = page.Canvas.Save();
page.Canvas.TranslateTransform(60, 560);
page.Canvas.ScaleTransform(1, 0.6);
page.Canvas.DrawString({ s: '纵向压缩的文本', font: font, brush: brush, x: 0, y: 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>绘制旋转与变形的文本</h1>
<button onClick={drawTransformedText}>
开始变换
</button>
</div>
);
}
export default App;
平移、旋转、横向切变与纵向压缩四种画布变换绘制出的文字:

绘制半透明文本
SetTransparency 设在画布上:alphaBrush 与 alphaPen 分别控制填充和描边的透明程度,取值是 0 到 1 之间的小数(0 全透明、1 不透明),blendMode 决定文字与下层内容如何叠加。它从设置的那一刻起对所有绘制生效,所以要用 Save 与 Restore 框在需要的范围内,否则后面的内容会一起变淡。
function App() {
const drawTransparentText = 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 文档并添加一个空白页面
const doc = new pdfModule.PdfDocument();
const page = doc.Pages.Add();
const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: 20 });
const brush = new pdfModule.PdfSolidBrush({ pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_SeaGreen() }) });
const text = 'Spire.PDF for JavaScript 绘制半透明文本';
// 第一行:不透明,作为对照
page.Canvas.DrawString({ s: text, font: font, brush: brush, x: 40, y: 90 });
// 打开透明度设置:填充与描边的 alpha 都设为 0.3
const state = page.Canvas.Save();
page.Canvas.SetTransparency({ alphaPen: 0.3, alphaBrush: 0.3, blendMode: pdfModule.PdfBlendMode.Normal });
page.Canvas.DrawString({ s: text, font: font, brush: brush, x: 40, y: 140 });
// 恢复画布状态,这段之外的绘制回到不透明
page.Canvas.Restore({ state: state });
page.Canvas.DrawString({ s: text, font: font, brush: brush, x: 40, y: 190 });
// 定义输出文件名并保存文档
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={drawTransparentText}>
开始绘制
</button>
</div>
);
}
export default App;
同一行文字的三次绘制:不透明、alpha 0.3,以及 Restore 之后恢复不透明:

常见问题
文字画到了页面外面,或者上下位置和预期相反
原因:画布坐标的原点在页面左上角(再内缩页边距),y 轴向下,单位是磅;落点是这行文字的左上角,不是文字的基线。按左下角为原点、y 轴向上的习惯换算,位置就会跑到相反的一侧,甚至落到页面可绘制区域之外——示例页面是 A4,上下页边距各 40 磅,可用高度只有 762 磅,落点 y 超过约 740 磅时,这行字就会被切掉。
解决:按左上角为原点、y 向下增大来摆放,需要从页面底部往上算时,取可用高度做减法:
// 页面可绘制区域的高度(A4 + 40 磅页边距时为 762 磅)
const height = page.Canvas.ClientSize.Height;
// 距离可绘制区域底部 100 磅处落笔
page.Canvas.DrawString({ s: '靠近页面底部的文字', font: font, brush: brush, x: 40, y: height - 100 });
渐变文字只显示一种颜色,或者颜色过渡不完整
原因:PdfLinearGradientBrush 的 rect 圈定的是渐变的起止范围,用的是画布上的绝对坐标,与文字落点相互独立。矩形没盖住整行文字时,文字只能落在渐变的一段上,看上去就像单色。实测把矩形放在 x 从 0 开始、文字落在 x 为 40 的位置,文字左端已经走过整段渐变的三分之一,红蓝过渡就不再完整。
解决:用 MeasureString 量出文字宽度,让矩形与文字等宽、起点与落点对齐,渐变就会完整扫过整行:
// 文字宽度作为渐变矩形的宽度
const textWidth = font.MeasureString({ text: text }).Width;
const gradient = new pdfModule.PdfLinearGradientBrush({
rect: new pdfModule.RectangleF({ x: 40, y: 90, width: textWidth, height: 40 }),
color1: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Red() }),
color2: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Blue() }),
mode: pdfModule.PdfLinearGradientMode.Horizontal,
});
// 落点与矩形左端对齐
page.Canvas.DrawString({ s: text, font: font, brush: gradient, x: 40, y: 110 });
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。







