afegit fallback a la font_gen
afegida deteccio de caracters no definits a font_gen
This commit is contained in:
@@ -101,32 +101,32 @@ columns 15
|
||||
124 1 # |
|
||||
125 4 # }
|
||||
126 4 # ~
|
||||
192 4 # À
|
||||
193 4 # Á
|
||||
200 4 # È
|
||||
201 4 # É
|
||||
205 4 # Í
|
||||
207 4 # Ï
|
||||
210 4 # Ò
|
||||
211 4 # Ó
|
||||
218 4 # Ú
|
||||
220 4 # Ü
|
||||
209 4 # Ñ
|
||||
199 4 # Ç
|
||||
224 4 # à
|
||||
225 4 # á
|
||||
232 4 # è
|
||||
233 4 # é
|
||||
192 6 # À
|
||||
193 6 # Á
|
||||
200 6 # È
|
||||
201 6 # É
|
||||
205 6 # Í
|
||||
207 6 # Ï
|
||||
210 6 # Ò
|
||||
211 6 # Ó
|
||||
218 6 # Ú
|
||||
220 6 # Ü
|
||||
209 6 # Ñ
|
||||
199 6 # Ç
|
||||
224 5 # à
|
||||
225 5 # á
|
||||
232 5 # è
|
||||
233 5 # é
|
||||
237 4 # í
|
||||
239 4 # ï
|
||||
242 4 # ò
|
||||
243 4 # ó
|
||||
250 4 # ú
|
||||
252 4 # ü
|
||||
241 4 # ñ
|
||||
231 4 # ç
|
||||
161 4 # ¡
|
||||
191 4 # ¿
|
||||
242 5 # ò
|
||||
243 5 # ó
|
||||
250 5 # ú
|
||||
252 5 # ü
|
||||
241 5 # ñ
|
||||
231 5 # ç
|
||||
161 2 # ¡
|
||||
191 6 # ¿
|
||||
171 4 # «
|
||||
187 4 # »
|
||||
183 4 # ·
|
||||
183 2 # ·
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 759 B After Width: | Height: | Size: 837 B |
@@ -97,3 +97,32 @@ columns 15
|
||||
121 6 # y
|
||||
122 7 # z
|
||||
126 6 # ~
|
||||
192 6 # À
|
||||
193 6 # Á
|
||||
200 7 # È
|
||||
201 7 # É
|
||||
205 6 # Í
|
||||
207 6 # Ï
|
||||
210 7 # Ò
|
||||
211 7 # Ó
|
||||
218 6 # Ú
|
||||
220 6 # Ü
|
||||
209 7 # Ñ
|
||||
199 7 # Ç
|
||||
224 6 # à
|
||||
225 6 # á
|
||||
232 7 # è
|
||||
233 7 # é
|
||||
237 6 # í
|
||||
239 6 # ï
|
||||
242 7 # ò
|
||||
243 7 # ó
|
||||
250 6 # ú
|
||||
252 6 # ü
|
||||
241 7 # ñ
|
||||
231 7 # ç
|
||||
161 2 # ¡
|
||||
191 6 # ¿
|
||||
171 5 # «
|
||||
187 5 # »
|
||||
183 2 # ·
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 784 B After Width: | Height: | Size: 959 B |
@@ -101,32 +101,32 @@ columns 15
|
||||
124 1 # |
|
||||
125 3 # }
|
||||
126 4 # ~
|
||||
192 3 # À
|
||||
193 3 # Á
|
||||
200 3 # È
|
||||
201 3 # É
|
||||
205 3 # Í
|
||||
207 3 # Ï
|
||||
210 3 # Ò
|
||||
211 3 # Ó
|
||||
218 3 # Ú
|
||||
220 3 # Ü
|
||||
209 3 # Ñ
|
||||
199 3 # Ç
|
||||
224 3 # à
|
||||
225 3 # á
|
||||
232 3 # è
|
||||
233 3 # é
|
||||
237 3 # í
|
||||
239 3 # ï
|
||||
242 3 # ò
|
||||
243 3 # ó
|
||||
250 3 # ú
|
||||
252 3 # ü
|
||||
241 3 # ñ
|
||||
192 5 # À
|
||||
193 5 # Á
|
||||
200 4 # È
|
||||
201 4 # É
|
||||
205 1 # Í
|
||||
207 1 # Ï
|
||||
210 5 # Ò
|
||||
211 5 # Ó
|
||||
218 5 # Ú
|
||||
220 5 # Ü
|
||||
209 5 # Ñ
|
||||
199 5 # Ç
|
||||
224 4 # à
|
||||
225 4 # á
|
||||
232 4 # è
|
||||
233 4 # é
|
||||
237 1 # í
|
||||
239 1 # ï
|
||||
242 4 # ò
|
||||
243 4 # ó
|
||||
250 4 # ú
|
||||
252 4 # ü
|
||||
241 4 # ñ
|
||||
231 3 # ç
|
||||
161 3 # ¡
|
||||
191 3 # ¿
|
||||
161 1 # ¡
|
||||
191 4 # ¿
|
||||
171 3 # «
|
||||
187 3 # »
|
||||
183 3 # ·
|
||||
183 1 # ·
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 596 B After Width: | Height: | Size: 648 B |
@@ -40,6 +40,19 @@ _ASCII_CHARS = [chr(i) for i in range(32, 127)] # 95 chars: espacio
|
||||
_EXTENDED_CHARS = list("ÀÁÈÉÍÏÒÓÚÜÑÇàáèéíïòóúüñç¡¿«»·") # 29 chars ES/CA/VA
|
||||
ALL_CHARS = _ASCII_CHARS + _EXTENDED_CHARS # 124 total
|
||||
|
||||
# Caracteres de fallback para TTFs sin soporte de acentos/especiales.
|
||||
# Si el TTF no tiene el char, se dibuja el fallback en su celda del bitmap.
|
||||
# El .fnt sigue registrando el codepoint original → texto localizado funciona.
|
||||
CHAR_FALLBACKS: dict[str, str] = {
|
||||
"À": "A", "Á": "A", "È": "E", "É": "E",
|
||||
"Í": "I", "Ï": "I", "Ò": "O", "Ó": "O",
|
||||
"Ú": "U", "Ü": "U", "Ñ": "N", "Ç": "C",
|
||||
"à": "a", "á": "a", "è": "e", "é": "e",
|
||||
"í": "i", "ï": "i", "ò": "o", "ó": "o",
|
||||
"ú": "u", "ü": "u", "ñ": "n", "ç": "c",
|
||||
"¡": "!", "¿": "?", "«": "<", "»": ">", "·": ".",
|
||||
}
|
||||
|
||||
|
||||
def _write_fnt(
|
||||
output_fnt: str,
|
||||
@@ -190,32 +203,69 @@ def render_font(
|
||||
ascent, descent = font.getmetrics()
|
||||
box_height = box_height_override if box_height_override is not None else (ascent + abs(descent))
|
||||
|
||||
# Filtrar chars: solo incluir los que el TTF soporta realmente.
|
||||
# Un char se descarta si su advance es 0 (el TTF no lo tiene) y no es un
|
||||
# espacio. Evita que chars sin glifo aparezcan con width=1 en el .fnt,
|
||||
# lo que causaría solapamiento masivo al renderizar texto localizado.
|
||||
chars_to_render = [
|
||||
ch for ch in ALL_CHARS
|
||||
if ch.isspace() or font.getlength(ch) >= 1.0
|
||||
]
|
||||
skipped = [ch for ch in ALL_CHARS if ch not in chars_to_render]
|
||||
if skipped:
|
||||
print(f"Omitidos : {len(skipped)} chars sin soporte en este TTF: {''.join(skipped)}")
|
||||
# Calcular y_offset antes de la clasificación (necesario para detectar .notdef)
|
||||
cap_tops = [font.getbbox(ch)[1] for ch in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" if font.getbbox(ch)]
|
||||
y_offset = -round(sum(cap_tops) / len(cap_tops)) if cap_tops else 0
|
||||
|
||||
# Detectar el glifo .notdef: algunos TTF devuelven un glifo sustituto (con
|
||||
# advance > 0 y bbox válido) para chars que no tienen en su cmap. Esto provoca
|
||||
# falsos positivos en el test getlength >= 1. Se renderiza U+FFFE (garantizado
|
||||
# ausente en cualquier fuente de uso normal) y se guardan sus píxeles como
|
||||
# referencia. Cualquier char con píxeles idénticos se considera no soportado.
|
||||
_tmp_w = box_width_override or 32
|
||||
_nd_bbox = font.getbbox(chr(0xFFFE))
|
||||
if _nd_bbox:
|
||||
_nd_img = Image.new("RGBA", (_tmp_w, box_height), (0, 0, 0, 0))
|
||||
ImageDraw.Draw(_nd_img).text((-_nd_bbox[0], y_offset), chr(0xFFFE),
|
||||
font=font, fill=(255, 255, 255, 255))
|
||||
_notdef_bytes = _nd_img.tobytes()
|
||||
else:
|
||||
_notdef_bytes = None
|
||||
|
||||
def _is_notdef(ch: str) -> bool:
|
||||
"""True si el char renderiza el glifo .notdef en lugar de un glifo real."""
|
||||
if _notdef_bytes is None:
|
||||
return False
|
||||
bbox = font.getbbox(ch)
|
||||
if not bbox:
|
||||
return True
|
||||
img = Image.new("RGBA", (_tmp_w, box_height), (0, 0, 0, 0))
|
||||
ImageDraw.Draw(img).text((-bbox[0], y_offset), ch, font=font, fill=(255, 255, 255, 255))
|
||||
return img.tobytes() == _notdef_bytes
|
||||
|
||||
# Clasificar chars: soportados nativamente, con fallback, o sin soporte.
|
||||
# Un char tiene soporte nativo si su advance >= 1 Y no renderiza .notdef.
|
||||
# Si no, se busca en CHAR_FALLBACKS. Sin soporte y sin fallback, se omite.
|
||||
chars_to_render: list[str] = []
|
||||
char_render_as: dict[str, str] = {} # char → qué char dibujar realmente
|
||||
truly_skipped: list[str] = []
|
||||
|
||||
for ch in ALL_CHARS:
|
||||
if ch.isspace():
|
||||
chars_to_render.append(ch)
|
||||
char_render_as[ch] = ch
|
||||
elif font.getlength(ch) >= 1.0 and not _is_notdef(ch):
|
||||
chars_to_render.append(ch)
|
||||
char_render_as[ch] = ch
|
||||
elif ch in CHAR_FALLBACKS and font.getlength(CHAR_FALLBACKS[ch]) >= 1.0:
|
||||
chars_to_render.append(ch)
|
||||
char_render_as[ch] = CHAR_FALLBACKS[ch]
|
||||
else:
|
||||
truly_skipped.append(ch)
|
||||
|
||||
if truly_skipped:
|
||||
print(f"Omitidos : {len(truly_skipped)} chars sin soporte ni fallback: {''.join(truly_skipped)}")
|
||||
fallback_used = [ch for ch, r in char_render_as.items() if r != ch]
|
||||
if fallback_used:
|
||||
print(f"Fallback : {len(fallback_used)} chars con fallback: " +
|
||||
" ".join(f"{ch}→{char_render_as[ch]}" for ch in fallback_used))
|
||||
|
||||
# Medir advance width tipográfico: solo se usa para calcular box_width del canvas
|
||||
# cuando el usuario no lo especifica. El ancho real del .fnt se mide desde píxeles.
|
||||
char_widths: dict[str, int] = {}
|
||||
for ch in chars_to_render:
|
||||
char_widths[ch] = max(1, int(font.getlength(ch)))
|
||||
|
||||
# Calcular el offset vertical para eliminar el espacio en blanco en la parte
|
||||
# superior de la celda. Muchas fuentes bitmap tienen un em-box más grande que
|
||||
# los píxeles visibles (ascent incluye espacio interno). Usamos las letras
|
||||
# mayúsculas como referencia de "cap height": su bbox[1] indica cuántos
|
||||
# píxeles en blanco hay sobre los caracteres más altos, y restamos ese valor
|
||||
# para que las mayúsculas queden alineadas al borde superior de la celda.
|
||||
cap_tops = [font.getbbox(ch)[1] for ch in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" if font.getbbox(ch)]
|
||||
y_offset = -round(sum(cap_tops) / len(cap_tops)) if cap_tops else 0
|
||||
render_ch = char_render_as[ch]
|
||||
char_widths[ch] = max(1, int(font.getlength(render_ch)))
|
||||
|
||||
# box_width: anchura de cada celda en el bitmap.
|
||||
# Si el usuario la especifica, se usa tal cual. Si no, se calcula como el
|
||||
@@ -244,7 +294,8 @@ def render_font(
|
||||
cell_x = col * box_width
|
||||
cell_y = row * box_height
|
||||
|
||||
bbox = font.getbbox(ch)
|
||||
draw_ch = char_render_as[ch] # char que realmente se dibuja (puede ser fallback)
|
||||
bbox = font.getbbox(draw_ch)
|
||||
if not bbox:
|
||||
# Sin glifos visibles (ej. espacio): celda vacía, correcto.
|
||||
continue
|
||||
@@ -261,7 +312,7 @@ def render_font(
|
||||
# glifo al inicio de la celda, compensando el bearing izquierdo.
|
||||
char_img = Image.new("RGBA", (box_width, box_height), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(char_img)
|
||||
draw.text((-bbox[0], y_offset), ch, font=font, fill=(255, 255, 255, 255))
|
||||
draw.text((-bbox[0], y_offset), draw_ch, font=font, fill=(255, 255, 255, 255))
|
||||
|
||||
# Umbralizar alpha y volcar al buffer de índices
|
||||
char_bytes = char_img.tobytes()
|
||||
|
||||
Reference in New Issue
Block a user