手写 → PDF
从手写样本构建真正的 .ttf 字体,然后用该字体将文本渲染成看起来像作者亲手写下的 PDF。一切都在本地运行;样本绝不会离开你的机器。
一开始就要告诉用户的真相: 这不是一个能从一张照片凭空发明 3000 个汉字的人工智能。一个字体只能包含样本中实际写下的字符。所以:拉丁文本需要大约 60–100 个手写字符;中文文本需要文档中的每一个不重复的字符(几百个是正常的,几千个则不切实际)。缺失的字符会用中性的手写后备字体绘制并报告——绝不会悄无声息地编造。
0. 定位工具
技能目录是包含此 SKILL.md 的文件夹。通过其启动器调用 CLI:
- Windows:
<skill>\bin\hwpdf.cmd <command> ... - macOS/Linux:
<skill>/bin/hwpdf <command> ...
如果启动器缺失,请先安装(Windows 上运行 <skill>\install.ps1,其他系统上运行 <skill>/install.sh),或者在依赖已就绪时直接运行 python <skill>/scripts/hwpdf.py <command> ...。hwpdf doctor 必须打印 status ok 才能进行后续操作;如果不是,请运行安装程序并报告依赖错误,而不是自行尝试修复。
1. 选择输入流程
| 情况 | 流程 |
|---|---|
| 默认:用户想要一份自己手写的真实文档 | A. 练习表(最佳质量,自动单元格映射) |
| 用户给你一份已经显示他们手写笔迹的 PDF | D. PDF 输入——先尝试嵌入字体,否则从文本层收割字形 |
| 用户已经发送了一张手写照片,并且不想再写更多 | B. 自由照片(由代理标记裁剪的字形) |
用户已有自己的 .ttf/.otf 字体 | C. 仅字体——跳到步骤 4,然后保存配置文件 |
在生成任何内容之前,先询问文档文本:因为练习表只需要文档中出现的字符,所以短文本意味着短练习表。
流程 A — 练习表(推荐)
- 将目标文本放入 UTF-8 文件,例如
hw/doc.txt,然后:
这将生成hwpdf template --text-file hw/doc.txt -d hwhw/template.pdf(和hw/template.json)。每页 30 个单元格,每 30 个不重复字符一页。告诉用户以下几点:- 以 100% / 实际大小 打印(永远不要“适应页面”);
- 用深色笔(0.5 毫米或更粗)描摹每个浅灰色字形——圆珠笔太浅;
- 保持四个黑色角方块可见且未被标记;
- 从上方在均匀光线下拍摄每页,填满取景框,然后发送文件。
- 按页码顺序扫描照片:
透视和小角度旋转会根据角方块进行校正。输出hwpdf scan hw/page1.jpg hw/page2.jpg -d hwhw/glyphs.json和hw/glyphs/*.png。如果一个单元格返回“缺失”,则表示该字形未被书写或太淡——指出是哪些字符,并提供仅重拍那些单元格的机会。 - 构建字体:
hwpdf font -d hw --family "Alex Hand" -o hw/font.ttf - 在渲染前检查覆盖率:
非零退出状态或hwpdf coverage --font hw/font.ttf --text-file hw/doc.txtmissing列表意味着那些字符没有字形。修复方法:仅针对那些字符写一份小练习表并合并进来:
否则,接受中性后备字体并明确说明。hwpdf template --chars "<missing chars>" -d hw2 hwpdf scan hw2/page1.jpg -d hw --append hwpdf font -d hw -o hw/font.ttf - 渲染:
hwpdf render --font hw/font.ttf --text-file hw/doc.txt -o hw/letter.pdf --size 12 --paper ruled - 检查结果。 将页面渲染为 PNG 并使用视觉工具检查:
判断:笔画是否太细/太粗,行距是否太紧,字符是否碰撞或溢出边缘,是否有明显错误?通过hwpdf preview --pdf hw/letter.pdf -d hw/preview --dpi 150--size、--leading、--jitter、--weight(重建字体)、--paper none|ruled|grid修复。
流程 D — 用户提供了一份带有手写笔迹的 PDF
这是最快的途径(当它奏效时),因为 PDF 已经包含了字形。
- 尝试嵌入式字体程序(最佳:完整字形集,无需重新描摹):
好的输出 =hwpdf extract-font --pdf user.pdf -d hwhw/fonts/中的一个.ttf/.otf文件,带有真实字符映射(mapped> 20)。如果报告显示“subset/cid”,则跳过此步骤。 - 否则,如果 PDF 有文本层(当你可以选择字符时就是有),从渲染页面中收割字形——文本层提供标签,光栅图提供墨迹:
hwpdf pdf-specimen --pdf user.pdf -d hw # 添加 --font-name "<family>" 仅保留他们的字体 hwpdf font -d hw --family "Alex Hand" -o hw/font.ttf - 否则,它是扫描件:
hwpdf rasterize --pdf user.pdf -d hw/pages→ 对页面图像执行流程 B,或者要求提供练习表(流程 A)。 - 将结果保存为配置文件(下面的步骤 5),然后渲染。
流程 B — 自由照片
hwpdf segment hw/photo.jpg -d hw→hw/contact.png(编号缩略图)+hw/crops/。- 打开
hw/contact.png并读取编号。编写hw/labels.json为{"0":"A","1":"b",...},或者使用hwpdf label -d hw --pair "0=A 1=B 2=''"(""表示丢弃一个块:点、污渍、页面边缘,或者分成两笔的字形片段)。 hwpdf font -d hw --labels hw/labels.json -o hw/font.ttf,然后执行上面的步骤 4–6。
连笔/草书和复合笔画(如中文字符)使得此流程容易出错;如果结果看起来不好,请切换到流程 A 并说明原因。
流程 C — 用户已有字体
直接跳到 hwpdf coverage 和 hwpdf render,使用 --font <their file> 参数,然后保存配置文件。
配置文件——稳定且可复用的方式
配置文件冻结了重现外观所需的一切:字体文件(复制到配置文件中,并附带 SHA-256 校验)、其默认值以及一个即用即粘贴的提示词。
hwpdf profile save --name alex --font hw/font.ttf --source "user's handwriting pdf"
hwpdf profile list
hwpdf render --profile alex --text-file letter.txt -o letter.pdf
- 配置文件存放在
~/.handwriting-pdf/profiles/<name>/目录下(profile.json、font.ttf、PROMPT.md);设置HWPDF_PROFILE_DIR可重定位。 PROMPT.md是为用户编写的:将其粘贴到任何机器的任何代理上,它将使用相同的布局默认值调用相同的字体。- 在迭代外观?一次只改一个参数——渲染、
preview、比较,然后运行hwpdf profile save --name alex --font hw/font.ttf --size ... --paper ...将其设为新默认值。font --weight 1会重建更粗的笔画;之后重新保存配置文件以使更改生效。 - 从 PDF 构建的配置文件仅覆盖该 PDF 中出现的字符。在承诺完整文档之前,请使用
hwpdf coverage --font <profile font> --text-file <new doc>进行检查。
合成——用户从未写过的字符(实验性)
文档几乎总是需要用户从未写过的字符;在中文中,这是常态而非例外。两种填充方式,按诚实程度排序:
- 中性后备字体(默认,可预测):
render --fallback auto自动选择一个系统手写字体;页面混合了用户的手写体和印刷体或中性手写体,并且render会报告每个以这种方式绘制的字符。 - 合成推断(
hwpdf synth,仍为实验性):构建第二个字体,通过借用用户风格的可测量部分——倾斜角度、笔画粗细、x-高度、字形宽度——并将其覆盖到系统骨架字形上,从而覆盖完整字符集。
hwpdf synth doctor # 哪些引擎可用,缺失什么
hwpdf synth build --style hw/font.ttf --charset common3500 -o hw/synth.ttf
hwpdf synth specimen --font hw/synth.ttf --real hw/font.ttf -o hw/sheet.png # 先检查
hwpdf synth verify --font hw/synth.ttf --charset common3500 # 覆盖率 / 缺失
hwpdf synth eval --font hw/synth.ttf --real hw/font.ttf -o hw/report.md # 报告的数字
hwpdf profile save --name alex --font hw/font.ttf --synth-font hw/synth.ttf
hwpdf render --profile alex --text-file doc.txt -o out.pdf # 合成填补空缺
引擎:stylefit(分析式——无需下载、无需 GPU、确定性;中文字符保留系统无衬线骨架,仅提取用户的倾斜/粗细)和 stub(测试夹具,不是风格迁移)。扩散引擎 diff-font 需要的权重文件的许可问题尚未解决(参见 PLAN.md §8 S4)——synth doctor 会如实说明,而不是假装。
说明其本质: 他们写下的字符会逐字重现;推断出的字符是“他们的倾斜和粗细叠加在印刷体骨架上”。不要告诉用户推断出的部分“看起来像他们”。发货前请检查 <font>.json 中的 meta.gate(rejected 应为 0)并查看样本。
规则
- 仅限本地。 永远不要上传样本、字体或输出;没有 API 调用。
- 只有用户自己的手写体。 拒绝模仿第三方的手写体、签名或信笺抬头,并拒绝生成签名/合同/证书/官方文件——简要解释(伪造、欺诈、证据规则)并提供一份普通的打印文档作为替代。
- 绝不编造质量。 如果字体有 40 个字形,就说 40。如果后备字体绘制了半页内容,就如实说明。逐字报告缺失的字符。
- 在指责工具之前,先要求重拍:模糊、有阴影、角部被裁剪或被网格线覆盖的照片是最常见的失败原因。
- 保留工作目录(
hw/)——用户以后可以添加字符并重建字体。
命令参考
| 命令 | 用途 |
|---|---|
hwpdf doctor | 依赖项 + 系统字体检查 |
hwpdf template --chars "abc" / --text-file doc.txt -d DIR | 练习表 PDF + 元数据 |
hwpdf scan IMG... [-d DIR] [--rotate 90] [--append] | 拍摄的表格 → 字形蒙版(append = 合并到现有工作中) |
hwpdf extract-font --pdf in.pdf -d DIR | 从 PDF 中提取嵌入式字体(最佳情况:完整 TTF) |
hwpdf pdf-specimen --pdf in.pdf -d DIR [--font-name F] | 从 PDF 的文本层 + 光栅图中收割带有标签的字形 |
hwpdf rasterize --pdf in.pdf -d DIR [--dpi 300] | 扫描的 PDF → 页面 PNG |
hwpdf segment IMG... [-d DIR] [--thresh N] [--deskew] | 自由照片 → 块 + 联系表 |
hwpdf label -d DIR --order "ABC" / --pair "0=A 1=B" | 将块映射到字符 |
hwpdf font -d DIR [--labels f] [--family NAME] [--weight -2..2] [--smooth 0..2] -o out.ttf | 构建字体 |
hwpdf coverage --font f --text-file doc.txt | 哪些字符缺失 |
hwpdf render --font f --text-file doc.txt -o out.pdf [--size 12] [--leading 1.8] [--paper ruled] [--jitter 1] [--ink "#1a1c24"] [--fallback auto|none|path] | PDF(--font system:cjk / system:latin = 内置手写字体,当用户没有样本且仅想要手写外观时) |
hwpdf render --profile NAME --text-file doc.txt -o out.pdf | 使用保存的配置文件的字体和默认值生成 PDF |
hwpdf profile save/list/show/remove ... | 冻结/检查可复用的手写配置文件(写入 PROMPT.md) |
hwpdf preview --pdf out.pdf -d DIR [--dpi 150] | PDF 页面 → 用于视觉检查的 PNG |
hwpdf synth doctor / build / specimen / verify / charsets | 可选的推断引擎:从已写子集生成完整字符集的字体(参见合成部分) |
hwpdf selftest -d DIR [--messy] | 合成的端到端冒烟测试 |
限制(在相关时说明)
- 每个字符一个字形:没有连笔草书,没有出现次之间的自然变化,没有压力/墨水纹理。间距抖动是模拟的,字形形状不是。
- CJK:字体仅包含写下的字符。每页 30 个单元格;一封 300 字的信需要一份 10 页的练习表。
hwpdf synth build --charset common3500是捷径,但推断的 CJK 是带有用户倾斜度的系统无衬线骨架,而不是他们实际的笔触。 - PDF 文本层是可搜索/可复制的真实文本(使用生成的字体)。
- 照片必须显示练习表的全部四个黑色角方块。