草庐IT

python - 生成字体名称无法正确解码的字符图像

coder 2023-05-26 原文

我正在创建中文图像 seal script .我为此任务准备了三种真字体( Jin_Wen_Da_Zhuan_Ti.7zZhong_Guo_Long_Jin_Shi_Zhuan.7zZhong_Yan_Yuan_Jin_Wen.7z ,仅用于测试目的)。以下是 Microsoft Word 中的外观

汉字“我”(I/me)。这是我的 Python 脚本:

import numpy as np
from PIL import Image, ImageFont, ImageDraw, ImageChops
import itertools
import os


def grey2binary(grey, white_value=1):
    grey[np.where(grey <= 127)] = 0
    grey[np.where(grey > 127)] = white_value
    return grey


def create_testing_images(characters,
                          font_path,
                          save_to_folder,
                          sub_folder=None,
                          image_size=64):
    font_size = image_size * 2
    if sub_folder is None:
        sub_folder = os.path.split(font_path)[-1]
        sub_folder = os.path.splitext(sub_folder)[0]
    sub_folder_full = os.path.join(save_to_folder, sub_folder)
    if not os.path.exists(sub_folder_full):
        os.mkdir(sub_folder_full)
    font = ImageFont.truetype(font_path,font_size)
    bg = Image.new('L',(font_size,font_size),'white')

    for char in characters:
        img = Image.new('L',(font_size,font_size),'white')
        draw = ImageDraw.Draw(img)
        draw.text((0,0), text=char, font=font)
        diff = ImageChops.difference(img, bg)
        bbox = diff.getbbox()
        if bbox:
            img = img.crop(bbox)
            img = img.resize((image_size, image_size), resample=Image.BILINEAR)

            img_array = np.array(img)
            img_array = grey2binary(img_array, white_value=255)

            edge_top = img_array[0, range(image_size)]
            edge_left = img_array[range(image_size), 0]
            edge_bottom = img_array[image_size - 1, range(image_size)]
            edge_right = img_array[range(image_size), image_size - 1]

            criterion = sum(itertools.chain(edge_top, edge_left, 
                                           edge_bottom, edge_right))

            if criteria > 255 * image_size * 2:
                img = Image.fromarray(np.uint8(img_array))
                img.save(os.path.join(sub_folder_full, char) + '.gif')

核心片段在哪里

        font = ImageFont.truetype(font_path,font_size)
        img = Image.new('L',(font_size,font_size),'white')
        draw = ImageDraw.Draw(img)
        draw.text((0,0), text=char, font=font)

例如,如果您将这些字体放在文件夹 ./fonts 中,并用

调用它
create_testing_images(['我'], 'fonts/金文大篆体.ttf', save_to_folder='test')

脚本会在你的文件系统中创建./test/金文大篆体/我.gif

现在的问题是,虽然它适用于第一种字体金文大篆体.ttf(在 Jin_Wen_Da_Zhuan_Ti.7z 中),但脚本不适用于其他两种字体,即使它们可以在 Microsoft Word 中正确呈现:对于中国龙金石篆.ttf(在Zhong_Guo_Long_Jin_Shi_Zhuan.7z),它什么都不画,所以bbox将是None;对于中研院金文.ttf(在Zhong_Yan_Yuan_Jin_Wen.7z中),它会在图片中画一个没有字符的黑框。

因此未能通过 criterion 的测试,其目的是测试全黑输出。我用了FontForge查看字体属性,发现第一个字体金文大篆体.ttf(在Jin_Wen_Da_Zhuan_Ti.7z中)使用了UnicodeBmp

而另外两个使用 Big5hkscs

这不是我系统的编码方案。这可能是字体名称在我的系统中无法识别的原因:

其实我也尝试通过获取字体名称困惑的字体来解决这个问题。我在安装这些字体后尝试了 pycairo:

import cairo

# adapted from
# http://heuristically.wordpress.com/2011/01/31/pycairo-hello-world/

# setup a place to draw
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 100, 100)
ctx = cairo.Context (surface)

# paint background
ctx.set_source_rgb(1, 1, 1)
ctx.rectangle(0, 0, 100, 100)
ctx.fill()

