Кодировки и Unicode в FPC
Одним из насущнейших вопросов для всякого русскоязычного программиста является вопрос кодировок. Так уж исторически сложилось, что компьютеры пришли к нам из англопишуших стран, чей алфавит насчитывает всего лишь 26 букв, а мы, спасибо Кириллу с Мефодием, пользуемся несколько другими. Впрочем, Кирилла и Мефодия винить не за что - они это не со зла, да и родной им греческий алфавит тоже далек от латиницы.
Как бы то ни было, мы в этом вопросе не одиноки. Более того, народам с иероглифической письменностью - еще хуже. Всемирное братание, когда все будут говорить на одном языке и писать на одном алфавите пока не предвидится, а посему возник некоторый стандарт кодирования символов, неэкономный для отдельно взятого языка, зато позволяющий работать сразу со многими.
Некоторые общие сведения
Было бы заманчиво назвать Unicode "универсальной кодировкой", но, к сожалению, такой единой кодировки не существует. Стандарт определяет порядковый номер каждого символа, однако их очень уж много. Отводить под каждый символ 4 байта и более как-то никому не хочется. Поэтому используется несколько способов кодирования: в одних случаях используется ограниченное подмножество символов, кодируемое одним или двумя байтами; в других - количество байт на символ зависит от самого символа.
Среди ограниченных вариантов главное место занимают однобайтные, которые, впрочем, к Unicode никакого отношения не имеют: Для кириллицы таких кодировок, к сожалению, много: KOI8-r, используемая в *nix-системах и ставшая стандартом de facto для электронной почты; кодировка Windows 1251, используемая в MS Windows, cp866 - альтернативная кодировка DOS: И это только наиболее распространенные. Что особенно , так это то, что кодировка, утвержденная Международной Организацией по Стандартизации - ISO, в силу ряда причин неудобна и практически никем не используется. Среди двухбайтных следует выделить UCS, которая использовалась в операционных системах Windows до версии 2000/XP.
Кодировки с переменным числом байт на символ тоже бывают разные - различается . Для нас наиболее интересными будут кодировки UTF-8, где минимум - один байт, и UTF-16, где, соответственно, два. UTF-16 используется в Windows 2000/XP (и, скорее всего, будет использоваться в последующих версиях), а UTF-8 широко распространена на *nix'овых платформах, но любим мы ее не только за это:
Дело в том, что FPC, как и большинство компиляторов и подобных им программ, воспринимает тексты только в однобайтной кодировке, причем в качестве значимых используются только символы из первой половины кодовой таблицы. В UTF-8 эти символы кодируются точно также. Иными словами, исходный код в UTF-8 компилятор прекрасно воспримет, какими бы ни были символы в строках и комментариях.
Что касается UTF-16, следует заметить, что все буквы европейских языков и знаки препинания кодируются одним двухбайтным символом. Таким образом, для большинства наших задач работа с ней и с UCS-кодировкой отличаться не будут.
Чтобы не останавливаться подробно на вопросе Unicode в целом, рекомендую статью Типы
В Free Pascal могут использоваться два типа символов, из которых составляются строки: одно- и двух-байтные. Для первых есть тип Char (он же - AnsiChar), для вторых - WideChar. И соответственно: string, AnsiString, WideString, PChar и PWideChar. Таким образом, мы можем использовать одно- и двух-байтные кодировки, причем как ограниченные, так и с переменным объемом символа, поскольку нулевой символ, имеющий специальное значение, везде одинаков.
Преобразование
Free Pascal поддерживает автоматическое преобразование Ansi и Wide-строк друг в друга. Тем не менее, кто пытался этим воспользоваться, испытал, наверное, жестокое разочарование - преобразуются только символы из первой части таблицы, ради которых переходить к двухбайтным кодировкам было б странно. Но не надо думать, что эта часть разработчиками FPC недоделана. Дело в том, что кодировки у всех разные, и задать такое преобразование "для всех" невозможно. В принципе, можно было б привязаться к текущим настройкам локализации, однако это добавляет платформо-зависимый код, и резко ухудшает переносимость программ. Вместо этого, определение перекодировки отдано на откуп программисту.
Управление перекодировкой зависит структуры следующего типа:
Впрочем, как видим, эта структура управляет не только преобразованием Ansi Wide, но и другими действиями над строками, зависящми от кодировки - преобразованием регистра, порядком сортировки и т.д. Для управления используются следующие три процедуры:
Замечу, однако, что не все строковые функции модулей System и SysUtils используют данную структуру. Кроме того, для полноценной работы с UTF-8, например, ее недостаточно. В целом, работа над RTL в этом направлении еще не закончена.
Теперь посмотрим, что нам может предложить модуль System для работы с UTF-8.
Это то, что в разделе "interface". Если заглянуть в "implementation", то увидим, что последние две функции реализованы через умолчательное преобразование Ansi Wide. Таким образом для их использования требуется адекватно определить это преобразование. Как - см. выше. В то же время преобразование UTF-8 Wide определено практически полностью - включая 3-байтные символы (кириллица находится в 2-байтных).
В целом на сегодняшний день работа с кодировками и Unicode в FPC вполне возможна. Далее мы еще поговорим, как проще всего добавить поддержку кириллицы. Заметим, что все вопросы, возникающие при локализации, решаются на уровне RTL, более того, их можно решить и не меняя стандартные модули - переопределение операторов и перегрузка функций могут производиться и в отдельных модулях (тут, правда, есть тонкости с текстовыми файлами). Управление строками на низком уровне, требующее поддежки компилятора полностью реализовано, за исключением, разве что совсем экзотических UTF-32 и UCS4 кодировок.
Шлифовка RTL, на мой взгляд, требуется в направлении большей стройности кода и ориентации на UTF-16 вместо UCS2 для WideString (то есть на использование переменной двухбайтной кодировки вместо ограниченной) - вдруг придется писать что-то для юго-восточной Азии: С другой стороны, строки с переменным числом байт на символ не позволяют уже обращаться с ними как с массивами символов и вообще значительно усложняют работу:
В стандартном наборе FPC наблюдается еще кое-что, что может облегчить работу с различными кодировками и Unicode. Это модуль CharSet и утилита creumap.
Модуль CharSet предлагает некое стандартное представление таблиц перекодировки. Вообще-то, он выглядит недоделаным и сам по себе малополезен, но в сочетании с программой creumap позволяет написать перекодирующие функции гораздо быстрее, чем с нуля.
Утилита формирует модули с таблицами перекодировки для CharSet из текстовых файлов специального вида. Что было бы тоже не особо полезно, если бы набор таких файлов не был включен в состав исходников FPC. Кстати, саму утилиту тоже придется компилировать из исходников - в бинарном дистрибутиве ее нет.
Поскольку без исходников все равно не обойтись, я не буду описывать модуль CharSet - его интерфейс умещается на одном экране - разобраться просто. Вместо этого замечу, что исходник CharSet находится в файле "./rtl/inc/charset.pp", creumap - "./utils/creumap.pp", а файлы кодировок - в каталоге "./rtl/ucmaps/", считая от корня исходников FPC. В частности, там наличествуют файлы для кодировок кириллицы: ISO-8859-5, cp855 (DOS-основная, практически не используемая), cp866 (DOS-альтернативная), cp1251 (Windows). KOI8-r, к сожалению, нет.
Таблицы перекодировки модуля CharSet устроены так, что нужный символ Unicode по ANSI выбирается сразу - как элемент таблицы по индексу, тогда как для обратного преобразования требуется перебор элементов, что не есть хорошо в случае перекодировки больших объемов текста.
В целом картина типичная для Free Pascal: компилятор свое дело делает, RTL базовую функциональность реализует, хотя напильник бы не помешал, а для практического применения нужно немного поработать ручками и ясно представлять, что и как происходит. Надеюсь данная статья хотя бы немного поспособствует последнему - в документации, к сожалению, почти ничего на эту тему нет. Строки в Free Pascal
type
TWideStringManager = record
Wide2AnsiMoveProc : procedure (Source : PWideChar; var Dest : AnsiString; Len : SizeInt);
Ansi2WideMoveProc : procedure (Source : PChar; var Dest : WideString; Len : SizeInt);
UpperWideStringProc : function (const S : WideString) : WideString;
LowerWideStringProc : function (const S : WideString) : WideString;
CompareWideStringProc : function (const S1, S2 : WideString) : PtrInt;
CompareTextWideStringProc : function (const S1, S2 : WideString) : PtrInt;
CharLengthPCharProc : function (const Str : PChar) : PtrInt;
UpperAnsiStringProc : function (const S : AnsiString) : AnsiString;
LowerAnsiStringProc : function (const S : AnsiString) : AnsiString;
CompareStrAnsiStringProc : function (const S1, S2 : AnsiString) : PtrInt;
CompareTextAnsiStringProc : function (const S1, S2 : AnsiString) : PtrInt;
StrCompAnsiStringProc : function (S1, S2 : PChar) : PtrInt;
StrICompAnsiStringProc : function (S1, S2 : PChar) : PtrInt;
StrLCompAnsiStringProc : function (S1, S2 : PChar; MaxLen : PtrUInt) : PtrInt;
StrLICompAnsiStringProc : function (S1, S2 : PChar; MaxLen : PtrUInt) : PtrInt;
StrLowerAnsiStringProc : function (Str : PChar) : PChar;
StrUpperAnsiStringProc : function (Str : PChar) : PChar;
end;
procedure GetWideStringManager (var Manager : TWideStringManager);
procedure SetWideStringManager (const New : TWideStringManager);
procedure SetWideStringManager (const New : TWideStringManager; var Old: TWideStringManager);
UTF-8
type
UTF8String = type AnsiString;
function UnicodeToUtf8 (
Dest : PChar;
Source : PWideChar;
MaxBytes : SizeInt
) : SizeInt;
function UnicodeToUtf8 (
Dest : PChar;
MaxDestBytes : SizeUInt;
Source : PWideChar;
SourceChars : SizeUInt
) : SizeUInt;
function Utf8ToUnicode (
Dest : PWideChar;
Source : PChar;
MaxChars : SizeInt
) : SizeInt;
function Utf8ToUnicode (
Dest : PWideChar;
MaxDestChars : SizeUInt;
Source : PChar;
SourceBytes : SizeUInt
) : SizeUInt;
function UTF8Encode (const S : WideString) : UTF8String;
function UTF8Decode (const S : UTF8String) : WideString;
function AnsiToUtf8 (const S : AnsiString) : UTF8String;
function Utf8ToAnsi (const S : UTF8String) : AnsiString;
Предварительные выводы
Дополнительные возможности
Итоги