Unicode et Emoji : des points de code au rendu

Unicode : la norme d'encodage qui unifie tous les caractères

Unicode attribue un numéro unique (point de code) à chaque caractère de presque tous les systèmes d'écriture du monde.

Concept Description Exemple
Point de code (Code Point) Numéro unique du caractère U+4E2D (中)
Plan de code Chaque 65536 points de code forme un plan BMP (plan 0)
BMP Plan multilingue de base, U+0000~U+FFFF La plupart des caractères courants
Plan supplémentaire U+10000~U+10FFFF Emoji, écritures anciennes, caractères rares

1. La famille d'encodages UTF

Encodage Longueur en octets Encodage de « 中 » Caractéristiques
UTF-8 1-4 octets E4 B8 AD Longueur variable, compatible ASCII, préféré pour le Web
UTF-16 2 ou 4 octets 4E2D Longueur variable, usage interne en JS
UTF-32 4 octets fixe 00004E2D Longueur fixe, consommation mémoire élevée

Règles d'encodage UTF-8

Plage de points de code Octets Modèle d'encodage
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

Exemple d'encodage

Caractère : 中
Point de code : U+4E2D = 0100 1110 0010 1101

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

UTF-16 (2 octets) :
  4E2D

UTF-32 (4 octets) :
  00004E2D

2. Pièges d'Unicode en JavaScript

Longueur de chaîne incorrecte

'中'.length              // 1 ✅
'𠮷'.length             // 2 ❌ (caractère du plan supplémentaire, paire de substitution)
'👨‍👩‍👧‍👦'.length           // 11 ❌ (séquence ZWJ d'Emoji)
'🏳️‍🌈'.length             // 6 ❌ (Emoji de drapeau)

Cause : JS utilise UTF-16

Les caractères du plan supplémentaire (point de code > U+FFFF) nécessitent des paires de substitution (2 unités de code UTF-16) :

Caractère : 𠮷 (U+20BB7)
Paire de substitution UTF-16 : D842 DF87
JS : length = 2 (deux unités de code)

Traitement correct

// Méthode 1 : opérateur de décomposition
[...'𠮷'].length           // 1 ✅
[...'👨‍👩‍👧‍👦'].length         // 1 ✅ (mais la séquence ZWJ est comptée comme une)

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

// Méthode 3 : for...of
for (const char of '𠮷👨‍👩‍👧‍👦') {
  console.log(char);  // itération caractère par caractère
}

Opérations sur les points de code

// Obtenir le point de code
'中'.codePointAt(0)        // 20013 (0x4E2D)
'𠮷'.codePointAt(0)        // 134047 (0x20BB7)

// Créer un caractère depuis un point de code
String.fromCodePoint(0x4E2D)  // '中'
String.fromCodePoint(0x20BB7) // '𠮷'

// Note : charCodeAt ne peut pas traiter le plan supplémentaire
'𠮷'.charCodeAt(0)         // 55362 (uniquement le premier substitut)

3. Analyse approfondie de l'encodage Emoji

Quatre types d'Emoji

Type Exemple Point de code Longueur
Emoji de base 😀 U+1F600 1 (point de code)
Emoji avec modificateur 👩🏽 U+1F469 + U+1F3FD 2 (points de code)
Séquence ZWJ 👨‍👩‍👧‍👦 Plusieurs points de code + ZWJ 7 (points de code)
Emoji de drapeau 🇨🇳 U+1F1E8 + U+1F1F3 2 (points de code)

Connecteur de largeur nulle (ZWJ)

ZWJ (U+200D) relie plusieurs Emoji en un Emoji composé :

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

Modificateurs de teinte de peau

Les modificateurs de teinte de peau (U+1F3FB ~ U+1F3FF) changent la teinte de peau de l'Emoji :

