Geração de PDF em Árabe / RTL
O código ReportLab ingênuo produz árabe que está sutil e completamente errado: letras aparecem desconectadas, palavras dentro de uma linha correm para trás e linhas dentro de um parágrafo trocam de lugar. Nada disso é visível no texto extraído — apenas na renderização. Esta habilidade agrupa um construtor que já resolve esses problemas.
Use scripts/arabic_pdf.py em vez de escrever lógica de reformatação do zero.
Cada modo de falha abaixo foi encontrado ao testá-lo em um documento real.
Dependências
pip install reportlab arabic-reshaper python-bidi pypdfium2
pypdfium2 é usado apenas para visualização, mas a visualização não é opcional — veja
Verificação abaixo.
Construindo um documento
Importe o construtor copiando scripts/arabic_pdf.py para o diretório do seu
script de geração, ou adicionando a pasta scripts/ desta habilidade ao 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("relatorio.pdf", doc_title="عنوان المستند", page_numbers=True)
pdf.title("عنوان المستند")
pdf.subtitle("سطر فرعي")
pdf.heading("أولاً — المقدمة")
pdf.para("فقرة عربية تتضمن مصطلحات لاتينية مثل PostgreSQL و FastAPI.")
pdf.heading("ثانياً — جدول")
pdf.table(
["البند", "التفصيل"], # primeira coluna à direita
[["الواجهة الخلفية", "Python — FastAPI"],
["قاعدة البيانات", "PostgreSQL — SQLAlchemy"]],
[5.0, 11.0], # larguras das colunas em cm
)
pdf.bullets(["نقطة أولى", "نقطة ثانية"])
pdf.save()
preview("relatorio.pdf") # um PNG por página
Ajuste o caminho se a habilidade estiver em outro local — nada mais no construtor depende de onde está instalado.
Os métodos encadeiam, então pdf.heading(...).para(...) também funciona.
| Método | Propósito |
|---|---|
title / subtitle | Cabeçalho centralizado do documento |
heading | Título de seção em negrito, alinhado à direita |
para | Parágrafo de corpo |
bullets(items, marker="–") | Uma linha alinhada à direita por item |
table(header, rows, widths_cm) | Tabela RTL; ordem de leitura natural na entrada, colunas invertidas na saída |
spacer(pts) / page_break() | Controle de layout |
save() | Escreve o arquivo, retorna o caminho |
O texto de células e parágrafos pode conter \n para forçar uma quebra de linha.
Verificação — sempre renderize e olhe
Após save(), chame preview() e veja os PNGs com a ferramenta de Leitura. A extração
de texto não consegue capturar defeitos RTL, porque os caracteres estão todos presentes e
corretos; apenas sua ordem e forma estão errados. Verifique especificamente:
- A primeira palavra de cada parágrafo está no canto superior direito?
- Os termos latinos (
PostgreSQL,FastAPI) estão onde devem estar no meio da frase, em vez de pular para a extrema esquerda da linha? - As letras estão unidas ou separadas como
ا ل س ل ا م? - Alguma caixa preta sólida? A fonte não possui esses glifos — escolha outra.
- Os parênteses estão na direção correta —
(20 فأكثر)não)20 فأكثر(? - Pares dentro de termos latinos estão intactos —
Array<String>,DECIMAL(5,2)? - Alguma linha de tabela foi órfã para a próxima página?
Corrigir um defeito que você nunca procurou custa muito mais tarde, quando o documento já está com seu destinatário.
Como a correção é alcançada
Útil ao adaptar o construtor ou depurar um documento incomum.
- Formatação —
arabic_reshapermapeia caracteres para formas de apresentação posicionais para que as letras se conectem. - Bidi com uma direção base explícita —
get_display(..., base_dir="R"). Sembase_dir, a direção é inferida a partir do primeiro caractere forte, então uma linha começando com uma palavra latina é disposta da esquerda para a direita e o árabe após ela fica errado. Esta é a causa mais comum de "quase certo, mas algumas linhas estão embaralhadas". - Quebra de linha manual — o texto reformatado está em ordem visual. Se o reportlab
o quebrar, as palavras que ele empurra para a próxima linha são as logicamente primeiras,
então as linhas saem embaralhadas. O construtor quebra primeiro, reformata cada linha
separadamente e as une com
<br/>para que o reportlab nunca requeibre. Uma margem de segurança de 8pt cobre diferenças de medição; uma linha que transborda por um ponto é suficiente para invertê-la. - Espelhamento —
get_displayreordena, mas nunca aplica a regra bidi L4, que diz que um caractere espelhado disposto da direita para a esquerda é desenhado como seu parceiro. Sem ela,(20 فأكثر)retorna com os dois parênteses em posições trocadas e seus glifos originais, e imprime)20 فأكثر(. Oprepare()os substitui primeiro, para que a reordenação mova o glifo correto. - Ilhas latinas —
<e>são espelhados, mas não são colchetes pareados do Unicode, portanto a regra que mantémDECIMAL(5,2)unido não os cobre; o>deArray<String>assume a direção do árabe após ele e é jogado para o lado oposto. Cada trecho latino é envolvido emLRI…PDIe os isolados são removidos novamente após a reordenação, para que nada invisível chegue ao PDF. - Escapamento após a formatação — o reportlab precisa que
&,<,>sejam escapados, mas escapar primeiro envia<através da passagem bidi como quatro caracteres e seu;final é ejetado para o lado oposto da linha. Formate, depois escape. - Colunas de tabela invertidas — o reportlab dispõe as colunas da esquerda para a direita, então o construtor as inverte, permitindo que você passe as colunas na ordem de leitura natural.
KeepTogetherem tabelas — evita que uma linha solitária fique em uma nova página.
Os pontos 4–6 são da mesma classe de defeito que as execuções de colchetes divididas descritas
em arabic-docx, alcançadas pela direção oposta: lá um par não deve ser dividido entre
as execuções, aqui um par não deve ser meio-espelhado. Qualquer caractere espelhado deve ser resolvido junto com seu parceiro.
Fontes
register_fonts() procura por uma fonte compatível com árabe: Arial, depois Tahoma no
Windows, Arial no macOS, Noto Naskh Arabic, depois DejaVu Sans no Linux. Para usar uma
face específica (Amiri e Cairo são adequadas para documentos formais):
from arabic_pdf import register_fonts
register_fonts("/caminho/Amiri-Regular.ttf", "/caminho/Amiri-Bold.ttf") # antes de ArabicPDF(...)
As fontes internas do Reportlab não têm cobertura de árabe, portanto uma TTF deve ser registrada — é por isso que uma fonte ausente aparece como caixas pretas em vez de um erro.
Nunca leve esta abordagem para o Word ou PowerPoint
Este é o limite mais importante em toda a família de habilidades RTL.
| reportlab / Pillow | Word / PowerPoint | |
|---|---|---|
| Mecanismo de texto | nenhum | mecanismo bidirecional completo |
| Espera | glifos finais em ordem visual | Unicode bruto em ordem lógica |
| Técnica | reformatar + bidi + quebra manual | flags de direção no XML |
A pré-formatação é necessária aqui e destrutiva lá: Word e PowerPoint formatam no momento da renderização, portanto fornecer a eles formas de apresentação faz com que formatem uma segunda vez — glifos quebrados e texto que não pode mais ser pesquisado, copiado ou verificado ortograficamente. As duas abordagens são opostas, não variações.
Use arabic-docx para .docx e arabic-pptx para .pptx. Não transporte código
entre os três sem reler por que eles diferem.
Notas
arabic_digits("38")→"٣٨"quando numerais arábico-indianos são desejados. Puramente cosmético; os dígitos ocidentais são perfeitamente normais na escrita técnica árabe.- Mantenha a formatação contida por padrão — texto preto, linhas finas. Documentos acadêmicos e oficiais em árabe são lidos como mais credíveis dessa forma, e usuários pedindo "simples" querem dizer isso.
- O mesmo mecanismo lida com persa, urdu e hebraico; apenas a fonte precisa cobrir o script.
O que faz o árabe gerado parecer gerado
Não o vocabulário — os acréscimos. Cada hábito abaixo diz ao leitor algo sobre como o documento foi escrito, em vez de sobre seu assunto, e um leitor editando manualmente exclui todos eles. Escreva a frase, não o relato de escrevê-la.
| Hábito | Em vez de | Escreva |
|---|---|---|
| Narrar o ato de escrever | «الشروط ثلاثة، نذكرها صراحة فيما يلي» | «الشروط ثلاثة:» |
| Apontar para outra seção | «…ونعود إلى هذه النقطة لاحقاً» | احذف العبارة |
| Rótulos de versão ou status em um título | «الخطة (النسخة الثانية — معتمدة)» | «الخطة» |
| Argumentar contra uma opção que ninguém levantou | «نحفظ المسار فقط. تخزين الملف كاملاً يضخّم الحجم بلا فائدة.» | «نحفظ المسار فقط.» |
| Justificar uma escolha pelo que o briefing omitiu | «لم يرد ذلك في الطلب، غير أن طبيعة العمل تفرضه.» | «طبيعة العمل تفرض ذلك.» |
| Primeira pessoa do singular | «وأستطيع لاحقاً تحليل الحالات» | «ويمكن لاحقاً تحليل الحالات» |
| Reafirmar um fato que o documento já forneceu | تكرار المعلومة في قسم آخر | احذف التكرار |
O árabe formal prefere o impessoal ou o plural em vez de «أنا»; o singular soa como uma anotação para si mesmo, em vez de um documento.