中考作文格描红字帖生成器
概述
把任意中文文稿变成可打印的硬笔描红字帖 PDF:上行红格灰字描红、下行蓝格空白临写,范字用 hanzi-writer-data 的真实笔画轮廓渲染(起笔顿点、行笔提按、收笔出锋),不是字体渲染的均匀机械线条。
速查:30 秒上手
# 1. 存稿(口述转文字后保存为任意一种)
# 我的作文.txt / 我的作文.md / 我的作文.docx
# 2. 生成(标题可省略,默认取文件名)
python3 scripts/make_copybook.py 我的作文.txt 窗外即景
# 3. 打印
# 输出 同目录/我的作文_中考格字帖.pdf → A4 直接打印
跑完把 PDF 交给用户,并说明:上行描红、下行临写;生僻字若显示为方框请告知(可补缓存后重跑)。
接一段用户原话直接开工:
> "把这篇作文做成字帖" / "做一份描红帖" / "把口述稿打印成练字本"
→ 问清稿子文件路径与标题,然后跑上面的命令。
使用流程
1. 运行生成器
python3 <skill目录>/scripts/make_copybook.py <原稿> [标题]
- 原稿支持
.txt/.md/.docx(docx 零依赖解压提取;md 去除标题/链接/强调标记) - 标题缺省取文件名;输出 HTML + PDF 落在原稿同目录
- 内置《通用规范汉字表》一级字表(3500 字)离线字库,日常作文全覆盖,无需联网
- 超出 3500 的生僻字会自动联网补下载(jsDelivr CDN)并缓存到
scripts/.hanzi_cache/ - 生成 PDF 需要 Chrome/Chromium(脚本自动探测 macOS/Linux 常见路径);没有 Chrome 时直接把输出的 HTML 用浏览器打开打印
2. 校验输出
脚本内置三道断言,全部通过才生成:
- 不超宽:每行 ≤ 24 格
- 行首禁则:,。;:!?)】…— 等不落行首
- 原文一致:断行拼接后与原稿逐字相同(含错别字,原样保留不订正)
再目检一页渲染图确认:范字居中、标点在基线、段首缩进可见。
何时使用 / 何时不用
用它:
- 学生有一篇自己的作文(txt / md / docx),想转成可打印的描红临摹帖
- 家长把孩子的口述稿转成文字后,要一份能打印的练字本
- 老师批量把范文或学生习字转成字帖
不要用它:
- 只有手写扫描件/PDF 图片——本skill 吃文本。手写稿需先人工誊录成txt;不要让模型"猜"扫描件上的字
- 要求订正错别字——本 skill 原样保留原稿错别字(三道断言之一是原文一致)。要订正请先改文本
- 要米字格/ 笔顺分解页/ 运笔箭头——默认版式是中考作文格。这些是变体,见文末
- 非中文文稿——范字数据只覆盖汉字
- 要自动批改或打分——本 skill 只排版,不做评价
版式规格(不要改动,除非用户明确要求)
| 项 | 值 | 依据 |
|---|---|---|
| 单格 | 7.5 × 8.0 mm | 多来源交叉验证,对标上海中考作文纸 |
| 行距 | 1.5 mm(行间距 9.5mm) | 同上 |
| 每行 | 24 格 | A4 15mm 边距 → 180/7.5 整除 |
| 字面 | 5.6 mm(格高 70%) | 「字占格约 2/3」的书写习惯 |
| 段首 | 空 2 格 | 中考规范 |
流传的"总格数 980/1000"不可信(不同地区/年份卷面互相矛盾)。对齐「格尺寸」而非「格数」。
三条不可违反的版式规则(踩坑总结)
- 段落逻辑:一段之内格子连续填满,换行只由"行满"触发,句号不是换行信号,句号照常写在行中间。分段唯一标志 = 段首空 2 格(+ 左侧红色标记条)。绝不在段间插空行——真卷作文纸不允许空行。
- 垂直校准:hanzi-writer-data 的 1024 是 em 框,但 Arphic 字面在 SVG 坐标里位于 y≈147~1116(79% 的字字底超出 1024 被裁)。必须用
viewBox="0 112.5 1024 1024"下移窗口:字心回中线(实测 405 字标准差仅 14.2)且不裁字底。数据坐标系是 y 轴向上,渲染必须加transform="scale(1,-1) translate(0,-1024)",字形与 medians 放同一翻转组。 - 绝对定位不落页边距:
left为负的元素(行号、标记条)实际坐标会进入 15mm 页边距,Chrome 预览可见但打印机会裁掉。一律left:≥0放格内。
详细技术依据与实测数据见 references/design.md,可复制示例见 references/examples.md。
资源说明
scripts/make_copybook.py— 单文件自包含生成器(Python 3.8+ 标准库,无第三方硬依赖)assets/hanzi-cache.txt— 内置离线字库(《通用规范汉字表》一级字表 3500 字,实际收录 3519,5.0MB)。格式:首行HWD1,其后为 zlib 压缩 + base64 的{"<字码hex>":{strokes,medians}}。必须用纯文本格式——上传平台禁用 .zip/.ttf 等二进制类型。扩充方式见references/examples.md例 5scripts/_font_subset_b64.py— 霞鹜文楷 GB 预子集字体(base64 内嵌,155KB,SIL OFL 1.1)。仅覆盖固定说明文案 + 全部标点 + ASCII。正文范字全部是 SVG 笔画,不依赖字体;标题等动态字符回退系统楷体('Kaiti SC'/'STKaiti'/'KaiTi')。⚠️ 若修改脚本的固定文案(tip/页眉/页脚),必须同步重新子集化字体并更新此文件,否则缺字。(base64 而非 .ttf 是因为上传平台禁用二进制字体文件)scripts/.hanzi_cache/— 运行时动态补下载的缓存(不存在也正常,不影响生成)
运行要求与已知限制
需要:
- Python 3.8+(仅标准库,无需 pip install)
- 生成 PDF 需要 Chrome / Chromium(脚本自动探测 macOS/Linux 常见路径)。没有 Chrome 时:把输出的 HTML 用浏览器打开打印,或手动另存为 PDF
- 离线也能跑(3500 字内置)。仅当出现超出字表的生僻字时才需联网补下载
已知限制:
- 大陆字形差异字不纠——底层字形是台湾标楷体(飞/节/爱/党/即/宣/家/门等有差异)。这是有意取舍,见 references/design.md
- 总格数不保证与真卷一致——流传的 980/1000/900 格互相矛盾(不同地区/年份卷面)。本 skill 对齐格尺寸而非格数,这是刻意决策
- 原稿错别字原样保留,不订正
- 超出 3500 字的生僻字需联网补下载;离线环境下会回退为楷体字体渲染(视觉略有差异,不破版)
- 标点无笔顺数据,用字体渲染,无笔锋
数据与许可
- 笔顺/笔画:
hanzi-writer-data@2.0.1(jsDelivr CDN),Arphic Public License,可商用 - 内置字库按《通用规范汉字表》一级字表(3500 字)构建
- 底层字形为台湾标楷体(Arphic UKai),笔顺符合国标;大陆字形差异字(飞/节/爱/党等)接受不纠——用户已确认取舍:笔画质感比字形标准更影响临摹
- 字体:霞鹜文楷 GB(LXGW WenKai GB),SIL OFL 1.1,免费商用
- 本 skill 源码:MIT
常见变体
米字格(.tpl .cell::before/::after 加对角线,inline SVG data-URI 更可靠,CSS rotate 会丢线):
.tpl .cell { position: relative; }
.tpl .cell::before, .tpl .cell::after {
content: ''; position: absolute; inset: 0;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E");
pointer-events: none;
}
/* 实际实现:把 SVG 的 <line> 写进 data-URI,配合 vector-effect="non-scaling-stroke" */
不同格数/尺寸:改 GW/GH/COLS 常量(脚本顶部),改完必须重新校验页宽整除:
A4 15mm 边距 → 可用宽 180mm。180 / GW 必须整除,否则打印会溢出或留白。
COLS = 180 / GW
笔顺分解页 / 运笔箭头:数据里有 medians(每笔中线),可画运笔箭头。
⚠️ 箭头必须画在笔画之上(先用白色描边打底stroke="#fff" stroke-width="18" opacity="0.55"),否则被黑色笔画盖住。
字形与 medians 必须放在同一个被翻转的 <g> 内,否则箭头方向反。
加诊断提示框:改脚本里的 tip 文案(同时必须重新子集化 KaiGB-subset.ttf,否则缺字)。
排错速查
| 现象 | 原因与处理 |
|---|---|
| PDF 没生成、只有 HTML | 没找到 Chrome。用浏览器打开 HTML 手动打印 |
| 某字显示为方框或空白 | 超出 3500 字表且离线。联网重跑自动补下载;离线时删 assets/hanzi-cache.txt 可退回纯联网模式 |
| 字看起来上下颠倒 | 缓存文件损坏(超时中断会留坏 JSON)。删掉 scripts/.hanzi_cache/ 重跑 |
| 报"内置字库加载失败" | assets/hanzi-cache.txt 首行必须是 HWD1 且独占一行。检查是否被编辑器改动 |
| 报"有行超宽" | 原稿有超长连续非标点串(如 URL)。断行需要人工介入 |
| 标题字体与正文不一致 | 动态字符不在字体子集内,回退系统楷体,属预期行为 |
| 段首没有缩进 | 原稿首段以标点或空格开头,或该段被解析成单行 |
| 内置字库缺失/损坏 | 删除 assets/hanzi-cache.txt 后重跑,脚本会自动联网重建(需联网,耗时几分钟) |