If (Leading < $D800) Or (Leading > $DFFF) Then
Return WordToUInt32(Leading)
Else If (Leading >= $DC00) Then
Error("Недопустимая кодовая последовательность.")
Else
Var Code: UInt32
Code = WordToUInt32(Leading And $3FF) Shl 10
Trailing = ReadWord()
If ((Trailing < $DC00) Or (Trailing > $DFFF)) Then
Error("Недопустимая кодовая последовательность.")
Else
Code = Code Or WordToUInt32(Trailing And $3FF)
Return (Code + $10000)
End If
End If
End Function
UTF-8
UTF-8 (от Unicode Transformation Format, 8-bit) — распространённая кодировка символов Юникода, совместимая с 8-битными форматами передачи текста. Нашла широкое применение в операционных системах и веб-пространстве.
В отличие от UTF-16, UTF-8 является самосинхронизирующейся кодировкой: при потере одного байта последующие байты будут раскодированы корректно.
Текст, состоящий только из символов Юникода с номерами меньше 128, при записи в UTF-8 превращается в обычный текст ASCII. И наоборот, в тексте UTF-8 любой байт со значением меньше 128 изображает символ ASCII с тем же кодом. Остальные символы Юникода изображаются последовательностями длиной от 2 до 6 байт (реально только до 4 байт, поскольку использование кодов больше 221 не планируется), в которых первый байт всегда имеет вид11xxxxxx, а остальные — 10xxxxxx.
Проще говоря, в формате UTF-8 символы латинского алфавита, знаки препинания и управляющие символы ASCII записываются кодами US-ASCII, a все остальные символы кодируются при помощи нескольких байтов со старшим битом 1. Это приводит к двум эффектам.
§ Даже если программа не распознаёт Юникод, то латинские буквы, арабские цифры и знаки препинания будут отображаться правильно.
§ В случае, если латинские буквы и простейшие знаки препинания (включая пробел) занимают существенный объём текста, UTF-8 даёт выигрыш по объёму по сравнению с UTF-16.
На первый взгляд может показаться, что UTF-16 удобнее, так как в ней большинство символов кодируется ровно двумя байтами.
Однако это сводится на нет необходимостью поддержки суррогатных пар, о которых часто забывают при использовании UTF-16, реализуя лишь поддержку символов UCS-2.
Формат UTF-8 был изобретён 2 сентября 1992 года Кеном Томпсоном и Робом Пайком и реализован в Plan 9. Сейчас стандарт UTF-8 официально закреплён в документах RFC 3629 и ISO/IEC 10646 Annex D.
Замечание: Символы, закодированные в UTF-8, могут быть длиной до шести байт, однако стандарт Unicode не определяет символов выше 0x10ffff, поэтому символы Unicode могут иметь максимальный размер в 4 байта в UTF-8.
Принцип кодирования
Текстовое описание
В UTF-8 можно кодировать значения кодов символов от 0 до 0x7FFFFFFF включительно (все комбинации 32-битных без установленного старшего бита).
1. Каждый символ кодируется переменным количеством последовательных 8-битных байт (октетов). Количество же может варьироваться от 1 до 6 байт включительно и определяется самым первым байтом.
2. Все ASCII-символы (0х00 — 0x7F включительно) записываются как есть одним байтом со сброшенным старшим битом.
3. Все остальные символы кодируются уже особым образом и далее текст этого раздела касается только их. Чтобы лучше понять принцип, лучше представляйте себе блоки бит с их позицией.
4. У байт не ASCII-символов старший бит всегда установлен в 1. При этом второй бит всегда сброшен у не первых байт (у первых, соответственно, установлен). Поэтому если чтение производится с произвольного байта, то по второму биту можно определять промежуточные байты.
5. И у не первых байт остальные 6 младших бит содержат фрагмент кода символа (об этом ниже).
Количество байт, которое отводится под символ, всегда равно количеству идущих подряд старших бит со значением 1 в первом байте. Эти биты всегда завершаются битом со значением 0. Оставшиеся младшие биты первого байта составляют код символа. Отсюда обуславливается ограничение в 6 байт на символ — если выше, то в первом байте уже не хватит места под биты данных. Поэтому последовательности бит 11111110 (0xFE) и 11111111 (0xFF) общепринято считаются не используемыми в UTF-8.
До этого описывалась структура, а теперь про расположение данных.
Как видно из описания выше, каждый байт имеет определённое количество младших бит под данные — переменное у первого и по 6 в последующих. 32-битный код символа последовательно размещается в этих контейнерах. Старшие биты оказываются в первых байтах, а младшие — в последних. Поэтому младшие 6 бит последнего байта всегда содержат биты 0..5 кода символа. Аналогично, предпоследний байт содержат биты 6..11, третий с конца — 12..17, четвёртый — 18..23, пятый — 24..29. Первый байт же содержит оставшиеся старшие биты значения.
Зная структуру и расположение данных внутри байт, теперь рассмотрим взаимосвязь кода символа и количества байт.
Каждое количество байт способно хранить конкретный диапазон значений кода символа. При этом сами диапазоны значений расположены плотно по порядку без всяких просветов.
Коды символов Unicode (HEX) | Размер в UTF-8 | Представленные классы символов |
00000000 — 0000007F | 1 байт | ASCII, в том числе латинский алфавит, простейшие знаки препинания и арабские цифры |
00000080 — 000007FF | 2 байта | кириллица, расширенная латиница, арабский, армянский, греческий, еврейский и коптский алфавит; сирийское письмо, тана, нко; МФА; некоторые знаки препинания |
00000800 — 0000FFFF | 3 байта | все другие современные формы письменности, в том числе грузинский алфавит, индийское, китайское, корейское и японское письмо; сложные знаки препинания; математические и другие специальные символы |
00010000 — 001FFFFF | 4 байта | музыкальные символы, редкие китайские иероглифы, вымершие формы письменности |
00200000 — 03FFFFFF | 5 байт | не используется в Unicode |
04000000 — 7FFFFFFF | 6 байт | не используется в Unicode |
Следует отметить, что данная таблица подразумевает плотное кодирование и поэтому она представляет только идеальные комбинации.
Кодировка UTF-8 не является однозначной, так как в ней учитывается размер бит значения без учёта позиции последнего установленного бита. Поэтому возможно написание «грубого» кодировщика, который не отбрасывает лидирующие нули. Например, ASCII-символ «1» (0x31), может быть представлен следующими двухбайтовыми и трёхбайтовыми последовательностями: 11000000 10110001 (0xC0 0xB1) и 11100000 10000000 10110001 (0xE0 0x80 0xB1). Отсюда выходят следующие бессмыленные битовые комбинации первых байт: 11000000 (0xC0), 11100000 (0xE0), 11110000 (0xF0), 11111000 (0xF8), 11111100 (0xFC), а также последующие за ними комбинации промежуточных байт 10000000 (0x80).
Максимальный потенциал
До этого рассматривалось кодирование в UTF-8 лишь 32-битных целых без отрицательных значений. Следует отметить, что в стандарте Unicode используются символы лишь до кода 0x001FFFFF включительно. Поэтому даже 32-битных значений может вполне хватить, но этот раздел был включён для полноты изложения в случае использования UTF-8 для кодирования несимвольных данных.
В первом байте количество установленных старших бит определяет количество байт на символ. Оставшиеся младшие биты хранят старшие биты значения кода символа. Мы можем сделать допущение о том, что первый байт не обязан содержать данные. При этом допускаем, что все биты за пределами байта равны нулю. Тогда данные будут содержать только 6 бит в последующих байтах. Получается 36 бит для семибайтового символа и 42 бита — для восьмибайтового.
Неиспользуемые значения байтов
В тексте UTF-8 принципиально не может быть байтов со значениями 254 (0xFE) и 255 (0xFF). Поскольку в Юникоде не определены символы с кодами выше 221, то в UTF-8 оказываются неиспользуемыми также значения байтов от 248 до 253 (0xF8 — 0xFD). Если запрещены искусственно удлинённые (за счёт добавления ведущих нулей) последовательности UTF-8, то не используются также байтовые значения 192 и 193 (0xC0 и 0xC1).
BOM (сигнатура)
Многие программы Windows (включая Блокнот) добавляют байты 0xEF, 0xBB, 0xBF в начале любого документа, сохраняемого как UTF-8.
Это метка порядка байтов (англ. Byte Order Mark, BOM), также её часто называют сигнатурой (соответственно, UTF-8 и UTF-8 with Signature). По наличию сигнатуры программы могут автоматически определить, является ли файл закодированным в UTF-8, однако файлы с такой сигнатурой могут некорректно обрабатываться старыми программами, в частности xml-анализаторами. Такие редакторы, как Notepad++, Notepad2 и Kate, позволяют явно указывать, следует ли добавлять сигнатуру при сохранении UTF-файлов.
Например: В файле записана одна латинская буква «a».
§ Если кодировка этого файла UTF-8 with Signature, то он будет содержать: 0xEF 0xBB 0xBF 0x61
§ Если кодировка этого файла UTF-8 (без сигнатуры), то он будет содержать: 0x61
Если считывающая программа не поддерживает BOM, то эти три байта успешно раскодируются в один Unicode-символ 0xFEFF. Это не разрывающий слова пробел нулевой ширины и поэтому он может не отобразиться. Этот же символ используется в BOM для кодировок UTF-16 и UTF-32.
Маркер последовательности байтов или метка порядка байтов (англ. Byte Order Mark (BOM)) — Юникод-символ, используемый для индикации порядка байтов текстового файла. Его кодовый символ U+FEFF. По спецификации, его использование не является обязательным, однако, если маркер последовательности байтов используется, то он должен быть установлен в начале текстового файла. Помимо своего конкретного использования в качестве указателя порядка байтов, символ может также указать, какой кодировкой Unicode закодирован текст.
Кодировка Unicode может использовать 16-разрядные или 32-разрядные числа и приложение должно знать, как дальше с ними поступать. Поэтому потребность в маркере последовательности байтов возникает при обмене документами.
Использование[
Согласно спецификации Unicode, символ U+FEFF в середине потока данных должен интерпретироваться как «нулевой ширины неразрывный пробел» (по существу, нулевой символ). Однако Unicode 3.2 настоятельно рекомендует использовать в этом качестве символ U+2060 «Word Joiner»[1], а U+FEFF использовать только как маркер последовательности байтов.
Представление кодировки маркера последовательности байтов
Кодировка | Представление | Если открыть в другой кодировке | |||||
hex | dec | ISO-8859-1 | KOI8-R | CP1251 | CP866 | комментарий | |
UTF-8[t 1] | EF BB BF | 239 187 191 |  | О╩© | п»ї | я╗┐ | |
UTF-16 (BE) | FE FF | 254 255 | þÿ | ЧЪ | юя | ■ | пробел — неразрывный |
UTF-16 (LE) | FF FE | 255 254 | ÿþ | ЪЧ | яю | ■ | |
UTF-32 (BE) | 00 00 FE FF | 0 0 254 255 | ␀␀þÿ | ␀␀ЧЪ | ␀␀юя | ␀␀■ | ␀ — NUL, пробел — неразрывный |
UTF-32 (LE) | FF FE 00 00 | 255 254 0 0 | ÿþ␀␀ | ЪЧ␀␀ | яю␀␀ | ■␀␀ | |
UTF-7[t 1] | 2B 2F 76 38 2B 2F 76 39 2B 2F 76 2B 2B 2F 76 2F[t 2] | 43 47 118 56 43 47 118 57 43 47 118 43 43 47 118 47 | +/v8 +/v9 +/v+ +/v/ | ||||
UTF-1[t 1] | F7 64 4C | 247 100 76 | ÷dL | ||||
UTF-EBCDIC[t 1] | DD 73 66 73 | 221 115 102 115 | Ýsfs | ||||
SCSU[t 1] | 0E FE FF[t 3] | 14 254 255 | ␎þÿ | ␎■ | ␎ — упр. символ Shift Out (англ.)русск., пробел — неразрывный | ||
BOCU-1[t 1] | FB EE 28 | 251 238 40 | ûî | √ю( | |||
GB-18030[t 1] | 84 31 95 33 | 132 49 149 51 | �1�3 | Д1Х3 | � — коды без значений |
1. ↑ Перейти к:1234567 В этих кодировках последовательность не определяет именно порядок байтов, так как кодировка однобайтная, но эта последовательность может использоваться для определения способа кодировки.[2][3]
2. ↑ В UTF-7 в связи с использованием base-64, четвёртый байт BOM является 001111xx в двоичном представлении, где xx зависит от следующего символа (первого после BOM). Поэтому четвёртый байт не является только частью BOM, но также содержит информацию о следующем (не BOM) символе. Для xx=00, 01, 10, 11, четвёртый байт будет, соответственно, 38, 39, 2B, или 2F при кодировке в base64. Если же следующий символ не кодируется base64, то используется 38 в качестве четвёртого байта, а следующий байт2D.
3. ↑ SCSU предусматривает и другие кодировки для U+FEFF, указанная последовательность является рекомендованной в UTR #6.[4]