公式、化学式、单位符号、脚注角标都带着上下标:a² + b² = c²、H₂SO₄、30m²。把这些排进 PDF 时,一行普通的文字画下去是不对的——上下标要比正文小一号,基线还要抬上去或沉下来,位置差一点整条式子就散了。
本文介绍用 Spire.PDF for JavaScript 在 PDF 页面中绘制上标和下标。它基于 WebAssembly 在浏览器端直接创建与保存 PDF 文档,全过程在本地完成,通过虚拟文件系统(VFS)读写文件,无需后端配合。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
绘制上标和下标
文字排成上标还是下标由 PdfStringFormat 的 SubSuperScript 决定:取 PdfSubSuperScript.SuperScript 时基线抬升并缩小字号,SubScript 则下沉。它作用的是一次 DrawString 画出的整段文字,所以基文本与上下标要分两次画,后一段的落点用 MeasureString 量出的宽度续接。
function App() {
const drawSuperAndSubScript = 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_Black() }) });
// 下标:基文本照常画
let text = '水的化学式是 H';
page.Canvas.DrawString({ s: text, font: font, brush: brush, x: 40, y: 110 });
// 量出基文本宽度,下标紧接在它右侧落笔
let x = 40 + font.MeasureString({ text: text }).Width;
// SubSuperScript 取 SubScript,这段文字自动缩小并下沉
const subFormat = new pdfModule.PdfStringFormat();
subFormat.SubSuperScript = pdfModule.PdfSubSuperScript.SubScript;
page.Canvas.DrawString({ s: '2', font: font, brush: brush, x: x, y: 110, format: subFormat });
// 上标同样分两次画,落点续在基文本右侧
text = '质能方程 E = mc';
page.Canvas.DrawString({ s: text, font: font, brush: brush, x: 40, y: 170 });
x = 40 + font.MeasureString({ text: text }).Width;
// SubSuperScript 取 SuperScript,这段文字自动缩小并抬升
const superFormat = new pdfModule.PdfStringFormat();
superFormat.SubSuperScript = pdfModule.PdfSubSuperScript.SuperScript;
page.Canvas.DrawString({ s: '2', font: font, brush: brush, x: x, y: 170, format: superFormat });
// 定义输出文件名并保存文档
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={drawSuperAndSubScript}>
开始绘制
</button>
</div>
);
}
export default App;
SubSuperScript 的两次取值分别画出的下标与上标:

精确控制上下标的缩放与偏移
SubSuperScript 一次只能把整段文字排成同一种形态,一行里与正文交替出现的上下标就得自己排:用 MeasureString 量出已绘文字的宽度推进 x,再换小一号的字体、按磅值偏移 y 落笔。字号取多大、抬升多少磅全由你给,不限于库内建的那套比例。
function App() {
const drawInlineSuperAndSubScript = 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 brush = new pdfModule.PdfSolidBrush({ pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Black() }) });
// 正文 16 磅,上下标自己挑字号,这里取 10 磅
const BASE_SIZE = 16;
const MARK_SIZE = 10;
const baseFont = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: BASE_SIZE });
const markFont = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: MARK_SIZE });
// 上标抬升 8 磅、下标下沉 6 磅;y 轴向下,抬升取减法
const SUPER_RISE = 8;
const SUB_SINK = 6;
// 逐段落笔:每画完一段就用该段的字体量出宽度,把 x 推到它右端
const writeRuns = (runs, lineY) => {
let x = 40;
runs.forEach((run) => {
const runFont = run.kind === 'base' ? baseFont : markFont;
// 落点是文字的左上角,基线在其下方一个字号的长度处;小字号先补回这段落差
const baselineFix = run.kind === 'base' ? 0 : BASE_SIZE - MARK_SIZE;
const riseFix = run.kind === 'super' ? -SUPER_RISE : run.kind === 'sub' ? SUB_SINK : 0;
const runY = lineY + baselineFix + riseFix;
page.Canvas.DrawString({ s: run.s, font: runFont, brush: brush, x: x, y: runY });
x += runFont.MeasureString({ text: run.s }).Width;
});
};
// 公式:a² + b² = c²
writeRuns([
{ s: 'a', kind: 'base' }, { s: '2', kind: 'super' },
{ s: ' + b', kind: 'base' }, { s: '2', kind: 'super' },
{ s: ' = c', kind: 'base' }, { s: '2', kind: 'super' },
], 110);
// 化学式:H₂SO₄
writeRuns([
{ s: 'H', kind: 'base' }, { s: '2', kind: 'sub' },
{ s: 'SO', kind: 'base' }, { s: '4', kind: 'sub' },
], 170);
// 定义输出文件名并保存文档
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={drawInlineSuperAndSubScript}>
开始绘制
</button>
</div>
);
}
export default App;
正文与小一号的上下标交替衔接,一行公式与一行化学式:

常见问题
为什么整段文字都变成了上标
原因:SubSuperScript 设在 PdfStringFormat 上,作用范围是这一次 DrawString 画出的全部字符,而不是其中的某几个字。把基文本和上标放进同一次 DrawString,整段都会一起缩小、一起抬升。
解决:拆成两次绘制,基文本不带格式,上标单独用带格式的那一次画,落点用 MeasureString 量出的宽度续接:
// 基文本不带格式
page.Canvas.DrawString({ s: '质能方程 E = mc', font: font, brush: brush, x: 40, y: 170 });
// 量出基文本宽度,只把上标这一次排成上标
const x = 40 + font.MeasureString({ text: '质能方程 E = mc' }).Width;
const superFormat = new pdfModule.PdfStringFormat();
superFormat.SubSuperScript = pdfModule.PdfSubSuperScript.SuperScript;
page.Canvas.DrawString({ s: '2', font: font, brush: brush, x: x, y: 170, format: superFormat });
手工排的上下标与正文对不齐,或者两段之间留着缝
原因:落点是这段文字的左上角,不是基线——基线在落点下方一个字号的长度处,字号一变小,这段距离也跟着变短。直接按基文本的落点给上标一个偏移量,得到的并不是想要的基线位移:小字号的上标会抬得比预期更高,下标则可能刚好压在基线上。x 推进若用了另一个字号量出的宽度,两段之间不是重叠就是留缝。
解决:按「落点 + 字号 = 基线」把想要的基线位移换算回落点,宽度用真正落笔的字体量:
// 基文本的基线在落点下方一个字号的长度处
const baselineY = lineY + BASE_SIZE;
// 上标要把基线抬高 8 磅,落点 = 目标基线 − 该段自己的字号
const superY = baselineY - 8 - MARK_SIZE;
page.Canvas.DrawString({ s: '2', font: markFont, brush: brush, x: x, y: superY });
// 宽度用实际落笔的字体量
x += markFont.MeasureString({ text: '2' }).Width;
想让上下标大一点或小一点,SubSuperScript 没有对应的参数
原因:PdfStringFormat 上只提供 SubSuperScript 这一个开关,缩放比例与升降量由库固定,没有公开可调的比例值。
解决:需要别的比例就自己排——换个小一号的字体对象画上下标,偏移量按需要的磅值给(写法见功能点 2)。
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。







