Pro 版本稳定性: 实验性
C2PA 预览 — 深度参考
本页是 NextPDF Pro 中 C2PA(Content Credentials)预览面的契约级参考。它涵盖 NextPDF\Pro\Compliance\C2pa 中的五个公开符号:C2paManifestEmbedder SPI、ManifestStore 值对象、JumbfBoxParser、C2paCapabilityStatus 描述符,以及受开关控制的 Experimental\ExperimentalC2paEmbedder。它同时记录 Feature::PREVIEW_C2PA_DRAFT 开关及其环境变量 NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT。
该面为实验性(experimental),并被拆分为两层。稳定接缝 —— ManifestStore、C2paManifestEmbedder、JumbfBoxParser —— 始终可达,并在两个方向上承载 Manifest Store 字节。草案清单的合成(synthesis)仅存在于 ExperimentalC2paEmbedder 中,且默认关闭。C2PA-PDF profile 尚未由工作组定稿;合成出的线格式被固定(pin)到某个草案提交。这里不作任何合规性声明,没有验证路径,启用预览开关也无法凭空创建其中任何一者。面向任务的视图见能力页。
可用性与许可
标题为“可用性与许可”的章节此能力随 NextPDF Pro(nextpdf/pro)发布,并在 Pro 级许可证信封(envelope)下激活。没有该授权的部署不会加载此能力的类。对比版本并获取许可证。
许可证作为整体激活 Pro 合规面。其中的 C2PA 面无论许可证级别如何都保持为预览。草案合成还额外需要本页所记录的进程级开关;仅凭 Pro 许可证永远不会启用它。
公开 API 面
标题为“公开 API 面”的章节| 符号 | 参数 | 默认行为 | 返回 | 抛出或失败于 | 备注 |
|---|---|---|---|---|---|
C2paManifestEmbedder | — | 仅字节的嵌入/提取 SPI;无 I/O;不合成 claim | — | — | 冻结的、厂商中立的接缝接口。 |
C2paManifestEmbedder::embed() | string $pdfBytes、ManifestStore $store | 在 profile 声明的位置嵌入 $store->toBytes();空 Store 可以(MAY)作为空操作往返 | string 新的 PDF 字节 | 任何嵌入失败时抛出 C2paException(Store 过大、PDF 无效、profile 位置冲突) | 实现绝不修改或保留输入字节。 |
C2paManifestEmbedder::extract() | string $pdfBytes | 廉价的检测探针;无 Store 的情形几乎不分配内存 | ?ManifestStore(未命中返回 null) | 当存在 Store 但违反某项加固不变量时,抛出 C2paException 子类 | 非 null 的 Store 已通过 JumbfBoxParser 加固。 |
ManifestStore::fromBoxes() | array $boxes(list<JumbfBox>) | 包装一份经解析器校验的有序 box 列表 | self | 自身不抛出;手工构造 JumbfBox 会强制执行相同的加固 | 构造函数为私有;box 顺序对往返相等性起决定作用。 |
ManifestStore::empty() | 无 | 零个根 box 的 Store | self | 不抛出 | 空 Store 的 toBytes() 为空字符串。 |
ManifestStore::isEmpty() | 无 | 检测是否为零个根 box | bool | 不抛出 | — |
ManifestStore::toBytes() | 无 | 拼接根 box 的序列化结果 | string | 不抛出 | 这段字节序列正是嵌入器所写入的内容。 |
ManifestStore::size() | 无 | toBytes() 的字节长度 | int(>= 0) | 不抛出 | — |
JumbfBoxParser::__construct() | 三个可选的上限覆盖 | 生产上限:每个 box 64 MiB、总计 128 MiB、每个 superbox 4096 个子级 | JumbfBoxParser | 不抛出 | 深度上限固定为 MAX_DEPTH(8),不可经构造函数调整。 |
JumbfBoxParser::parse() | string $bytes | 校验并物化根 box;空输入产生 [] | list<JumbfBox> | JumbfBombException、JumbfCycleDetectedException、JumbfDepthExceededException、MalformedJumbfException | 无状态;绝不返回部分图;在同一实例上的并发调用是安全的。 |
C2paCapabilityStatus::__construct() | 六个具名的 readonly 字段 | 构建一个任意的描述符实例 | C2paCapabilityStatus | 不抛出 | current() 是规范的构造入口。 |
C2paCapabilityStatus::current() | 无 | 实时读取开关;硬编码 claim 布尔值 | C2paCapabilityStatus | 不抛出 | generallyAvailable 与 conformanceClaimed 始终为 false。 |
C2paCapabilityStatus::summary() | 无 | 一行状态文本 | string | 不抛出 | 措辞上不携带任何 GA 或合规性声明。 |
Feature | 字符串支撑的枚举,1 个 case | 单个 case PREVIEW_C2PA_DRAFT;常量 ENV_PREVIEW_C2PA_DRAFT | 枚举 case | 访问 case 时不发生任何抛出 | 作用域内的稳定性开关;与许可证授权相区分。 |
Feature::isEnabled() | 无 | 实时读取 getenv();与字符串 1 严格比较 | bool | 不抛出 | 变量缺失或为任何其他值(包括 0、true、yes)均为关闭。 |
ExperimentalC2paEmbedder::__construct() | 无 | 构造时进行失败即关闭(fail-closed)的开关检查 | ExperimentalC2paEmbedder | 当 Feature::PREVIEW_C2PA_DRAFT 关闭时抛出 LogicException | 不存在静默回退。 |
ExperimentalC2paEmbedder::buildManifestStore() | string $sourceBytes、string $producer(非空) | 构建一个草案形态的 Store,通过 SHA-256 绑定 $sourceBytes | ManifestStore | 载荷编码失败时抛出 \JsonException;box 构造抛出 C2paException 子类 | 省略 c2cs Claim Signature box;其输出在构造上即为未签名。 |
interface C2paManifestEmbedder
public function embed(string $pdfBytes, ManifestStore $store): string;public function extract(string $pdfBytes): ?ManifestStore;final readonly class ManifestStore
public static function fromBoxes(array $boxes): selfpublic static function empty(): selfpublic function isEmpty(): boolpublic function toBytes(): stringpublic function size(): intfinal class JumbfBoxParser
public const int MAX_DEPTH = 8;public const int MAX_PER_BOX_BYTES = 64 * 1024 * 1024;public const int MAX_TOTAL_BYTES = 128 * 1024 * 1024;public const int MAX_CHILDREN_PER_SUPERBOX = 4096;public const array SUPERBOX_TBOXES = ['jumb', 'c2pa', 'c2ma', 'c2as', 'c2cl', 'c2cs', 'c2vc'];
public function __construct( private readonly int $maxPerBoxBytes = self::MAX_PER_BOX_BYTES, private readonly int $maxTotalBytes = self::MAX_TOTAL_BYTES, private readonly int $maxChildrenPerSuperbox = self::MAX_CHILDREN_PER_SUPERBOX,)
public function parse(string $bytes): arrayfinal readonly class C2paCapabilityStatus
public const string MATURITY_PREVIEW_DRAFT = 'preview-draft';
public function __construct( public bool $previewEnabled, public bool $generallyAvailable, public bool $conformanceClaimed, public string $maturity, public string $specPin, public string $envGate,)
public static function current(): selfpublic function summary(): stringenum Feature: string
case PREVIEW_C2PA_DRAFT = 'preview_c2pa_draft';
public const string ENV_PREVIEW_C2PA_DRAFT = 'NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT';
public function isEnabled(): boolfinal class ExperimentalC2paEmbedder
public const string SPEC_PIN_SHA = '4e2afed8f3ace20d41317e2e386c9340d2959d55';public const string SPEC_PIN_DATE = '2026-04-26';
public function __construct()
public function buildManifestStore(string $sourceBytes, string $producer): ManifestStore行为契约
标题为“行为契约”的章节- 两层拆分。 稳定接缝(
ManifestStore、C2paManifestEmbedder、JumbfBoxParser)始终可达。草案合成仅存在于NextPDF\Pro\Compliance\C2pa\Experimental\ExperimentalC2paEmbedder中,位于默认关闭的开关之后。提取与字节承载从不需要该开关;合成则始终需要。 - 接缝不变量。
C2paManifestEmbedder契约仅涉及字节:没有内存中的 PDF 对象跨越接缝,实现不执行任何网络或文件系统 I/O,且接缝自身从不组装 claim 断言。extract()以返回null来表示不存在;它从不因不存在而抛出。 - Store 语义。
ManifestStore是一个不可变的、由根JumbfBox实例构成的有序列表,遵循 C2PA 2.1 §11.1.1 的 Manifest Store 模型:一个 JUMBF 容器聚合一个或多个 manifest,可按 URI 寻址。它不暴露任何 claim 级访问器。box 顺序被保留,且对往返相等性起决定作用。 - 加固上限。
JumbfBoxParser无条件拒绝任何超出上限的输入:单个 box 超过 64 MiB、累计 store 超过 128 MiB、嵌套深于 8 层,或某个 superbox 中子级超过 4096 个。没有任何策略开关可以禁用这些上限。对于内存受限的进程,可经构造函数注入更紧的上限。 - 结构性拒绝。 解析器还以失败即关闭的方式拒绝:
LBox = 0(BMFF 直至 EOF)、LBox = 1(XLBox 64 位长度)、小于 8 字节头部的LBox、截断超出剩余输入、位于可打印 ASCII(0x20–0x7E)之外的 TBox 字节、偏移量重入(环)、以及 superbox 载荷的非精确子级铺排。它绝不返回部分构造的图。 - Superbox 路由。
SUPERBOX_TBOXES中的 TBox 值会作为子级序列递归解析;其他每个 TBox 都是携带不透明载荷的叶子。出于解析器安全考虑,cbor被刻意视为叶子;上游层在需要时再解析其载荷。 - 进程级开关。
Feature::PREVIEW_C2PA_DRAFT默认关闭。仅当NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT恰好等于字符串1时,isEnabled()才返回true。每次调用都实时读取;不做任何记忆化。 - 失败即关闭的构造。 当开关关闭时,
new ExperimentalC2paEmbedder()抛出LogicException。消息会指明该开关、环境变量以及固定的草案 SHA 与日期。调用方无法意外抵达草案合成。 - 合成形态。
buildManifestStore()发出一个c2pasuperbox,内含一个c2mamanifest,后者持有一个c2as断言存储(一条c2pa.hash.data断言)与一个c2clclaim。该断言记录对$sourceBytes的一条 SHA-256 哈希断言;由于省略了c2csClaim Signature box 且输出未签名,这不是 C2PA 硬绑定(hard binding),也不是溯源裁定 —— 它只是遵循 §9.1 所描述的结构形态。Description-box 载荷携带一个类型 UUID、切换位0x03以及一个以 null 结尾的 UTF-8 标签,遵循 C2PA 2.1 §11.1.4.1.1–11.1.4.1.2。 - 无 Claim Signature。
c2csbox —— 按 C2PA 2.1 §11.1.4.4 是一个标签为c2pa.signature、由单个 CBOR 内容 box 构成的盒子 —— 被刻意从合成出的 Store 中省略。其输出在构造上即为未签名。这是被判定为在工作组冻结前最可能发生漂移的 profile 区域。 - 草案固定,无 BC 保证。 合成出的线格式被固定到
c2pa-org/specifications的SPEC_PIN_SHA(4e2afed8…,日期2026-04-26)。它可能在不另行通知的情况下更改,且不带任何向后兼容保证。 - 诚实不变量。
C2paCapabilityStatus::current()将generallyAvailable与conformanceClaimed硬编码为false。没有任何配置或环境开关能翻转其中任一布尔值。只有previewEnabled反映开关状态;maturity是不作声明的令牌preview-draft。
边界情形与失败模式
标题为“边界情形与失败模式”的章节- 将开关变量设为
0、true、yes、on或空字符串,都会让开关保持关闭。只有恰好为字符串1才会启用它。 putenv()的更改会在下一次isEnabled()调用时生效,因为读取是实时的。进程运行中途切换的开关会被立即观察到。extract()区分两种结果:无 Store 时返回null(廉价、无异常),以及存在 Store 但为敌意或畸形时抛出C2paException子类。不存在从不是错误;存在加上畸形则始终是错误。JumbfBoxParser::parse('')返回空列表。一个存在但为空的ManifestStore会往返为自身;接缝不会将其塌陷为null。- 嵌入一个空 Store 可以(MAY)原样返回输入。接缝契约允许这一空操作,但不强制要求。
- 手工构造的
JumbfBox图在构造时会运行相同的加固:TBox 长度与 ASCII 检查、深度上限、子级深度不变量、载荷与子级互斥规则,以及单 box 大小上限。手工构造的炸弹在构造时失败,而非在嵌入时。 - 每个解析器异常都携带结构化字段 ——
capKind/observed/cap、offset或kind—— 因此遥测无需刮取消息字符串。所有子类都扩展自C2paException(其本身是RuntimeException),后者是总括性的捕获类型。 - 解析器 docblock 禁止静默吞掉这些异常;使用方应将其浮现,或有意图地重映射它们。
buildManifestStore()以JSON_THROW_ON_ERROR编码 JSON 载荷;一个不是有效 UTF-8 的$producer字符串会在任何 box 被构建之前以\JsonException失败。- 一个格式良好的
extract()结果只是一条结构性陈述。此面上任何位置都没有 claim 校验、没有签名验证、也没有信任评估。识别不是溯源裁定。 - 此面不处理任何签名密钥、证书或 COSE 结构。唯一的密码学操作是受开关控制的合成路径内的一次 SHA-256 内容哈希。
合规性
标题为“合规性”的章节| 声明 | 标准 | 条款 |
|---|---|---|
| Manifest 序列化进一个持有多个 manifest、可按 URI 寻址的 JUMBF store。 | C2PA 2.1 | §11.1.1 (p63.b) |
| Description-box 标签是以 null 结尾的 UTF-8,含排除范围;所有 Description box 都定义了切换位。 | C2PA 2.1 | §11.1.4.1.1–11.1.4.1.2 (p63.a) |
Claim Signature box 的标签为 c2pa.signature、类型为 c2cs,持有单个 CBOR 内容 box。 | C2PA 2.1 | §11.1.4.4 (p63.c) |
| 硬绑定以密码学方式将 manifest 与其资产相绑并暴露修改 —— 预览的未签名哈希断言不满足此标准。 | C2PA 2.1 | §9.1 (p57) |
以上条款均为转述。NextPDF 不复制规范正文。NextPDF 未持有任何认证,也不授予任何认证。 上述陈述是关于 box 布局、标签与绑定的结构对齐陈述 —— 它们不是合规测试结果、不是第三方证明,也不是 C2PA 或 ISO 合规声明。C2PA-PDF profile 尚未定稿;合成出的线格式跟踪一个固定的草案提交。C2paCapabilityStatus 在代码中固化了这一姿态:generallyAvailable 与 conformanceClaimed 在任何配置下都为 false。此面的输出不是可验证的 Content Credential,且 NextPDF 中不存在验证路径。
开发注意事项
标题为“开发注意事项”的章节-
解析器实现的 JUMBF box 文法(4 字节大端 LBox、4 字节 ASCII TBox、载荷;superbox 嵌套子 box)遵循 ISO 19566-5;该标准不在所引语料范围内,因此解析器行为基于产品源码确立,而非某条规范引用。
-
在生产中保持开关关闭。草案合成不增加任何持久能力;发出的字节是临时的,一旦稳定的适配器发布,就应重新嵌入。
-
针对你的流水线所期望的草案提交断言
ExperimentalC2paEmbedder::SPEC_PIN_SHA。在 CI 中运行composer c2pa:draft-status(fresh 退出 0,soft-warn 退出 1,hard-fail 退出 2)以检测固定值的陈旧。 -
在工具或 UI 中呈现 C2PA 状态时,将
C2paCapabilityStatus::current()视为唯一可信来源。不要手工复述其布尔值;summary()可安全用于日志与状态端点。 -
在消费
extract()或parse()时,将C2paException作为总括类型捕获。使用四个子类的结构化字段将它们映射到彼此不同的遥测计数器。 -
对于内存受限的验证器进程,经
JumbfBoxParser构造函数注入更紧的上限;默认值是宽松的生产上限。 -
C2paCapabilityStatus::__construct()是公开的,因此手工构造的实例可以携带任意布尔值。这样的实例只是一个值对象;它不改变任何行为。
发布边界
标题为“发布边界”的章节本页仅记录外部可观测的行为与受支持的公开 API 面。内部命名空间路径、辅助类、机制表、runbook 文件名以及工单前缀均不在范围内。