# 单页 PDF 翻译处理规范 本轮源语言为 英语,目标语言为 英语。第七章提供的原文均为源语言,...

创建于:2026年8月22日

使用 GPT-5.6 Thinking 回答 Chat01

问题

单页 PDF 翻译处理规范

本轮源语言为 英语,目标语言为 英语。第七章提供的原文均为源语言,译文均为目标语言;不得自行改变既定语言方向。

本规范分为十章。第一章说明任务;第二章定义基础对象模型;第三章规定针对单处内容的原子处理方法;第四章规定整页路线的识别与编排;第五章规定各路线共用的字体、排版与脚本规则;附章 P 规定 PDF-safe 字体预处理;附章 Q 规定全书字体使用声明协议;第六章规定失败状态;第七章给出本轮输入;第八章规定基础输入与输出完整性检查;第九章规定目标结果自检、闭环重试与交付验收;第十章规定唯一输出协议。

目录

text
第一章 任务说明与执行边界 1.1 任务目标 1.2 本轮输入 1.3 执行边界:只实施已给定的替换,不自行充当审校器 1.4 最高优先级失败出口:无法下载 PDF 第二章 基础对象模型:N、V、I 2.1 三类待译文字载体 2.2 分类范围限定 第三章 原子处理方法 3.1 N 处理器:原生文本替换 3.2 V 处理器:矢量轮廓文字删除与原生文本重建 3.3 I 处理器:图像像素文字编辑 3.3.1 公共图像编辑方法 3.3.2 I-LOCAL:局部补丁模式 3.3.3 I-FULL:整页图像模式 第四章 页面路线:A、3C、3D、H、R 4.1 路线与载体组合的唯一对应关系 4.2 路线识别方法 4.3 各路线定义与处理器编排 4.4 路线锁定规则 第五章 公共规则 5.1 固定字体映射与字体映射规则 5.2 不同文字体系混排排版规则 5.3 公共资源目录与字体路径 5.4 脚本通用约束 5.5 PDF 字体嵌入与子集化强制规则 5.6 依赖限制 附章 P PDF-safe 字体预处理 P.1 适用范围与执行时机 P.2 固定 PDF-safe 字体清洗算法 P.3 临时字体复用与生命周期 P.4 N/V 写入文本的 NFC 规范化 P.5 与固定字体映射及 PDF 子集化的关系 P.6 禁止事项 附章 Q 全书字体使用声明协议 Q.1 目的与适用范围 Q.2 字体使用统计口径 Q.3 固定声明格式 Q.4 与单页字体处理及最终 PDF 的关系 Q.5 禁止事项 第六章 失败状态与特殊出口 第七章 本轮输入内容 第八章 基础输入与输出完整性检查 8.1 输入检查 8.2 全路线公共输出完整性检查 8.3 N 处理器基础检查 8.4 V 处理器基础检查 8.5 I-LOCAL 处理器基础检查 8.6 I-FULL 处理器基础检查 8.7 页面路线基础检查组合 8.8 报错处理 第九章 目标结果自检、闭环重试与交付验收 9.1 核心原则:不审校内容,但必须验证实施结果 9.2 自检范围限定与两层验证 9.3 N 处理器目标结果自检 9.4 V 处理器目标结果自检 9.5 I-LOCAL 处理器目标结果自检 9.6 I-FULL 处理器目标结果自检 9.7 全路线公共视觉与排版检查 9.8 PDF 字体嵌入与子集化结果检查 9.9 非目标内容保护检查 9.10 闭环重试与最终交付条件 9.11 页面路线结果自检组合 第十章 唯一输出协议

第一章 任务说明与执行边界

1.1 任务目标

阅读本规范,然后按规范进行处理。你必须基于同一份真实单页 PDF 自行选择最合适的处理路线,并严格遵守本规范唯一的输出协议。

外层程序不会预判本页应该走哪条路线。你必须先下载、打开并检查真实的单页 PDF,再依据第二章的载体定义和第四章的路线规则完成判定。

请访问第七章提供的“单页 PDF 链接”,下载并打开本轮实际需要处理的单页 PDF,对真实单页 PDF 进行解析、生成处理脚本、执行和检查。生成的脚本只能读取 --input 指定的本地 PDF。

无论选择哪条路线,最终都必须输出一个接受 --input 单页 PDF、输出 --output 单页 PDF 的 Python 脚本。路线 R 的结果没有原生文本层是允许且正常的;路线 A/3C/3D 则必须保留或重建可搜索的原生文本层;路线 H 必须保留补丁范围外的原生文本层,且不得为图片内部文字伪造 OCR 或隐藏文本层。

整体处理思路:

  • 路线 A/3C/3D/H 不把整页转换成图片,不用整页涂抹或叠加文字;必须依据实际单页 PDF 的对象结构完成原生替换、矢量重建或局部图片补丁。
  • 路线 R 把本页视为一张整页图像,使用局部放大、真实擦除中文像素、写入译文、缩回原比例的方式完成翻译;最后将译后的整页图像以原页面尺寸封装为输出 PDF。本路线不要求也不得伪造 PDF 原生文本层。

1.2 本轮输入

本轮提供:

  • 单页 PDF 链接:用于下载、检查对象结构、渲染、定位并据此生成脚本;
  • 本页原文转录和审校译文:用于逐项确认每一处替换任务;
  • 固定字体映射:对 N、V 载体,用于确定写入译文时唯一允许使用的目标字体文件;对 I 载体,它是优先复用的候选字体资源。图像像素没有可查询的原文 PDF 字体,I 载体也可以通过第十章的字体资源下载区块补充字体。

原文、译文和单页 PDF 链接见第七章,固定字体映射见 5.1。

1.3 执行边界:只实施已给定的替换,不自行充当审校器

第七章的“原文”和“译文”是本轮唯一权威的内容范围。你的任务是把它们之间已经确定的替换写入真实 PDF;不要自行决定还应翻译、删除、保留或检查哪些其他内容。

本条所称“不自行充当审校器”,仅表示不得自行修改第七章给出的译文、不得自行增加新的翻译任务、不得把第七章之外的其他可见中文擅自加入本轮替换范围;它不表示可以跳过对既定替换结果的验证。

对于第七章已经明确给出的每一项替换,必须验证它是否在真实输出 PDF 中被完整、正确地实施。输入检查、基础输出完整性检查由第八章规定;目标内容、排版、字体、对象和视觉结果的闭环验证由第九章规定。

第三章至第五章中如提及检查,均表示引用第八章和第九章中适用于对应处理器和页面路线的规则,不得脱离第七章给定任务范围自行创造新的翻译目标。

仍须根据真实 PDF 的基本对象类型选择 A、3C、3D、H 或 R,但路线选择完成后只按已给定原文/译文实施替换。翻译内容本身的语言质量、措辞选择和第七章之外哪些文字也应翻译,不是本脚本的自主判断任务;已给定替换是否实际成功,则必须按照第九章完成验证。

1.4 最高优先级失败出口:无法下载 PDF

如果你无法亲自从“单页 PDF 链接”成功下载文件、使用 PyMuPDF 成功打开该文件、确认它确实只有一页,
则不得根据原文转录、译文、文件名、截图或任何猜测生成脚本。

此时四个输出协议区块都必须且只能输出 [无法下载PDF]。不得输出脚本、命令、解释、其他标记或任何额外文字。
[无法下载PDF] 仅表示本轮无法访问并检查真实 PDF;它不表示页面没有文字、没有可映射字体,也不代表任何路线选择。
外层程序会只重试当前任务实例,因此你必须诚实使用这个失败出口,不能臆测真实 PDF 的对象结构。


第二章 基础对象模型:N、V、I

2.1 三类待译文字载体

先对本页需要替换的全部可见文字建立以下三类载体集合:

  • N(Native Text):可由 PDF 文本对象提取的原生可见文字。
  • V(Vector Outline Text):不是 PDF 文本对象,而是由矢量路径、轮廓、填充或描边绘制出的可见文字。
  • I(Image Pixel Text):存在于扫描图、截图、位图或其他图像像素内部的可见文字。

每一处待替换文字必须且只能归入一类载体。三类载体分别由第三章中对应的原子处理方法处理:

text
N -> 3.1 N 处理器 V -> 3.2 V 处理器 I -> 3.3 I 处理器

2.2 分类范围限定

载体分类只统计第七章“原文”和“译文”所确定的待替换文字。页面中与本轮替换无关的图片、照片、Logo、表格线、箭头、电路图、普通矢量图形或其他资源,不得影响载体分类,也不得影响第四章的路线判定。

具体限定:

  • 只有图片内部实际存在本轮需要替换的文字时,该图片才计入 I
  • 只有矢量路径实际构成本轮需要替换的文字时,该矢量内容才计入 V
  • 页面中存在某类对象,不代表待替换文字属于该类载体。

第三章 原子处理方法

本章规定针对单处待替换文字的处理方法。第四章的五条页面路线只负责编排调用本章的处理器,不得另行定义与本章不同的处理方法。

本章中为实施既定替换而进行的定位、文字测量、换行、字号计算、bbox 适配、ROI 计算、背景采样和绘制范围计算,属于处理算法,不属于第八章和第九章所称的保存后验收。

3.1 N 处理器:原生文本替换

适用于载体为 N 的每一处待替换文字。凡调用本处理器的位置,包括路线 A、路线 3D 的原生文字支和路线 H 中的 N 目标,均逐条适用以下全部规则。

步骤一:解析 PDF

使用 PyMuPDF 打开单页 PDF,解析页面中的组成对象,并找到所有原生可见文本对象。
对每个文本对象读取:文字内容、字体、字号、颜色、方向、旋转角度、包围矩形 bbox。

固定字体映射表的映射键已经去除了 PDF 子集字体名称中类似 ABCDEF+ 的六位大写字母前缀。
如果本页提取出的字体名仍带有这种子集前缀,查询固定映射前必须先去除该前缀;
不得因为子集前缀不同而认为它是未映射的新字体。

步骤二:定位与判断

对比第七章的“原文”和“译文”,找到需要替换的地方。

不得只处理正文段落。第七章已经列出的流程图、时序图、表格、箭头旁、图题和矢量轮廓中的每一个普通中文标签也必须逐项核对并替换。技术缩写可按审校译文保留,但包含普通中文词的混合标签必须完整按照审校译文实施替换:例如审校译文为“主机驱动 NACK”时,原标签 Master Drives NACK 中的 MasterDrives 都必须消失,只保留审校译文明确保留的 NACK

不得因为页面上另外存在未列入第七章的中文而自行增加翻译任务。

步骤三:提取文本信息

对每个命中位置,记录:原文内容、原文字体、字号、颜色、方向、旋转角度、包围矩形 bbox。
原文 bbox 是译文唯一允许占用的区域,不得扩大,不得增加容差。

步骤四:替换文本

必须严格使用 5.1 针对整本 PDF 预先确定的固定字体映射,也就是 target_font_file
指定的字体文件,禁止替代方案。
译文中的全部字符,包括汉字、拉丁字母、数字、西文标点、英语标点、单位、型号、寄存器名、引脚名、技术缩写及其他可见字符,均必须使用该位置原字体对应的同一个 target_font_file
不得继续使用原文字体或该页已有的其他西文字体写入译文中的任何字符。
禁止使用固定字体映射表之外的任何字体。
不得使用映射表之外的字体,不得使用 PyMuPDF 内置 china-schina-tjapan
korea 等 CJK 字体临时替代,也不得读取外部字体映射配置文件。

字体映射规则见 5.1,不同文字体系混排排版规则见 5.2,字体注册与子集化规则见 5.5。

对每个命中位置:

  1. 以原文字号为初始值尝试排入译文;
  2. 若超出 bbox,整段文字的整体缩放比例乘以 0.95,重新尝试;
  3. 持续缩小直到译文完整位于 bbox 内;
  4. 如果因换行、定位或其他排版需要将译文拆分为多个写入片段,各写入片段必须使用同一个映射字体,并统一应用整体缩放系数;
  5. 确认可以放入后,先删除原中文文字(只删文字,不删图片和矢量图形),
    再写入译文。

步骤五:处理完成后,按第八章和第九章中适用于 N 处理器及当前页面路线的规则保存、自检、必要时修改并重新执行,只有实际结果通过全部适用检查后才能交付。

3.2 V 处理器:矢量轮廓文字删除与原生文本重建

适用于载体为 V 的每一处待替换文字。凡调用本处理器的位置,包括路线 3C、路线 3D 的轮廓文字支和路线 H 中的 V 目标,均逐条适用以下全部规则。

  • 这类目标通常无法由 page.get_text() 提取,而是由大量 vector drawing/path 构成。缺少原生文字对象或无法查询原字体名称,本身不触发 [本页无可映射字体];此时必须按照 5.1 为该 V 目标从固定映射表中选择并硬编码一个已有映射键。只有无法为非空译文确定非 nulltarget_font_file 时,才输出 [本页无可映射字体]
  • 目标是安全删除原中文的可见矢量轮廓,并将译文作为可见、可搜索、可复制的原生 PDF 文本
    写入原位置;不是添加隐藏 OCR 层,也不是把整页转为图片,更不是在原中文上叠加英语。
  • 对每个替换任务,脚本必须硬编码 source_text、完整译文以及因换行、旋转或定位而需要的写入片段、映射键、target_font_file、bbox、对齐方式、
    旋转、字号、颜色和粗细。所有写入片段必须使用同一个映射字体。bbox 必须来自实际可见文字区域,排版结果不得超出该 bbox。
  • 当该页没有原生字体时,只能选用固定映射表中与该页视觉样式对应的、非 null 的既有目标字体;
    不得使用系统字体、内置 CJK 字体或映射表之外的字体。该目标字体必须用于译文中的全部字符,不得为拉丁字母、数字、标点或技术缩写另行使用其他字体。
  • 必须优先在 PDF 内容流中定位只属于已给定替换文字的完整 q ... Q 绘制分组,并仅删除这些分组。
    若无法可靠定位,可使用对应位置的最小遮蔽 bbox。
  • page.insert_font(..., set_simple=False) 注册固定目标字体,以 page.insert_text() 写入全部可见译文字符。
  • 处理完成后,按第八章和第九章中适用于 V 处理器及当前页面路线的规则保存、自检、必要时修改并重新执行,只有实际结果通过全部适用检查后才能交付。

3.3 I 处理器:图像像素文字编辑

适用于载体为 I 的每一处待替换文字。本处理器有两种工作模式:

text
I-LOCAL:局部补丁模式,供路线 H 使用 I-FULL:整页图像模式,供路线 R 使用

两种模式共用 3.3.1 的图像编辑方法,区别只在处理范围与最终 PDF 封装方式。

图片内文字不得作为 OCR 文本层写入。

3.3.1 公共图像编辑方法

对图中每一处需要翻译的区域重复执行以下步骤:

步骤一:定位与判断
扫描高分辨率渲染图,结合原文和译文逐项定位第七章已经给出的原始中文。不得仅依据译文条目顺序猜测位置,也不得因为扫描过程中看到第七章之外的其他中文而自行增加翻译任务。

步骤二:局部放大
裁取包含该处文字及其周边的足够大局部矩形区域(ROI),在放大状态下进行后续编辑。

步骤三:擦除原文
在放大区域内用该位置的背景色精确擦除原中文文字,恢复干净背景。

  • 只擦文字本身,不动任何线条、波形、箭头、边框、表格、图形或相邻内容;
  • 背景为纯色时直接精确填充背景色;
  • 背景含图形、纹理或渐变时必须局部修补背景,保留周围图形;
  • 不得只在中文上叠加英语,不得留下中文残影、白块或不自然的修补痕迹。

步骤四:写入译文
在擦除后的位置写入译文。

【视觉容器规则】
可用区域不是中文字符笔画的紧包围框,而是原文字所属的完整视觉容器:

  • 表格文字:使用对应单元格内部区域,保留合理内边距;
  • 段落文字:使用原段落整体所占区域;
  • 标题和图题:使用原标题所在的完整横向区域;
  • 编号脚注:每个编号及其对应正文是一个独立区域,必须逐条对应;
  • 图表坐标轴文字:使用坐标轴与周边图形之间的空白区域;
  • 时序图或框图标签:使用不接触线条、箭头和其他标签的局部空白区域。

译文不得跨越所属视觉容器的边框、表格线或图形边界。

