アラビア語 / 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], # 列幅(cm)
)
pdf.bullets(["نقطة أولى", "نقطة ثانية"])
pdf.save()
preview("report.pdf") # ページごとに1つのPNG
スキルが別の場所にある場合はパスを調整してください。ビルダーの他の部分はインストール場所に依存しません。
メソッドはチェーン可能で、pdf.heading(...).para(...) のようにも記述できます。
| メソッド | 目的 |
|---|---|
title / subtitle | 中央揃えのドキュメント見出し |
heading | 右揃えの太字セクション見出し |
para | 本文段落 |
bullets(items, marker="–") | 各項目を右揃えで1行ずつ表示 |
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)? - テーブルの単一の行が次のページに孤立していませんか?
確認しなかった欠陥を後で修正するのは、ドキュメントがすでに受信者の手元にある場合、はるかにコストがかかります。
正しさを達成する方法
ビルダーを適応させたり、特殊なドキュメントをデバッグしたりする際に役立ちます。
- シェイピング —
arabic_reshaperが文字を位置別の表示形態にマッピングし、文字が結合するようにします。 - 明示的なベース方向を用いた双方向テキスト処理 —
get_display(..., base_dir="R")を使用します。base_dirがないと、方向は最初の強い文字から推測されるため、ラテン語の単語で始まる行は左から右にレイアウトされ、後続のアラビア語が間違った位置に表示されます。これが「ほとんど正しいが、一部の行がスクランブルされる」という最も一般的な原因です。 - 手動による行折り返し — リシェイプされたテキストは視覚的な順序になっています。reportlabがこれを折り返すと、次の行に送られる単語は論理的に先頭の単語になるため、行がシャッフルされてしまいます。ビルダーは最初に折り返し、各行を個別にリシェイプし、
<br/>で結合することで、reportlabが再折り返しを行わないようにします。8ptの安全マージンで計測誤差をカバーしています。1ポイントでもオーバーフローすると行が逆転する可能性があります。 - ミラーリング —
get_displayは並べ替えを行いますが、双方向テキストルールL4(右から左にレイアウトされるミラー文字は対応するパートナーとして描画される)は適用しません。これがないと、(20 فأكثر)は2つの括弧の位置が入れ替わり、元のグリフのままになり、)20 فأكثر(と印刷されます。prepare()が最初にそれらを置換し、並べ替えによって正しいグリフが移動するようにします。 - ラテン語アイランド —
<と>はミラー文字ですが、Unicodeの対になった括弧ではないため、DECIMAL(5,2)をまとめておくルールはこれらには適用されません。Array<String>の>は後続のアラビア語の方向を取り、遠くの端に投げ出されます。各ラテン語スパンはLRI…PDIで囲まれ、並べ替え後にアイソレートが除去されるため、PDFに不可視の文字が残ることはありません。 - シェイピング後のエスケープ — reportlabは
&,<,>のエスケープを必要としますが、先にエスケープすると、<が双方向テキストパスを4文字として通過し、末尾の;が行の遠端に排出されます。シェイプしてからエスケープします。 - テーブル列の逆順 — 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では破壊的です。WordとPowerPointは描画時にシェイプするため、表示形態を渡すと二重にシェイプされ、グリフが壊れ、テキストの検索、コピー、スペルチェックができなくなります。2つのアプローチは正反対であり、バリエーションではありません。
.docx には arabic-docx を、.pptx には arabic-pptx を使用してください。なぜそれらが異なるのかを再確認せずに、コードを3つの間で移植しないでください。
注意点
arabic_digits("38")は、アラビア・インド数字が必要な場合に"٣٨"を返します。純粋に外観上の問題です。西洋数字はアラビア語の技術文書では完全に普通です。- デフォルトでは書式を控えめに保ってください — 黒いテキスト、細い罫線。学術的および公式のアラビア語文書はその方が信頼性が高く、ユーザーが「シンプル」を求めているなら、それは本気です。
- 同じ仕組みでペルシャ語、ウルドゥー語、ヘブライ語も処理できます。フォントがそのスクリプトをカバーしていることだけが必要です。
生成されたアラビア語が生成されたと読者に思わせるもの
語彙の問題ではありません — 追加されたものの問題です。以下の各習慣は、読者にドキュメントの主題についてではなく、どのように書かれたかについて何かを伝えます。手作業で編集する読者はこれらをすべて削除します。文章を書きなさい。それを書いたという記述を書いてはいけません。
| 習慣 | 使う代わりに | こう書く |
|---|---|---|
| 執筆行為を語る | «الشروط ثلاثة، نذكرها صراحة فيما يلي» | «الشروط ثلاثة:» |
| 別のセクションを指す | «…ونعود إلى هذه النقطة لاحقاً» | そのフレーズを削除 |
| タイトルにバージョンやステータスラベル | «الخطة (النسخة الثانية — معتمدة)» | «الخطة» |
| 誰も挙げていない選択肢に対して反論 | «نحفظ المسار فقط. تخزين الملف كاملاً يضخّم الحجم بلا فائدة.» | «نحفظ المسار فقط.» |
| 指示に含まれていなかったことで選択を正当化 | «لم يرد ذلك في الطلب، غير أن طبيعة العمل تفرضه.» | «طبيعة العمل تفرض ذلك.» |
| 一人称単数 | «وأستطيع لاحقاً تحليل الحالات» | «ويمكن لاحقاً تحليل الحالات» |
| ドキュメントがすでに示した事実を繰り返す | 別のセクションで情報を繰り返す | 繰り返しを削除 |
正式なアラビア語では、«أنا»ではなく非人称または複数形が好まれます。単数形はドキュメントではなく、自分自身へのメモとして読まれます。