Статья 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 | Dž | Особый 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.