【字体与字号规则】

  1. 图像像素中的文字没有可查询的原文 PDF 字体。首先继承原图的视觉层级,根据原文用途区分章节标题、表头、表格正文、普通正文、图题、图中标签、脚注等文字类型;优先选用固定映射表中与该视觉样式协调的非 null target_font_file。若现有映射资源不适合该图像文字的视觉样式,必须在第十章的字体资源下载区块声明所需字体,外层程序会在执行脚本前将其放入 FONT_DIR。I 载体不得因固定映射缺失或不适合而输出 [本页无可映射字体]
  2. 同一视觉层级的文字必须保持相同或视觉一致的选定字体、字重、颜色、字形风格、基准字号以及左对齐、居中、右对齐关系;译文中的汉字、拉丁字母、数字、标点、单位、型号、技术缩写及其他字符必须统一使用对应的同一个选定字体文件,不得使用其他字体;
  3. 每个译文先使用所属层级的统一基准字号,在对应视觉容器内正常换行;
  4. 只有完整译文以统一基准字号并合理换行后仍无法放入视觉容器时,才允许对整个文本块做最小幅度的等比缩小;
  5. 缩小后的每行宽度不得超过容器宽度 W,所有行总高度不得超过容器高度 H;不得只缩小某些字、某一行或某个词;
  6. 同一组文字能以统一基准字号放下时必须保持一致,只有确实放不下的个别文本块才可单独最小幅度缩小;
  7. 空间不足绝不是省略、缩写、简化或漏译的理由,只能通过合理换行和缩小字号解决;
  8. 写入后选定字体的视觉风格、颜色、字重必须与被替换的原文视觉协调,不得突兀。

对 I 载体,固定映射表中的现有字体优先于新增下载字体;只有现有资源均不适合图像中的视觉样式时,才可在字体资源下载区块中补充字体。选定某个字体文件后,只能通过字号、颜色、对齐、字距、行距以及必要时的描边参数协调视觉效果,不得为同一处译文的不同字符改用其他字体。

【硬性禁止】

  • 文字溢出视觉容器边界;
  • 文字压住线条、边框、波形、箭头;
  • 文字与相邻文字重叠;
  • 字号缩小到肉眼无法辨认;
  • 漏掉第七章已经给出的任何 I 类型替换目标,或只处理原生文字层而忽略第七章已经明确列出的图中中文。

步骤五:缩回原尺寸
将编辑完成的局部区域缩回原始比例并贴回对应位置,与周围图像无缝衔接。图像编辑操作必须严格限制在明确许可的局部编辑区域内,不得主动修改其他区域。

3.3.2 I-LOCAL:局部补丁模式

本模式只对属于 I 的目标所在的局部矩形进行图像化处理,页面其余内容必须保持原生对象。它不是整页截图,也不是隐藏 OCR 层。

  1. 打开输入 PDF,读取单页、页面尺寸和旋转。
  2. 复制输入页作为输出页,绝不把全页重新封装成一张图片。仅对每个已给定的局部矩形,以至少 400 DPI 渲染原始页面的同一矩形,使用 Pillow 擦除原中文并写入译文;补丁外像素不得被主动修改。
  3. 将编辑后的局部补丁准确覆盖到原页面同一矩形。补丁覆盖区域以外的原生文本、矢量、链接、图片和页面尺寸必须保留。补丁区域内若原来没有原生文本,禁止额外添加隐藏文本层。
  4. 处理完成后,按第八章和第九章中适用于 I-LOCAL 处理器及路线 H 的规则保存、自检、必要时修改并重新执行,只有实际结果通过全部适用检查后才能交付。

3.3.3 I-FULL:整页图像模式

本模式把 PDF 的唯一页面渲染为高分辨率整页图像,采用“局部放大—擦除—写字—缩回”的图像编辑方式完成翻译;再把译图封装回同尺寸的单页 PDF。它不是隐藏 OCR 层,不得在原中文上叠加英语,也不要求原生文本层。

  • 用 PyMuPDF 打开 --input,并按照第八章执行输入检查;以至少 300 DPI(优先 400 DPI)渲染该页为脚本工作目录中的临时 PNG,再用 Pillow 编辑该临时图。
  • 所有渲染缩放、ROI、允许改动区域、背景色、修补方法、字体文件名、字号、颜色、对齐、换行结果、原文和译文都必须是 Python 字面量;不得读取外部 JSON、YAML、TXT 或调用网络、AI API、subprocess。
  • 译文中的全部字符只能从 5.3 规定的公共 FONT_DIR / "字体文件名" 所指向的字体读取。可优先使用固定字体映射中非 null 的现有 target_font_file;若其均不适合图像文字的视觉样式,必须使用本页字体资源下载区块中声明、并由外层程序预先放入 FONT_DIR 的字体。汉字、拉丁字母、数字、标点、单位、型号、技术缩写及其他字符必须统一使用同一个选定字体文件,不得使用系统字体、脚本目录私有资源或其他字体。I-FULL 不得因固定映射缺失或不适合而输出 [本页无可映射字体]
  • 图像编辑操作必须严格限制在硬编码的允许编辑区域内,不得主动修改允许编辑区域之外的像素。
  • 用 PyMuPDF 新建单页 PDF,页面宽高、旋转方向和裁切尺寸必须与输入页一致;把译后整页图像铺满该页,并以临时文件保存。输出 PDF 中允许只有这一张显示位图和零可提取文本。
  • 临时 PNG 必须在成功和失败时清理;不得交付半成品。
  • 处理完成后,按第八章和第九章中适用于 I-FULL 处理器及路线 R 的规则保存、自检、必要时修改并重新执行,只有实际结果通过全部适用检查后才能交付。
  • 不得使用 --image,不得输出 PNG 作为最终结果。

第四章 页面路线:A、3C、3D、H、R

4.1 路线与载体组合的唯一对应关系

根据本页实际需要替换的可见文字载体,在 A、3C、3D、H、R 五条平级路线中选择且只能选择一条。

五条路线是互斥的最终页面路线,不是依次尝试的降级层级。路线只能由原始 PDF 中待译文字的真实载体决定,不得因为某条路线实现困难、字体缺失、脚本报错、检查失败或处理效果不理想而改选其他路线。

唯一对应关系如下:

text
N -> 路线 A V -> 路线 3C N + V -> 路线 3D N + I -> 路线 H V + I -> 路线 H N + V + I -> 路线 H I -> 路线 R

即:

  • 路线 A:待译文字载体集合只有 N
  • 路线 3C:待译文字载体集合只有 V
  • 路线 3D:待译文字载体集合为 N + V,且不存在 I
  • 路线 H:待译文字载体集合中存在 I,并且同时存在 NV
  • 路线 R:待译文字载体集合只有 I

路线 H 不是路线 A、3C 或 3D 失败后的降级路线。路线 R 虽然在技术上可以把其他类型页面整页栅格化,但在本规范中只代表待译文字全部位于图像像素中的 I 页面,不得作为其他路线的通用兜底。

某个原字体缺少可用的 target_font_file、某个目标难以定位或某段脚本执行失败,都不会改变页面原本的 NVI 载体集合,也不得改变已经判定的页面路线。

4.2 路线识别方法

判定本页的处理路线是最高优先级规则。

必须对实际下载的单页 PDF 同时检查 page.get_text()page.get_fonts(full=True)
page.get_images(full=True)page.get_drawings()page.get_contents(),并至少以 200 DPI 渲染后再作判断。
不得只依据转录文本、译文、文件名或页面截图推断路线。

判定路线时,必须先逐项核对第七章原文和译文所确定的全部待替换文字,并判断每一项实际属于 NVI。完成全部目标的载体归类后,再根据 4.1 的唯一组合表选择 A、3C、3D、H 或 R。

不得根据页面整体是否含有图片、绘图或文本粗略判路。载体分类范围限定见 2.2。

4.3 各路线定义与处理器编排

路线 3D:原生文字与矢量轮廓文字共存页(优先级高于路线 A)

  • 若本页全部待替换文字的载体集合为 N + V,且不存在任何 I 类型待替换文字,则必须走路线 3D。不能因为 page.get_text() 非空而忽略属于 V 的轮廓文字并误走路线 A,也不能因为页面存在不含待译文字的图片而误走路线 H。
  • 路线 3D 必须由同一个页级专用 Python 脚本在同一份输入 PDF 上完成两类处理:
    1. 对原生文字按 3.1 N 处理器的既有原生文字替换规则处理;
    2. 对矢量轮廓文字完整执行 3.2 V 处理器的安全删除和原生译文重建规则。
      严禁把两类对象拆成两份 PDF、分别渲染后再拼接,也严禁只完成其中一类后交付。
  • 对原生文字:沿用原字体映射、原生文本 bbox 和字体注册规则。
    对轮廓文字:沿用 V 处理器的完整 q ... Q 分组删除和 bbox 定位规则。
  • 3D 脚本必须分别硬编码两类目标的 source_text、完整译文、映射键、target_font_file、因换行、旋转或定位而需要的写入片段、bbox、颜色、字号、旋转和对齐;
    所有写入片段必须使用对应替换任务的同一个映射字体。原生文字 bbox 与轮廓文字 bbox 必须分别作为各自排版计算所使用的边界,任何译文字符均不得被排版到对应 bbox 之外。
  • 完成两类已给定替换后,按第八章和第九章中适用于路线 3D 的规则保存、自检、必要时修改并重新执行,只有实际结果通过全部适用检查后才能交付。

路线 A:仅原生文字页

  • 若本页全部待替换文字的载体集合只有 N,则必须走路线 A。页面可以同时存在普通矢量图形、表格线、电路图、照片、Logo 或不含待译文字的图片;这些非目标对象不构成 VI,也不改变路线 A 的判定。
  • 按 3.1 N 处理器的既有原生文字替换规则处理。原字体映射、bbox、字体注册、字体子集化和验证规则全部照常执行。

路线 3C:矢量轮廓文字页

  • 若本页全部待替换文字的载体集合只有 V,则必须走路线 3C。通常这类目标无法由 page.get_text() 提取,而是由大量 vector drawing/path 构成。页面中可以存在不含待译文字的图片或其他非目标资源;只要全部待替换文字均属于 V,这些资源就不得使页面改走 H 或 R。
  • 按 3.2 V 处理器的全部规则处理。

路线 H:图像文字与非图像文字混合页

  • 若本页待替换文字载体集合中存在 I,并且同时存在 NV,则必须走路线 H。
  • 路线 H 包括 N + IV + IN + V + I 三种组合。
  • 路线 H 中,属于 N 的目标完整执行 3.1 N 处理器;属于 V 的目标完整执行 3.2 V 处理器;属于 I 的目标完整执行 3.3.2 I-LOCAL 局部补丁模式。
  • 图片只是 Logo、照片、曲线、芯片照片或其他没有本轮待译文字的装饰内容时,不计入 I,不得因此选择路线 H。
  • 路线 H 是独立的最终页面路线,不得因原生字体映射为 null、某一目标难以处理或其他路线执行失败而触发。字体映射状态只影响对应路线是否能够成功执行,不改变页面的载体分类。
  • 若待替换文字全部属于 I,必须走路线 R,不得走路线 H。
  • 若待替换文字中不存在 I,必须根据 NV 的组合走路线 A、3C 或 3D,不得走路线 H。

路线 R:纯图像文字页

  • 若本页全部待替换文字的载体集合只有 I,不存在任何 NV 类型待替换文字,则必须走路线 R。
  • 按 3.3.3 I-FULL 整页图像模式处理。
  • 路线 R 的判定只取决于待译文字载体,不取决于页面是否暴露独立图片 XObject。某些页面即使没有可直接枚举的独立图片 XObject,只要所有待替换文字真实属于最终可见图像像素,仍属于 I
  • 若存在任何属于 NV 的待替换文字:同时存在 I 时必须走路线 H;不存在 I 时必须走路线 A、3C 或 3D。
  • 路线 R 不是路线 A、3C、3D 或 H 失败后的兜底路线。

4.4 路线锁定规则

一旦根据 NVI 载体集合确定路线,必须只执行对应路线的规则,且本轮不得改选其他路线:

  • 路线 A 只处理 N
  • 路线 3C 只处理 V
  • 路线 3D 同时处理 NV
  • 路线 H 同时处理其实际包含的 NVI
  • 路线 R 只处理 I

路线 3D 的原生文字部分逐条遵守 3.1 的全部规则,轮廓文字部分逐条遵守 3.2 的全部规则。

路线 H 中的 N 部分逐条遵守 3.1 的全部规则,V 部分逐条遵守 3.2 的全部规则,I 部分逐条遵守 3.3.2 的全部规则。

路线 A 的“仅处理原生文本对象”限制不适用于路线 3C、3D 或 H 中的非原生目标;V 处理器的矢量删除规则不得用于路线 A 或路线 R,但必须用于路线 3D 和路线 H 中属于 V 的目标。

不得因为原路线实现困难、字体映射缺失、目标定位困难、代码报错或检查未通过而将 A 改为 H 或 R、将 3C 改为 R、将 3D 改为 H 或 R,或进行任何其他跨路线降级。

检查失败时,应在已经锁定的路线内修改定位、删除、遮蔽、排版、字体注册、ROI、背景修补、换行、字号、坐标或其他实现参数并重新执行,不得通过改选路线逃避自检失败。


第五章 公共规则

本章规则由多个处理器和多条路线共用。任何路线在需要写入译文字符、排版文本、定位字体资源或生成脚本时,均逐条适用本章。

5.1 固定字体映射与字体映射规则

固定字体映射如下:

{
"version": 3,
"mappings": {
"Arial": {
"target_font_file": "Arimo-Regular.ttf",
"download_url": "https://raw.githubusercontent.com/googlefonts/Arimo/main/fonts/ttf/Arimo-Regular.ttf"
},
"Arial,Bold": {
"target_font_file": null,
"download_url": null
},
"Calibri": {
"target_font_file": "Carlito-Regular.ttf",
"download_url": "https://raw.githubusercontent.com/google/fonts/main/ofl/carlito/Carlito-Regular.ttf"
},
"Calibri,Bold": {
"target_font_file": "Carlito-Bold.ttf",
"download_url": "https://raw.githubusercontent.com/google/fonts/main/ofl/carlito/Carlito-Bold.ttf"
},
"Impact": {
"target_font_file": "Anton-Regular.ttf",
"download_url": "https://raw.githubusercontent.com/google/fonts/main/ofl/anton/Anton-Regular.ttf"
},
"Symbol": {
"target_font_file": null,
"download_url": null
},
"Times New Roman": {
"target_font_file": "Tinos-Regular.ttf",
"download_url": "https://raw.githubusercontent.com/google/fonts/main/ofl/tinos/Tinos-Regular.ttf"
},
"Times New Roman,Bold": {
"target_font_file": "Tinos-Bold.ttf",
"download_url": "https://raw.githubusercontent.com/google/fonts/main/ofl/tinos/Tinos-Bold.ttf"
},
"Times New Roman,Italic": {
"target_font_file": "Tinos-Italic.ttf",
"download_url": "https://raw.githubusercontent.com/google/fonts/main/ofl/tinos/Tinos-Italic.ttf"
},
"Wingdings": {
"target_font_file": null,
"download_url": null
},
"¿¬Ìå": {
"target_font_file": "KleeOne-Regular.ttf",
"download_url": "https://raw.githubusercontent.com/google/fonts/main/ofl/kleeone/KleeOne-Regular.ttf"
},
"ËÎÌå": {
"target_font_file": "Tinos-Regular.ttf",
"download_url": "https://raw.githubusercontent.com/google/fonts/main/ofl/tinos/Tinos-Regular.ttf"
}
}
}

映射表可能为空。映射表为空时,只有待译文字全部属于 I 的路线 R 可以继续处理,并应按本节的 I 规则通过字体资源下载区块准备字体;只要存在 N 或 V 类型的非空译文任务,仍必须按本节的无可映射字体规则处理。

