PDF 工作流

PDF 支持仍是实验性、显式开启的能力。本页是 export-pdf 和 import-pdf CLI 工作流、JSON 诊断、支持范围和发布验证引用的中文公开契约。

构建选项

工作流

CMake 选项

用途

PDF 导出

-DFEATHERDOC_BUILD_PDF=ON

启用 featherdoc_cli export-pdf 使用的进程内 PDF writer。

PDF 导入

-DFEATHERDOC_BUILD_PDF_IMPORT=ON

启用 featherdoc_cli import-pdf 使用的 PDFium 文本导入器。

使用 CLI 入口前,需要先构建 PDF 能力:

PDF 导出必须使用 -DFEATHERDOC_BUILD_PDF=ON 配置;PDF 导入还必须启用 -DFEATHERDOC_BUILD_PDF_IMPORT=ON。

cmake -S . -B build-pdf -DFEATHERDOC_BUILD_PDF=ON -DFEATHERDOC_BUILD_PDF_IMPORT=ON -DBUILD_CLI=ON

模块与发布边界

PDF 目前保留在 FeatherDoc 同一仓库中,但已通过独立的 FeatherDoc::Pdf、 FeatherDoc::PdfImport target、条件编译的 CLI 源码和独立依赖提供器与 Word/Core 隔离。默认构建不会下载或编译 PDFio、FreeType、HarfBuzz、PNG 或 PDFium,也不会 注册 PDF CLI 命令。

当 PDF writer 的 FreeType、ZLIB、PNG 与启用的 HarfBuzz 都是可发现的 CMake package target 时,安装包会导出 Pdf 组件;消费方显式请求并链接它:

find_package(FeatherDoc CONFIG REQUIRED COMPONENTS Core Pdf)
target_link_libraries(my_app PRIVATE FeatherDoc::Pdf)

依赖查找只在请求 Pdf 时发生,只消费 Core / Word 不会引入 PDF 依赖。 如果 writer 使用仓库内 fallback 依赖构建,它仍只在构建树可用,安装包不会伪装成 已发布组件,外部消费方请求 Pdf 时会明确失败。PdfImport 也只有在 PDFium 来自可发现 package 时才允许安装。这个边界允许继续在同仓库复用文档模型和端到端 测试;未来若 PDF 的发布节奏、ABI 或依赖治理需要独立,可以再迁出 target,无需现在 拆分仓库。

PDF 导出

export-pdf 通过进程内 PDF writer 把 .docx 文档写成 PDF:

featherdoc_cli export-pdf input.docx --output output.pdf --json
featherdoc_cli export-pdf input.docx --output output.pdf --render-headers-and-footers --summary-json output.summary.json --json
featherdoc_cli export-pdf input.docx --output output.pdf --render-headers-and-footers --expand-header-footer-page-placeholders --summary-json output.summary.json --json

常用选项:

  • --output <pdf> 选择输出 PDF 路径。

  • --render-headers-and-footers 渲染分节页眉和页脚。

  • --expand-header-footer-page-placeholders 在导出时展开 {{page}} 和 {{total_pages}} 占位符。

  • --render-inline-images 启用受支持的行内图片渲染。

  • --font-file <path> 和 --cjk-font-file <path> 提供显式字体。

  • --font-map <family>=<path> 把文档字体族映射到字体文件。

  • --no-font-subset 关闭 Unicode 字体子集。

  • --no-system-font-fallbacks 关闭系统字体回退查找。

  • --summary-json <path> 写出机器可读导出摘要。

  • --json 输出机器可读命令结果。

CJK 字体来源必须显式可追溯。当前发行包不重新分发 CJK TTF / OTF / TTC 字体文件; 调用方通过 --cjk-font-file、--font-map、FEATHERDOC_PDF_CJK_FONT、 测试专用 FEATHERDOC_TEST_CJK_FONT 或已安装的系统字体候选提供字体。未来若随包 分发 CJK 字体,必须选择 Noto Sans CJK、Source Han Sans 或 Source Han Serif 等 OFL 1.1 字体,并在 release manifest 中记录 source URL、精确版本、字体文件名、 LICENSE / NOTICE、Reserved Font Name 义务和打包审计证据。

成功 JSON 包含 command、ok、output、bytes_written 和实际选项:

{
  "command": "export-pdf",
  "ok": true,
  "output": "output.pdf",
  "bytes_written": 12345,
  "options": {
    "render_headers_and_footers": true,
    "render_inline_images": false,
    "expand_header_footer_page_placeholders": true,
    "subset_unicode_fonts": true,
    "use_system_font_fallbacks": true
  }
}

