python - programación - Genera imágenes de caracteres con una fuente cuyo nombre no se puede decodificar correctamente
python 3 pdf (2)
Estoy creando imágenes de guión de sello chino. Tengo tres fuentes de tipo verdadero para esta tarea ( Jin_Wen_Da_Zhuan_Ti.7z , Zhong_Guo_Long_Jin_Shi_Zhuan.7z , Zhong_Yan_Yuan_Jin_Wen.7z , solo con fines de prueba) A continuación se presentan las apariciones en Microsoft Word.
del carácter chino "我" (I / me). Aquí está mi script de 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'')
donde está el fragmento de núcleo
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)
Por ejemplo, si coloca esas fuentes en la carpeta ./fonts
y lo llama con
create_testing_images([''我''], ''fonts/金文大篆体.ttf'', save_to_folder=''test'')
el script creará ./test/金文大篆体/我.gif
en su sistema de archivos.
Ahora el problema es que, aunque funciona bien con la primera fuente 金文 大 篆体 .ttf (en Jin_Wen_Da_Zhuan_Ti.7z), la secuencia de comandos no funciona en las otras dos fuentes, incluso si se pueden representar correctamente en Microsoft Word: para 龍金石 篆 .ttf (en Zhong_Guo_Long_Jin_Shi_Zhuan.7z), no dibuja nada por lo que bbox
será None
; para 中研院 金文 .ttf (en Zhong_Yan_Yuan_Jin_Wen.7z), dibujará un marco negro sin caracteres en la imagen.
y, por lo tanto, no pasa la prueba de criterion
, cuyo propósito es probar una salida completamente negra. FontForge para verificar las propiedades de las fuentes, y encontré que la primera fuente 金文 大 篆体 .ttf (en Jin_Wen_Da_Zhuan_Ti.7z) usa UnicodeBmp
mientras que los otros dos usan Big5hkscs
que no es el esquema de codificación de mi sistema. Esta puede ser la razón por la que los nombres de las fuentes son irreconocibles en mi sistema:
En realidad, también trato de resolver esto tratando de obtener la fuente con el nombre de fuente desordenado. Intenté pycairo
después de instalar esas fuentes:
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'')
Esto funciona bien otra vez con 金文 大 篆体 .ttf (en Jin_Wen_Da_Zhuan_Ti.7z):
Pero todavía no con los demás. Por ejemplo: ni ctx.select_font_face(''中國龍金石篆'')
(que informa _cairo_win32_scaled_font_ucs4_to_index:GetGlyphIndicesW
) ni ctx.select_font_face(''¤¤°êÀsª÷¥Û½f'')
(El último nombre es el código desordenado que se muestra en el visor de fuentes como se muestra arriba, obtenido por una línea del código Mathematica ToCharacterCode["中國龍金石篆", "CP950"] // FromCharacterCode
donde CP950
es la página de códigos de Big5.)
Así que creo que he hecho todo lo posible para abordar este problema, pero aún no puedo resolverlo. También he encontrado otras formas como cambiar el nombre de la fuente con FontForge o cambiar la codificación del sistema a Big5, pero aún preferiría una solución que involucre solo a Python y, por lo tanto, necesite menos acciones adicionales del usuario. Cualquier consejo será muy apreciado. Gracias.
Para los moderadores de stackoverflow : este problema puede parecer "demasiado localizado" a primera vista, pero puede ocurrir en otros idiomas / otras codificaciones / otras fuentes, y la solución puede generalizarse a otros casos, así que no la cierre. con esta razon Gracias.
ACTUALIZACIÓN : Extrañamente Mathematica puede reconocer el nombre de la fuente en CP936 (GBK, que puede considerarse como la codificación de mi sistema). Toma 中國 龍 金石 篆 .ttf (en Zhong_Guo_Long_Jin_Shi_Zhuan.7z) para ver un ejemplo:
Pero la configuración de ctx.select_font_face(''ÖÐøý½ðʯ*'')
tampoco funciona, lo que creará la imagen del personaje con la fuente predeterminada.
El comentario de Silvia sobre el OP ...
Es posible que desee considerar la posibilidad de especificar el parámetro de
encoding
comoImageFont.truetype(font_path,font_size,encoding="big5")
... te lleva hasta la mitad, pero parece que también tienes que traducir manualmente los caracteres Unicode si no estás utilizando una fuente Unicode.
Para las fuentes que utilizan la codificación "big5hkscs", tuve que hacer esto ...
>>> u = u''/u6211'' # Unicode for 我
>>> u.encode(''big5hkscs'')
''/xa7/xda''
... luego usa u''/ua7da''
para obtener el glifo correcto, que es un poco extraño, pero parece ser la única forma de pasar un carácter de múltiples bytes a PIL.
El siguiente código me funciona tanto en Python 2.7.4 como en Python 3.3.1, con 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)
Tenga en cuenta que cambié el nombre de los archivos de fuentes a los mismos nombres que los archivos 7zip. Intento evitar el uso de caracteres no ASCII en los ejemplos de código, ya que a veces se arruinan al copiar / pegar.
Este ejemplo debería funcionar bien para los tipos declarados en ENCODING_MAP
, que pueden extenderse si es necesario (consulte las cadenas de codificación de FreeType para ver las codificaciones de FreeType válidas), pero deberá cambiar parte del código en los casos en que el str.encode()
Python str.encode()
no produce una cadena de varios bytes de longitud 2.
Actualizar
Si el problema está en el archivo ttf, ¿cómo podría encontrar la respuesta en el código fuente de PIL y FreeType? Arriba, parece que dices que la culpa es de PIL, pero ¿por qué deberías pasar unicode_char.encode (...). Decode (...) cuando solo quieres unicode_char?
Según tengo entendido, el TrueType fuente TrueType se desarrolló antes de que Unicode se adoptara ampliamente, por lo que si deseaba crear una fuente china en ese entonces, tendría que haber utilizado una de las codificaciones que estaban en uso en ese momento, y China Había estado usando Big5 desde mediados de los años ochenta.
Es lógico, entonces, que tenía que haber una manera de recuperar los glifos de un TTF codificado en Big5 usando las codificaciones de caracteres Big5.
El código C para representar una cadena con PIL comienza con la función font_render()
y, finalmente, llama a FT_Get_Char_Index()
para ubicar el glifo correcto, dado el código de carácter como un unsigned long
.
Sin embargo, la función font_getchar()
PIL, que produce ese unsigned long
solo acepta string
tipos de string
y unicode
Python, y dado que no parece hacer ninguna traducción de las codificaciones de caracteres en sí misma, parece que la única forma de obtener el valor correcto para el conjunto de caracteres Big5 consistió en forzar a un carácter unicode
Python en el valor unsigned long
correcto aprovechando el hecho de que u''/ua7da''
se almacenó internamente como el entero 0xa7da
, ya sea en 16 bits o 32 bits, dependiendo de cómo compiló Python.
TBH, hubo una gran cantidad de conjeturas involucradas, ya que no me molesté en investigar cuál es exactamente el efecto del ImageFont.truetype()
de ImageFont.truetype()
de ImageFont.truetype()
, pero por su aspecto, no se supone que haga ninguna traducción de codificaciones de caracteres, sino más bien para permitir que un solo archivo TTF admita varias codificaciones de caracteres de los mismos glifos, utilizando la función FT_Select_Charmap()
para alternar entre ellos.
Entonces, como lo entiendo, la interacción de la biblioteca FreeType con los archivos TTF funciona de esta manera ...
#!/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)
... pero depende de cada TTF proporcionar la variable encoding_maps
, y no hay ningún requisito para que un TTF proporcione uno para Unicode. De hecho, es poco probable que una fuente creada antes de la adopción de Unicode tenga.
Suponiendo que todo eso es correcto, entonces no hay nada malo con el TTF: el problema es que PIL hace que sea un poco incómodo acceder a los glifos de las fuentes que no tienen un mapa Unicode, y para los cuales el código de caracteres unsigned long
del glifo requerido es mayor de 255.
El problema es que las fuentes no se ajustan estrictamente a la especificación TrueType. Una solución rápida es utilizar FontForge (ya lo está utilizando) y dejar que desinfecte las fuentes.
- Abrir un archivo de fuente
- Vaya a
Encoding
, luego seleccioneReencode
- Elija
ISO 10646-1 (Unicode BMP)
- Ir a
File
luegoGenerate Fonts
- Guardar como TTF
- Ejecuta tu script con las fuentes recién generadas
- Voila! Imprime 我 en hermosa fuente!