如何使用Python的reportlab、rtl和bidi创建包含波斯语(Farsi)文本的PDF。

11

我一直在尝试从内容中创建PDF文件,该内容可以是英文、波斯语、数字或它们的组合。

波斯语文本存在一些问题,例如:"این یک متن فارسی است"

1- 文字必须从右到左书写

2- 单词中不同位置的字符有所不同(即字符根据其周围字符而改变形状)

3- 由于句子是从右向左读取的,因此常规的文本换行不起作用。


如果你的应用程序是在线应用程序,请尝试使用已经用PHP完成此操作的库,因为对于你来说从Python或任何其他语言构建PDF并不重要。如果正确,请通知我将其发布为答案。 - S.A.Parkhid
谢谢回复。我可以用Python创建PDF,目前看起来没问题,但我还没有测试过所有不同的文本样式。如果您检查了我的代码并发现任何问题,请告诉我,我会非常感激。 - r.aj
4个回答

11

我使用reportlab创建PDF,但不幸的是,reportlab不支持阿拉伯语和波斯语字母,所以我使用了Vahid Mardani的'rtl'库和Meir Kriheli的'pybidi'库来使文本在PDF结果中正确显示。

首先,我们需要添加一个支持波斯语的字体到reportlab:

  1. in ubuntu 14.04:

    copy Bahij-Nazanin-Regular.ttf into
    /usr/local/lib/python3.4/dist-packages/reportlab/fonts folder
    
  2. add font and styles to reportlab:

    from reportlab.lib.enums import TA_RIGHT
    from reportlab.pdfbase import pdfmetrics
    from reportlab.pdfbase.ttfonts import TTFont
    pdfmetrics.registerFont(TTFont('Persian', 'Bahij-Nazanin-Regular.ttf'))
    styles = getSampleStyleSheet()
    styles.add(ParagraphStyle(name='Right', alignment=TA_RIGHT, fontName='Persian', fontSize=10))
    

接下来,我们需要将波斯文本字母调整到正确的形状,并使每个单词的方向从右到左:

    from bidi.algorithm import get_display
    from rtl import reshaper
    import textwrap

    def get_farsi_text(text):
        if reshaper.has_arabic_letters(text):
          words = text.split()
          reshaped_words = []
          for word in words:
            if reshaper.has_arabic_letters(word):
              # for reshaping and concating words
              reshaped_text = reshaper.reshape(word)
              # for right to left    
              bidi_text = get_display(reshaped_text)
              reshaped_words.append(bidi_text)
            else:
              reshaped_words.append(word)
          reshaped_words.reverse()
         return ' '.join(reshaped_words)
        return text

我们可以使用以下函数来添加项目符号或将文本进行换行:

    def get_farsi_bulleted_text(text, wrap_length=None):
       farsi_text = get_farsi_text(text)
       if wrap_length:
           line_list = textwrap.wrap(farsi_text, wrap_length)
           line_list.reverse()
           line_list[0] = '{} •'.format(line_list[0])
           farsi_text = '<br/>'.join(line_list)
           return '<font>%s</font>' % farsi_text
       return '<font>%s &#x02022;</font>' % farsi_text

为了测试代码,我们可以编写:

    from reportlab.lib.pagesizes import letter
    from reportlab.platypus import SimpleDocTemplate, Paragraph
    from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle

    doc = SimpleDocTemplate("farsi_wrap.pdf", pagesize=letter,    rightMargin=72, leftMargin=72, topMargin=72,
                    bottomMargin=18)
    Story = []

    text = 'شاید هنوز اندروید نوقا برای تمام گوشی‌های اندرویدی عرضه نشده باشد، ولی اگر صاحب یکی از گوشی‌های نکسوس یا پیک' \
   'سل باشید احتمالا تا الان زمان نسبتا زیادی را با آخرین نسخه‌ی اندروید سپری کرده‌اید. اگر در کار با اندروید نوقا' \
   ' دچار مشکل شده‌اید، با دیجی‌کالا مگ همراه باشید تا با هم برخی از رایج‌ترین مشکلات گزارش شده و راه حل آن‌ها را' \
   ' بررسی کنیم. البته از بسیاری از این روش‌ها در سایر نسخه‌های اندروید هم می‌توانید استفاده کنید. اندروید برخلاف iOS ' \
   'روی گستره‌ی وسیعی از گوشی‌ها با پوسته‌ها و اپلیکیشن‌های اضافی متنوع نصب می‌شود. بنابراین تجویز یک نسخه‌ی مشترک برا' \
   'ی حل مشکلات آن کار چندان ساده‌ای نیست. با این حال برخی روش‌های عمومی وجود دارد که بهتر است پیش از هر چیز آن‌ها را' \
   ' بیازمایید.'
    tw = get_farsi_bulleted_text(text, wrap_length=120)
    p = Paragraph(tw, styles['Right'])
    Story.append(p)
    doc.build(Story)

