博客

PDF 元数据字段详解:title、language、author、subject、creator、producer

逐字段讲解 gPdf 暴露的六个标准 PDF 元数据字段——title、language、author、subject、creator、producer。每个字段的用途、读者、常见错误,以及如何验证最终输出。

这篇是 PDF 文档属性应显示自己的品牌,而不是别人家的工具名 的姊妹篇——那一篇讲了为什么要在意 PDF 元数据,这一篇是操作手册:每个字段在 PDF 规范里的角色、谁会读、常见错误,以及如何验证输出里确实写进了你想写的内容。

gPdf 暴露的是 PDF 规范为文档级元数据定义的六个标准字段,放在 DocumentRequest JSON 的 settings.metadata 之下。每个字段都是可选的——没设的话,gPdf 会回退到令牌的 default_metadata(企业策略功能)或系统默认值。

{
  "settings": {
    "metadata": {
      "title":    "...",
      "language": "...",
      "author":   "...",
      "subject":  "...",
      "creator":  "...",
      "producer": "..."
    }
  }
}

接下来每一节讲一个字段,形式统一:字段是什么在哪里露面常见错误经验法则。顺序就是先填什么、再填什么……最后填什么。

title — 文档是什么

PDF 规范将其描述为“文档标题“。

会在哪里露面:

  • PDF 阅读器的标题栏(Adobe Reader、Preview、Foxit、Chromium PDF 阅读器都会显示)。
  • 当 PDF 内嵌打开时(Content-Disposition: inline)的浏览器标签。
  • 搜索索引——Spotlight、Outlook、SharePoint、Google Drive 的全文索引器都会读 title 并给予较高权重。

常见错误:

  • 把 title 设成文件名。invoice-20260318.pdf 是文件名。title 应该是给人读的,比如 Invoice INV-2026-3401。文件名和标题是两件事:文件名给文件系统看,标题给阅读器和搜索看。
  • ❌ **title 留空。**阅读器会回退到文件名,呈现出来一看就是机器自动生成的。
  • 把品牌塞进 title。Acme Logistics — Invoice INV-2026-3401 把标题栏挤得乱糟糟的。品牌归 author,不归 title

**经验法则:**title 应该和页面渲染出来的 H1 一致。如果发票模板顶部那行写着“Invoice INV-2026-3401“,那就是 title。

language — 为了无障碍、搜索和合规

language 是 BCP-47 语言标签:endezh-Hanspt-BRar-SA。每一份文档都要设。在这六个字段里,它的下游后果最具体、实现成本最低——这就是它排在第 2 位而不是被埋到后面的原因。

会在哪里露面:

  • 屏幕阅读器——JAWS、NVDA、VoiceOver 会用它选合适的音素集。一个英文屏幕阅读器在读 language: "de" 的 PDF 时会正确发音德语词;不带标签的话,韵律就乱了。
  • 搜索引擎和索引器——决定用哪种语言的词干提取和停用词表。language: "zh-Hans" 的发票会用中文分词建索引;缺失标签时通常会默认按英文处理,索引就用不了了。
  • PDF/A 合规——PDF/A-2a 和 PDF/A-3a(无障碍配置)要求带语言标签。没有的话,veraPDF 校验直接失败。

常见错误:

  • ❌ **不设。**默认应该是“收件方所在的语言“,而不是“平台的默认语言“。大多数会漏的栈干脆就不写这个字段;结果是屏幕阅读器发音不对、搜索索引提取词干错位。
  • 用非 BCP-47 的字符串,比如 "english""EN-US"。PDF 规范要求 RFC 5646 标签:enen-USdept-BR
  • 不管文档实际内容是什么语言,都硬编码平台默认值(比如永远写 "en")。一份葡萄牙语发票被打上 "en" 的标签,比不打标签还糟——这是在主动误导索引器。

**经验法则:**标签应该匹配文档实际的内容语言。给巴西客户用葡萄牙语开发票,就设 "language": "pt-BR",不是 "en"。多语言文档则挑出主导语言,其余部分对单个内容元素用 Lang 属性——这是超出文档级 language 字段范畴的、属于带标签 PDF 的无障碍特性。

author — 文档归谁所有

PDF 规范里,author 是“创建文档的个人或组织名称“。对于发给收件方的业务 PDF,答案几乎永远是组织——不过具体形态会因场景而真有差别。

会在哪里露面:

  • 每一款 PDF 阅读器的属性对话框,显眼地标着“Author“。
  • DMS / 归档索引器,经常用作筛选条件。
  • PDF/A 的 XMP 元数据流,会随之进入长期归档。

常见错误:

  • "author": "[email protected]" —— 顺手把操作人员的邮箱漏到每份 PDF 里,进了每个搜索索引,变成长期 PII 隐患。
  • "author": "PDF Generator Service" —— 内部工具名;对收件方毫无意义。
  • ❌ 留空 —— Preview 和大多数阅读器会在属性对话框里赤裸裸地显示“(no author)“,读起来就是“没人对这份文档负责”。