失败 JSON

带 --json 时,失败输出保持 ok 为 false,并报告 command、 stage 和 message。文档或 writer 失败还可能携带 detail、entry 和 xml_offset:

{
  "command": "export-pdf",
  "ok": false,
  "stage": "export",
  "message": "failed to write PDF output",
  "detail": "Unable to create PDF file: output.pdf"
}

未启用 PDF writer 时,export-pdf 不会注册,也不会出现在 CLI 帮助中。 配置构建时启用 FEATHERDOC_BUILD_PDF 才会加入该命令。

当前导出失败阶段包括 "stage": "parse"、"stage": "open"、 "stage": "export" 和 "stage": "summary"。

导出支持范围包括段落文本、基础表格、基础 run 样式、部分 CJK 字体回退流程、分节页面设置、 可选页眉页脚、可选行内图片,以及 PDF 视觉验证流程使用的回归样本。它不是生产级 Word 兼容排版引擎,复杂分页、完整 Word 字段求值、任意绘图重建和视觉级精确还原仍在稳定契约外。

开发构建和发布闸门证据仍记录在 BUILDING_PDF.md 和 docs/pdf_release_readiness_checklist_zh.rst。

PDF 导入

import-pdf 面向可提取文本和字符几何信息的 text-first PDF。它不是通用的 PDF-to-Word converter,也不承诺 arbitrary visual fidelity。

featherdoc_cli import-pdf input.pdf --output imported.docx --json
featherdoc_cli import-pdf input.pdf --output imported.docx --import-table-candidates-as-tables --json
featherdoc_cli import-pdf input.pdf --output imported.docx --import-table-candidates-as-tables --min-table-continuation-confidence 90 --json

--import-table-candidates-as-tables 显式启用表格候选提升。 --min-table-continuation-confidence 调整跨页表格延续阈值。不要在正式页面写占位形式; 示例应使用具体数字阈值。

PDF 导入 JSON 诊断

本节说明 featherdoc_cli import-pdf --json 输出的 PDF import JSON diagnostics。

成功 JSON 包含通用 mutation 字段 command、ok、in_place、 sections、headers 和 footers,随后是 PDF import 字段 input、output 和导入计数:

{
  "command": "import-pdf",
  "ok": true,
  "in_place": false,
  "sections": 1,
  "headers": 0,
  "footers": 0,
  "input": "input.pdf",
  "output": "imported.docx",
  "paragraphs_imported": 2,
  "tables_imported": 1,
  "table_continuation_diagnostics_count": 2,
  "table_continuation_diagnostics": [
    {
      "page_index": 0,
      "block_index": 1,
      "source_row_offset": 0,
      "continuation_confidence": 0,
      "minimum_continuation_confidence": 0,
      "has_previous_table": false,
      "is_first_block_on_page": false,
      "is_near_page_top": true,
      "source_rows_consistent": true,
      "column_count_matches": false,
      "column_anchors_match": false,
      "previous_has_repeating_header": false,
      "source_has_repeating_header": false,
      "header_matches_previous": true,
      "header_match_kind": "not_required",
      "skipped_repeating_header": false,
      "disposition": "created_new_table",
      "blocker": "no_previous_table"
    },
    {
      "page_index": 1,
      "block_index": 0,
      "source_row_offset": 0,
      "continuation_confidence": 85,
      "minimum_continuation_confidence": 0,
      "has_previous_table": true,
      "is_first_block_on_page": true,
      "is_near_page_top": true,
      "source_rows_consistent": true,
      "column_count_matches": true,
      "column_anchors_match": true,
      "previous_has_repeating_header": false,
      "source_has_repeating_header": false,
      "header_matches_previous": true,
      "header_match_kind": "not_required",
      "skipped_repeating_header": false,
      "disposition": "merged_with_previous_table",
      "blocker": "none"
    }
  ],
  "import_table_candidates_as_tables": true
}

命令行设置 --min-table-continuation-confidence 时会额外输出 min_table_continuation_confidence。 table_continuation_diagnostics 按表格候选发现顺序排列,用于解释候选表格为什么新建表格、 为什么与上一页表格合并,或者为什么被阻止跨页合并。

设置阈值的运行会在 summary 层输出该阈值字段,并与 diagnostics 同时出现:

{
  "table_continuation_diagnostics_count": 2,
  "import_table_candidates_as_tables": true,
  "min_table_continuation_confidence": 90
}

