18 votes

Générer des images de caractères avec une police dont le nom ne peut être correctement décodé

Je crée des images de la Chine seal script . Je dispose de trois polices true type pour cette tâche ( Jin_Wen_Da_Zhuan_Ti.7z , Zhong_Guo_Long_Jin_Shi_Zhuan.7z , Zhong_Yan_Yuan_Jin_Wen.7z (uniquement à des fins de test). Voici les apparences dans Microsoft Word

appearance in Word

du caractère chinois "" (je/moi). Voici mon 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')

où l'extrait de base est

        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)

Par exemple, si vous placez ces polices dans le dossier ./fonts et le suivre avec

create_testing_images([''], 'fonts/.ttf', save_to_folder='test')

le script créera ./test//.gif dans votre système de fichiers.

Le problème est que, bien que cela fonctionne bien avec la première police .ttf (dans Jin_Wen_Da_Zhuan_Ti.7z), le script ne fonctionne pas avec les deux autres polices, même si elles peuvent être rendues correctement dans Microsoft Word : pour .ttf (dans Zhong_Guo_Long_Jin_Shi_Zhuan.7z), il ne dessine rien de la façon suivante bbox sera None ; pour .ttf (dans Zhong_Yan_Yuan_Jin_Wen.7z), il dessinera un cadre noir sans aucun caractère dans l'image.

enter image description here

et ne passe donc pas le test de la criterion dont le but est de tester une sortie entièrement noire. J'ai utilisé FontForge pour vérifier les propriétés des polices, et j'ai trouvé que la première police .ttf (dans Jin_Wen_Da_Zhuan_Ti.7z) utilise UnicodeBmp

UnicodeBmp

tandis que les deux autres utilisent Big5hkscs

Big5hkscs_

ce qui n'est pas le schéma d'encodage de mon système. C'est peut-être la raison pour laquelle les noms des polices sont méconnaissables dans mon système :

font viewer

En fait, j'essaie aussi de résoudre ce problème en essayant d'obtenir la police avec le nom de police en désordre. J'ai essayé pycairo après avoir installé ces polices :

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

Cela fonctionne à nouveau avec .ttf (dans Jin_Wen_Da_Zhuan_Ti.7z) :

enter image description here