有效写法:

  • "author": "Acme Logistics, Inc." —— 直白的组织名。
  • "author": "Acme Logistics — Billing" —— 组织 + 部门,适合送到某个具体接收岗的文档。
  • "author": "Bridge Capital Partners — Fund III" —— 在金融 / 法律领域很有用,这些地方的署名通常是某个具体实体。
  • "author": "Maria López, RICS Surveyor" —— 单一作者出版(报告、估值、法律意见)时,个人本身就是编辑署名。

**经验法则:**author 是收件方应该把文档和谁挂钩的那个实体。在多租户 SaaS 中,平台代客户生成 PDF 时,author 应该是客户的组织名,而不是平台的名称。(平台的名称归 creator——见下文。)对于咨询、出版、法律等场景,个人本身就是品牌,那写个人也无妨。

subject — 这份文档是哪类

subject 是文档的简短描述。阅读器不会把它放在显眼位置——除非打开属性对话框,大多数用户根本看不到。但文档管理系统、归档系统,以及基于规则的邮件 / 文件路由,都会用它。

会在哪里露面:

  • 属性对话框的次要位置。
  • DMS 路由规则、归档分桶逻辑。
  • XMP 元数据流(PDF/A)。

常见错误:

  • "subject": "Invoice for Acme on 2026-03-18 for $4,532.10" —— 这是某一份文档实例的描述,不是分类。这种内容归 title
  • ❌ 留空 —— 白白浪费了下游系统的一个免费路由钩子。
  • ❌ 类别混用不一致("Invoice" vs "Invoice/2026-03" vs "Monthly invoice")—— DMS 过滤器在变动的目标上根本没法分桶。

有效写法:

  • "subject": "Invoice"
  • "subject": "Monthly account statement"
  • "subject": "Shipping label — 4×6 thermal"
  • "subject": "Q3 2026 board pack"

经验法则:合适的粒度是文档类别,而不是文档实例。处理着每天上千份新进 PDF 的 DMS,只要给它一套稳定的词汇表,就能依据 subject 做路由。给平台挑一组有限的类别,一以贯之——平台生成的每一份发票都应严格写 "subject": "Invoice"

creatorproducer —— 最容易搞混的一对

到了这里,大多数团队就停下不读 PDF 规范开始猜了。规范说得很清楚:这两个字段含义不同。

  • creator —— 生成源内容的应用(决定文档应该写什么的上游系统)。
  • producer —— 生成 PDF 字节流的应用(把内容变成 PDF 文件的渲染引擎)。

对于通过 gPdf 这种 JSON 转 PDF API 生成发票的 SaaS 计费平台:

  • creator = 带版本号的 SaaS 计费平台。正是这个应用决定了这应该是一份给 Acme 的、金额 4,532.10 美元的发票。
  • producer = 渲染器。默认是 “gPdf”。但渲染层是 SaaS 自己选的基础设施,SaaS 完全可以把 producer 设成自家的平台名——它的平台在某种实质意义上确实通过把活儿派给作为基础设施的 gPdf 而生成了 PDF 字节。
{
  "creator":  "Acme Billing Platform v7.2",
  "producer": "Acme Billing Platform"
}

会在哪里露面:

  • 属性对话框,两个字段都有标签。
  • pdfinfo 输出里并排显示。
  • PDF/A 的 XMP 流(在 PDF/A 中这两个字段都必须非空)。

常见错误:

  • ❌ **creator 设成 Chromium / Mozilla 的 User-Agent 字符串。**当 headless 浏览器 PDF 栈自动把 User-Agent 塞进 creator 时就会出现。那是浏览器版本,不是真理源系统。请覆盖掉。
  • ❌ **producer 任其保留为默认渲染器名。**大多数团队从不覆盖,所以每份 PDF 都写着 “Skia/PDF m120” 或 “wkhtmltopdf” —— 关于这件事对 B2B 为什么要紧,参见 白标那篇
  • ❌ **两个字段填一样的值。**可以,但浪费——之所以设两个字段,正是为了让阅读器区分“源应用“和“渲染引擎“。请用上。

经验法则:creator 是你的应用名带版本号(例如 "Acme Billing Platform v7.2");producer 是你应用的品牌或平台名不带版本号(例如 "Acme Billing Platform")。两者都应该是收件方能认得出的值。

空字段、令牌级默认值、下游意外

发上线前,有三个实现细节值得知道:

  1. **空字符串或仅含空白的字符串等同于未提供。**发 "title": "" 和省略 title 等价——不会把空串写进 PDF,而是走回退链(令牌默认值 → 系统默认值)。这是“我明明设了,却没生效“类报障最常见的原因。
  2. **令牌策略可以剥离或为元数据字段设默认值。**使用 gPdf 的多租户 SaaS 可以为每个 API 令牌设置 default_metadata,这样该令牌生成的每份 PDF 都会带上客户的 authorproducer,而无需信任每个开发者在每次请求里都设上。“Acme 的每份 PDF 都必须写 Acme“这种约束,令牌级默认值正是合适的强制点。
  3. **下游流水线可能重写你的元数据。**gPdf 返回之后再做后处理的工具——没显式开启元数据保留选项的 Ghostscript、某些企业 DRM 工具、某些“PDF 优化器“——会用自己的名字覆盖 Producer,把你刚设好的品牌化元数据一笔抹掉。请对照你真实的生产流水线验证,而不仅仅是看 gPdf 的原始响应。

