아랍어 / 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)? - 테이블 행 하나가 다음 페이지로 분리되지 않았는가?
나중에 결함을 찾아 수정하는 비용은 문서가 이미 수신자에게 전달된 후에 훨씬 더 큽니다.
올바름을 달성하는 방법
빌더를 수정하거나 특이한 문서를 디버깅할 때 유용합니다.
- 모양 형성(shaping) —
arabic_reshaper가 문자를 위치 기반 표시 형태로 매핑하여 글자가 연결되도록 합니다. - 명시적 기본 방향이 있는 양방향 처리 —
get_display(..., base_dir="R").base_dir이 없으면 첫 번째 강한 문자에서 방향이 추론되므로, 라틴어 단어로 시작하는 줄은 왼쪽에서 오른쪽으로 배치되고 뒤의 아랍어가 잘못 위치합니다. 이는 "대부분 맞지만 일부 줄이 뒤섞인" 경우의 가장 흔한 원인입니다. - 수동 줄바꿈 — 재구성된 텍스트는 시각적 순서입니다. reportlab이 줄바꿈하면 다음 줄로 밀려나는 단어는 논리적으로 첫 번째 단어이므로 줄이 뒤섞입니다. 빌더가 먼저 줄바꿈하고, 각 줄을 별도로 재구성한 후
<br/>로 결합하여 reportlab이 다시 줄바꿈하지 않도록 합니다. 8pt 안전 여백으로 측정 차이를 보상하며, 1pt만 넘쳐도 줄이 역전될 수 있습니다. - 미러링(mirroring) —
get_display는 순서를 재정렬하지만 양방향 규칙 L4를 적용하지 않습니다. 이 규칙은 오른쪽에서 왼쪽으로 배치된 미러링 문자가 대응 문자로 그려지도록 합니다. 이 규칙 없이(20 فأكثر)는 두 괄호의 위치가 바뀌고 원래 글리프로 반환되어)20 فأكثر(로 출력됩니다.prepare()가 먼저 대체하여 재정렬이 올바른 글리프를 이동시키도록 합니다. - 라틴어 섬(island) —
<와>는 미러링되지만 유니코드 쌍 괄호가 아니므로DECIMAL(5,2)를 유지하는 규칙이 적용되지 않습니다.Array<String>의>는 뒤의 아랍어 방향을 따라 맨 왼쪽으로 이동합니다. 각 라틴어 범위를LRI…PDI로 감싸고 재정렬 후 격리 문자를 제거하여 PDF에 아무것도 보이지 않도록 합니다. - 재구성 후 이스케이프 — reportlab은
&,<,>를 이스케이프해야 하지만, 먼저 이스케이프하면<가 4개의 문자로 양방향 패스에 전달되고 마지막;가 줄 맨 오른쪽으로 밀려납니다. 먼저 모양을 형성한 후 이스케이프합니다. - 테이블 열 반전 — reportlab은 열을 왼쪽에서 오른쪽으로 배치하므로, 빌더가 열을 반전하여 자연스러운 읽기 순서로 열을 전달할 수 있게 합니다.
- 테이블에
KeepTogether적용 — 단일 행이 새 페이지로 분리되는 것을 방지합니다.
포인트 4~6은 arabic-docx에 설명된 분할 괄호 실행(run)과 동일한 결함 클래스이지만, 반대 방향에서 접근합니다. 거기서는 쌍이 실행 간에 분리되지 않아야 하고, 여기서는 쌍이 절반만 미러링되지 않아야 합니다. 모든 미러링 문자는 파트너 문자와 함께 처리되어야 합니다.
폰트
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 | |
|---|---|---|
| 텍스트 엔진 | 없음 | 완전한 양방향 엔진 |
| 입력 기대값 | 최종 시각적 순서 글리프 | 원시 논리적 순서 유니코드 |
| 기술 | 재구성 + 양방향 + 수동 줄바꿈 | XML의 방향 플래그 |
사전 모양 형성은 여기서 필수이지만, 저기서는 파괴적입니다. Word와 PowerPoint는 렌더링 시 모양을 형성하므로, 표시 형태를 전달하면 두 번째로 모양이 형성되어 글리프가 깨지고, 텍스트를 검색, 복사, 맞춤법 검사할 수 없게 됩니다. 두 접근 방식은 반대이며, 변형이 아닙니다.
.docx에는 arabic-docx를, .pptx에는 arabic-pptx를 사용하세요. 두 방식이 왜 다른지 다시 읽지 않고 세 방식 간에 코드를 포팅하지 마세요.
참고 사항
arabic_digits("38")→ 아랍-인도 숫자가 필요할 때"٣٨"입니다. 순전히 미용상의 차이입니다. 서양 숫자는 아랍어 기술 문서에서 완전히 정상적입니다.- 기본적으로 서식을 절제하세요 — 검은색 텍스트, 얇은 선. 학술 및 공식 아랍어 문서는 그렇게 더 신뢰할 수 있어 보이며, "간단한" 문서를 원하는 사용자는 그 의미를 알고 있습니다.
- 동일한 메커니즘이 페르시아어, 우르두어, 히브리어도 처리합니다. 폰트만 해당 문자를 지원하면 됩니다.
생성된 아랍어를 생성된 것처럼 보이게 만드는 요소
어휘가 아닙니다 — 추가된 내용입니다. 아래 각 습관은 문서의 주제보다는 어떻게 작성되었는지에 대해 독자에게 알려주며, 수동 편집자는 이 모두를 삭제합니다. 문장을 쓰되, 문장을 쓰는 과정에 대한 설명을 쓰지 마세요.
| 습관 | 대신 | 작성 |
|---|---|---|
| 글쓰기 행위를 서술 | «الشروط ثلاثة، نذكرها صراحة فيما يلي» | «الشروط ثلاثة:» |
| 다른 섹션을 가리킴 | «…ونعود إلى هذه النقطة لاحقاً» | عبارة احذف |
| 제목에 버전/상태 레이블 | «الخطة (النسخة الثانية — معتمدة)» | «الخطة» |
| 아무도 제기하지 않은 옵션에 반박 | «نحفظ المسار فقط. تخزين الملف كاملاً يضخّم الحجم بلا فائدة.» | «نحفظ المسار فقط.» |
| 요구사항에 누락된 내용으로 결정을 정당화 | «لم يرد ذلك في الطلب، غير أن طبيعة العمل تفرضه.» | «طبيعة العمل تفرض ذلك.» |
| 1인칭 단수 | «وأستطيع لاحقاً تحليل الحالات» | «ويمكن لاحقاً تحليل الحالات» |
| 문서가 이미 제공한 사실을 반복 | 같은 정보를 다른 섹션에서 반복 | 반복 제거 |
공식 아랍어는 «أنا»보다 비인칭 또는 복수를 선호합니다. 단수형은 문서가 아닌 개인 메모처럼 읽힙니다.