阿拉伯语 / RTL PDF 生成
朴素的 reportlab 代码生成的阿拉伯语存在微妙但彻底的错误:字母看起来断开,单词在一行内反向排列,段落内的行互换位置。这些错误在提取的文本中不可见——仅在渲染中可见。本技能包含一个已经解决了这些问题的构建器。
请使用 scripts/arabic_pdf.py,而不是从头编写整形逻辑。
下面的每种失败模式都是在实际文档中碰到的。
依赖
pip install reportlab arabic-reshaper python-bidi pypdfium2
pypdfium2 仅用于预览,但预览不是可选的——请参见下面的“验证”。
构建文档
通过将 scripts/arabic_pdf.py 复制到生成脚本旁边,或者将该技能的 scripts/ 文件夹添加到 sys.path 来导入构建器:
import pathlib, sys
sys.path.insert(0, str(pathlib.Path.home() / ".claude" / "skills" / "arabic-pdf" / "scripts"))
from arabic_pdf import ArabicPDF, preview
pdf = ArabicPDF("report.pdf", doc_title="عنوان المستند", page_numbers=True)
pdf.title("عنوان المستند")
pdf.subtitle("سطر فرعي")
pdf.heading("أولاً — المقدمة")
pdf.para("فقرة عربية تتضمن مصطلحات لاتينية مثل PostgreSQL و FastAPI.")
pdf.heading("ثانياً — جدول")
pdf.table(
["البند", "التفصيل"], # 最右列在前
[["الواجهة الخلفية", "Python — FastAPI"],
["قاعدة البيانات", "PostgreSQL — SQLAlchemy"]],
[5.0, 11.0], # 列宽(厘米)
)
pdf.bullets(["نقطة أولى", "نقطة ثانية"])
pdf.save()
preview("report.pdf") # 每页一张 PNG
如果技能安装在其他位置,请调整路径——构建器中的其他部分不依赖于其安装位置。
方法支持链式调用,因此 pdf.heading(...).para(...) 也可以工作。
| 方法 | 用途 |
|---|---|
title / subtitle | 居中的文档标题 |
heading | 右对齐的粗体章节标题 |
para | 正文段落 |
bullets(items, marker="–") | 每项右对齐的一行 |
table(header, rows, widths_cm) | RTL 表格;自然阅读顺序输入,列顺序反转输出 |
spacer(pts) / page_break() | 布局控制 |
save() | 写入文件,返回路径 |
单元格和段落文本中可以使用 \n 强制换行。
验证——始终渲染并查看
在 save() 之后,调用 preview() 并使用阅读工具查看 PNG。文本提取无法捕获 RTL 缺陷,因为所有字符都存在且正确;出错的只是它们的顺序和形状。请特别检查:
- 每个段落的第一个单词是否位于右上角?
- 拉丁语术语(
PostgreSQL、FastAPI)是否位于句子中应有的位置,而不是跳到行的最左边? - 字母是连在一起的,还是像
ا ل س ل ا م一样分开的? - 是否有实心黑色方块?字体缺少这些字形——请选择另一种字体。
- 括号方向是否正确——
(20 فأكثر)而不是)20 فأكثر(? - 拉丁术语内部的对是否完整——
Array<String>、DECIMAL(5,2)? - 是否有单个表格行被孤立到下一页?
修复一个你从未查找过的缺陷,在文档已经发送给接收者后,成本要高得多。
正确性如何实现
在调整构建器或调试异常文档时有用。
- 整形 —
arabic_reshaper将字符映射到位置呈现形式,使字母连接。 - 带有显式基础方向的 Bidi —
get_display(..., base_dir="R")。 如果没有base_dir,方向由第一个强字符推断,因此以拉丁语单词开头的行将按从左到右布局,之后的阿拉伯语会出错。这是“大部分正确但某些行混乱”的最常见原因。 - 手动换行 — 整形后的文本是视觉顺序。如果 reportlab 对其换行,它推到下一行的单词在逻辑上是第一个,因此行顺序被打乱。构建器先换行,分别整形每一行,并用
<br/>连接,这样 reportlab 就不会重新换行。8pt 的安全边距弥补了测量差异;溢出一点的行就可能使其反转。 - 镜像 —
get_display会重新排序,但从不应用 bidi 规则 L4,该规则规定以从右到左布局的镜像字符应绘制为其对应的字符。没有它,(20 فأكثر)会返回两个括号位置交换且使用原始字形,并打印为)20 فأكثر(。prepare()首先替换它们,因此重新排序会移动正确的字形。 - 拉丁语孤岛 —
<和>是镜像字符,但不是 Unicode 配对括号,因此保持DECIMAL(5,2)完整的规则不涵盖它们;Array<String>中的>会采用其后阿拉伯语的方向并被扔到最远端。每个拉丁语片段被包裹在LRI…PDI中,并在重新排序后再次移除隔离符,因此没有任何不可见的内容到达 PDF。 - 整形后进行转义 — reportlab 需要对
&、<、>进行转义,但先转义会将<作为四个字符发送给 bidi 处理,其尾部的;会被排到行的最远端。先整形,再转义。 - 反转的表格列 — reportlab 从左到右布局列,因此构建器反转它们,使您可以按自然阅读顺序传递列。
- 表格上的
KeepTogether— 防止单行被孤立到新页面上。
第 4-6 点与 arabic-docx 中描述的分割括号运行属于同一类缺陷,只是从相反方向实现:在那里,一对括号不得跨运行分割;在这里,一对括号不得被部分镜像。任何镜像字符都必须与其配对字符一起处理。
字体
register_fonts() 搜索支持阿拉伯语的字体:Windows 上为 Arial 然后是 Tahoma,macOS 上为 Arial,Linux 上为 Noto Naskh Arabic 然后是 DejaVu Sans。要使用特定字体(Amiri 和 Cairo 适合正式文档):
from arabic_pdf import register_fonts
register_fonts("/path/Amiri-Regular.ttf", "/path/Amiri-Bold.ttf") # 在 ArabicPDF(...) 之前
Reportlab 的内置字体没有阿拉伯语覆盖,因此必须注册 TTF——这就是为什么缺少字体会显示为黑色方块而不是错误。
切勿将此方法用于 Word 或 PowerPoint
这是整个 RTL 技能系列中最重要的界限。
| reportlab / Pillow | Word / PowerPoint | |
|---|---|---|
| 文本引擎 | 无 | 完整的双向引擎 |
| 期望 | 最终的视觉顺序字形 | 原始逻辑顺序 Unicode |
| 技术 | 整形 + bidi + 手动换行 | XML 中的方向标志 |
预整形在这里是必需的,而在那里是破坏性的:Word 和 PowerPoint 在渲染时整形,因此提供呈现形式会使它们第二次整形——导致字形损坏,并且文本无法再搜索、复制或拼写检查。这两种方法是相反的,而不是变体。
对 .docx 使用 arabic-docx,对 .pptx 使用 arabic-pptx。在没有重新阅读它们为什么不同之前,不要在这三者之间移植代码。
说明
- 当需要阿拉伯-印度数字时,
arabic_digits("38")→"٣٨"。纯装饰性;西方数字在阿拉伯语技术写作中完全正常。 - 默认情况下保持格式简洁——黑色文本,细线。学术和官方阿拉伯语文档读起来更可信,而且要求“简单”的用户就是这么想的。
- 同样的机制也适用于波斯语、乌尔都语和希伯来语;只有字体需要覆盖该文字。
是什么让生成的阿拉伯语读起来像机器生成的
不是词汇——而是添加的内容。以下每种习惯都告诉读者一些关于文档是如何编写的信息,而不是其主题;手工编辑的读者会删除所有这些习惯。写句子,而不是写关于如何写句子的描述。
| 习惯 | 而不是 | 请写 |
|---|---|---|
| 叙述写作行为 | «الشروط ثلاثة، نذكرها صراحة فيما يلي» | «الشروط ثلاثة:» |
| 指向另一个章节 | «…ونعود إلى هذه النقطة لاحقاً» | 删除该短语 |
| 标题中的版本或状态标签 | «الخطة (النسخة الثانية — معتمدة)» | «الخطة» |
| 针对没人提出的选项进行争论 | «نحفظ المسار فقط. تخزين الملف كاملاً يضخّم الحجم بلا فائدة.» | «نحفظ المسار فقط.» |
| 通过简报未提及的内容来证明选择的合理性 | «لم يرد ذلك في الطلب، غير أن طبيعة العمل تفرضه.» | «طبيعة العمل تفرض ذلك.» |
| 第一人称单数 | «وأستطيع لاحقاً تحليل الحالات» | «ويمكن لاحقاً تحليل الحالات» |
| 重复文档已经给出的事实 | 在另一个部分重复信息 | 删除重复 |
正式阿拉伯语更喜欢非人称或复数形式而非 «أنا»;单数形式读起来像给自己的笔记,而不是一个文档。