Юникод
Юникод (Unicode) — универсальный стандарт, который присваивает уникальный номер каждому символу всех письменностей мира, а также знакам и эмодзи. Кодовое пространство охватывает позиции от U+0000 до U+10FFFF, а записываются они формами UTF-8, UTF-16 и UTF-32.
Кодовое пространство: номер для каждого знака
До Юникода каждый язык жил в своей однобайтовой таблице на 256 позиций, и текст, написанный в одной кодировке, а прочитанный в другой, превращался в мусор. Стандарт решил проблему радикально: вместо десятков несовместимых таблиц — единый реестр, где у каждого символа есть постоянный номер, называемый кодовой точкой и записываемый в виде U+ и шестнадцатеричного числа. Латинская A — это U+0041, русская А — U+0410, знак рубля — U+20BD, а привычный смайлик — U+1F600.
Пространство ограничено диапазоном от U+0000 до U+10FFFF, то есть 1 114 112 позициями, разбитыми на 17 плоскостей по 65 536 адресов. Нулевая плоскость (BMP) содержит письменности, которыми пользуется большинство: латиницу, кириллицу, греческий, иврит, арабский, китайские иероглифы и типографские знаки. Первая дополнительная плоскость отдана эмодзи, историческим письменностям и математическим алфавитам. Занято сегодня немногим больше 150 тысяч позиций — запас на десятилетия вперёд.
Первые 128 позиций совпадают с базовой таблицей ASCII символ в символ, и каждая из них укладывается в один октет, и это осознанное решение: любой текст на латинице, написанный в 1970-х, остаётся корректным текстом в Юникоде. Помимо номеров стандарт описывает свойства каждого символа — категорию, направление письма, правила смены регистра и переноса строк, — поэтому сортировка и поиск работают одинаково в разных программах.
UTF-8, UTF-16 и UTF-32: номер надо ещё записать байтами
Кодовая точка — абстракция. Чтобы положить текст в файл, её представляют одной из форм кодирования, и разница между ними практическая.
- UTF-8 — переменная длина от 1 до 4 байт. ASCII занимает один байт, кириллица и большинство европейских алфавитов — два, китайские иероглифы — три, эмодзи — четыре. Совместимость с ASCII и устойчивость к потере байта сделали его стандартом де-факто для веба и файлов.
- UTF-16 — два байта на символы нулевой плоскости и четыре на остальные, через механизм суррогатных пар (старший суррогат из диапазона D800-DBFF плюс младший из DC00-DFFF). Используется внутри Windows, Java и JavaScript, поэтому в этих средах длина строки с эмодзи оказывается на единицу больше ожидаемой.
- UTF-32 — ровно четыре байта на любой символ. Прост в обработке, расточителен по объёму, применяется в основном внутри программ.
Отсюда два постоянных источника ошибок. Первый — BOM, метка порядка байт: три байта EF BB BF в начале файла UTF-8 нужны Excel, чтобы правильно открыть CSV, но ломают шелл-скрипты и заголовки HTTP. Второй — путаница между байтами, кодовыми точками и видимыми знаками: символ «й» может храниться и одной точкой U+0439, и парой «и» с диакритикой, а флаг или эмодзи с модификатором тона состоят из нескольких точек и выглядят одним знаком. Из-за этого длина строки, посчитанная разными способами, даёт разные числа. Что происходит, когда программа выбирает не ту форму или не ту таблицу символов, показано в термине кодовая страница Windows-1251.
Юникод в QR-кодах: сколько букв реально помещается
Спецификация ISO/IEC 18004 работает с байтами, а не с символами. В байтовом режиме каждый байт занимает восемь бит, поэтому русская буква в UTF-8 съедает вдвое больше места, чем латинская. Максимум версии 40 при уровне коррекции L — 2953 байта, то есть примерно 1476 символов кириллицы против 2953 символов латиницы. На практике до таких объёмов не доходят: символ версии 40 состоит из 177×177 модулей, и на этикетке они становятся неразличимыми для камеры. Практический потолок для наклейки на упаковке — несколько сотен байт, дальше модуль становится мельче качества печати.
Второй нюанс — как декодер узнаёт, что перед ним UTF-8. По умолчанию стандарт предполагает ISO 8859-1, и явное указание кодировки даёт механизм ECI: значение 000026 обозначает UTF-8, 000022 — Windows-1251. Большинство современных генераторов пишут кириллицу в UTF-8, а сканеры распознают её эвристически, поэтому в быту всё работает и без ECI. Проблемы начинаются на промышленных считывателях и старых терминалах сбора данных, которые следуют букве стандарта: там русский текст без ECI превращается в набор латинских символов с диакритикой.
Практические выводы для тех, кто печатает коды. Кириллицу в ссылках лучше не использовать: домен в Punycode и латинский путь дают более компактный и предсказуемый символ, чем URL с процентным кодированием, где каждая русская буква превращается в шесть символов вида %D0%9F. Эмодзи в тексте кода занимают четыре байта каждый и на части сканеров отображаются пустым прямоугольником. Проверять результат стоит не в редакторе, а на реальном телефоне: собрать код и сразу считать его можно в генераторе QR-кодов.
Частые вопросы
Юникод и UTF-8 — это одно и то же?
Нет. Юникод — реестр символов и их номеров, UTF-8 — один из способов записать эти номера байтами. У одной и той же кодовой точки U+0416 в UTF-8 будет двухбайтовое представление, в UTF-16 — двухбайтовое, но другое, в UTF-32 — четырёхбайтовое. Когда говорят «файл в Юникоде», обычно имеют в виду именно UTF-8 или UTF-16.
Сколько байт занимает русская буква?
В UTF-8 — два байта, в UTF-16 — тоже два, в старой однобайтовой Windows-1251 — один. Для QR-кода это означает, что текст на кириллице занимает вдвое больше ёмкости, чем такой же по длине текст на латинице. При планировании содержимого кода считать нужно байты, а не символы, иначе версия символа окажется выше расчётной.
Почему эмодзи иногда считается за два символа?
Потому что в UTF-16 символы за пределами нулевой плоскости записываются суррогатной парой из двух шестнадцатибитных единиц. JavaScript, Java и Windows считают длину строки в этих единицах, поэтому один смайлик даёт длину 2. Составные эмодзи — флаги, семьи, символы с модификатором цвета кожи — состоят из нескольких кодовых точек и дают ещё большие числа.
Нужно ли указывать ECI при кодировании русского текста в QR?
Для потребительских сценариев обычно нет: телефоны определяют UTF-8 сами. Указание кодировки становится важным, когда код читают промышленные сканеры, терминалы сбора данных или встроенные считыватели, следующие стандарту строго. Если в приёмной системе вместо русских слов появляются латинские буквы с диакритикой, проблема почти всегда в отсутствии ECI.
Что делать, если вместо текста в файле видны вопросительные знаки?
Это признак того, что текст сохранили в однобайтовой кодировке, где нужных символов нет, — обратного пути тут не бывает, данные потеряны. Если же видны последовательности вроде «Ð Ñ€Ð¸», исходные байты целы и файл просто открыт не в той кодировке: достаточно переоткрыть его как UTF-8. Универсальная профилактика — хранить всё в UTF-8 и явно указывать кодировку при выгрузках.