Back to skills
SKILL.md
Scanned Pdf To Word
ASecurity把扫描件 PDF(公文、表格、通知书)还原为字体字号一致、可编辑可填写的 Word。铁律顺序:①先要原件(有原件则零误差,一切推断作废)②先问发布机关取先验(只缩范围)③像素级字形匹配实测确认(绝不靠目视或套公文规范猜字体)④按扫描 dpi 分级承诺可信度 ⑤交付时标注每个参数的来源与误差范围。当用户说"把 PDF 改成 Word""字体字号保留""扫描件转可编辑"时使用。
- 9 stars
- 0 votes
- 0 copies
- 0 views
- Added September 25, 2026
Security analysis
100/100Pro scans all 16 files and shows the line behind each finding
npx -y skills add CSlawyer1985/legal-skillhub --skill scanned-pdf-to-word --agent claude-codeAre you the author of Scanned Pdf To Word?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/cslawyer1985-scanned-pdf-to-word)---
name: scanned-pdf-to-word
description: 把扫描件 PDF(公文、表格、通知书)还原为字体字号一致、可编辑可填写的 Word。铁律顺序:①先要原件(有原件则零误差,一切推断作废)②先问发布机关取先验(只缩范围)③像素级字形匹配实测确认(绝不靠目视或套公文规范猜字体)④按扫描 dpi 分级承诺可信度 ⑤交付时标注每个参数的来源与误差范围。当用户说"把 PDF 改成 Word""字体字号保留""扫描件转可编辑"时使用。
license: MIT
metadata:
slug: scanned-pdf-to-word
displayName: 扫描件复原转 Word
version: 1.4.1
author: 陆凌燕
agent_created: true
summary: 把扫描件 PDF(公文、表格、通知书)还原为字体字号一致、可编辑可填写的 Word。铁律顺序:①先要原件(有原件则零误差,一切推断作废)②先问发布机关取先验(只缩范围)③像素级字形匹配实测确认(绝不靠目视或套公文规范猜字体)④按扫描 dpi 分级承诺可信度 ⑤交付时标注每个参数的来源与误差范围。当用户说"把 PDF 改成 Word""字体字号保留""扫描件转可编辑"时使用。
---
# 扫描件复原转 Word
**目的**:把"只有图像的扫描件"变成"**可编辑、可填写、且与原件等效**的文档"。
**本质困难**:反推是**欠定推断**——扫描件是文档的有损投影,往回推**一定有错,而且错误在成品上看不出来**。
(实证:验收数据"均差 0.15pt / IoU 0.994",实际 21 项里错 17 项,见 `references/像素反推误差档案.md`。)
**所以本技能要交付的是"一份带可信度标注的文档",不是"一份看起来像的文档"。**
"像"是手段,"能改能填、不被退回"才是目的。
---
## 一、执行卡
### 进门三问(不答不动手)
| 问 | 为什么 |
|---|---|
| 要「**能填能改的附件本身**」,还是「整页视觉复刻」? | 两者验收标准不同,混了必错(**默认前者**) |
| 这份文件的 **Word 原件**能拿到吗? | 拿到就用原件,**本技能全部推断作废** |
| 扫描件**多少 dpi**? | 决定**能承诺到什么程度**——承诺之前先分级 |
**「先定交付目标」的差别**(默认按左列做):
| | ① 还原文档(默认) | ② 复刻页面画像(用户明说才做) |
|---|---|---|
| 页眉页脚 | 只保留属于该文档的;**发文通知的版记/页码一律不要** | 照页面所见全部保留 |
| 页边距 | 照原件声明值 | 照页面实测值 |
⚠️ **动手前先分清:这一页上有几个文档。** 从公文里抽出的一页,页脚的**版记**(抄送/印发)与
**页码**属于**发文通知**,不属于附件本身。判别法:**原件 Word 里有没有 footer/header 文件**、
附件标识是否另起一行顶格。我曾把版记 + 3 条版记线 + 页码一起复刻进附件,多出 5 个元素。
### 输入质量分级(先分级,再承诺)
| 分辨率 | 能承诺 | **不能**承诺 |
|---|---|---|
| ≥300dpi、字体齐全 | 保真复刻:几何 ≤0.5%、字号 ±0.2pt、字体到"款" | — |
| 200~300dpi | 版式复刻 + **字体只到"族"**(宋/黑/楷) | 具体字体款名 |
| 150~200dpi | 内容+版式近似 | 任何"复刻"措辞 |
| <150dpi | 只能给内容(OCR 级) | 保真 |
### 五条不许破的线
1. **先要原件**——有原件则一切推断作废
2. **先验只缩范围,不下结论**
3. **每一处与原件的不同,都要能回答"原件是不是本来就这样"**——答不出就是我自己加的
4. **工具不能倒逼内容**——手段不好用就换手段,别改被验收的文档
5. **交付时按来源分层声明**(原件 / 实证 / 反推),不把推断说成事实
### 误差预算(力气按容差分,不按"哪里容易测")
**结构性属性容差为 0**(错了没法用别的参数补);**度量性属性有容差**。
| 参数 | 容差 | 超了会怎样 |
|---|---|---|
| 元素归属(哪些属于本文件) | **0** | 交付物性质变了(多/少整个元素) |
| `docGrid` / 行距 | **0** | 整块位移,无法用间距补回(实测 36.5pt) |
| 段落数 / 空段 | **0** | 版面整体位移 |
| 全/半角、数字形态 | **0** | 字形与字宽都不同,换行随之变 |
| 字号 | ±0.2pt | 换行漂 1 字;填空线断点错位 |
| 表格列宽 | ±0.5pt/列 | 单元格内**折行数变化**(直接影响可用性) |
| 页边距 / 版心 | ±0.5pt | 整块水平位移 |
| 表格总宽 | ±1pt | 与文本列的溢出量变化(观感) |
| 下划线纵向位置 | ±1.5pt | 观感 |
| 边框粗细 | 0.5~0.75pt 皆可 | 观感(黑白打印更明显) |
⚠️ **三个数字别混用**:**±0.2pt** = 版面敏感阈值(判断值不值得死磕);**≤0.5pt** = 取整档闸门;
**±0.5pt** = 对外声明的不确定度。
### 能力清单(未实测的不许说成"支持")
| 场景 | 状态 |
|---|---|
| 段落式填空表单(无框线) | ✅ 已实测 |
| 带框线表格、**无合并单元格** | ✅ 已实测 |
| 带框线表格含**合并单元格** | ⚠️ 方法可行("缺线"反推 `gridSpan`/`vMerge`),**未实测** |
| 无框线、靠空白对齐的表 | ⚠️ 未做,易多列/少列 |
| 跨页表(表头行重复) | ⚠️ 未做,需 `w:tblHeader` |
| 印章/手写 | ❌ 不复刻;留白或标「(此处盖章)」「(签名)」 |
| 已有文本层的 PDF | ❌ 不适用(走文本抽取) |
### 拿不到原件时:可证伪五件套(至少做前四条)
1. **内部一致性**:列宽合计 = 表格宽 = 版心(或已知溢出量);行高合计 + 上下留白 = 页高
2. **多分辨率交叉**:同一元素在 200/300/600dpi 各测一次,结果应**收敛**
3. **≥3 个独立样本**:字体与字号由 3 处不同位置交叉得出,彼此差异 <0.1pt
4. **误差声明**:写明**这是反推件**,并给范围(几何 ≤0.5% / 字号 ±0.5pt / 字体族级确定、款级存疑)
5. **可用性抽检**:填入示例数据看折行与串行(见第 5 步)
---
## 二、流程
### 第 0 步 先要原件(最高优先)
问一句:**"这份文件是哪个部门发的?他们那边有没有 Word 原版?能拿到我就不用从扫描件反推了,改出来零误差。"**
拿到原件后只剩一件事:**核对原件与手上的扫描件是否同版**(机关可能事后改过)。判据:
| 判据 | 同版特征 |
|---|---|
| 横向 | 表格线**总跨距比值**≈1(允许 0.5% 打印/扫描缩差) |
| 纵向 | 行高总和比值≈1(纵向通常不被缩放) |
| 平移 | 只允许**整体**位移;**个别元素错位就要怀疑不同版** |
不同版时以**扫描件为准**(用户手上那份才是他要的结果),原件值当**强候选**逐一确认,并把差异写进交付说明。
### 第 0.1 步 判断是不是扫描件 + 有没有整页缩放
```python
import fitz
pg = fitz.open(path)[0]
print(len(pg.get_text()), pg.get_images(full=True)) # 文本 0 + 有图 = 扫描件
```
再算 `图片像素宽 ÷ 页面宽(pt) × 72`:**等于 300 说明 1:1 扫描**,测得尺寸即原件真实尺寸;
**不是整数说明被缩放过**,此时只能复刻比例、不能照搬绝对值。
### 第 0.5 步 问发布机关 → 查先验(只缩范围)
问:"这份文件是哪个机关发布的?知道机关我就能先把候选字体缩小,测出来和惯例冲突时也能立刻警觉。
不知道就说'不清楚',我按纯实测走。"
然后查 `references/发文机关先验.md`(含来源分级:【原件】/【实证】/【反推】/【待验】)。
⚠️ **读条文找「限定词」**(这条能救你):带"**一般** / 推荐 / 如无特殊说明"的是**可调整项,必须实测**;
带"**应当** / 必须 / 用…字"(无限定词)的才是**硬规定**,可直接照用。
例:GB/T 9704 附件标识「**用**3号黑体字」是硬的(实测吻合);正文「**一般用**3号仿宋」是软的
(实测为楷体,**不违反**国标——同条明文允许"特定情况作适当调整")。
**别拿"公文正文=仿宋三号"去套表单**——国标管"公文版式",不管"表单内容"。
**先验只用来缩范围**:用 `identify_font.py --filter` 跑完,**必须再跑一次全量对照**——
全量第一名若落在候选集之外,说明先验失准,**信实测**,并把这次实测回填进先验表。
> 先验按机关存,但**同一机关内部不同元素字体也会不同**,每个元素都要单独实测。
### 第 1 步 字体 + 字号双定标(`scripts/identify_font.py`)——不可跳过
**字体靠目视=赌博,用掩膜 IoU 网格搜索给数据。**
```bash
python3 scripts/identify_font.py --pdf scan.pdf --probe # ① 探行带坐标
python3 scripts/identify_font.py --pdf scan.pdf --text "授权委托事项:" --rect 76 466 173 484
python3 scripts/identify_font.py --pdf scan.pdf --text "…" --rect … --filter 楷体 # ③ 先验候选集
```
- **判定规则**:第一名字形分 ≥ 次名 **1.15 倍**(差 ≥0.15)可下结论;<1.08 倍必须换行交叉验证。
同设计变体(简体/繁体/_GBK)脚本已归并,别被"次名是自己"骗了。
- **两侧必须同等膨胀**(默认 `--dilate 1`):只膨胀原件会**系统性偏袒粗笔字体**。
- **短串要连全/半角一起判**:把 `附件1`(全角)与 `附件1`(半角)各跑一次比分数。
⚠️ **全/半角必须每案单独判**,不要沿用上一个案子。
- **字号三口径互校**:墨迹宽法(脚本自带)/**列投影字宽法**(`(末-首)/(n-1)`)/反证法(换字号渲染比几何)。
⚠️ 列投影在短串(≤4 字)上不可信,**必须用 ≥10 字**的串。
⚠️ 反证法用**逐行墨迹投影相关**,别用"墨迹带条数"(下划线与文字并带,条数天然不等)。
- **小字号 + 长串 IoU 会失效**(200dpi、12pt:各候选 IoU 都 0.30~0.37)。两条兜底:
① 降到**族级**结论(写"宋体/黑体/楷体");
② 改用**墨迹密度**(墨迹像素 ÷ 墨迹包围盒面积,**同口径**)——
楷体≈15~17 / 书宋≈17~20 / 黑体≈27~28 / 宋体≈30~34(数据见 `references/像素反推误差档案.md`)。
三条限制:**只能排除不能定案**(样本量 2、与字重强相关);看**量级与排序**别抠绝对值;
**候选必须用本机真实字体**,先 `fc-match <字体名>` 查映射(本机 macOS:`黑体`→Noto Sans SC、
`宋体`→Songti SC,**不是** SimHei/SimSun,密度**不可跨机照抄**)。
- **字号优先取整档**(22/16/14/12/11pt)。要动**字符缩放** `w:w` 必须**三条闸门全过**:
① 实测与整档差 ≤0.5pt 就直接写整档;② 该值由 **≥3 个独立样本**交叉得出、彼此差 <0.1pt;
③ 有原件时一律照抄声明值。写法见 `references/构建规范.md`。
> 反例:原件标题本是**干净的整档值**,我测出小数后写「下调 0.5pt + 缩放 102%」去凑——**把测量噪声写进了文档**。
> (具体数值见 `references/发文机关先验.md` 案例 #2)
### 第 2 步 量几何(页面 / 表格)
- **页面**:页宽高、四边边距、页脚距离、**`w:docGrid`**(照抄)、版心宽。
- **表格网格**:先用 `cv2.HoughLinesP` 测倾角(歪的先转正),再形态学开运算分离长横线/长竖线,
横线数−1 = 行数、竖线数−1 = 列数,线中心距即列宽/行高。
所有竖线纵贯全表 ⇒ 无纵向合并;某行横线缺段 ⇒ 该处有 `gridSpan`。
表内文字先 `cv2.subtract(bw, bitwise_or(横线, 竖线))` 去线再取范围。
- **短串(页码级)**再加"分段拟合":比分段模式(段宽+段间距),只比总宽会选错。
### 第 3 步 构建
```bash
# 表格式:参数驱动,脚本不猜任何值
python3 scripts/build_table_docx.py spec.json -o out.docx
# 段落式:脚本内含案例 #1 的参数(改成你自己的实测值)
python3 scripts/build_form_docx.py
```
- spec 结构见 `references/table-spec-模板.json`;经原件核对的样例见 `references/table-spec-律协附件3.json`。
**spec 必填 `_sources`**(每个参数的来源等级)。
- **构建要点清单**(细节与原理见 `references/构建规范.md`):
`w:rFonts` 三属性齐设(`ascii`/`hAnsi`/`eastAsia`);**ASCII 字体必须是 Times New Roman**(决定 `_` 下沉);
关 `autoSpaceDE/DN`;行距用 `lineRule="exact"`;长填空线手动 `<w:br/>` 断行并**每行留 8pt 余量**;
**`w:docGrid` 照抄原件**(或全段显式行距,**绝不能删网格还留几段没有行距**);
**`w:tblPr` 子元素顺序是强制的**(乱序会被静默忽略);单元格边距用原件值(别乱压 `tblCellMar`);
**表格可以比文本列宽**(`jc=center` 居中溢出,别改页边距迁就);边框 `w:sz` 单位 **1/8pt**。
- ⚠️ **已知不对称**:`spec + _sources + 原件核对样本` 这套机制**目前只有表格分支有**;
段落式只有 `build_form_docx.py`(参数写死在脚本里)。下次做段落式案件、且拿到原件时,顺手把参数导成 spec。
### 第 4 步 校对(渲染器**优先 Microsoft Word**)
```bash
python3 ~/.workbuddy/skills/word-to-pdf-macos/scripts/batch_word_to_pdf.py <out.docx>
```
- **LibreOffice 的纵向位置与折行数在两种情况下不可信**:① 文档带 `w:docGrid`;② 目标字体被回退。
文档无网格且字体齐全时 LO 也可信。根因与实测见 `references/校对与验证.md`。
我曾被 LO 的**假失配**骗着改了不该改的地方(压单元格边距、把表格改左对齐)——**验收工具选错比不验收更危险**。
- **Word 不可用时**(TCC 权限被拒等):换「同引擎独立实现对照」验构建保真度;**绝对禁止**据 LO 渲染改文档。
- **同引擎独立实现对照**(验"构建保真度",不依赖 Word):把原件参数导出成 spec → 跑构建器 →
两份**用同一引擎**渲染,引擎偏差相互抵消。本案 8 横线 + 9 竖线逐条 Δ=0.00pt ⇒ 构建环节零缺陷。
- **验收线与基准定义**见 `references/校对与验证.md`(有原件 / 无原件两套基准,别混)。
- ⚠️ 从扫描件反推的绝对尺寸带 **~0.5% 系统性偏差**,列宽会整体略窄于原件;**别声称"0.1pt 级吻合"**。
### 第 5 步 交付前自检(脚本化,不通过不交付)
```bash
python3 scripts/audit_docx.py out.docx # 参数全表 + 计数 + 一致性
python3 scripts/audit_docx.py out.docx --spec spec.json # 再与 spec 逐项比对(理想:零差异)
```
1. **数量校验(双向条目比对)**——脚本列出段落数/表格行列数/下划线字符数/页眉脚元素/页面元素清单;
**与"原件有什么"的集合必须一一对上**,多出的要能说出理由。**别用眼睛数表格行。**
2. **逐段核对** `w:eastAsia`/`w:ascii`/`w:sz`/`w:w`/行距/缩进/`w:docGrid`/表格列宽行高 —— 读脚本打印的表。
3. 每一**视觉行**宽度 ≤ 可用宽度(CJK 1em、`_` 0.5em 累加,含缩放)。
4. 原文关键句 substring 全在(比对前先去掉 `<w:br/>` 渲染出的 `\n`)。
5. 无占位符/模板残留(`[\(\)\[\]\{\}]|\{\{|XX|待填|TODO|示例`)。
6. **元素归属复核**:逐个页眉/页脚/线框元素反问"它属于这份文档,还是属于页面上的**其他文档**?"
7. **无"未经质询的改动"清单**:把与原件的每一处不同列出来,逐条回答"原件是不是本来就这样?"——答不出就回退。
8. **可用性抽检**("像"之外的验收):**填一遍**——数据格填示例、填空线打字,看
① 折行位置是否变化 ② 是否串行/行高跳变 ③ 是否仍可编辑。**精度是手段,"填得进去"才是目的。**
9. **可信度分层声明落笔**(见下)。
---
## 三、交付说明要写什么
- **有没有拿到原件**:拿到了就说"与原件逐项一致";没拿到必须写明**这是反推件**并给误差范围。
- **参数来源分层**:哪些来自原件、哪些实测、哪些推断(对应 spec 的 `_sources`)。
- **发布机关与惯例**,以及本次实测是否与惯例一致(不一致要点名)。
- 用到的字体名(含 ASCII 字体),方便换机核对。字号若用了字符缩放要说明为什么。
- **哪些元素是我判定"属于/不属于"本文件的**,以及依据(附件标识留、版记页码去)。
- **残留差异**要点名(哪些没复刻、为什么)。
---
## 四、遇到问题去哪查
| 文件 | 管什么 |
|---|---|
| `references/发文机关先验.md` | 机关 → 字体/字号(含来源分级与回填模板);两个案例的逐元素值 |
| `references/构建规范.md` | OOXML 细节:rFonts/行距/`docGrid`/字符缩放/下划线字体度量/表格属性与顺序 |
| `references/校对与验证.md` | 渲染器选择与根因/同引擎对照/验收线/阈值调参/三个工具的分工 |
| `references/像素反推误差档案.md` | 「我错在哪」的完整清单、三类归因、反证实验、墨迹密度判别器数据 |
| `references/坑速查.md` | 症状 → 根因 → 解法(通用坑 + 表格专有坑) |
| `references/table-spec-*.json` | spec 模板 + 唯一经原件核对的样例 |
**改完技能本体,跑 `python3 scripts/self_check.py`**(frontmatter/引用完整性/
摘要表与先验表同值/编号不重复/计数口径/脚本冒烟/关键规则在位),不通过不发布。
---
> 法律科技实务工具 · 维护者陆凌燕律师(北京德恒·无锡)· 关注公众号「鹿鸣于野 UMU」获取更多内容
Files in this skill
- SKILL.md
- _meta.json
- references/table-spec-律协附件3.json
- references/table-spec-模板.json
- references/像素反推误差档案.md
- references/发文机关先验.md
- references/坑速查.md
- references/构建规范.md
- references/校对与验证.md
- scripts/audit_docx.py
- scripts/build_form_docx.py
- scripts/build_table_docx.py
- scripts/identify_font.py
- scripts/measure_scan.py
- scripts/self_check.py
- scripts/verify_layout.py
Attribution
Comments
Loading comments…