PDF 工作流

PDF 支持仍是实验性、显式开启的能力。本页是 export-pdfimport-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 能力:

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

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-mapFEATHERDOC_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 包含 commandokoutputbytes_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 时,失败输出保持 okfalse,并报告 commandstagemessage。文档或 writer 失败还可能携带 detailentryxml_offset

{
  "command": "export-pdf",
  "ok": false,
  "stage": "export",
  "message": "Operation not supported",
  "detail": "PDF export requires configuring with -DFEATHERDOC_BUILD_PDF=ON"
}

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

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

开发构建和发布闸门证据仍记录在 BUILDING_PDF.mddocs/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 字段 commandokin_placesectionsheadersfooters,随后是 PDF import 字段 inputoutput 和导入计数:

{
  "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_confidencetable_continuation_diagnostics 按表格候选发现顺序排列,用于解释候选表格为什么新建表格、 为什么与上一页表格合并,或者为什么被阻止跨页合并。

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

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

稳定字段包括:

  • page_indexblock_indexsource_row_offset

  • continuation_confidenceminimum_continuation_confidence

  • has_previous_tableis_first_block_on_pageis_near_page_topsource_rows_consistentcolumn_count_matchescolumn_anchors_match

  • previous_has_repeating_headersource_has_repeating_headerheader_matches_previousheader_match_kindskipped_repeating_header

  • dispositionnonecreated_new_tablemerged_with_previous_table

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

诊断对象字段按 CLI JSON 输出顺序展示,用户可见示例应与 CLI 实现保持一致: page_indexblock_indexsource_row_offsetcontinuation_confidenceminimum_continuation_confidencehas_previous_tableis_first_block_on_pageis_near_page_topsource_rows_consistentcolumn_count_matchescolumn_anchors_matchprevious_has_repeating_headersource_has_repeating_headerheader_matches_previousheader_match_kindskipped_repeating_headerdispositionblocker

header_match_kind 可以是 nonenot_requiredexactnormalized_textplural_variantcanonical_texttoken_setblocker 可以是 noneno_previous_tablenot_first_block_on_pagenot_near_page_topinconsistent_source_rowscolumn_count_mismatchcolumn_anchors_mismatchrepeated_header_mismatchcontinuation_confidence_below_threshold

重复表头续页候选合并时,诊断会显式记录规则决策: source_row_offset = 1 表示跳过候选表第一行重复表头, skipped_repeating_header = truecontinuation_confidence = 95header_match_kind = exactblocker = 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 保持 okfalse,并报告 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_faileddocument_create_faileddocument_population_failedextract_text_disabledextract_geometry_disabledtable_candidates_detectedno_text_paragraphs。未启用 --import-table-candidates-as-tables 且检测到表格候选时, 会报告 table_candidates_detected,并且不会写出目标 DOCX。没有可提取文本段落的扫描件 或 image-only PDF 会报告 no_text_paragraphs,同样不会写出目标 DOCX。

命令行解析错误仍使用 stageparse 的通用 JSON 形状。当前消息包括 missing value after --min-table-continuation-confidenceinvalid value after --min-table-continuation-confidenceduplicate --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 检查维护。