Por que texto estilizado vira quadradinho (tofu): fallback de fontes e Unicode
Você cola um nome em negrito cursivo no perfil, ele aparece perfeito no seu celular, e um amigo avisa que só vê uma fileira de quadradinhos. Nada está errado com o texto. O problema é que o aparelho dele não tinha como d
Você cola um nome em negrito cursivo no perfil, ele aparece perfeito no seu celular, e um amigo avisa que só vê uma fileira de quadradinhos. Nada está errado com o texto. O problema é que o aparelho dele não tinha como desenhar aqueles caracteres. Este post explica o porquê e mostra duas armadilhas de código que costumam aparecer junto.
O que é o "tofu"
O quadradinho vazio é chamado de tofu, porque lembra um bloco de tofu. Ele não é um caractere que você digita. É um sintoma de renderização: o sistema pediu uma forma (glyph) para aquele caractere em todas as fontes disponíveis, não achou nenhuma e desenhou um substituto. A família de fontes Noto, do Google, foi nomeada justamente com esse objetivo, "no tofu".
Texto estilizado não é fonte
Os geradores de "letras diferentes" não instalam fonte nenhuma. Eles trocam cada letra comum por outro caractere Unicode que parece negrito, cursivo ou gótico:
const normal = "Ghost";
const estilizado = "𝓖𝓱𝓸𝓼𝓽";
console.log(normal.length); // 5
console.log(estilizado.length); // 10
console.log([...estilizado].length); // 5
A maior parte desses estilos vem do bloco Mathematical Alphanumeric Symbols (U+1D400 a U+1D7FF), criado para notação matemática. Como cada letra estilizada está fora do plano básico do Unicode, em JavaScript ela ocupa duas unidades UTF-16. Por isso .length devolve 10 e não 5.
Armadilha 1: contar caracteres com .length
Se o seu formulário tem limite de caracteres (um nick de jogo, por exemplo), .length conta unidades UTF-16 e vai dizer que 𝓖𝓱𝓸𝓼𝓽 tem 10. O spread [...texto] conta pontos de código e dá 5. Mas ainda existe um terceiro caso: marcas combinantes, como as do texto "zalgo" ou de um acento decomposto.
const seg = new Intl.Segmenter("pt-BR", { granularity: "grapheme" });
const contar = (t) => ({
utf16: t.length,
codepoints: [...t].length,
graphemes: [...seg.segment(t)].length,
});
Num texto de teste com 6 letras e 13 marcas combinantes empilhadas sobre elas, utf16 e codepoints devolvem 19, enquanto graphemes devolve 6, que é o que a pessoa enxerga. Escolha a contagem que combina com a regra do seu produto e deixe o limite claro para o usuário.
Armadilha 2: pilha de fontes sem fallback
O navegador percorre a lista de font-family até encontrar uma fonte que tenha o caractere. Se a lista termina cedo, sobra o tofu. Termine sempre com uma família genérica:
body {
font-family: Inter, system-ui, sans-serif;
}
Com sans-serif no fim, o sistema ainda pode usar a fonte de reserva dele para os caracteres que a sua fonte principal não cobre. Isso não garante que todo caractere apareça em todo aparelho, mas reduz muito o problema. E atenção: font-family não "estiliza" texto Unicode estilizado. São caracteres diferentes, não a mesma letra com outra fonte.
Acessibilidade
Leitores de tela podem ler caracteres estilizados pelos nomes longos que o Unicode dá a eles, o que atrapalha quem usa esse recurso. Mantenha em texto comum o que é importante, como nome, data, endereço e link, e use o estilizado só como enfeite.
Checklist rápido
- Termine a pilha de
font-familycomsans-serifousystem-ui. - Conte caracteres com
[...texto]ouIntl.Segmenter, não com.length. - Teste a página em um segundo aparelho, de preferência um Android mais antigo.
- Não coloque informação importante só em texto estilizado.
Se quiser um lugar para ver esses estilos em ação e testá-los em outro celular, eu mantenho um gerador gratuito em português chamado Letra Diferente.
Para saber mais
- Fallback font (em inglês): como o sistema escolhe uma fonte substituta.
- Mathematical Alphanumeric Symbols (em inglês): o bloco por trás de muitos estilos de negrito e cursivo.
- Intl.Segmenter no MDN (em inglês): contagem de grafemas em JavaScript.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.