Unicode y Emoji: de puntos de código a renderizado

CodificaciónEncoding, hashing, and QR

Unicode: el estándar de codificación que unifica todos los caracteres

Unicode asigna un número único (punto de código) a cada carácter de casi todos los sistemas de escritura del mundo.

Concepto Descripción Ejemplo
Punto de código (Code Point) Número único del carácter U+4E2D (中)
Plano de código Cada 65536 puntos de código forma un plano BMP (plano 0)
BMP Plano multilingüe básico, U+0000~U+FFFF La mayoría de caracteres comunes
Plano suplementario U+10000~U+10FFFF Emoji, escrituras antiguas, caracteres raros

1. Familia de codificaciones UTF

Codificación Longitud en bytes Codificación de "中" Características
UTF-8 1-4 bytes E4 B8 AD Longitud variable, compatible con ASCII, preferido para Web
UTF-16 2 o 4 bytes 4E2D Longitud variable, uso interno en JS
UTF-32 4 bytes fijo 00004E2D Longitud fija, alto consumo de memoria

Reglas de codificación UTF-8

Rango de puntos de código Bytes Plantilla de codificación
U+0000 ~ U+007F 1 0xxxxxxx
U+0080 ~ U+07FF 2 110xxxxx 10xxxxxx
U+0800 ~ U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx
U+10000 ~ U+10FFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Ejemplo de codificación

Carácter: 中
Punto de código: U+4E2D = 0100 1110 0010 1101

UTF-8 (3 bytes):
  1110xxxx 10xxxxxx 10xxxxxx
  1110 0100 10 111000 10 101101
  = E4 B8 AD

UTF-16 (2 bytes):
  4E2D

UTF-32 (4 bytes):
  00004E2D

2. Trampas de Unicode en JavaScript

Longitud de cadena incorrecta

'中'.length              // 1 ✅
'𠮷'.length             // 2 ❌ (carácter del plano suplementario, par suplente)
'👨‍👩‍👧‍👦'.length           // 11 ❌ (secuencia ZWJ de Emoji)
'🏳️‍🌈'.length             // 6 ❌ (Emoji de bandera)

Causa: JS usa UTF-16

Los caracteres del plano suplementario (punto de código > U+FFFF) requieren pares suplentes (2 unidades de código UTF-16):

Carácter: 𠮷 (U+20BB7)
Par suplente UTF-16: D842 DF87
JS: length = 2 (dos unidades de código)

Forma correcta de procesar

// Método 1: operador de expansión
[...'𠮷'].length           // 1 ✅
[...'👨‍👩‍👧‍👦'].length         // 1 ✅ (pero la secuencia ZWJ se cuenta como uno)

// Método 2: Array.from
Array.from('𠮷').length    // 1 ✅

// Método 3: for...of
for (const char of '𠮷👨‍👩‍👧‍👦') {
  console.log(char);  // iteración carácter por carácter
}

Operaciones con puntos de código

// Obtener punto de código
'中'.codePointAt(0)        // 20013 (0x4E2D)
'𠮷'.codePointAt(0)        // 134047 (0x20BB7)

// Crear carácter desde punto de código
String.fromCodePoint(0x4E2D)  // '中'
String.fromCodePoint(0x20BB7) // '𠮷'

// Nota: charCodeAt no puede procesar el plano suplementario
'𠮷'.charCodeAt(0)         // 55362 (solo el primer suplente)

3. Análisis profundo de la codificación Emoji

Cuatro tipos de Emoji

Tipo Ejemplo Punto de código Longitud
Emoji básico 😀 U+1F600 1 (punto de código)
Emoji con modificador 👩🏽 U+1F469 + U+1F3FD 2 (puntos de código)
Secuencia ZWJ 👨‍👩‍👧‍👦 Múltiples puntos de código + ZWJ 7 (puntos de código)
Emoji de bandera 🇨🇳 U+1F1E8 + U+1F1F3 2 (puntos de código)

Conector de ancho cero (ZWJ)

ZWJ (U+200D) conecta múltiples Emoji en un Emoji compuesto:

👨 (U+1F468) + ZWJ (U+200D) + 👩 (U+1F469) + ZWJ (U+200D) + 👧 (U+1F467) + ZWJ (U+200D) + 👦 (U+1F466)
= 👨‍👩‍👧‍👦 (familia de cuatro)

Modificadores de tono de piel