字体映射规则:

  1. 映射表的每个键都是一个原字体映射键。
    处理某个原生文本对象时,必须先去除该对象字体名称中类似 ABCDEF+
    的六位大写字母子集前缀,再使用规范化后的字体名称查询映射表。

    对于 V 类型目标,如果无法从该目标取得可查询的原字体名称,必须在检查真实 PDF 和页面渲染结果后,从固定字体映射表已有条目中为该替换任务选择一个视觉样式对应的映射键,并将该映射键及其 target_font_file 作为 Python 字面量硬编码在脚本中。不得由脚本在运行时动态选择映射键,不得创建固定字体映射表中不存在的新键,也不得直接指定映射表之外的字体文件。

    对于 I 类型目标,图像像素不存在可查询的原字体名称。应先从固定字体映射表已有非 null 条目中选择视觉样式协调的字体;若均不适合,可在第十章字体资源下载区块中声明一个可下载字体,并将该字体文件名作为 Python 字面量硬编码在脚本中。I 类型不得因没有可查询的原字体、固定映射缺失或现有映射不适合而输出 [本页无可映射字体]

  2. 对 N、V 目标,target_font_file 是该原字体在需要写入任何译文字符时唯一允许使用的目标字体文件。当某个原文位置需要替换为非空译文时,必须使用该位置原字体对应的 target_font_file 写入译文中的全部字符,包括汉字、拉丁字母、数字、标点、单位、型号、寄存器名、引脚名、技术缩写及其他可见字符。

    对 I 目标,选定的既有 target_font_file 或本页资源下载区块声明的字体文件,是该位置写入全部译文字符时唯一允许使用的字体文件。

  3. 固定映射中的 download_url 仅用于标识 target_font_file 对应的固定字体资源,字体文件已经由外层程序准备在 FONT_DIR 中。I 目标需要补充字体时,下载信息只能在第十章的字体资源下载区块中声明;外层程序会在执行脚本前下载并放入 FONT_DIR。生成的 Python 脚本不得访问该 URL,不得联网下载字体,不得把下载逻辑写入脚本。对 N、V 目标,必须通过 FONT_DIR / "目标字体文件名" 读取本地 target_font_file 原始字体,并按照附章 P 生成本页临时 PDF-safe 字体后再用于 PDF 原生文本写入;对 I 目标仍直接通过 FONT_DIR / "字体文件名" 使用本地字体文件。

  4. 在调用 3.1 N 处理器的位置,包括路线 A、路线 3D 的原生文字支和路线 H 中的 N 目标,只要该原文位置对应的译文为非空字符串,其原字体对应的 target_font_file 就必须为非 null
    如果该译文为非空字符串,而对应的 target_font_filenull,则属于无可映射字体;无论译文包含汉字、拉丁字母、数字、标点或其他字符,均按相同规则判断。
    只有译文为空字符串、不需要写入任何字符时,target_font_filenull 才不触发该失败状态。

    V 目标同样必须从固定字体映射表中确定非 null 的 target_font_file;无法确定时属于无可映射字体。

  5. 如果至少一个 N 或 V 类型的非空译文任务未能按照本节规则确定一个非 nulltarget_font_file
    不得自行选择替代字体,不得继续使用原字体,不得生成部分替换结果,四个输出区块都必须且只能输出:
    [本页无可映射字体]
    该标记的性质与限制见第六章。

  6. 对通过 N 处理器或 V 处理器写入 PDF 原生文本时实际使用的每个不同 target_font_file,必须注册为一个不同且稳定的 PDF 字体别名。
    相同的 target_font_file 在同一页只能注册一次,并在所有对应的 N、V 目标中复用同一个别名。
    对 I 处理器使用的选定字体文件(既有 target_font_file 或本页资源下载字体),不得注册或嵌入为 PDF 字体;应由 Pillow 从对应字体文件加载,并在相同字体文件对应的所有 I 目标中复用。
    字体别名只是脚本内部用于引用 PDF 字体文件的标识,不代表字体款式或类别。
    可以按照本页实际用于 N、V 处理的目标字体文件名排序,依次使用 TRFONT1TRFONT2
    TRFONT3 等别名。

  7. 字号以每个原文位置的原始字号为初始值。
    若译文超出原始 bbox,则按照 3.1 步骤四规定的整体缩放规则逐步缩小。
    不得通过改用其他字体规避 bbox 限制。

5.2 不同文字体系混排排版规则

本节所称“使用同一个选定字体”,对 N、V 目标是指使用同一个 target_font_file,对 I 目标是指使用同一个既有 target_font_file 或本页资源下载字体文件。N、V 目标应使用该文件对应的同一个 PDF 字体别名;I 目标应使用 Pillow 从选定文件加载的字体对象,不得加载其他字体文件。

  1. 中中文等不同文字体系的混合文本不得按文字体系使用不同字体:
    整段译文中的全部字符必须统一使用该位置对应的选定字体文件,并保持统一基线、颜色、行高和视觉字高;
    不得为汉字、拉丁字母、数字、标点或其他字符分别选用不同字体。

  2. 汉字、拉丁字母、数字、西文标点、英语标点、单位、型号、寄存器名、引脚名及其他保留的西文内容,
    N、V 目标必须使用固定字体映射中该位置对应的 target_font_file;I 目标必须使用该位置选定的既有 target_font_file 或本页资源下载字体文件;
    不得继续使用该位置原文字对象对应的比例西文字体或该页已有的其他西文字体资源。

  3. 同一句混合文本仍属于一个替换任务:

    • 共用同一个原始 bbox 或视觉容器;
    • 只删除一次原文;
    • 如因换行、旋转、定位或其他排版需要拆成多个写入片段,各写入片段必须使用同一个选定字体;
    • 各写入片段连续排版;
    • 不得把同一句话拆成互不关联、分别缩放的多个替换任务。
  4. 所有写入片段必须使用同一个选定字体和同一个整体缩放比例。
    如果整行超出 bbox 或视觉容器,必须同步缩小整行所有写入片段;
    不得只缩小英语内容或只缩小西文内容。

5.3 公共资源目录与字体路径

所有页面共用同一个公共资源目录。
Python 处理脚本位于:
精翻工程\page_XXX\render\

公共资源目录位于:
精翻工程_图像处理公共资源\

字体统一放在公共资源目录的 resources\fonts 子目录中。
不得把字体下载、复制或保存到当前页面的 render\resources 目录。
第十章字体资源下载区块中声明的字体,由外层程序在执行脚本前下载到此目录;脚本不得自行下载、复制或移动字体。

脚本必须使用以下代码定位公共资源目录:

python
SCRIPT_DIR = Path(__file__).resolve().parent SHARED_RESOURCE_DIR = ( SCRIPT_DIR.parent.parent / "_图像处理公共资源" ) FONT_DIR = SHARED_RESOURCE_DIR / "resources" / "fonts"

固定映射中的原始 target_font_file 路径必须写成:

python
字体路径 = FONT_DIR / "字体文件名"

对 N、V 目标,上述路径只作为附章 P 的原始字体输入;实际传给 page.insert_font(..., fontfile=...) 的必须是附章 P 根据该原始字体生成的本页临时 PDF-safe 字体路径。对 I 目标仍直接使用上述 FONT_DIR / "字体文件名" 路径。

不得硬编码系统字体路径。
不得使用 SCRIPT_DIR / "resources" / "fonts"。

5.4 脚本通用约束

本页需要处理并输出 Python 源代码时,脚本必须满足以下全部规则:

  1. 第一行必须是以下三者之一:

    • # apply_translation.py:路线 A、3C 或 3D;
    • # apply_hybrid_image_translation.py:路线 H;
    • # apply_raster_translation.py:路线 R。
  2. 脚本末尾必须包含:
    if name == "main":
    main()

  3. 脚本接受且仅接受两个命令行参数:
    --input 输入的单页 PDF 绝对路径,required=True
    --output 输出的翻译后 PDF 绝对路径,required=True
    调用方式固定为:
    python "{脚本绝对路径占位符}" --input "{输入PDF绝对路径占位符}" --output "{输出PDF绝对路径占位符}"

  4. --input 参数必须按照第八章执行输入检查。

  5. 所有本次操作中确定的参数,包括替换对列表、字体文件名、每处译文字符串,
    全部以 Python 字面量形式硬编码在脚本内,不得读取外部配置文件。

  6. 所有页面必须直接共用公共字体资源,不得把公共原始字体复制到当前页面目录。附章 P 为 N、V 原生文本写入生成的本页临时 PDF-safe 派生字体除外;该临时字体只能存在于脚本运行期间使用的临时目录中,不得写回公共 FONT_DIR,也不得作为新的永久字体资源保存。公共资源目录与字体路径写法见 5.3。

  7. 脚本不得请求网络,不得调用任何 AI API,不得读取环境变量来获取编辑参数。

  8. 不得使用 subprocess 在脚本内部调用外部可执行文件。

  9. 依赖限制见 5.6。

  10. 路线 A、3C、3D,以及路线 H 中存在 N 或 V 原生文本写入时,在保存任何候选、临时或最终输出 PDF 之前,必须先对本次写入的嵌入字体执行子集化,
    只保留 PDF 中实际使用的字符,然后使用
    doc.save(output_path, garbage=4, deflate=True) 保存;
    不得先保存后子集化,也不得静默跳过子集化失败。路线 R 不写入 PDF 字体,禁止为了满足本条而伪造字体子集化。

  11. 路线 A、3C、3D,以及路线 H 中存在 N 或 V 原生文本写入时,如果需要多次保存不同候选 PDF,则每一次保存之前都必须重新调用
    doc.subset_fonts(fallback=True);不得仅在第一次保存前调用一次。路线 R 只需按 3.3.3 的 PDF 封装规则保存。

  12. 必须先保存到临时文件,并按第八章和第九章中适用于当前路线和处理器的规则完成检查;全部检查通过后使用 os.replace() 原子替换正式输出文件,失败时删除临时文件。

  13. N、V 目标的译文中的全部字符只能使用固定映射表中的 target_font_file。I 目标可以使用既有 target_font_file,或本页字体资源下载区块中声明并由外层程序准备在 FONT_DIR 的字体文件。不得继续使用原 PDF 字体、PyMuPDF 内置 CJK 字体或其他内置字体作为兜底。

  14. 中中文等不同文字体系的混合文本必须统一使用该位置对应的同一个选定字体:
    汉字、拉丁字母、数字、标点、单位、型号、寄存器名、引脚名、技术缩写及其他字符均不得使用其他字体;
    如因换行、旋转、定位或其他排版需要拆成多个写入片段,各写入片段必须使用同一个 N、V target_font_file 或同一个 I 选定字体文件,并保持统一基线、颜色、行高和视觉字高。

  15. 混合文本超出 bbox 或视觉容器时,所有字符必须使用同一个整体缩放系数同步缩小;
    不得分别独立缩放后再拼接。

  16. 第九章中能够通过 PyMuPDF、Pillow、fontTools 或 Python 标准库确定性完成的结果检查,应直接实现于生成脚本中。需要根据真实页面渲染结果进行视觉判断的检查,由你在本轮实际运行脚本后对真实输出 PDF 的渲染结果亲自完成;不得因为脚本内部无法使用 AI 或 OCR 而跳过第九章规定的视觉自检。

5.5 PDF 字体嵌入与子集化强制规则

本节仅适用于通过 N 处理器或 V 处理器写入 PDF 原生文本的部分,包括路线 H 中的 N、V 处理部分;不适用于 I 处理器,也不适用于路线 R 中由 Pillow 写入图像像素的文字。

  1. 本页通过 N 处理器或 V 处理器写入任何译文字符时实际使用的每一个目标字体文件,都必须使用
    “每个字体文件预处理一次、预注册一次、按别名复用”的方式:

    • 每个不同的 target_font_file 必须先按照附章 P,从 FONT_DIR 中的原始字体生成一个本页临时 PDF-safe 字体;
    • 相同的 target_font_file 在同一页只能生成一次临时 PDF-safe 字体,并在本页所有对应 N、V 目标中复用;
    • 每页使用
      page.insert_font(fontname=字体别名, fontfile=临时PDF-safe字体路径, set_simple=False)
      注册字体;
    • 同一个 target_font_file 在同一页只能注册一次;
    • 后续 insert_text()insert_textbox() 或其他文字写入操作,
      只能传入该目标字体文件对应的字体别名;
    • 禁止直接把 FONT_DIR 中未经附章 P 处理的原始 target_font_file 传给 page.insert_font()
    • 禁止在每次写字时重复传入 fontfile
  2. 字体别名必须与本页实际使用的 target_font_file 一一对应:

    • 每一个不同的 target_font_file 必须使用不同且稳定的字体别名;
    • 相同的 target_font_file 必须在本页所有位置复用同一个字体别名;
    • 字体别名只用于脚本内部引用字体文件,不代表字体款式、字体类别或字符类型;
    • 不得根据常规体、粗体、斜体、衬线体、无衬线体、汉字、拉丁字母、数字、标点或其他分类重新分配字体;
    • 可以按照本页实际使用的目标字体文件名排序,依次使用
      TRFONT1TRFONT2TRFONT3 等别名;
    • 不得为每一段文字或每种字符类型生成新的字体别名。
  3. 保存前必须执行:

    doc.subset_fonts(fallback=True)
    

    禁止使用不带 fallback=Truedoc.subset_fonts()

  4. 字体子集化完成后,必须保存到临时文件;临时输出的检查与原子替换按第八章和第九章执行。

  5. 脚本必须实际执行第九章规定的字体嵌入、字体子集化和最终结果验证。脚本正常完成后仍只打印输出路径和输出文件大小;第九章要求的字体名称、xref、字体流大小、子集状态、对象信息、像素差异等内部验证数据可以在内存中构造、比较并作为失败条件,不要求正常成功时打印。验证失败抛出的 RuntimeError 可以包含足以定位问题的诊断信息。

5.6 依赖限制

路线 A、3C、3D 可以使用 PyMuPDF(import pymupdf as fitz)和 fontTools;fontTools 只能用于附章 P 规定的 PDF-safe 字体预处理,不得用于自动选择字体、替换固定字体映射、改变排版规则或其他未经附章 P 明确规定的处理。路线 H 可以同时使用 PyMuPDF 和 Pillow,并且其中属于 N 或 V 的处理部分可以仅为附章 P 的 PDF-safe 字体预处理使用 fontTools。路线 R 可以同时使用 PyMuPDF 和 Pillow,不需要也不得为了路线 R 单独执行附章 P 的字体预处理。

路线 H 中属于 NV 的处理部分仍须逐条遵守 3.1、3.2 及 5.1、5.2、5.5 的既有 PyMuPDF、字体注册、排版和保存规则;Pillow 只能用于 3.3.2 的 I 类型局部图像补丁以及 3.3.3 的整页图像处理。fontTools 只能用于附章 P 中从固定 target_font_file 生成临时 PDF-safe 字体。

除 PyMuPDF、Pillow 和 fontTools 外不得使用其他第三方依赖。fontTools 的导入方式按附章 P 使用 from fontTools.ttLib import TTFont, TTCollection
如使用 argparse、pathlib、os、re、tempfile、math、json、unicodedata、shutil 等 Python 标准库,
无需额外安装。

N、V 目标的译文中的全部字符只能使用固定字体映射表中的 target_font_file 所对应并按附章 P 生成的本页临时 PDF-safe 字体。I 目标还可以使用本页字体资源下载区块中声明、并由外层程序准备的字体文件。不得使用 PyMuPDF 内置字体、系统字体或原 PDF 字体作为兜底。


附章 P PDF-safe 字体预处理

本附章只负责 N、V 原生 PDF 文本写入前的字体预处理。它不负责字体选择,不改变 5.1 已确定的固定字体映射,不改变任何字体的视觉用途,不改变第三章规定的文字定位、bbox、字号、颜色、旋转或排版规则,也不替代 5.5 规定的 doc.subset_fonts(fallback=True)

P.1 适用范围与执行时机

  1. 本附章适用于所有通过 N 处理器或 V 处理器写入 PDF 原生文本的位置,包括:

    • 路线 A;
    • 路线 3C;
    • 路线 3D 的 N、V 两部分;
    • 路线 H 中实际存在的 N、V 部分。
  2. 本附章不适用于:

    • I-LOCAL 中由 Pillow 写入图像像素的文字;
    • I-FULL 中由 Pillow 写入整页图像的文字;
    • 路线 R。
  3. 对每个本页实际用于 N、V 写入的不同 target_font_file,必须按照以下固定顺序处理:

text
FONT_DIR 中的原始 target_font_file 按照 P.2 的固定算法生成本页临时 PDF-safe 字体 使用临时 PDF-safe 字体执行 page.insert_font(..., set_simple=False) 使用对应字体别名执行 insert_text() / insert_textbox() 等原生文字写入 全部 N、V 写入完成 按照 5.5 执行 doc.subset_fonts(fallback=True) 按照第五章、第八章和第九章保存、检查并交付
  1. PDF-safe 字体预处理必须发生在该字体第一次传给 page.insert_font() 之前。不得先用原始 target_font_file 注册或写入文字,再对字体进行清洗。

  2. FONT_DIR 中的原始 target_font_file 是阶段 0 已确定和准备的固定字体资源。脚本只能读取它,不得覆盖、修改、删除或原地保存该字体。

  3. PDF-safe 字体必须是本页运行期间生成的临时派生文件。该临时文件仍然代表同一个固定 target_font_file 的视觉字体身份,不构成新的字体映射,也不得据此改变 5.1、5.2 中关于“同一个选定字体”的判断。

P.2 固定 PDF-safe 字体清洗算法

本节算法是基础设施规则,不是模型需要自行设计的字体处理策略。

模型不得根据页面内容、本页译文、语种、字体名称、主观判断或测试结果自行扩大、缩小或改变本算法的清洗范围;不得自己建立兼容汉字替换表;不得自行决定哪个兼容码位应删除。

所有页面必须使用同一套确定性算法。相同原始字体文件在不同页面中,即使本页实际使用的字符不同,也必须扫描和处理该原始字体自身的完整 Unicode cmap,不得只处理本页译文实际出现的字符。

生成脚本必须实现与下列代码逻辑一致的函数;可以调整局部变量名或函数组织方式,但不得改变任何判断条件、Unicode 范围、正规化形式、删除条件或执行顺序的语义:

python
from pathlib import Path import shutil import unicodedata from fontTools.ttLib import TTCollection, TTFont PDF_SAFE_CJK_COMPAT_RANGES = ( (0xF900, 0xFAFF), (0x2F800, 0x2FA1F), ) def _is_pdf_safe_cjk_compat_codepoint(codepoint: int) -> bool: return any( start <= codepoint <= end for start, end in PDF_SAFE_CJK_COMPAT_RANGES ) def _clean_one_pdf_safe_ttfont(font: TTFont) -> int: if "cmap" not in font: return 0 best_cmap = font.getBestCmap() or {} removable: dict[int, tuple[int, str]] = {} for codepoint, glyph_name in list(best_cmap.items()): if not _is_pdf_safe_cjk_compat_codepoint(codepoint): continue normalized = unicodedata.normalize( "NFC", chr(codepoint), ) if len(normalized) != 1: continue canonical_codepoint = ord(normalized) if canonical_codepoint == codepoint: continue if best_cmap.get(canonical_codepoint) != glyph_name: continue removable[codepoint] = ( canonical_codepoint, glyph_name, ) if not removable: return 0 for table in font["cmap"].tables: if not table.isUnicode(): continue cmap = getattr(table, "cmap", None) if not isinstance(cmap, dict): continue for codepoint in removable: cmap.pop(codepoint, None) return len(removable) def make_pdf_safe_font( source_path: Path, output_path: Path, ) -> None: source_path = Path(source_path) output_path = Path(output_path) if not source_path.is_file(): raise RuntimeError( f"原始字体文件不存在:{source_path}" ) output_path.parent.mkdir( parents=True, exist_ok=True, ) output_path.unlink(missing_ok=True) suffix = source_path.suffix.lower() removed_count = 0 if suffix in {".ttc", ".otc"}: collection = TTCollection(str(source_path)) try: for font in collection.fonts: removed_count += _clean_one_pdf_safe_ttfont( font ) if removed_count > 0: collection.save(str(output_path)) finally: for font in collection.fonts: try: font.close() except Exception: pass if removed_count == 0: shutil.copy2( source_path, output_path, ) elif suffix in {".ttf", ".otf"}: font = TTFont(str(source_path)) try: removed_count = _clean_one_pdf_safe_ttfont( font ) if removed_count > 0: font.save(str(output_path)) finally: try: font.close() except Exception: pass if removed_count == 0: shutil.copy2( source_path, output_path, ) else: raise RuntimeError( "PDF-safe 字体预处理不支持该字体扩展名:" f"{source_path.name}" ) if ( not output_path.is_file() or output_path.stat().st_size <= 0 ): raise RuntimeError( f"PDF-safe 临时字体生成失败:{output_path}" )

本算法的删除条件必须同时满足:

  1. 码位位于:
    • U+F900~U+FAFF,或
    • U+2F800~U+2FA1F
  2. Python Unicode 数据库通过 unicodedata.normalize("NFC", ...) 明确将该码位正规化为另一个单一 Unicode 码位;
  3. 正规化后的标准码位与原兼容码位不同;
  4. 在该字体的 getBestCmap() 中,标准码位与兼容码位确实指向同一个 glyph_name

只有同时满足以上全部条件,才允许从该字体的 Unicode cmap 子表中删除兼容码位。

不得因为某个码位位于 U+F900~U+FAFFU+2F800~U+2FA1F 就直接删除;如果它没有 NFC 单字符正规化结果,或者标准码位不存在,或者标准码位与兼容码位没有指向同一个 glyph,则必须保留。

不得把本算法扩展到其他 Unicode 范围。不得使用 NFKC、NFKD 或自定义替换表替代本节的 NFC 判断。

P.3 临时字体复用与生命周期

  1. 每个生成脚本必须为本页实际使用的每一个不同 target_font_file 最多生成一个临时 PDF-safe 字体。

  2. 相同 target_font_file 在本页多个 N、V 替换任务中必须复用同一个临时 PDF-safe 字体,不得为不同段落、不同字符、不同文字体系或不同替换位置重复生成。

  3. 不同的 target_font_file 必须分别独立生成各自的临时 PDF-safe 字体,不得把不同字体合并,不得让一种目标字体的清洗结果代替另一种字体。

  4. 临时 PDF-safe 字体应放入脚本运行期间创建的独立临时目录,例如通过 Python 标准库 tempfile.TemporaryDirectory() 管理。临时字体文件扩展名必须与原始字体保持一致。

  5. 临时 PDF-safe 字体只用于本页本次脚本执行,不得:

    • 覆盖 FONT_DIR 中的原始字体;
    • 写回 FONT_DIR
    • 作为新的永久字体资源;
    • 修改 font_mapping.json
    • 改变 target_font_file 的文件名;
    • 被其他页面任务依赖。
  6. 临时 PDF-safe 字体必须在成功和失败时清理。应优先使用 TemporaryDirectory()、上下文管理器或 try/finally 保证清理。

  7. 临时字体文件名可以根据原始 target_font_file 确定性生成,例如:

python
safe_name = ( source_font_path.stem + ".pdfsafe" + source_font_path.suffix )

临时文件名只用于当前脚本内部,不属于固定字体映射的一部分。

  1. 不得根据本页实际出现的译文字符决定清洗哪些兼容码位。PDF-safe 字体清洗必须始终对该原始字体的完整 cmap 执行 P.2 的固定算法。不同页面即使使用完全不同的文字内容,只要原始 target_font_file 相同,清洗规则就必须完全相同。

P.4 N/V 写入文本的 NFC 规范化

  1. 对所有通过 N 处理器或 V 处理器写入 PDF 原生文本的译文字符串,在进行文字尺寸测量、换行、bbox 适配、字号缩放、写入片段拆分以及实际 insert_text() / insert_textbox() 之前,必须先执行:
python
normalized_text = unicodedata.normalize( "NFC", original_text, )
  1. 实际用于排版计算和写入 PDF 的必须是 NFC 规范化后的字符串,不得先按未规范化文本测量排版、最后才替换字符串。

  2. 如果同一句译文因换行、旋转、定位或其他排版原因需要拆为多个写入片段,应先对该完整译文执行 NFC,再基于规范化后的文本进行拆分。

  3. NFC 规范化只改变 Unicode 标准明确规定的规范等价形式,不得使用 NFKC、NFKD、自定义字符替换表或其他兼容性折叠代替本规则。

  4. 本节只规定实际写入 N、V 原生 PDF 文本的译文内容。不得因为本节而修改第七章原文的匹配语义,不得自行改译文,不得改变型号、寄存器名、引脚名、数字、单位、标点或技术缩写的内容。

P.5 与固定字体映射及 PDF 子集化的关系

  1. target_font_file 仍然完全由 5.1 的固定字体映射决定。PDF-safe 预处理不创建新的映射字体,不允许模型重新选择字体,也不允许因为清洗结果改变映射键或目标字体。

  2. 本规范其他章节中凡规定 N、V 必须“使用 target_font_file”或“全部字符使用同一个 target_font_file”,其字体选择语义保持不变。

    对实际 PDF 写入而言,其执行含义统一为:

text
固定映射 target_font_file 从 FONT_DIR 读取该原始字体 按照 P.2 生成该字体的本页临时 PDF-safe 派生版本 用该临时派生版本注册对应 PDF 字体别名 所有属于该 target_font_file 的 N、V 字符都通过该别名写入
  1. PDF-safe 预处理和 doc.subset_fonts(fallback=True) 是两个不同步骤:
text
PDF-safe 字体预处理 发生在 page.insert_font() 之前 负责消除符合 P.2 条件的 CJK 兼容码位与标准码位共享同一 glyph 时的反向 Unicode 歧义 doc.subset_fonts(fallback=True) 发生在文字写入之后、每次保存之前 负责对子集字体进行文档级字体子集化

不得认为已经执行 doc.subset_fonts(fallback=True) 就可以跳过附章 P,也不得因为执行了附章 P 而跳过 5.5 的字体子集化。

  1. 本页使用多个不同 target_font_file 时,每一个字体都必须独立执行 P.2。一个字体没有可删除的兼容映射,不代表其他字体可以跳过处理。

  2. 即使同一原始字体在不同单页 PDF 中最终经 doc.subset_fonts(fallback=True) 形成不同字体子集,也不得改变 P.2 的清洗规则。页面实际使用字符不同只允许导致最终字体子集内容不同,不允许导致同一兼容码位在不同页面被采用不同处理方法。

P.6 禁止事项

对附章 P,硬性禁止以下行为:

  • 禁止让模型自行判断哪些汉字属于中国、日本、韩国字形并据此删除码位;
  • 禁止根据字体名称、语言名称、页面语种或主观视觉判断决定删除哪个 Unicode;
  • 禁止建立或硬编码诸如 器→器量→量不→不 的人工字符替换表来代替 P.2;
  • 禁止直接删除整个 U+F900~U+FAFFU+2F800~U+2FA1F 范围;
  • 禁止删除不满足 P.2 全部条件的 cmap 项;
  • 禁止把清洗范围扩展到本章未规定的其他 Unicode 范围;
  • 禁止使用 NFKC 或 NFKD 代替 NFC;
  • 禁止修改 glyph outline、字形轮廓、字宽、字距、字体名称、字重、斜体属性、OpenType 布局规则或其他与本问题无关的字体数据;
  • 禁止对原始 FONT_DIR / target_font_file 原地修改或保存;
  • 禁止把临时 PDF-safe 字体写回公共字体目录;
  • 禁止不同页面根据各自译文字符只清洗本页使用到的码位;
  • 禁止为同一个 target_font_file 在同一页生成多个不同清洗版本;
  • 禁止因为 PDF-safe 预处理失败而改选其他字体、改走其他页面路线、使用 PyMuPDF 内置 CJK 字体、系统字体或其他字体兜底;
  • PDF-safe 预处理属于写入前字体处理基础设施,本身不替代第八章和第九章规定的保存后结果验证。不得把“已经执行附章 P”当成跳过字体、文本、排版或视觉结果自检的理由。

附章 Q 全书字体使用声明协议

本附章只负责为后续整本 PDF 收集阶段声明:本页通过 N、V 处理器向 PDF 原生文本层实际写入了哪些固定 target_font_file,以及每个字体实际写入了哪些 Unicode 文本。

本附章不改变当前单页 PDF 的生成、字体预处理、字体嵌入、字体子集化、保存、自检或交付方式。当前单页 Python 处理脚本仍必须完整执行第三章、第五章、附章 P、第八章和第九章规定的全部规则。

后续收集阶段可以使用本声明对同一个 target_font_file 在整本 PDF 中实际出现的字符进行汇总和去重,并结合阶段 4 实际生成的单页 PDF 中的真实字体对象、字符编码、ToUnicode、CIDToGIDMap、FontDescriptor、FontFile 等低层事实进行整本字体资源统一。模型不得自行推测这些保存后的低层 PDF 编码事实。

Q.1 目的与适用范围

  1. 第十章新增的“全书字体使用”区块是声明性协议,不是新的页面处理路线,不参与 A、3C、3D、H、R 的判定,也不改变任何页面路线。

  2. 本声明只统计通过 N 处理器或 V 处理器实际写入 PDF 原生文本层的字符,包括:

    • 路线 A 中的 N 写入;
    • 路线 3C 中的 V 重建文字写入;
    • 路线 3D 中的 N、V 写入;
    • 路线 H 中实际存在的 N、V 写入。
  3. 以下内容不得计入本声明:

    • I-LOCAL 中由 Pillow 写入图像像素的文字;
    • I-FULL 中由 Pillow 写入整页图像像素的文字;
    • 路线 R 中的任何图像文字;
    • 原 PDF 中原本存在且本轮没有通过 N/V 处理器重新写入的文字;
    • 原文匹配字符串;
    • 仅用于测量、换行、字号计算、bbox 适配或候选排版但最终没有实际写入 PDF 的字符串;
    • 译文为空字符串、因此没有实际写入任何字符的替换任务。
  4. 本声明的字体身份始终使用固定映射中的原始 target_font_file 文件名表示。
    不得使用附章 P 生成的临时 PDF-safe 字体文件名代替 target_font_file,也不得使用下载 URL、字体内部名称、BaseFont 名称或 PDF 子集前缀作为全书字体身份。

  5. 每个实际用于 N/V 写入的 target_font_file 还必须声明本页 Python 处理脚本中对应的稳定 PDF 字体别名,即 5.1 和 5.5 所规定的 TRFONT1TRFONT2TRFONT3 等别名。该别名只用于让外层程序在真实单页输出 PDF 中定位本页由阶段 4 注册的对应字体资源,不改变 target_font_file 才是全书字体身份这一规则。

Q.2 字体使用统计口径

  1. 对每个本页实际用于 N/V 原生文本写入的不同 target_font_file,必须且只能生成一个字体使用条目。

  2. 每个条目的 text 必须来自本页 Python 处理脚本最终实际执行的 N/V 原生文本写入内容。

  3. 对所有 N/V 译文,必须先按照 P.4 对完整译文执行 NFC 规范化,再进行排版、拆分和实际写入。因此本声明中的 text 也必须使用与脚本实际写入完全相同的 NFC 规范化文本。

  4. 同一个 target_font_file 在本页用于多个替换任务时,text 必须按照 Python 处理脚本中这些 N/V 写入操作的实际执行顺序,将每一次实际写入的字符串直接串接起来。

  5. 如果同一句完整译文因换行、旋转、定位或其他排版原因拆成多个实际写入片段,则必须按照这些片段在 Python 处理脚本中的实际写入顺序,各计入一次,不得恢复成另一个自行推测的字符串,也不得遗漏任何实际写入片段。

  6. text 必须保留实际写入内容中的全部字符,包括:

    • 汉字;
    • 拉丁字母;
    • 数字;
    • 空格;
    • 西文标点;
    • 英语标点;
    • 单位;
    • 型号;
    • 寄存器名;
    • 引脚名;
    • 技术缩写;
    • 以及其他实际写入的可见字符。
  7. 不得为了缩短声明而对 text 自行去重、排序、删减、摘要、归类、改写或只保留汉字。
    同一个字符在本页实际写入多次时,必须按照实际写入结果保留多次。
    后续收集阶段会自行按 target_font_file 对整本字符进行汇总和去重。

  8. 如果某个 target_font_file 仅被 I 处理器使用,而没有通过 N/V 处理器实际写入 PDF 原生文本,则不得因为 I 使用了该字体文件而把它列入本声明。

  9. 如果本页正常处理,但没有任何 N/V 原生文本字符实际写入,例如路线 R,或全部 N/V 替换任务的译文均为空字符串,则 fonts 必须为空数组。

Q.3 固定声明格式

本页可以正常处理时,第十章“全书字体使用”区块必须且只能包含一个合法 JSON 对象。

JSON 顶层结构固定为:

json
{ "version": 1, "fonts": [ { "target_font_file": "字体文件名", "font_alias": "TRFONT1", "text": "该字体在本页实际写入的全部N/V文本" } ] }

具体规则:

  1. 顶层只能包含:

    • version
    • fonts
  2. version 必须且只能为整数 1

  3. fonts 必须是 JSON 数组。

  4. 每个字体条目只能包含:

    • target_font_file
    • font_alias
    • text
  5. target_font_file 必须与本页 Python 处理脚本实际用于对应 N/V 写入的固定映射 target_font_file 文件名完全一致。

  6. font_alias 必须与本页 Python 处理脚本实际通过 page.insert_font() 注册并在对应 N/V 写入中复用的稳定字体别名完全一致。

  7. text 必须严格按照 Q.2 生成,不得输出原文、解释、统计数量、字符列表、Unicode 码位列表或其他辅助信息。

  8. 每个不同 target_font_file 必须且只能出现一次。

  9. fonts 中各条目必须按照 target_font_file 文件名不区分大小写排序,确保相同页面输入产生稳定声明顺序。

  10. 如果没有任何 N/V 原生文本实际写入,固定输出:

json
{"version":1,"fonts":[]}
  1. 本区块不得使用 Markdown 代码围栏包裹 JSON。

Q.4 与单页字体处理及最终 PDF 的关系

  1. 本声明不得改变本页单页 Python 处理脚本的任何字体处理流程。

    N/V 仍必须按照:

text
固定 target_font_file 附章 P PDF-safe 预处理 本页临时 PDF-safe 字体 page.insert_font(..., set_simple=False) N/V 原生文本写入 doc.subset_fonts(fallback=True) 保存单页 PDF

