gPdf API 接口规范
版本: Unified API / Current Engine 更新: 2026-03-24
说明: 本文描述当前正式公开 schema。唯一 canonical 路由为
POST /api/v1/render,prod/test仅通过环境与 token 区分。
1. 接口概览
| 路由 | 鉴权方式 | 用途 |
|---|---|---|
POST /api/v1/render | Authorization: Bearer <token> | 唯一公开请求入口 |
Content-Type:application/json- 成功响应:
application/pdf(二进制流) - 默认输出模式:
binary binary与file都返回 PDF 二进制;差异只在Content-Disposition
2. 请求结构
{
"settings": {
"defaults": {
"text": {
"font_family": "NotoSans-Regular",
"font_size": 11,
"color": "#111111"
},
"stroke": {
"color": "#000000",
"width": 0.4
},
"fill": {
"color": "#FFFFFF",
"opacity": 1.0
},
"shape": {
"corner_radius": 0
}
},
"metadata": {
"title": "gPdf Document",
"author": "gPdf"
},
"output": {
"mode": "file",
"file_name": "invoice-20260310.pdf"
},
"profile": "pdfa-2b"
},
"layers": {
"background": {
"elements": []
},
"watermark": {
"elements": []
}
},
"header": {
"height": 12,
"elements": []
},
"footer": {
"height": 10,
"elements": []
},
"pages": [
{
"size": "label_100_150",
"elements": []
}
]
}3. 顶层对象
3.1 DocumentRequest
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
settings | Settings | 否 | 全局设置 |
layers | Layers | 否 | 全局页面装饰层 |
pages | Page[] | 是 | 页面数组 |
header | Section | 否 | 全局页眉 |
footer | Section | 否 | 全局页脚 |
3.1.1 Page 尺寸
每个页面必须显式指定尺寸,支持两种写法,二选一:
sizewidth+height
规则:
size和width/height不能同时出现- 不写
size时,必须同时提供width与height size不区分大小写
3.1.2 Layers(页面装饰层)
layers 用于定义文档级重复装饰层,不参与正文分页,也不占用 header/footer 的布局高度。
第一版支持:
backgroundwatermarkstamp
结构:
background- 使用普通
elements[]
- 使用普通
watermark- 使用算法化 spec:
template + style + layout + opacity
- 使用算法化 spec:
stamp- 使用普通
elements[]
- 使用普通
background / stamp 结构:
{
"repeat": "all_pages",
"elements": []
}watermark 结构:
{
"repeat": "all_pages",
"opacity": 0.12,
"template": {
"type": "text",
"content": "PRIVATE COPY"
},
"style": {
"font_family": "NotoSans-Regular",
"font_size": 10.5,
"font_weight": "bold",
"color": "#B91C1C",
"width": 56,
"text_align": "center"
},
"layout": {
"preset": "diagonal_tile",
"angle": 330,
"gap_x": 18,
"gap_y": 16,
"offset_x": 8,
"offset_y": 10,
"stagger_x": 34
}
}规则:
repeat可选,默认all_pages- 支持值:
all_pagesfirst_pagelast_page
background在正文前渲染watermark/stamp在正文后渲染stamp是最上层,适合最后一页的盖章、审批标记、状态角标background.elements/stamp.elements仅允许轻量元素:textimagerectlinecircleellipsepolygonlink
background/stamp不允许:tablestack
- 第一版
watermark.template.type仅支持:text
watermark.layout.preset支持:centertilediagonal_tile
watermark.opacity为0..1watermark.layout.angle使用度数,支持任意角度
常见用法:
background- 适合整页底色、底纹、背景图
- 常见写法是在
background.elements里放一个整页rect或image
watermark- 适合大量重复的提示文字、品牌浅色文字、斜向平铺文案
- 只需要定义一个模板,系统会按
layout自动铺满页面
stamp- 适合最后一页盖章、审批通过标记、对账完成标记
- 常见写法是
repeat: "last_page",再组合circle + line + text
当前边界:
layers不参与正文分页,也不会占用header/footer高度- 其他元素的旋转能力仍以各自字段说明为准
watermark.layout.angle是专门给水印平铺使用的- 如果需要半透明图片盖章,优先使用带 alpha 的 PNG/SVG 资源
示例:背景 + 算法平铺水印 + 最后一页盖章
{
"layers": {
"background": {
"elements": [
{
"type": "rect",
"x": 0,
"y": 0,
"width": 215.9,
"height": 279.4,
"fill": { "color": "#FFFBEB" }
}
]
},
"watermark": {
"repeat": "all_pages",
"opacity": 0.12,
"template": {
"type": "text",
"content": "PRIVATE COPY"
},
"style": {
"font_family": "NotoSans-Regular",
"font_size": 10.5,
"font_weight": "bold",
"color": "#B91C1C",
"width": 56,
"text_align": "center"
},
"layout": {
"preset": "diagonal_tile",
"angle": 330,
"gap_x": 18,
"gap_y": 16,
"offset_x": 8,
"offset_y": 10,
"stagger_x": 34
}
},
"stamp": {
"repeat": "last_page",
"elements": [
{
"type": "circle",
"cx": 171,
"cy": 228,
"r": 18,
"stroke": { "color": "#B91C1C", "width": 0.9 }
},
{
"type": "text",
"x": 152,
"y": 220,
"content": "PAID",
"style": {
"font_size": 12,
"font_weight": "bold",
"color": "#B91C1C",
"width": 38,
"text_align": "center"
}
}
]
}
}
}支持的 size 预设:
a4=210 x 297 mma6=105 x 148 mmletter=215.9 x 279.4 mmlegal=215.9 x 355.6 mmlabel_100_100=100 x 100 mmlabel_100_150=100 x 150 mmlabel_4_6_in=101.6 x 152.4 mm
示例:
{
"pages": [
{ "size": "A4", "elements": [] },
{ "width": 100, "height": 150, "elements": [] }
]
}3.1.2 Page Margin / Content Box
可选配置:
settings.page_marginpages[].margin
示例:
{
"settings": {
"page_margin": { "top": 10, "right": 12, "bottom": 10, "left": 12 }
},
"pages": [
{
"size": "letter",
"margin": { "top": 8, "right": 10, "bottom": 12, "left": 10 },
"elements": []
}
]
}规则:
- 未配置
page_margin时,正文元素维持当前页面绝对坐标语义。 - 一旦配置
page_margin,pages[].elements的x/y改为相对content box左上角。 pages[].elements超出content box会直接校验报错,不做自动裁剪或纠偏。header/footer仍按页面坐标工作,不受page_margin约束。- 正文自动分页到后续页时,会从下一页
content box顶部继续排版。
3.2 Section(header / footer)
header 和 footer 共用同一个结构:
{
"height": 12,
"elements": [
{ "type": "text", "x": 5, "y": 8, "content": "Page Header" }
]
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
height | number | 是 | 区域高度,单位 mm |
elements | Element[] | 否 | 该区域内渲染的元素列表 |
语义说明:
header是全局页眉,会应用到每一页。footer是全局页脚,会应用到每一页。elements为空时,该区域本身仍然存在;不要依赖"空数组自动忽略区域"。
坐标与定位:
header.elements按页面绝对坐标渲染,通常约定写在y = 0 .. header.height区间内。footer.elements会自动整体下移到页面底部,偏移量为page.height - footer.height。 也就是说,footer内部元素通常写成相对页脚区域的坐标,例如y = 0 .. footer.height。
与正文的关系:
header不会自动把正文元素整体下移。正文元素仍按页面绝对坐标渲染。- 因此如果你使用页眉,建议正文元素自己避开页眉区域,例如从
y >= header.height开始排布。 footer.height会参与正文可用高度计算,正文分页/溢出时会避开页脚底部区域。- 配置
page_margin后,正文自动分页到后续页会从下一页content box顶部开始。
实践建议:
header.height/footer.height应与内部元素实际占用高度大体一致,不要随意写过大。- 如果只是普通固定区域装饰,推荐把页眉内容放在
header,页脚内容放在footer,不要手工复制到每页。 - 如果需要每页不同的顶部/底部内容,仍应放在
pages[].elements中单独控制,而不是使用全局header/footer。
3.3 Settings
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
defaults | Defaults | 否 | 全局默认样式 |
metadata | Metadata | 否 | PDF 元数据 |
output | OutputSettings | 否 | 响应输出模式与文件名 |
profile | string | 否 | PDF/A 配置 |
page_margin | PageMargin | 否 | 正文内容区页边距 |
profile 支持: pdfa-1b, pdfa-2b, pdfa-3b, pdfa-4, pdfa-2u, pdfa-3u, pdfa-ua1 等。
3.3.1 OutputSettings
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
mode | binary | file | 否 | 响应呈现方式,默认 binary |
file_name | string | 否 | 自定义文件名,binary 和 file 都可用 |
规则:
binary或不传:返回 PDF 二进制,并附带Content-Disposition: inline; filename=\"...\"。file:仍然返回 PDF 二进制,但使用Content-Disposition: attachment; filename=\"...\",用于下载。file_name不传:回退到默认文件名gPdf-MMDDHHmmssSSS.pdf。file_name会经过安全清洗,并自动补.pdf后缀。
3.4 Defaults
| 字段 | 类型 | 说明 |
|---|---|---|
text | TextStyle | 文本默认样式 |
stroke | StrokeStyle | 线条/描边默认样式 |
fill | FillStyle | 填充默认样式 |
shape | ShapeDefaults | 形状默认样式 |
ShapeDefaults:
| 字段 | 类型 | 说明 |
|---|---|---|
corner_radius | number | 矩形默认圆角半径(mm) |
说明:
settings.defaults只接受结构化写法:text / stroke / fill / shapesettings.defaults.text也属于字体作用域;如果这里显式设置font_family且未写font_mode,默认按strict处理
3.5 Metadata
| 字段 | 类型 | 说明 |
|---|---|---|
title | string | 标题 |
author | string | 作者 |
subject | string | 主题 |
creator | string | 创建工具 |
producer | string | 生成器 |
language | string | 语言代码 |
4. 元素类型
elements[] 中每项都必须带 type:
textbarcodelinerectcircleellipsepolygonlinkimagetablestack
通用字段:
- 大多数元素支持
z_index(默认0) - 大多数元素支持
comment(仅备注,不渲染) - 支持旋转的元素:
text支持任意整数角度image支持任意角度barcode及其附带文本当前只支持0/90/180/270rect、ellipse当前建议仍使用0/90/180/270
- 超链接支持两种模式:
- 元素挂载
link字段(text/barcode/line/rect/circle/ellipse/polygon/image) - 独立热点元素
type: "link"
- 元素挂载
4.1 x_anchor(横向锚定定位)
x_anchor 用于按参考边界自动计算元素的最终 x。
当前支持的元素:
textbarcoderectimagelink
不支持:
line/circle/ellipse/polygon/table/stack/block
规则:
x与x_anchor互斥,同时传入会直接报错- 不使用
x_anchor时,仍按原有x绝对定位 text使用x_anchor时必须提供宽度:- 纯文本 /
spans简写使用style.width - block text 使用
frame.width
- 纯文本 /
barcode / rect / image / link使用x_anchor时,使用元素自身widthtable_left / table_right只允许在stack -> block内使用
参考值:
page_leftpage_rightcontent_leftcontent_righttable_lefttable_right
计算规则:
- 左侧参考:
resolved_x = reference + offset - 右侧参考:
resolved_x = reference - offset - width
示例:
{
"type": "text",
"x_anchor": { "reference": "content_right", "offset": 8 },
"y": 12,
"content": "$1,235.85",
"style": {
"width": 24,
"text_align": "right"
}
}5. 样式对象
5.1 StrokeStyle
{
"color": "#111111",
"width": 0.5,
"opacity": 1.0,
"cap": "butt",
"join": "miter",
"miter_limit": 10,
"dash": {
"preset": "dashed",
"pattern": [3, 2],
"phase": 0
},
"compound": {
"kind": "double",
"gap": 0.3
}
}字段说明:
cap:butt/round/squarejoin:miter/round/beveldash.preset:solid/dashed/dotted/customdash.pattern仅在preset=custom时建议提供compound.kind: 当前仅支持doublecompound.gap: 双线两根线之间的净距(mm)
说明:
- 未设置
compound时按单线处理。 compound供line、table.grid、table.cell.borders统一复用。double + dash为非法组合。double + marker为非法组合。rect/circle/ellipse/polygon若传入compound会报不支持。
5.2 FillStyle
{
"color": "#F5F5F5",
"opacity": 1.0,
"rule": "nonzero"
}rule: nonzero / even_odd
5.3 MarkerStyle (Line)
{
"start": "none",
"end": "arrow",
"size": 2.5
}start/end: none / arrow / open_arrow / circle / bar
5.4 LinkSpec (超链接)
{
"target": { "type": "url", "url": "https://example.com" },
"alt": "open official site",
"padding": 1.0,
"border": { "color": "#1A202C", "width": 0.3 }
}LinkTarget:
- URL:
{ "type": "url", "url": "https://..." } - 页内跳转:
{ "type": "page", "page": 2, "x": 10, "y": 20 }
LinkBorderStyle:
color: hex 颜色width: 线宽(mm),0视为不绘制
约束:
- URL 仅支持
http://、https://、mailto:、tel: - URL 前后空白会被裁剪(trim)后再写入
page从1开始,且不能超过请求中显式pages数padding必须为有限数且>= 0border.width必须为有限数且>= 0border.color(若提供)必须是合法 hex 颜色- 任一
link字段不合法会直接返回 ValidationError(不会静默跳过)
6. Shape 元素
line/rect/circle/ellipse/polygon 均可选挂载 link: LinkSpec。
6.1 Line
{
"type": "line",
"x1": 4,
"y1": 99,
"x2": 96,
"y2": 99,
"stroke": {
"color": "#000000",
"width": 0.4,
"dash": { "preset": "solid" }
},
"link": {
"target": { "type": "url", "url": "https://example.com/spec" }
}
}stroke 省略时会走默认链路(见第 9 节)。
6.2 Rect
{
"type": "rect",
"x_anchor": { "reference": "content_right", "offset": 6 },
"y": 20,
"width": 60,
"height": 20,
"fill": { "color": "#FFFFFF" },
"stroke": { "color": "#222222", "width": 0.6 },
"corner_radius": 2
}6.3 Circle
{
"type": "circle",
"cx": 40,
"cy": 40,
"r": 12,
"fill": { "color": "#E6F4FF" },
"stroke": { "color": "#2B6CB0", "width": 0.5 }
}6.4 Ellipse
{
"type": "ellipse",
"cx": 70,
"cy": 40,
"rx": 16,
"ry": 10,
"rotation": 0,
"fill": { "color": "#FFF7E6" },
"stroke": { "color": "#C05621", "width": 0.5 }
}6.5 Polygon
{
"type": "polygon",
"points": [
{ "x": 20, "y": 80 },
{ "x": 35, "y": 60 },
{ "x": 50, "y": 80 },
{ "x": 40, "y": 95 }
],
"fill": { "color": "#F0FFF4" },
"stroke": { "color": "#2F855A", "width": 0.5 }
}6.6 Link(独立热点)
{
"type": "link",
"x_anchor": { "reference": "content_left", "offset": 10 },
"y": 10,
"width": 40,
"height": 8,
"target": { "type": "url", "url": "https://example.com" },
"alt": "Open website"
}7. 其他元素
7.1 Text
公开 text 元素支持 3 种输入形式:
- 纯文本简写:
"content": "Hello gPdf" spans富文本简写:"content": { "spans": [...] }- block text:
"content": { "blocks": [...] }
这 3 种写法在校验与渲染阶段会得到一致处理。
其中 block text 的表达能力最完整,适合复杂排版、变量、列表和分页控制。
必填字段:
ycontent- 且必须二选一提供:
xx_anchor
纯文本简写:
{
"type": "text",
"x": 18,
"y": 18,
"content": "Hello gPdf"
}spans 富文本简写:
{
"type": "text",
"x": 18,
"y": 30,
"content": {
"spans": [
{ "text": "Hello ", "style": { "font_weight": "bold" } },
{ "text": "gPdf", "style": { "color": "#2563EB" } }
]
},
"style": {
"font_family": "NotoSans-Regular",
"font_size": 11,
"width": 80
}
}block text:
{
"type": "text",
"x": 18,
"y": 42,
"rotation": 0,
"frame": {
"width": 120,
"overflow": "paginate"
},
"defaults": {
"run": {
"font_family": "NotoSans-Regular",
"font_size": 11,
"color": "#111111"
},
"paragraph": {
"align": "left",
"direction": "auto",
"line_height": 1.35
}
},
"content": {
"blocks": [
{
"type": "paragraph",
"inlines": [
{ "type": "text", "text": "Page " },
{ "type": "variable", "name": "page", "scope": "system" }
]
},
{ "type": "page_break" },
{
"type": "paragraph",
"inlines": [
{ "type": "text", "text": "Page " },
{ "type": "variable", "name": "page", "scope": "system" },
{ "type": "text", "text": " / " },
{ "type": "variable", "name": "total_pages", "scope": "system" }
]
}
]
}
}顶层字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type | "text" | 是 | 元素类型 |
y | number | 是 | 页面坐标,单位 mm |
x | number | 二选一 | 页面坐标,单位 mm |
x_anchor | HorizontalAnchor | 二选一 | 锚定定位 |
rotation | integer | 否 | 元素级旋转,单位为角度 |
z_index | number | 否 | 层级 |
comment | string | 否 | 备注 |
_if | string | 否 | 模板链路元字段;POST /api/v1/render 不会执行 _if 条件过滤 |
link | LinkSpec | 否 | 整个文本元素级链接 |
style | TextStyle | 否 | 纯文本 / spans 简写的样式 |
frame | BlockTextFrame | 否 | block text 文本框样式 |
defaults | BlockTextDefaults | 否 | block text 默认样式 |
content | string | { spans: TextSpan[] } | BlockTextContent | 是 | 文本内容,支持 3 种输入形式 |
顶层规则:
x与x_anchor互斥- 使用
x_anchor时必须提供宽度:- 纯文本 /
spans简写使用style.width - block text 使用
frame.width
- 纯文本 /
text.link与任意 inlinelink互斥rotation使用整数角度值,例如0、37、-15
7.1.1 BlockTextDefaults
{
"run": {},
"paragraph": {},
"frame": {}
}run: 行内默认样式paragraph: 段落默认样式frame: 文本框默认样式
7.1.2 BlockTextContent
{
"blocks": [
{
"type": "paragraph",
"inlines": [
{ "type": "text", "text": "Page " },
{ "type": "variable", "name": "page", "scope": "system" },
{ "type": "text", "text": " / " },
{ "type": "variable", "name": "total_pages", "scope": "system" }
]
}
]
}blocks 当前公开三种类型:
paragraphlistpage_break
page_break 是显式 block 节点。公开 block text contract 不再使用 \f、{page}、{total_pages}、 这类字符串控制语法。
7.1.3 Paragraph Block
{
"type": "paragraph",
"style": {
"align": "justify",
"direction": "auto",
"line_height": 1.35,
"space_after": 2.5,
"indent_first_line": 6
},
"inlines": [
{ "type": "text", "text": "页码:" },
{ "type": "variable", "name": "page", "scope": "system" }
]
}paragraph.style 支持:
align:left | center | right | justifydirection:auto | ltr | rtlline_heightspace_beforespace_afterindent_leftindent_rightindent_first_linehanging_indentkeep_togetherkeep_with_nextwidow_orphan_controltabs
7.1.4 List Block
{
"type": "list",
"list": {
"kind": "ordered",
"marker_gap": 2.5,
"item_spacing": 1.5,
"start_at": 1
},
"items": [
{
"blocks": [
{
"type": "paragraph",
"inlines": [{ "type": "text", "text": "第一条" }]
}
]
}
]
}list 当前只在 full_text_profile 中开放,不允许出现在:
section_text_profiletable_text_profilebarcode_text_profile
7.1.5 Inline Nodes
当前公开四种 inline 节点:
textvariableline_breaktab
text 示例:
{
"type": "text",
"text": "Hello",
"style": {
"font_weight": "bold",
"color": "#B91C1C"
},
"link": {
"target": { "type": "url", "url": "https://example.com" }
}
}variable 示例:
{
"type": "variable",
"name": "page",
"scope": "system"
}variable.scope 只允许:
systembindingcomputed
其中:
system在POST /api/v1/render中目前只公开支持page与total_pagesbinding的值来源于模板数据链路;如果你需要业务数据替换,请使用POST /api/v1/template-rendercomputed当前未作为公开能力开放
也就是说,直渲染接口当前只保证页码类系统变量。如果你需要业务数据替换或条件过滤,请使用模板接口。
7.1.6 InlineTextStyle
run 级样式支持:
font_familyfont_sizefont_weight:normal | medium | semibold | boldfont_style:normal | italicfont_mode:strict | prefercoloropacityletter_spacingscript:normal | superscript | subscriptbackgrounddecorationlink_style
规则:
- 不支持在 run 级直接写段落或 frame 字段
font_mode不允许脱离同层font_family单独出现font_mode = "auto"不是公开输入值;自动模式只来自“当前链路没有显式字体”strict覆盖失败返回API-002- 自动 /
prefer完整回退失败返回API-504
7.1.7 BlockTextFrame
frame 支持:
widthheightvertical_align:top | middle | bottomoverflow:visible | clip | ellipsis | paginateshrink_to_fitmin_font_sizepaddingborderbackgroundcolumnscolumn_gap
规则:
frame在table_text_profile与barcode_text_profile中不允许出现rotation != 0不允许与frame.overflow = "paginate"同时出现frame.height不允许与page_break同时出现frame.overflow = clip | ellipsis不允许与page_break同时出现frame.overflow = paginate不允许与shrink_to_fit = true同时出现section_text_profile不允许overflow = paginate或多栏布局
7.1.8 Feature Profiles
text 能力会按上下文收缩,profile 不需要在 JSON 中显式传入,而是由元素所在位置决定:
full_text_profile- 顶层普通
text - block:
paragraph | list | page_break - inline:
text | variable | line_break | tab
- 顶层普通
section_text_profileheader / footer / layers.background / layers.watermark / layers.stamp- block:
paragraph - inline:
text | variable | line_break | tab - 不允许
list - 不允许
page_break - 不允许
frame.overflow = paginate - 不允许多栏布局
table_text_profiletable文本- block:
paragraph - inline:
text | variable | line_break - 不允许
list - 不允许
page_break - 不允许
tab - 不允许 inline
link - 不允许
frame
barcode_text_profilebarcode_text- block:
paragraph - inline:
text | variable | line_break - 不允许
list - 不允许
page_break - 不允许
tab - 不允许 inline
link - 不允许
frame
7.2 Barcode
必填字段: y, format, content, width, height,并且必须二选一提供:
xx_anchor
可选: style, options, barcode_text, rotation, z_index, comment, link
说明:
rotation当前只支持0、90、180、270barcode_text会继承条码旋转,因此也只支持同一组角度format当前大小写不敏感,并将-与_视为等价分隔符。- 2D / 矩阵类条码以模块矩阵方式编码;1D / 线性类条码以条带方式编码;
maxicode使用六边形网格。 - 当前构建支持的
format值包括:- 2D / 矩阵类:
qrcode(qr)、microqr(micro-qr)、pdf417、micropdf417(micro-pdf417)、datamatrix(data-matrix)、gs1datamatrix(gs1-datamatrix,gs1_datamatrix)、aztec、maxicode、gs1qrcode(gs1-qrcode,gs1_qr,gs1-qr) - 1D / 线性类:
code128(code-128)、code128a(code-128a)、code128b(code-128b)、code128c(code-128c)、gs1128(gs1-128)、code39(code-39)、code93(code-93)、codabar、ean8(ean-8)、ean13(ean-13)、upca(upc-a)、upce(upc-e)、itf(interleaved2of5)、itf14(itf-14)、gtin8(gtin-8)、gtin12(gtin-12)、gtin13(gtin-13)、gtin14(gtin-14)、isbn(isbn-13)、sscc(sscc-18) - 其他业务类:
msi(msi-plessey)、msi10(msi-10)、msi11(msi-11)、msi1010(msi-1010)、msi1110(msi-1110)、upus10(s10)、uspsimb(usps-imb,intelligent-mail)、upcacomposite(upca-composite)、upcecomposite(upce-composite)
- 2D / 矩阵类:
7.3 Image
必填字段: y, width, height,并且必须二选一提供:
xx_anchor
可选: rotation, z_index, comment, link
图片来源支持两种互斥写法:
- 简写:顶层
asset,可选顶层format - 显式:顶层
source
规则:
asset与source必须二选一- 顶层
format仅用于顶层asset简写 - 使用
source时,格式提示写在source内部 - 不支持
data:image/...;base64,...形式的 Data URI
source.kind 当前支持:
assetbase64
说明:
asset指服务托管的资源 key,不是客户端本地文件路径。source.kind = "base64"的payload仅接受原始 Base64 内容,不带data:image/...;base64,前缀。rotation使用角度值,图片支持任意角度,例如45或-30。
资源简写:
{
"type": "image",
"x": 4,
"y": 8,
"width": 15,
"height": 6.5,
"asset": "pdn_express_logo",
"format": "jpg"
}显式资源写法:
{
"type": "image",
"x": 4,
"y": 8,
"width": 15,
"height": 6.5,
"source": {
"kind": "asset",
"key": "pdn_express_logo",
"format": "jpg"
}
}显式 Base64 写法:
{
"type": "image",
"x": 4,
"y": 8,
"width": 15,
"height": 6.5,
"source": {
"kind": "base64",
"format": "jpg",
"payload": "/9j/4AAQSkZJRgABAQ..."
}
}7.3.1 核心元素样式 JSON 示例
带样式 text:
{
"type": "text",
"x": 18,
"y": 18,
"content": "Invoice #INV-2026-001",
"style": {
"font_family": "NotoSans-Regular",
"font_mode": "prefer",
"font_size": 12,
"font_weight": "bold",
"color": "#111827",
"width": 90,
"text_align": "left",
"line_height": 1.25,
"letter_spacing": 0.2
}
}带样式 barcode:
{
"type": "barcode",
"x": 18,
"y": 34,
"format": "qrcode",
"content": "https://example.com/track/INV-2026-001",
"width": 28,
"height": 28,
"style": {
"color": "#111111",
"background_color": "#FFFFFF"
},
"barcode_text": {
"enabled": true,
"position": "bottom",
"offset": 1.5,
"style": {
"font_family": "NotoSans-Regular",
"font_mode": "prefer",
"font_size": 8,
"color": "#374151",
"width": 28,
"text_align": "center"
}
}
}带样式 rect:
{
"type": "rect",
"x": 12,
"y": 70,
"width": 80,
"height": 18,
"corner_radius": 2,
"fill": {
"color": "#F9FAFB",
"opacity": 1
},
"stroke": {
"color": "#D1D5DB",
"width": 0.4
}
}带样式 image:
{
"type": "image",
"x": 14,
"y": 92,
"width": 18,
"height": 9,
"rotation": -8,
"asset": "pdn_express_logo",
"format": "jpg",
"link": {
"target": {
"type": "url",
"url": "https://example.com"
}
}
}带样式 header / footer:
{
"header": {
"height": 14,
"elements": [
{
"type": "text",
"x": 12,
"y": 8,
"content": "Monthly Report",
"style": {
"font_family": "NotoSans-Regular",
"font_mode": "prefer",
"font_size": 10,
"font_weight": "bold",
"color": "#111827",
"width": 80
}
}
]
},
"footer": {
"height": 12,
"elements": [
{
"type": "text",
"x": 150,
"y": 6,
"content": "Page 1 / 12",
"style": {
"font_family": "NotoSans-Regular",
"font_mode": "prefer",
"font_size": 8,
"color": "#6B7280",
"width": 40,
"text_align": "right"
}
}
]
}
}7.4 Table
说明:
- 当前公开
tableschema 仅支持本节定义的结构。
顶层结构:
{
"type": "table",
"x": 12,
"y": 24,
"width": 180,
"columns": [],
"rows": [],
"cell": {},
"header": {},
"row_header": {},
"body": {},
"grid": {},
"pagination": {}
}顶层字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
x | number | 是 | 左上角 X(mm) |
y | number | 是 | 左上角 Y(mm) |
z_index | integer | 否 | 层级 |
comment | string | 否 | 备注 |
width | number | 否 | 表格总宽度 |
columns | TableColumn[] | 是 | 列定义 |
rows | TableRow[] | 是 | 行数据 |
cell | TableCellStyle | 否 | 全表默认单元格样式 |
header | TableHeaderConfig | 否 | 列表头配置 |
row_header | TableZoneConfig | 否 | 行表头区配置 |
body | TableBodyConfig | 否 | 正文区配置 |
grid | TableGridConfig | 否 | 网格线配置 |
pagination | TablePaginationConfig | 否 | 分页配置 |
7.4.1 Column
{
"key": "amount",
"header": "Amount",
"width": { "mode": "fixed", "value": 30 },
"role": "data",
"cell": {
"text": { "text_align": "right" }
},
"header_cell": {
"text": { "font_weight": "bold" }
}
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
key | string | 是 | 列 key,必须唯一 |
header | string | 否 | 叶子列表头文本,默认空字符串 |
width | TableColumnWidth | 是 | 列宽模型 |
role | string | 否 | data / row_header,默认 data |
cell | TableCellStyle | 否 | 该列正文单元格样式 |
header_cell | TableCellStyle | 否 | 该列列表头单元格样式 |
规则:
columns[].key必须唯一。role = "row_header"的列必须连续放在最左侧。- 支持多个行表头列。
TableColumnWidth:
{ "mode": "fixed", "value": 30 }
{ "mode": "percent", "value": 25 }
{ "mode": "auto" }7.4.2 Row / Cell
rows 为对象数组,key 对应 columns[].key。
简写单元格:
{ "name": "Apple", "qty": 2, "enabled": true, "note": null }复杂单元格:
{
"group": {
"content": "Fruit",
"row_span": 2,
"col_span": 1,
"style": {
"text": { "font_weight": "bold" }
},
"link": {
"target": { "type": "url", "url": "https://example.com" }
}
}
}复杂单元格字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
content | string | number | boolean | null | BlockTextContent | 否 | 单元格内容;可为标量或富文本 |
row_span | integer | 否 | 向下合并行数 |
col_span | integer | 否 | 向右合并列数 |
style | TableCellStyle | 否 | 单元格样式覆盖 |
link | LinkSpec | 否 | 单元格超链接 |
规则:
row_span >= 1col_span >= 1- span 不能越界,也不能和其他 span 重叠
null渲染为空字符串boolean渲染为"true"/"false"BlockTextContent按table_text_profile处理link仅能出现在复杂单元格对象中
7.4.3 TableCellStyle
{
"padding": { "x": 1, "y": 1 },
"text": { "font_size": 9, "color": "#111111" },
"fill": { "color": "#FFFFFF" },
"content_offset_x": 1.5,
"content_offset_y": 0.5,
"borders": {
"top": false,
"right": { "color": "#111111", "width": 0.2 },
"bottom": { "color": "#111111", "width": 0.2 },
"left": false
}
}| 字段 | 类型 | 说明 |
|---|---|---|
padding | TablePadding | 单元格内边距 |
text | TextStyle | 文本样式 |
fill | FillStyle | 填充样式 |
content_offset_x | number | 单元格内容横向微调(mm) |
content_offset_y | number | 单元格内容纵向微调(mm) |
borders | TableBorders | 单元格单边边框 |
TablePadding:
| 字段 | 类型 | 说明 |
|---|---|---|
x | number | 水平内边距(mm) |
y | number | 垂直内边距(mm) |
TableBorders:
top?: false | StrokeStyleright?: false | StrokeStylebottom?: false | StrokeStyleleft?: false | StrokeStylediagonal_tl_br?: false | StrokeStylediagonal_bl_tr?: false | StrokeStyle
7.4.4 Header / Row Header / Body
header:
{
"show": true,
"repeat_on_page_break": true,
"rows": [
{
"cells": [
{ "content": "Product", "col_span": 2 },
{ "content": "Stock", "row_span": 2 }
]
}
],
"cell": {
"fill": { "color": "#F3F4F6" },
"text": { "font_weight": "bold" }
}
}字段:
show?: boolean,默认truerepeat_on_page_break?: boolean,默认truerows?: TableHeaderRow[],分组表头行;叶子表头仍来自columns[].headercell?: TableCellStyle
row_header:
{
"cell": {
"fill": { "color": "#F8F8F8" },
"text": { "font_weight": "bold" }
}
}字段:
cell?: TableCellStyle
body:
{
"cell": {},
"alternate_fill": { "color": "#FAFAFA" }
}字段:
cell?: TableCellStylealternate_fill?: FillStyle
表达规则:
- 分组表头使用
header.rows - 叶子列表头使用
columns[].header - 行表头使用
columns[].role = "row_header" header.rows[].cells[].content与columns[].header都支持string | number | boolean | null | BlockTextContent
7.4.5 Grid
{
"top": {
"color": "#111111",
"width": 0.3,
"compound": { "kind": "double", "gap": 0.3 }
},
"right": {
"color": "#111111",
"width": 0.3,
"compound": { "kind": "double", "gap": 0.3 }
},
"bottom": {
"color": "#111111",
"width": 0.3,
"compound": { "kind": "double", "gap": 0.3 }
},
"left": {
"color": "#111111",
"width": 0.3,
"compound": { "kind": "double", "gap": 0.3 }
},
"horizontal": { "color": "#D1D5DB", "width": 0.2 },
"vertical": false
}字段:
top?: false | StrokeStyleright?: false | StrokeStylebottom?: false | StrokeStyleleft?: false | StrokeStylehorizontal?: false | StrokeStylevertical?: false | StrokeStyle
说明:
grid.top/right/bottom/left/horizontal/vertical统一使用StrokeStyle
7.4.6 Pagination
{
"keep_spans_together": true,
"row_min_height": 10,
"header_min_height": 12
}字段:
keep_spans_together?: boolean,默认truerow_min_height?: numberheader_min_height?: number
说明:
- 当存在
row_span时,要求keep_spans_together = true
7.4.7 宽度与样式规则
宽度规则:
columns.length >= 1columns[].width统一使用fixed / percent / auto- 若提供
table.width:fixed先按 mm 占用percent按table.width百分比分配auto吃剩余空间,并基于表头/正文内容测量分配- 若不存在
auto,列宽求解结果必须精确填满table.width
- 若未提供
table.width:- 所有列都必须使用
fixed
- 所有列都必须使用
percent总和不得超过100rows中出现未在columns[].key定义的字段会报错header.show = false时,header.rows、columns[].header与header_cell视为忽略- 当使用
row_span时,不支持keep_spans_together = false
样式优先级:
settings.defaultstable.cellheader.cell / row_header.cell / body.cellcolumns[].cell / columns[].header_cellcell.style
边框优先级:
gridtable.cell.borders- 区域级
cell.borders - 列级
cell/header_cell.borders cell.style.borders
7.5 Stack / Block
用途:
stack用于"表格结束后再跟随一组内容"的发票/对账单场景。- 它不接管 table 的原有定位;
table.x/y/width仍按当前语义工作。 stack只解决table与后续内容块之间的顺序排版和分页问题。
结构:
{
"type": "stack",
"gap": 6,
"children": [
{
"type": "table",
"x": 18,
"y": 123,
"width": 180,
"columns": [],
"rows": []
},
{
"type": "block",
"elements": [
{ "type": "text", "x": 128, "y": 0, "content": "Subtotal" },
{ "type": "text", "x": 168, "y": 0, "content": "$1,343.65" },
{ "type": "line", "x1": 128, "y1": 7, "x2": 178, "y2": 7 }
]
}
]
}顶层字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
gap | number | 否 | 前后 child 之间的垂直间距(mm),默认 0 |
children | StackChild[] | 是 | 顺序排版的子项 |
规则:
stack仅允许出现在pages[].elementsstack.children[0]必须是tablestack.children[1..]只能是blockchildren.length >= 2
block:
{
"type": "block",
"elements": [
{ "type": "text", "x": 128, "y": 0, "content": "Subtotal" },
{ "type": "text", "x": 168, "y": 0, "content": "$1,343.65" }
]
}规则:
block不提供自己的x/y/width/heightblock.elements[].x继续使用现有 body 元素语义block.elements[].y为相对block起点的偏移block内不允许再嵌套table/stack/blockblock高度由系统根据内部元素自动测量
分页语义:
table先按现有逻辑分页- 只有在
table真正结束后,后续block才开始排版 - 若某个
block当前页放不下,则整块移到下一页 - 若某个
block自身高度已超过一页可用高度,则直接报错 gap表示前一个 child 最终结束位置到下一个 child 起始位置的垂直距离- 当
block被整体移到下一页时,不保留上一页的gap
8. 坐标与单位
- 坐标单位: mm
- 原点: 左上角
(0, 0) - X 轴向右, Y 轴向下
9. 默认值优先级
9.1 样式优先级
- 元素本身字段(例如
line.stroke.width) settings.defaults对应字段(如defaults.stroke.width)- 系统默认配置
table 的优先级为:
settings.defaultstable.cellheader.cell / row_header.cell / body.cellcolumns[].cell / columns[].header_cellcell.style
9.2 线条/形状默认行为
line.stroke全省略时:color默认#000000width默认0.4
rect/circle/ellipse/polygon.stroke全省略时:- 不绘制边框
- 若显式提供
stroke,缺失字段会继续按公开默认链路补齐
fill全省略时默认不填充(透明)rect.corner_radius默认链路:element.corner_radiussettings.defaults.shape.corner_radius- 系统默认值
0
10. 错误码
| 错误码 | HTTP | 触发条件 |
|---|---|---|
| API-001 | 400 | JSON 非法 |
| API-002 | 400 | 请求校验失败 |
| API-003 | 400 | PDF/A profile 非法或不支持 |
| API-004 | 400 | 页数超限 |
| API-005 | 400 | 页面尺寸或边距非法 |
| API-006 | 400 | 链接参数非法 |
| API-007 | 400 | 图片 payload 超限 |
| API-008 | 400 | 请求体过大 |
| API-009 | 400 | 文本 form feed 使用受限 |
| API-010 | 400 | Stack 布局参数错误 |
| API-101 | 401 | Authorization 缺失或格式错误 |
| API-102 | 403 | 鉴权失败 |
| API-103 | 403 | 鉴权失败(token 已列入黑名单) |
| API-501 | 500 | PDF 渲染失败 |
| API-502 | 500 | PDF/A 合规校验失败 |
| API-503 | 500 | 布局溢出错误 |
| API-504 | 500 | 资源加载失败 |
| API-505 | 500 | 字体解析失败 |
| API-506 | 500 | 图片未找到 |
| API-507 | 500 | 分页内部错误 |
| API-900 | 500 | 系统内部错误 |
| API-901 | 500 | 系统内部错误 |
| API-902 | 500 | 系统内部错误 |
| API-999 | 500 | 未知内部错误 |
脱敏规则:
API-102、API-103以及系统错误(API-900/API-901/API-902/API-999)对外返回脱敏后的公开文案。- 输入错误与渲染错误(
API-501~API-507)会保留可用于定位问题的细节。
常见 ValidationError 触发:
link非法(不支持的 URL scheme、页码越界、非法padding/border)table非法(未知列 key、table.width无法为未声明列分配正宽度、非法 span)profile非法font_mode脱离同层font_family单独出现- 显式
strict字体无法覆盖提交文本 x与x_anchor同时出现
边界说明:
API-008当前默认是16 MiB;不同服务部署可以配置不同上限API-004不是固定常量:实际生效值可能同时受服务配置与当前 token 有效策略约束API-007按策略生效:仅当当前请求命中的有效策略启用了图片大小上限时才会触发