From b876ccbb09cc92497d904b4317a881f7bdad38c7 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 22 Mar 2026 19:19:00 +0100 Subject: [PATCH] afegit fallback a la font_gen afegida deteccio de caracters no definits a font_gen --- data/font/8bithud.fnt | 50 +++++++++---------- data/font/8bithud.gif | Bin 759 -> 837 bytes data/font/gauntlet.fnt | 29 +++++++++++ data/font/gauntlet.gif | Bin 784 -> 959 bytes data/font/subatomic.fnt | 52 ++++++++++---------- data/font/subatomic.gif | Bin 596 -> 648 bytes tools/font_gen/font_gen.py | 97 ++++++++++++++++++++++++++++--------- 7 files changed, 154 insertions(+), 74 deletions(-) diff --git a/data/font/8bithud.fnt b/data/font/8bithud.fnt index a46ffcb..fc23149 100644 --- a/data/font/8bithud.fnt +++ b/data/font/8bithud.fnt @@ -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 # · diff --git a/data/font/8bithud.gif b/data/font/8bithud.gif index f46fe6ce7f9b51032c250ea4dc7d65c926f84b2c..9b08e471d4a678df6c4c97e87f6b6849800d65b0 100644 GIT binary patch delta 228 zcmV5ggpikct85k zUy-oTW1?VjIceB_Y>1~3TbNx2<(X2h>0)(UoTO%(4;=>Blx=p_2}}y<@Z~*Q{n<^B z>wg(LuxapSRo|kep>~1r{$!N05B*y37vaUfhn6pZ? eIH$DbXvn0sNO_2?wnee_skh>eOKxff0028-DsaaD delta 150 zcmV;H0BQfl2KNQ99s&W@lOF<6f5Rgnxp{!mc;6lQB9cZV$>fqw-l88yL;@M44*Rho zo<>zJ*WePkdPS8KZ2rk#1#$)}ln`ibY9 zf(}Y(p+W+xXP}8L+Gmq@GKy%UR2oWYrIrRM>7#XC%4w&behO!%e}V>T2&tx?3P1q> EJ7(@gr2qf` diff --git a/data/font/gauntlet.fnt b/data/font/gauntlet.fnt index 416de87..d91832a 100644 --- a/data/font/gauntlet.fnt +++ b/data/font/gauntlet.fnt @@ -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 # · diff --git a/data/font/gauntlet.gif b/data/font/gauntlet.gif index 945342551d9e61eff232e61b9ece23b463c21b65..0cbdf2ea058b4bbddb084befc3e6660219e765fa 100644 GIT binary patch delta 259 zcmV+e0sQ`u2EPXzM@dFFH(_`HKmhOn00000|Ns9i00000cmP1L8v_CX|C1jA$$!IE zIq7v77Em+BS&Vg|9z|TM7hX6EolT;er=JqOMk|3J9u+8+w35Y+IJErPBT#f^TA-#_ zo-#CSsXGaqr4TY!Q=EHi{^}k#DPgzesDx>S<6FD6y5WCkI1F{z2kFBP<%OtQ;YgnJT=|UT8pC>5+%&}uCi&>mrmV+$CQ5dTdw*|Yn z*_v17*2RzlPrGWhcwUPzZ3T%8;C%PK%x<_gE_s?}8Rzt)z3YzqNYAd8Vx(8BHs~+C JMuS%X06Q`=c8~x7 delta 83 zcmdnbK7mcz-P6s&Jh6hog5d)L2>k#5Uk5}3MK($^GcmrOEYEaO!};FwMM@zTRxDfP kbY|tMyr5UB)|EM3T)k29Bw3q zXPbwjt;ShcqEz_dVqEPdAcPdwSK?$Y1_a!JHr|NigkLq6BODqS_DEHCX#p7|dPFXm QjfqDt$z+pG8c+ZLJ8_FlNB{r; delta 109 zcmV-z0FwWR1=Iwv?*V1kT7P*Md)e)s2lI{e8G5eiT*XuU`q%t`w0e_4vcp z+rN+h4}I?Q", "·": ".", +} + 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()