👩 (U+1F469) + 🏽 (U+1F3FD) = 👩🏽 (femme avec teinte de peau moyenne)
Modificateur Point de code Teinte de peau
U+1F3FB 🏻 Teinte claire
U+1F3FC 🏼 Teinte moyen clair
U+1F3FD 🏽 Teinte moyenne
U+1F3FE 🏾 Teinte moyen foncé
U+1F3FF 🏿 Teinte foncée

Emoji de drapeau

Les drapeaux sont composés de deux symboles indicateurs régionaux (ISO 3166-1 alpha-2) :

🇨 (U+1F1E8) + 🇳 (U+1F1F3) = 🇨🇳 (Chine)
🇺 (U+1F1FA) + 🇸 (U+1F1F8) = 🇺🇸 (États-Unis)
🇯 (U+1F1EF) + 🇵 (U+1F1F5) = 🇯🇵 (Japon)

4. Sélecteurs de variante

VS16 (U+FE0F) convertit la présentation textuelle en présentation Emoji :

❤ (U+2764)           → présentation textuelle : ❤ (cœur noir)
❤️ (U+2764 + U+FE0F) → présentation Emoji : ❤️ (cœur rouge)
Caractère Sans VS16 Avec VS16
☕ ☕ (texte) ☕️ (Emoji)
⚡ ⚡ (texte) ⚡️ (Emoji)
❤ ❤ (texte) ❤️ (Emoji)

5. Utilisation de bibliothèques d'outils pour traiter Unicode

Encodage/décodage Unicode

  1. Ouvrez l'outil d'encodage/décodage Unicode
  2. Entrez du texte ou un point de code
  3. Vérifiez les résultats de conversion :
    • Texte → séquence d'échappement Unicode (\u4E2D)
    • Point de code → caractère
    • Séquence d'octets UTF-8/16/32

Encodage Emoji

  1. Ouvrez l'outil d'encodage Emoji
  2. Entrez un Emoji
  3. Vérifiez les informations complètes d'encodage :
    • Séquence de points de code
    • Octets UTF-8
    • Décomposition ZWJ/modificateurs
    • Nom court CLDR

6. Pièges courants et solutions

Piège 1 : Troncature de chaînes

// Erreur : la troncature peut couper les paires de substitution
text.substring(0, 10)  // peut produire des caractères invalides

// Correct : tronquer par point de code
function sliceByCodePoint(str, start, end) {
  const chars = [...str];
  return chars.slice(start, end).join('');
}

Piège 2 : Inversion de chaînes

// Erreur : l'inversion détruit les caractères combinés
'👨‍👩‍👧‍👦'.split('').reverse().join('')  // texte illisible

// Correct : inverser par point de code
[...'👨‍👩‍👧‍👦'].reverse().join('')  // ✅

Piège 3 : Correspondance regex

// Erreur : . ne correspond pas aux caractères du plan supplémentaire
/^.$/.test('𠮷')  // false

// Correct : utiliser le flag u
/^.$/u.test('𠮷')  // true

Piège 4 : Tri

// Tri par défaut selon les unités de code UTF-16
['中', '𠮷', 'a'].sort()  // peut ne pas être celui attendu

// Correct : trier par point de code
['中', '𠮷', 'a'].sort((a, b) =>
  a.codePointAt(0) - b.codePointAt(0)
)

7. API pratiques

// Normalisation (traiter l'équivalence des caractères combinés)
'café'.normalize('NFC')   // café (combiné)
'café'.normalize('NFD')   // café (décomposé)

// Détecter les Emoji
function isEmoji(char) {
  const code = char.codePointAt(0);
  return (code >= 0x1F600 && code <= 0x1F64F) ||  // Émoticônes
         (code >= 0x1F300 && code <= 0x1F5FF) ||  // Symboles divers
         (code >= 0x1F680 && code <= 0x1F6FF) ||  // Transport
         (code >= 0x1F900 && code <= 0x1F9FF);    // Supplémentaire
}

// Obtenir la longueur réelle des caractères
function graphemeLength(str) {
  const segmenter = new Intl.Segmenter('fr', { granularity: 'grapheme' });
  return [...segmenter.segment(str)].length;
}

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