Generación de PDF en árabe / RTL
El código ingenuo de Reportlab produce un árabe sutil y profundamente incorrecto: las letras aparecen desconectadas, las palabras dentro de una línea corren hacia atrás y las líneas dentro de un párrafo intercambian lugares. Nada de esto es visible en el texto extraído, solo en una representación visual. Este skill incluye un constructor que ya tiene estos problemas resueltos.
Usa scripts/arabic_pdf.py en lugar de escribir lógica de reshaping desde cero.
Cada modo de falla a continuación se encontró al probarlo en un documento real.
Dependencias
pip install reportlab arabic-reshaper python-bidi pypdfium2
pypdfium2 solo es necesario para la vista previa, pero la vista previa no es opcional; consulta
Verificación a continuación.
Construir un documento
Importa el constructor copiando scripts/arabic_pdf.py junto a tu
script de generación, o agregando la carpeta scripts/ de este skill a 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(
["البند", "التفصيل"], # primera columna a la derecha
[["الواجهة الخلفية", "Python — FastAPI"],
["قاعدة البيانات", "PostgreSQL — SQLAlchemy"]],
[5.0, 11.0], # anchos de columna en cm
)
pdf.bullets(["نقطة أولى", "نقطة ثانية"])
pdf.save()
preview("report.pdf") # un PNG por página
Ajusta la ruta si el skill se encuentra en otro lugar; nada más en el constructor depende de dónde esté instalado.
Los métodos se encadenan, por lo que pdf.heading(...).para(...) también funciona.
| Método | Propósito |
|---|---|
title / subtitle | Encabezado centrado del documento |
heading | Encabezado de sección en negrita alineado a la derecha |
para | Párrafo del cuerpo |
bullets(items, marker="–") | Una línea alineada a la derecha por elemento |
table(header, rows, widths_cm) | Tabla RTL; orden de lectura natural de entrada, columnas invertidas de salida |
spacer(pts) / page_break() | Control de diseño |
save() | Escribe el archivo, devuelve la ruta |
El texto de las celdas y párrafos puede contener \n para forzar un salto de línea.
Verificación: siempre renderiza y observa
Después de save(), llama a preview() y revisa los PNG con la herramienta Read. La extracción de texto
no puede detectar defectos RTL, porque los caracteres están todos presentes y correctos;
solo su orden y forma son incorrectos. Verifica específicamente:
- ¿La primera palabra de cada párrafo está en la parte superior derecha?
- ¿Los términos latinos (
PostgreSQL,FastAPI) están donde deben estar en medio de la oración, en lugar de saltar al extremo izquierdo de la línea? - ¿Las letras están unidas o separadas como
ا ل س ل ا م? - ¿Aparecen cuadros negros sólidos? La fuente carece de esos glifos; elige otra.
- ¿Los corchetes están orientados correctamente?
(20 فأكثر)no)20 فأكثر(? - ¿Los pares dentro de términos latinos están intactos?
Array<String>,DECIMAL(5,2)? - ¿Una fila de tabla quedó huérfana en la página siguiente?
Arreglar un defecto que nunca buscaste cuesta mucho más tarde, cuando el documento ya está con su destinatario.
Cómo se logra la corrección
Útil al adaptar el constructor o depurar un documento extraño.
- Shaping —
arabic_reshaperasigna a los caracteres formas de presentación posicionales para que las letras se conecten. - Bidi con dirección base explícita —
get_display(..., base_dir="R"). Sinbase_dir, la dirección se infiere del primer carácter fuerte, por lo que una línea que comienza con una palabra latina se diseña de izquierda a derecha y el árabe después de ella queda mal. Esta es la causa más común de "mayormente correcto pero algunas líneas están desordenadas". - Ajuste de línea manual — el texto reshaped está en orden visual. Si Reportlab
lo ajusta, las palabras que empuja a la siguiente línea son las lógicamente primeras,
por lo que las líneas salen mezcladas. El constructor ajusta primero, reshapea cada línea
por separado y las une con
<br/>para que Reportlab nunca reajuste. Un margen de seguridad de 8pt cubre diferencias de medición; una línea que desborda por un punto es suficiente para invertirla. - Espejado —
get_displayreordena pero nunca aplica la regla bidi L4, que dice que un carácter reflejado diseñado de derecha a izquierda se dibuja como su contraparte. Sin ella,(20 فأكثر)vuelve con los dos paréntesis en posiciones intercambiadas y sus glifos originales, e imprime)20 فأكثر(.prepare()los sustituye primero, por lo que el reordenamiento mueve el glifo correcto. - Islas latinas —
<y>están reflejados pero no son corchetes emparejados en Unicode, por lo que la regla que mantieneDECIMAL(5,2)unido no los cubre; el>deArray<String>toma la dirección del árabe que lo sigue y se lanza al lado lejano. Cada tramo latino se envuelve enLRI…PDIy los aislantes se eliminan nuevamente después del reordenamiento, para que nada invisible llegue al PDF. - Escapado después del shaping — Reportlab necesita que
&,<,>se escapen, pero escapar primero envía<a través del paso bidi como cuatro caracteres y su;final se expulsa al lado lejano de la línea. Primero se reshapea, luego se escapa. - Columnas de tabla invertidas — Reportlab coloca las columnas de izquierda a derecha, por lo que el constructor las invierte, permitiéndote pasar las columnas en orden de lectura natural.
KeepTogetheren tablas — evita que una fila solitaria quede varada en una nueva página.
Los puntos 4–6 son la misma clase de defecto que las ejecuciones de corchetes divididos descritas en
arabic-docx, abordados desde la dirección opuesta: allí un par no debe dividirse entre
ejecuciones, aquí un par no debe reflejarse a medias. Cualquier carácter reflejado
debe resolverse junto con su contraparte.
Fuentes
register_fonts() busca una fuente compatible con árabe: Arial luego Tahoma en
Windows, Arial en macOS, Noto Naskh Arabic luego DejaVu Sans en Linux. Para usar una
fuente específica (Amiri y Cairo son adecuadas para documentos formales):
from arabic_pdf import register_fonts
register_fonts("/ruta/Amiri-Regular.ttf", "/ruta/Amiri-Bold.ttf") # antes de ArabicPDF(...)
Las fuentes integradas de Reportlab no tienen cobertura de árabe, por lo que se debe registrar un TTF; esta es la razón por la que una fuente faltante se muestra como cuadros negros en lugar de un error.
Nunca lleves este enfoque a Word o PowerPoint
Este es el límite más importante en toda la familia de habilidades RTL.
| Reportlab / Pillow | Word / PowerPoint | |
|---|---|---|
| Motor de texto | ninguno | motor bidireccional completo |
| Espera | glifos finales en orden visual | Unicode en orden lógico sin procesar |
| Técnica | reshape + bidi + ajuste manual | indicadores de dirección en el XML |
El pre-shaping es requerido aquí y destructivo allí: Word y PowerPoint shapesan en tiempo de renderizado, por lo que entregarles formas de presentación hace que eshapeen una segunda vez: glifos rotos y texto que ya no se puede buscar, copiar ni revisar ortográficamente. Los dos enfoques son opuestos, no variaciones.
Usa arabic-docx para .docx y arabic-pptx para .pptx. No portes código
entre los tres sin releer por qué difieren.
Notas
arabic_digits("38")→"٣٨"cuando se desean números arábigo-indios. Puramente cosmético; los dígitos occidentales son perfectamente normales en la escritura técnica árabe.- Mantén el formato moderado por defecto: texto negro, reglas finas. Los documentos académicos y oficiales en árabe se leen como más creíbles de esa manera, y los usuarios que piden "simple" lo dicen en serio.
- El mismo mecanismo maneja persa, urdu y hebreo; solo la fuente debe cubrir la escritura.
Qué hace que el árabe generado se lea como generado
No el vocabulario, sino las adiciones. Cada hábito a continuación le dice al lector algo sobre cómo se escribió el documento en lugar de sobre su tema, y un lector que edita a mano los elimina todos. Escribe la oración, no el relato de escribirla.
| Hábito | En lugar de | Escribe |
|---|---|---|
| Narrar el acto de escribir | «الشروط ثلاثة، نذكرها صراحة فيما يلي» | «الشروط ثلاثة:» |
| Señalar otra sección | «…ونعود إلى هذه النقطة لاحقاً» | احذف العبارة |
| Etiquetas de versión o estado en un título | «الخطة (النسخة الثانية — معتمدة)» | «الخطة» |
| Argumentar contra una opción que nadie planteó | «نحفظ المسار فقط. تخزين الملف كاملاً يضخّم الحجم بلا فائدة.» | «نحفظ المسار فقط.» |
| Justificar una elección por lo que omitió el resumen | «لم يرد ذلك في الطلب، غير أن طبيعة العمل تفرضه.» | «طبيعة العمل تفرض ذلك.» |
| Primera persona del singular | «وأستطيع لاحقاً تحليل الحالات» | «ويمكن لاحقاً تحليل الحالات» |
| Repetir un hecho que el documento ya proporcionó | تكرار المعلومة في قسم آخر | احذف التكرار |
El árabe formal prefiere lo impersonal o el plural sobre «أنا»; el singular se lee como una nota para uno mismo en lugar de un documento.