cannot properly render RTL in pdfMake for Arabic pdf

Discussão em 'Angular' iniciado por Samir Malik, Abril 20, 2025.

  1. Samir Malik

    Samir Malik Guest

    I'm working on generating Arabic-language PDFs in an Angular app using pdfMake. I'm currently using the Amiri font loaded via base64 and VFS. Here's what I'm trying to achieve:


    • Proper RTL layout


    • Correct Arabic character joining


    • Avoid using arabic-persian-reshaper or bidi-js (due to output issues and broken characters)
    What I've tried:


    • Setting direction: 'rtl' and alignment: 'right'


    • Using table layout with noWrap: true


    • Inserting line breaks manually after Arabic commas (،)


    • Using this reverse function:

    private formatArabic(text: string): string {
    return this.stripHtmlTags(text);
    }

    private stripHtmlTags(html: string): string {
    const div = document.createElement('div');
    div.innerHTML = html;
    return (div.textContent || div.innerText || '')
    .replace(/[\uFFFD\u200F\u200E\u202A-\u202E]/g, '') // remove invalid & BiDi chars
    .trim();
    }
    public generateArabicPdf(formData: any) { try { const uniqueVaccines = this.getAllUniqueVaccines(formData)
    Promise.all([
    this.convertImageToBase64('https://portal-dev.pha.gov.sa/Style%20Library/assets/images/img/pdf-1.png'),
    this.convertImageToBase64('https://portal-dev.pha.gov.sa/Style%20Library/assets/images/img/pdf-2.png'),
    ]).then(([headerImage, footerImage]) => {
    const tableBody = [
    [
    { text: 'المؤشرات', style: 'tableHeader', alignment: 'right', direction: 'rtl' } as any,
    { text: 'التطعيم', style: 'tableHeader', alignment: 'right', direction: 'rtl' } as any
    ]
    ];

    uniqueVaccines.forEach(vaccine => {
    const recommendations = this.getCombinedRecommendations(vaccine, formData);
    if (recommendations.length > 0) {
    tableBody.push([
    {
    text: recommendations.map((rec: string) => this.formatArabic(rec)).join('\n'),
    style: 'cell',
    alignment: 'right',
    direction: 'rtl'
    } as any,
    {
    text: this.formatArabic(vaccine),
    style: 'cell',
    alignment: 'right',
    direction: 'rtl'
    } as any
    ]);
    }
    });

    const docDefinition: import('pdfmake/interfaces').TDocumentDefinitions = {
    pageSize: 'A4',
    pageMargins: [40, 60, 40, 60],
    content: [
    {
    image: headerImage,
    width: 150,
    alignment: 'center',
    margin: [0, 0, 0, 20],
    },
    {
    text: 'تقرير توصيات التطعيم',
    style: 'header',
    alignment: 'center',
    direction: 'rtl'
    } as any,
    {
    table: {
    headerRows: 1,
    widths: ['70%', '30%'],
    body: tableBody
    },
    layout: {
    hLineWidth: (i, node) => (i === 0 || i === node.table.body.length ? 2 : 1),
    vLineWidth: (i, node) => (i === 0 || i === node.table.widths?.length ? 2 : 1),
    hLineColor: (i, node) => (i === 0 || i === node.table.body.length ? '#008080' : '#E2E8F0'),
    vLineColor: (i, node) => (i === 0 || i === node.table.widths?.length ? '#008080' : '#E2E8F0'),
    paddingLeft: () => 10,
    paddingRight: () => 10,
    paddingTop: () => 8,
    paddingBottom: () => 8,
    }
    }
    ],
    footer: (currentPage, pageCount) => ({
    columns: [
    {
    image: footerImage,
    width: 300,
    alignment: 'left',
    margin: [40, 0, 0, 0]
    },
    {
    text: `صفحة ${currentPage} من ${pageCount}`,
    alignment: 'right',
    margin: [0, 20, 40, 0],
    style: 'footer',
    direction: 'rtl'
    } as any
    ]
    }),

    styles: {
    header: {
    fontSize: 24,
    bold: true,
    color: '#008080'
    },
    tableHeader: {
    fontSize: 12,
    bold: true,
    color: '#008080',
    fillColor: '#F0FFFF'
    },
    cell: {
    fontSize: 11,
    color: '#334155',
    lineHeight: 1.4
    },
    footer: {
    fontSize: 10,
    color: '#64748B'
    }
    },
    defaultStyle: {
    font: 'IBMPlexSansArabic',
    alignment: 'right',
    direction: 'rtl',
    rtl: true
    } as any
    };

    const pdfDoc = pdfMake.createPdf(docDefinition);
    pdfDoc.getBlob(blob => {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'توصيات-التطعيم.pdf';
    document.body.appendChild(a);
    a.click();
    setTimeout(() => {
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
    }, 100);
    });

    }).catch(error => {
    console.error('Image load error:', error);
    alert('تعذر تحميل الصور في ملف PDF');
    });

    } catch (err) {
    console.error('PDF generation error:', err);
    alert('حدث خطأ أثناء إنشاء ملف PDF.');
    }
    }​

    }

    I tried below:

    private convertTextToRtl(text: string): string { const cleaned = this.stripHtmlTags(text); return cleaned.split(' ').reverse().join(' '); }
    private reverseRtlSentence(text: string): string {
    const cleaned = this.stripHtmlTags(text).trim();

    // Handle line breaks or punctuation at end
    const match = cleaned.match(/^([\s\S]*?)([.!؟…،؛:()\-\u061F]*)$/);
    if (!match) return cleaned;

    const mainText = match[1];
    const punctuation = match[2];

    const words = mainText.split(/\s+/).reverse();
    return words.join(' ') + punctuation;
    }


    also I tried arabic-shaper and bidi but was not successful, the problem is for some arabic characters, there is no space.

    English characters like 19 come like 91 and if arabic characters goes to next line, they behave LTR.

    Question:


    Is there a reliable way to render Arabic text properly in pdfMake without relying on reshaper libraries?
    Are there any techniques to preserve full RTL and character joining using only pdfMake and a good Arabic font?

    [​IMG]

    Continue reading...

Compartilhe esta Página