Handwriting-simulator

bamboostripMIT活跃维护

把普通文本渲染成逼真的手写体图片,支持手写字体、信纸背景和字距行距笔画随机扰动,提供 GUI 和 CLI 两种用法。

把普通文本变成以假乱真的手写体图片:手写字体 + 信纸背景 + 字距/行距/笔画随机扰动。GUI(PyQt6)与 CLI 双入口,numpy/scipy 高性能渲染引擎。

v0.5.1发布于 2026-10-29 03:08 UTC210star +3 / 7 天28fork同步于 2026-11-10 12:04 UTC

怎么上手

由 README 提炼,执行前请确认
uv sync
uv run python main.py
硬件要求按 README 说明

README 未说明 GPU 或显存要求;操作系统支持 Windows、macOS、Linux,需要 Python 3.12+(项目锁定 3.14)和 uv 包管理器。

支持 Apple Silicon
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

把手写字体、信纸背景和随机扰动组合起来,把普通文本渲染成逼真手写图片,提供 GUI 和 CLI 两种入口

把普通文本渲染成逼真的手写体图片,支持手写字体、信纸背景和字距行距笔画随机扰动,提供 GUI 和 CLI 两种用法。

核心能力

  • 用 numpy+scipy 向量化渲染,预览一页约 0.15 秒
  • 对字距、行距、字号、笔画位移和笔画旋转施加随机扰动
  • 支持错字率模拟,可划掉错字并按右上方或后文重写
  • 在预览图上框选区域,区域内独立排版,支持手写体与打印体混排
  • 导入 PDF 或 Word 文档作为底图,识别表格与高亮填空区域

容易踩的坑