稳定字段包括:

  • page_index、block_index、source_row_offset。

  • continuation_confidence、minimum_continuation_confidence。

  • has_previous_table、is_first_block_on_page、is_near_page_top、 source_rows_consistent、column_count_matches、column_anchors_match。

  • previous_has_repeating_header、source_has_repeating_header、 header_matches_previous、header_match_kind、skipped_repeating_header。

  • disposition:none、created_new_table 或 merged_with_previous_table。

  • blocker:拒绝跨页合并的第一个原因,成功合并时为 none。

诊断对象字段按 CLI JSON 输出顺序展示,用户可见示例应与 CLI 实现保持一致: page_index、block_index、source_row_offset、 continuation_confidence、minimum_continuation_confidence、 has_previous_table、is_first_block_on_page、is_near_page_top、 source_rows_consistent、column_count_matches、 column_anchors_match、previous_has_repeating_header、 source_has_repeating_header、header_matches_previous、 header_match_kind、skipped_repeating_header、disposition 和 blocker。

header_match_kind 可以是 none、not_required、exact、 normalized_text、plural_variant、canonical_text 或 token_set。 blocker 可以是 none、no_previous_table、 not_first_block_on_page、not_near_page_top、 inconsistent_source_rows、column_count_mismatch、 column_anchors_mismatch、repeated_header_mismatch 或 continuation_confidence_below_threshold。

重复表头续页候选合并时,诊断会显式记录规则决策: source_row_offset = 1 表示跳过候选表第一行重复表头, skipped_repeating_header = true,continuation_confidence = 95, header_match_kind = exact,blocker = none。该 confidence 是确定性的启发式分数, 不是概率。

{
  "source_row_offset": 1,
  "continuation_confidence": 95,
  "header_match_kind": "exact",
  "skipped_repeating_header": true,
  "disposition": "merged_with_previous_table",
  "blocker": "none"
}

inconsistent_source_rows 是内部一致性保护; 当前 parser 会按检测到的列锚点补齐候选表的每一行,因此正常导入不应把它当作稳定的用户可触发 blocker。

常见 continuation blockers:

  • column_count_mismatch:候选表格检测到的列数与上一张表不一致。

  • repeated_header_mismatch:重复表头语义不匹配。

  • column_anchors_mismatch:列锚点漂移超过容忍范围。

  • continuation_confidence_below_threshold:未达到 --min-table-continuation-confidence。

  • not_first_block_on_page:候选表格不是页面第一个内容块。

  • not_near_page_top:候选表格不在页面顶部附近。

代表性诊断片段:

下面的完整 blocker diagnostic object 按 CLI JSON 字段顺序展示常见拆表场景:

{
  "page_index": 1,
  "block_index": 0,
  "source_row_offset": 0,
  "continuation_confidence": 70,
  "minimum_continuation_confidence": 0,
  "has_previous_table": true,
  "is_first_block_on_page": true,
  "is_near_page_top": true,
  "source_rows_consistent": true,
  "column_count_matches": true,
  "column_anchors_match": true,
  "previous_has_repeating_header": true,
  "source_has_repeating_header": true,
  "header_matches_previous": false,
  "header_match_kind": "none",
  "skipped_repeating_header": false,
  "disposition": "created_new_table",
  "blocker": "repeated_header_mismatch"
}
{
  "page_index": 1,
  "block_index": 0,
  "source_row_offset": 0,
  "continuation_confidence": 30,
  "minimum_continuation_confidence": 0,
  "has_previous_table": true,
  "is_first_block_on_page": true,
  "is_near_page_top": true,
  "source_rows_consistent": true,
  "column_count_matches": false,
  "column_anchors_match": false,
  "previous_has_repeating_header": true,
  "source_has_repeating_header": true,
  "header_matches_previous": false,
  "header_match_kind": "none",
  "skipped_repeating_header": false,
  "disposition": "created_new_table",
  "blocker": "column_count_mismatch"
}
{
  "page_index": 1,
  "block_index": 0,
  "source_row_offset": 0,
  "continuation_confidence": 55,
  "minimum_continuation_confidence": 0,
  "has_previous_table": true,
  "is_first_block_on_page": true,
  "is_near_page_top": true,
  "source_rows_consistent": true,
  "column_count_matches": true,
  "column_anchors_match": false,
  "previous_has_repeating_header": true,
  "source_has_repeating_header": true,
  "header_matches_previous": true,
  "header_match_kind": "exact",
  "skipped_repeating_header": false,
  "disposition": "created_new_table",
  "blocker": "column_anchors_mismatch"
}
{
  "page_index": 1,
  "block_index": 0,
  "source_row_offset": 0,
  "continuation_confidence": 85,
  "minimum_continuation_confidence": 90,
  "has_previous_table": true,
  "is_first_block_on_page": true,
  "is_near_page_top": true,
  "source_rows_consistent": true,
  "column_count_matches": true,
  "column_anchors_match": true,
  "previous_has_repeating_header": false,
  "source_has_repeating_header": false,
  "header_matches_previous": true,
  "header_match_kind": "not_required",
  "skipped_repeating_header": false,
  "disposition": "created_new_table",
  "blocker": "continuation_confidence_below_threshold"
}
{
  "page_index": 0,
  "block_index": 2,
  "source_row_offset": 0,
  "continuation_confidence": 35,
  "minimum_continuation_confidence": 0,
  "has_previous_table": true,
  "is_first_block_on_page": false,
  "is_near_page_top": false,
  "source_rows_consistent": true,
  "column_count_matches": true,
  "column_anchors_match": true,
  "previous_has_repeating_header": false,
  "source_has_repeating_header": false,
  "header_matches_previous": true,
  "header_match_kind": "not_required",
  "skipped_repeating_header": false,
  "disposition": "created_new_table",
  "blocker": "not_first_block_on_page"
}
{
  "page_index": 1,
  "block_index": 0,
  "source_row_offset": 0,
  "continuation_confidence": 45,
  "minimum_continuation_confidence": 0,
  "has_previous_table": true,
  "is_first_block_on_page": true,
  "is_near_page_top": false,
  "source_rows_consistent": true,
  "column_count_matches": true,
  "column_anchors_match": true,
  "previous_has_repeating_header": false,
  "source_has_repeating_header": false,
  "header_matches_previous": true,
  "header_match_kind": "not_required",
  "skipped_repeating_header": false,
  "disposition": "created_new_table",
  "blocker": "not_near_page_top"
}

