Перейти к содержанию

Статья 1. Основы Unicode: кодовые точки, плоскости, блоки

1. Зачем вообще понадобился Unicode?

До Unicode каждая страна и компания изобретала собственную кодировку. В мире одновременно существовали сотни несовместимых систем: ASCII (7-бит, только английский), KOI8-R (русский), CP1252 (Windows Western), ISO 8859-1..16, GB2312 (китайский), Shift-JIS (японский) и т. д.

Проблемы были повсюду:

  • Письмо, написанное в Windows на русском, открывалось кракозябрами в Unix.
  • Один байт 0xE9 — это é в Latin-1, й в KOI8-R и совсем другое в Big5.
  • Смешать русский с греческим в одном тексте было невозможно без специальных хаков.

В 1987 году инженеры Xerox и Apple начали работу над универсальным стандартом. В 1991 году вышла версия Unicode 1.0. Сегодня (Unicode 17.0, 2025) стандарт охватывает более 154 000 символов, поддерживает 170+ письменностей и все живые и многие исторические письменности мира.


2. Кодовые точки (Code Points)

Ключевое понятие Unicode — кодовая точка (code point). Это просто целое неотрицательное число, однозначно идентифицирующее символ. Записывается как U+XXXX (hex).

U+0041  →  A  (LATIN CAPITAL LETTER A)
U+0410  →  А  (CYRILLIC CAPITAL LETTER A)
U+1F30D →  🌍 (EARTH GLOBE EUROPE-AFRICA)

Всё кодовое пространство: U+0000..U+10FFFF — 1 114 112 позиций.

Важно: кодовая точка — это абстрактное число, а не байты в памяти. Как именно хранить эти числа — задача кодировок (UTF-8, UTF-16, UTF-32). Это разные вещи! (Подробнее — в статье 3.)

Стандартная библиотека Python предоставляет прямой доступ к свойствам Unicode Character Database (UCD — база данных всех символов, подробнее в статье 2) через модуль unicodedata:

import unicodedata

for ch in ['A', 'А', '🌍', '½', '\u0301']:
    cp = ord(ch)
    print(f"U+{cp:05X}  cat={unicodedata.category(ch)}  {unicodedata.name(ch, '?')}")
U+00041  cat=Lu  LATIN CAPITAL LETTER A
U+00410  cat=Lu  CYRILLIC CAPITAL LETTER A
U+1F30D  cat=So  EARTH GLOBE EUROPE-AFRICA
U+000BD  cat=No  VULGAR FRACTION ONE HALF
U+00301  cat=Mn  COMBINING ACUTE ACCENT

В JavaScript нет встроенного аналога unicodedata, но получить кодовую точку и создать символ по номеру можно через codePointAt() и String.fromCodePoint():

for (const ch of ['A', 'А', '🌍', '½', '\u0301']) {
  const cp = ch.codePointAt(0);
  console.log(`U+${cp.toString(16).toUpperCase().padStart(5, '0')}  ${ch}`);
}

// Обратное преобразование — из кодовой точки в символ:
console.log(String.fromCodePoint(0x1F30D));  // 🌍

Символ, глиф и шрифт

Важное разграничение, которое в Unicode проводится явно:

  • Символ (character) — абстрактная единица значения. U+0041 — это символ «латинская заглавная буква A». Он существует независимо от того, как выглядит на экране.
  • Глиф (glyph) — конкретное визуальное начертание символа. Буква A в Times New Roman и A в Arial — разные глифы, но один символ.
  • Шрифт (font) — набор глифов. Шрифт не меняет символ; он определяет только внешний вид.

Практическое следствие: Unicode стандартизирует символы и их свойства, но не регламентирует внешний вид. Символ U+0041 — это всегда «LATIN CAPITAL LETTER A», но как именно он нарисован — решает шрифт: в Arial у него нет засечек, в Times New Roman есть, в рукописном шрифте он может быть вовсе неузнаваем. Сам символ при этом не меняется.

Особенно это заметно в CJK: иероглиф U+8FBA (辺) в китайском, японском и корейском шрифтах имеет разные традиционные начертания, оставаясь одной кодовой точкой. Именно поэтому Unicode не стал кодировать «японский вариант» и «китайский вариант» как разные символы — это задача шрифта, а не стандарта.


3. Плоскости (Planes)

Всё кодовое пространство поделено на 17 плоскостей по 65 536 (0x10000) позиций каждая:

Диапазон Название Что там
0 U+0000..U+FFFF Basic Multilingual Plane (BMP) Почти все современные письменности, CJK, символы
1 U+10000..U+1FFFF Supplementary Multilingual Plane (SMP) Исторические письменности, эмодзи, нотация
2 U+20000..U+2FFFF Supplementary Ideographic Plane (SIP) Редкие CJK-иероглифы
3 U+30000..U+3FFFF Tertiary Ideographic Plane (TIP) Очень редкие CJK (с Unicode 13.0)
14 U+E0000..U+EFFFF Supplementary Special-purpose Plane (SSP) Языковые теги, вариационные селекторы
15 U+F0000..U+FFFFF Supplementary Private Use Area-A (SPUA-A) Приватное использование
16 U+100000..U+10FFFF Supplementary Private Use Area-B (SPUA-B) Приватное использование
4-13 (зарезервированы) Не используются