# draw text
ctx.select_font_face('金文大篆体')
ctx.set_font_size(80)
ctx.move_to(12,80)
ctx.set_source_rgb(0, 0, 0)
ctx.show_text('我')

# finish up
ctx.stroke() # commit to surface
surface.write_to_png('我.gif')

这再次适用于金文大篆体.ttf(在 Jin_Wen_Da_Zhuan_Ti.7z 中):

但仍然没有和其他人在一起。例如:ctx.select_font_face('中国龙金石篆')(报告_cairo_win32_scaled_font_ucs4_to_index:GetGlyphIndicesW)和ctx.select_font_face('¤¤°êÀsª÷÷ ¥Û½f') (使用默认字体绘制)有效。 (后一个名字就是上图字体查看器中显示的乱码,通过一行Mathematica code ToCharacterCode["中国龙金石篆", "CP950"]//FromCharacterCode 其中CP950是Big5的代码页。)

所以我想我已经尽力解决这个问题,但仍然无法解决。我还提出了其他方法,例如使用 FontForge 重命名字体名称或将系统编码更改为 Big5,但我仍然更喜欢仅涉及 Python 的解决方案,因此用户需要较少的额外操作。任何提示将不胜感激。谢谢。

致stackoverflow的版主:这个问题乍一看可能看起来“过于本地化”,但它可能发生在其他语言/其他编码/其他字体中,解决方案可以概括为其他情况,请勿以此理由关闭。谢谢。

更新:奇怪的是 Mathematica 可以识别 CP936 中的字体名称(GBK,可以认为是我的系统编码)。以中国龙金石篆.ttf(Zhong_Guo_Long_Jin_Shi_Zhuan.7z)为例:

但是设置 ctx.select_font_face('ÖÐøý½ðʯ*') 也不起作用,这将使用默认字体创建字符图像。

最佳答案

Silvia 对 OP 的评论...

You might want to consider specifying the encoding parameter like ImageFont.truetype(font_path,font_size,encoding="big5")

...让您走到一半,但如果您不使用 Unicode 字体,您似乎还必须手动翻译 Unicode 字符。

对于使用“big5hkscs”编码的字体,我必须这样做......

>>> u = u'\u6211'      # Unicode for 我
>>> u.encode('big5hkscs')
'\xa7\xda'

...然后使用 u'\ua7da' 得到正确的字形,这有点奇怪,但它看起来是向 PIL 传递多字节字符的唯一方法。

以下代码适用于我在 Python 2.7.4 和 Python 3.3.1 上使用 PIL 1.1.7...

from PIL import Image, ImageDraw, ImageFont


# Declare font files and encodings
FONT1 = ('Jin_Wen_Da_Zhuan_Ti.ttf',          'unicode')
FONT2 = ('Zhong_Guo_Long_Jin_Shi_Zhuan.ttf', 'big5hkscs')
FONT3 = ('Zhong_Yan_Yuan_Jin_Wen.ttf',       'big5hkscs')


# Declare a mapping from encodings used by str.encode() to encodings used by
# the FreeType library
ENCODING_MAP = {'unicode':   'unic',
                'big5':      'big5',
                'big5hkscs': 'big5',
                'shift-jis': 'sjis'}


# The glyphs we want to draw
GLYPHS = ((FONT1, u'\u6211'),
          (FONT2, u'\u6211'),
          (FONT3, u'\u6211'),
          (FONT3, u'\u66ce'),
          (FONT2, u'\u4e36'))


# Returns PIL Image object
def draw_glyph(font_file, font_encoding, unicode_char, glyph_size=128):

    # Translate unicode string if necessary
    if font_encoding != 'unicode':
        mb_string = unicode_char.encode(font_encoding)
        try:
            # Try using Python 2.x's unichr
            unicode_char = unichr(ord(mb_string[0]) << 8 | ord(mb_string[1]))
        except NameError:
            # Use Python 3.x-compatible code
            unicode_char = chr(mb_string[0] << 8 | mb_string[1])

    # Load font using mapped encoding
    font = ImageFont.truetype(font_file, glyph_size, encoding=ENCODING_MAP[font_encoding])

    # Now draw the glyph
    img = Image.new('L', (glyph_size, glyph_size), 'white')
    draw = ImageDraw.Draw(img)
    draw.text((0, 0), text=unicode_char, font=font)
    return img


# Save an image for each glyph we want to draw
for (font_file, font_encoding), unicode_char in GLYPHS:
    img = draw_glyph(font_file, font_encoding, unicode_char)
    filename = '%s-%s.png' % (font_file, hex(ord(unicode_char)))
    img.save(filename)

请注意,我将字体文件重命名为与 7zip 文件相同的名称。我尽量避免在代码示例中使用非 ASCII 字符,因为它们有时会在复制/粘贴时搞砸。

这个例子应该适用于 ENCODING_MAP 中声明的类型,如果需要可以扩展(参见 FreeType encoding strings 了解有效的 FreeType 编码),但您需要更改一些Python str.encode() 不产生长度为 2 的多字节字符串时的代码。


更新

If the problem is in the ttf file, how could you find the answer in the PIL and FreeType source code? Above, you seem to be saying PIL is to blame, but why should one have to pass unicode_char.encode(...).decode(...) when you just want unicode_char?

据我了解,TrueType字体格式是在 Unicode 被广泛采用之前开发的,所以如果你想创建一个当时的中文字体,你必须使用当时正在使用的一种编码,而中国大多使用 Big5自 1980 年代中期以来。

因此,必然有一种方法可以使用 Big5 字符编码从 Big5 编码的 TTF 中检索字形。

使用 PIL 渲染字符串的 C 代码以 font_render() 开头函数,最终调用 FT_Get_Char_Index()找到正确的字形,给定字符代码为 unsigned long

但是,PIL 的 font_getchar()函数,它产生 unsigned long 只接受 Python stringunicode 类型,因为它似乎没有对字符进行任何翻译编码本身,似乎获得 Big5 字符集正确值的唯一方法是利用这一事实将 Python unicode 字符强制转换为正确的 unsigned longu'\ua7da' 在内部存储为整数 0xa7da,可以是 16 位还是 32 位,具体取决于您编译 Python 的方式。

TBH,这涉及到相当多的猜测,因为我没有费心去研究 ImageFont.truetype()encoding 参数究竟是什么效果是,但从外观上看,它不应该对字符编码进行任何转换,而是允许单个 TTF 文件支持相同字形的多个字符编码,使用 FT_Select_Charmap()函数在它们之间切换。

所以,据我了解,FreeType 库与 TTF 文件的交互是这样的......

#!/usr/bin/env python
# -*- coding: utf-8 -*-

class TTF(object):
    glyphs = {}
    encoding_maps = {}

    def __init__(self, encoding='unic'):
        self.set_encoding(encoding)

    def set_encoding(self, encoding):
        self.current_encoding = encoding

    def get_glyph(self, charcode):
        try:
            return self.glyphs[self.encoding_maps[self.current_encoding][charcode]]
        except KeyError:
            return ' '


class MyTTF(TTF):
    glyphs = {1: '我',
              2: '曎'}
    encoding_maps = {'unic': {0x6211: 1, 0x66ce: 2},
                     'big5': {0xa7da: 1, 0x93be: 2}}


font = MyTTF()
print 'Get via Unicode map: %s' % font.get_glyph(0x6211)
font.set_encoding('big5')
print 'Get via Big5 map: %s' % font.get_glyph(0xa7da)

...但是由每个 TTF 提供 encoding_maps 变量,并且没有要求 TTF 为 Unicode 提供一个。事实上,在采用 Unicode 之前创建的字体不太可能有。

假设所有这些都是正确的,那么 TTF 没有任何问题 - 问题只是 PIL 使得访问没有 Unicode 映射的字体的字形有点尴尬,并且所需字形的 unsigned long 字符代码大于 255。

关于python - 生成字体名称无法正确解码的字符图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16886481/

有关python - 生成字体名称无法正确解码的字符图像的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  4. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  5. ruby-on-rails - unicode 字符串的长度 - 2

    在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)

  6. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  7. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  8. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  9. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  10. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

随机推荐