关于 boxpdf
boxpdf 是基于 pdf-lib 的 TypeScript PDF 布局库,使用声明式布局组件生成分页 PDF,并支持表格、主题和表单控件。它面向需要在应用中排版 PDF 或使用模板的开发者,也附带供 Agent 使用的资源型 MCP 服务。
主要功能
- 01声明式堆栈与文本布局
- 02多页排版及表格分页
- 03主题与 PDF 表单控件
- 04流式 PDF 生成
- 05CLI 模板脚手架
适用场景
- 01生成收据、简历或证书 PDF
- 02排版包含表格和页眉页脚的文档
- 03创建带可填写表单控件的 PDF
- 04从模板快速搭建 PDF 文件
MCP 相关
CLI 附带一个仅提供资源的 MCP 服务,供 Agent 访问文档和模板。README 未说明该服务暴露的具体资源内容。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
@boxpdf/writer
基于 pdf-lib 的盒式布局 DSL。使用可移植的 JavaScript 实现,可在 Node 20+、Cloudflare Workers、Deno 和浏览器中运行。
在线图库:https://earonesty.github.io/boxpdf/
相关仓库:boxpdf-html(HTML → BoxPDF)和 streaming-pdf-reader(PDF → 文档模型、HTML 和结构化数据)。
import { cleanTheme, flowToPdf, hline, hstack, standardFonts, text, vstack } from "@boxpdf/writer";
const bytes = await flowToPdf(async (pdf) => {
const { font, bold } = await standardFonts(pdf);
const theme = cleanTheme({ font, bold });
return [
vstack({ gap: 8 },
text("Receipt #18472", theme.type.h1),
text("May 14, 2026", theme.type.caption)
),
hline(theme.hr),
hstack({ gap: 16, justify: "between", width: 515 },
text("Wool socks", theme.type.body),
text("$28.00", { ...theme.type.body, font: bold, align: "right", width: 80 })
)
];
});
flowToPdf 负责管理文档生命周期并返回保存后的字节。standardFonts 一次调用即可嵌入内置的 Helvetica 字体系列(常规、粗体、斜体、粗斜体)。
想自行管理文档?仍然可以使用显式方式。
import { PDFDocument, StandardFonts } from "pdf-lib";
import { cleanTheme, renderFlow, text, vstack } from "@boxpdf/writer";
const pdf = await PDFDocument.create();
const font = await pdf.embedFont(StandardFonts.Helvetica);
const bold = await pdf.embedFont(StandardFonts.HelveticaBold);
const theme = cleanTheme(font, bold);
await renderFlow(pdf, [
vstack({ gap: 8 },
text("Receipt #18472", theme.type.h1),
text("May 14, 2026", theme.type.caption)
)
]);
const bytes = await pdf.save();
renderFlow(pdf, nodes, options) 会将内容分页到你管理的文档中,并返回 { pages }——需要多次渲染、页面对象或自定义 save() 选项时,请使用它。boxpdf 会重新导出 PDFDocument 和 StandardFonts,供这种显式生命周期使用。
安装
npm install @boxpdf/writer pdf-lib
pdf-lib 是对等依赖项。
旧版包名
原始的 boxpdf 包仍受支持,并以相同版本从同一构建发布。现有导入方式和 boxpdf CLI 均可继续使用,无需更改:
npm install boxpdf pdf-lib
新项目应使用 @boxpdf/writer。两个包名提供相同的 API,也都提供 boxpdf 命令。
功能
- 声明式布局基元:
vstack、hstack、text、image、hline、vline、spacer、flex、keepTogether、link、svgPath、table。 - 支持布局感知的 AcroForm 字段:文本、复选框、单选框、下拉框、选项列表和按钮小部件。
- 内边距、外边距、背景、背景图片、边框、borderRadius、溢出裁剪、flex-grow、flex-shrink、justify、align。
- 支持混合内联片段、内联替换节点、硬换行、悬挂缩进和可选段落浮动的富文本段落。
- 自动换行,支持
maxLines截断、可选的breakWords和禁止换行控制。 - 主题:
cleanTheme、stripeTheme、editorialTheme、brutalistTheme。 - 多页流式布局,支持逐页页眉和页脚、堆栈拆分和表格行拆分。
- 支持流式生成,限制内存占用。
- PDF 链接注释、文本装饰、文档元数据。
- 核心库压缩后约为 7 KB。只有调用
loadFont或embedInter时,自定义字体才会引入@pdf-lib/fontkit。
模板
templates/ 中的文件涵盖收据、登机牌、简历、订单确认单和证书。每个模板都是单个文件。
使用 CLI 将模板脚手架生成到应用中:
npx boxpdf init receipt --out src/pdf/receipt.ts
npx boxpdf list
CLI 还提供仅含资源的 MCP 服务,供 Agent 使用:
claude mcp add boxpdf -- npx -y boxpdf mcp
主题
import { cleanTheme, editorialTheme, standardFonts } from "@boxpdf/writer";
const theme = cleanTheme(await standardFonts(pdf)); // Helvetica
const serif = editorialTheme(await standardFonts(pdf, "times")); // serif + italic slot
每个主题工厂都接受 { font, bold, italic? } 对象(这正是 standardFonts(pdf) 和 embedInter(pdf) 返回的对象),也接受旧版的位置参数字体:
cleanTheme({ font, bold }) // or cleanTheme(font, bold)
stripeTheme({ font, bold })
editorialTheme({ font, bold, italic }) // or editorialTheme(font, bold, italic)
brutalistTheme({ font, bold }) // courier regular + bold
standardFonts(pdf, family) 接受 "helvetica"(默认值)、"times" 或 "courier",并返回 { font, bold, italic, boldItalic }。每个主题都提供相同的结构:colors、spacing、radii、type、card、hr。
API
容器
vstack(style, ...children)。垂直布局。hstack(style, ...children)。水平布局。keepTogether({ gap?, margin? }, ...children)。分页时将内容作为一个整体处理。
容器 style:
| 字段 | 类型 | 说明 |
|---|---|---|
width / height | number | 固定尺寸;否则根据内容确定尺寸。 |
padding / margin | number | { top, right, bottom, left } | 简写形式或分别设置各侧。 |
background | RGB | 纯色填充。 |
backgroundImage | { image, width, height, offsetX?, offsetY?, repeat? } | 绘制在子项后方的图像,并裁剪至容器框内。 |
border | { color, width } | 容器框周围宽度为 1pt 或更大的描边。 |
borderSides | { top?, right?, bottom?, left? } | 使用 { color, width } 分别设置各侧描边。 |
borderRadius | number | 圆角半径。 |
overflow | "visible" | "hidden" | 将堆栈子项和绝对定位后代裁剪至容器框矩形内。 |
position | "relative" | "absolute" | 类似 CSS 的容器框定位方式。 |
top / right / bottom / left | number | 绝对偏移量,单位为 points。 |
zIndex | number | 定位容器框的绘制顺序;值越大,绘制越靠后。 |
rotate | number | 绕容器框中心按顺时针方向旋转绘制,单位为度;布局不变。 |
transform | BoxTransform[] | 按顺序应用的绘制变换:translate、scale、rotate、skew 和 matrix。 |
transformOrigin | { x, y } | 使用 { length, percent } 组件设置旋转中心;默认为容器框中心。 |
grow | number | 沿父级主轴的弹性增长权重。 |
shrink | number | 弹性收缩权重。 |
breakInside | "auto" | "avoid" | renderFlow 下的分片提示;avoid 会将容器框保持为一个整体。 |
gap | number | 子项之间的间距。 |
justify | "start" | "center" | "end" | "between" | "around" | "evenly" | 主轴分布方式。 |
align | "start" | "center" | "end" | "stretch" | "baseline" | 交叉轴对齐方式。baseline 适用于 hstack 行。 |
叶节点
text(content, { size, font, color?, align?, width?, lineHeight?, maxLines?, underline?, strikethrough?, margin? })。设置width后会自动换行。设置maxLines后,超出的文本会以省略号截断。默认lineHeight使用字体的完整高度,包括下伸部。paragraph({ width?, align?, lineHeight?, margin?, paddingLeft?, textIndent?, wrap?, floats? }, ...runs)。混排内联文本片段和不可拆分的内联节点,并将它们作为一个段落进行换行。使用run(text, style)、linkRun(text, style, href)和inlineNode(node, { verticalAlign?, href? })。片段中的换行符会产生硬换行;wrap: false会禁用软换行。image(pdfImage, { width, height, margin? })。接收已嵌入的PDFImage。imageFit(pdfImage, { width, height, fit?, margin? })。将图像居中绘制在固定矩形内,缩放以适应矩形(默认)或铺满矩形并裁剪。spacer(size, { grow? })/flex(weight = 1)。固定或可增长的间距。hline({ color, thickness?, width?, margin? })。vline({ color, thickness?, height?, margin? })。link({ href }, child)。包装一个子项,并在其绘制边界框上注册 PDF 链接注释。table({ columns, rows, ... })。支持固定、自动或分数列,以及表头/表尾行、分隔线、样式化单元格和renderFlow下按行分页。单元格可以是普通节点,也可以是{ content, colSpan?, padding?, background?, border?, borderSides?, borderRadius?, align?, valign? }。
AcroForm 字段
表单控件是原子布局节点,因此无需手动指定页面坐标,就能在堆栈、表格、分页和流式文档中使用。
import {
checkbox,
dropdown,
flowToPdf,
standardFonts,
text,
textField,
vstack
} from "@boxpdf/writer";
const bytes = await flowToPdf(async (pdf) => {
const { font } = await standardFonts(pdf);
return [
vstack({ gap: 10 },
text("Registration", { size: 18, font }),
textField({
name: "person.name",
width: 260,
height: 26,
font,
fontSize: 11,
required: true
}),
dropdown({
name: "person.state",
width: 140,
height: 26,
font,
options: ["CA", "NY", "WA"]
}),
checkbox({
name: "terms.accepted",
width: 16,
height: 16,
required: true
})
)
];
});
textField({ name, width, height, ... })。支持初始value、multiline、password、maxLength、combed、对齐方式,以及通用字段标志和外观选项。默认情况下,密码文本字段不可导出,除非显式设置exported: true。checkbox({ name, width, height, checked? })。radioOption({ name, option, width, height, selected? })。名称相同的节点会组成一个单选组。dropdown({ name, options, width, height, selected?, editable?, sorted? })。保持单选,以确保查看器行为一致。optionList({ name, options, width, height, selected?, multiselect?, sorted? })。button({ name, label, width, height, ... })。创建可移植的按钮控件及其外观;BoxPDF 不会附加 PDF JavaScript 或提交操作。标准的 SubmitForm 操作取决于阅读器;浏览器查看器可能会根据来源策略阻止从本地 PDF 提交。
所有字段都接受 margin、alignSelf、readOnly、required、exported、hidden、backgroundColor、borderColor 和 borderWidth。包含文本的字段还接受 font、fontSize 和 textColor。字段名称在整个文档中必须唯一。重复使用名称会为同一个逻辑字段添加另一个控件;将名称用于不同字段类型则会抛出异常。密码文本字段默认设置为 exported: false,因此若要有意允许提交或导出,请设置 exported: true。表单支持普通、流式和加密输出;使用加密选项 fillForms 控制符合规范的阅读器是否允许修改。
对于共享逻辑字段,文本、下拉列表和选项列表的初始化状态(value、options 和初始 selected)由第一个节点确定。单选组标志(offToggleable、mutuallyExclusive)也仅由第一个节点确定。radioOption 上的 selected: true 会标记该选项,而 selected: false 不会清除任何现有选择。
处理调用方拥有的 PDFDocument 时,请使用 getFormValues(pdf)、setFormValues(pdf, values) 和 flattenForm(pdf)。更新非 WinAnsi 文本时,请将嵌入字体作为 { font } 传入 setFormValues 或 flattenForm,以便 pdf-lib 使用该字体重新生成外观。
AcroForm 控件是 PDF 注释,而非页面绘制操作。因此,它们不能放在经过变换的 BoxPDF 祖先节点内;BoxPDF 会抛出异常,而不是生成位置错误的控件。XFA、签名字段、PDF JavaScript 和加密签名不属于核心表单层的范畴。
渲染
flowToPdf(build, options?)。生成 PDF 字节的最简路径。创建一个PDFDocument,将其传给你的build(pdf)回调(在其中嵌入字体/图片并返回顶层节点),使用renderFlow进行分页,然后返回保存后的Uint8Array。options与renderFlow相同。renderFlow(pdf, nodes[], options)。对一系列顶层子节点进行分页。顶层vstack节点可在子节点之间拆分;table()可在行之间拆分,并在后续页面重复表头。对于不可拆分的区块,请使用keepTogether()或breakInside: "avoid"。选项:size、margin、header?、footer?、reserveBottom?、title?、author?、subject?、keywords?、creator?、producer?、debug?、warnings?、profile?。页眉和页脚会接收{ pageNumber, totalPages }。默认使用 LETTER(612×792)。A4 请传入{ size: PageSizes.A4 }。如果顶层子节点测得的宽度超出页面内容区域,boxpdf 会发出console.warn。传入warnings: false可抑制警告。savePdf(pdf, options?)。保存由调用方拥有的文档,可选择启用密码加密。直接调用pdf.save()始终会保持 pdf-lib 的未加密行为。streamFlow(pdf, writable, asyncIterable, options)。按页增量渲染。无论页数多少,内存占用都保持有界。每页关闭时,都会将 PDF 字节写入WritableStream<Uint8Array>。契约详见下文的流式处理部分。renderToPdf(node, options)。便捷的单页渲染方法。pageInner(size, margin)/pageContent(size, margin)。计算页面的内容区域宽度或矩形。render(node, page, x, yTop, parentWidth)。在已有PDFPage上的指定位置绘制子树。measure(node, parentWidth)。独立于渲染计算固有尺寸。
传入 { debug: true },即可用红色描出内容框,用橙色描出边距框。
辅助函数
standardFonts(pdf, family?)。嵌入 pdf-lib 内置字体系列(默认"helvetica",也可选"times"、"courier"),并返回{ font, bold, italic, boldItalic },可直接用于任何主题。这些字体使用紧凑的 PDF 标准字体引用。loadFont(pdf, source, options?)。从 URL、字节、base64 或数据 URL 嵌入 TTF。loadImage(pdf, source)。嵌入 PNG 或 JPEG(自动检测)。aspectRatio(ratio, { width })/aspectRatio(ratio, { height })。为固定比例的框或图片推导缺失的尺寸。formatCurrency(n, { currency, locale })。Intl.NumberFormat的封装。defineStyles({ ... })。为可复用的样式集合提供类型化标识。hex("#1f8a4d")/rgb255(31, 138, 77)。颜色构造函数。
加载字体
有三种方式。
通过 CLI 打包字节。 推荐用于生产环境。
npx boxpdf font add ./Acme-Regular.ttf=regular ./Acme-Bold.ttf=bold \
--out src/fonts/acme.ts
生成包含 export const base64 字符串的 src/fonts/acme.ts。然后:
import { loadFont } from "@boxpdf/writer";
import { regular, bold } from "./fonts/acme.js";
const font = await loadFont(pdf, regular);
const acmeBold = await loadFont(pdf, bold);
字节会随你的包一起发布,可立即在本地加载。
内置的 Inter 字重。
import { loadFont } from "@boxpdf/writer";
import { inter, interBold } from "@boxpdf/writer/inter";
const font = await loadFont(pdf, inter);
const bold = await loadFont(pdf, interBold);
boxpdf/inter 会将相同的 Inter 子集重新导出为原始 base64 字符串(inter、interBold、interItalic),以及 embedInter(pdf, { italic?, tabularFigures? })。
导入 boxpdf/inter 会加载约 325 KB 的字体字节以及 @pdf-lib/fontkit。仅导入核心模块则会使用更小的核心包。
import { embedInter } from "@boxpdf/writer/inter";
const { font, bold } = await embedInter(pdf);
const theme = cleanTheme(font, bold);
传入 { tabularFigures: true },还可获取用于金额列的等宽数字变体:
const { font, bold, tabularFont, tabularBold } = await embedInter(pdf, {
tabularFigures: true
});
text(formatCurrency(amount), { size: 12, font: tabularBold, align: "right" });
从 URL 获取。
const brand = await loadFont(pdf, "https://example.com/Acme-Regular.ttf");
完整 TTF 会在嵌入时获取并进行子集化。在缓存已预热的 Cloudflare Workers 上速度很快(约 5–15 ms)。在冷缓存或 Node 环境中,每次都需要完整获取。
无论来源如何,loadFont 都接受相同的 { subset?: boolean; features?: { tnum: true } } 选项。使用 features: { tnum: true } 可启用等宽数字。
密码加密
BoxPDF 可以使用 Standard Security Handler 修订版 6 和 AES-256 写入 PDF 2.0 密码加密输出。加密使用运行时的 Web Crypto 实现,不会为浏览器包增加加密依赖。只有在请求加密时,才会将实现作为独立分块加载。
const bytes = await flowToPdf(
async (pdf) => {
const { font } = await standardFonts(pdf);
return [text("Confidential", { font, size: 18 })];
},
{
encryption: {
password: "document-open-password",
ownerPassword: "administrative-password",
permissions: {
printing: "lowResolution",
copying: false,
modify: false
}
}
}
);
对于由调用方拥有的文档,请通过 savePdf 保存:
import { PDFDocument, renderFlow, savePdf } from "@boxpdf/writer";
const pdf = await PDFDocument.create();
await renderFlow(pdf, nodes);
const bytes = await savePdf(pdf, {
encryption: { password: "open me" }
});
password 为必填项,且不能为空。ownerPassword 为可选项;如果省略,BoxPDF 会生成并丢弃一个随机的内部所有者凭据。密码会使用 SASLprep 处理,允许包含 Unicode 字符,处理后最多为 127 个 UTF-8 字节。可用权限包括 printing、modify、copying、annotate、fillForms 和 assemble。
PDF 权限是查看器的建议性设置,并非 DRM。请通过与 PDF 不同的渠道发送密码。加密不能与 PDF/A 同时使用,BoxPDF 也不会解密输入的 PDF 或保留现有签名。再次保存同一文档时,会生成新的密钥、盐值、文件标识符和 IV。
流式输出
对于长时间运行的文档生成,请使用 streamFlow,而不是 renderFlow。每当页面结束时,它都会向 WritableStream<Uint8Array> 输出 PDF 字节。无论总页数多少,堆内存峰值都限制在 O(shared resources + one page in flight)。
import { PDFDocument, StandardFonts } from "pdf-lib";
import { streamFlow, text, cleanTheme } from "@boxpdf/writer";
const pdf = await PDFDocument.create();
const font = await pdf.embedFont(StandardFonts.Helvetica);
const bold = await pdf.embedFont(StandardFonts.HelveticaBold);
const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>();
streamFlow(pdf, writable, generate(font, bold)).catch(console.error);
return new Response(readable, {
headers: { "content-type": "application/pdf" }
});
async function* generate(font, bold) {
for await (const order of fetchOrders()) {
yield buildOrderRow(font, bold, order);
}
}
对于 Node,请适配 stream.Writable:
import { createWriteStream } from "node:fs";
import { streamFlow, nodeAdapter } from "@boxpdf/writer";
const out = nodeAdapter(createWriteStream("./report.pdf"));
await streamFlow(pdf, out, nodes, {
encryption: { password: "open me" }
});
如果某个逻辑上的堆栈或表格过大,无法一次性构建,请使用 flowContinuation 分段输出有界内容。具有相同 id 的相邻片段会像同一个节点一样分页,包括堆栈间距、装饰、表格标题和行分隔符:
for (let offset = 0; offset < rows.length; offset += 100) {
const final = offset + 100 >= rows.length;
yield flowContinuation(
table({ columns, header, rows: rows.slice(offset, offset + 100) }),
"orders",
final
);
}
续接片段必须连续,且最后一个片段必须设置 final: true;如果序列中断或未完成,streamFlow 会报错,而不会悄然生成不完整的布局。
使用约定
- 所有
embedFont/embedJpg/embedPng调用都必须在streamFlow开始前完成。在流式处理中途嵌入会抛出异常。 - 可迭代对象会一次消耗一个节点。请传入生成器。
streamFlow会独占可写流的所有权:成功时关闭,失败时中止。- 流式输出的页眉和页脚会接收
ctx.pageNumber。如果页眉或页脚要显示“第 X 页,共 Y 页”,请使用renderFlow;在流式处理中访问ctx.totalPages会抛出异常。 - 输出大小比
renderFlow默认的save()大 0–5%。
内存基准测试
渲染期间的峰值堆内存。每项测量都在独立子进程中运行。每页包含 50 行文本。包含 @react-pdf/renderer 以便比较形态。
| 页数 | streamFlow 峰值 | renderFlow 峰值 | @react-pdf 峰值 | 输出 |
|---|---|---|---|---|
| 50 | 12.8 MB | 31.7 MB | 160.8 MB | 70 KB |
| 250 | 15.4 MB | 91.1 MB | 643.1 MB | 347 KB |
| 500 | 18.7 MB | 120.8 MB | 1,219.9 MB | 693 KB |
| 1000 | 25.4 MB | 219.6 MB | 2,292.6 MB | 1.4 MB |
随着工作负载增加 100 倍,streamFlow 的峰值堆内存基本保持平稳(12 → 25 MB)。renderFlow 则随页数大致线性增长。在此工作负载下,@react-pdf/renderer 每页增加约 2.3 MB 内存,1000 页时峰值达到 2.3 GB。设计说明和图表请参见 docs/design/streaming.md。
续页路径有独立的堆内存限制子进程检查。与上方较早的对比不同,此检查会延迟构建每个片段,并在输出后强制执行 GC,以区分 V8 的分配高水位与保留的活动集:
| 续页片段数 | 输出页数 | 采样峰值堆内存 | GC 后保留的堆内存 | 输出 |
|---|---|---|---|---|
| 100 | 81 | ~58 MB | ~16 MB | 129 KB |
| 1000 | 810 | ~125 MB | ~26 MB | 1.3 MB |
两次运行都在 --max-old-space-size=128 下完成。工作负载增加 10 倍后,保留的堆内存增加约 10 MB,主要来自最终页面树和 xref 索引;渲染后的页面内容和续页输入会逐步释放。运行 pnpm memory:check:continuation 可复现此检查。
Cloudflare Workers
核心模块和 boxpdf/inter 子路径都可在 Workers 上运行,无需 nodejs_compat。
import { Hono } from "hono";
import { cleanTheme, flowToPdf, standardFonts, text } from "@boxpdf/writer";
const app = new Hono();
app.get("/receipt.pdf", async (c) => {
const bytes = await flowToPdf(async (pdf) => {
const t = cleanTheme(await standardFonts(pdf));
return [
text("Thanks!", t.type.h1),
text("This PDF was generated at the edge.", t.type.body)
];
});
return new Response(bytes, { headers: { "content-type": "application/pdf" } });
});
export default app;
示例
examples/ 中可运行的脚本:
receipt.ts。带合计金额的单页收据。itinerary.ts。双栏旅行行程单。invoice.ts。多页发票,带连续页眉和页脚,以及keepTogether。debug.ts。使用{ debug: true }的布局。themes-showcase.ts。以全部四种主题渲染同一张收据。inter-showcase.ts。使用 Inter 渲染的简洁主题。flex-shrink.ts。并列展示三种 URL 溢出行为。hanging-indent.ts。段落使用paddingLeft和负值textIndent实现列表标记。overflow-clipping.ts。带绝对定位叠加元素和背景图片的裁剪卡片。
Flex-shrink
在 hstack 或 vstack 的任意子项上设置 shrink: number 即可选择启用。当子项在主轴上的固有尺寸之和超过父项的可用空间时,shrink > 0 的子项会按 shrink × baseSize 的比例让出空间。shrink = 0 的子项(默认值)保持不变。
hstack(
{ width: 360, gap: 16 },
text("Customer:", { size: 11, font: bold }),
text("Mr. Algernon Hephaestus Constantine Pemberton-Smythe III", {
size: 11, font, shrink: 1
})
)
行为:
- 文本子项的最小宽度等于其以空白分隔的最长单词。换行发生在空白处。
- 单个词元字符串(URL、哈希、slug)会保留其固有宽度,并明显溢出所在位置。启用以下两项中的任一项可降低此下限:
maxLines: N。引擎会用省略号处理溢出。文本会缩小至所在位置的宽度,并以…截断。breakWords: true。CSSoverflow-wrap: break-word。会在字符边界处强制换行。
- 缩小后的文本重新换行成更多行时,容器的固有高度会相应增加。
- 某个子项达到最小单词宽度下限时,其剩余的收缩权重会分配给其他子项。
- 对
vstack同样有效,前提是父项的固定height小于子项高度之和。 link会传递其子项的收缩权重,因此链接文本会像普通文本一样缩小并重新换行。
请参见 examples/flex-shrink.ts。
绝对定位
方框可使用一种类似 CSS 的简易定位模型:
vstack(
{ width: 240, height: 120, position: "relative", padding: 16 },
text("Receipt", { size: 18, font: bold }),
hstack(
{ position: "absolute", top: 12, right: 12, width: 70 },
text("PAID", { size: 14, font: bold, align: "center", width: 70 })
)
)
行为:
- 任何已定位的方框都会为其绝对定位的后代方框建立包含块。
position: "absolute"会将vstack或hstack从正常堆叠流中移除。- 绝对定位的方框会在普通子项之后渲染,因此可用于印章、徽章、叠加层和水印。
top、right、bottom和left是相对于最近的已定位祖先的点偏移量;若不存在,则相对于当前render()根节点。- 如果同时设置了
left和right,但省略了width,方框会拉伸至剩余宽度。top和bottom同理,会拉伸至剩余高度。 - 绝对定位的同级方框按
zIndex从低到高渲染。zIndex相同的方框按文档顺序渲染。 - 父项测量、间距、弹性增长/收缩和分页均会忽略绝对定位的方框。如需稳定定位,请为包含方框设置固定的
width和height。
限制
- 定位支持相对定位的包含方框、脱离文档流的绝对定位方框、点偏移、
zIndex,以及通过设置一对相对边进行拉伸。 - 字体整形遵循 pdf-lib 和 fontkit 的支持范围。复杂的印度语系、阿拉伯语和泰语脚本需要基于 HarfBuzz 的技术栈;目前可用的 HarfBuzz 技术栈面向 Cloudflare Workers 以外的运行时。
streamFlow支持增量生成。PDF 线性化(重新排列字节流,使第 1 个字节对应第 1 页)仍需单独进行后处理。
许可证
MIT © Erik Aronesty
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。