阿拉伯文 / 從右到左 (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() 並使用 Read 工具查看 PNG 圖片。文字提取無法捕捉 RTL 缺陷,因為所有字元都正確存在;錯誤的只是它們的順序和形狀。請特別檢查:
- 每個段落的第一個單詞是否位於右上角?
- 拉丁術語(
PostgreSQL、FastAPI)是否位於句中應在的位置,而不是跳到行的最左邊? - 字母是否連接在一起,還是分開像
ا ل س ل ا م? - 是否有任何實心黑色方塊?表示該字型缺少這些字符——請更換字型。
- 括號方向是否正確——
(20 فأكثر)而不是)20 فأكثر(? - 拉丁術語內部的配對是否完整——
Array<String>、DECIMAL(5,2)? - 表格的某一行是否被孤立到下一頁?
修復一個你從未檢查過的缺陷,日後當文件已經送達收件人時,代價會高得多。
如何達成正確性
在改寫建構器或除錯異常文件時很有用。
- 塑形 (Shaping)——
arabic_reshaper將字元映射到位置相關的呈現形式,使字母得以連接。 - 明確指定基底方向的雙向處理 (Bidi)——
get_display(..., base_dir="R")。若不指定base_dir,方向會從第一個強字元推斷,因此以拉丁單詞開頭的行會從左到右排列,後面的阿拉伯文就會錯位。這是「大部分正確但某些行錯亂」最常見的原因。 - 手動換行——重塑後的文字是視覺順序。如果 reportlab 對其換行,它推到下一行的單詞反而是邏輯上第一個,因此各行會被打亂。建構器先換行,再對每行分別重塑,然後用
<br/>連接,這樣 reportlab 就不會再重新換行。我們使用 8pt 的安全邊距來補償測量差異;哪怕多一個點的溢出都足以反轉該行。 - 鏡像處理 (Mirroring)——
get_display會重新排序,但從不套用 bidi 規則 L4,該規則規定從右到左排列的鏡像字元應以其配對字元繪製。沒有這個處理,(20 فأكثر)會變成兩個括號位置互換且使用原始字形,因此印出)20 فأكثر(。prepare()會先替換它們,這樣重新排序時就能移動到正確的字形。 - 拉丁文字島 (Latin islands)——
<和>是鏡像字元,但不是 Unicode 配對括號,因此保持DECIMAL(5,2)完整的規則不適用於它們;Array<String>中的>會採用其後阿拉伯文的方向,而被扔到最遠的一側。每個拉丁文字段都被包裹在LRI…PDI中,並在重新排序後移除這些隔離符,因此沒有不可見的東西進入 PDF。 - 塑形後再跳脫字元——reportlab 需要跳脫
&、<、>,但如果先跳脫,<會作為四個字元通過雙向處理,其尾端的;會被丟到行的最遠一側。正確順序是先塑形,再跳脫。 - 表格欄位反向——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 |
| 處理技術 | 塑形 + 雙向處理 + 手動換行 | XML 中的方向標記 |
在此處預先塑形是必要的,但在那裡則是破壞性的:Word 和 PowerPoint 在渲染時才進行塑形,因此給予它們呈現形式會導致它們進行第二次塑形——產生破碎的字形,且文字無法再被搜尋、複製或進行拼字檢查。這兩種方法完全相反,而非僅是變體。
請對 .docx 使用 arabic-docx,對 .pptx 使用 arabic-pptx。在未重新閱讀它們為何不同的情況下,請勿在這三者之間移植程式碼。
備註
arabic_digits("38")→"٣٨"(當需要阿拉伯-印度數字時)。純屬裝飾性;西方式數字在阿拉伯技術寫作中完全正常。- 預設情況下請保持格式簡潔——黑色文字、細線條。學術和正式阿拉伯文件這樣讀起來更可信,要求「簡單」的使用者通常就是這個意思。
- 同樣的機制也適用於波斯文、烏爾都文和希伯來文;只需要字型能支援這些書寫系統即可。
什麼讓產生的阿拉伯文讀起來像機器生成的
不是詞彙——而是那些添加物。以下每個習慣都在告訴讀者文件是如何寫成的,而非文件的主題是什麼,而手動編輯的讀者會全部刪除它們。寫句子本身,而不是寫關於寫句話的說明。
| 習慣 | 不該這樣寫 | 應該這樣寫 |
|---|---|---|
| 敘述寫作行為 | «الشروط ثلاثة، نذكرها صراحة فيما يلي» | «الشروط ثلاثة:» |
| 指向另一個章節 | «…ونعود إلى هذه النقطة لاحقاً» | 刪除該句 |
| 在標題中加入版本或狀態標籤 | «الخطة (النسخة الثانية — معتمدة)» | «الخطة» |
| 反駁一個沒人提出的選項 | «نحفظ المسار فقط. تخزين الملف كاملاً يضخّم الحجم بلا فائدة.» | «نحفظ المسار فقط.» |
| 用需求規格書沒提到的內容來為選擇辯護 | «لم يرد ذلك في الطلب، غير أن طبيعة العمل تفرضه.» | «طبيعة العمل تفرض ذلك.» |
| 第一人稱單數 | «وأستطيع لاحقاً تحليل الحالات» | «ويمكن لاحقاً تحليل الحالات» |
| 重複文件已提供的資訊 | 在其他章節重複資訊 | 刪除重複內容 |
正式阿拉伯文偏好非人稱或複數形式,而非「أنا」;單數形式讀起來像是給自己的筆記,而非正式文件。