# 单页 PDF 翻译处理规范 本轮源语言为 英语,目标语言为 英语。第七章提供的原文均为源语言,...
创建于:2026年8月22日
使用 GPT-5.6 Thinking 回答 Chat01
创建于:2026年8月22日
使用 GPT-5.6 Thinking 回答 Chat01
本轮源语言为 英语,目标语言为 英语。第七章提供的原文均为源语言,译文均为目标语言;不得自行改变既定语言方向。
本规范分为十章。第一章说明任务;第二章定义基础对象模型;第三章规定针对单处内容的原子处理方法;第四章规定整页路线的识别与编排;第五章规定各路线共用的字体、排版与脚本规则;附章 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 页面路线结果自检组合 第十章 唯一输出协议
阅读本规范,然后按规范进行处理。你必须基于同一份真实单页 PDF 自行选择最合适的处理路线,并严格遵守本规范唯一的输出协议。
外层程序不会预判本页应该走哪条路线。你必须先下载、打开并检查真实的单页 PDF,再依据第二章的载体定义和第四章的路线规则完成判定。
请访问第七章提供的“单页 PDF 链接”,下载并打开本轮实际需要处理的单页 PDF,对真实单页 PDF 进行解析、生成处理脚本、执行和检查。生成的脚本只能读取 --input 指定的本地 PDF。
无论选择哪条路线,最终都必须输出一个接受 --input 单页 PDF、输出 --output 单页 PDF 的 Python 脚本。路线 R 的结果没有原生文本层是允许且正常的;路线 A/3C/3D 则必须保留或重建可搜索的原生文本层;路线 H 必须保留补丁范围外的原生文本层,且不得为图片内部文字伪造 OCR 或隐藏文本层。
整体处理思路:
本轮提供:
原文、译文和单页 PDF 链接见第七章,固定字体映射见 5.1。
第七章的“原文”和“译文”是本轮唯一权威的内容范围。你的任务是把它们之间已经确定的替换写入真实 PDF;不要自行决定还应翻译、删除、保留或检查哪些其他内容。
本条所称“不自行充当审校器”,仅表示不得自行修改第七章给出的译文、不得自行增加新的翻译任务、不得把第七章之外的其他可见中文擅自加入本轮替换范围;它不表示可以跳过对既定替换结果的验证。
对于第七章已经明确给出的每一项替换,必须验证它是否在真实输出 PDF 中被完整、正确地实施。输入检查、基础输出完整性检查由第八章规定;目标内容、排版、字体、对象和视觉结果的闭环验证由第九章规定。
第三章至第五章中如提及检查,均表示引用第八章和第九章中适用于对应处理器和页面路线的规则,不得脱离第七章给定任务范围自行创造新的翻译目标。
仍须根据真实 PDF 的基本对象类型选择 A、3C、3D、H 或 R,但路线选择完成后只按已给定原文/译文实施替换。翻译内容本身的语言质量、措辞选择和第七章之外哪些文字也应翻译,不是本脚本的自主判断任务;已给定替换是否实际成功,则必须按照第九章完成验证。
如果你无法亲自从“单页 PDF 链接”成功下载文件、使用 PyMuPDF 成功打开该文件、确认它确实只有一页,
则不得根据原文转录、译文、文件名、截图或任何猜测生成脚本。
此时四个输出协议区块都必须且只能输出 [无法下载PDF]。不得输出脚本、命令、解释、其他标记或任何额外文字。
[无法下载PDF] 仅表示本轮无法访问并检查真实 PDF;它不表示页面没有文字、没有可映射字体,也不代表任何路线选择。
外层程序会只重试当前任务实例,因此你必须诚实使用这个失败出口,不能臆测真实 PDF 的对象结构。
先对本页需要替换的全部可见文字建立以下三类载体集合:
N(Native Text):可由 PDF 文本对象提取的原生可见文字。V(Vector Outline Text):不是 PDF 文本对象,而是由矢量路径、轮廓、填充或描边绘制出的可见文字。I(Image Pixel Text):存在于扫描图、截图、位图或其他图像像素内部的可见文字。每一处待替换文字必须且只能归入一类载体。三类载体分别由第三章中对应的原子处理方法处理:
textN -> 3.1 N 处理器 V -> 3.2 V 处理器 I -> 3.3 I 处理器
载体分类只统计第七章“原文”和“译文”所确定的待替换文字。页面中与本轮替换无关的图片、照片、Logo、表格线、箭头、电路图、普通矢量图形或其他资源,不得影响载体分类,也不得影响第四章的路线判定。
具体限定:
I;V;本章规定针对单处待替换文字的处理方法。第四章的五条页面路线只负责编排调用本章的处理器,不得另行定义与本章不同的处理方法。
本章中为实施既定替换而进行的定位、文字测量、换行、字号计算、bbox 适配、ROI 计算、背景采样和绘制范围计算,属于处理算法,不属于第八章和第九章所称的保存后验收。
适用于载体为 N 的每一处待替换文字。凡调用本处理器的位置,包括路线 A、路线 3D 的原生文字支和路线 H 中的 N 目标,均逐条适用以下全部规则。
步骤一:解析 PDF
使用 PyMuPDF 打开单页 PDF,解析页面中的组成对象,并找到所有原生可见文本对象。
对每个文本对象读取:文字内容、字体、字号、颜色、方向、旋转角度、包围矩形 bbox。
固定字体映射表的映射键已经去除了 PDF 子集字体名称中类似 ABCDEF+ 的六位大写字母前缀。
如果本页提取出的字体名仍带有这种子集前缀,查询固定映射前必须先去除该前缀;
不得因为子集前缀不同而认为它是未映射的新字体。
步骤二:定位与判断
对比第七章的“原文”和“译文”,找到需要替换的地方。
不得只处理正文段落。第七章已经列出的流程图、时序图、表格、箭头旁、图题和矢量轮廓中的每一个普通中文标签也必须逐项核对并替换。技术缩写可按审校译文保留,但包含普通中文词的混合标签必须完整按照审校译文实施替换:例如审校译文为“主机驱动 NACK”时,原标签 Master Drives NACK 中的 Master 和 Drives 都必须消失,只保留审校译文明确保留的 NACK。
不得因为页面上另外存在未列入第七章的中文而自行增加翻译任务。
步骤三:提取文本信息
对每个命中位置,记录:原文内容、原文字体、字号、颜色、方向、旋转角度、包围矩形 bbox。
原文 bbox 是译文唯一允许占用的区域,不得扩大,不得增加容差。
步骤四:替换文本
必须严格使用 5.1 针对整本 PDF 预先确定的固定字体映射,也就是 target_font_file
指定的字体文件,禁止替代方案。
译文中的全部字符,包括汉字、拉丁字母、数字、西文标点、英语标点、单位、型号、寄存器名、引脚名、技术缩写及其他可见字符,均必须使用该位置原字体对应的同一个 target_font_file。
不得继续使用原文字体或该页已有的其他西文字体写入译文中的任何字符。
禁止使用固定字体映射表之外的任何字体。
不得使用映射表之外的字体,不得使用 PyMuPDF 内置 china-s、china-t、japan、
korea 等 CJK 字体临时替代,也不得读取外部字体映射配置文件。
字体映射规则见 5.1,不同文字体系混排排版规则见 5.2,字体注册与子集化规则见 5.5。
对每个命中位置:
步骤五:处理完成后,按第八章和第九章中适用于 N 处理器及当前页面路线的规则保存、自检、必要时修改并重新执行,只有实际结果通过全部适用检查后才能交付。
适用于载体为 V 的每一处待替换文字。凡调用本处理器的位置,包括路线 3C、路线 3D 的轮廓文字支和路线 H 中的 V 目标,均逐条适用以下全部规则。
page.get_text() 提取,而是由大量 vector drawing/path 构成。缺少原生文字对象或无法查询原字体名称,本身不触发 [本页无可映射字体];此时必须按照 5.1 为该 V 目标从固定映射表中选择并硬编码一个已有映射键。只有无法为非空译文确定非 null 的 target_font_file 时,才输出 [本页无可映射字体]。source_text、完整译文以及因换行、旋转或定位而需要的写入片段、映射键、target_font_file、bbox、对齐方式、q ... Q 绘制分组,并仅删除这些分组。page.insert_font(..., set_simple=False) 注册固定目标字体,以 page.insert_text() 写入全部可见译文字符。适用于载体为 I 的每一处待替换文字。本处理器有两种工作模式:
textI-LOCAL:局部补丁模式,供路线 H 使用 I-FULL:整页图像模式,供路线 R 使用
两种模式共用 3.3.1 的图像编辑方法,区别只在处理范围与最终 PDF 封装方式。
图片内文字不得作为 OCR 文本层写入。
对图中每一处需要翻译的区域重复执行以下步骤:
步骤一:定位与判断
扫描高分辨率渲染图,结合原文和译文逐项定位第七章已经给出的原始中文。不得仅依据译文条目顺序猜测位置,也不得因为扫描过程中看到第七章之外的其他中文而自行增加翻译任务。
步骤二:局部放大
裁取包含该处文字及其周边的足够大局部矩形区域(ROI),在放大状态下进行后续编辑。
步骤三:擦除原文
在放大区域内用该位置的背景色精确擦除原中文文字,恢复干净背景。
步骤四:写入译文
在擦除后的位置写入译文。
【视觉容器规则】
可用区域不是中文字符笔画的紧包围框,而是原文字所属的完整视觉容器:
译文不得跨越所属视觉容器的边框、表格线或图形边界。
【字体与字号规则】
target_font_file。若现有映射资源不适合该图像文字的视觉样式,必须在第十章的字体资源下载区块声明所需字体,外层程序会在执行脚本前将其放入 FONT_DIR。I 载体不得因固定映射缺失或不适合而输出 [本页无可映射字体];对 I 载体,固定映射表中的现有字体优先于新增下载字体;只有现有资源均不适合图像中的视觉样式时,才可在字体资源下载区块中补充字体。选定某个字体文件后,只能通过字号、颜色、对齐、字距、行距以及必要时的描边参数协调视觉效果,不得为同一处译文的不同字符改用其他字体。
【硬性禁止】
步骤五:缩回原尺寸
将编辑完成的局部区域缩回原始比例并贴回对应位置,与周围图像无缝衔接。图像编辑操作必须严格限制在明确许可的局部编辑区域内,不得主动修改其他区域。
本模式只对属于 I 的目标所在的局部矩形进行图像化处理,页面其余内容必须保持原生对象。它不是整页截图,也不是隐藏 OCR 层。
本模式把 PDF 的唯一页面渲染为高分辨率整页图像,采用“局部放大—擦除—写字—缩回”的图像编辑方式完成翻译;再把译图封装回同尺寸的单页 PDF。它不是隐藏 OCR 层,不得在原中文上叠加英语,也不要求原生文本层。
--input,并按照第八章执行输入检查;以至少 300 DPI(优先 400 DPI)渲染该页为脚本工作目录中的临时 PNG,再用 Pillow 编辑该临时图。FONT_DIR / "字体文件名" 所指向的字体读取。可优先使用固定字体映射中非 null 的现有 target_font_file;若其均不适合图像文字的视觉样式,必须使用本页字体资源下载区块中声明、并由外层程序预先放入 FONT_DIR 的字体。汉字、拉丁字母、数字、标点、单位、型号、技术缩写及其他字符必须统一使用同一个选定字体文件,不得使用系统字体、脚本目录私有资源或其他字体。I-FULL 不得因固定映射缺失或不适合而输出 [本页无可映射字体]。--image,不得输出 PNG 作为最终结果。根据本页实际需要替换的可见文字载体,在 A、3C、3D、H、R 五条平级路线中选择且只能选择一条。
五条路线是互斥的最终页面路线,不是依次尝试的降级层级。路线只能由原始 PDF 中待译文字的真实载体决定,不得因为某条路线实现困难、字体缺失、脚本报错、检查失败或处理效果不理想而改选其他路线。
唯一对应关系如下:
textN -> 路线 A V -> 路线 3C N + V -> 路线 3D N + I -> 路线 H V + I -> 路线 H N + V + I -> 路线 H I -> 路线 R
即:
N。V。N + V,且不存在 I。I,并且同时存在 N 或 V。I。路线 H 不是路线 A、3C 或 3D 失败后的降级路线。路线 R 虽然在技术上可以把其他类型页面整页栅格化,但在本规范中只代表待译文字全部位于图像像素中的 I 页面,不得作为其他路线的通用兜底。
某个原字体缺少可用的 target_font_file、某个目标难以定位或某段脚本执行失败,都不会改变页面原本的 N、V、I 载体集合,也不得改变已经判定的页面路线。
判定本页的处理路线是最高优先级规则。
必须对实际下载的单页 PDF 同时检查 page.get_text()、page.get_fonts(full=True)、
page.get_images(full=True)、page.get_drawings()、page.get_contents(),并至少以 200 DPI 渲染后再作判断。
不得只依据转录文本、译文、文件名或页面截图推断路线。
判定路线时,必须先逐项核对第七章原文和译文所确定的全部待替换文字,并判断每一项实际属于 N、V 或 I。完成全部目标的载体归类后,再根据 4.1 的唯一组合表选择 A、3C、3D、H 或 R。
不得根据页面整体是否含有图片、绘图或文本粗略判路。载体分类范围限定见 2.2。
路线 3D:原生文字与矢量轮廓文字共存页(优先级高于路线 A)
N + V,且不存在任何 I 类型待替换文字,则必须走路线 3D。不能因为 page.get_text() 非空而忽略属于 V 的轮廓文字并误走路线 A,也不能因为页面存在不含待译文字的图片而误走路线 H。q ... Q 分组删除和 bbox 定位规则。source_text、完整译文、映射键、target_font_file、因换行、旋转或定位而需要的写入片段、bbox、颜色、字号、旋转和对齐;路线 A:仅原生文字页
N,则必须走路线 A。页面可以同时存在普通矢量图形、表格线、电路图、照片、Logo 或不含待译文字的图片;这些非目标对象不构成 V 或 I,也不改变路线 A 的判定。路线 3C:矢量轮廓文字页
V,则必须走路线 3C。通常这类目标无法由 page.get_text() 提取,而是由大量 vector drawing/path 构成。页面中可以存在不含待译文字的图片或其他非目标资源;只要全部待替换文字均属于 V,这些资源就不得使页面改走 H 或 R。路线 H:图像文字与非图像文字混合页
I,并且同时存在 N 或 V,则必须走路线 H。N + I、V + I 和 N + V + I 三种组合。N 的目标完整执行 3.1 N 处理器;属于 V 的目标完整执行 3.2 V 处理器;属于 I 的目标完整执行 3.3.2 I-LOCAL 局部补丁模式。I,不得因此选择路线 H。null、某一目标难以处理或其他路线执行失败而触发。字体映射状态只影响对应路线是否能够成功执行,不改变页面的载体分类。I,必须走路线 R,不得走路线 H。I,必须根据 N、V 的组合走路线 A、3C 或 3D,不得走路线 H。路线 R:纯图像文字页
I,不存在任何 N 或 V 类型待替换文字,则必须走路线 R。I。N 或 V 的待替换文字:同时存在 I 时必须走路线 H;不存在 I 时必须走路线 A、3C 或 3D。一旦根据 N、V、I 载体集合确定路线,必须只执行对应路线的规则,且本轮不得改选其他路线:
N;V;N 和 V;N、V、I;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、背景修补、换行、字号、坐标或其他实现参数并重新执行,不得通过改选路线逃避自检失败。
本章规则由多个处理器和多条路线共用。任何路线在需要写入译文字符、排版文本、定位字体资源或生成脚本时,均逐条适用本章。
固定字体映射如下:
{
"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 类型的非空译文任务,仍必须按本节的无可映射字体规则处理。
字体映射规则:
映射表的每个键都是一个原字体映射键。
处理某个原生文本对象时,必须先去除该对象字体名称中类似 ABCDEF+
的六位大写字母子集前缀,再使用规范化后的字体名称查询映射表。
对于 V 类型目标,如果无法从该目标取得可查询的原字体名称,必须在检查真实 PDF 和页面渲染结果后,从固定字体映射表已有条目中为该替换任务选择一个视觉样式对应的映射键,并将该映射键及其 target_font_file 作为 Python 字面量硬编码在脚本中。不得由脚本在运行时动态选择映射键,不得创建固定字体映射表中不存在的新键,也不得直接指定映射表之外的字体文件。
对于 I 类型目标,图像像素不存在可查询的原字体名称。应先从固定字体映射表已有非 null 条目中选择视觉样式协调的字体;若均不适合,可在第十章字体资源下载区块中声明一个可下载字体,并将该字体文件名作为 Python 字面量硬编码在脚本中。I 类型不得因没有可查询的原字体、固定映射缺失或现有映射不适合而输出 [本页无可映射字体]。
对 N、V 目标,target_font_file 是该原字体在需要写入任何译文字符时唯一允许使用的目标字体文件。当某个原文位置需要替换为非空译文时,必须使用该位置原字体对应的 target_font_file 写入译文中的全部字符,包括汉字、拉丁字母、数字、标点、单位、型号、寄存器名、引脚名、技术缩写及其他可见字符。
对 I 目标,选定的既有 target_font_file 或本页资源下载区块声明的字体文件,是该位置写入全部译文字符时唯一允许使用的字体文件。
固定映射中的 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 / "字体文件名" 使用本地字体文件。
在调用 3.1 N 处理器的位置,包括路线 A、路线 3D 的原生文字支和路线 H 中的 N 目标,只要该原文位置对应的译文为非空字符串,其原字体对应的 target_font_file 就必须为非 null。
如果该译文为非空字符串,而对应的 target_font_file 为 null,则属于无可映射字体;无论译文包含汉字、拉丁字母、数字、标点或其他字符,均按相同规则判断。
只有译文为空字符串、不需要写入任何字符时,target_font_file 为 null 才不触发该失败状态。
V 目标同样必须从固定字体映射表中确定非 null 的 target_font_file;无法确定时属于无可映射字体。
如果至少一个 N 或 V 类型的非空译文任务未能按照本节规则确定一个非 null 的 target_font_file,
不得自行选择替代字体,不得继续使用原字体,不得生成部分替换结果,四个输出区块都必须且只能输出:
[本页无可映射字体]
该标记的性质与限制见第六章。
对通过 N 处理器或 V 处理器写入 PDF 原生文本时实际使用的每个不同 target_font_file,必须注册为一个不同且稳定的 PDF 字体别名。
相同的 target_font_file 在同一页只能注册一次,并在所有对应的 N、V 目标中复用同一个别名。
对 I 处理器使用的选定字体文件(既有 target_font_file 或本页资源下载字体),不得注册或嵌入为 PDF 字体;应由 Pillow 从对应字体文件加载,并在相同字体文件对应的所有 I 目标中复用。
字体别名只是脚本内部用于引用 PDF 字体文件的标识,不代表字体款式或类别。
可以按照本页实际用于 N、V 处理的目标字体文件名排序,依次使用 TRFONT1、TRFONT2、
TRFONT3 等别名。
字号以每个原文位置的原始字号为初始值。
若译文超出原始 bbox,则按照 3.1 步骤四规定的整体缩放规则逐步缩小。
不得通过改用其他字体规避 bbox 限制。
本节所称“使用同一个选定字体”,对 N、V 目标是指使用同一个 target_font_file,对 I 目标是指使用同一个既有 target_font_file 或本页资源下载字体文件。N、V 目标应使用该文件对应的同一个 PDF 字体别名;I 目标应使用 Pillow 从选定文件加载的字体对象,不得加载其他字体文件。
中中文等不同文字体系的混合文本不得按文字体系使用不同字体:
整段译文中的全部字符必须统一使用该位置对应的选定字体文件,并保持统一基线、颜色、行高和视觉字高;
不得为汉字、拉丁字母、数字、标点或其他字符分别选用不同字体。
汉字、拉丁字母、数字、西文标点、英语标点、单位、型号、寄存器名、引脚名及其他保留的西文内容,
N、V 目标必须使用固定字体映射中该位置对应的 target_font_file;I 目标必须使用该位置选定的既有 target_font_file 或本页资源下载字体文件;
不得继续使用该位置原文字对象对应的比例西文字体或该页已有的其他西文字体资源。
同一句混合文本仍属于一个替换任务:
所有写入片段必须使用同一个选定字体和同一个整体缩放比例。
如果整行超出 bbox 或视觉容器,必须同步缩小整行所有写入片段;
不得只缩小英语内容或只缩小西文内容。
所有页面共用同一个公共资源目录。
Python 处理脚本位于:
精翻工程\page_XXX\render\
公共资源目录位于:
精翻工程_图像处理公共资源\
字体统一放在公共资源目录的 resources\fonts 子目录中。
不得把字体下载、复制或保存到当前页面的 render\resources 目录。
第十章字体资源下载区块中声明的字体,由外层程序在执行脚本前下载到此目录;脚本不得自行下载、复制或移动字体。
脚本必须使用以下代码定位公共资源目录:
pythonSCRIPT_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"。
本页需要处理并输出 Python 源代码时,脚本必须满足以下全部规则:
第一行必须是以下三者之一:
# apply_translation.py:路线 A、3C 或 3D;# apply_hybrid_image_translation.py:路线 H;# apply_raster_translation.py:路线 R。脚本末尾必须包含:
if name == "main":
main()
脚本接受且仅接受两个命令行参数:
--input 输入的单页 PDF 绝对路径,required=True
--output 输出的翻译后 PDF 绝对路径,required=True
调用方式固定为:
python "{脚本绝对路径占位符}" --input "{输入PDF绝对路径占位符}" --output "{输出PDF绝对路径占位符}"
--input 参数必须按照第八章执行输入检查。
所有本次操作中确定的参数,包括替换对列表、字体文件名、每处译文字符串,
全部以 Python 字面量形式硬编码在脚本内,不得读取外部配置文件。
所有页面必须直接共用公共字体资源,不得把公共原始字体复制到当前页面目录。附章 P 为 N、V 原生文本写入生成的本页临时 PDF-safe 派生字体除外;该临时字体只能存在于脚本运行期间使用的临时目录中,不得写回公共 FONT_DIR,也不得作为新的永久字体资源保存。公共资源目录与字体路径写法见 5.3。
脚本不得请求网络,不得调用任何 AI API,不得读取环境变量来获取编辑参数。
不得使用 subprocess 在脚本内部调用外部可执行文件。
依赖限制见 5.6。
路线 A、3C、3D,以及路线 H 中存在 N 或 V 原生文本写入时,在保存任何候选、临时或最终输出 PDF 之前,必须先对本次写入的嵌入字体执行子集化,
只保留 PDF 中实际使用的字符,然后使用
doc.save(output_path, garbage=4, deflate=True) 保存;
不得先保存后子集化,也不得静默跳过子集化失败。路线 R 不写入 PDF 字体,禁止为了满足本条而伪造字体子集化。
路线 A、3C、3D,以及路线 H 中存在 N 或 V 原生文本写入时,如果需要多次保存不同候选 PDF,则每一次保存之前都必须重新调用
doc.subset_fonts(fallback=True);不得仅在第一次保存前调用一次。路线 R 只需按 3.3.3 的 PDF 封装规则保存。
必须先保存到临时文件,并按第八章和第九章中适用于当前路线和处理器的规则完成检查;全部检查通过后使用 os.replace() 原子替换正式输出文件,失败时删除临时文件。
N、V 目标的译文中的全部字符只能使用固定映射表中的 target_font_file。I 目标可以使用既有 target_font_file,或本页字体资源下载区块中声明并由外层程序准备在 FONT_DIR 的字体文件。不得继续使用原 PDF 字体、PyMuPDF 内置 CJK 字体或其他内置字体作为兜底。
中中文等不同文字体系的混合文本必须统一使用该位置对应的同一个选定字体:
汉字、拉丁字母、数字、标点、单位、型号、寄存器名、引脚名、技术缩写及其他字符均不得使用其他字体;
如因换行、旋转、定位或其他排版需要拆成多个写入片段,各写入片段必须使用同一个 N、V target_font_file 或同一个 I 选定字体文件,并保持统一基线、颜色、行高和视觉字高。
混合文本超出 bbox 或视觉容器时,所有字符必须使用同一个整体缩放系数同步缩小;
不得分别独立缩放后再拼接。
第九章中能够通过 PyMuPDF、Pillow、fontTools 或 Python 标准库确定性完成的结果检查,应直接实现于生成脚本中。需要根据真实页面渲染结果进行视觉判断的检查,由你在本轮实际运行脚本后对真实输出 PDF 的渲染结果亲自完成;不得因为脚本内部无法使用 AI 或 OCR 而跳过第九章规定的视觉自检。
本节仅适用于通过 N 处理器或 V 处理器写入 PDF 原生文本的部分,包括路线 H 中的 N、V 处理部分;不适用于 I 处理器,也不适用于路线 R 中由 Pillow 写入图像像素的文字。
本页通过 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。字体别名必须与本页实际使用的 target_font_file 一一对应:
target_font_file 必须使用不同且稳定的字体别名;target_font_file 必须在本页所有位置复用同一个字体别名;TRFONT1、TRFONT2、TRFONT3 等别名;保存前必须执行:
doc.subset_fonts(fallback=True)
禁止使用不带 fallback=True 的 doc.subset_fonts()。
字体子集化完成后,必须保存到临时文件;临时输出的检查与原子替换按第八章和第九章执行。
脚本必须实际执行第九章规定的字体嵌入、字体子集化和最终结果验证。脚本正常完成后仍只打印输出路径和输出文件大小;第九章要求的字体名称、xref、字体流大小、子集状态、对象信息、像素差异等内部验证数据可以在内存中构造、比较并作为失败条件,不要求正常成功时打印。验证失败抛出的 RuntimeError 可以包含足以定位问题的诊断信息。
路线 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 中属于 N 或 V 的处理部分仍须逐条遵守 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 字体作为兜底。
本附章只负责 N、V 原生 PDF 文本写入前的字体预处理。它不负责字体选择,不改变 5.1 已确定的固定字体映射,不改变任何字体的视觉用途,不改变第三章规定的文字定位、bbox、字号、颜色、旋转或排版规则,也不替代 5.5 规定的 doc.subset_fonts(fallback=True)。
本附章适用于所有通过 N 处理器或 V 处理器写入 PDF 原生文本的位置,包括:
本附章不适用于:
对每个本页实际用于 N、V 写入的不同 target_font_file,必须按照以下固定顺序处理:
textFONT_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) ↓ 按照第五章、第八章和第九章保存、检查并交付
PDF-safe 字体预处理必须发生在该字体第一次传给 page.insert_font() 之前。不得先用原始 target_font_file 注册或写入文字,再对字体进行清洗。
FONT_DIR 中的原始 target_font_file 是阶段 0 已确定和准备的固定字体资源。脚本只能读取它,不得覆盖、修改、删除或原地保存该字体。
PDF-safe 字体必须是本页运行期间生成的临时派生文件。该临时文件仍然代表同一个固定 target_font_file 的视觉字体身份,不构成新的字体映射,也不得据此改变 5.1、5.2 中关于“同一个选定字体”的判断。
本节算法是基础设施规则,不是模型需要自行设计的字体处理策略。
模型不得根据页面内容、本页译文、语种、字体名称、主观判断或测试结果自行扩大、缩小或改变本算法的清洗范围;不得自己建立兼容汉字替换表;不得自行决定哪个兼容码位应删除。
所有页面必须使用同一套确定性算法。相同原始字体文件在不同页面中,即使本页实际使用的字符不同,也必须扫描和处理该原始字体自身的完整 Unicode cmap,不得只处理本页译文实际出现的字符。
生成脚本必须实现与下列代码逻辑一致的函数;可以调整局部变量名或函数组织方式,但不得改变任何判断条件、Unicode 范围、正规化形式、删除条件或执行顺序的语义:
pythonfrom 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}" )
本算法的删除条件必须同时满足:
U+F900~U+FAFF,或U+2F800~U+2FA1F;unicodedata.normalize("NFC", ...) 明确将该码位正规化为另一个单一 Unicode 码位;getBestCmap() 中,标准码位与兼容码位确实指向同一个 glyph_name。只有同时满足以上全部条件,才允许从该字体的 Unicode cmap 子表中删除兼容码位。
不得因为某个码位位于 U+F900~U+FAFF 或 U+2F800~U+2FA1F 就直接删除;如果它没有 NFC 单字符正规化结果,或者标准码位不存在,或者标准码位与兼容码位没有指向同一个 glyph,则必须保留。
不得把本算法扩展到其他 Unicode 范围。不得使用 NFKC、NFKD 或自定义替换表替代本节的 NFC 判断。
每个生成脚本必须为本页实际使用的每一个不同 target_font_file 最多生成一个临时 PDF-safe 字体。
相同 target_font_file 在本页多个 N、V 替换任务中必须复用同一个临时 PDF-safe 字体,不得为不同段落、不同字符、不同文字体系或不同替换位置重复生成。
不同的 target_font_file 必须分别独立生成各自的临时 PDF-safe 字体,不得把不同字体合并,不得让一种目标字体的清洗结果代替另一种字体。
临时 PDF-safe 字体应放入脚本运行期间创建的独立临时目录,例如通过 Python 标准库 tempfile.TemporaryDirectory() 管理。临时字体文件扩展名必须与原始字体保持一致。
临时 PDF-safe 字体只用于本页本次脚本执行,不得:
FONT_DIR 中的原始字体;FONT_DIR;font_mapping.json;target_font_file 的文件名;临时 PDF-safe 字体必须在成功和失败时清理。应优先使用 TemporaryDirectory()、上下文管理器或 try/finally 保证清理。
临时字体文件名可以根据原始 target_font_file 确定性生成,例如:
pythonsafe_name = ( source_font_path.stem + ".pdfsafe" + source_font_path.suffix )
临时文件名只用于当前脚本内部,不属于固定字体映射的一部分。
target_font_file 相同,清洗规则就必须完全相同。insert_text() / insert_textbox() 之前,必须先执行:pythonnormalized_text = unicodedata.normalize( "NFC", original_text, )
实际用于排版计算和写入 PDF 的必须是 NFC 规范化后的字符串,不得先按未规范化文本测量排版、最后才替换字符串。
如果同一句译文因换行、旋转、定位或其他排版原因需要拆为多个写入片段,应先对该完整译文执行 NFC,再基于规范化后的文本进行拆分。
NFC 规范化只改变 Unicode 标准明确规定的规范等价形式,不得使用 NFKC、NFKD、自定义字符替换表或其他兼容性折叠代替本规则。
本节只规定实际写入 N、V 原生 PDF 文本的译文内容。不得因为本节而修改第七章原文的匹配语义,不得自行改译文,不得改变型号、寄存器名、引脚名、数字、单位、标点或技术缩写的内容。
target_font_file 仍然完全由 5.1 的固定字体映射决定。PDF-safe 预处理不创建新的映射字体,不允许模型重新选择字体,也不允许因为清洗结果改变映射键或目标字体。
本规范其他章节中凡规定 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 字符都通过该别名写入
doc.subset_fonts(fallback=True) 是两个不同步骤:textPDF-safe 字体预处理 发生在 page.insert_font() 之前 负责消除符合 P.2 条件的 CJK 兼容码位与标准码位共享同一 glyph 时的反向 Unicode 歧义 doc.subset_fonts(fallback=True) 发生在文字写入之后、每次保存之前 负责对子集字体进行文档级字体子集化
不得认为已经执行 doc.subset_fonts(fallback=True) 就可以跳过附章 P,也不得因为执行了附章 P 而跳过 5.5 的字体子集化。
本页使用多个不同 target_font_file 时,每一个字体都必须独立执行 P.2。一个字体没有可删除的兼容映射,不代表其他字体可以跳过处理。
即使同一原始字体在不同单页 PDF 中最终经 doc.subset_fonts(fallback=True) 形成不同字体子集,也不得改变 P.2 的清洗规则。页面实际使用字符不同只允许导致最终字体子集内容不同,不允许导致同一兼容码位在不同页面被采用不同处理方法。
对附章 P,硬性禁止以下行为:
器→器、量→量、不→不 的人工字符替换表来代替 P.2;U+F900~U+FAFF 或 U+2F800~U+2FA1F 范围;FONT_DIR / target_font_file 原地修改或保存;target_font_file 在同一页生成多个不同清洗版本;本附章只负责为后续整本 PDF 收集阶段声明:本页通过 N、V 处理器向 PDF 原生文本层实际写入了哪些固定 target_font_file,以及每个字体实际写入了哪些 Unicode 文本。
本附章不改变当前单页 PDF 的生成、字体预处理、字体嵌入、字体子集化、保存、自检或交付方式。当前单页 Python 处理脚本仍必须完整执行第三章、第五章、附章 P、第八章和第九章规定的全部规则。
后续收集阶段可以使用本声明对同一个 target_font_file 在整本 PDF 中实际出现的字符进行汇总和去重,并结合阶段 4 实际生成的单页 PDF 中的真实字体对象、字符编码、ToUnicode、CIDToGIDMap、FontDescriptor、FontFile 等低层事实进行整本字体资源统一。模型不得自行推测这些保存后的低层 PDF 编码事实。
第十章新增的“全书字体使用”区块是声明性协议,不是新的页面处理路线,不参与 A、3C、3D、H、R 的判定,也不改变任何页面路线。
本声明只统计通过 N 处理器或 V 处理器实际写入 PDF 原生文本层的字符,包括:
以下内容不得计入本声明:
本声明的字体身份始终使用固定映射中的原始 target_font_file 文件名表示。
不得使用附章 P 生成的临时 PDF-safe 字体文件名代替 target_font_file,也不得使用下载 URL、字体内部名称、BaseFont 名称或 PDF 子集前缀作为全书字体身份。
每个实际用于 N/V 写入的 target_font_file 还必须声明本页 Python 处理脚本中对应的稳定 PDF 字体别名,即 5.1 和 5.5 所规定的 TRFONT1、TRFONT2、TRFONT3 等别名。该别名只用于让外层程序在真实单页输出 PDF 中定位本页由阶段 4 注册的对应字体资源,不改变 target_font_file 才是全书字体身份这一规则。
对每个本页实际用于 N/V 原生文本写入的不同 target_font_file,必须且只能生成一个字体使用条目。
每个条目的 text 必须来自本页 Python 处理脚本最终实际执行的 N/V 原生文本写入内容。
对所有 N/V 译文,必须先按照 P.4 对完整译文执行 NFC 规范化,再进行排版、拆分和实际写入。因此本声明中的 text 也必须使用与脚本实际写入完全相同的 NFC 规范化文本。
同一个 target_font_file 在本页用于多个替换任务时,text 必须按照 Python 处理脚本中这些 N/V 写入操作的实际执行顺序,将每一次实际写入的字符串直接串接起来。
如果同一句完整译文因换行、旋转、定位或其他排版原因拆成多个实际写入片段,则必须按照这些片段在 Python 处理脚本中的实际写入顺序,各计入一次,不得恢复成另一个自行推测的字符串,也不得遗漏任何实际写入片段。
text 必须保留实际写入内容中的全部字符,包括:
不得为了缩短声明而对 text 自行去重、排序、删减、摘要、归类、改写或只保留汉字。
同一个字符在本页实际写入多次时,必须按照实际写入结果保留多次。
后续收集阶段会自行按 target_font_file 对整本字符进行汇总和去重。
如果某个 target_font_file 仅被 I 处理器使用,而没有通过 N/V 处理器实际写入 PDF 原生文本,则不得因为 I 使用了该字体文件而把它列入本声明。
如果本页正常处理,但没有任何 N/V 原生文本字符实际写入,例如路线 R,或全部 N/V 替换任务的译文均为空字符串,则 fonts 必须为空数组。
本页可以正常处理时,第十章“全书字体使用”区块必须且只能包含一个合法 JSON 对象。
JSON 顶层结构固定为:
json{ "version": 1, "fonts": [ { "target_font_file": "字体文件名", "font_alias": "TRFONT1", "text": "该字体在本页实际写入的全部N/V文本" } ] }
具体规则:
顶层只能包含:
versionfontsversion 必须且只能为整数 1。
fonts 必须是 JSON 数组。
每个字体条目只能包含:
target_font_filefont_aliastexttarget_font_file 必须与本页 Python 处理脚本实际用于对应 N/V 写入的固定映射 target_font_file 文件名完全一致。
font_alias 必须与本页 Python 处理脚本实际通过 page.insert_font() 注册并在对应 N/V 写入中复用的稳定字体别名完全一致。
text 必须严格按照 Q.2 生成,不得输出原文、解释、统计数量、字符列表、Unicode 码位列表或其他辅助信息。
每个不同 target_font_file 必须且只能出现一次。
fonts 中各条目必须按照 target_font_file 文件名不区分大小写排序,确保相同页面输入产生稳定声明顺序。
如果没有任何 N/V 原生文本实际写入,固定输出:
json{"version":1,"fonts":[]}
本声明不得改变本页单页 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
完整执行。
不得因为输出了全书字体使用声明,就跳过:
doc.subset_fonts(fallback=True);garbage=4;deflate=True;os.replace() 原子交付。本声明中的 target_font_file 用于后续收集阶段按字体文件汇总整本字符使用集合。
本声明中的 font_alias 用于外层程序在本页 Python 处理脚本实际生成的单页 PDF 中定位对应的阶段 4 N/V 字体资源。
保存后的真实单页 PDF 可能经过 doc.subset_fonts(fallback=True) 形成新的 PDF 子集字体名称、内部字符码、CID、GID、ToUnicode、CIDToGIDMap、FontDescriptor、FontFile 或其他 PDF 字体结构。
这些保存后的低层事实必须由外层程序或第九章规定的实际输出验证逻辑从真实生成的单页 PDF 中读取,不得由模型根据脚本、原字体或经验猜测。
本声明不要求、也不得要求本页 Python 处理脚本提前知道整本 PDF 其他页面使用了哪些字符。
本声明不要求当前模型打开、读取、下载或分析整本 PDF。模型仍然只处理本轮第七章提供的真实单页 PDF。
后续收集阶段是否以及如何将同一个 target_font_file 的多个单页字体资源统一为整本共享字体资源,不属于当前单页 Python 处理脚本的职责,也不得因此修改当前页面的视觉排版或内容。
对附章 Q,硬性禁止以下行为:
fonts;fonts;fonts;fonts;target_font_file 拆成多个字体使用条目;text 自行去重、排序、摘要、删减或只保留汉字;target_font_file;target_font_file;font_alias 与 Python 处理脚本实际注册并写入时使用的字体别名不一致;页面路线与执行状态是两个独立维度。页面路线只有 A、3C、3D、H、R 五种,由 4.1 的载体组合唯一决定;执行状态描述本轮是否成功完成,不得改变已判定的页面路线。
特殊出口只有以下两个:
[无法下载PDF]
[本页无可映射字体]
null 的固定映射字体而无法完成时的执行失败标记,不是页面路线,也不得触发跨路线降级;N、V、I 载体集合判定的 A、3C、3D、H 或 R。I 类型目标不得使用本特殊出口;I 所需字体应优先复用固定映射资源,必要时在第十章字体资源下载区块中声明。
如果本页至少一个 N 或 V 类型的非空译文任务未能按照 5.1 确定非 null 的
target_font_file,四个输出区块都必须且只能输出
[本页无可映射字体]。
第八章或第九章检查失败不构成新的页面路线,也不构成新的特殊出口。只要不属于上述两个特殊出口,就必须在已经锁定的页面路线内修复明确发现的问题、重新执行脚本并重新检查,只有实际结果通过后才能按正常协议交付。
上海贝岭
SHANGHAI BELLING
BL0937B 内置时钟单相插座表计量芯片
BL0937B 是一颗宽量程单相多功能电能计量芯片,适用于单相插座表、单相插排、智能家电控制电路等应用,具有较高的性价比。
BL0937B 集成了 2 路高精度 Sigma-Delta ADC, 参考电压,电源管理等模拟电路模块,以及处理有功功率、电流电压有效值等电参数的数字信号处理电路。提供高频 CF1 用于指示电流/电压有效值,高频 CF 用于电能计量。
BL0937B 能够测量单相有功能量、有功功率、电流电压有效值等参数;能够充分满足插座表、单相插排、智能家电等领域的需要。
BL0937B 具有专利防潜动设计,配合合理的外部硬件设计,
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
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,
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
https://raw.githubusercontent.com/a2848273598/pdf-transcribe-images/main/BL0937B_V1_0_cn_e7705da55898_pdf_pages/2.pdf
第八章负责输入合法性和候选输出 PDF 的基础结构完整性检查。第九章负责目标内容是否正确实施、排版和视觉结果是否正确、字体是否正确嵌入与子集化、非目标内容是否被误伤,以及检查失败后的闭环修复与重新执行。
本章和第九章共同构成本规范的完整自检与验收体系。
必须在本轮实际提供的单页 PDF 上运行脚本。
--input 必须是绝对路径;任何一项不符,立即 raise RuntimeError 终止。
保存临时结果后,必须执行以下公共输出完整性检查:
通过本节只能证明候选输出 PDF 在基础结构上有效,不能替代第九章的内容、字体、排版、视觉和目标级结果检查。
只有 8.2 和第九章所有适用于当前路线的检查全部通过后,才能使用 os.replace() 原子替换为正式输出文件;验证失败时删除对应失败候选临时文件。
N 处理器完成写入并保存临时结果后,执行 8.2 的全路线公共输出完整性检查。
N 目标的原文是否正确删除、译文是否实际写入、是否可搜索可复制、译文 bbox、字体嵌入与子集化、渲染效果和非目标对象保护,继续按照第九章中适用于 N 的规则进行验证。
V 处理器完成写入并保存临时结果后,执行 8.2 的全路线公共输出完整性检查。
V 目标的原中文矢量轮廓是否正确删除、译文是否成功重建为原生文本、译文 bbox、字体嵌入与子集化、渲染效果和非目标矢量保护,继续按照第九章中适用于 V 的规则进行验证。
I-LOCAL 处理器完成写入并保存临时结果后,执行 8.2 的全路线公共输出完整性检查。
I-LOCAL 的原中文残留、译文视觉效果、补丁边缘、补丁外内容保护、线条和图形是否被误伤等,继续按照第九章中适用于 I-LOCAL 的规则进行验证。
I-FULL 处理器在将编辑后的整页图像封装为临时输出 PDF 前,必须逐像素验证所有硬编码允许编辑区域之外的像素与本次渲染输入图一致;如有变化,立即 raise RuntimeError。
该检查比较的是本次渲染输入图和编辑完成、尚未封装为 PDF 的整页图像,用于确保 Pillow 编辑过程没有越出允许区域。
完成 PDF 封装后执行 8.2 的全路线公共输出完整性检查。允许输出 PDF 只有一张显示位图和零可提取文本;不得因没有原生文本层而失败。
I-FULL 封装后的实际视觉结果、指定替换目标是否完整实施、原中文是否仍可见、译文是否清晰以及重新渲染后的整体页面是否正常,继续按照第九章进行验证。
每条页面路线执行以下基础检查:
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,同时完成适用于当前路线的确定性检查和视觉检查后,才能认为任务完成。
必须严格区分:
text内容审校: “译文应该翻成什么?” “页面上另一个未列出的中文是否也应翻译?” “是否应该自行改写审校译文?” → 不属于本任务,不得自行决定。 执行结果验证: “第七章给出的原文是否真的被正确替换?” “第七章给出的译文是否真的完整写入?” “是否越界、乱码、残影、漏字、错误字体、错误对象?” “是否误伤非目标内容?” → 属于本任务,必须检查。
“不自行充当审校器”绝不等于“不验证执行结果”。
第七章的译文内容本身不可由模型自行修改,但必须验证该译文是否完整、正确地落实到了真实输出 PDF。
自检范围只围绕第七章给出的替换任务以及完成这些替换所直接影响的页面对象展开。
不得因为结果自检而自行增加第七章之外的新翻译任务。
例如:
Master Drives NACK 替换为 主机驱动 NACK,则必须检查原来的 Master 和 Drives 已经不再残留,而译文明确保留的 NACK 仍然正确存在;结果自检分为两层:
凡是能够通过 PyMuPDF、Pillow、fontTools 或 Python 标准库可靠判断的事项,必须尽可能在生成的 Python 脚本中直接验证,包括但不限于:
确定性验证失败必须 raise RuntimeError,不得继续把失败候选交付为正式文件。
生成并运行脚本后,你必须重新打开实际候选输出 PDF,并至少以 200 DPI 渲染;涉及细小文字、图表标签、I-LOCAL、I-FULL 或复杂矢量目标时,优先使用 300~400 DPI。
必须亲自检查:
视觉检查必须基于真实执行后的候选输出,而不是只根据脚本代码推测“应该正确”。
对第七章中每一个属于 N 的替换目标,保存候选 PDF 后必须逐项验证:
重新打开候选输出 PDF。
在该目标原始 bbox 对应区域内重新提取文本。
若译文为非空字符串:
原始 N 文本对象必须已经按既定替换任务被删除。
实际写入译文的 bbox 必须严格位于该目标原始 bbox 内。
如仍超出,必须缩小整体字号、重新排版并重新生成。
如果译文拆为多个写入片段:
target_font_file;以至少 200 DPI、复杂或细小目标优先 300~400 DPI 渲染对应区域,检查:
任一 N 目标未通过以上检查,当前候选结果即失败,必须修改并重新执行。
对第七章中每一个属于 V 的替换目标,保存候选 PDF 后必须逐项验证:
原中文矢量轮廓必须实际被删除或可靠遮蔽,不得继续与译文重叠存在。
如果脚本采用完整 q ... Q 绘制分组删除:
如果脚本采用最小遮蔽 bbox:
非空译文必须作为原生 PDF 文本实际存在,并且:
target_font_file;以至少 200 DPI、复杂图形优先 300~400 DPI 渲染目标区域,检查:
如果译文包含审校明确保留的中文缩写、型号、数字或单位,这些字符不得被当作原轮廓残留。
任一 V 目标未通过以上检查,当前候选结果即失败,必须修改并重新执行。
对第七章中每一个属于 I 且由 I-LOCAL 处理的目标,保存候选 PDF 后必须逐项验证:
必须重新渲染候选输出 PDF 的目标 ROI,优先使用与编辑阶段一致或更高的 DPI。
原中文像素必须已经被真实擦除:
背景修补必须自然:
译文必须:
字体、字号、颜色、字重、对齐和视觉层级必须与原图协调。
对同一视觉层级的多个 I 目标,应检查其基准字号、字体和视觉风格是否保持一致;确因容器放不下而单独缩小的目标必须只做必要的最小幅度缩小。
补丁范围以外的页面内容必须继续按照 9.9 验证未被误伤。
不得因为目标 ROI 之外仍有第七章未列出的中文而判定失败。
任一 I-LOCAL 目标未通过以上检查,当前候选结果即失败,必须修改并重新执行。
路线 R 中,对第七章中每一个 I 目标必须逐项验证:
在 PDF 封装前,必须完成 8.6 规定的允许编辑区域外逐像素一致性检查。
封装候选 PDF 后,必须重新打开候选 PDF 并重新渲染真实输出页面。
对每个硬编码目标区域进行视觉检查:
必须检查整页重新渲染结果没有明显的缩放、拉伸、旋转、裁切、模糊异常或页面边缘错误。
输出 PDF 可以没有任何可提取文本,本路线不得因为零文本层而失败。
路线 R 不执行 N/V 的 PDF 字体检查。
不得因为目标区域之外存在第七章未列出的中文而把它加入翻译范围或判定失败。
任一 I-FULL 目标未通过以上检查,当前候选结果即失败,必须修改并重新执行。
除各处理器自己的目标检查外,所有路线都必须对实际候选输出执行公共视觉检查。
至少确认:
对于路线 A、3C、3D 和 H 中的 N/V 目标,还必须确认原生译文能够正确显示,而不是只在文本提取结果中存在但视觉不可见。
任何公共视觉检查未通过,都必须修改并重新执行。
本节适用于路线 A、3C、3D,以及路线 H 中实际存在 N 或 V 原生文本写入的部分。
调用 doc.subset_fonts(fallback=True) 没有抛出异常,不代表字体子集化已经成功。
保存候选临时 PDF 后,必须重新打开真实候选 PDF,并检查本次脚本新嵌入的全部目标字体。
每个本次实际用于 N/V 写入的不同 target_font_file 必须在脚本中只注册一次,并复用对应稳定字体别名。
每个本次新嵌入并完成子集化的目标字体,其保存后实际 PDF BaseFont 名称应包含六位大写字母子集前缀,例如:
textABCDEF+目标字体名称
应使用实际输出 PDF 中读取到的字体对象进行判断,不得只根据脚本调用 subset_fonts() 成功与否推测。
如果本次脚本新嵌入的任意目标字体在实际候选 PDF 中未表现为应有的子集字体,必须视为字体子集化验证失败并抛出 RuntimeError,不得交付该候选。
必须检查同一个目标字体没有出现多个不必要的新嵌入 xref。
相同 target_font_file 应复用同一个已注册字体资源,不得因多个替换目标反复嵌入完整字体。
必须检查本次新嵌入字体的实际字体流大小。
对单页 PDF,本次脚本新嵌入的任意一个目标字体流如果超过 2 MiB,
应视为疑似嵌入完整字体并抛出 RuntimeError,
除非本页确实使用了足以合理产生该大小的大量不同字形,并且能够从本页实际写入字符数量和真实字体资源结构明确证明该大小合理。
RuntimeError。发生该错误时,诊断信息应至少能够列出:
路线 H 含 I-LOCAL 图像补丁,文件体积增长可能来自局部补丁图像,因此不得仅因整个输出文件增加超过 5 MiB 就直接认定字体失败;但路线 H 中的每一个 N/V 目标字体仍必须执行本节关于子集字体名称、重复 xref 和字体流大小的检查。
路线 R 不执行本节。
不得仅依赖控制台是否出现 MuPDF 警告判断字体处理成功。
必须依据重新打开的真实候选输出 PDF 中的实际字体资源验证最终结果。
本节检查失败时,不得通过改用其他字体、改走其他页面路线或跳过子集化解决;必须修复当前路线的字体注册、PDF-safe 预处理、写入或子集化实现并重新执行。
结果正确不仅意味着目标被替换,也意味着不应修改的内容没有被误伤。
检查范围根据路线分别确定。
非目标内容保护检查的目的仅是发现本次替换是否误伤页面原有内容,不得借此把第七章之外的中文转化为新的翻译任务。
必须形成以下闭环:
text下载并打开真实 PDF ↓ 分析真实对象结构 ↓ 确定 N / V / I ↓ 锁定 A / 3C / 3D / H / R ↓ 生成页级专用脚本 ↓ 实际运行脚本 ↓ 生成候选临时 PDF ↓ 执行第八章基础检查 ↓ 执行第九章脚本内确定性检查 ↓ 重新打开真实候选 PDF ↓ 重新渲染并执行目标级视觉检查 ↓ 全部通过? ┌───────┴───────┐ 否 是 ↓ ↓ 定位真实失败原因 os.replace() 修改当前路线实现 ↓ 重新运行 正式输出 └──────→重新检查
如果任何适用检查失败:
允许修复的内容包括但不限于:
不得通过以下方式让检查“通过”:
脚本正常完成后仍按 5.5 只打印正式输出路径和输出文件大小。内部验证信息无需在成功时全部打印,但验证必须实际执行。
每条页面路线必须执行以下结果自检:
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 确定非 null 的 target_font_file,且所有 I 类型的非空译文任务均已选择固定映射资源或字体资源下载区块中的字体,并且真实执行结果已经通过第八章和第九章全部适用检查,则必须按正常协议输出字体资源下载命令、完整 Python 脚本、全书字体使用声明和固定执行命令行。
不得只生成脚本但不实际执行和检查后直接交付。
如果无法下载、打开并按 4.2 完成真实单页 PDF 的检查,四个区块都必须且只能输出
[无法下载PDF]。
如果本页至少一个 N 或 V 类型的非空译文任务未能按照 5.1 确定非 null 的 target_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处理脚本 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执行命令行 START]
(本页可以正常处理时,只输出下面这一行)
python "{脚本绝对路径占位符}" --input "{输入PDF绝对路径占位符}" --output "{输出PDF绝对路径占位符}"
(无法下载、打开并检查真实单页 PDF 时,此处必须且只能放置:[无法下载PDF])
(N 或 V 类型存在无可映射字体时,此处必须且只能放置:[本页无可映射字体])
[python执行命令行 END]
本页可以正常处理时,此区块必须且只能包含上面的 Python 命令行一行,其中:
如果本页至少一个 N 或 V 类型的非空译文任务未能按照 5.1 确定非 null 的
target_font_file,此区块必须且只能包含
[本页无可映射字体]。
如果无法下载、打开并检查真实单页 PDF,此区块必须且只能包含
[无法下载PDF]。
[字体资源下载 START] [字体资源下载 END] [python处理脚本 START]
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, }
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
textbest_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)
textif 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 必须是绝对路径")
textif 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") )
textmissing = [ 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 = {}
textfor 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, )
textif "".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"] )
textrect = 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]
textfor 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 )
textreplacement_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( "临时输出文件不存在或为空" )
texttry: 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 = []
textfor ( 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 )
textfor ( 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 )
texttry: 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, )
textif ( 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, )
textfd, 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()
textparser.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]