- 人工智能
- AI 应用
- 桌面应用
- AI Agent
- MCP 服务
- AI 技能
【免费下载链接】genoffice
Free, open-source AI Office suite: Docs, Sheets, Slides, PDF, Markdown and HTML editors with a built-in AI agent, plus a `genoffice` CLI and agent skill so Claude Code, Codex and Cursor can create and edit real .docx/.xlsx/.pptx files locally. Bring your own key. macOS, Windows & Linux.
在 genoffice 的pdf2docx包中,packages/pdf2docx/tests/golden/zh/README.md定义了简体中文 PDF 转 DOCX 的"黄金回归语料"(golden regression corpus)验收基线。它把中文转换必须满足的五条硬性要求固化为可自动断言的测试约定:CJK 字符之间绝不插入机器空格、中英混排产生交替的 cjk/latin 文本片段、中文字体写入w:eastAsia槽位、全角标点留在 cjk 片段内、首行缩进段落被正确还原。读完本文,你将理解这五条规则各自的排版动机、在源码中的实现位置与判定阈值,以及如何通过 golden 语料和按需生成的 PDF 夹具对转换结果做端到端验证。
一、语料定位:zh 在 golden 目录中的角色
packages/pdf2docx/tests/golden/按语言分子目录,每种子语言有一套独立的覆盖目标(见 golden corpus 总览):
| 目录 | 覆盖重点 |
|---|---|
en/ | 拉丁文本:自适应词距阈值、行尾连字符拼接、左右居中对齐、首行缩进 |
zh/ | 简体中文:不插入机器空格、w:eastAsia字体槽位、中英混排交替片段 |
ja/ | 日文:假名/汉字混排无空格、半角片假名;竖排(tategaki)页降级为位图 |
ko/ | 韩文:谚文保留真实词间空格、映射到w:eastAsia槽位 |
ar/ | 阿拉伯文:RTL 方向还原、数字方向、NFKC 折叠与括号镜像 |
每种语言的 golden 用例是一对文件:<name>.pdf(输入文档,要求小而可再分发)+<name>.expected.json(断言:按顺序排列的段落文本、每段对齐/缩进、图片数量、预期的warnings)。上游 pdf2docx 的负面案例(如丢失/倍增空格 #103、RTL 反转 #73)移植进来时统一命名为issue-<n>-*。
zh 语料聚焦的正是中文排版最容易在"PDF 抽取 → DOCX 重建"过程中被破坏的五个点,下面逐一展开。
二、硬规则:CJK 字符之间绝不插入机器空格
中文文本本身没有词间空格,任何"为了让排版好看"而向中文字符之间插入的空格都会直接破坏原文语义与观感。这被pdf2docx定为硬规则(hard rule),违反它即视为转换失败。
该规则在源码中的落点有两处:
- script.ts 的 isNoSpaceScript 明确返回
cjk、kana、thai三种脚本为"无空格脚本",且特意把hangul排除在外——韩文书写本身使用真实词间空格,因此遵循拉丁词距规则而不是无空格规则。 - words.ts 的词法注释 说明:CJK/kana/Thai 字符每个字符自成一词(each form their own word),永远不会被机器插入空格。
这带来一个更细的问题:PDF 引擎在渲染时如果遇到中英混排,有时会在脚本边界处生成一个空格字符(PDFium 的生成物,不承载真实字形)。words.ts 的GENERATED_BOUNDARY_KEEP_EMS = 0.75给出了精确判定:当拉丁↔CJK 脚本边界上的生成空格跨越了至少 0.75 em的真实排版间隙时才保留(例如"5.3 CJK 标题"这类编号场景,Tab 没有字形,生成空格是唯一证据);而对齐(justified)的中文行内部字距伸展永远不会拉开这么大,因此正常中文行不会被误判出空格。
三、中英混排:交替的 cjk/latin 文本片段
中文技术文档几乎必然夹带英文术语与数字(如"PDF 转 DOCX 工具")。转换目标不是把整行揉成一个混合 run,而是让中文字符与拉丁字符各自成为独立的连续片段,便于下游按脚本分配字体槽位。
实现链条如下:
- 分类:script.ts 的
scriptOf基于有序稀疏 Unicode 区间把每个码点归入latin/cjk/kana/hangul/thai/arabic/hebrew/common之一;common(数字、标点等)不属于任何书写系统。 - 切分:spans.ts 的模块注释 说明 span 是 DOCX run 的前身,要求字体族、字号、加粗、斜体、颜色与脚本五者统一;脚本一旦变化就切分 span,所以"中英混排产生交替的 CJK/Latin 片段"是设计使然,而
common字符(数字、标点)跟随当前 span 的脚本归属。 - 重建:rebuild/index.ts 的
runFromSpan把 span 转成 run 时,按脚本决定写入哪个字体槽位(见下一节)。
对应断言见 script.test.ts:'a'→latin、'中'→cjk、'。'→cjk、全角拉丁A(0xFF21)→cjk、半角片假名ア→kana、谚文한→hangul、'1'/'.'/空格→common,希腊字母Ω超出 P1 范围则归为 common。
四、w:eastAsia 字体槽位:CJK 字体的正确落点
OOXML 的w:rFonts允许一个 run 同时声明多个字体槽位:ascii/hAnsi管拉丁字符,eastAsia管东亚字符。若把中文字体错误地只写进ascii槽,Word/WPS 会用系统默认拉丁字体渲染汉字,字形度量(advance)与原文不符,直接导致回排错位。
源码判定非常明确:script.ts 的isEastAsianScript规定cjk、kana、hangul三种脚本族映射到w:eastAsia槽位(韩文谚文虽然走拉丁词距规则,但字体槽位仍属东亚族);泰文、拉丁文不在其列。
落点仍在 rebuild/index.ts 的runFromSpan:isEastAsianScript(span.script)为真时写入run.font(即w:eastAsia),否则只声明run.fontAscii。测试端由 script.test.ts 验证:isEastAsianScript('cjk'/'kana'/'hangul')均为 true,thai/latin为 false。
值得注意的补充细节:中文 CJK 字体家族被刻意排除在输出字体替换表之外。rebuild/fontmap.ts 的模块注释 说明该表只处理拉丁家族的度量兼容替换,CJK 家族因不存在度量兼容的替身,宁可交给 Word 自身的回退渲染,也不做冒险替换;已知的区域性字体伪影问题(regional-artifact)由 analyze/chars.ts 的正规化逻辑处理。
五、全角标点归属:标点必须留在 cjk 片段内
中文全角标点(。、「」、!?等)如果被误判为common而剥离出 cjk 片段,会导致字体槽位错配、标点用错字体。pdf2docx的 Unicode 区间表把全角标点明确划入 cjk:
0x3000–0x303F(CJK 符号与标点,即 。、「」全角标点区)→ cjk,见 script.ts 区间表;0xFF00–0xFF65(全角 ASCII 与全角标点)→ cjk,script.ts;0xFE30–0xFE4F(CJK 竖排形式)→ cjk,script.ts;0xFFE0–0xFFEE(全角符号,如 ¥)→ cjk,script.ts。
测试端同样固化:scriptOf('。')必须等于cjk(script.test.ts),全角拉丁字母A(0xFF21)也归 cjk(script.test.ts)。另外,组合标记/零宽字符(isCombiningMark)覆盖拉丁附加符号、泰文元音声调、阿拉伯文 harakat 以及零宽空格(0x200B)等,它们必须并入前一基础字符的字符簇,避免被误当成独立片段或空格源。
六、首行缩进:检测与 w:ind 还原
中文正文段落普遍采用首行缩进两字符的排版习惯,PDF 里它表现为"首行左缘比其他行右移一段距离"。golden 语料要求转换后还原为真正的段落属性(w:ind w:firstLine),而不是塞进文本前置空格。
检测侧在 blocks.ts 的段块排版分析:对左对齐段落,比较首行与其他行的左缘差,当差值大于0.5 × fontSize且小于上限INDENT_MAX_EMS × fontSize时判定为firstLineIndentPt(下限过滤对齐噪声,上限防止把整段位移误判为缩进);对应的边界条件与左/右/居中对齐判定见 blocks.ts。
重建侧在 rebuild/index.ts 的段格式组装:block.firstLineIndentPt > 0时写入format.indentFirstLine,并按PT_TO_TWIPS(磅转缇,1 磅 = 20 缇)取整,最终输出为<w:ind w:firstLine="…"/>。注意 RTL 块不走此分支(见 rebuild/index.ts),缩进语义对 RTL 方向另行处理。
七、夹具与验证:从 PDFium 生成中文 PDF 到端到端断言
golden 语料的 PDF 由 tests/helpers/fixtures.ts 的buildCjkPdf按需生成:通过 PDFium 的_FPDFText_LoadFont把单个 sfnt 字体嵌入新文档(FPDF_FONT_TRUETYPE),逐行调用_FPDFText_SetText摆放文本对象。选择系统 CJK 字体的路径见 fixtures.ts 的CJK_FONT_PATHS:macOS 的Arial Unicode.ttf、Windows 的arialuni.ttf、Linux 的NotoSansCJK-Regular.ttc(注意buildCjkPdf只接受单面 sfnt,.ttc会被跳过)。这是因为 pdf-lib 内置字体没有 CJK 覆盖,必须依赖宿主系统字体;测试在找不到合适字体时自动跳过(skip),保证跨平台可运行。
每份 golden 用例的断言文件<name>.expected.json由 runner 行走整个golden/目录时读取,校验:按顺序出现的段落文本、每段对齐/首行缩进、图片数量、期望的warnings列表。单元层由 script.test.ts 覆盖脚本分类与谓词,端到端层由tests/integration.test.ts走完整 PDFium 抽取管线。
八、小结:中文 golden 语料的验收口径
packages/pdf2docx/tests/golden/zh/README.md虽然只有短短五条覆盖目标,却是简体中文转换质量的完整验收清单。汇总如下:
| 覆盖目标 | 硬性要求 | 主要实现位置 |
|---|---|---|
| 无机器空格 | CJK 字符之间绝不插入空格(hangul除外) | script.ts、words.ts |
| 中英混排片段 | 脚本变化切分 span,产生交替 cjk/latin runs | spans.ts |
| 字体槽位 | cjk/kana/hangul 写入w:eastAsia | rebuild/index.ts |
| 全角标点归属 | 全角标点(0x3000–0x303F、0xFF00–0xFF65 等)留在 cjk 片段内 | script.ts |
| 首行缩进 | 左对齐段首行位移还原为w:ind w:firstLine(磅转缇) | blocks.ts、rebuild/index.ts |
这些规则共同构成"中文 PDF 转换不破坏原文排版"的底线:既是新增回归用例(issue-<n>-*)的收纳标准,也是每条修复合入前的自动验收门槛。若你正在为pdf2docx贡献中文相关修复,最直接的落地方式是向golden/zh/提交一对"小体积可再分发 PDF +expected.json断言",并确保通过 script.test.ts 与tests/integration.test.ts的端到端校验。
- 人工智能
- AI 应用
- 桌面应用
- AI Agent
- MCP 服务
- AI 技能
【免费下载链接】genoffice
Free, open-source AI Office suite: Docs, Sheets, Slides, PDF, Markdown and HTML editors with a built-in AI agent, plus a `genoffice` CLI and agent skill so Claude Code, Codex and Cursor can create and edit real .docx/.xlsx/.pptx files locally. Bring your own key. macOS, Windows & Linux.
相关推荐
如何用MarkItDown快速将PowerPoint转换为结构化Markdown文档
如何用MarkItDown快速将PowerPoint转换为结构化Markdown文档 你是否经常需要将PowerPoint演示文稿转换为Markdown格式,却
人工智能AI 应用桌面应用AI AgentMCP 服务AI 技能RPG-JS模块开发指南:打造可复用的游戏组件与插件
RPG JS模块开发指南:打造可复用的游戏组件与插件 RPG JS是一个使用TypeScript在浏览器中创建RPG或MMORPG游戏的框架,通过模块和插件系统
游戏开发后端前端Prettier Markdown 中日韩(CJK)文本处理解析:从 symbolSpaceNewLine 测试看换行、标点与空格的格式化规则
Prettier Markdown 中日韩(CJK)文本处理解析:从 symbolSpaceNewLine 测试看换行、标点与空格的格式化规则 本篇技术指南以
开发工具格式化CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考