失败 JSON 保持 ok 为 false,并报告 import 阶段:

{
  "command": "import-pdf",
  "ok": false,
  "stage": "import",
  "failure_kind": "table_candidates_detected",
  "message": "PDF text import detected table-like structure candidates; enable table-candidate import to import them as DOCX tables",
  "input": "input.pdf",
  "output": "imported.docx"
}

failure_kind 可以是 parse_failed、document_create_failed、 document_population_failed、extract_text_disabled、 extract_geometry_disabled、table_candidates_detected 或 no_text_paragraphs。未启用 --import-table-candidates-as-tables 且检测到表格候选时, 会报告 table_candidates_detected,并且不会写出目标 DOCX。没有可提取文本段落的扫描件 或 image-only PDF 会报告 no_text_paragraphs,同样不会写出目标 DOCX。

命令行解析错误仍使用 stage 为 parse 的通用 JSON 形状。当前消息包括 missing value after --min-table-continuation-confidence、 invalid value after --min-table-continuation-confidence 和 duplicate --min-table-continuation-confidence option。Parse errors do not write the target DOCX。

PDF 导入支持范围和限制

PDF import supported scope and limits 刻意保持保守。导入器是 text-first:从 extractable PDF text 和 character geometry 重建保守 Document,再按需提升表格候选。

可靠范围:

  • Paragraph import from extractable PDF text。

  • Conservative table-candidate detection,覆盖简单对齐网格、key-value tables 和 selected borderless aligned tables。

  • Table candidates are rejected by default。

  • Opt-in table promotion through --import-table-candidates-as-tables。

  • Cross-page table continuation:下一页顶部附近出现兼容表格候选时延续。

  • Repeated-header detection:支持 exact text、whitespace/case/punctuation 归一化、 conservative plural variants、small abbreviation whitelist,以及 token-set word order normalization。

  • Conservative subtotal / total summary-row handling。

  • Diagnostics through table_continuation_diagnostics。

保守边界:

  • Column-count mismatches、incompatible column anchors、semantic repeated-header mismatches、 intervening paragraphs 或 low continuation confidence 会保持表格分离。

  • Ordinary two-column prose、numbered lists、short-label prose 和 free-form forms 应保持段落。

  • scanned PDFs、OCR、image-only tables、arbitrary nested table semantics、 complex vector reconstruction、rotated or floating content recovery、 exact visual reconstruction、arbitrary local column drift 都不属于稳定契约。

  • Unsupported cases must fail or remain paragraphs,而不是静默生成误导性的 Word 结构。

发布验证

开发环境和本地验证请看 BUILDING_PDF.md。PDF 发布准入证据仍由发布治理脚本和有界 CTest 检查维护。