ReportLab:如何处理中文/Unicode 字符

21

TL;DR: 有没有办法告诉ReportLab使用特定的字体,并在某些字符缺失时回退到另一个字体?或者,是否知道一种紧凑的TrueType字体,其中包含所有欧洲语言、希伯来语、俄语、中文、日语和阿拉伯语的字符?

我一直在使用ReportLab创建报告,但在渲染包含中文字符的字符串时遇到了问题。我使用的字体是DejaVu Sans Condensed,它不包含中文字符(然而,它包括西欧语言的各种Umlauts、希伯来语、阿拉伯语和西里尔语,使它相当通用,我有时需要这些字符)。

然而,该字体不支持中文,并且我找不到一个TrueType字体支持所有语言并满足我们的平面设计要求。为了临时解决这个问题,我让面向中文客户的报告使用完全不同的字体,仅包含英文和中文字符,希望其他语言的字符不会出现在字符串中。但由于这不是DejaVu Sans字体,因此很笨重,破坏了整体的平面设计。

因此,问题是,如何处理一个文档需要支持多种语言,并保持每种语言使用指定的字体。由于有时字符串包含多种语言,因此确定每个字符串应使用哪种字体不是一个选项。

有没有办法告诉ReportLab使用特定的字体,并在某些字符缺失时回退到另一个字体?我在文档中找到了模糊的提示,表明可能可以,尽管我可能理解不正确。

或者,您是否知道一种紧凑的TrueType字体,其中包含所有欧洲语言、希伯来语、俄语、中文、日语和阿拉伯语的字符?

谢谢。


我不知道完整的答案,但我相信使用任何Unicode字体[链接]https://en.wikipedia.org/wiki/Unicode_font可以帮助您显示许多语言中的字符。 - bmbigbang
3个回答

6
这个问题让我着迷了整整一周,所以在周末我深入研究并找到了一个解决方案,我称之为MultiFontParagraph。它是一个普通的Paragraph,但有一个很大的区别,你可以精确地设置字体回退顺序。

Example of the font fallback working

例如,我从互联网上随机获取了这段日文文本,使用了以下字体回退"Bauhaus","Arial","HanaMinA"。它检查第一个字体是否具有该字符的字形,如果是,则使用它,否则就会回退到下一个字体。目前,该代码并不是真正有效的,因为它在每个字符周围放置标签,但这很容易修复,但出于清晰起见,我没有在此处进行修复。

使用以下代码,我创建了上面的示例:

foreign_string = u'6905\u897f\u963f\u79d1\u8857\uff0c\u5927\u53a6\uff03\u5927'
P = MultiFontParagraph(foreign_string, styles["Normal"],
                     [  ("Bauhaus", "C:\Windows\Fonts\\BAUHS93.TTF"),
                        ("Arial", "C:\Windows\Fonts\\arial.ttf"),
                        ("HanaMinA", 'C:\Windows\Fonts\HanaMinA.ttf')])

MultiFontParagraph (git) 的源代码如下:

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import Paragraph


class MultiFontParagraph(Paragraph):
    # Created by B8Vrede for https://dev59.com/N1sW5IYBdhLWcg3wSFjX
    def __init__(self, text, style, fonts_locations):

        font_list = []
        for font_name, font_location in fonts_locations:
            # Load the font
            font = TTFont(font_name, font_location)

            # Get the char width of all known symbols
            font_widths = font.face.charWidths

            # Register the font to able it use
            pdfmetrics.registerFont(font)

            # Store the font and info in a list for lookup
            font_list.append((font_name, font_widths))

        # Set up the string to hold the new text
        new_text = u''

        # Loop through the string
        for char in text:

            # Loop through the fonts
            for font_name, font_widths in font_list:

                # Check whether this font know the width of the character
                # If so it has a Glyph for it so use it
                if ord(char) in font_widths:

                    # Set the working font for the current character
                    new_text += u'<font name="{}">{}</font>'.format(font_name, char)
                    break

        Paragraph.__init__(self, new_text, style)

1
我还没有测试过,但它似乎可以工作。问题是,尽管这个解决方案是正确的,但这正是我试图避免的解决方案 :) 由于除了迭代文本中每个字符的所有字体之外,没有其他方法,而且有些报告长达数百页,这可能会导致性能受到很大影响。 此外,Paragraph() 不是唯一有问题的元素。在某些情况下,我还直接在画布上绘制文本(不使用 Flowable 构造),尽管可以在那里复制此解决方案。无论如何,感谢您的回复并赞扬这个解决方案。 - ztorage
2
顺便说一下,我最终选择将我需要的不同字体合并成一个TTF文件。这使得整个过程可以无缝运行。 - ztorage
合并字体通常是我认为最简单的解决方案。但是使用此解决方案,其复杂度在最坏情况下不会很高,最多为O(NF1),其中N是字符数,F是指定的字体数,1是用于查找字典的次数,但是如果选择适当的字体,则只需检查2或3种字体即可找到能够提供所需字符的字体。 - B8vrede

3

来自Google Noto字体:

谷歌一直在开发一种名为Noto的字体系列,旨在支持所有语言并具有和谐的外观和感觉。

统一的Noto Sans字体包括一个字体,支持以下地区的581种语言:

enter image description here

其他如希伯来语、阿拉伯语和日语在Noto网站上列为单独的项目。


0
我们也可以使用Reportlab中文字体包。
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont

# Register the Chinese font with Reportlab
pdfmetrics.registerFont(UnicodeCIDFont('STSong-Light'))

# Create a new canvas
c = canvas.Canvas("sample.pdf")

# Set the font to the Chinese font
c.setFont('STSong-Light', 32)

# Draw some Chinese characters
c.drawString(50, 750, '世界,你好!')

# Save the PDF
c.save()

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