mais toujours pas avec les autres. Par exemple : ni ctx.select_font_face('') (qui rapporte _cairo_win32_scaled_font_ucs4_to_index:GetGlyphIndicesW ) ni ctx.select_font_face('¤¤°êÀsª÷¥f') (qui dessine avec la police par défaut) fonctionne. (Ce dernier nom est le code désordonné affiché dans le visualisateur de polices comme indiqué ci-dessus, obtenu par une ligne de code Mathematica ToCharacterCode["", "CP950"] // FromCharacterCode donde CP950 est la page de code de Big5).

Je pense donc avoir fait de mon mieux pour résoudre ce problème, mais je n'y suis toujours pas parvenu. J'ai également trouvé d'autres solutions comme renommer le nom de la police avec FontForge ou changer l'encodage du système en Big5, mais je préférerais toujours une solution qui n'implique que Python et qui nécessite donc moins d'actions supplémentaires de la part de l'utilisateur. Toute indication sera grandement appréciée. Je vous remercie.

Aux modérateurs de stackoverflow ce problème peut sembler "trop localisé" à première vue, mais il peut se produire dans d'autres langues / d'autres encodages / d'autres polices, et la solution peut être généralisée à d'autres cas, alors s'il vous plaît ne le fermez pas pour cette raison. Je vous remercie de votre attention.

MISE À JOUR : Bizarrement Mathematica peut reconnaître le nom de la police en CP936 (GBK, qui peut être considéré comme l'encodage de mon système). Prenons l'exemple du .ttf (dans Zhong_Guo_Long_Jin_Shi_Zhuan.7z) :

Mathematica

Mais la mise en place ctx.select_font_face('ÖÐøý½ð*­') ne fonctionne pas non plus, car elle crée l'image du caractère avec la police par défaut.

7voto

Aya Points 13144

Commentaire de Silvia sur le PO...

Vous pouvez envisager de spécifier le paramètre encoding p ImageFont.truetype(font_path,font_size,encoding="big5")

...vous permet de faire la moitié du chemin, mais il semble que vous deviez également traduire manuellement les caractères Unicode si vous n'utilisez pas une police Unicode.

Pour les polices qui utilisent l'encodage "big5hkscs", j'ai dû faire ceci...

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

...puis utiliser u'\ua7da' pour obtenir le bon glyphe, ce qui est un peu bizarre, mais il semble que ce soit la seule façon de passer un caractère multi-octets à PIL.

Le code suivant fonctionne pour moi sur Python 2.7.4 et Python 3.3.1, avec 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)

Notez que j'ai renommé les fichiers de polices avec les mêmes noms que les fichiers 7zip. J'essaie d'éviter d'utiliser des caractères non ASCII dans les exemples de code, car ils sont parfois altérés lors du copier/coller.

Cet exemple devrait fonctionner correctement pour les types déclarés dans ENCODING_MAP qui peut être étendu si nécessaire (voir la rubrique Chaînes de codage FreeType pour les encodages FreeType valides), mais vous devrez modifier une partie du code dans les cas où l'encodage Python str.encode() ne produit pas une chaîne de plusieurs octets de longueur 2.


Mise à jour

Si le problème se situe dans le fichier ttf, comment pouvez-vous trouver la réponse dans le fichier dans le code source de PIL et de FreeType ? Ci-dessus, vous semblez dire que PIL est à blâmer. à blâmer, mais pourquoi devrait-on passer unicode_char.encode(...).decode(...) quand on veut juste unicode_char ?

Si j'ai bien compris, la TrueType a été développé avant l'adoption généralisée d'Unicode, de sorte que si vous vouliez créer une police de caractères chinoise à l'époque, vous auriez dû utiliser l'un des encodages en usage à ce moment-là, et la Chine utilisait principalement le format Big5 depuis le milieu des années 1980.

Il est donc logique qu'il existe un moyen de récupérer les glyphes d'un TTF codé en Big5 en utilisant les codages de caractères Big5.

Le code C pour le rendu d'une chaîne de caractères avec la LIP commence par la fonction font_render() et appelle finalement la fonction FT_Get_Char_Index() pour localiser le glyphe correct, étant donné le code du caractère en tant qu'élément d'information. unsigned long .

Toutefois, les LIP font_getchar() qui produit que unsigned long n'accepte que Python string y unicode et comme il ne semble pas faire de traduction des encodages de caractères lui-même, il semblait que la seule façon d'obtenir la valeur correcte pour le jeu de caractères Big5 était de contraindre un fichier Python unicode dans le caractère correct unsigned long en exploitant le fait que u'\ua7da' a été stockée en interne sous la forme d'un nombre entier 0xa7da soit en 16 bits, soit en 32 bits, selon la façon dont vous avez compilé Python.

TBH, il y avait une bonne part de supposition, puisque je n'ai pas pris la peine de chercher à savoir quel était exactement l'effet des ImageFont.truetype() 's encoding est, mais d'après ce qu'il semble, il n'est pas censé traduire les codages de caractères, mais plutôt permettre à un seul fichier TTF de prendre en charge plusieurs codages de caractères pour les mêmes glyphes, en utilisant le paramètre FT_Select_Charmap() pour passer de l'un à l'autre.

Donc, si je comprends bien, l'interaction de la bibliothèque FreeType avec les fichiers TTF fonctionne de la manière suivante...

#!/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)

...mais c'est à chaque FTT de fournir les informations nécessaires à la mise en œuvre du programme. encoding_maps et rien n'oblige un TTF à en fournir une pour Unicode. En effet, il est peu probable qu'une police créée avant l'adoption de l'Unicode en fournisse une.

En supposant que tout cela soit correct, il n'y a pas de problème avec le TTF - le problème vient simplement du fait que PIL rend un peu difficile l'accès aux glyphes pour les polices qui n'ont pas de correspondance Unicode, et pour lesquelles les glyphes requis sont des unsigned long est supérieur à 255.

4voto

Kenji Noguchi Points 581

Le problème est que les polices ne sont pas strictement conformes à la spécification TrueType. Une solution rapide consiste à utiliser FontForge (vous l'utilisez déjà) et à le laisser assainir les polices.

  1. Ouvrir un fichier de police
  2. Aller à Encoding , puis sélectionnez Reencode
  3. Choisir ISO 10646-1 (Unicode BMP)
  4. Aller à File puis Generate Fonts
  5. Enregistrer en TTF
  6. Exécutez votre script avec les polices nouvellement générées.
  7. Voilà ! Il imprime 我 dans une belle police de caractères !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X