完整执行。

  1. 不得因为输出了全书字体使用声明,就跳过:

    • 附章 P;
    • 5.5 的字体注册;
    • doc.subset_fonts(fallback=True)
    • garbage=4
    • deflate=True
    • 第八章规定的单页输出完整性检查;
    • 第九章规定的目标结果自检与闭环验收;
    • 临时文件与 os.replace() 原子交付。
  2. 本声明中的 target_font_file 用于后续收集阶段按字体文件汇总整本字符使用集合。

  3. 本声明中的 font_alias 用于外层程序在本页 Python 处理脚本实际生成的单页 PDF 中定位对应的阶段 4 N/V 字体资源。

  4. 保存后的真实单页 PDF 可能经过 doc.subset_fonts(fallback=True) 形成新的 PDF 子集字体名称、内部字符码、CID、GID、ToUnicode、CIDToGIDMap、FontDescriptor、FontFile 或其他 PDF 字体结构。
    这些保存后的低层事实必须由外层程序或第九章规定的实际输出验证逻辑从真实生成的单页 PDF 中读取,不得由模型根据脚本、原字体或经验猜测。

  5. 本声明不要求、也不得要求本页 Python 处理脚本提前知道整本 PDF 其他页面使用了哪些字符。

  6. 本声明不要求当前模型打开、读取、下载或分析整本 PDF。模型仍然只处理本轮第七章提供的真实单页 PDF。

  7. 后续收集阶段是否以及如何将同一个 target_font_file 的多个单页字体资源统一为整本共享字体资源,不属于当前单页 Python 处理脚本的职责,也不得因此修改当前页面的视觉排版或内容。

Q.5 禁止事项

对附章 Q,硬性禁止以下行为:

  • 禁止把 I 载体由 Pillow 写入图像像素的文字计入 fonts
  • 禁止把原 PDF 中本轮未重新写入的原生文字计入 fonts
  • 禁止把原文匹配字符串计入 fonts
  • 禁止把仅用于测量或候选排版、最终没有实际写入的字符串计入 fonts
  • 禁止将同一个 target_font_file 拆成多个字体使用条目;
  • 禁止对 text 自行去重、排序、摘要、删减或只保留汉字;
  • 禁止使用 PDF-safe 临时字体文件名代替原始 target_font_file
  • 禁止使用 BaseFont、字体内部名称、PDF 子集字体名称或下载 URL 代替 target_font_file
  • 禁止让 font_alias 与 Python 处理脚本实际注册并写入时使用的字体别名不一致;
  • 禁止在“全书字体使用”声明区块中输出或猜测保存后 PDF 的 xref、对象号、FontFile xref、FontDescriptor xref、CID、GID、字符码、ToUnicode、CIDToGIDMap、内容流 xref 或其他低层 PDF 编码结果;本限制只约束附章 Q 的声明内容,不限制第九章基于真实输出 PDF 在脚本内部读取这些事实用于结果自检;
  • 禁止为了全书字体使用声明改变现有 N/V 的字体、字号、bbox、颜色、旋转、对齐、换行或写入内容;
  • 禁止为了本声明改变或削弱第八章和第九章规定的检查流程;
  • 禁止让本页 Python 处理脚本为了本声明读取其他页面、打开整本 PDF 或依赖其他页面生成结果;
  • 禁止因为本声明生成困难而改变页面路线、字体映射或特殊失败出口。

第六章 失败状态与特殊出口

页面路线与执行状态是两个独立维度。页面路线只有 A、3C、3D、H、R 五种,由 4.1 的载体组合唯一决定;执行状态描述本轮是否成功完成,不得改变已判定的页面路线。

特殊出口只有以下两个:

[无法下载PDF]

  • 触发条件与含义见 1.4;
  • 它不代表任何路线选择。

[本页无可映射字体]

  • 触发条件见 5.1 规则 4 与规则 5;
  • 它是 N 或 V 类型的至少一个非空译文任务因无法按照 5.1 确定非 null 的固定映射字体而无法完成时的执行失败标记,不是页面路线,也不得触发跨路线降级;
  • 即使输出该标记,页面原始路线仍然是根据 NVI 载体集合判定的 A、3C、3D、H 或 R。

I 类型目标不得使用本特殊出口;I 所需字体应优先复用固定映射资源,必要时在第十章字体资源下载区块中声明。

如果本页至少一个 N 或 V 类型的非空译文任务未能按照 5.1 确定非 null
target_font_file,四个输出区块都必须且只能输出
[本页无可映射字体]

第八章或第九章检查失败不构成新的页面路线,也不构成新的特殊出口。只要不属于上述两个特殊出口,就必须在已经锁定的页面路线内修复明确发现的问题、重新执行脚本并重新检查,只有实际结果通过后才能按正常协议交付。


第七章 本轮输入内容

原文

上海贝岭
SHANGHAI BELLING

BL0937B 内置时钟单相插座表计量芯片

特点

  • 高精度,在输入动态工作范围 2500:12500:1 内,非线性测量误差小于 ±0.5%\pm0.5\%
  • 大信号稳定性,采样电流 300mA300\mathrm{mA} 点,CF 输出跳动小于 ±0.2%\pm0.2\%
  • 小信号稳定性,采样电流 50mA50\mathrm{mA} 点 CF 跳动小于 ±0.3%\pm0.3\%
  • 芯片给出电压和电流的有效值,电流测量范围(4mA30A4\mathrm{mA}\sim30\mathrm{A})@1mohm
  • 芯片具有防潜动设计,确保无电流时噪声功率切除。
  • 芯片上有电源电压监测电路,检测掉电状况,工作电压低于 2.6V2.6\mathrm{V} 时,芯片进入复位状态
  • 芯片内置 1.1V1.1\mathrm{V} 参考电压源(典型值)
  • 芯片内置振荡电路,无需外接晶振
  • 芯片单工作电源 3.3V3.3\mathrm{V},低功耗 8mW8\mathrm{mW}(典型值)
  • SOP8 封装

概述

BL0937B 是一颗宽量程单相多功能电能计量芯片,适用于单相插座表、单相插排、智能家电控制电路等应用,具有较高的性价比。

BL0937B 集成了 2 路高精度 Sigma-Delta ADC, 参考电压,电源管理等模拟电路模块,以及处理有功功率、电流电压有效值等电参数的数字信号处理电路。提供高频 CF1 用于指示电流/电压有效值,高频 CF 用于电能计量。

BL0937B 能够测量单相有功能量、有功功率、电流电压有效值等参数;能够充分满足插座表、单相插排、智能家电等领域的需要。

BL0937B 具有专利防潜动设计,配合合理的外部硬件设计,

1 管脚与系统框图

VDD 1         8 SEL
IP  2         7 CF1
IN  3   BL0937B   6 CF
VP  4         5 GND

SOP8

VDD

Internal Clock

Power On/Reset

IP
IN
PGA
ADC

VP
PGA
ADC

DSP

RMS

WATT

Reference
Voltage

Voltage to
Frequency
Converter

SEL
CF1
CF

BL0937B

GND

上海贝岭股份有限公司
上海市宜山路 810 号 021-24261000
www.belling.com.cn

2 / 8

译文

上海贝岭
SHANGHAI BELLING

BL0937B Single-Phase Socket Metering IC with Built-in Clock

Features

  • High accuracy: nonlinearity measurement error is less than ±0.5%\pm0.5\% over an input dynamic range of 2500:12500:1
  • Large-signal stability: at a sampling current of 300mA300\mathrm{mA}, CF output fluctuation is less than ±0.2%\pm0.2\%
  • Small-signal stability: at a sampling current of 50mA50\mathrm{mA}, CF output fluctuation is less than ±0.3%\pm0.3\%
  • Provides RMS voltage and current measurements, with a current measurement range of (4mA30A4\mathrm{mA}\sim30\mathrm{A}) @1mohm
  • Anti-creep design ensures that noise-induced power is suppressed when no current is present.
  • On-chip supply voltage monitoring circuit detects power-down conditions; when the operating voltage falls below 2.6V2.6\mathrm{V}, the IC enters the reset state
  • Built-in 1.1V1.1\mathrm{V} voltage reference (typical)
  • Built-in oscillator circuit; no external crystal is required
  • Single 3.3V3.3\mathrm{V} supply with low power consumption of 8mW8\mathrm{mW} (typical)
  • SOP8 package

Overview

BL0937B is a wide-range, multifunction single-phase energy metering IC suitable for applications such as single-phase socket meters, single-phase power strips, and smart appliance control circuits, offering excellent cost performance.

BL0937B integrates two high-precision Sigma-Delta ADCs, a voltage reference, power management and other analog circuit blocks, as well as digital signal processing circuitry for electrical parameters such as active power and RMS current and voltage. It provides a high-frequency CF1 output for indicating RMS current/voltage and a high-frequency CF output for energy metering.

BL0937B can measure parameters including single-phase active energy, active power, and RMS current and voltage, fully meeting the requirements of socket meters, single-phase power strips, smart appliances, and similar applications.

BL0937B features a patented anti-creep design which, when combined with proper external hardware design,

1 Pin Configuration and System Block Diagram

VDD 1         8 SEL
IP  2         7 CF1
IN  3   BL0937B   6 CF
VP  4         5 GND

SOP8

VDD

Internal Clock

Power On/Reset

IP
IN
PGA
ADC

VP
PGA
ADC

DSP

RMS

WATT

Reference
Voltage

Voltage to
Frequency
Converter

SEL
CF1
CF

BL0937B

GND

Shanghai Belling Co., Ltd.
810 Yishan Road, Shanghai 021-24261000
www.belling.com.cn

2 / 8

单页 PDF 链接

https://raw.githubusercontent.com/a2848273598/pdf-transcribe-images/main/BL0937B_V1_0_cn_e7705da55898_pdf_pages/2.pdf

第八章 基础输入与输出完整性检查

第八章负责输入合法性和候选输出 PDF 的基础结构完整性检查。第九章负责目标内容是否正确实施、排版和视觉结果是否正确、字体是否正确嵌入与子集化、非目标内容是否被误伤,以及检查失败后的闭环修复与重新执行。

本章和第九章共同构成本规范的完整自检与验收体系。

必须在本轮实际提供的单页 PDF 上运行脚本。

8.1 输入检查

  • --input 必须是绝对路径;
  • 文件必须存在;
  • 必须能被 PyMuPDF 正常打开;
  • 必须恰好包含一页;
  • 必须能够读取页面尺寸和旋转。

任何一项不符,立即 raise RuntimeError 终止。

8.2 全路线公共输出完整性检查

保存临时结果后,必须执行以下公共输出完整性检查:

  • 临时输出文件必须存在且非空;
  • 临时输出 PDF 必须能被 PyMuPDF 正常打开;
  • 临时输出 PDF 必须恰好包含一页;
  • 输出页面尺寸必须与输入页面一致;
  • 输出页面旋转必须与输入页面一致。

通过本节只能证明候选输出 PDF 在基础结构上有效,不能替代第九章的内容、字体、排版、视觉和目标级结果检查。

只有 8.2 和第九章所有适用于当前路线的检查全部通过后,才能使用 os.replace() 原子替换为正式输出文件;验证失败时删除对应失败候选临时文件。

8.3 N 处理器基础检查

N 处理器完成写入并保存临时结果后,执行 8.2 的全路线公共输出完整性检查。

N 目标的原文是否正确删除、译文是否实际写入、是否可搜索可复制、译文 bbox、字体嵌入与子集化、渲染效果和非目标对象保护,继续按照第九章中适用于 N 的规则进行验证。

8.4 V 处理器基础检查

V 处理器完成写入并保存临时结果后,执行 8.2 的全路线公共输出完整性检查。

V 目标的原中文矢量轮廓是否正确删除、译文是否成功重建为原生文本、译文 bbox、字体嵌入与子集化、渲染效果和非目标矢量保护,继续按照第九章中适用于 V 的规则进行验证。

8.5 I-LOCAL 处理器基础检查

I-LOCAL 处理器完成写入并保存临时结果后,执行 8.2 的全路线公共输出完整性检查。

I-LOCAL 的原中文残留、译文视觉效果、补丁边缘、补丁外内容保护、线条和图形是否被误伤等,继续按照第九章中适用于 I-LOCAL 的规则进行验证。

8.6 I-FULL 处理器基础检查

I-FULL 处理器在将编辑后的整页图像封装为临时输出 PDF 前,必须逐像素验证所有硬编码允许编辑区域之外的像素与本次渲染输入图一致;如有变化,立即 raise RuntimeError

该检查比较的是本次渲染输入图和编辑完成、尚未封装为 PDF 的整页图像,用于确保 Pillow 编辑过程没有越出允许区域。

完成 PDF 封装后执行 8.2 的全路线公共输出完整性检查。允许输出 PDF 只有一张显示位图和零可提取文本;不得因没有原生文本层而失败。

I-FULL 封装后的实际视觉结果、指定替换目标是否完整实施、原中文是否仍可见、译文是否清晰以及重新渲染后的整体页面是否正常,继续按照第九章进行验证。

8.7 页面路线基础检查组合

每条页面路线执行以下基础检查:

text
路线 A -> 8.1 输入检查 + 8.2 公共检查 + 8.3 N 处理器基础检查 路线 3C -> 8.1 输入检查 + 8.2 公共检查 + 8.4 V 处理器基础检查 路线 3D -> 8.1 输入检查 + 8.2 公共检查 + 8.3 N 处理器基础检查 + 8.4 V 处理器基础检查 路线 H -> 8.1 输入检查 + 8.2 公共检查 + 本页实际包含的 8.3、8.4、8.5 路线 R -> 8.1 输入检查 + 8.2 公共检查 + 8.6 I-FULL 处理器基础检查

子集化或结果验证错误,然后重新执行。

如果脚本能够正常运行但第九章任何一项适用于当前路线的结果检查未通过,也必须视为候选结果失败。应根据真实失败现象修改对应的硬编码参数、定位逻辑、删除范围、bbox、ROI、背景修补、字号、换行、对齐、旋转、颜色、字体注册或其他当前路线内部实现,再重新执行和重新检查。

检查失败不得触发跨路线降级,不得修改第七章已经给出的译文,不得通过减少替换目标来让检查通过。


第九章 目标结果自检、闭环重试与交付验收

本章恢复并强化结果导向的闭环自检。

本章的目的不是重新审校译文,也不是要求寻找第七章之外的其他中文,而是确认:第七章已经明确给出的每一个替换任务,是否确实在真实输出 PDF 中按照本规范被正确实施。

只有实际运行生成脚本、生成真实候选 PDF、重新打开并检查该候选 PDF,同时完成适用于当前路线的确定性检查和视觉检查后,才能认为任务完成。

9.1 核心原则:不审校内容,但必须验证实施结果

必须严格区分:

text
内容审校: “译文应该翻成什么?” “页面上另一个未列出的中文是否也应翻译?” “是否应该自行改写审校译文?” → 不属于本任务,不得自行决定。 执行结果验证: “第七章给出的原文是否真的被正确替换?” “第七章给出的译文是否真的完整写入?” “是否越界、乱码、残影、漏字、错误字体、错误对象?” “是否误伤非目标内容?” → 属于本任务,必须检查。

“不自行充当审校器”绝不等于“不验证执行结果”。

第七章的译文内容本身不可由模型自行修改,但必须验证该译文是否完整、正确地落实到了真实输出 PDF。

9.2 自检范围限定与两层验证

自检范围只围绕第七章给出的替换任务以及完成这些替换所直接影响的页面对象展开。

不得因为结果自检而自行增加第七章之外的新翻译任务。

例如:

  • 如果页面上另有一个中文标签没有出现在第七章原文中,它不得因此被加入翻译任务;
  • 如果第七章明确要求把 Master Drives NACK 替换为 主机驱动 NACK,则必须检查原来的 MasterDrives 已经不再残留,而译文明确保留的 NACK 仍然正确存在;
  • 不得使用“页面中仍有任何中文字母”作为失败条件;
  • 不得因为审校译文本身保留型号、寄存器名、引脚名、单位或技术缩写而把这些字符误判为漏译。

结果自检分为两层:

第一层:脚本内确定性验证

凡是能够通过 PyMuPDF、Pillow、fontTools 或 Python 标准库可靠判断的事项,必须尽可能在生成的 Python 脚本中直接验证,包括但不限于:

  • PDF 是否能重新打开;
  • 页数、页面尺寸、旋转;
  • N/V 译文是否能从目标区域提取;
  • N/V 写入文本 bbox 是否位于规定区域;
  • 本次目标字体是否实际存在于输出 PDF;
  • 字体是否完成子集化;
  • 字体资源是否异常重复或异常巨大;
  • I-FULL 允许编辑区域外像素是否未被修改;
  • 其他可以确定性判断的当前目标结果。

确定性验证失败必须 raise RuntimeError,不得继续把失败候选交付为正式文件。

第二层:真实输出渲染视觉验证

生成并运行脚本后,你必须重新打开实际候选输出 PDF,并至少以 200 DPI 渲染;涉及细小文字、图表标签、I-LOCAL、I-FULL 或复杂矢量目标时,优先使用 300~400 DPI。

必须亲自检查:

  • 第七章每个目标位置的原文是否已被正确移除;
  • 第七章每个非空译文是否完整可见;
  • 是否存在漏字、乱码、文字裁切、重叠、越界;
  • 是否存在中文残影;
  • 是否存在英语直接压在原中文之上的情况;
  • 字体视觉大小、颜色、字重和对齐是否协调;
  • 是否错误遮住边框、表格线、波形、箭头或其他图形;
  • 是否出现明显白块、补丁边缘、异常背景修补;
  • 页面是否正常渲染。

