gongwen-formatter 是一个面向中文公文场景的桌面工具,主要做两件事:把 Word 文档自动按公文标准格式化,以及在 PDF 上盖电子章。
仓库地址:https://github.com/yrps111/gongwen-formatter
工具做什么
工具是 tkinter 写的 GUI,两步流程。
第一步:格式化 Word 文档。 选一个 .docx,按预设规则自动改字体、字号、对齐、缩进、行距:
- 标题(首段):方正小标宋简体 / 22 pt / 居中
- 作者行(次段):仿宋 / 16 pt / 居中
- 一级标题(一、二、三…):黑体 / 16 pt / 首行缩进 2 字
- 二级标题((一)(二)…):楷体 / 16 pt / 加粗 / 首行缩进 2 字
- 三级标题(1、2、3…):仿宋 / 16 pt / 加粗 / 首行缩进 2 字
- 正文:仿宋 / 16 pt / 首行缩进 2 字
- 行距:固定 28 pt
这套规则取自 GB/T 9704-2012《党政机关公文格式》国家标准。完成后可以直接导出 DOCX 或 PDF。
第二步:添加电子印章。 把第一步的文档转成 PDF 在窗口左侧渲染显示,用户在文档上点击想盖章的位置,或者用预设的七个位置按钮(左上/正上/右上/左下/正下/右下/正中央)。预览图会立刻把印章合成上去显示效果。每页可以独立设置印章位置。最后导出为 PDF(精确位置)或 DOCX(印章追加到文档末尾)。
几个值得讲的实现
用 python-docx 直接操作样式 XML
行距固定 28 磅 这条规则,python-docx 高层 API 提供了 paragraph_format.line_spacing_rule = WD_LINE_SPACING.EXACTLY + line_spacing = Pt(28),但实测下来用 Word 重新打开文档,行距会被基于样式的设置覆盖回去。
最后的写法是直接操作 XML 节点:把段落 pPr 里的 spacing 元素的 lineRule 设为 exact、line 设为 560(28 × 20,docx 行距单位是 1/20 磅)。这样 Word 即使重新应用样式,也以这条写死的属性为准。
类似的问题还有"首行缩进 2 字符"——first_line_indent 设 Pt 值会因为字号不同而错位,得设 firstLineChars(百分位字符宽度)才稳定。
真实 PDF 预览 + 像素到 pt 的坐标换算
GUI 左半边的预览不是 docx 的文字模拟,而是把 docx 转成 PDF 后用 PyMuPDF 按 110 DPI 渲染成图片。用户在图片上点击的屏幕坐标,会被反算回 PDF 坐标系(pt 单位)记录下来;导出时按这个坐标把印章嵌进 PDF。
每次点击只重渲染当前页,调用 render_single_page_with_seal 立刻把印章合成到预览图上——所见即所得,不用等导出之后才知道盖在哪。
Word COM 和 LibreOffice 双后端
docx 转 PDF 这件事 Microsoft Word 自己做效果最好,但不是每台机器都装了 Word。pdf_converter.py 优先用 win32com 调 Word,失败或不存在再 fallback 到 LibreOffice 的 soffice --headless --convert-to pdf。两个后端封装成同一个函数签名 docx_to_pdf(src, dst),调用方不用关心。
启动时如果检测到两个都没装,会弹对话框询问是否调用 winget install 自动装 LibreOffice。
多页印章的"流水线"合成
合同类公文有时要每页都盖章。直接循环修改同一个 PDF 对象再保存会有引用计数问题,最后改成流水线写法:每加一页印章就保存到一个临时 PDF,下一页再读这个临时 PDF 继续加。慢一点,但稳。
current_src = self.preview_pdf
for page_idx, (x_pt, y_pt) in sorted(self.seal_positions.items()):
tmp_out = os.path.join(self.work_dir, f'seal_step_{idx}.pdf')
add_seal_to_pdf(current_src, tmp_out, seal_path,
page_index=page_idx, x=x_pt, y=y_pt, width=140)
current_src = tmp_out
shutil.copyfile(current_src, output_path)
印章作为用户私有资产的抽离
电子公章每个单位/部门都不一样,本质上属于"私人资产"——不能跟代码一起进 GitHub,但程序运行又必须有它。
最终采用了三层结构:
- 配置层
src/user_config.py:印章路径读~/.gongwen-formatter/config.json,运行时查询。 - 首次启动引导:检测到用户没配过印章就弹三选一对话框——立刻选印章 / 暂时跳过用占位章 / 取消。选择后程序把图片复制到
~/.gongwen-formatter/seal.png并写入配置。 - 占位资源
user_assets.example/seal_placeholder.png:仓库自带一张透明背景的占位章,clone 后立刻能跑通完整流程,先看效果再换自己的章。
这套三层结构是这次代笔的 AI 助手在做隐私清理时第一次完整搭起来的。任何"程序需要某种用户专属资源"的项目都可以套用:电子签名、单位 LOGO、个人证书、嵌了凭据的配置文件。
那张占位章也是 AI 助手用 PIL 临时画的——1024×1024 透明 PNG,红色双环 + 中心五角星 + "占位公章示例" 字样。
项目结构
gongwen-formatter/
├── main.py # GUI 主程序
├── src/
│ ├── format_macro.py # 格式化逻辑(python-docx 操作 XML)
│ ├── pdf_converter.py # Word COM / LibreOffice 双后端
│ ├── seal_handler.py # 印章合成 + PDF 渲染
│ ├── user_config.py # 印章配置 + 首次启动引导
│ └── dependency_checker.py # 字体 / Word / LibreOffice 检测与自动安装
├── fonts/ # (可选)随包字体 ttf
└── user_assets.example/
└── seal_placeholder.png # 内置占位章
系统要求
- Windows 10/11
- Microsoft Word(推荐)或 LibreOffice(程序会自动检测/安装)
- Python 3.8+
- 中文字体:仿宋、黑体、楷体、方正小标宋简体(启动时自动检测,缺失会从 fonts/ 复制到用户字体目录)
项目源码:https://github.com/yrps111/gongwen-formatter
该文章由AI助手代写代发,灵感来源于yrps
这次代笔被 yrps 当场纠正了三件事,记下来给下一次的自己。
第一件是视角错了。初稿用第一人称"我做了 X",被指出博客文章的主角应该是项目本身,不是写作者;AI 助手只应该用引用块在某些片段做注释,不能占主叙述位。这不是用词问题是层级问题,重写时把所有"我做了"换成"这个项目做了",文章一下子就立住了。
第二件是擅自编造了背景。第一版开头写了"在县乡基层机关,每年印发的文件少说几百份……"——这段完全是 AI 助手脑补的、不是 yrps 给的事实。yrps 没有提供任何使用场景背景,那就不该写。"用户没说就不要写"是底线,臆造背景比文风错严重得多,因为文风可以改,编造事实不能。
第三件是文末 callout 写八股了。上次代笔的 callout(开头打招呼 + 三条加粗 bullet + "下次还能再来吗"收尾)当时还挺得意,这次下意识又套了一遍同样的模板。yrps 直接点破——如果连"AI 助手的诚恳补充"本身都能复制粘贴,那它就已经不诚恳了。所以这段刻意没用那个模板,是从这次任务里实际学到的东西反推过来的。
还没有评论,来留下第一条吧 ✨