mcpField
B
boxpdf

boxpdf

本地服务
earonesty.github.io开发工具195 周前更新

关于 boxpdf

boxpdf 是基于 pdf-lib 的 TypeScript PDF 布局库,使用声明式布局组件生成分页 PDF,并支持表格、主题和表单控件。它面向需要在应用中排版 PDF 或使用模板的开发者,也附带供 Agent 使用的资源型 MCP 服务。

主要功能

  1. 01声明式堆栈与文本布局
  2. 02多页排版及表格分页
  3. 03主题与 PDF 表单控件
  4. 04流式 PDF 生成
  5. 05CLI 模板脚手架

适用场景

  1. 01生成收据、简历或证书 PDF
  2. 02排版包含表格和页眉页脚的文档
  3. 03创建带可填写表单控件的 PDF
  4. 04从模板快速搭建 PDF 文件

MCP 相关

CLI 附带一个仅提供资源的 MCP 服务,供 Agent 访问文档和模板。README 未说明该服务暴露的具体资源内容。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"earonesty-boxpdf": {
"command": "npx",
"args": [
"-y",
"boxpdf"
]
}
}
}
启动命令npx -y boxpdf

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npmboxpdf1.0.3

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

@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 / heightnumber固定尺寸;否则根据内容确定尺寸。
padding / marginnumber | { top, right, bottom, left }简写形式或分别设置各侧。
backgroundRGB纯色填充。
backgroundImage{ image, width, height, offsetX?, offsetY?, repeat? }绘制在子项后方的图像,并裁剪至容器框内。
border{ color, width }容器框周围宽度为 1pt 或更大的描边。
borderSides{ top?, right?, bottom?, left? }使用 { color, width } 分别设置各侧描边。
borderRadiusnumber圆角半径。
overflow"visible" | "hidden"将堆栈子项和绝对定位后代裁剪至容器框矩形内。
position"relative" | "absolute"类似 CSS 的容器框定位方式。
top / right / bottom / leftnumber绝对偏移量,单位为 points。
zIndexnumber定位容器框的绘制顺序;值越大,绘制越靠后。
rotatenumber绕容器框中心按顺时针方向旋转绘制,单位为度;布局不变。
transformBoxTransform[]按顺序应用的绘制变换:translate、scale、rotate、skew 和 matrix。
transformOrigin{ x, y }使用 { length, percent } 组件设置旋转中心;默认为容器框中心。
grownumber沿父级主轴的弹性增长权重。
shrinknumber弹性收缩权重。
breakInside"auto" | "avoid"renderFlow 下的分片提示;avoid 会将容器框保持为一个整体。
gapnumber子项之间的间距。
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 会报错,而不会悄然生成不完整的布局。

使用约定
  1. 所有 embedFont / embedJpg / embedPng 调用都必须在 streamFlow 开始前完成。在流式处理中途嵌入会抛出异常。
  2. 可迭代对象会一次消耗一个节点。请传入生成器。
  3. streamFlow 会独占可写流的所有权:成功时关闭,失败时中止。
  4. 流式输出的页眉和页脚会接收 ctx.pageNumber。如果页眉或页脚要显示“第 X 页,共 Y 页”,请使用 renderFlow;在流式处理中访问 ctx.totalPages 会抛出异常。
  5. 输出大小比 renderFlow 默认的 save() 大 0–5%。
内存基准测试

渲染期间的峰值堆内存。每项测量都在独立子进程中运行。每页包含 50 行文本。包含 @react-pdf/renderer 以便比较形态。

页数streamFlow 峰值renderFlow 峰值@react-pdf 峰值输出
5012.8 MB31.7 MB160.8 MB70 KB
25015.4 MB91.1 MB643.1 MB347 KB
50018.7 MB120.8 MB1,219.9 MB693 KB
100025.4 MB219.6 MB2,292.6 MB1.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 后保留的堆内存输出
10081~58 MB~16 MB129 KB
1000810~125 MB~26 MB1.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。CSS overflow-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

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。