当混合文本方向时,例如在主要为波斯语的文本中插入LTR单词(“英语”),或者反过来,这种方法是否也适用? - Jongware
1
它可以很好地处理混合的语句(既有波斯语又有英语),并从右到左显示。 - r.aj
@r.aj,感谢您提供了详细的完整示例。当我尝试上述示例时,阿拉伯语单词以从左到右的方向显示为"أختبار الرياضة",但显示为"ة ض اي رلا راب ت خ أ"。有什么建议吗? - User
嗨,我用这段脚本试了一下你的文本,它可以正常工作。你确定你使用了 get_farsi_text 或 get_farsi_bulleted_text 函数吗?因为似乎你的文本没有经过这些函数: #用于调整和连接单词的#:               reshaped_text = reshaper.reshape(word)               #从右到左:#               bidi_text = get_display(reshaped_text)               reshaped_words.append(bidi_text) - r.aj
谢谢您的建议,虽然它对我有效,但是PDF中的行是从最后一行开始写入的,即使是您的示例也是如此。 - NASRIN

7

在使用Reportlab一段时间后,我们遇到了组织和格式化的问题。这需要很多时间,而且有些复杂。

因此,我们决定使用pdfkit和jinja2。这样,我们可以使用html和CSS进行格式化和组织,而不需要重新格式化波斯语文本。

首先,我们可以设计一个html模板文件,如下所示:

    <!DOCTYPE html>
        &lthtml>
        &lthead lang="fa-IR">
            &ltmeta charset="UTF-8">
            &lttitle></title>
        </head>
        &ltbody >
            &ltp dir="rtl">سوابق کاری</p>
            &ltul dir="rtl">
                {% for experience in experiences %}
                &ltli>&lta href="{{ experience.url }}">{{ experience.title }}</a></li>
                {% endfor %}
            </ul>
        </body>
        </html>

然后,我们使用jinja2库将数据渲染到模板中,然后使用pdfkit从渲染结果创建pdf:

    from jinja2 import Template
    from pdfkit import pdfkit

    sample_data = [{'url': 'http://www.google.com/', 'title': 'گوگل'},
                   {'url': 'http://www.yahoo.com/fa/', 'title': 'یاهو'},
                   {'url': 'http://www.amazon.com/', 'title': 'آمازون'}]

    with open('template.html', 'r') as template_file:
        template_str = template_file.read()
        template = Template(template_str)
        resume_str = template.render({'experiences': sample_data})

        options = {'encoding': "UTF-8", 'quiet': ''}
        bytes_array = pdfkit.PDFKit(resume_str, 'string', options=options).to_pdf()
        with open('result.pdf', 'wb') as output:
            output.write(bytes_array)

我这里也遇到了同样的问题,pdfkit本身可以正常工作,但是当尝试添加像dir='rtl'或者text-align=right这样的样式时,同时包含英文和波斯语单词的文本会奇怪地改变位置并相互覆盖,边距和填充也无法正常工作。你是否也遇到了这样的问题?如果是,能否指导我解决方案? - aasmpro

4

如果有人想使用Django从HTML模板生成PDF,可以按照以下步骤进行:

template = get_template("app_name/template.html")
context = Context({'something':some_variable})
html = template.render(context)
pdf = pdfkit.from_string(html, False)
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename=output.pdf'
return response

0

将多字节(波斯语,阿拉伯语)字符串作为参数发送到以下 TypeScript 函数,并将返回的字符串放入 pdfMaker 或任何其他 PDF 生成器中。

farsiNew(farsistr){ 
         // because pdfmake display it mirrored by default
        var allText = '';
        var point = 19;
        var words = farsistr.split("\n");
        var newword;
        for(var i=0; i<=words.length-1; i++){
           newword = words[i].split( ' ');
            if (newword.length <point) {
                allText = allText + newword.reverse().join(' ') + "\n";
            }else{
 
                for(var q =0; q<= Math.ceil (newword.length / point); q++) {

                    var s , t;
                    if (q === 0) {
                        s = 0; t = point;
                    }
                    else {
                         s = q * point + q;
                        t = s + point;
                    }
                    for (var v = t; v >= s; v--) {
                        if(!newword[v])
                            continue;
                        allText = allText + ' ' +newword[v]

                    }
                   allText = allText + '\n';
                }
            }
        }
        return allText;

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接