Em cenários empresariais, é comum utilizar sockets para transmitir conteúdo textual entre pontos A e B. No entanto, ao lidar com emojis ou símbolos especiais, foi relatado que, após a transmissão:
- No iOS, os emojis são substituídos por interrogações.
- Em dispositivos PC, os caracteres aparecem como texto corrompido.
O problema está relacionado à má manipulação do conjunto de caracteres UTF-8 durante o processamento de emojis e outros símbolos complexos. Esses elementos pertencem a áreas específicas da tabela Unicode que requerem quatro bytes para representação adequada.
Entendendo o Unicode
O Unicode é um padrão global criado para atribuir uma codificação exclusiva a todos os caracteres conhecidos, desde letras até hieróglifos antigos. Abaixo estão alguns conceitos importantes associados ao Unicode:
- Glyphs (Caracteres): Representam unidades de escrita, como letras, números e sinais de pontuação.
- Pontos de código (Code Points): São identificadores numéricos únicos atribuídos a cada caractere Unicode, geralmente expressos na forma U+XXXX.
- Formas de codiifcação: Métodos para converter esses pontos de código em sequências de bytes, incluindo UTF-8, UTF-16 e UTF-32.
Planos Unicode
O Unicode organiza seus caracteres em planos distintos:
- BMP (Plano Multilíngue Básico): Contém caracteres amplamente utilizados, de U+0000 a U+FFFF.
- SMP (Plano Multilíngue Suplementar): Inclui símbolos antigos e musicais, de U+10000 a U+1FFFF.
- SIP (Plano Ideográfico Suplementar): Focado em ideogramas chineses, japoneses e coreanos, de U+20000 a U+2FFFF.
Codificações Unicode Comuns
- UTF-8: Codificação variável de 1 a 4 bytes, compatível com ASCII.
- UTF-16: Usa 2 ou 4 bytes, dependendo se o caractere está dentro ou fora do BMP.
- UTF-32: Usa sempre 4 bytes, sendo mais simples mas menos eficiente em termos de espaço.
Análise do Problema
A raiz do problema está na conversão inadequada dos emojis para ArrayBuffer antes do envio via socket. O método utilizado emprega uma lógica personalizada baseada em charCodeAt, que não suporta corretamente caracteres que ultrapassam o BMP.
Aqui está uma versão simplificada do algoritmo original:
function encodeString(value) {
let buffer = [];
for (let i = 0; i < value.length; i++) {
let code = value.charCodeAt(i);
if (code <= 0x7F) {
buffer.push(code);
} else if (code <= 0x7FF) {
buffer.push(0xC0 | (code >> 6), 0x80 | (code & 0x3F));
} else if (code <= 0xFFFF) {
buffer.push(0xE0 | (code >> 12),
0x80 | ((code >> 6) & 0x3F),
0x80 | (code & 0x3F));
}
}
return new Uint8Array(buffer);
}
Como solução alternativa, pode-se usar o método codePointAt para tratar corretamente caracteres multibyte:
function encodeWithCodePoint(value) {
let buffer = [];
let position = 0;
while (position < value.length) {
let code = value.codePointAt(position);
position += (code > 0xFFFF) ? 2 : 1;
if (code <= 0x7F) {
buffer.push(code);
} else if (code <= 0x7FF) {
buffer.push(0xC0 | (code >> 6), 0x80 | (code & 0x3F));
} else if (code <= 0xFFFF) {
buffer.push(0xE0 | (code >> 12),
0x80 | ((code >> 6) & 0x3F),
0x80 | (code & 0x3F));
} else if (code <= 0x10FFFF) {
buffer.push(0xF0 | (code >> 18),
0x80 | ((code >> 12) & 0x3F),
0x80 | ((code >> 6) & 0x3F),
0x80 | (code & 0x3F));
}
}
return new Uint8Array(buffer);
}
Solução Mais Simples
A abordagem ideal seria utilizar APIs modernas como TextEncoder e TextDecoder para lidar com a codificação de forma nativa:
// Codificação
const encoder = new TextEncoder();
const encodedData = encoder.encode('😀 Olá');
console.log(encodedData); // Uint8Array contendo os bytes codificados
// Decodificação
const decoder = new TextDecoder('utf-8');
const decodedString = decoder.decode(new Uint8Array([240, 159, 152, 128, 79, 108, 97]));
console.log(decodedString); // "😀 Olá"
Considerações Finais
Para evitar problemas futuros:
- Não use
charCodeAtao lidar com emojis ou caracteres fora do BMP. Opte porcodePointAt. - Prefira APIs como
TextEncodereTextDecoderpara garantir uma codificação robusta e padronizada.