Подтвердить что ты не робот

Сгенерировать символьные изображения шрифтом, имя которого невозможно правильно декодировать

Я создаю изображения китайского печать script. У меня есть три истинных шрифта для этой задачи (Jin_Wen_Da_Zhuan_Ti.7z, Zhong_Guo_Long_Jin_Shi_Zhuan.7z, Zhong_Yan_Yuan_Jin_Wen.7z, только для целей тестирования). Ниже перечислены возможности Microsoft Word

appearance in Word

китайского иероглифа "我" (I/me). Вот мой Python script:

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')

script создаст ./test/金文大篆体/我.gif в вашей файловой системе.

Теперь проблема в том, что она хорошо работает с первым шрифтом 金文 大 篆体.ttf(в Jin_Wen_Da_Zhuan_Ti.7z), script не работает с двумя другими шрифтами, даже если они могут быть правильно отображены в Microsoft Word: для 中國 龍 金石 篆.ttf(в Zhong_Guo_Long_Jin_Shi_Zhuan.7z) он ничего не рисует, поэтому bbox будет None; для 中研院 金文.ttf(в Zhong_Yan_Yuan_Jin_Wen.7z) он нарисует черный кадр без символа на картинке.

enter image description here

и, таким образом, не проходит тест criterion, целью которого является тестирование полноцветного вывода. Я использовал FontForge, чтобы проверить свойства шрифтов и обнаружил, что первый шрифт 金文 大 篆体.ttf(в Jin_Wen_Da_Zhuan_Ti.7z) использует UnicodeBmp

UnicodeBmp

в то время как другие два используют Big5hkscs

Big5hkscs_中國龍金石篆中研院金文

который не является схемой кодирования моей системы. Это может быть причиной того, что имена шрифтов неузнаваемы в моей системе:

font viewer

Собственно, я также пытаюсь решить эту проблему, пытаясь получить шрифт с грязным именем шрифта. Я попробовал 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):

enter image description here

но все же не с другими. Например: ни ctx.select_font_face('中國龍金石篆') (который сообщает _cairo_win32_scaled_font_ucs4_to_index:GetGlyphIndicesW), ни ctx.select_font_face('¤¤°êÀsª÷¥Û½f') (который рисует шрифтом по умолчанию) работает. (Последнее имя - это беспорядочный код, отображаемый в средстве просмотра шрифтов, как показано выше, полученном линией кода Mathematica ToCharacterCode["中國龍金石篆", "CP950"] // FromCharacterCode, где CP950 является кодовой страницей Big5.)

Поэтому я думаю, что я изо всех сил пытался решить эту проблему, но все равно не могу ее решить. Я также придумал другие способы, как переименование имени шрифта с помощью FontForge или изменение системного кодирования на Big5, но я бы предпочел решение, которое включает только Python и, следовательно, для пользователя требуется меньше дополнительных действий. Любые подсказки будут высоко оценены. Спасибо.

Модераторам stackoverflow: эта проблема может показаться "слишком локализованной" с первого взгляда, но это может произойти в других языках/других кодировках/других шрифтах, и решение может быть обобщено на в других случаях, поэтому, пожалуйста, не закрывайте его по этой причине. Спасибо.

ОБНОВЛЕНИЕ: странно Mathematica может распознавать имя шрифта в CP936 (GBK, которое можно рассматривать как мое системное кодирование). Возьмите 中國 龍 金石 篆.ttf(в Zhong_Guo_Long_Jin_Shi_Zhuan.7z) для примера:

Mathematica

Но установка ctx.select_font_face('ÖÐøý½ðʯ*­') тоже не работает, что создаст образ символа со шрифтом по умолчанию.

4b9b3361

Ответ 1

Сильвия прокомментирует OP...

Возможно, вы захотите рассмотреть параметр encoding, например 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 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 для действительных Коды FreeType), но вам нужно будет изменить часть кода в случаях, когда Python str.encode() не создает многобайтную строку длины 2.


Обновление

Если проблема в файле ttf, как вы могли бы найти ответ в исходный код PIL и FreeType? Вы, кажется, говорите, что PIL виноват, но зачем нужно проходить unicode_char.encode(...). decode (...), когда вы просто хотите unicode_char?

Как я понимаю, формат TrueType был разработан до того, как Unicode стал широко распространенным, поэтому, если вы хотите создать китайский шрифт тогда вам пришлось бы использовать один из кодировок, который использовался в то время, и Китай в основном использовал Big5 с середины 1980-х годов.

Разумеется, тогда должен был быть способ получить глифы из TTF с кодировкой Big5 с использованием кодировок символов Big5.

Код C для визуализации строки с PIL начинается с функции font_render() и в конечном итоге вызывает FT_Get_Char_Index(), чтобы найти правильный глиф, учитывая код символа как unsigned long.

Однако функция PIL font_getchar(), которая дает, что unsigned long принимает только типы Python string и unicode, и поскольку он, похоже, не делает никакого перевода кодировок символов сам по себе, казалось, что единственным способом получить правильное значение для набора символов Big5 было принуждение символа Python unicode к правильному значению unsigned long, используя факт, что u'\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.

Ответ 2

Проблема заключается в том, что шрифты не строго соответствуют спецификации TrueType. Быстрое решение - использовать FontForge (вы уже используете его), и пусть он дезинфицирует шрифты.

  • Открыть файл шрифта
  • Перейдите к Encoding, затем выберите Reencode
  • Выберите ISO 10646-1 (Unicode BMP)
  • Перейдите к File, затем Generate Fonts
  • Сохранить как TTF
  • Запустите script с новыми созданными шрифтами
  • Voila! Он печатает 我 в красивом шрифте!