视觉检查必须基于真实执行后的候选输出,而不是只根据脚本代码推测“应该正确”。

9.3 N 处理器目标结果自检

对第七章中每一个属于 N 的替换目标,保存候选 PDF 后必须逐项验证:

  1. 重新打开候选输出 PDF。

  2. 在该目标原始 bbox 对应区域内重新提取文本。

  3. 若译文为非空字符串:

    • 实际写入的 NFC 规范化译文必须能够从输出 PDF 中提取;
    • 如果因换行、定位或旋转被拆成多个写入片段,必须能够按照脚本实际写入顺序从对应目标区域重建出全部写入内容;
    • 不得漏掉译文中的汉字、拉丁字母、数字、标点、单位、型号、寄存器名、引脚名、技术缩写或其他字符;
    • 译文必须可搜索、可复制。
  4. 原始 N 文本对象必须已经按既定替换任务被删除。

    • 如果译文与原文不同,原来的完整原文不得仍作为旧对象残留于该目标区域;
    • 如果译文明确保留了原文中的某些技术缩写、型号、数字或字符,则这些被审校译文明确保留的内容允许继续出现;
    • 不得因为保留字符仍存在而误判失败;
    • 检查对象是原始待替换内容是否被正确替换,而不是扫描整页寻找中文。
  5. 实际写入译文的 bbox 必须严格位于该目标原始 bbox 内。
    如仍超出,必须缩小整体字号、重新排版并重新生成。

  6. 如果译文拆为多个写入片段:

    • 所有片段必须使用同一个对应 target_font_file
    • 必须使用统一整体缩放比例;
    • 基线、颜色、行高、视觉字高必须协调;
    • 不得发生片段互相覆盖或顺序错误。
  7. 以至少 200 DPI、复杂或细小目标优先 300~400 DPI 渲染对应区域,检查:

    • 原文没有可见残影;
    • 译文完整可见;
    • 没有乱码、缺字或方框;
    • 没有超出 bbox;
    • 没有覆盖相邻图形或文字;
    • 视觉结果与原页面层级协调。

任一 N 目标未通过以上检查,当前候选结果即失败,必须修改并重新执行。

9.4 V 处理器目标结果自检

对第七章中每一个属于 V 的替换目标,保存候选 PDF 后必须逐项验证:

  1. 原中文矢量轮廓必须实际被删除或可靠遮蔽,不得继续与译文重叠存在。

  2. 如果脚本采用完整 q ... Q 绘制分组删除:

    • 必须确认实际删除的是该目标对应的完整目标分组;
    • 不得留下同一目标的部分轮廓;
    • 不得误删同一内容流中属于非目标对象的其他绘图分组。
  3. 如果脚本采用最小遮蔽 bbox:

    • 遮蔽范围必须足以消除原中文轮廓;
    • 不得明显遮住目标文字之外的线条、边框、箭头、波形或其他图形;
    • 遮蔽背景必须与周围视觉背景协调。
  4. 非空译文必须作为原生 PDF 文本实际存在,并且:

    • 可见;
    • 可搜索;
    • 可复制;
    • 能从目标区域提取;
    • 使用指定的同一个 target_font_file
    • 实际 bbox 不得超出该 V 目标规定 bbox。
  5. 以至少 200 DPI、复杂图形优先 300~400 DPI 渲染目标区域,检查:

    • 原中文轮廓已经真正不可见;
    • 没有残留的中文笔画;
    • 没有英语叠在旧轮廓上;
    • 新译文完整清晰;
    • 没有误伤附近矢量线条或图形;
    • 对齐、旋转、颜色、字号和视觉粗细合理。
  6. 如果译文包含审校明确保留的中文缩写、型号、数字或单位,这些字符不得被当作原轮廓残留。

任一 V 目标未通过以上检查,当前候选结果即失败,必须修改并重新执行。

9.5 I-LOCAL 处理器目标结果自检

对第七章中每一个属于 I 且由 I-LOCAL 处理的目标,保存候选 PDF 后必须逐项验证:

  1. 必须重新渲染候选输出 PDF 的目标 ROI,优先使用与编辑阶段一致或更高的 DPI。

  2. 原中文像素必须已经被真实擦除:

    • 不得仍有完整中文;
    • 不得留下明显残影;
    • 不得仅用英语直接覆盖在中文之上。
  3. 背景修补必须自然:

    • 纯色区域不得出现与背景明显不同的白块、色块;
    • 渐变、纹理、波形、线条或图形背景不得出现明显断裂;
    • ROI 边缘不得出现明显补丁接缝。
  4. 译文必须:

    • 完整;
    • 清晰可辨;
    • 不漏字;
    • 不被裁切;
    • 不跨越视觉容器;
    • 不压住边框、表格线、波形、箭头或其他图形;
    • 不与相邻文字重叠。
  5. 字体、字号、颜色、字重、对齐和视觉层级必须与原图协调。

  6. 对同一视觉层级的多个 I 目标,应检查其基准字号、字体和视觉风格是否保持一致;确因容器放不下而单独缩小的目标必须只做必要的最小幅度缩小。

  7. 补丁范围以外的页面内容必须继续按照 9.9 验证未被误伤。

  8. 不得因为目标 ROI 之外仍有第七章未列出的中文而判定失败。

任一 I-LOCAL 目标未通过以上检查,当前候选结果即失败,必须修改并重新执行。

9.6 I-FULL 处理器目标结果自检

路线 R 中,对第七章中每一个 I 目标必须逐项验证:

  1. 在 PDF 封装前,必须完成 8.6 规定的允许编辑区域外逐像素一致性检查。

  2. 封装候选 PDF 后,必须重新打开候选 PDF 并重新渲染真实输出页面。

  3. 对每个硬编码目标区域进行视觉检查:

    • 原中文已经不可见;
    • 没有中文残影;
    • 没有英语直接覆盖中文;
    • 译文完整、清晰;
    • 没有漏字、裁切或乱码;
    • 没有越出视觉容器;
    • 没有压住边框、线条、波形、箭头或相邻文字;
    • 背景修补自然;
    • 字体、字号、字重、颜色、对齐与原图视觉层级协调。
  4. 必须检查整页重新渲染结果没有明显的缩放、拉伸、旋转、裁切、模糊异常或页面边缘错误。

  5. 输出 PDF 可以没有任何可提取文本,本路线不得因为零文本层而失败。

  6. 路线 R 不执行 N/V 的 PDF 字体检查。

  7. 不得因为目标区域之外存在第七章未列出的中文而把它加入翻译范围或判定失败。

任一 I-FULL 目标未通过以上检查,当前候选结果即失败,必须修改并重新执行。

9.7 全路线公共视觉与排版检查

除各处理器自己的目标检查外,所有路线都必须对实际候选输出执行公共视觉检查。

至少确认:

  • 页面尺寸不变;
  • 页面方向和旋转不变;
  • 页面可以正常渲染;
  • 没有乱码、缺字方框或明显错误字形;
  • 第七章全部替换任务均已完成,不得漏掉任何已给定目标;
  • 所有非空译文均完整可见;
  • 空间不足时只能通过合理换行和整体缩放解决,不得删减或改写译文;
  • 不得出现译文被裁切;
  • 不得出现不同写入片段互相覆盖;
  • 不得出现译文与相邻文字重叠;
  • 不得出现译文压住不应被覆盖的表格线、框线、箭头、波形或其他图形;
  • 颜色、字号、字重、旋转和对齐不得出现明显异常;
  • 同一视觉层级应保持视觉一致;
  • 页面整体不得因为处理而出现明显损坏。

对于路线 A、3C、3D 和 H 中的 N/V 目标,还必须确认原生译文能够正确显示,而不是只在文本提取结果中存在但视觉不可见。

任何公共视觉检查未通过,都必须修改并重新执行。

9.8 PDF 字体嵌入与子集化结果检查

本节适用于路线 A、3C、3D,以及路线 H 中实际存在 N 或 V 原生文本写入的部分。

调用 doc.subset_fonts(fallback=True) 没有抛出异常,不代表字体子集化已经成功。

保存候选临时 PDF 后,必须重新打开真实候选 PDF,并检查本次脚本新嵌入的全部目标字体。

  1. 每个本次实际用于 N/V 写入的不同 target_font_file 必须在脚本中只注册一次,并复用对应稳定字体别名。

  2. 每个本次新嵌入并完成子集化的目标字体,其保存后实际 PDF BaseFont 名称应包含六位大写字母子集前缀,例如:

text
ABCDEF+目标字体名称

应使用实际输出 PDF 中读取到的字体对象进行判断,不得只根据脚本调用 subset_fonts() 成功与否推测。

如果本次脚本新嵌入的任意目标字体在实际候选 PDF 中未表现为应有的子集字体,必须视为字体子集化验证失败并抛出 RuntimeError,不得交付该候选。

  1. 必须检查同一个目标字体没有出现多个不必要的新嵌入 xref。
    相同 target_font_file 应复用同一个已注册字体资源,不得因多个替换目标反复嵌入完整字体。

  2. 必须检查本次新嵌入字体的实际字体流大小。

对单页 PDF,本次脚本新嵌入的任意一个目标字体流如果超过 2 MiB,
应视为疑似嵌入完整字体并抛出 RuntimeError
除非本页确实使用了足以合理产生该大小的大量不同字形,并且能够从本页实际写入字符数量和真实字体资源结构明确证明该大小合理。

  1. 对路线 A、3C、3D,如果输出文件比输入文件增加超过 5 MiB,
    应视为异常并检查是否错误嵌入了完整字体、重复字体或异常资源。
    如果无法从本页实际处理内容明确证明该增长合理,必须抛出 RuntimeError

发生该错误时,诊断信息应至少能够列出:

  • 输入文件大小;
  • 输出文件大小;
  • 每个本次脚本新嵌入的目标字体名称;
  • 字体 xref;
  • 提取后的字体数据大小。

路线 H 含 I-LOCAL 图像补丁,文件体积增长可能来自局部补丁图像,因此不得仅因整个输出文件增加超过 5 MiB 就直接认定字体失败;但路线 H 中的每一个 N/V 目标字体仍必须执行本节关于子集字体名称、重复 xref 和字体流大小的检查。

路线 R 不执行本节。

  1. 不得仅依赖控制台是否出现 MuPDF 警告判断字体处理成功。
    必须依据重新打开的真实候选输出 PDF 中的实际字体资源验证最终结果。

  2. 本节检查失败时,不得通过改用其他字体、改走其他页面路线或跳过子集化解决;必须修复当前路线的字体注册、PDF-safe 预处理、写入或子集化实现并重新执行。

9.9 非目标内容保护检查

结果正确不仅意味着目标被替换,也意味着不应修改的内容没有被误伤。

检查范围根据路线分别确定。

路线 A

  • 只允许修改第七章指定 N 目标对应的原生文字对象及为写入译文新增的字体资源;
  • 原页面中的图片、普通矢量图形、表格线、电路图、照片、Logo、链接及其他非目标内容不得被删除或栅格化;
  • 必须通过重新打开和渲染候选输出确认页面没有因为文字替换而破坏其他可见内容。

路线 3C

  • 只允许删除或遮蔽第七章指定 V 目标对应的矢量文字轮廓;
  • 其他非目标矢量图形、边框、线条、箭头、波形、图片和原生文字不得被误删;
  • 对目标周围存在复杂矢量图形时,应重点比较输入与输出对应 ROI。

路线 3D

  • N 部分遵守路线 A 的非目标保护要求;
  • V 部分遵守路线 3C 的非目标保护要求;
  • 不得因为同页同时存在两类目标而误删两类目标之外的对象。

路线 H

  • N、V 部分分别遵守其对应非目标保护要求;
  • I-LOCAL 补丁之外的原生文本、矢量、图片、链接和其他页面对象必须保留;
  • 应比较补丁边界附近的输入与输出渲染结果,确认没有明显越界修改;
  • 允许补丁矩形内部为了 I 目标而产生必要图像覆盖,但不得扩大到未授权区域。

路线 R

  • 页面整体栅格化属于本路线预期行为,不执行原生对象保留检查;
  • 必须执行 8.6 的允许编辑区域外原始工作图像逐像素一致性检查;
  • 封装后还必须按 9.6、9.7 检查整页实际视觉结果正常。

非目标内容保护检查的目的仅是发现本次替换是否误伤页面原有内容,不得借此把第七章之外的中文转化为新的翻译任务。

9.10 闭环重试与最终交付条件

必须形成以下闭环:

text
下载并打开真实 PDF 分析真实对象结构 确定 N / V / I 锁定 A / 3C / 3D / H / R 生成页级专用脚本 实际运行脚本 生成候选临时 PDF 执行第八章基础检查 执行第九章脚本内确定性检查 重新打开真实候选 PDF 重新渲染并执行目标级视觉检查 全部通过? ┌───────┴───────┐ 否 是 ↓ ↓ 定位真实失败原因 os.replace() 修改当前路线实现 ↓ 重新运行 正式输出 └──────→重新检查

如果任何适用检查失败:

  1. 不得交付当前候选结果;
  2. 必须根据实际失败现象定位原因;
  3. 必须修改当前路线内部的实现;
  4. 必须重新执行脚本;
  5. 必须重新生成候选 PDF;
  6. 必须重新执行全部受修改影响的第八章和第九章检查;
  7. 只有实际生成结果检查通过后才能回复。

允许修复的内容包括但不限于:

  • Python 代码错误;
  • 文本定位;
  • 原文删除范围;
  • V 绘制分组定位;
  • 遮蔽 bbox;
  • 字号;
  • 整体缩放比例;
  • 换行;
  • 写入片段;
  • 基线;
  • 对齐;
  • 旋转;
  • 颜色;
  • ROI;
  • 背景采样;
  • 背景修补;
  • 图像补丁边界;
  • 字体注册;
  • PDF-safe 临时字体处理;
  • 字体子集化;
  • 临时文件保存和原子替换。

不得通过以下方式让检查“通过”:

  • 修改第七章给定译文;
  • 删除本来应实施的替换任务;
  • 把 N/V 目标改成图像覆盖;
  • 把 A、3C、3D、H 降级为 R;
  • 为图片文字伪造 OCR;
  • 使用未授权字体;
  • 扩大 bbox 或视觉容器;
  • 忽略实际发现的错误;
  • 只因为代码没有抛异常就宣称成功。

脚本正常完成后仍按 5.5 只打印正式输出路径和输出文件大小。内部验证信息无需在成功时全部打印,但验证必须实际执行。

9.11 页面路线结果自检组合

每条页面路线必须执行以下结果自检:

text
路线 A -> 9.2 两层验证 + 9.3 N 目标结果自检 + 9.7 公共视觉与排版检查 + 9.8 PDF 字体嵌入与子集化结果检查 + 9.9 路线 A 非目标内容保护检查 + 9.10 闭环重试 路线 3C -> 9.2 两层验证 + 9.4 V 目标结果自检 + 9.7 公共视觉与排版检查 + 9.8 PDF 字体嵌入与子集化结果检查 + 9.9 路线 3C 非目标内容保护检查 + 9.10 闭环重试 路线 3D -> 9.2 两层验证 + 9.3 N 目标结果自检 + 9.4 V 目标结果自检 + 9.7 公共视觉与排版检查 + 9.8 PDF 字体嵌入与子集化结果检查 + 9.9 路线 3D 非目标内容保护检查 + 9.10 闭环重试 路线 H -> 9.2 两层验证 + 本页实际存在的 9.3 N 目标结果自检 + 本页实际存在的 9.4 V 目标结果自检 + 9.5 I-LOCAL 目标结果自检 + 9.7 公共视觉与排版检查 + 本页存在 N/V 时执行 9.8 PDF 字体嵌入与子集化结果检查 + 9.9 路线 H 非目标内容保护检查 + 9.10 闭环重试 路线 R -> 9.2 两层验证 + 9.6 I-FULL 目标结果自检 + 9.7 公共视觉与排版检查 + 9.9 路线 R 非目标内容保护检查 + 9.10 闭环重试

第八章和本章所有适用于当前路线的检查均通过,才构成“处理完成”。


第十章 唯一输出协议

处理完成后,无论你选择路线 A、3C、3D、H 还是 R,都只输出以下四个区块。

四个区块之外不得出现任何解释性文字、Markdown 标题、说明、总结或致谢。

每个 START 标记和每个 END 标记必须且只能出现一次。

四个区块必须按顺序紧邻输出,区块之间不得插入任何内容。

区块内容不得使用 Markdown 代码围栏(python)包裹。