Плоскость 0 (BMP) — самая важная. Если символ в BMP, то в UTF-16 он занимает ровно 2 байта. Символы вне BMP называются supplementary characters и требуют суррогатных пар в UTF-16.


4. Блоки (Blocks)

Внутри каждой плоскости символы сгруппированы в блоки — непрерывные диапазоны, обычно объединяющие символы одной письменности или тематики.

Примеры:

Блок Диапазон Размер
Basic Latin U+0000..U+007F 128
Latin-1 Supplement U+0080..U+00FF 128
Cyrillic U+0400..U+04FF 256
Cyrillic Supplement U+0500..U+052F 48
Arabic U+0600..U+06FF 256
CJK Unified Ideographs U+4E00..U+9FFF 20 912
Emoticons U+1F600..U+1F64F 80
Mahjong Tiles U+1F004..U+1F00F 16

Полный список — в файле Blocks.txt из Unicode Character Database (UCD). Подробнее о файлах UCD и структуре данных о символах — в статье 2.

Блок ≠ Скрипт. Блок — это фиксированный диапазон адресов. Скрипт — это свойство символа (к какой письменности он относится). Например, знаки препинания из блока «Basic Latin» относятся к скрипту «Common», а не «Latin».


5. Скрипты (Scripts)

Скрипт — это атрибут символа из файла Scripts.txt. Каждому символу назначен ровно один скрипт. Большинство символов получают конкретную письменность (Latin, Cyrillic, Arabic…), но есть два особых значения:

  • Common — символ не принадлежит ни одной конкретной письменности и используется во многих: цифры 0–9, знаки препинания, пробел, большинство эмодзи. Скрипт Common сам по себе не является письменностью — это просто «разделяемый всеми» статус.
  • Inherited — символ наследует скрипт от предыдущего символа в строке. Так устроены комбинирующие знаки (диакритика): U+0301 (знак ударения ́) встаёт поверх любой буквы и «становится» того же скрипта, что и она. Стоит после е — Latin; стоит после е кириллической — Cyrillic.
U+0041 (A)  →  Script: Latin
U+0410 (А)  →  Script: Cyrillic
U+0660 (٠)  →  Script: Arabic      (арабская цифра 0)
U+0030 (0)  →  Script: Common      (обычная цифра 0 — общая для всех)
U+0301 ( ́)  →  Script: Inherited   (combining accent наследует скрипт базового символа)

В JavaScript (ES2018+) скрипт символа можно проверить через Unicode property escapes в регулярных выражениях с флагом u:

// Проверка скрипта символа
console.log(/\p{Script=Latin}/u.test('A'));      // true
console.log(/\p{Script=Cyrillic}/u.test('А'));   // true  — кириллическая А (U+0410)
console.log(/\p{Script=Arabic}/u.test('٠'));     // true  — арабская цифра
console.log(/\p{Script=Common}/u.test('0'));     // true  — обычная цифра (общая для всех)

// Детекция смешанных скриптов (anti-spoofing):
// визуально «А» и «A» неотличимы, но принадлежат разным скриптам
console.log('Apple  содержит кириллицу?', /\p{Script=Cyrillic}/u.test('Apple'));   // false
console.log('Аpple  содержит кириллицу?', /\p{Script=Cyrillic}/u.test('Аpple'));   // true — первая «А» кириллическая!

Скрипты важны для:

  • Регулярных выражений: \p{Script=Cyrillic}
  • Обнаружения спуфинга (IDN homograph attacks) — подробнее в статье 5, раздел «Unicode spoofing»
  • Выбора шрифта — операционная система и браузер используют скрипт символа, чтобы автоматически подобрать подходящий шрифт: латинский текст рендерится латинским шрифтом, арабский — арабским, даже если в CSS указан один и тот же font-family

6. Категории символов (General Category)

Каждый символ имеет General Category — двухбуквенный код. Первая буква — основная группа, вторая — подкатегория. Категория важна для алгоритмов обработки текста: что считать «буквой», «пробелом», «цифрой», что пропускать при переносе строк и т. д.

L — Letters (буквы)

Буквы всех письменностей мира. Разделены по регистру и роли:

Код Название Пример Пояснение
Lu Letter, Uppercase A, А, Ñ Буква верхнего регистра
Ll Letter, Lowercase a, а, ñ Буква нижнего регистра
Lt Letter, Titlecase Особый titlecase (не просто заглавная — специальная форма для написания в заголовках, есть в сербском, хорватском)
Lm Letter, Modifier ʰ, ʲ Модифицирующая буква — изменяет произношение предыдущего звука, но пишется отдельным символом
Lo Letter, Other 中, ء, ि Буква без понятия регистра — CJK-иероглифы, арабские буквы, слоговые письменности

M — Marks (диакритика и знаки)

Знаки, которые не существуют самостоятельно — они комбинируются с базовым символом:

Код Название Пример Пояснение
Mn Mark, Nonspacing ◌́ (combining acute) Не занимает собственного места — накладывается поверх предыдущего символа (акценты, огласовки в арабском)
Mc Mark, Spacing Combining ा (деванагари гласная) Занимает место рядом с базовым символом — видимый знак, но привязан к нему
Me Mark, Enclosing ◌⃝ Охватывает базовый символ (обводит в кружок и т. п.)

N — Numbers (числа)

Все символы с числовым смыслом:

Код Название Пример Пояснение
Nd Number, Decimal Digit 0..9, ٠..٩, ০..৯ Десятичные цифры из любой письменности — не только ASCII, но и арабские, бенгальские и т. д. str.isdigit() в Python возвращает True для всех
Nl Number, Letter Ⅳ, ⅻ Числа, записанные буквами — римские цифры, греческие числовые формы
No Number, Other ½, ¾, ②, ⑩ Числа, не являющиеся ни цифрами, ни буквами — дроби, обведённые цифры

P — Punctuation (пунктуация)

Код Название Пример Пояснение
Pc Punctuation, Connector _ Соединяет слова (underscore, тибетские связующие знаки)
Pd Punctuation, Dash -, –, —, ‐ Все виды тире и дефисов
Ps Punctuation, Open (, [, {, 「 Открывающие скобки (в т.ч. из других письменностей)
Pe Punctuation, Close ), ], }, 」 Закрывающие скобки
Pi Punctuation, Initial Quote «, ‹, " Открывающие кавычки — отличаются от Ps тем, что это именно кавычки
Pf Punctuation, Final Quote », ›, " Закрывающие кавычки
Po Punctuation, Other !, ., ,, ;, ? Всё остальное — точки, запятые, восклицательные знаки

S — Symbols (символы)

Код Название Пример Пояснение
Sm Symbol, Math +, =, ∑, ∫, ≠ Математические операторы и знаки
Sc Symbol, Currency $, €, ₽, ¥ Знаки валют
Sk Symbol, Modifier ^ (circumflex), ˋ (grave) Модифицирующие символы с пробелом (в отличие от Mn — они занимают место)
So Symbol, Other ©, ™, 🌍, ♠, ✓ Всё остальное — эмодзи, пиктограммы, торговые знаки

Z — Separators (разделители)

Код Название Пример Пояснение
Zs Separator, Space U+0020, U+00A0, U+2002..U+200A Пробельные символы разной ширины. Важно: сюда не входят \t, \n — они в Cc
Zl Separator, Line U+2028 Разделитель строк (не \n!) — используется в некоторых форматах
Zp Separator, Paragraph U+2029 Разделитель абзацев

C — Other (прочие)

Код Название Пример Пояснение
Cc Other, Control U+0000..U+001F, U+007F Управляющие символы: \t, \n, \r, \0, ESC и т. д.
Cf Other, Format U+200B (zero-width space), U+FEFF (BOM), U+200D (ZWJ) Невидимые символы форматирования — влияют на отображение, но не печатаются
Cs Other, Surrogate U+D800..U+DFFF Суррогатные кодовые точки — зарезервированы для UTF-16, сами по себе не являются символами
Co Other, Private Use U+E000..U+F8FF Символы для частного использования — Unicode не назначает им смысл, его определяет конкретное приложение
Cn Other, Not Assigned Кодовые точки, которым ещё не назначен символ

В JavaScript категории символов доступны через Unicode property escapes — как конкретные подкатегории (Lu, Nd), так и группы по первой букве (L, N, P):

// Конкретные подкатегории
console.log('A  Lu?', /\p{Lu}/u.test('A'));       // true  — Letter, Uppercase
console.log('a  Ll?', /\p{Ll}/u.test('a'));       // true  — Letter, Lowercase
console.log('٣  Nd?', /\p{Nd}/u.test('٣'));       // true  — Number, Decimal (арабская тройка)
console.log('€  Sc?', /\p{Sc}/u.test('€'));       // true  — Symbol, Currency
console.log('◌́  Mn?', /\p{Mn}/u.test('\u0301')); // true  — Mark, Nonspacing (combining acute)

// Группы (первая буква — все подкатегории сразу)
console.log('中  L?', /\p{L}/u.test('中'));    // true  — любая буква (Lu + Ll + Lt + Lm + Lo)
console.log('½  N?', /\p{N}/u.test('½'));     // true  — любое число (Nd + Nl + No)
console.log('«  P?', /\p{P}/u.test('«'));     // true  — любая пунктуация
console.log('©  S?', /\p{S}/u.test('©'));     // true  — любой символ (Sm + Sc + Sk + So)

// Практический пример: оставить только буквы и цифры из любых письменностей
console.log('café 中文 123 !!!'.replace(/[^\p{L}\p{N}]/gu, ''));  // café中文123

Все свойства символов (имя, категория, декомпозиция, числовые значения, правила регистра и др.) хранятся в Unicode Character Database (UCD) — наборе текстовых файлов, публикуемых вместе с каждой версией стандарта. Полный разбор всех UCD-файлов, форматов и примеров парсинга — в статье 2.