Los modificadores de tono de piel (U+1F3FB ~ U+1F3FF) cambian el tono de piel del Emoji:

👩 (U+1F469) + 🏽 (U+1F3FD) = 👩🏽 (mujer con tono de piel medio)
Modificador Punto de código Tono de piel
U+1F3FB 🏻 Tono claro
U+1F3FC 🏼 Tono medio claro
U+1F3FD 🏽 Tono medio
U+1F3FE 🏾 Tono medio oscuro
U+1F3FF 🏿 Tono oscuro

Emoji de bandera

Las banderas se componen de dos símbolos indicadores regionales (ISO 3166-1 alpha-2):

🇨 (U+1F1E8) + 🇳 (U+1F1F3) = 🇨🇳 (China)
🇺 (U+1F1FA) + 🇸 (U+1F1F8) = 🇺🇸 (Estados Unidos)
🇯 (U+1F1EF) + 🇵 (U+1F1F5) = 🇯🇵 (Japón)

4. Selectores de variante

VS16 (U+FE0F) convierte la presentación de texto a presentación Emoji:

❤ (U+2764)           → presentación de texto: ❤ (corazón negro)
❤️ (U+2764 + U+FE0F) → presentación Emoji: ❤️ (corazón rojo)
Carácter Sin VS16 Con VS16
☕ ☕ (texto) ☕️ (Emoji)
⚡ ⚡ (texto) ⚡️ (Emoji)
❤ ❤ (texto) ❤️ (Emoji)

5. Uso de bibliotecas de herramientas para procesar Unicode

Codificación y decodificación Unicode

  1. Abre la herramienta de codificación/decodificación Unicode
  2. Ingresa texto o punto de código
  3. Revisa los resultados de la conversión:
    • Texto → secuencia de escape Unicode (\u4E2D)
    • Punto de código → carácter
    • Secuencia de bytes UTF-8/16/32

Codificación Emoji

  1. Abre la herramienta de codificación Emoji
  2. Ingresa un Emoji
  3. Revisa la información completa de codificación:
    • Secuencia de puntos de código
    • Bytes UTF-8
    • Descomposición ZWJ/modificadores
    • Nombre corto CLDR

6. Trampas comunes y soluciones

Trampa 1: Truncamiento de cadenas

// Error: el truncamiento puede cortar pares suplentes
text.substring(0, 10)  // puede producir caracteres inválidos

// Correcto: truncar por punto de código
function sliceByCodePoint(str, start, end) {
  const chars = [...str];
  return chars.slice(start, end).join('');
}

Trampa 2: Inversión de cadenas

// Error: la inversión destruye caracteres combinados
'👨‍👩‍👧‍👦'.split('').reverse().join('')  // texto ilegible

// Correcto: invertir por punto de código
[...'👨‍👩‍👧‍👦'].reverse().join('')  // ✅

Trampa 3: Coincidencia con expresiones regulares

// Error: . no coincide con caracteres del plano suplementario
/^.$/.test('𠮷')  // false

// Correcto: usar flag u
/^.$/u.test('𠮷')  // true

Trampa 4: Ordenación

// Ordenación por defecto según unidades de código UTF-16
['中', '𠮷', 'a'].sort()  // puede no ser el esperado

// Correcto: ordenar por punto de código
['中', '𠮷', 'a'].sort((a, b) =>
  a.codePointAt(0) - b.codePointAt(0)
)

7. API prácticas

// Normalización (manejar equivalencia de caracteres combinados)
'café'.normalize('NFC')   // café (combinado)
'café'.normalize('NFD')   // café (descompuesto)

// Detectar Emoji
function isEmoji(char) {
  const code = char.codePointAt(0);
  return (code >= 0x1F600 && code <= 0x1F64F) ||  // Emoticonos
         (code >= 0x1F300 && code <= 0x1F5FF) ||  // Símbolos varios
         (code >= 0x1F680 && code <= 0x1F6FF) ||  // Transporte
         (code >= 0x1F900 && code <= 0x1F9FF);    // Suplementario
}

// Obtener la longitud real de los caracteres
function graphemeLength(str) {
  const segmenter = new Intl.Segmenter('es', { granularity: 'grapheme' });
  return [...segmenter.segment(str)].length;
}

graphemeLength('👨‍👩‍👧‍👦')  // 1 ✅
graphemeLength('🏳️‍🌈')    // 1 ✅
#Unicode#Emoji#UTF-8#字符编码#码点