PDF 对自己知道些什么:元数据与 XMP 封包
Spec: ISO 16684-1:2019ISO 16684-1:2019Spec: ISO 32000-2ISO 32000-2
打开一份现代 PDF,它或许会把自己描述两遍。有一份小巧、老旧的键/值清单,叫做 Document Information 字典,还可能有一块 XML——XMP 封包——以一种更丰富、更结构化的形式说着大致相同的内容。本页讲的就是那两套并行的系统,为何两者都存续了下来,以及为何封存与搜索流水线坚持要它们彼此一致。
对标题的简短回答:一份 PDF 知道它的标题、作者、主题、关键词、制作它的工具,以及时间。它把那份知识同时储存在两处,而有意思的工程问题就在于让它们保持诚实。
为何这很重要
标题为“为何这很重要”的章节元数据是文档中人很少看见、而机器几乎总在读取的部分。你操作系统的文件预览、一个数字资产管理器、一份图书馆目录、一个搜索索引、一个法律电子取证工具——它们之中有许多会在文档正文之前、或与之并行地读取元数据,并信任它所说的内容。
因此当这两套系统彼此不一致时——Info 字典说是一个作者,而 XMP 封包说是另一个——下游某处会挑一个,而你无从选择是哪一个。一个搜索索引可能呈现出错误的标题。一个封存验证器可能直接拒绝该文件。这种漂移在一条流水线被它绊倒之前都是隐形的,而那正是发现它的最糟时机。
简短版本
标题为“简短版本”的章节- 一份 PDF 可以用两套并行系统携带元数据:老旧的 Document Information 字典,以及现代的 RDF/XML XMP 封包。
- XMP 封包被包进一个 xpacket 处理指令——一个
begin标头与一个end尾段——好让工具能找到它,甚至能就地重写它,而不必重新解析整份文件。 - 属性住在命名空间里:
dc(Dublin Core)放标题与创建者,xmp放创建与修改日期,pdf放制作器,xmpMM放文档身份与历史。 - PDF/A 要求 XMP 元数据,而那些有 XMP 对应项的 DocInfo 条目必须与之相符——一处不一致便是一次验证失败。
- NextPDF 把这两者当作一项工作来处理:它两者都写入、并把 XMP 读回,且守护封包的大小,宁可失败封闭,也不输出一份格式损坏的文件。
NextPDF 如何处理
标题为“NextPDF 如何处理”的章节诚实的框定方式是:这是一个伪装成格式化问题的同步问题。Document Information 字典(Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3),由尾段透过 /Info 条目引用,是一份扁平的字符串清单:Title、Author、Subject、Keywords、Creator、Producer,以及一对日期。它简单、早于 XML,且因为太多工具仍会先读它,它至今仍随附在几乎每一份文件里。
XMP 封包(Spec: ISO 16684-1:2019, §7ISO 16684-1:2019 §7)是现代的那一半。它是一份 XML 文档——确切地说是 RDF/XML——把文件建模为一组分组进模式(schema)的具名属性(property),每个模式绑定到一个命名空间(Spec: ISO 16684-1:2019, §6ISO 16684-1:2019 §6)。那种结构正是扁平字典做不到的:一个值可以是一份有序清单、一个带语言标签的替代项(“英文标题、法文标题”),或一笔结构化记录。在一份 PDF 里,那份 XML 住在一个附着于文档目录的元数据流中(Spec: ISO 32000-2, §14.3.2ISO 32000-2 §14.3.2),那正是当前读取器查看的典型位置。
有三个细节值得剖析,因为它们正是工具把封包弄错之处。
那个包装。 一个 XMP 封包被一个处理指令所括住,好让在封包以可直接搜索之字节储存的那些格式中,程序能定位到元数据而无需完整解析;而具体到 PDF 中,目录元数据流才是典型的定位器。begin 标头携带一个声明文本编码的字节顺序标记;end 尾段携带一个陈述封包是只读、还是可就地编辑的标志位。当它可写时,序列化器会在 XML 之后留下一段空白填充,好让编辑器能略微增长内容,而不必移动其后的每一个字节。那段填充并非装饰——它正是让就地元数据编辑得以成立的东西。
那些命名空间。 一个属性唯有对照它的命名空间才有意义,而其中有少数几个近乎通用。Dublin Core(dc)持有标题与创建者。XMP basic 模式(xmp)持有创建与修改日期以及创作工具。PDF 模式(pdf)持有制作器字符串与关键词。媒体管理模式(xmpMM)持有文档的身份及其衍生历史——那条说着“这份文件来自那一份”的轨迹。就这些命名空间 URI 与属性名称达成一致——通常以约定俗成的前缀展示——正是核心模式的全部要点(Spec: ISO 16684-1:2019, Annex BISO 16684-1:2019 Annex B);两个都说着 Dublin Core 标题属性的工具,无需事先约定便能互操作。
那条一致性规则。 因为两套系统都能命名同一个属性,它们便可能彼此不一致。封存设定档拒绝允许那种情况。一份 PDF/A 文件必须携带一份 XMP 元数据流,而任何有 XMP 对应项的 Document Information 条目都必须与之相符;一处不一致便是一次验证失败。实务上的要点很直接:在一条封存流水线里,这两者不是各自独立的字段,而是被写了两次的同一个事实,且它们必须保持步调一致。
NextPDF 把这三件事当作单一关注点来处理。元数据流是一条路径,而非两条相互竞争的路径。
- Collect the values onceTitle, author, subject, keywords, creator, producer and dates are set on the document a single time, so there is one source of truth, not two.
- Write the Info dictionaryThe DocInfo writer emits the legacy Title, Author, Subject, Keywords, Creator, Producer and date entries that older tools read first.
- Build the XMP packetThe XMP builder serializes the same values as RDF/XML under dc, xmp, pdf and xmpMM, wrapped in the xpacket header and trailer with writable padding.
- Guard the packet sizeBefore serialization is accepted, the engine checks the packet against a size bound and fails closed with a typed exception rather than emit a malformed or oversized stream.
- Read XMP back to verifyThe XMP reader parses the packet so a pipeline can confirm the engine wrote the metadata it was given — the kind of check an archival validator builds on.
实际示例
标题为“实际示例”的章节下面的写法把元数据设定一次,然后让引擎把它扇出到两套系统,接着把 XMP 标题读回,好让一条流水线能检查它。那个大小守护,正是把“大概没问题”变成“不行就可被证明地拒绝”的那一行。
<?php
declare(strict_types=1);
use NextPDF\Core\Document;use NextPDF\Metadata\Exception\XmpPacketTooLargeException;
$document = Document::createStandalone();
// Set the values ONCE. The engine writes them to both the Info dictionary// and the XMP packet, so the two systems cannot drift apart at the source.$document->setTitle('Annual Report 2026');$document->setAuthor('Records Office');$document->setSubject('Statutory annual filing');$document->setKeywords('annual report, statutory, 2026');
try { // Building the document serializes the XMP packet. The engine guards the // packet size and fails closed: a packet that exceeds the bound is a // typed exception, never a silently truncated or malformed stream. $bytes = $document->getPdfData();} catch (XmpPacketTooLargeException $e) { // Decide deliberately: trim the metadata, not the guarantees. error_log('XMP packet exceeded the size bound: ' . $e->getMessage()); throw $e;}
// Read the packet back. This confirms the XMP the engine serialized carries the// value you set — the kind of integrity check an archival pipeline runs before// it trusts the file. (Both systems are written from one source, so they agree// by construction; reading the XMP back proves it serialized as intended.)$xmp = $document->readXmpMetadata($bytes);$xmpTitle = $xmp->get('dc', 'title'); // 'Annual Report 2026'
if ($xmpTitle !== $document->getTitle()) { throw new \RuntimeException('XMP title does not match the value that was set.');}这里没有任何一条路径会让两套系统悄悄地各走各的:值进入一次,两个写入器消费同一个来源,而读取器让一条流水线检查结果。
常见的误解
标题为“常见的误解”的章节常见的信念是,Info 字典已经过时,你可以无视它。你不能——还不行。大量已安装的软件,包括某些操作系统文件预览与较旧的封存工具,仍然先读、或只读 Info 字典。丢弃它并不会让一份文件现代化;它会让该文件在任何尚未采用 XMP 的东西看来都是无标题的。
镜像反面的错误,是把这两者当作你各自填写的独立字段。那正是它们漂移的方式。它们是同一个事实的两种序列化。从一个来源写它们,否则就接受下游某处会挑你不打算要的那个版本。
限制与边界
标题为“限制与边界”的章节- NextPDF 写入两套系统,并把 XMP 读回。 它的范畴是 XMP 元数据构建器、DocInfo 写入器,以及一个 XMP 读取器。它并不承诺成为一个通用的 RDF/XML 查询引擎。
- XMP 封包受大小守护,并失败封闭。 一个超出引擎边界的封包会引发一个类型化异常。引擎不会为了让一个超量请求“塞得进去”而输出一份被截断、填充超限,或以其他方式格式损坏的封包。
- 一致性是在写入时从一个来源强制实现的;它不是对一份并非你产生之文件的神奇协调。 如果你导入一份其两套系统本就彼此不一致的文档,解决那一点是你的决定,而非一次隐式的重写。
- PDF/A 的元数据要求是封存设定档的一部分。 本页解释那条规则;而标准符合性的裁决属于一个验证器,对封存工作而言一向如此。关于那条边界,参见封存页面。
元数据构建器、DocInfo 写入器,以及 XMP 读取器都是 Core 能力。诚实的限定语,连同那个大小守护,置于下方。
| Edition | Availability |
|---|---|
| Core | The XMP metadata builder, the Document Information dictionary writer, and the XMP reader ship in Core, with the size guard that fails closed on an oversized packet — including the PDF/A conformance output and validation that make the XMP metadata stream mandatory and require DocInfo to match it. |
| Pro | Adds batch and templated metadata workflows over the same Core writer and reader. |
| Enterprise | Adds the broader conformance policy and reporting surface across a document estate. |
相关文档
标题为“相关文档”的章节- 封存与 PDF/A — XMP 封包从此不再可选、且 DocInfo 到 XMP 的一致性规则成为一项过或不过之要求的地方。
- 可交给审计员的合规 — 为何能往返一致、并与自身相符的元数据,是一份可审计产物的一部分。
- PDF 文件剖析 — 尾段、目录,以及元数据流在文件结构中所处的位置。
- 流与过滤器 — 元数据流是一个流;这是 PDF 流如何被编码并保持确定性的方式。
词汇表
标题为“词汇表”的章节- Document Information 字典 — 那份由尾段透过
/Info条目引用的老旧、扁平的键/值元数据:Title、Author、Subject、Keywords、Creator、Producer,以及日期。早于 XMP;仍被广泛读取。 - XMP — Extensible Metadata Platform,一种以具名属性分组进模式来描述资源的 XML(RDF/XML)格式。标准化为 ISO 16684-1。
- xpacket — 那个包裹一个 XMP 封包的处理指令,带一个
begin标头(编码标记)与一个end尾段(只读或可写标志位),好让工具能定位并编辑封包而无需完整解析。 - 命名空间 / 模式 — 一套绑定到一个 URI 的具名属性词汇。常见前缀:
dc(Dublin Core)、xmp(XMP basic)、pdf(PDF 专属)、xmpMM(媒体管理 / 文档身份)。 - 元数据流 — 那个附着于文档目录、携带 XMP 封包的 PDF 对象。现代读取器首先查看的典型位置。
- PDF/A — 封存 PDF 设定档族(ISO 19005)。它要求一份 XMP 元数据流,并要求任何有 XMP 对应项的 Document Information 条目都与之相符,否则验证失败。