如果本页所有 N、V 类型的非空译文任务均已按照 5.1 确定非 nulltarget_font_file,且所有 I 类型的非空译文任务均已选择固定映射资源或字体资源下载区块中的字体,并且真实执行结果已经通过第八章和第九章全部适用检查,则必须按正常协议输出字体资源下载命令、完整 Python 脚本、全书字体使用声明和固定执行命令行。

不得只生成脚本但不实际执行和检查后直接交付。

如果无法下载、打开并按 4.2 完成真实单页 PDF 的检查,四个区块都必须且只能输出
[无法下载PDF]

如果本页至少一个 N 或 V 类型的非空译文任务未能按照 5.1 确定非 nulltarget_font_file,四个输出区块都必须且只能输出
[本页无可映射字体]

不得在特殊标记前后添加任何其他文字。

区块一:字体资源下载命令

[字体资源下载 START]
(本页可以正常处理时,此处只能包含下载字体的 curl.exe 命令;每条命令单独一行,不得使用代码围栏、解释、注释或其他命令)
(命令格式固定为:curl.exe -L "字体 HTTPS 直链" -o "resources\fonts\字体文件名"
(只有 I 类型目标需要固定映射表之外的字体时才输出命令;路线 A、3C、3D 必须为空;路线 H、R 如完全复用固定映射资源也必须为空)
(I 类型目标需要补充字体时,必须在此区块输出对应下载命令;不得因固定映射缺失或不适合而输出 [本页无可映射字体]
(外层程序会在公共资源目录执行这些命令,再执行 Python 脚本;Python 脚本不得自行下载字体)
(无法下载、打开并检查真实单页 PDF 时,此处必须且只能放置:[无法下载PDF])
(N 或 V 类型存在无可映射字体时,此处必须且只能放置:[本页无可映射字体])
[字体资源下载 END]

区块二:Python 处理脚本

[python处理脚本 START]
(本页可以正常处理时,此处直接放置完整 Python 源代码,不得使用代码围栏)
(无法下载、打开并检查真实单页 PDF 时,此处必须且只能放置:[无法下载PDF])
(N 或 V 类型存在非空译文任务但无法按照 5.1 确定非 null 目标字体时,此处必须且只能放置:[本页无可映射字体])
[python处理脚本 END]

本页需要处理并输出 Python 源代码时,脚本必须满足 5.4 的全部通用约束、5.5 的全部字体嵌入与子集化强制规则、5.6 的依赖限制、附章 P 的全部 PDF-safe 字体预处理规则,以及所选路线在第三章、第四章、第八章和第九章对应的全部处理、自检和闭环重试规则。

区块三:全书字体使用声明

[全书字体使用 START]
(本页可以正常处理时,此处必须且只能包含一个符合附章 Q 的合法 JSON 对象,不得使用代码围栏、解释、注释或其他文字)
(JSON 顶层格式固定为:{"version":1,"fonts":[...]}
fonts 只统计本页通过 N 处理器或 V 处理器实际写入 PDF 原生文本的字体;I-LOCAL 和 I-FULL 中由 Pillow 写入图像像素的字体不得列入)
(每个实际用于 N/V 写入的不同 target_font_file 必须且只能出现一次,条目格式固定为:{"target_font_file":"字体文件名","font_alias":"TRFONTn","text":"该字体在本页实际写入的全部N/V文本"}
target_font_file 必须是 5.1 固定映射中本页实际使用的原始目标字体文件名,不得写附章 P 生成的 PDF-safe 临时字体文件名)
font_alias 必须与本页 Python 处理脚本中实际通过 page.insert_font() 注册并在对应 N/V 写入中复用的稳定 PDF 字体别名完全一致)
text 必须按照附章 Q 的统计口径,由该 target_font_file 在本页所有 N/V 写入操作中实际写入的 NFC 规范化字符串按脚本实际写入顺序直接串接得到;必须保留重复字符、空格、拉丁字母、数字、标点、单位、型号、寄存器名、引脚名和技术缩写,不得自行去重、排序、删减或改写)
(收集阶段会按 target_font_file 对整本字符自行汇总和去重)
(如果本页正常处理但没有任何 N/V 原生文本实际写入,固定输出:{"version":1,"fonts":[]}
(本区块不得输出或猜测保存后 PDF 的 xref、CID、GID、字符码、ToUnicode、CIDToGIDMap、FontDescriptor、FontFile xref 或内容流对象号;这些低层事实由外层程序在单页 PDF 实际生成后从真实输出 PDF 读取,也可由第九章规定的脚本内部验证逻辑读取并用于自检,但不得写入本声明)
(本区块不改变当前单页 Python 处理脚本的字体预处理、字体注册、doc.subset_fonts(fallback=True)、保存、自检或交付规则,也不得因此让脚本跳过附章 P、5.5、第八章或第九章)
(无法下载、打开并检查真实单页 PDF 时,此处必须且只能放置:[无法下载PDF])
(N 或 V 类型存在无可映射字体时,此处必须且只能放置:[本页无可映射字体])
[全书字体使用 END]

区块四:Python 执行命令行

[python执行命令行 START]
(本页可以正常处理时,只输出下面这一行)
python "{脚本绝对路径占位符}" --input "{输入PDF绝对路径占位符}" --output "{输出PDF绝对路径占位符}"
(无法下载、打开并检查真实单页 PDF 时,此处必须且只能放置:[无法下载PDF])
(N 或 V 类型存在无可映射字体时,此处必须且只能放置:[本页无可映射字体])
[python执行命令行 END]

本页可以正常处理时,此区块必须且只能包含上面的 Python 命令行一行,其中:

  • {脚本绝对路径占位符} 是保存对应路线 Python 脚本的绝对路径;
  • {输入PDF绝对路径占位符} 是待处理单页 PDF 的绝对路径;
  • {输出PDF绝对路径占位符} 是翻译后 PDF 的输出绝对路径;
  • 这三个占位符保留原样输出,不要替换为真实路径。

如果本页至少一个 N 或 V 类型的非空译文任务未能按照 5.1 确定非 null
target_font_file,此区块必须且只能包含
[本页无可映射字体]

如果无法下载、打开并检查真实单页 PDF,此区块必须且只能包含
[无法下载PDF]

思考

Reviewed translation script and compared rendered PDF outputs

Worked for 10m 34s

回答

[字体资源下载 START] [字体资源下载 END] [python处理脚本 START]

apply_translation.py

import argparse import math import os import re import shutil import tempfile import unicodedata from pathlib import Path

import pymupdf as fitz from fontTools.ttLib import TTFont, TTCollection

SCRIPT_DIR = Path(file).resolve().parent SHARED_RESOURCE_DIR = ( SCRIPT_DIR.parent.parent / "_图像处理公共资源" ) FONT_DIR = SHARED_RESOURCE_DIR / "resources" / "fonts"

PDF_SAFE_CJK_COMPAT_RANGES = ( (0xF900, 0xFAFF), (0x2F800, 0x2FA1F), )

FONT_ALIASES = { "KleeOne-Regular.ttf": "TRFONT1", "Tinos-Regular.ttf": "TRFONT2", }

def _R(source_text, source_markers, text, mapping_key, target_font_file, bbox, fontsize, color=(0.0, 0.0, 0.0), lineheight=1.08, align=0, rotate=0, weight="regular"): return { "source_text": source_text, "source_markers": list(source_markers), "text": text, "mapping_key": mapping_key, "target_font_file": target_font_file, "font_alias": FONT_ALIASES[target_font_file], "bbox": tuple(bbox), "delete_rect": tuple(bbox), "fontsize": float(fontsize), "color": tuple(color), "lineheight": float(lineheight), "align": int(align), "rotate": int(rotate), "weight": weight, }

Page-specific, hard-coded N targets from the inspected one-page PDF.

REPLACEMENTS = [ _R( "内置时钟单相插座表计量芯片", ("内置时钟单相插座表计量芯片",), "Single-Phase Socket Metering IC with Built-in Clock", "ËÎÌå", "Tinos-Regular.ttf", (351.20, 58.45, 508.96, 70.45), 12.0, (0.4, 0.4, 0.6), 1.0, ), _R( "特点", ("特点",), "Features", "ËÎÌå", "Tinos-Regular.ttf", (89.90, 78.55, 111.02, 89.11), 10.56, lineheight=1.0, ), _R( "高精度,在输入动态工作范围2500:1内,非线性测量误差小于±0.5%", ("高精度", "线性测量误差小于"), "High accuracy: nonlinearity measurement error is less than ±0.5% over an input dynamic range of 2500:1", "¿¬Ìå", "KleeOne-Regular.ttf", (109.70, 97.96, 287.09, 125.92), 9.0, ), _R( "大信号稳定性,采样电流300mA点,CF输出跳动小于±0.2%", ("大信号稳定性", "跳动小于"), "Large-signal stability: at a sampling current of 300mA, CF output fluctuation is less than ±0.2%", "¿¬Ìå", "KleeOne-Regular.ttf", (109.70, 133.96, 286.97, 161.92), 9.0, ), _R( "小信号稳定性,采样电流50mA点CF跳动小于±0.3%", ("小信号稳定性", "于"), "Small-signal stability: at a sampling current of 50mA, CF output fluctuation is less than ±0.3%", "¿¬Ìå", "KleeOne-Regular.ttf", (109.70, 169.96, 286.97, 197.92), 9.0, ), _R( "芯片给出电压和电流的有效值,电流测量范围(4mA30A)@1mohm", ("芯片给出电压和电流的有效值", "(", ")"), "Provides RMS voltage and current measurements, with a current measurement range of (4mA30A) @1mohm", "¿¬Ìå", "KleeOne-Regular.ttf", (109.70, 206.25, 287.09, 233.92), 9.0, ), _R( "芯片具有防潜动设计,确保无电流时噪声功率切除。", ("芯片具有防潜动设计", "切除。"), "Anti-creep design ensures that noise-induced power is suppressed when no current is present.", "¿¬Ìå", "KleeOne-Regular.ttf", (109.70, 242.25, 287.09, 269.28), 9.0, ), _R( "芯片上有电源电压监测电路,检测掉电状况,工作电压低于2.6V时,芯片进入复位状态", ("芯片上有电源电压监测电路", "工作电压低于", "芯片进入复位状态"), "On-chip supply voltage monitoring circuit detects power-down conditions; when the operating voltage falls below 2.6V, the IC enters the reset state", "¿¬Ìå", "KleeOne-Regular.ttf", (109.70, 278.28, 289.87, 305.95), 9.0, ), _R( "芯片内置1.1V参考电压源(典型值)", ("芯片内置", "参考电压源", "典型值"), "Built-in 1.1V voltage reference (typical)", "¿¬Ìå", "KleeOne-Regular.ttf", (109.70, 313.99, 248.18, 323.95), 9.0, lineheight=1.0, ), _R( "芯片内置振荡电路,无需外接晶振", ("芯片内置振荡电路", "无需外接晶振"), "Built-in oscillator circuit; no external crystal is required", "¿¬Ìå", "KleeOne-Regular.ttf", (109.70, 332.28, 244.73, 341.28), 9.0, lineheight=1.0, ), _R( "芯片单工作电源3.3V,低功耗8mW(典型值)", ("芯片单工作电源", "低功耗", "典型值"), "Single 3.3V supply with low power consumption of 8mW (typical)", "¿¬Ìå", "KleeOne-Regular.ttf", (109.70, 349.99, 291.63, 359.95), 9.0, lineheight=1.0, ), _R( "SOP8封装", ("封装",), "SOP8 package", "¿¬Ìå", "KleeOne-Regular.ttf", (109.70, 367.99, 150.98, 377.95), 9.0, lineheight=1.0, ), _R( "概述", ("概述",), "Overview", "ËÎÌå", "Tinos-Regular.ttf", (308.35, 81.31, 329.47, 91.87), 10.56, lineheight=1.0, ), _R( "BL0937B是一颗宽量程单相多功能电能计量芯片,适用于单相插座表、单相插排、智能家电控制电路等应用,具有较高的性价比。", ("是一颗宽量程单相多功能电能计量", "制电路等应用,具有较高的性价比。"), "BL0937B is a wide-range, multifunction single-phase energy metering IC suitable for applications such as single-phase socket meters, single-phase power strips, and smart appliance control circuits, offering excellent cost performance.", "¿¬Ìå", "KleeOne-Regular.ttf", (308.35, 100.36, 505.54, 145.65), 9.0, ), _R( "BL0937B集成了2路高精度Sigma-Delta ADC,参考电压,电源管理等模拟电路模块,以及处理有功功率、电流电压有效值等电参数的数字信号处理电路。提供高频CF1用于指示电流/电压有效值,高频CF用于电能计量。", ("集成了", "参考电压,电源管理等模拟电路模块", "电路。提供高频", "用于电能计量。"), "BL0937B integrates two high-precision Sigma-Delta ADCs, a voltage reference, power management and other analog circuit blocks, as well as digital signal processing circuitry for electrical parameters such as active power and RMS current and voltage. It provides a high-frequency CF1 output for indicating RMS current/voltage and a high-frequency CF output for energy metering.", "¿¬Ìå", "KleeOne-Regular.ttf", (308.35, 154.36, 510.00, 236.32), 9.0, ), _R( "BL0937B能够测量单相有功能量、有功功率、电流电压有效值等参数;能够充分满足插座表、单相插排、智能家电等领域的需要。", ("能够测量单相有功能量、有功功率", "电流电压有效值等参数", "相插排、智能家电等领域的需要。"), "BL0937B can measure parameters including single-phase active energy, active power, and RMS current and voltage, fully meeting the requirements of socket meters, single-phase power strips, smart appliances, and similar applications.", "¿¬Ìå", "KleeOne-Regular.ttf", (308.35, 244.36, 508.18, 289.68), 9.0, ), _R( "BL0937B具有专利防潜动设计,配合合理的外部硬件设计,", ("具有专利防潜动设计,配合合理的外", "部硬件设计,"), "BL0937B features a patented anti-creep design which, when combined with proper external hardware design,", "¿¬Ìå", "KleeOne-Regular.ttf", (308.35, 298.39, 505.51, 325.68), 9.0, ), _R( "管脚与系统框图", ("管脚与系统框图",), "Pin Configuration and System Block Diagram", "¿¬Ìå", "KleeOne-Regular.ttf", (110.90, 428.12, 195.22, 440.12), 12.0, lineheight=1.0, ), _R( "上海贝岭股份有限公司", ("上海贝岭股份有限公司",), "Shanghai Belling Co., Ltd.", "ËÎÌå", "Tinos-Regular.ttf", (89.90, 760.00, 179.90, 769.00), 9.0, lineheight=1.0, ), _R( "上海市宜山路810号 021-24261000", ("上海市宜山路", "号"), "810 Yishan Road, Shanghai 021-24261000", "ËÎÌå", "Tinos-Regular.ttf", (89.90, 771.36, 230.18, 781.32), 9.0, lineheight=1.0, ), ]

def _is_pdf_safe_cjk_compat_codepoint(codepoint: int) -> bool: return any( start <= codepoint <= end for start, end in PDF_SAFE_CJK_COMPAT_RANGES )

def _clean_one_pdf_safe_ttfont(font: TTFont) -> int: if "cmap" not in font: return 0

text
best_cmap = font.getBestCmap() or {} removable: dict[int, tuple[int, str]] = {} for codepoint, glyph_name in list(best_cmap.items()): if not _is_pdf_safe_cjk_compat_codepoint(codepoint): continue normalized = unicodedata.normalize( "NFC", chr(codepoint), ) if len(normalized) != 1: continue canonical_codepoint = ord(normalized) if canonical_codepoint == codepoint: continue if best_cmap.get(canonical_codepoint) != glyph_name: continue removable[codepoint] = ( canonical_codepoint, glyph_name, ) if not removable: return 0 for table in font["cmap"].tables: if not table.isUnicode(): continue cmap = getattr(table, "cmap", None) if not isinstance(cmap, dict): continue for codepoint in removable: cmap.pop(codepoint, None) return len(removable)

def make_pdf_safe_font( source_path: Path, output_path: Path, ) -> None: source_path = Path(source_path) output_path = Path(output_path)

text
if not source_path.is_file(): raise RuntimeError( f"原始字体文件不存在:{source_path}" ) output_path.parent.mkdir( parents=True, exist_ok=True, ) output_path.unlink(missing_ok=True) suffix = source_path.suffix.lower() removed_count = 0 if suffix in {".ttc", ".otc"}: collection = TTCollection(str(source_path)) try: for font in collection.fonts: removed_count += _clean_one_pdf_safe_ttfont( font ) if removed_count > 0: collection.save(str(output_path)) finally: for font in collection.fonts: try: font.close() except Exception: pass if removed_count == 0: shutil.copy2( source_path, output_path, ) elif suffix in {".ttf", ".otf"}: font = TTFont(str(source_path)) try: removed_count = _clean_one_pdf_safe_ttfont( font ) if removed_count > 0: font.save(str(output_path)) finally: try: font.close() except Exception: pass if removed_count == 0: shutil.copy2( source_path, output_path, ) else: raise RuntimeError( "PDF-safe 字体预处理不支持该字体扩展名:" f"{source_path.name}" ) if ( not output_path.is_file() or output_path.stat().st_size <= 0 ): raise RuntimeError( f"PDF-safe 临时字体生成失败:{output_path}" )

def _nfc(text): return unicodedata.normalize("NFC", text)

def _collapse_ws(text): return re.sub(r"\s+", "", _nfc(text))

def _rect_inside(inner, outer, eps=0.15): return ( inner.x0 >= outer.x0 - eps and inner.y0 >= outer.y0 - eps and inner.x1 <= outer.x1 + eps and inner.y1 <= outer.y1 + eps )

def _validate_input(input_path): if not input_path.is_absolute(): raise RuntimeError("--input 必须是绝对路径")

text
if not input_path.is_file(): raise RuntimeError( f"输入 PDF 不存在:{input_path}" ) try: doc = fitz.open(input_path) except Exception as exc: raise RuntimeError( f"无法打开输入 PDF:{exc}" ) from exc if len(doc) != 1: doc.close() raise RuntimeError( "输入 PDF 必须恰好包含一页" ) page = doc[0] rect = fitz.Rect(page.rect) rotation = int(page.rotation) if rect.width <= 0 or rect.height <= 0: doc.close() raise RuntimeError( "无法读取有效页面尺寸" ) page.get_text("text") page.get_fonts(full=True) page.get_images(full=True) page.get_drawings() page.get_contents() return doc, rect, rotation

def _validate_sources(page): page_text = _nfc( page.get_text("text") )

text
missing = [ marker for item in REPLACEMENTS for marker in item["source_markers"] if marker not in page_text ] if missing: raise RuntimeError( "输入页未找到预期原文标记:" + " | ".join(missing) )

def _prepare_fonts(temp_dir): prepared = {}

text
for font_file in sorted( FONT_ALIASES, key=str.casefold, ): source = FONT_DIR / font_file if ( not source.is_file() or source.stat().st_size <= 0 ): raise RuntimeError( f"目标字体文件不存在:{source}" ) safe_path = ( temp_dir / ( source.stem + ".pdfsafe" + source.suffix ) ) make_pdf_safe_font( source, safe_path, ) prepared[font_file] = safe_path return prepared

def _wrap_fragments( text, font, fontsize, max_width, ): chunks = re.findall( r"\S+\s*", text, )

text
if "".join(chunks) != text: raise RuntimeError( f"内部换行分词失败:{text!r}" ) lines = [] current = "" for chunk in chunks: trial = current + chunk if ( current and font.text_length( trial, fontsize=fontsize, ) > max_width + 1e-6 ): lines.append(current) current = chunk else: current = trial if ( font.text_length( current, fontsize=fontsize, ) > max_width + 1e-6 ): remainder = current current = "" while remainder: piece = "" for ch in remainder: if ( piece and font.text_length( piece + ch, fontsize=fontsize, ) > max_width + 1e-6 ): break piece += ch if not piece: raise RuntimeError( "单字符无法放入 bbox:" f"{remainder[0]!r}" ) if len(piece) == len(remainder): current = piece remainder = "" else: lines.append(piece) remainder = remainder[ len(piece): ] if current: lines.append(current) if "".join(lines) != text: raise RuntimeError( "内部换行未保持完整译文:" f"{text!r}" ) return lines

def _fit_and_insert( page, item, font, ): text = _nfc( item["text"] )

text
rect = fitz.Rect( item["bbox"] ) size = item["fontsize"] ascender = float( font.ascender ) descender = float( font.descender ) for _ in range(120): fragments = _wrap_fragments( text, font, size, rect.width, ) lineheight = max( item["lineheight"], ascender - descender + 0.02, ) total_height = ( (ascender - descender) * size + max( 0, len(fragments) - 1, ) * lineheight * size ) widths_ok = all( font.text_length( fragment, fontsize=size, ) <= rect.width + 1e-6 for fragment in fragments ) if ( total_height <= rect.height + 1e-6 and widths_ok ): baseline = ( rect.y0 + ascender * size ) for ( line_index, fragment, ) in enumerate(fragments): page.insert_text( ( rect.x0, baseline + line_index * lineheight * size, ), fragment, fontsize=size, fontname=item[ "font_alias" ], color=item[ "color" ], fill=item[ "color" ], render_mode=0, rotate=item[ "rotate" ], overlay=True, ) return ( size, fragments, ) size *= 0.95 raise RuntimeError( "译文无法放入原始 bbox:" f"{item['source_text']}" " -> " f"{item['text']}" )

def _apply_replacements( doc, safe_fonts, ): page = doc[0]

text
for item in REPLACEMENTS: page.add_redact_annot( fitz.Rect( item["delete_rect"] ), fill=None, cross_out=False, ) ok = page.apply_redactions( images=( fitz.PDF_REDACT_IMAGE_NONE ), graphics=( fitz.PDF_REDACT_LINE_ART_NONE ), text=( fitz.PDF_REDACT_TEXT_REMOVE ), ) if not ok: raise RuntimeError( "原生文字删除失败" ) for ( font_file, alias, ) in sorted( FONT_ALIASES.items(), key=lambda kv: kv[ 0 ].casefold(), ): page.insert_font( fontname=alias, fontfile=str( safe_fonts[ font_file ] ), set_simple=False, ) measurement_fonts = { name: fitz.Font( fontfile=str(path) ) for ( name, path, ) in safe_fonts.items() } for item in REPLACEMENTS: _fit_and_insert( page, item, measurement_fonts[ item[ "target_font_file" ] ], )

def _repair_subset_tounicode_for_written_ascii( doc, ): all_written = "".join( _nfc(item["text"]) for item in REPLACEMENTS )

text
replacement_map = {} if ( " " in all_written and "\u00a0" not in all_written ): replacement_map[ "00a0" ] = "0020" if ( "-" in all_written and "\u00ad" not in all_written ): replacement_map[ "00ad" ] = "002d" if not replacement_map: return for font in doc[ 0 ].get_fonts( full=True ): if ( font[4] not in FONT_ALIASES.values() ): continue font_obj = doc.xref_object( font[0], compressed=False, ) match = re.search( r"/ToUnicode\s+(\d+)\s+0\s+R", font_obj, ) if not match: raise RuntimeError( "目标字体缺少 ToUnicode:" f"alias={font[4]}, " f"xref={font[0]}" ) tounicode_xref = int( match.group(1) ) stream = doc.xref_stream( tounicode_xref ) if not stream: raise RuntimeError( "目标字体 ToUnicode 为空:" f"alias={font[4]}" ) changed = False updated_lines = [] for line in stream.decode( "latin1" ).splitlines( keepends=True ): matches = list( re.finditer( r"<([0-9A-Fa-f]+)>", line, ) ) if len(matches) == 2: destination_index = 1 elif len(matches) == 3: destination_index = 2 else: updated_lines.append( line ) continue destination_match = ( matches[ destination_index ] ) destination = ( destination_match .group(1) .lower() ) new_destination = ( replacement_map.get( destination ) ) if ( new_destination is None ): updated_lines.append( line ) continue updated_lines.append( line[ : destination_match.start() ] + f"<{new_destination}>" + line[ destination_match.end() : ] ) changed = True if changed: doc.update_stream( tounicode_xref, "".join( updated_lines ).encode( "latin1" ), )

def _validate_output_structure( candidate, input_rect, input_rotation, ): if ( not candidate.is_file() or candidate.stat().st_size <= 0 ): raise RuntimeError( "临时输出文件不存在或为空" )

text
try: out_doc = fitz.open( candidate ) except Exception as exc: raise RuntimeError( "无法重新打开临时输出 PDF:" f"{exc}" ) from exc if len(out_doc) != 1: out_doc.close() raise RuntimeError( "输出 PDF 必须恰好包含一页" ) page = out_doc[0] rect = fitz.Rect( page.rect ) if not ( math.isclose( rect.width, input_rect.width, abs_tol=0.01, ) and math.isclose( rect.height, input_rect.height, abs_tol=0.01, ) ): out_doc.close() raise RuntimeError( "输出页面尺寸与输入不一致" ) if ( int(page.rotation) != input_rotation ): out_doc.close() raise RuntimeError( "输出页面旋转与输入不一致" ) return out_doc

def _validate_target_text( out_doc, ): page = out_doc[0] failures = []

text
for ( index, item, ) in enumerate( REPLACEMENTS, 1, ): rect = fitz.Rect( item["bbox"] ) expected = _nfc( item["text"] ) extracted = ( page.get_text( "text", clip=rect, sort=True, ) ) if ( _collapse_ws( extracted ) != _collapse_ws( expected ) ): failures.append( f"#{index} " "译文提取不一致 " f"expected={expected!r} " f"got={extracted!r}" ) continue for marker in item[ "source_markers" ]: if marker in extracted: failures.append( f"#{index} " "原文仍残留:" f"{marker}" ) spans = [] data = page.get_text( "dict", clip=rect, sort=True, ) for block in data.get( "blocks", [], ): if ( block.get( "type" ) != 0 ): continue for line in block.get( "lines", [], ): for span in line.get( "spans", [], ): if span.get( "text", "", ): spans.append( span ) if not spans: failures.append( f"#{index} " "目标区域没有可提取文本" ) continue for span in spans: span_rect = fitz.Rect( span["bbox"] ) if not _rect_inside( span_rect, rect, ): failures.append( f"#{index} " "译文 bbox 越界:" f"{span['bbox']} " "not in " f"{tuple(rect)}" ) if failures: raise RuntimeError( "目标文本验证失败:\n" + "\n".join( failures ) )

def _validate_font_resources( out_doc, ): fonts = out_doc[ 0 ].get_fonts( full=True )

text
for ( font_file, alias, ) in sorted( FONT_ALIASES.items(), key=lambda kv: kv[ 0 ].casefold(), ): matches = [ font for font in fonts if font[4] == alias ] if len(matches) != 1: raise RuntimeError( "目标字体资源 " f"{alias} 数量异常:" f"{len(matches)}" ) ( xref, _, _, basefont, _, _, _, ) = matches[0] if not re.match( r"^[A-Z]{6}\+", basefont, ): raise RuntimeError( "目标字体未呈现子集前缀:" f"alias={alias}, " f"basefont={basefont}" ) font_data = ( out_doc.extract_font( xref )[3] ) font_size = len( font_data ) if font_size <= 0: raise RuntimeError( "目标字体流为空:" f"alias={alias}, " f"xref={xref}" ) if ( font_size > 2 * 1024 * 1024 ): raise RuntimeError( "目标字体流超过 2 MiB:" f"alias={alias}, " f"basefont={basefont}, " f"xref={xref}, " f"bytes={font_size}" )

def _validate_non_target_resources( input_path, out_doc, ): in_doc = fitz.open( input_path )

text
try: input_page = in_doc[0] output_page = out_doc[0] input_images = ( input_page.get_images( full=True ) ) output_images = ( output_page.get_images( full=True ) ) if ( len(input_images) != len(output_images) ): raise RuntimeError( "非目标图片数量改变:" f"input={len(input_images)}, " f"output={len(output_images)}" ) input_drawings = ( input_page.get_drawings() ) output_drawings = ( output_page.get_drawings() ) if ( len(input_drawings) != len(output_drawings) ): raise RuntimeError( "非目标矢量绘图数量改变" ) input_links = ( input_page.get_links() ) output_links = ( output_page.get_links() ) if ( len(input_links) != len(output_links) ): raise RuntimeError( "非目标链接数量改变" ) if input_images: input_pixmap = fitz.Pixmap( in_doc, input_images[0][0], ) output_pixmap = fitz.Pixmap( out_doc, output_images[0][0], ) if ( input_pixmap.width, input_pixmap.height, input_pixmap.n, input_pixmap.samples, ) != ( output_pixmap.width, output_pixmap.height, output_pixmap.n, output_pixmap.samples, ): raise RuntimeError( "非目标 Logo 图片像素发生改变" ) finally: in_doc.close()

def _validate_render( out_doc, ): pixmap = out_doc[ 0 ].get_pixmap( matrix=fitz.Matrix( 200 / 72.0, 200 / 72.0, ), alpha=False, )

text
if ( pixmap.width <= 0 or pixmap.height <= 0 or not pixmap.samples ): raise RuntimeError( "输出页面无法正常渲染" )

def _save_and_validate( doc, input_path, output_path, input_rect, input_rotation, ): output_path.parent.mkdir( parents=True, exist_ok=True, )

text
fd, temp_name = tempfile.mkstemp( prefix=( output_path.stem + ".candidate." ), suffix=".pdf", dir=str( output_path.parent ), ) os.close(fd) candidate = Path( temp_name ) candidate.unlink( missing_ok=True ) try: doc.subset_fonts( fallback=True ) _repair_subset_tounicode_for_written_ascii( doc ) doc.save( candidate, garbage=4, deflate=True, ) out_doc = ( _validate_output_structure( candidate, input_rect, input_rotation, ) ) try: _validate_target_text( out_doc ) _validate_font_resources( out_doc ) _validate_non_target_resources( input_path, out_doc, ) _validate_render( out_doc ) finally: out_doc.close() input_size = ( input_path.stat().st_size ) output_size = ( candidate.stat().st_size ) if ( output_size - input_size > 5 * 1024 * 1024 ): check_doc = fitz.open( candidate ) try: font_info = [] for font in check_doc[ 0 ].get_fonts( full=True ): if ( font[4] in FONT_ALIASES.values() ): font_info.append( ( font[3], font[4], font[0], len( check_doc.extract_font( font[0] )[3] ), ) ) finally: check_doc.close() raise RuntimeError( "输出文件异常增长超过 5 MiB:" f"input={input_size}, " f"output={output_size}, " f"fonts={font_info}" ) os.replace( candidate, output_path, ) except Exception: candidate.unlink( missing_ok=True ) raise

def main(): parser = argparse.ArgumentParser()

text
parser.add_argument( "--input", required=True, ) parser.add_argument( "--output", required=True, ) args = parser.parse_args() input_path = Path( args.input ) output_path = Path( args.output ) if not output_path.is_absolute(): raise RuntimeError( "--output 必须是绝对路径" ) ( doc, input_rect, input_rotation, ) = _validate_input( input_path ) try: _validate_sources( doc[0] ) with tempfile.TemporaryDirectory( prefix="pdfsafe-fonts-" ) as temp_font_dir: safe_fonts = ( _prepare_fonts( Path( temp_font_dir ) ) ) _apply_replacements( doc, safe_fonts, ) _save_and_validate( doc, input_path, output_path, input_rect, input_rotation, ) finally: doc.close() print( str( output_path ) ) print( output_path.stat().st_size )

if name == "main": main() [python处理脚本 END] [全书字体使用 START] {"version":1,"fonts":[{"target_font_file":"KleeOne-Regular.ttf","font_alias":"TRFONT1","text":"High accuracy: nonlinearity measurement error is less than ±0.5% over an input dynamic range of 2500:1Large-signal stability: at a sampling current of 300mA, CF output fluctuation is less than ±0.2%Small-signal stability: at a sampling current of 50mA, CF output fluctuation is less than ±0.3%Provides RMS voltage and current measurements, with a current measurement range of (4mA~30A) @1mohmAnti-creep design ensures that noise-induced power is suppressed when no current is present.On-chip supply voltage monitoring circuit detects power-down conditions; when the operating voltage falls below 2.6V, the IC enters the reset stateBuilt-in 1.1V voltage reference (typical)Built-in oscillator circuit; no external crystal is requiredSingle 3.3V supply with low power consumption of 8mW (typical)SOP8 packageBL0937B is a wide-range, multifunction single-phase energy metering IC suitable for applications such as single-phase socket meters, single-phase power strips, and smart appliance control circuits, offering excellent cost performance.BL0937B integrates two high-precision Sigma-Delta ADCs, a voltage reference, power management and other analog circuit blocks, as well as digital signal processing circuitry for electrical parameters such as active power and RMS current and voltage. It provides a high-frequency CF1 output for indicating RMS current/voltage and a high-frequency CF output for energy metering.BL0937B can measure parameters including single-phase active energy, active power, and RMS current and voltage, fully meeting the requirements of socket meters, single-phase power strips, smart appliances, and similar applications.BL0937B features a patented anti-creep design which, when combined with proper external hardware design,Pin Configuration and System Block Diagram"},{"target_font_file":"Tinos-Regular.ttf","font_alias":"TRFONT2","text":"Single-Phase Socket Metering IC with Built-in ClockFeaturesOverviewShanghai Belling Co., Ltd.810 Yishan Road, Shanghai 021-24261000"}]} [全书字体使用 END] [python执行命令行 START] python "{脚本绝对路径占位符}" --input "{输入PDF绝对路径占位符}" --output "{输出PDF绝对路径占位符}" [python执行命令行 END]

分享这个问答