验证元数据

按上面做完后,有三种快速方式可以检查 PDF 是否真的按你想要的内容交付了:

命令行(macOS / Linux,需要 poppler-utils):

$ pdfinfo your-output.pdf | head -10
Title:           Invoice INV-2026-3401
Subject:         Monthly invoice 2026-03
Author:          Acme Logistics, Inc.
Creator:         Acme Billing Platform v7.2
Producer:        Acme Billing Platform
Language:        en

Acrobat / Adobe Reader:文件 → 属性 → 说明 标签页。六个字段都在,其中 Title 还会显示在阅读器最上方的标题栏里。

macOS Preview:⌘+I(显示简介)。“PDF” 检查器面板会列出同样的字段。

如果任何字段显示为空、空白、或者出现一个你没设过的工具名,回头排查请求体——最常见的原因是发了 ""(空字符串),API 把它当成“未提供“,走回退链取到了某个默认值。次常见的原因是下游流水线(Ghostscript、DRM、优化器)在 gPdf 返回之后覆盖了字段;请对生产环境测试,而不只是看原始渲染响应。

PDF/A 归档中的元数据

如果你为长期归档而渲染、用 settings.profile: "pdfa-2b"(或 -2a-3a-3b),元数据就不再是可选项,而是承重结构:

  • 在符合 PDF/A 的文件里,producer 字段不能为空——至少会落到系统默认值。
  • 无障碍配置(PDF/A-2a、PDF/A-3a)要求 language。没有的话,veraPDF 校验直接不通过。
  • PDF/A 要求的 XMP 元数据流会从上述六个字段自动生成,你无需自己构造。
  • titleauthorsubjectcreatorproducerlanguage 全部会进入 XMP 流,这样下游归档的元数据索引器(Preservica、Archivematica)就可以基于这些字段建目录,无需重新解析文档正文。

对一份归档文档而言,品牌化的元数据不只是品牌打磨,而是这件制品耐久性的一部分。十年后,德国海关、巴西税务局,或任何打开你这份 PDF 的长期归档系统,看到的都将是你渲染那天写进这些字段的内容。在渲染时主动设好,是你唯一一次机会。

gPdf 暂未提供的能力

为了对今天的能力面诚实:PDF 规范还定义了 Keywords(自由形式的搜索关键词),以及一条支持任意自定义键值对的 XMP 元数据流。当前 API 里 gPdf 都没暴露。

如果今天就要往 PDF 内塞任意业务数据(订单 UUID、仓库编码、模板版本),变通办法是:

  • subject 设成下游系统可解析的结构化短字符串。
  • 把业务数据放在自家数据库里,以文件名或内容哈希为键。
  • 等一等——XMP 自定义字段已经在路线图上,上线后才是面向隐藏机器可读流程上下文的正解。

把“品牌化元数据“(今天就有的六个标准字段)与“自定义业务元数据“(未来的 XMP 自定义字段)混为一谈,是最容易超额承诺当下能力的方式。在你自己的规划里,值得把这两件事分开。

一个完整示例

一家 SaaS 计费平台(Acme Billing Platform)给一位德国客户(Müller Versand GmbH)开发票,准备以 PDF/A 形式归档:

{
  "settings": {
    "profile": "pdfa-3b",
    "metadata": {
      "title":    "Rechnung RE-2026-0412",
      "language": "de",
      "author":   "Müller Versand GmbH",
      "subject":  "Monatsrechnung — März 2026",
      "creator":  "Acme Billing Platform v7.2",
      "producer": "Acme Billing Platform"
    }
  }
}

对生成的 PDF 跑 pdfinfo:

$ pdfinfo invoice-2026-0412.pdf | head -10
Title:           Rechnung RE-2026-0412
Subject:         Monatsrechnung — März 2026
Author:          Müller Versand GmbH
Creator:         Acme Billing Platform v7.2
Producer:        Acme Billing Platform
Language:        de

title 是德语,author 是 Müller Versand(客户的 GmbH 实体,也是文档收件方),creator 是 Acme Billing Platform(决定页面上写什么的编辑系统),producer 是 Acme Billing Platform 的品牌,language 标得正确——德语屏幕阅读器和稍后在 Müller 的 DMS 里抓到这份文档的德语全文索引器都能识别。PDF/A-3b 配置意味着这套元数据也会被序列化到 XMP 流里以备长期归档。

文件属性里没有任何地方点名 gPdf、Chromium 或任何客户没选的工具——这正是要点所在。

最小可行的升级

如果你已经在 POST 到 /api/v1/pdf/render,而当前调用没有 settings.metadata,最小的改进就是在已经在发的 JSON 里加三行:

 {
   "pages": [...],
   "settings": {
+    "metadata": {
+      "author":   "Your customer's organisation",
+      "producer": "Your platform"
+    }
   }
 }

两个字段,一个新键。几秒钟用 pdfinfo 就能验证。这两个落地后,等有时间再补 titlelanguagesubjectcreator

这件事的落点