字体文件需自备,版权字体不入库,首次运行会在 exe 旁自动创建 fonts/、backgrounds/、presets/ 目录;框选文字区域与 PDF/Word 文档底图仅 GUI 提供,CLI 不支持;导入 Word 底图建议先在 Word 中另存为 PDF 再导入,避免本机字体渲染差异导致表格错位。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.5.1
  1. v0.5.1最新正式版2026-10-29 · 2026-10-29 03:08 UTC

    更新内容

    Fixed

    • 修复表格单元格的手写文字整体上移、压住单元格上边框:单元格区域纵向此前按 pdfium 的 loose 包围盒生成,而 loose 框是「行高」(约 58px,字形仅约 33px);渲染引擎从区域顶部起笔,于是手写落点比原印刷文字高十余像素、直接压在表格线上(真实 docx 实测)。现命中格纵向改以字形紧包围盒为基准(与擦字口径一致),手写回到原文字位置。
    • 修复单字格出现「印刷字 + 手写字」双影:单字格不参与字符流匹配(防跨页误匹配的既定规则),此前只生成区域、不擦除,底图残留的印刷字会与手写字重叠。现对带推断(未定位)的单元格按「字形中心落在单元格矩形内」逐字擦除,表格线照旧保留。
    • 测试新增:区域纵向贴合紧包围盒(loose 与 tight 不同时的落点)、未定位格擦除自身印刷字形。

    ▶ 查看完整提交记录

    产物下载

    点击链接直接下载本版本对应文件,无需翻找页面底部 Assets。

    说明

    • 便携包解压即用(可执行文件 + presets/ 预设 + backgrounds/ 背景 + fonts/ 字体目录),整个文件夹可随意移动 / U 盘拷贝。
    • 首次运行会自动创建缺失的 fonts/、backgrounds/、presets/ 目录。
    • 字体请自备:商业字体版权原因发布包不含字体,下载后放入 fonts/ 即可,推荐免费可商用的霞鹜文楷(LXGW WenKai)。
    • 预设中的字体路径(如 fonts/云烟体.ttf)为占位名称,放入对应字体文件后即可直接使用,也可以改用其他字体。
  2. v0.5.02026-10-29 · 2026-10-29 02:47 UTC
    查看更新说明

    更新内容

    对齐 Rust 版 0.5.0(handwrite-sim 0.4.3 → 0.5.0):新增 Word 表格识别导入 与 右侧操作栏重排,并同步两个导入链路的修复。

    Added

    • Word 表格识别导入:导入 .docx 底图时自动解析文档表格结构——表格线保留在底图,每个单元格文字提取为可编辑手写区域。
      • 支持跨列(w:gridSpan)与纵向合并(w:vMerge,续行格占位不出区域,未声明跨列时从上方覆盖格继承);空单元格由「行带 × 列带」交集推算矩形;嵌套表格与文本框内容不泄漏进外层格。
      • 单元格高亮色映射笔迹角色(与表外高亮共用同一角色池);无高亮时饱和彩色字色回退映射;{{打印:...}} 格生成印刷体区域;灰色高亮格整格保留印刷底图(不擦除、不生成区域)。
      • 跨页拆分的表格按页各输出自己的一半:每页只从上次消费位置起在页面字符流中做两指针子序列匹配,整表定位比例严格过半才消费,分割中的表钳制流游标(假段落匹配防护)。
      • 区域列表单元格带 表N· 前缀;区域对话框新增「此字体应用到整张表格」批量应用字体与字号;区域字体选择对手写体同样开放(不再限打印体)。
      • 已知限制:整行均为单字格的表格行(如序号行「一 / 二 / 三」)保持印刷底图(单字格不参与定位匹配以防跨页误匹配),其余行不受影响。
    • 右侧操作栏重排(阶段分组 + 折叠):右栏按使用阶段分 6 组折叠——文本内容 / 文字区域 / 笔迹角色 / 导入素材 / 笔迹仿真与版式 / 预设;组头带箭头与计数(文字区域(3)、笔迹角色(4)),折叠状态写入 QSettings 记忆;四个「导入文件」入口(Word 正文 .docx / PDF·Word 打印底图 / 背景图片 / 字体)统一收进「导入素材」组并配语义说明。纯布局调整,功能与数据流不变。

    Changed

    • 主题切换移入底部状态栏:新增状态栏(左侧 v{版本} · 关于与更新、中间运行状态、右侧主题按钮),移除参数面板标题行的「ℹ️ 关于」与主题控件;主题支持「跟随系统 / 浅色 / 深色」三态轮转并持久化(QSettings)。
    • 「选区角色」标记条按需显示:编辑器无选区时整条隐藏,有选区才出现,避免无效点击。
    • 折叠组启用时不销毁内容控件(编辑器焦点/选区、区域列表悬浮态、角色卡内部展开态均保活)。

    Fixed

    • 修复「已安装 LibreOffice,导入 DOCX 底图仍提示需要安装」:Windows 版 LibreOffice 安装时不会把自己加入 PATH,旧逻辑只做 shutil.which("soffice"),因此必然误判为未安装。现按 PATH → 平台常见安装目录(Windows 两处 / macOS / Linux 包管理器)→ Windows 注册表 LibreOffice\UNO\InstallPath 三级探测(与 Rust 版 find_soffice 对齐并补注册表);无头转换使用独立临时用户配置目录(-env:UserInstallation),用户开着 LibreOffice 界面时也能稳定转换、不会把任务转交给 GUI 实例弹窗;仍找不到时错误信息列出「已检查的位置」与三条可执行解决办法。
    • 修复「识别高亮填空」把跨行完整句子切成上下两个文字区域:句子从行中间开始高亮、在下一行行首附近结束时,两行高亮条纹水平投影零重叠,原有 40% 重叠合并规则判为两块。现按换行签名(上行条纹是行尾、下行条纹是行首、同色、垂直间隙 ≤1.5×行框高)补合为单个区域;行尾/行首由行内字符占位判定,单趟原始框配对禁止链式吞并,且在表格分流之后进行、表内高亮不参与配对(防误擦表格线)。
    • 修复 Word COM 转 PDF 成功结果被误判为失败:COM 拉起的 Word 在最后一个文档关闭后可能自行退出,$word.Quit() 抛 RPC 0x800706BE,且 powershell -Command 下异常被捕获后退出码仍会变 1,覆盖成功转换。现收尾 Quit 容错(try/catch),并以 PDF 文件存在性显式决定退出码,与调用方判定口径一致。
    • 修复表格单元格内 {{...}} 标签产生重复区域:标签接管判定由「中心落在单元格内」改为「矩形相交」(标签框含 {{打印: 前缀等模板字符,比正文包围盒更宽,中心可能落在矩形外);被接管的标签仍擦除标签框(单元格路径只擦除命中的正文汉字,模板字符会残留底图),但不再生成独立区域。
    • 修复高亮/标签擦除误伤表格线:单元格路径改为按字符包围盒逐字擦白(glyph 级),并让表内高亮框不整框擦白,表格线得以保留。
    • 修复真实文档的表格线被擦成缺口(LibreOffice/Word 渲染实测):
      1. 字符擦除改纵向用紧包围盒:pdfium 的 loose 包围盒高度是「行高」,Table Grid 这类行高≈行距的表格中,loose 框上下各压住一条边框,按 loose 擦会把整行边框擦出缺口;现纵向取字形紧包围盒(横向仍用 loose 前进宽度),字形照常擦净、边框完整保留。
      2. 标签擦除框对异常字体矩阵免疫:{{打印:...}} 中的 { } : 在 Word/LibreOffice 导出的 PDF 里字号可达 221pt、loose 框纵向跨整张表格,按 loose 擦标签会一次抹掉该列四条表格线;现纵向取紧包围盒并按中位数 2 倍剔除离群字形,标签的 TextRegion 高度也随之回到行高。ExtractedChar 新增 tight_min_y/tight_max_y(旧构造方式默认 0 → 自动回退 loose,向后兼容)。

    ▶ 查看完整提交记录

    产物下载

    点击链接直接下载本版本对应文件,无需翻找页面底部 Assets。

    说明

    • 便携包解压即用(可执行文件 + presets/ 预设 + backgrounds/ 背景 + fonts/ 字体目录),整个文件夹可随意移动 / U 盘拷贝。
    • 首次运行会自动创建缺失的 fonts/、backgrounds/、presets/ 目录。
    • 字体请自备:商业字体版权原因发布包不含字体,下载后放入 fonts/ 即可,推荐免费可商用的霞鹜文楷(LXGW WenKai)。
    • 预设中的字体路径(如 fonts/云烟体.ttf)为占位名称,放入对应字体文件后即可直接使用,也可以
  3. v0.4.42026-09-04 · 2026-09-04 07:57 UTC
    查看更新说明

    更新内容

    Fixed

    • 便携版自动更新重试与真·无窗口执行(修复独占锁未释放导致更新未覆盖及终端弹窗):
      1. 文件锁重试循环:批处理引入 :copyloop 重试循环(最多 20 次,每次 500ms),等待旧版进程完全退出释放 Windows 内核文件独占锁后再覆盖,彻底解决单次复制卡锁失败;
      2. 彻底解决黑框终端弹窗:改用 Windows 原生 GUI 子系统的 WScript.Shell.Run(0 隐藏模式)拉起批处理,避免底层 CREATE_NO_WINDOW 与 DETACHED_PROCESS 互斥触发控制台窗口;
      3. 退出即刻释放锁:调用更新脚本后主进程立即强制退出,加速释放 .exe 文件句柄。

    ▶ 查看完整提交记录

    产物下载

    点击链接直接下载本版本对应文件,无需翻找页面底部 Assets。

    说明

    • 便携包解压即用(可执行文件 + presets/ 预设 + backgrounds/ 背景 + fonts/ 字体目录),整个文件夹可随意移动 / U 盘拷贝。
    • 首次运行会自动创建缺失的 fonts/、backgrounds/、presets/ 目录。
    • 字体请自备:商业字体版权原因发布包不含字体,下载后放入 fonts/ 即可,推荐免费可商用的霞鹜文楷(LXGW WenKai)。
    • 预设中的字体路径(如 fonts/云烟体.ttf)为占位名称,放入对应字体文件后即可直接使用,也可以改用其他字体。
  4. v0.4.32026-09-04 · 2026-09-04 07:33 UTC
    查看更新说明

    更新内容

    Fixed

    • 版本检查多级容灾降级(彻底解决 GitHub API 403 限流与连接失败): GitHub REST API 对未鉴权 IP 有 60 次/小时频次限制,开发测试或共享代理出口 IP 极易触发 HTTP 403: rate limit exceeded 导致检查更新报无法连接。现对齐 Rust 版引入多级容灾:
      1. 优先查询 GitHub REST API;
      2. API 受限或网络超时时,自动无缝降级至 GitHub Releases Atom 订阅源 + expanded_assets 资产页(走 github.com 网页域,免 API 频次限制);
      3. 仍失败时通过网页 302 重定向探测最新 Tag; 同时优化关于窗口状态文案,并新增单元测试锁定容灾降级逻辑。

    ▶ 查看完整提交记录

    产物下载

    点击链接直接下载本版本对应文件,无需翻找页面底部 Assets。

    说明

    • 便携包解压即用(可执行文件 + presets/ 预设 + backgrounds/ 背景 + fonts/ 字体目录),整个文件夹可随意移动 / U 盘拷贝。
    • 首次运行会自动创建缺失的 fonts/、backgrounds/、presets/ 目录。
    • 字体请自备:商业字体版权原因发布包不含字体,下载后放入 fonts/ 即可,推荐免费可商用的霞鹜文楷(LXGW WenKai)。
    • 预设中的字体路径(如 fonts/云烟体.ttf)为占位名称,放入对应字体文件后即可直接使用,也可以改用其他字体。
  5. v0.4.22026-09-04 · 2026-09-04 07:04 UTC
    查看更新说明

    更新内容

    Fixed

    • 修复打包版检查更新永远失败(无法连接至 GitHub Releases API): HandWriteSim.spec 在 Windows 下误删了 libssl-3-x64.dll / libcrypto-3-x64.dll(CPython _ssl.pyd 的依赖),导致打包版所有 https(检查更新 / 下载)必然失败。现仅剔除无用的 Qt DLL,保留 OpenSSL,并新增 tests/test_build_spec.py 回归测试锁定。

    ▶ 查看完整提交记录

    产物下载

    点击链接直接下载本版本对应文件,无需翻找页面底部 Assets。

    说明

    • 便携包解压即用(可执行文件 + presets/ 预设 + backgrounds/ 背景 + fonts/ 字体目录),整个文件夹可随意移动 / U 盘拷贝。
    • 首次运行会自动创建缺失的 fonts/、backgrounds/、presets/ 目录。
    • 字体请自备:商业字体版权原因发布包不含字体,下载后放入 fonts/ 即可,推荐免费可商用的霞鹜文楷(LXGW WenKai)。
    • 预设中的字体路径(如 fonts/云烟体.ttf)为占位名称,放入对应字体文件后即可直接使用,也可以改用其他字体。

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    手写模拟器(HandWriteSim)

    把普通文本变成以假乱真的手写体图片:选择一款手写字体、一张信纸背景,程序会按真实的书写习惯排版并施加字距、行距、字号、笔画位移、笔画旋转等多种随机扰动,让每个字、每一页都独一无二。

    提供 图形界面(GUI) 与 命令行(CLI) 两种使用方式,核心引擎基于 numpy + scipy 全向量化重写,预览渲染一页约 0.15 秒,实时交互流畅。

    界面预览

    主界面:输入文本、选择字体 / 背景、调整排版参数,右侧实时预览渲染效果,支持多页翻页与一键导出 PNG / PDF:

    Python 版主界面

    框选文字区域:在预览图上拖拽画框,框内文字独立排版渲染,支持手写体 / 打印体混排与逐区域参数覆盖:

    框选文字区域示例

    与 Rust 版的关系

    本项目另有 handwrite-sim(Rust 版)——纯 Rust 重写(ab_glyph + 自研笔画扰动引擎),两个版本功能保持同步:

    Python 版Rust 版
    渲染引擎numpy + scipy(FastEngine)纯 Rust:ab_glyph + 自研笔画扰动引擎
    界面PyQt6Tauri 2 + Vue 3(系统 WebView 渲染;Rust 命令层复用核心引擎)
    PDF 导出✅(同步对齐)✅ 300 DPI 位图层 PDF(printpdf + lopdf)
    错字率模拟✅(同步对齐)✅ 错字率 + 划掉重写(单线/双线/斜线/叉号四种涂改样式)
    框选文字区域 / 文档底图✅(同步对齐)✅(该功能由 Rust 版先行实现,Python 版已对齐)
    docx 导入python-docxzip + quick-xml 自研解析(对齐/首行缩进)
    预览降采样PIL resizefast_image_resize(SIMD,32MP 背景毫秒级)

    两个版本功能保持同步(PDF 导出、错字率模拟 Python 版已对齐)。 由于 Rust 版性能优异,部分新功能可能由 Rust 版先行实现,随后同步到 Python 版。

    技术栈

    类别选型
    语言 / 运行时Python 3.14(.python-version 锁定)
    依赖管理uv(pyproject.toml + uv.lock)
    界面PyQt6(纯 Qt 控件 + 自动布局,无背景图片依赖)
    渲染引擎numpy + scipy(FastEngine,默认);handright 8.2.0(可选经典后端)
    图像处理Pillow
    PDF 导出img2pdf(300 DPI 位图层,PNG 无损嵌入)
    PDF 栅格化pypdfium2(PDF 文档底图逐页渲染)
    docx 解析python-docx
    测试pytest
    打包PyInstaller(onefile 单文件,跨 Windows/macOS/Linux,HandWriteSim.spec)

    功能特性

    GUI(图形界面)

    • 富文本输入:多段文本、空行,所见即所得
    • 段落排版工具:左对齐 / 居中 / 右对齐 / 首行缩进(按 2 倍字体大小),支持整段应用
    • 导入 docx:自动解析段落对齐方式与首行缩进(支持 Word 的「首行缩进 2 字符」写法,沿样式链继承);若文档带有高亮 / 背景色,导入时弹窗选择排版方式——「全部作为手写」忽略高亮整篇手写,「打印/手写混排」则高亮文字用手写体、未高亮文字保留打印体
    • 划选标记与笔迹角色:正文编辑器中划选文字后一键「设为打印 / 角色1 黄 / 角色2 绿 / 清除标记」,以背景色直观预览;「笔迹角色管理」为每个角色单独配置字体 / 颜色 / 扰动 σ,打印体角色强制零扰动零错字;Word 中首次出现的背景高亮自动映射为手写角色1,次出现映射为手写角色2……
    • 字体 / 背景选择:字体支持 .ttf .ttc .otf;背景支持 .png .jpg .jpeg .bmp .webp
    • 文字颜色:直接输入 #RRGGBB 十六进制颜色值
    • 排版参数:字水平间距、字竖直间距(行距)、字体大小,每个都带独立的随机扰动 σ
    • 笔画扰动:水平位移、竖直位移、笔画旋转三个独立扰动强度
    • 写错字模拟:错字率 0~30% 滑杆调节,错字划掉后按「右上方重写」(正上方略偏右、小一号、同一支笔加粗)或「后文重写」两种方式补写,涂改样式支持单线 / 双线 / 斜线 / 叉号
    • 框选文字区域:预览图上拖拽画框,文字在框内独立排版渲染,支持手写体 / 打印体混排;区域可指定所在页(如第 2 页框选)、列表悬浮高亮、单击显示可拖动 / 8 向缩放的调整框二次微调、双击重新编辑;表格导入生成的单元格区域在列表里带 表N· 前缀,区域对话框可一键「此字体应用到整张表格」
    • 区域编辑对话框:内置富文本段落编辑器(逐段对齐 / 首行缩进、行状态提示、一键导入 docx 段落);字体文件对手写体与打印体都可单独指定;排版与扰动参数可逐区域覆盖(字距 / 行距 / 字号及 σ、笔画扰动、错字率与涂改样式、文字颜色、四向内边距),未设置项跟随主设置,打印体区域强制零扰动零错字;区域内容在所在页内排版,超出框选范围自然截断
    • 导入 PDF / Word 文档底图:任意 PDF(pypdfium2 栅格化)或 DOCX(Word COM / LibreOffice 转 PDF)逐页渲染为多页背景,直接在「打印稿」上框选手写填写,适合表格、试卷、实验报告等场景;强烈推荐先在 Word 中另存为 PDF 再导入,版式完全固化、像素级还原,避免 Word 直接导入受本机字体/渲染差异导致的表格错位;文档中的高亮色块或 {{...}}、【...】 占位标签会被自动检测——选「提取填空框」则擦除高亮底色、自动生成手写填空区域并按高亮颜色关联笔迹角色,选「保留完整底图」则原样保留色彩不生成填空(适合自带色块 / 表格样式的完整模板)
    • Word 表格识别导入:导入 .docx 底图时自动解析表格结构——表格线原样保留在底图上,每个单元格的文字被提取为可编辑的手写区域;支持跨列(gridSpan)与纵向合并(vMerge,续行格不重复出区域)、空单元格按行列带推算矩形、单元格高亮 / 彩色字自动映射笔迹角色、{{打印:...}} 格生成印刷体区域、灰色高亮格整格保留印刷底图;跨页拆分的表格按页各自输出自己的一半,不重复不丢失(对齐 Rust 0.5.0 实现)
    • 纯背景预览:无需输入文字,选好背景(图片或导入文档)即可直接预览,方便先摆好版式再逐页框选
    • 边距设置:上 / 下 / 左 / 右 位置式布局,输入框位置即含义
    • 边界提示(仅预览):开关 + 自定义颜色,非渲染区域半透明着色并绘制边距框线,直观看清文字实际渲染边界(默认关闭)
    • 实时自动预览:停止输入 300ms 后自动渲染,全程后台线程不卡界面;参数不完整时静默跳过
    • 多页预览:上一页 / 下一页 / 页码指示,自动分页
    • 预览底色切换:浅灰绿 / 深灰两档,背景图与底色撞色时可切换以区分边界
    • 预设系统:预设文件夹(presets/)内预设可直接下拉切换,也支持保存 / 载入任意位置(JSON 格式,颜色为 #RRGGBB),旧版预设自动兼容
    • 右栏六组折叠布局:参数面板按使用阶段分为「文本内容 / 文字区域 / 笔迹角色 / 导入素材 / 笔迹仿真与版式 / 预设」六组,组头带箭头、计数徽标与折叠记忆(QSettings);四种「导入文件」入口(Word 正文 .docx / PDF·Word 打印底图 / 背景图片 / 字体)统一收进「导入素材」组并配语义说明;「选区角色」标记条仅在编辑器选中文字时出现
    • 深色模式与状态栏:底部状态栏左侧为「v{版本} · 关于与更新」,右侧一键轮转「跟随系统 / 浅色 / 深色」主题并即时生效、重启后保持
    • 一键导出:全部页面导出为 0.png、1.png……到所选目录(默认 output/),或一键导出 300 DPI 位图层 PDF(img2pdf,PNG 无损嵌入)

    CLI(命令行)

    • 纯文本或 docx 输入,批量生成手写图片
    • 全部排版 / 扰动 / 颜色参数可用命令行指定
    • 载入预设作为基础参数,命令行参数可覆盖
    • --save-preset 生成后保存参数预设
    • 未指定背景时自动生成纯白背景,可用 --width / --height 控制尺寸
    • --preview-only 仅渲染第一页,快速查看效果
    • --pdf 直接导出 300 DPI 位图层 PDF(替代图片导出)
    • --miswrite-* 系列参数启用错字模拟(错字率 / 重写方式 / 涂改样式)

    渲染引擎

    • 高性能:连通区域提取用 scipy.ndimage.label(C 实现),笔画扰动一次为全部笔画生成随机参数并做向量化坐标变换(旋转 + 平移),替代 handright 的逐像素 Python 循环
    • 两种后端:默认 FastEngine;可切换 backend="handright" 使用经典引擎对照
    • 段落化排版:标题居中、右对齐、首行缩进、空行占位,逐行流式跨页,与纯文本路径行为一致
    • 真实书写习惯:标点不换行(end_chars)、行首禁则字符(start_chars)等换行规则
    • 随机性可控:引擎与排版各自持有随机源,可传入 seed 复现结果

    环境要求与安装

    • 操作系统:Windows / macOS / Linux(GUI 与打包均支持)
    • Python 3.12+(项目锁定 3.14)
    • 包管理器:uv
    # 一键安装全部依赖(自动按 .python-version 选择 Python 版本)
    uv sync
    
    # 如需开发/打包依赖(pytest、pyinstaller)
    uv sync --extra dev
    

    依赖清单

    包版本用途
    handright8.2.0经典手写渲染后端(可选)
    numpy>=2.5.1向量化渲染计算
    scipy>=1.18.0连通区域标记等
    pillow>=11,<13图像绘制与读写
    PyQt6>=6.6图形界面
    python-docx>=1.2.0docx 解析
    img2pdf>=0.6.3PDF 位图层导出
    pypdfium2>=4.30PDF 文档底图栅格化
    pytest>=8.0(dev)测试
    pyinstaller>=6.0(dev)打包

    快速开始

    启动图形界面

    uv run python main.py
    # 或
    uv run handwrite-gui
    

    操作流程:输入文字 → 选择字体 → 选择背景 → (可选)调整排版参数与颜色 → 点「预览」或直接「导出」。

    便携模式(推荐)

    exe 首次运行会自动在自身所在目录创建 fonts/、backgrounds/、presets/ 三个文件夹:

    • fonts/:把字体文件(.ttf / .ttc / .otf)放进去,选择字体时默认打开此目录
    • backgrounds/:放背景图片,选择背景时默认打开此目录
    • presets/:放预设文件,界面上的预设下拉框会列出此目录内的预设,一键切换

    整个文件夹(exe + 资源目录)可以随意拷贝到任意位置,所有相对路径都以 exe 所在目录为锚点,无需修改任何路径。

    命令行示例

    # 最简:微软雅黑字体,导出到 output/
    uv run handwrite-cli "你好,世界!" --font C:/Windows/Fonts/msyh.ttc
    
    # 指定背景与字号,多页文本自动分页
    uv run handwrite-cli "第一页正文……第二页正文……" --font f.ttf --background bg.png --font-size 40
    
    # 载入预设 + 命令行覆盖参数
    uv run handwrite-cli --preset preset.json "文本内容" --font-size 48 --out output
    
    # 导入 docx(解析对齐与首行缩进)
    uv run handwrite-cli --docx 通知.docx --font f.ttf --background 信纸.png
    
    # 仅预览第一页
    uv run handwrite-cli "你好" --font f.ttf --background bg.png --preview-only
    
    # 生成并保存当前参数为预设
    uv run handwrite-cli "你好" --font f.ttf --background bg.png --save-preset my.preset.json
    
    # 直接导出 PDF(300 DPI 位图层)
    uv run handwrite-cli "你好,世界!" --font f.ttf --background bg.png --pdf output/手写.pdf
    
    # 开启错字模拟:5% 错字率,右上方小字重写,单线涂改
    uv run handwrite-cli "很长很长的正文……" --font f.ttf --background bg.png --miswrite-rate 0.05 --miswrite-mode above --miswrite-style line
    

    框选文字区域与 PDF / Word 文档底图为 GUI 功能(涉及画布交互),CLI 暂不提供。

    CLI 参数说明

    参数类型默认值说明
    text位置参数""要处理的手写文本
    --docx路径""导入 docx(解析对齐与首行缩进),与 text 互斥优先
    --font路径必填字体文件(.ttf / .ttc)
    --background路径""背景图片;缺省时生成纯白背景
    --width / --heightint800 / 1200纯白背景尺寸
    --out目录output输出目录
    --preset路径""载入预设(.json / .txt / .preset)
    --save-preset路径""生成后保存参数为预设
    --preview-onlyflag关仅渲染第一页保存为 preview.png
    --font-sizeint36字体大小
    --word-spacingint5字水平间距
    --line-spacingint48字竖直间距(行距,不含字高)
    --left/right/top/bottom-marginint30四周边距
    --word-spacing-sigmaint2字间距随机扰动
    --line-spacing-sigmaint2行距随机扰动
    --font-size-sigmaint2字号随机扰动
    --perturb-x-sigmaint2笔画水平位移扰动
    --perturb-y-sigmaint2笔画竖直位移扰动
    --perturb-theta-sigmafloat0.05笔画旋转扰动(弧度)
    --red / --green / --blueint0文字颜色 RGB(0-255)
    --pdf路径""导出 PDF 到指定文件(替代图片导出,300 DPI)
    --miswrite-ratefloat0.0错字率 0~1(默认 0 关闭)
    --miswrite-modeenumabove错字重写方式:above(右上方小字重写)/ rewrite(后文重写)
    --miswrite-styleenumline涂改样式:line / double_line / slash / cross

    预设文件格式

    JSON 预设(v2,现行格式)

    保存到 .json 时使用版本 2 结构,只保存排版参数(不含文本内容),颜色为 #RRGGBB 十六进制:

    {
      "version": 2,
      "params": {
        "color": "#000000",
        "font_path": "C:/Windows/Fonts/msyh.ttc",
        "background_path": "D:/信纸/横线信纸.png",
        "font_size": 36,
        "word_spacing": 5,
        "line_spacing": 48,
        "left_margin": 30,
        "right_margin": 30,
        "top_margin": 30,
        "bottom_margin": 30,
        "word_spacing_sigma": 2,
        "line_spacing_sigma": 2,
        "font_size_sigma": 2,
        "perturb_x_sigma": 2,
        "perturb_y_sigma": 2,
        "perturb_theta_sigma": 0.05,
        "end_chars": ",。",
        "start_chars": ""
      }
    }
    

    预设刻意不包含 text / paragraphs:文本属于内容而非排版风格,载入预设时保留当前输入框内容。

    兼容性

    旧格式载入行为
    JSON v1(red / green / blue 数字)自动转换载入,等价于新 hex 颜色
    旧版 18 行纯文本(.txt / .preset)格式不变,照常载入

    旧预设载入后若再保存,将统一以新格式(v2 + #RRGGBB)写出。

    项目结构

    Handwriting-simulator/
    ├── main.py                      # GUI 启动入口(uv run python main.py)
    ├── pyproject.toml               # 项目元数据、依赖声明、脚本入口、uv 依赖覆盖
    ├── uv.lock                      # 依赖锁定文件
    ├── .python-version              # Python 版本锁定(3.14)
    ├── HandWriteSim.spec            # PyInstaller 打包配置(onefile,跨平台)
    ├── build.ps1                    # Windows 一键打包脚本
    ├── backgrounds/                 # 内置背景素材(随仓库分发,可自行替换)
    ├── presets/                     # 内置预设示例(JSON v2,相对路径引用资源)
    ├── fonts/                       # 运行时自动创建,用户自备字体(版权字体不入库)
    ├── ui/                          # GUI 资源
    │   └── 3d.ico                   # 窗口图标
    ├── docs/
    │   ├── images/                    # README 界面示例图
    │   └── superpowers/               # 设计文档(段落化排版功能的设计与实现计划)
    │       ├── plans/
    │       └── specs/
    ├── src/handwritesim/            # 包源码(src 布局)
    │   ├── app.py                   # GUI 应用入口(QApplication + MainWindow)
    │   ├── cli.py                   # 命令行入口(argparse)
    │   ├── core/                    # 核心逻辑层(不依赖任何 GUI 组件)
    │   │   ├── models.py            # HandwritingParams / Paragraph 数据模型、校验、序列化
    │   │   ├── paths.py             # 资产目录解析(exe 旁/项目根)与目录自动创建
    │   │   ├── engine.py            # 引擎门面:统一接口,默认 fast 后端,可切 handright
    │   │   ├── engine_fast.py       # FastEngine:numpy/scipy 高性能渲染
    │   │   ├── engine_handright.py  # HandrightEngine:handright 经典后端
    │   │   ├── docx_io.py           # docx 解析:段落对齐 + 首行缩进(含样式链继承)、表格 body 元素流
    │   │   ├── docx_tables.py       # docx 表格视图与单元格定位(跨页匹配 / 行列带矩形推断)
    │   │   ├── doc_render.py        # PDF/DOCX 文档底图栅格化(pypdfium2 / Word COM / LibreOffice)+ 表格/高亮区域识别
    │   │   └── presets.py           # 预设读写:JSON v2 + 旧版文本兼容 + 相对路径双向转换
    │   └── gui/                     # 图形界面层
    │       ├── ui.py                # Qt 界面构建(纯控件 + 自动布局 + 六组折叠面板 + 主题偏好)
    │       ├── collapsible.py       # 可折叠分组控件(标题计数 + 折叠态持久化)
    │       ├── main_window.py       # 主窗口逻辑:参数映射、事件、预览降采样、框选区域与文档底图管理
    │       ├── region_dialog.py     # 框选区域编辑对话框:段落编辑器 + 逐区域参数覆盖面板 + 整表字体
    │       ├── workers.py           # QThread 后台渲染 / 导出,信号回传结果
    │       └── resources.py         # 资源路径解析(开发 / PyInstaller 双环境)
    └── tests/                       # pytest 测试
        ├── test_docx_io.py          # docx 对齐与缩进解析
        ├── test_docx_tables.py      # 表格结构解析与单元格定位匹配(跨页分割 / 合并格 / 带推断)
        ├── test_table_render.py     # 表格区域生成、字符级擦除、换行签名合并、高亮分流
        ├── test_doc_background.py   # 文档底图(PDF/DOCX 打印预览)与逐页背景
        ├── test_docx_conversion.py  # docx→PDF 转换器探测(PATH/安装目录/注册表)与失败提示
        ├── test_engine.py           # 引擎接口、校验、参数序列化
        ├── test_engine_fast.py      # FastEngine 渲染与段落路径
        ├── test_gui_consistency.py  # GUI 预览/导出一致性回归
        ├── test_miswrite.py         # 错字划掉重写(错字率驱动)
        ├── test_pdf_export.py       # PDF 导出(img2pdf 位图层,300 DPI 页尺寸)
        ├── test_presets.py          # 预设读写、hex 颜色、相对路径往返、新旧格式兼容
        ├── test_preview_label.py    # 预览坐标换算(系统 DPI 缩放下框选不错位)
        └── test_regions.py          # 框选文字区域(手写/打印混排)引擎
    

    分层约定

    • core/ 不 import 任何 GUI 模块,GUI 与 CLI 共同依赖 core,保证命令行与图形界面行为一致
    • 数据流:GUI/CLI → HandwritingParams(校验)→ HandwritingEngine → 图片
    • 界面控件名沿用历史命名(lineEdit_10 等),main_window.py 通过对象名引用,与 ui.py 解耦

    渲染引擎原理

    FastEngine(默认)

    针对 handright 逐像素纯 Python 循环的性能瓶颈,用 numpy + scipy 重写:

    1. 排版:复用 PIL C 层 ImageDraw.text 逐字绘制(本就快,非瓶颈),逐字字号 / 行位高斯扰动,字符宽度按 (字号, 字符) 缓存
    2. 连通区域提取:scipy.ndimage.label(C 实现)一次得到所有笔画的像素标签,替代原 Python DFS
    3. 笔画扰动:对每个笔画用 numpy 向量化坐标变换(绕笔画中心旋转 + 平移),一次写回画布

    段落渲染路径

    params.paragraphs 非空时启用:每个段落独立排版(对齐 / 首行缩进),按行提取墨迹后逐行流式拼接跨页;行节奏与纯文本路径完全对齐,空行保留占位。居中按行测量水平置中;右对齐按行逻辑宽度(含尾部空格)平移到右边距。

    区域渲染路径

    params.regions 非空时,每个框选区域在其指定页内独立排版:区域文本按 region.paragraphs(或区域对齐 / 缩进设置)转为段落流,套用逐区域覆盖参数(未设置项跟随主设置;打印体区域强制零扰动、零错字),墨迹绘制限制在框选矩形内,超出部分自然截断、不跨页延伸。区域随机源独立(random.Random(f"{seed}|region{i}")),与主文本互不干扰,同 seed 下预览与导出逐像素一致。

    随机性

    • 排版随机源:random.Random(seed),保证逐字扰动序列可复现
    • 笔画扰动随机源:numpy.random.default_rng(seed)
    • 预览与导出的随机行为一致(预览仅针对超大背景做参数等比缩放)

    换行规则

    • end_chars(默认 ,。):行尾遇到这些字符时不换行,避免标点孤悬行首
    • start_chars:行首禁则字符

    预览降采样策略

    背景宽度超过 4096px 时,预览将背景缩放到 4096 宽,并按同一比例缩放全部空间参数(字号、边距、间距、扰动),保证预览与导出布局一致、行线不错位;导出始终使用原始参数全分辨率渲染。

    开发指南

    # 运行全部测试
    uv run pytest
    
    # 运行单个测试文件
    uv run pytest tests/test_presets.py -v
    

    当前测试覆盖:引擎生成 / 预览 / 导出、参数校验、段落往返序列化、docx 对齐与缩进解析、预设新旧格式兼容、hex 颜色解析等。

    代码规范要点

    • 核心逻辑放 core/,GUI 只做控件映射与任务调度
    • 渲染 / 导出在 QThread 中执行,禁止在子线程操作控件
    • 参数校验统一在 HandwritingParams.validate(),GUI 与 CLI 共用
    • 数字输入框禁用滚轮改值(NoWheelSpinBox),避免与滚动面板冲突

    设计文档

    功能开发遵循「先设计后编码」流程,相关设计文档位于 docs/superpowers/specs/ 与 docs/superpowers/plans/(如段落化排版的设计与实现计划)。

    字体与版权

    仓库不随包分发任何字体:手写体多为商业版权字体(如汉呈、华阳、云江等字库),开源分发需授权,故请用户自备字体。推荐以下开源 / 免费可商用的手写体(下载后放入 fonts/ 即可):

    字体协议说明
    霞鹜文楷(LXGW WenKai)OFL 1.1开源可商用,最接近手写体观感
    沐瑶随心手写体免费可商用灵动手写风格
    站酷小薇 / 站酷快乐体免费可商用站酷字库出品
    内海字体(NeiHai)开源手写圆体

    仓库内置的 backgrounds/(信纸、格子纸等)与 presets/(参数示例)均为原创素材,可自由使用与分发。

    打包发布

    # Windows
    powershell -ExecutionPolicy Bypass -File build.ps1
    
    # macOS / Linux(等价命令)
    uv run --extra dev pyinstaller --noconfirm --clean HandWriteSim.spec
    

    也可以直接在 GitHub Actions 中构建:推送 v* 标签或手动触发 Build and Release workflow(.github/workflows/build.yml),会自动在 Windows / Linux / macOS 三平台打包并组装便携 zip + 单文件升级包(exe + 预设 + 背景 + fonts 目录),打标签时自动发布到 Release。

    • 产物:dist/HandWriteSim(单文件,Windows 约 55 MB,Linux/macOS 体积相近);Release 中每个平台另有便携 zip(首次使用,解压即用)与单文件升级包(老用户直接替换旧可执行文件即可,应用内自动更新用的就是这个文件)
    • 便携模式:首次运行自动创建 fonts/、backgrounds/、presets/ 目录,把资源放进去即可,整个文件夹可随意拷贝
    • 打包配置见 HandWriteSim.spec(PyInstaller onefile 模式),已按 sys.platform 跨平台适配:
      • imageformats 插件按平台文件名收集(qjpeg.dll / libqjpeg.so / libqjpeg.dylib)
      • 未使用 Qt DLL 的剔除清单仅 Windows 生效,其余平台保留避免启动失败
      • 图标仅 Windows 支持 .ico,Linux/macOS 使用默认图标
      • UPX 压缩仅非 macOS 平台启用(不破坏签名)
    • 首次启动时单文件会自解压到系统临时目录,启动稍慢属正常现象
    • 首次运行若被杀毒软件(如 Windows Defender)实时扫描拦截,等待扫描完成或将其加入白名单后即可正常启动(PyInstaller 单文件程序的共性现象,并非程序问题)

    常见问题

    为什么没有随包字体? 手写体多为商业版权字体,开源分发需授权,故仓库不提供字体。请自备字体放入 fonts/ 目录,或从上方「字体与版权」章节的开源字体清单中下载。预设中的字体路径为占位(如 fonts/云烟体.ttf),对应的字体文件放入后即可使用,也可以直接改用其他字体。

    为什么预览没有边界提示? 边界提示默认关闭,勾选「边界提示(仅预览)」后按边距参数在预览图上着色并绘制框线,可自定义提示颜色(#RRGGBB)。

    载入预设后文本被清空了吗? 不会。预设只包含排版参数,载入时保留当前文本输入框内容;仅当旧预设本身携带文本时才会回填。

    自动预览什么时候不触发? 字体 / 背景缺失时静默跳过,不弹窗打断输入;未输入文字但背景就绪时会渲染纯背景预览(方便先摆版式再框选),输入停止 300ms 后触发。

    导入 Word 文档底图需要什么环境? PDF 底图开箱即用(内置 pypdfium2 栅格化);DOCX 底图需要本机装有 Microsoft Word(COM 自动化,仅 Windows)或 LibreOffice 其一。LibreOffice 无需加入 PATH:程序会依次检查 PATH、各平台默认安装目录(如 C:\Program Files\LibreOffice\program、/Applications/LibreOffice.app/Contents/MacOS)与 Windows 注册表 LibreOffice\UNO\InstallPath;仍找不到时,报错里会列出已检查的位置与解决办法。手动改选图片背景时,文档底图状态自动失效。

    导入时弹出的「检测到高亮」对话框怎么选?

    • 导入 docx 到正文:选「全部作为手写」则忽略高亮整篇手写;选「打印/手写混排」则高亮文字用手写体、未高亮文字保留打印体。
    • 导入 PDF / Word 文档底图:选「提取填空框」会擦除高亮底色、自动生成手写填空区域并按颜色关联笔迹角色;选「保留完整底图」则不擦除、不生成填空区域,适合带色块 / 表格样式的完整模板。

    软件里能查看功能使用说明吗? 可以。点底部状态栏左侧的「v{版本} · 关于与更新」→「📖 使用指南」,分章节介绍了快速上手、基础排版、docx 智能混排、文档底图与表格识别、错字模拟等内容。

    Word 表格导入后为什么有的格子没生成手写区域? 表格识别按「行带 × 列带」定位单元格:整行都是单字格(如序号行「一 / 二 / 三」)时没有可定位的多字格提供行带,该行保持印刷底图(这是为避免跨页单字误匹配而设的已知限制);灰色高亮格与空单元格也按规则保留印刷底图。其余单元格、合并单元格均可正常生成。

    框选区域里的文字超长会怎样? 区域内容只在所在页的框选矩形内排版,超出部分自然截断、不会跨页延伸;区域编辑对话框内有单页截断提示。需要更多内容时可加大框选范围或另开区域。

    试卷 / 表格文档高亮手写填空教程

    适合实验报告、试卷答题、各类申请表/登记表等既有固定打印题干、又需在空白处手写填空的场景。

    1. 文档制作与高亮标记

    1. 在 Word(或 WPS)中打开文档;
    2. 题目、表格、题干等保持无背景色(将原样保留为打印体底图);
    3. 选中需要手写填空的内容,用 Word 的**「突出显示文本颜色(高亮)」**标记背景色(如黄、绿、青等),或直接写 {{...}}、【...】 占位标签。

    2. 导出 PDF 并导入(推荐)

    • 在 Word 中「文件」→「另存为 PDF」;
    • 在「导入素材」组点「导入 PDF / Word 打印底图」并选择该 PDF;
    • 为什么推荐 PDF? Word 直接导入依赖本机 Word(COM,仅 Windows)或 LibreOffice,受字体/渲染差异影响易错位;转 PDF 后版式固化,逐页像素级还原。

    3. 智能自动识别

    • 逐页渲染为高清底图,自动擦除高亮底色恢复干净纸面,并抹除 {{...}} / 【...】 标签文字;
    • 按全角字符紧包围盒校准原文字号、行距、缩进,在对应位置自动生成手写区域;
    • 不同高亮颜色自动归类为「手写角色 1」「手写角色 2」,可在「笔迹角色」组中换字体/墨色。

    3.5 Word 表格自动识别(0.5.0 新增)

    直接导入 .docx 底图时(而不是先转 PDF),软件还会解析文档的表格结构,把每个单元格的文字变成独立可编辑区域:

    单元格情况处理方式
    普通文字格擦除原印刷字、生成手写区域,矩形贴合文字包围盒(保留表格线)
    跨列 gridSpan / 纵向合并 vMerge合并格只出一个区域;续行格占位不出区域
    空单元格由同行行带 × 同列列带推算矩形,生成空手写区域(可直接填内容)
    高亮 / 彩色字格映射到对应手写角色(与表外高亮共用同一套角色配色)
    {{打印:文本}} 格生成印刷体区域(重排为规整打印字形)
    灰色高亮格整格保留印刷底图(不擦除、不生成区域)
    整行都是单字格保持印刷底图(单字格不参与定位匹配,防跨页误匹配)
    • 区域列表里表格单元格带 表N· 前缀(N 为文档中第几张表);
    • 在任一单元格的区域对话框里点 「此字体应用到整张表格」,可把当前字体与字号一次应用到该表全部单元格;
    • 表格跨页被拆开时,每页只输出落在本页的那部分单元格,不会重复也不会漏。

    4. 选框微调

    • 右侧「文字区域」列表单击某项,或直接单击预览图上的区域,即可拖动整体平移、拖 8 向手柄缩放;
    • 双击区域重新打开编辑属性。

    5. 特殊需求

    • 想保留背景色(不擦除、不生成填空):导入底图时选**「保留完整底图」**,原样保留色彩与文字,直接当普通背景。
    • 模板带高亮但想通篇全手写:用「导入 Word 正文文本 (.docx)」到正文时选**「全部作为手写」**,忽略全部高亮标记,无需手动清格式。