补齐预印边界、签章定位与标准证据
This commit is contained in:
@@ -106,7 +106,7 @@ node scripts/verify_gongwen_docx.mjs --input /tmp/formal.docx --profile formal -
|
|||||||
|
|
||||||
只有需要完整电子红头文件时,才加 `--letterhead digital`。红头和标题要使用目标电脑中实际安装的小标宋体;生成器缺少小标宋体或仿宋体时会在终端明确警告并写出替代字体,要求严格阻止替代输出时加 `--require-standard-fonts`。红线下标题会写入两个显式的28磅版心空行,不会依赖渲染器自行解释段前距;发文机关标志到文号也按相同方式留出两行。上行文的文号和签发人会在同一行编排。打开文档后,Word/WPS 的“引用→目录”可以按“标题1—标题4”生成并更新目录;是否显示第四级,在目录设置中选择。
|
只有需要完整电子红头文件时,才加 `--letterhead digital`。红头和标题要使用目标电脑中实际安装的小标宋体;生成器缺少小标宋体或仿宋体时会在终端明确警告并写出替代字体,要求严格阻止替代输出时加 `--require-standard-fonts`。红线下标题会写入两个显式的28磅版心空行,不会依赖渲染器自行解释段前距;发文机关标志到文号也按相同方式留出两行。上行文的文号和签发人会在同一行编排。打开文档后,Word/WPS 的“引用→目录”可以按“标题1—标题4”生成并更新目录;是否显示第四级,在目录设置中选择。
|
||||||
|
|
||||||
信函、命令(令)、纪要和横排表格走专用分支:信函生成170mm上粗下细、下页边20mm处上细下粗的两条红色双线,底线放在页脚并关闭页码;命令(令)落实机关标志距版心上边缘20mm、令号下空二行、正文下空二行;纪要把“出席”“请假”“列席”标签设为黑体、人员名单设为仿宋,并放在正文或附件说明下一行;横排表格生成横向A4和225mm表格,单双页表头方向仍需目标Word/WPS模板核对。联合行文可重复传入 `--joint-org`,生成主办机关在前、联署机关名称分行排列且“文件”置右并上下居中的标志;不能与正文一起装订的附件加 `--attachment-detached` 会在首行补排文号和附件序号。附件可用重复的 `--attachment-file` 另页生成“附件”标签、第三行标题和正文,并保持正式页码连续。带抄送或印发信息时,版记浮动锚定在最后一页版心底部,首末粗线约0.35mm、中间细线约0.25mm。
|
信函、命令(令)、纪要和横排表格走专用分支:信函生成170mm上粗下细、下页边20mm处上细下粗的两条红色双线,底线放在页脚并关闭页码;命令(令)落实机关标志距版心上边缘20mm、令号下空二行、正文下空二行;纪要把“出席”“请假”“列席”标签设为黑体、人员名单设为仿宋,并放在正文或附件说明下一行;横排表格生成横向A4和225mm表格,单双页表头方向仍需目标Word/WPS模板核对。联合行文可重复传入 `--joint-org`,生成主办机关在前、联署机关名称分行排列;输入含“文件”时将“文件”置右并上下居中的标志;不能与正文一起装订的附件加 `--attachment-detached` 会在首行补排文号和附件序号。附件可用重复的 `--attachment-file` 另页生成“附件”标签、第三行标题和正文,并保持正式页码连续。带抄送或印发信息时,版记浮动锚定在最后一页版心底部,首末粗线约0.35mm、中间细线约0.25mm。
|
||||||
|
|
||||||
## 跨平台安装
|
## 跨平台安装
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ GB/T 9704-2012《党政机关公文格式》现行,2025-05-30 复审继续有
|
|||||||
|
|
||||||
本工具只处理排版,不判断单位身份、授权状态或后续盖章方式。它会根据用户明确选择的版式模式应用对应的排版规则,不因“正式”“国企”“报告”或仅提供机构名称而擅自使用红头。
|
本工具只处理排版,不判断单位身份、授权状态或后续盖章方式。它会根据用户明确选择的版式模式应用对应的排版规则,不因“正式”“国企”“报告”或仅提供机构名称而擅自使用红头。
|
||||||
|
|
||||||
生成器可输出 A4、156mm×225mm 版心、三号仿宋正文、小标宋二号标题、四级标题层次、两字缩进,以及居中或单双页页码。四级标题均写入 Word/WPS 标题样式和大纲级别,便于后续插入、更新目录;文章总标题使用独立“公文标题”样式,不进入目录。上行文的文号和签发人在同一行编排,左右各空一字;信函、命令(令)、纪要和横排表格使用各自专用版式;电子联合行文可重复传入 `--joint-org`;主办机关在前,联署机关名称分行排列,“文件”置右并按联署名称上下居中;分离装订附件可加 `--attachment-detached`;重复传入 `--attachment-file` 时必须同时提供与附件顺序、标题一致的 `--attachment-note`,附件会另页生成并保持页码连续。版记会锚定最后一页版心底部,正式页码按版心下边缘下7mm的位置生成。正式版头的份号、密级、紧急程度和电子机关标志以版心上边缘为垂直锚点固定定位,不会因为左上字段而把机关标志或预印红头留白向下推移。只有传入密级而未传入份号时,首行会保留空位,密级仍从第二行开始。
|
生成器可输出 A4、156mm×225mm 版心、三号仿宋正文、小标宋二号标题、四级标题层次、两字缩进,以及居中或单双页页码。四级标题均写入 Word/WPS 标题样式和大纲级别,便于后续插入、更新目录;文章总标题使用独立“公文标题”样式,不进入目录。上行文的文号和签发人在同一行编排,左右各空一字;信函、命令(令)、纪要和横排表格使用各自专用版式;电子联合行文可重复传入 `--joint-org`;主办机关在前,联署机关名称分行排列;输入含“文件”时将“文件”置右并按联署名称上下居中;分离装订附件可加 `--attachment-detached`;重复传入 `--attachment-file` 时必须同时提供与附件顺序、标题一致的 `--attachment-note`,附件会另页生成并保持页码连续。版记会锚定最后一页版心底部,正式页码按版心下边缘下7mm的位置生成。正式版头的份号、密级、紧急程度和电子机关标志以版心上边缘为垂直锚点固定定位,不会因为左上字段而把机关标志或预印红头留白向下推移。只有传入密级而未传入份号时,首行会保留空位,密级仍从第二行开始。
|
||||||
|
|
||||||
`references/formal-checklist.md` 是可选的版式复核清单。涉及印章或签名章时,使用者可在 Word/WPS 中插入已取得的图片或完成实体盖章;本生成器不内置印章图像。
|
`references/formal-checklist.md` 是可选的版式复核清单。涉及印章或签名章时,使用者可在 Word/WPS 中插入已取得的图片或完成实体盖章;本生成器不内置印章图像。
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ node scripts/generate_gongwen_docx.mjs --input source.md --output output.docx --
|
|||||||
node scripts/generate_gongwen_docx.mjs --input source.md --output output.docx --format formal --letterhead digital --org "发文机关名称" --doc-no "单位发〔2026〕1号" --title "文档标题"
|
node scripts/generate_gongwen_docx.mjs --input source.md --output output.docx --format formal --letterhead digital --org "发文机关名称" --doc-no "单位发〔2026〕1号" --title "文档标题"
|
||||||
```
|
```
|
||||||
|
|
||||||
`letter`、`command`、`minutes`、`horizontal-table` 分别对应信函、命令(令)、纪要和横排表格格式;需要时显式传入 `--format` 和相应字段。横排表格会生成横向A4页面和225mm表格,单双页表头在订口/切口的朝向仍需目标 Word/WPS 模板核对。联合行文使用 `--joint-org`,主办机关在前、联署机关名称分行排列,“文件”置右并按联署名称上下居中;附件不能与正文一起装订时使用 `--attachment-detached`,附件首行会补排文号和附件序号。`command` 会要求机关标志以“命令”或“令”结尾;信函中的份号、密级和紧急程度会在第一条双线下按标准顺序编排,联合行文和机关专用信函模板仍需目标软件核对。正式发文默认单双页页码;`standard` 为单页右、双页左,`center` 与 `none` 供普通材料选择。
|
`letter`、`command`、`minutes`、`horizontal-table` 分别对应信函、命令(令)、纪要和横排表格格式;需要时显式传入 `--format` 和相应字段。横排表格会生成横向A4页面和225mm表格,单双页表头在订口/切口的朝向仍需目标 Word/WPS 模板核对。联合行文使用 `--joint-org`,主办机关在前、联署机关名称分行排列;输入含“文件”时将“文件”置右并按联署名称上下居中;附件不能与正文一起装订时使用 `--attachment-detached`,附件首行会补排文号和附件序号。`command` 会要求机关标志以“命令”或“令”结尾;信函中的份号、密级和紧急程度会在第一条双线下按标准顺序编排,联合行文和机关专用信函模板仍需目标软件核对。正式发文默认单双页页码;`standard` 为单页右、双页左,`center` 与 `none` 供普通材料选择。
|
||||||
|
|
||||||
附件可重复传入 `--attachment-file attachment.md`。附件 Markdown 的首个一级标题会作为附件标题,第一页第一行写“附件1”(后续文件递增),第三行写标题,随后排正文;附件说明通过 `--attachment-note` 提供,工具会统一为“附件:”并去除末尾标点,同时拒绝顺序或标题与附件文件不一致的输入。复杂横排附件、不能与正文一起装订的附件和机关专用模板仍需在目标 Word/WPS 中核对。
|
附件可重复传入 `--attachment-file attachment.md`。附件 Markdown 的首个一级标题会作为附件标题,第一页第一行写“附件1”(后续文件递增),第三行写标题,随后排正文;附件说明通过 `--attachment-note` 提供,工具会统一为“附件:”并去除末尾标点,同时拒绝顺序或标题与附件文件不一致的输入。复杂横排附件、不能与正文一起装订的附件和机关专用模板仍需在目标 Word/WPS 中核对。
|
||||||
|
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
@@ -52,7 +52,7 @@
|
|||||||
- [ ] 印发机关和日期为四号仿宋,左空一字与右空一字;日期完整且不补零,后加“印发”。
|
- [ ] 印发机关和日期为四号仿宋,左空一字与右空一字;日期完整且不补零,后加“印发”。
|
||||||
- [ ] 页码为四号半角宋体阿拉伯数字,两侧各一字线,线距版心下边缘7mm;单页右空一字、双页左空一字;空白页、特定版记页和附件连续页码已按规则处理。
|
- [ ] 页码为四号半角宋体阿拉伯数字,两侧各一字线,线距版心下边缘7mm;单页右空一字、双页左空一字;空白页、特定版记页和附件连续页码已按规则处理。
|
||||||
- [ ] 横排表格使用 `--format horizontal-table` 或目标 Word/WPS 横向模板;本项目已实际渲染横向页面和225mm表格,但单双页表头在订口/切口的方向仍需目标软件核对。
|
- [ ] 横排表格使用 `--format horizontal-table` 或目标 Word/WPS 横向模板;本项目已实际渲染横向页面和225mm表格,但单双页表头在订口/切口的方向仍需目标软件核对。
|
||||||
- [ ] 信函、命令(令)、纪要、联合行文已使用相应专用版式;`formal --joint-org` 的截图已确认主办机关在前、联署机关分行排列且“文件”置右并上下居中;复杂机关标志仍按现行模板核对;不能用普通打印稿替代。
|
- [ ] 信函、命令(令)、纪要、联合行文已使用相应专用版式;`formal --joint-org` 的截图已确认主办机关在前、联署机关分行排列;输入含“文件”时将“文件”置右并上下居中;复杂机关标志仍按现行模板核对;不能用普通打印稿替代。
|
||||||
- [ ] 信函格式已核对上边缘30mm、上粗下细双线、下页边20mm处上细下粗双线,双线长度均170mm;如有份号、密级、紧急程度,均置于第一条双线下并距线3号字高7/8;文号顶格居右;首页无页码且版记不画分隔线。当前生成器对这些可选信函版头字段要求使用专用模板。
|
- [ ] 信函格式已核对上边缘30mm、上粗下细双线、下页边20mm处上细下粗双线,双线长度均170mm;如有份号、密级、紧急程度,均置于第一条双线下并距线3号字高7/8;文号顶格居右;首页无页码且版记不画分隔线。当前生成器对这些可选信函版头字段要求使用专用模板。
|
||||||
- [ ] 命令(令)格式已核对发文机关全称加“命令”或“令”、标志上边缘距版心20mm、令号下空二行、正文下空二行;签发章和日期按7.3.5.3核对。
|
- [ ] 命令(令)格式已核对发文机关全称加“命令”或“令”、标志上边缘距版心20mm、令号下空二行、正文下空二行;签发章和日期按7.3.5.3核对。
|
||||||
- [ ] 纪要格式已核对标志上边缘距版心35mm,出席/请假/列席标签为黑体,人员为仿宋。
|
- [ ] 纪要格式已核对标志上边缘距版心35mm,出席/请假/列席标签为黑体,人员为仿宋。
|
||||||
|
|||||||
@@ -160,7 +160,7 @@
|
|||||||
| 预印红头纸套打 | 是 | `--format formal --letterhead preprinted`,按纸样设置预留高度 |
|
| 预印红头纸套打 | 是 | `--format formal --letterhead preprinted`,按纸样设置预留高度 |
|
||||||
| 完整电子红头 | 是 | `--format formal --letterhead digital` |
|
| 完整电子红头 | 是 | `--format formal --letterhead digital` |
|
||||||
| 印章、签名章 | 文字版式之外 | 在 Word/WPS 中插入图片或实体盖章 |
|
| 印章、签名章 | 文字版式之外 | 在 Word/WPS 中插入图片或实体盖章 |
|
||||||
| 联合行文 | 通过 `--joint-org` 分行生成,主办机关在前,“文件”置右并按联署名称上下居中 | 复杂机关专用标志和联署模板复核 |
|
| 联合行文 | 通过 `--joint-org` 分行生成,主办机关在前;输入含“文件”时将“文件”置右并按联署名称上下居中 | 复杂机关专用标志和联署模板复核 |
|
||||||
| 附件 | 通过 `--attachment-file` 另页生成标签、标题和正文;`--attachment-detached` 补排文号和附件序号 | 复杂附件版式仍需人工复核 |
|
| 附件 | 通过 `--attachment-file` 另页生成标签、标题和正文;`--attachment-detached` 补排文号和附件序号 | 复杂附件版式仍需人工复核 |
|
||||||
| 版记 | 是 | `--cc`、`--print-org`、`--print-date` 适用时生成 |
|
| 版记 | 是 | `--cc`、`--print-org`、`--print-date` 适用时生成 |
|
||||||
| 信函、命令、纪要、横排表格 | 是/部分通过 | `--format letter|command|minutes|horizontal-table`,按实际字段生成;横排表头方向需模板复核 |
|
| 信函、命令、纪要、横排表格 | 是/部分通过 | `--format letter|command|minutes|horizontal-table`,按实际字段生成;横排表头方向需模板复核 |
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
| 7.2.1 | 份号六位、3 号、左上第一行 | `--copy-no` 只接受 1—6 位阿拉伯数字并补足六位 | 结构检查;实际是否需要份号由发文机关确认 |
|
| 7.2.1 | 份号六位、3 号、左上第一行 | `--copy-no` 只接受 1—6 位阿拉伯数字并补足六位 | 结构检查;实际是否需要份号由发文机关确认 |
|
||||||
| 7.2.2 | 密级和保密期限第二行、3 号黑体 | `--secret` 按黑体3号写入;没有份号时自动保留第一行空位,使密级仍在第二行 | 结构检查和视觉审计;文字内容和期限写法人工复核 |
|
| 7.2.2 | 密级和保密期限第二行、3 号黑体 | `--secret` 按黑体3号写入;没有份号时自动保留第一行空位,使密级仍在第二行 | 结构检查和视觉审计;文字内容和期限写法人工复核 |
|
||||||
| 7.2.3 | 紧急程度及排列顺序 | `--urgent` 按黑体 3 号写入,并按份号、密级、紧急程度顺序下移 | 适用性和紧急级别人工确认 |
|
| 7.2.3 | 紧急程度及排列顺序 | `--urgent` 按黑体 3 号写入,并按份号、密级、紧急程度顺序下移 | 适用性和紧急级别人工确认 |
|
||||||
| 7.2.4 | 发文机关标志居中、红色、版心上边缘下 35mm、推荐小标宋 | `formal --letterhead digital` 才绘制;预印模式只留纸样区域;首面字段以版心上边缘为垂直锚点,机关标志固定在其下35mm,不推动机关标志或预留区;联合行文通过 `--joint-org` 分行生成,主办机关在前,“文件”置右并按联署名称上下居中;名称过长时自适应标志字号 | `verify_gongwen_docx.mjs`、`tests/measure-header-coordinates.mjs` 和[`gbt9704-screenshot-evidence.md`](gbt9704-screenshot-evidence.md);当前机缺少推荐字体时严格模式失败,复杂机关专用标志仍需模板核对 |
|
| 7.2.4 | 发文机关标志居中、红色、版心上边缘下 35mm、推荐小标宋 | `formal --letterhead digital` 才绘制;预印模式只留纸样区域;首面字段以版心上边缘为垂直锚点,机关标志固定在其下35mm,不推动机关标志或预留区;联合行文通过 `--joint-org` 分行生成,主办机关在前;输入含“文件”时将“文件”置右并按联署名称上下居中;名称过长时自适应标志字号 | `verify_gongwen_docx.mjs`、`tests/measure-header-coordinates.mjs` 和[`gbt9704-screenshot-evidence.md`](gbt9704-screenshot-evidence.md);当前机缺少推荐字体时严格模式失败,复杂机关专用标志仍需模板核对 |
|
||||||
| 7.2.5 | 文号下空二行、六角括号、全年份、顺序号不加“第”不补零 | `formal`/`letter` 对 `--doc-no` 做格式校验,并写入两个显式 28 磅空行 | 文号语义由机关编号规则确认 |
|
| 7.2.5 | 机关标志下空二行、六角括号、全年份、顺序号不加“第”不补零 | `formal`/`letter` 对 `--doc-no` 做格式校验,并写入两个显式 28 磅空行 | 文号语义由机关编号规则确认 |
|
||||||
| 7.2.6 | 上行文文号左空一字、签发人右空一字并同一行;标签仿宋、姓名楷体 | `--upward true --signer` 同行生成单一或两个签发人,左右各空一字;多签发人、联合行文转人工模板 | 结构校验和 PDF;复杂联合行文需按机关顺序人工排布 |
|
| 7.2.6 | 上行文文号左空一字、签发人右空一字并同一行;标签仿宋、姓名楷体 | `--upward true --signer` 同行生成单一或两个签发人,左右各空一字;多签发人、联合行文转人工模板 | 结构校验和 PDF;复杂联合行文需按机关顺序人工排布 |
|
||||||
| 7.2.7 | 文号下 4mm、版心等宽红线 | `redRule()` 生成 156mm 红线,文号段后 4mm;预印模式不重绘 | PDF 量测;红色油墨和实际纸样人工核对 |
|
| 7.2.7 | 文号下 4mm、版心等宽红线 | `redRule()` 生成 156mm 红线,文号段后 4mm;预印模式不重绘 | PDF 量测;红色油墨和实际纸样人工核对 |
|
||||||
| 7.3.1 | 标题 2 号小标宋,红线下空二行、居中 | 标题使用 `GongwenTitle`,红线/预印线后插入两个显式 28 磅空段落 | XML + PDF 视觉门;小标宋缺失时严格模式失败 |
|
| 7.3.1 | 标题 2 号小标宋,红线下空二行、居中 | 标题使用 `GongwenTitle`,红线/预印线后插入两个显式 28 磅空段落 | XML + PDF 视觉门;小标宋缺失时严格模式失败 |
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ bash tests/visual-audit.sh /tmp/gongwen-visual-audit-all-rules
|
|||||||
| 7.2.1 | 份号六位三号阿拉伯数字,版心左上第一行。 | `formal-digital-all-1.png`、`formal-preprinted-1.png` 显示 `000007`;独立坐标日志显示 39.88mm 首行位置。 | 截图通过。 |
|
| 7.2.1 | 份号六位三号阿拉伯数字,版心左上第一行。 | `formal-digital-all-1.png`、`formal-preprinted-1.png` 显示 `000007`;独立坐标日志显示 39.88mm 首行位置。 | 截图通过。 |
|
||||||
| 7.2.2 | 密级和保密期限第二行三号黑体,数字用阿拉伯数字。 | `formal-digital-all-1.png` 显示 `机密★3年` 位于第二行。 | 截图通过;密级文字及期限是否适用需机关确认。 |
|
| 7.2.2 | 密级和保密期限第二行三号黑体,数字用阿拉伯数字。 | `formal-digital-all-1.png` 显示 `机密★3年` 位于第二行。 | 截图通过;密级文字及期限是否适用需机关确认。 |
|
||||||
| 7.2.3 | 紧急程度三号黑体;与份号、密级同时出现时按份号、密级、紧急程度自上而下。 | `formal-digital-all-1.png` 显示三项顺序;`letter-fields-1.png` 显示信函双线下同顺序。 | 截图通过。 |
|
| 7.2.3 | 紧急程度三号黑体;与份号、密级同时出现时按份号、密级、紧急程度自上而下。 | `formal-digital-all-1.png` 显示三项顺序;`letter-fields-1.png` 显示信函双线下同顺序。 | 截图通过。 |
|
||||||
| 7.2.4 | 机关全称/规范简称可加“文件”,居中红色;上边缘距版心上边缘35mm;联合行文主办机关在前,联署名称上下居中。 | `formal-digital-all-1.png`、`formal-joint-1.png`;坐标日志显示机关标志 71.41mm 页面坐标,字段差0.00mm;联合行文以主办机关在前、联署机关名称分行排列,“文件”置右并按联署名称上下居中;名称过长时自适应标志字号。 | 截图通过,推荐小标宋字体需目标机复核;复杂机关专用标志仍需模板核对。 |
|
| 7.2.4 | 机关全称/规范简称可加“文件”,居中红色;上边缘距版心上边缘35mm;联合行文主办机关在前,联署名称上下居中。 | `formal-digital-all-1.png`、`formal-joint-1.png`;坐标日志显示机关标志 71.41mm 页面坐标,字段差0.00mm;联合行文以主办机关在前、联署机关名称分行排列,输入含“文件”时将“文件”置右并按联署名称上下居中;名称过长时自适应标志字号。 | 截图通过,推荐小标宋字体需目标机复核;复杂机关专用标志仍需模板核对。 |
|
||||||
| 7.2.5 | 机关标志下空二行;年份全称、六角括号、顺序号不加“第”不补零、末尾“号”;上行文文号左空一字。 | `formal-digital-all-1.png`、`formal-digital-upward-1.png`;文号输入边界和显式空行均经校验。 | 截图通过。 |
|
| 7.2.5 | 机关标志下空二行;年份全称、六角括号、顺序号不加“第”不补零、末尾“号”;上行文文号左空一字。 | `formal-digital-all-1.png`、`formal-digital-upward-1.png`;文号输入边界和显式空行均经校验。 | 截图通过。 |
|
||||||
| 7.2.6 | “签发人:”三号仿宋、姓名三号楷体,右空一字;多签发人按顺序、一般每行两名、回行对齐。 | `formal-digital-upward-1.png` 显示同一行双签发人,姓名字体和标签分 run。 | 截图通过;超过两名或联合行文复杂签发人仍须专用模板。 |
|
| 7.2.6 | “签发人:”三号仿宋、姓名三号楷体,右空一字;多签发人按顺序、一般每行两名、回行对齐。 | `formal-digital-upward-1.png` 显示同一行双签发人,姓名字体和标签分 run。 | 截图通过;超过两名或联合行文复杂签发人仍须专用模板。 |
|
||||||
| 7.2.7 | 文号下4mm处、版心等宽红色分隔线。 | `formal-digital-all-1.png`、`formal-joint-1.png`;线宽156mm,红线下标题空两行。 | 截图通过;预印模式明确不重绘红线。 |
|
| 7.2.7 | 文号下4mm处、版心等宽红色分隔线。 | `formal-digital-all-1.png`、`formal-joint-1.png`;线宽156mm,红线下标题空两行。 | 截图通过;预印模式明确不重绘红线。 |
|
||||||
@@ -98,7 +98,7 @@ bash tests/visual-audit.sh /tmp/gongwen-visual-audit-all-rules
|
|||||||
## 本次发现并修正的实现问题
|
## 本次发现并修正的实现问题
|
||||||
|
|
||||||
1. 命令(令)分支曾把20mm误当成纸张上边距,导致机关标志距离纸顶约20mm;现在恢复标准37mm天头,并在版心上边缘下20mm处排标志,实际截图已复核。
|
1. 命令(令)分支曾把20mm误当成纸张上边距,导致机关标志距离纸顶约20mm;现在恢复标准37mm天头,并在版心上边缘下20mm处排标志,实际截图已复核。
|
||||||
2. 联合行文曾把联署机关名称错误压成同一横行;现在主办机关在前、联署机关名称分行排列,“文件”置右并按联署名称上下居中,名称过长时自适应标志字号。
|
2. 联合行文曾把联署机关名称错误压成同一横行;现在主办机关在前、联署机关名称分行排列,输入含“文件”时将“文件”置右并按联署名称上下居中;名称过长时自适应标志字号。
|
||||||
3. 纪要出席、请假、列席名单曾追加在附件和版记之后;现在紧跟正文或附件说明下一行生成,符合10.3的编排位置。
|
3. 纪要出席、请假、列席名单曾追加在附件和版记之后;现在紧跟正文或附件说明下一行生成,符合10.3的编排位置。
|
||||||
4. PDF 字形量测曾按 `yMin` 粗略分组,遇到高字形会把同一视觉行拆开;现在按字形垂直重叠合并后再量测,独立坐标结果稳定。
|
4. PDF 字形量测曾按 `yMin` 粗略分组,遇到高字形会把同一视觉行拆开;现在按字形垂直重叠合并后再量测,独立坐标结果稳定。
|
||||||
5. 视觉审计补入了横排表格、22×28校准页、联合行文、分离装订附件、不盖章公文和命令(令)等场景,不再只依赖电子红头样例。
|
5. 视觉审计补入了横排表格、22×28校准页、联合行文、分离装订附件、不盖章公文和命令(令)等场景,不再只依赖电子红头样例。
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
bash tests/visual-audit.sh /tmp/gongwen-visual-audit-all-rules
|
bash tests/visual-audit.sh /tmp/gongwen-visual-audit-all-rules
|
||||||
```
|
```
|
||||||
|
|
||||||
脚本实际生成 18 份 DOCX,逐份通过校验器,使用 LibreOffice 转 PDF,再用 `pdftoppm` 输出 37 个 PDF 页面和 PNG。人工检查以 PNG 为准,XML 和校验器只作为辅助证据。脚本还用 `pdftotext -bbox-layout` 独立量测电子红头首页:份号字形落在 37 mm 版心首行附近,机关标志字形落在 35 mm 版心上边缘下方附近,并把带版头字段与不带字段的机关标志位置差控制在 0.8 mm 内;结果写入运行目录的 `logs/header-coordinates.log`。当前批次的文件清单保存在提交的 [`assets/visual-audit/all-rules/manifest.tsv`](assets/visual-audit/all-rules/manifest.tsv),单页证据和总览图保存在同目录;每份文件的校验日志保存在运行目录的 `logs/`。完整逐条矩阵见 [`gbt9704-screenshot-evidence.md`](gbt9704-screenshot-evidence.md)。
|
脚本实际生成 18 份 DOCX,逐份通过校验器,使用 LibreOffice 转 PDF,再用 `pdftoppm` 输出 37 个 PDF 页面和 PNG。人工检查以 PNG 为准,XML 和校验器只作为辅助证据。脚本还用 `pdftotext -bbox-layout` 独立量测电子红头首页:份号字形落在 37 mm 版心首行附近,机关标志字形落在 35 mm 版心上边缘下方附近;本批次带版头字段与不带字段的机关标志位置差为 0.00 mm,校验阈值为 0.8 mm;结果写入运行目录的 `logs/header-coordinates.log`。当前批次的文件清单保存在提交的 [`assets/visual-audit/all-rules/manifest.tsv`](assets/visual-audit/all-rules/manifest.tsv),单页证据和总览图保存在同目录;每份文件的校验日志保存在运行目录的 `logs/`。完整逐条矩阵见 [`gbt9704-screenshot-evidence.md`](gbt9704-screenshot-evidence.md)。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -58,8 +58,8 @@ bash tests/visual-audit.sh /tmp/gongwen-visual-audit-all-rules
|
|||||||
| 7.2.1 | 份号六位、左上第一行 | 截图通过 | `formal-digital-all-1.png` 和 `formal-preprinted-1.png` 显示 `000007`;首面字段采用固定定位。 |
|
| 7.2.1 | 份号六位、左上第一行 | 截图通过 | `formal-digital-all-1.png` 和 `formal-preprinted-1.png` 显示 `000007`;首面字段采用固定定位。 |
|
||||||
| 7.2.2 | 密级和保密期限第二行、3 号黑体 | 截图通过 | `formal-digital-all-1.png` 显示左上第二行;保密期限具体写法仍需机关确认。 |
|
| 7.2.2 | 密级和保密期限第二行、3 号黑体 | 截图通过 | `formal-digital-all-1.png` 显示左上第二行;保密期限具体写法仍需机关确认。 |
|
||||||
| 7.2.3 | 紧急程度及顺序 | 截图通过 | `formal-digital-all-1.png` 显示份号、密级、紧急程度顺序;是否适用由发文机关决定。 |
|
| 7.2.3 | 紧急程度及顺序 | 截图通过 | `formal-digital-all-1.png` 显示份号、密级、紧急程度顺序;是否适用由发文机关决定。 |
|
||||||
| 7.2.4 | 机关标志居中、红色、距版心上边缘 35 mm | 版式结构通过,标准字体需复核 | `formal-digital-all-1.png` 加 `references/assets/visual-audit/header-coordinates.log` 的 PDF 字形量测;左上字段不再推动机关标志。机关标志字体按标准为推荐小标宋,本机缺少该字体,严格模式失败;`formal-joint-1.png` 已观察主办机关在前、联署机关分行排列且“文件”置右并按联署名称上下居中,复杂机关专用标志仍需模板。 |
|
| 7.2.4 | 机关标志居中、红色、距版心上边缘 35 mm | 版式结构通过,标准字体需复核 | `formal-digital-all-1.png` 加 `references/assets/visual-audit/header-coordinates.log` 的 PDF 字形量测;左上字段不再推动机关标志。机关标志字体按标准为推荐小标宋,本机缺少该字体,严格模式失败;`formal-joint-1.png` 已观察主办机关在前、联署机关分行排列且输入含“文件”时将“文件”置右并按联署名称上下居中,复杂机关专用标志仍需模板。 |
|
||||||
| 7.2.5 | 文号下空二行、年份、六角括号、序号规则 | 截图通过 | `formal-digital-all-1.png`、`formal-digital-upward-1.png`;输入验证拒绝“第”和前导零。 |
|
| 7.2.5 | 机关标志下空二行、文号年份、六角括号、序号规则 | 截图通过 | `formal-digital-all-1.png`、`formal-digital-upward-1.png`;输入验证拒绝“第”和前导零。 |
|
||||||
| 7.2.6 | 上行文签发人及多签发人 | 结构通过,仍需人工 | `formal-digital-upward-1.png` 已见同一行和两个签发人;超过两个签发人或联合行文转机关模板。 |
|
| 7.2.6 | 上行文签发人及多签发人 | 结构通过,仍需人工 | `formal-digital-upward-1.png` 已见同一行和两个签发人;超过两个签发人或联合行文转机关模板。 |
|
||||||
| 7.2.7 | 文号下 4 mm、版心等宽红线 | 截图通过 | `formal-digital-all-1.png` 红线与版心同宽;预印模式不重绘,改由纸样提供。 |
|
| 7.2.7 | 文号下 4 mm、版心等宽红线 | 截图通过 | `formal-digital-all-1.png` 红线与版心同宽;预印模式不重绘,改由纸样提供。 |
|
||||||
| 7.3.1 | 标题二号小标宋、红线下空二行、居中 | 版式结构通过,标准字体需复核 | `formal-digital-all-1.png`、`formal-preprinted-1.png`;本机缺小标宋时使用替代并报警,不能把替代字体称为标准字体。 |
|
| 7.3.1 | 标题二号小标宋、红线下空二行、居中 | 版式结构通过,标准字体需复核 | `formal-digital-all-1.png`、`formal-preprinted-1.png`;本机缺小标宋时使用替代并报警,不能把替代字体称为标准字体。 |
|
||||||
@@ -84,7 +84,7 @@ bash tests/visual-audit.sh /tmp/gongwen-visual-audit-all-rules
|
|||||||
|
|
||||||
1. 版头字段曾以普通段落排列,份号、密级、紧急程度会把电子红头机关标志向下推移,预印红头套打的留白也会被挤开。现在这些字段改为首面固定定位,版心中保留等高占位;`formal-digital-all-1.png` 和 `formal-preprinted-1.png` 已重新观察。
|
1. 版头字段曾以普通段落排列,份号、密级、紧急程度会把电子红头机关标志向下推移,预印红头套打的留白也会被挤开。现在这些字段改为首面固定定位,版心中保留等高占位;`formal-digital-all-1.png` 和 `formal-preprinted-1.png` 已重新观察。
|
||||||
2. 命令(令)分支曾把20mm误当成纸顶边距;现在恢复37mm天头,并从版心上边缘下20mm量测。
|
2. 命令(令)分支曾把20mm误当成纸顶边距;现在恢复37mm天头,并从版心上边缘下20mm量测。
|
||||||
3. 联合行文曾把联署机关错误压成同一横行;现在主办机关在前、联署名称分行排列,“文件”置右并按联署名称上下居中,名称过长时自适应标志字号。
|
3. 联合行文曾把联署机关错误压成同一横行;现在主办机关在前、联署名称分行排列,输入含“文件”时将“文件”置右并按联署名称上下居中,名称过长时自适应标志字号。
|
||||||
4. 纪要人员曾追加在附件和版记之后;现在紧跟正文或附件说明下一行。
|
4. 纪要人员曾追加在附件和版记之后;现在紧跟正文或附件说明下一行。
|
||||||
5. 红线下标题曾只依赖段前距,部分渲染器会把标题贴在线上。现在红线或纸上红线之后写入两个显式 28 磅空段落;截图能看到标题与红线之间的实际距离。
|
5. 红线下标题曾只依赖段前距,部分渲染器会把标题贴在线上。现在红线或纸上红线之后写入两个显式 28 磅空段落;截图能看到标题与红线之间的实际距离。
|
||||||
6. 主送机关带不带末尾冒号时曾可能重复,现在归一化为一行全角冒号。
|
6. 主送机关带不带末尾冒号时曾可能重复,现在归一化为一行全角冒号。
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ const MARGIN = { top: 2098, bottom: 1984, left: 1588, right: 1474 };
|
|||||||
const CONTENT_W = PAGE_W - MARGIN.left - MARGIN.right;
|
const CONTENT_W = PAGE_W - MARGIN.left - MARGIN.right;
|
||||||
const FIRST_LINE_INDENT = 640;
|
const FIRST_LINE_INDENT = 640;
|
||||||
const ONE_CHAR_INDENT = FIRST_LINE_INDENT / 2;
|
const ONE_CHAR_INDENT = FIRST_LINE_INDENT / 2;
|
||||||
|
// Four 3号 Chinese characters equal 1280 dxa; GB/T 9704-2012 7.3.5.1/7.3.5.3
|
||||||
|
// uses this right indent for the signed/sealed date position.
|
||||||
|
const FOUR_CHAR_RIGHT_INDENT = FIRST_LINE_INDENT * 2;
|
||||||
// First-page red-head frames use the type-area top as their vertical anchor.
|
// First-page red-head frames use the type-area top as their vertical anchor.
|
||||||
// GB/T 9704-2012 7.2.1-7.2.4 then become direct coordinates: the first
|
// GB/T 9704-2012 7.2.1-7.2.4 then become direct coordinates: the first
|
||||||
// header line is y=0 and the agency mark begins 35 mm below it. Keeping the
|
// header line is y=0 and the agency mark begins 35 mm below it. Keeping the
|
||||||
@@ -494,15 +497,15 @@ function estimatedTextWidthDxa(text) {
|
|||||||
function signatureParagraphs(args) {
|
function signatureParagraphs(args) {
|
||||||
if (args["seal-mode"] === "signed") {
|
if (args["seal-mode"] === "signed") {
|
||||||
if (!args.signer || !args["signer-title"]) throw new Error("signed seal mode requires --signer and --signer-title");
|
if (!args.signer || !args["signer-title"]) throw new Error("signed seal mode requires --signer and --signer-title");
|
||||||
const lines = [rightWithIndent(`${args["signer-title"]} ${args.signer}`, FIRST_LINE_INDENT * 2)];
|
const lines = [rightWithIndent(`${args["signer-title"]} ${args.signer}`, FOUR_CHAR_RIGHT_INDENT)];
|
||||||
if (args.date) lines.push(...blankGridLines(1), rightWithIndent(args.date, FIRST_LINE_INDENT * 2));
|
if (args.date) lines.push(...blankGridLines(1), rightWithIndent(args.date, FOUR_CHAR_RIGHT_INDENT));
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
if (args["seal-mode"] === "seal") {
|
if (args["seal-mode"] === "seal") {
|
||||||
if (!args.sender && !args.date) return [];
|
if (!args.sender && !args.date) return [];
|
||||||
const lines = [];
|
const lines = [];
|
||||||
if (args.sender) lines.push(rightWithIndent(args.sender, FIRST_LINE_INDENT * 2));
|
if (args.sender) lines.push(rightWithIndent(args.sender, FOUR_CHAR_RIGHT_INDENT));
|
||||||
if (args.date) lines.push(rightWithIndent(args.date, FIRST_LINE_INDENT * 2));
|
if (args.date) lines.push(rightWithIndent(args.date, FOUR_CHAR_RIGHT_INDENT));
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
if (!args.sender && !args.date) return [];
|
if (!args.sender && !args.date) return [];
|
||||||
|
|||||||
@@ -165,7 +165,8 @@ if (profile === "formal") {
|
|||||||
check("Upward document number and signer share one row", paragraphs(doc).some((p) => /签发人:/.test(textOf(p)) && /〔|\[|文号|发/.test(textOf(p))), "signer is in the same paragraph row as the document number");
|
check("Upward document number and signer share one row", paragraphs(doc).some((p) => /签发人:/.test(textOf(p)) && /〔|\[|文号|发/.test(textOf(p))), "signer is in the same paragraph row as the document number");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
check("Preprinted letterhead reserve", /w:before="\d{4,}"/.test(doc), "first-page top reserve is present");
|
const preprintedReserveParagraph = /w:line="1" w:lineRule="exact"[\s\S]*?w:snapToGrid w:val="false"[\s\S]*?w:sz w:val="2"/.test(doc);
|
||||||
|
check("Preprinted letterhead reserve", preprintedReserveParagraph, "first-page top reserve is present");
|
||||||
const preprintedParagraphs = paragraphs(doc);
|
const preprintedParagraphs = paragraphs(doc);
|
||||||
const titleXmlStart = firstTitleIndex < 0 ? doc.length : doc.indexOf(preprintedParagraphs[firstTitleIndex]);
|
const titleXmlStart = firstTitleIndex < 0 ? doc.length : doc.indexOf(preprintedParagraphs[firstTitleIndex]);
|
||||||
const preprintedHeaderXml = titleXmlStart < 0 ? doc : doc.slice(0, titleXmlStart);
|
const preprintedHeaderXml = titleXmlStart < 0 ? doc : doc.slice(0, titleXmlStart);
|
||||||
|
|||||||
+31
-1
@@ -40,6 +40,9 @@ assert len(gap) == 2
|
|||||||
assert all('w:line="560"' in paragraph and 'w:lineRule="exact"' in paragraph and 'w:snapToGrid w:val="false"' in paragraph for paragraph in gap)
|
assert all('w:line="560"' in paragraph and 'w:lineRule="exact"' in paragraph and 'w:snapToGrid w:val="false"' in paragraph for paragraph in gap)
|
||||||
PY
|
PY
|
||||||
|
|
||||||
|
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/preprinted-minimum.docx" --format formal --letterhead preprinted --letterhead-reserve-mm 37 --doc-no "示例发〔2026〕7号" --title "最小预印留白测试"
|
||||||
|
"$NODE" "$VERIFY" --input "$TMP_DIR/preprinted-minimum.docx" --profile formal --letterhead preprinted
|
||||||
|
|
||||||
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/digital.docx" --format formal --letterhead digital --org "示例单位文件" --doc-no "示例发〔2026〕1号" --title "电子红头测试" --page-number standard
|
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/digital.docx" --format formal --letterhead digital --org "示例单位文件" --doc-no "示例发〔2026〕1号" --title "电子红头测试" --page-number standard
|
||||||
"$NODE" "$VERIFY" --input "$TMP_DIR/digital.docx" --profile formal --letterhead digital
|
"$NODE" "$VERIFY" --input "$TMP_DIR/digital.docx" --profile formal --letterhead digital
|
||||||
unzip -p "$TMP_DIR/digital.docx" word/document.xml | grep -q '示例单位文件'
|
unzip -p "$TMP_DIR/digital.docx" word/document.xml | grep -q '示例单位文件'
|
||||||
@@ -123,7 +126,34 @@ test "${minutes_xml%%w:sectPr*}" != "$minutes_xml"
|
|||||||
test "${minutes_xml%%出席:*}" != "${minutes_xml%%w:sectPr*}"
|
test "${minutes_xml%%出席:*}" != "${minutes_xml%%w:sectPr*}"
|
||||||
|
|
||||||
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/seal-layout.docx" --format formal --letterhead preprinted --letterhead-reserve-mm 72 --title "签章位置测试" --sender "示例机关" --date "2026年9月10日" --seal-mode seal
|
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/seal-layout.docx" --format formal --letterhead preprinted --letterhead-reserve-mm 72 --title "签章位置测试" --sender "示例机关" --date "2026年9月10日" --seal-mode seal
|
||||||
unzip -p "$TMP_DIR/seal-layout.docx" word/document.xml | grep -q 'w:right="1280"'
|
python3 - "$TMP_DIR/seal-layout.docx" <<'PY'
|
||||||
|
import sys, zipfile
|
||||||
|
from xml.etree import ElementTree as ET
|
||||||
|
ns={'w':'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}
|
||||||
|
with zipfile.ZipFile(sys.argv[1]) as z:
|
||||||
|
root=ET.fromstring(z.read('word/document.xml'))
|
||||||
|
for p in root.findall('.//w:p', ns):
|
||||||
|
if ''.join(p.itertext()).strip() == '2026年9月10日':
|
||||||
|
assert p.find('./w:pPr/w:ind', ns).attrib['{http://schemas.openxmlformats.org/wordprocessingml/2006/main}right'] == '1280'
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise SystemExit('seal date paragraph with four-character right indent not found')
|
||||||
|
PY
|
||||||
|
|
||||||
|
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/signed-layout.docx" --format formal --letterhead preprinted --letterhead-reserve-mm 72 --title "签名章位置测试" --date "2026年9月10日" --seal-mode signed --signer-title "主要负责人" --signer "张三"
|
||||||
|
python3 - "$TMP_DIR/signed-layout.docx" <<'PY'
|
||||||
|
import sys, zipfile
|
||||||
|
from xml.etree import ElementTree as ET
|
||||||
|
ns={'w':'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}
|
||||||
|
with zipfile.ZipFile(sys.argv[1]) as z:
|
||||||
|
root=ET.fromstring(z.read('word/document.xml'))
|
||||||
|
for p in root.findall('.//w:p', ns):
|
||||||
|
if ''.join(p.itertext()).strip() == '2026年9月10日':
|
||||||
|
assert p.find('./w:pPr/w:ind', ns).attrib['{http://schemas.openxmlformats.org/wordprocessingml/2006/main}right'] == '1280'
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise SystemExit('signed date paragraph with four-character right indent not found')
|
||||||
|
PY
|
||||||
|
|
||||||
if "$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/bad-doc-no.docx" --format formal --letterhead digital --org "示例单位文件" --doc-no "示例发〔2026〕01号" --title "非法文号" >"$TMP_DIR/bad-doc-no.out" 2>&1; then
|
if "$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/bad-doc-no.docx" --format formal --letterhead digital --org "示例单位文件" --doc-no "示例发〔2026〕01号" --title "非法文号" >"$TMP_DIR/bad-doc-no.out" 2>&1; then
|
||||||
echo "invalid document number unexpectedly succeeded" >&2
|
echo "invalid document number unexpectedly succeeded" >&2
|
||||||
|
|||||||
Reference in New Issue
Block a user