LCL Unicode Support/ru: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: LCL Unicode Support/ru
| Old | New | Line | |
|---|---|---|---|
| 1 | + | ## Введение | |
| 2 | + | ||
| 3 | + | Начиная с 0.9.25 Lazarus полностью поддерживает юникод (Unicode) на всех платформах, исключая Gtk 1. На этой странице размещены инструкции для пользователей Lazarus, планы на будущее, описаны основные концепции и детали реализации. | |
| 4 | + | ||
| 5 | + | ## Инструкции для пользователей | |
| 6 | + | ||
| 7 | + | Хотя в Lazarus есть юникодные наборы виджетов, важно отметить, что не всё в юникоде. Кодировка строк и правильное преобразование между библиотеками, ожидающими разные кодировки, является ответственностью разработчика. | |
| 8 | + | ||
| 9 | + | Обычно библиотекой используется единая кодировка строк (речь о динамической библиотеке DLL или пакете Lazarus). Каждая библиотека обычно ожидает одну кодировку, которой может быть юникод (UTF-8 для Lazarus) или ANSI (что означает системную кодировку, которая может быть UTF-8 или другой). RTL и FCL в FPC версий 2.4-2.6 ожидают строки ANSI. | |
| 10 | + | ||
| 11 | + | Преобразование между юникодом и ANSI возможно при помощи функций **UTF8ToAnsi** и **AnsiToUTF8** модуля System или **UTF8ToSys** и **SysToUTF8** модуля FileUtil (Lazarus). Последние две мощнее (быстрее), но добавят к программе больше кода. | |
| 12 | + | ||
| 13 | + | ### FPC не юникодный | |
| 14 | + | ||
| 15 | + | Библиотеки Free Pascal времени исполнения (RTL) и свободных компонентов (FCL) в текущих версиях FPC (по 2.6.1) используют ANSI, поэтому требуется преобразование строк, получаемых из юникодных или переделываемых в юникодные библиотек (например, LCL). | |
| 16 | + | ||
| 17 | + | ### Преобразование между ANSI и юникодом | |
| 18 | + | ||
| 19 | + | Примеры: | |
| 20 | + | ||
| 21 | + | Допустим, нужно передать в файловую функцию из RTL строку, полученную из TEdit: | |
| 22 | + | ||
| 23 | + | ``` pascal | |
| 24 | + | var | |
| 25 | + | MyString: string; // в кодировке utf-8 | |
| 26 | + | begin | |
| 27 | + | MyString := MyTEdit.Text; | |
| 28 | + | SomeRTLRoutine(UTF8ToAnsi(MyString)); | |
| 29 | + | end; | |
| 30 | + | ``` | |
| 31 | + | ||
| 32 | + | И в обратную сторону: | |
| 33 | + | ||
| 34 | + | ``` pascal | |
| 35 | + | var | |
| 36 | + | MyString: string; // в кодировке ANSI | |
| 37 | + | begin | |
| 38 | + | MyString := SomeRTLRoutine; | |
| 39 | + | MyTEdit.Text := AnsiToUTF8(MyString); | |
| 40 | + | end; | |
| 41 | + | ``` | |
| 42 | + | ||
| 43 | + | **Важно**: UTF8ToAnsi возвратит пустую строку, если строка в UTF-8 содержит неверные символы. | |
| 44 | + | ||
| 45 | + | **Важно**: AnsiToUTF8 и UTF8ToAnsi требуют менеджера "широких" строк (widestring) в Linux, BSD и macOS. Можно воспользоваться функциями SysToUTF8 или UTF8ToSys (из модуля FileUtil) или добавить менеджер, включив cwstring одним из первых модулей в разделе uses программы. | |
| 46 | + | ||
| 47 | + | ### Строки WideString и AnsiString | |
| 48 | + | ||
| 49 | + | WideString -- это строковый тип, у которого базовый элемент данных имеет размер 2 байта. Строки этого типа почти всегда в кодировке UTF-16. См. [[Widestrings]] | |
| 50 | + | ||
| 51 | + | Обратите внимание, что при доступе к WideString как к массиву, каждый его элемент представлен 2 байтами, а символ в UTF-16 может состоять из 1 или 2 элементов, занимающих соответственно 2 или 4 байта. Поэтому при доступе к WideString как к массиву **совершенно неправильно** ожидать соответствия "один элемент -- один символ", наличие в строке 4-х байтовых символов приведёт к ошибкам. Заметьте также, что UTF-16 как и UTF-8, допускает составные (decomposed) символы. Например, символ "Á" может быть представлен как одним элементом, так и парой: "A" + изменяющий акцент. Таким образом, включающий акцентированные буквы текст в юникоде часто может быть закодирован различными способами, Lazarus и FPC не обрабатывают такие случаи автоматически. | |
| 52 | + | ||
| 53 | + | При передаче AnsiString в качестве WideString требуется преобразование кодировки. | |
| 54 | + | ||
| 55 | + | ``` pascal | |
| 56 | + | var | |
| 57 | + | w: widestring; | |
| 58 | + | begin | |
| 59 | + | w:='Über'; // неправильно, FPC преобразует системную кодовую страницу в UTF16 | |
| 60 | + | w:=UTF8ToUTF16('Über'); // правильно | |
| 61 | + | Button1.Caption:=UTF16ToUTF8(w); | |
| 62 | + | end; | |
| 63 | + | ``` | |
| 64 | + | ||
| 65 | + | ### Работа со строками и символами UTF-8 | |
| 66 | + | ||
| 67 | + | В Lazarus до версии 0.9.30 включительно функции LCL для работы с UTF-8 находились в модуле LCLProc. В Lazarus 0.9.31 и последующих они в целях совместимости по-прежнему доступны при подключении LCLProc, но сам код работы с UTF-8 находится в модуле lazutf8 пакета lazutils. | |
| 68 | + | ||
| 69 | + | Для выполнения операций над строками в UTF-8 используйте функции модуля lazutf8 вместо предоставляемых модулем SysUtils из FPC, поскольку SysUtils не готов для работы с юникодом, а lazutf8 готов. Просто замените утилиты из SysUtils на эквивалентные из lazutf8, имеющие те же имена с префиксом UTF-8. | |
| 70 | + | ||
| 71 | + | Учтите также, что перебор символов как элементов строки-массива не подходит для юникода. Эта особенность присуща не только UTF-8, юникод не предполагает фиксированный размер каждого символа. Возможны два основных способа перебора символов строки в кодировке UTF-8: | |
| 72 | + | ||
| 73 | + | - побайтовый -- подходит для поиска подстроки или поиска ASCII символов в строке UTF-8, например, при разборе файлов XML. | |
| 74 | + | - посимвольный -- подходит для экранных компонентов, таких как synedit, когда, например, нужно определить третий символ, отображаемый на экране. | |
| 75 | + | ||
| 76 | + | #### Поиск подстроки | |
| 77 | + | ||
| 78 | + | Благодаря особенностям UTF-8 для поиска подстроки подходят обычные функции поиска подстроки. Несмотря на мультибайтность, первый байт не может быть спутан со вторым. Поэтому поиск корректной подстроки в UTF-8 при помощи Pos всегда возвращает правильную позицию с точки зрения UTF-8: | |
| 79 | + | ||
| 80 | + | ``` pascal | |
| 81 | + | uses lazutf8; // LCLProc для Lazarus 0.9.30 и ранее | |
| 82 | + | ... | |
| 83 | + | procedure Where(SearchFor, aText: string); | |
| 84 | + | var | |
| 85 | + | BytePos: LongInt; | |
| 86 | + | CharacterPos: LongInt; | |
| 87 | + | begin | |
| 88 | + | BytePos:=Pos(SearchFor,aText); | |
| 89 | + | CharacterPos:=UTF8Length(PChar(aText),BytePos-1); | |
| 90 | + | writeln('Подстрока "',SearchFor,'" начинается в тексте "',aText,'"', | |
| 91 | + | ' с байта ',BytePos,', с символа ',CharacterPos); | |
| 92 | + | end; | |
| 93 | + | ``` | |
| 94 | + | ||
| 95 | + | Из-за неоднозначности юникода возможно неожиданное поведение Pos() (как и любых сравнений), если одна из строк содержит составные (decomposed) символы, а другая -- их непосредственные коды. RTL не обрабатывает такие случаи автоматически. | |
| 96 | + | ||
| 97 | + | #### Доступ к символам UTF-8 | |
| 98 | + | ||
| 99 | + | Юникодные символы могут различаться по длине, поэтому лучшее решение для посимвольного доступа -- последовательный перебор символов. Для перебора символов используйте следующий код: | |
| 100 | + | ||
| 101 | + | ``` pascal | |
| 102 | + | uses lazutf8; // LCLProc для Lazarus 0.9.30 и ранее | |
| 103 | + | ... | |
| 104 | + | procedure DoSomethingWithString(AnUTF8String: string); | |
| 105 | + | var | |
| 106 | + | p: PChar; | |
| 107 | + | CharLen: integer; | |
| 108 | + | FirstByte, SecondByte, ThirdByte: Char; | |
| 109 | + | begin | |
| 110 | + | p := PChar(AnUTF8String); | |
| 111 | + | repeat | |
| 112 | + | CharLen := UTF8CharacterLength(p); | |
| 113 | + | ||
| 114 | + | // У нас есть указатель на символ и его длина | |
| 115 | + | // Для побайтного доступа к UTF-8 символу: | |
| 116 | + | if CharLen >= 1 then FirstByte := P[0]; | |
| 117 | + | if CharLen >= 2 then SecondByte := P[1]; | |
| 118 | + | if CharLen >= 3 then ThirdByte := P[2]; | |
| 119 | + | ||
| 120 | + | inc(p,CharLen); | |
| 121 | + | until (CharLen=0) or (p^ = #0); | |
| 122 | + | end; | |
| 123 | + | ``` | |
| 124 | + | ||
| 125 | + | #### Доступ к n-му символу UTF-8 | |
| 126 | + | ||
| 127 | + | Иногда помимо последовательного требуется и произвольный доступ к символам UTF-8. | |
| 128 | + | ||
| 129 | + | ``` pascal | |
| 130 | + | uses lazutf8; // LCLProc для Lazarus 0.9.30 и ранее | |
| 131 | + | ... | |
| 132 | + | var | |
| 133 | + | AnUTF8String, NthChar: string; | |
| 134 | + | begin | |
| 135 | + | NthChar := UTF8Copy(AnUTF8String, N, 1); | |
| 136 | + | ``` | |
| 137 | + | ||
| 138 | + | #### Получение кодов символов при помощи UTF8CharacterToUnicode | |
| 139 | + | ||
| 140 | + | Пример демонстрирует получение 32-битного кода каждого символа строки в UTF-8: | |
| 141 | + | ||
| 142 | + | ``` pascal | |
| 143 | + | uses lazutf8; // LCLProc для Lazarus 0.9.30 и ранее | |
| 144 | + | ... | |
| 145 | + | procedure IterateUTF8Characters(const AnUTF8String: string); | |
| 146 | + | var | |
| 147 | + | p: PChar; | |
| 148 | + | unicode: Cardinal; | |
| 149 | + | CharLen: integer; | |
| 150 | + | begin | |
| 151 | + | p:=PChar(AnUTF8String); | |
| 152 | + | repeat | |
| 153 | + | unicode:=UTF8CharacterToUnicode(p,CharLen); | |
| 154 | + | writeln('Unicode=',unicode); | |
| 155 | + | inc(p,CharLen); | |
| 156 | + | until (CharLen=0) or (unicode=0); | |
| 157 | + | end; | |
| 158 | + | ``` | |
| 159 | + | ||
| 160 | + | #### UTF-8 Copy, Length, LowerCase и т.п. | |
| 161 | + | ||
| 162 | + | В модуле lazutf8 (LCLProc для Lazarus 0.9.30 и ранее) реализованы практически все операции над строками в UTF-8. Ниже приведён список утилит из lazutf8.pas: | |
| 163 | + | ||
| 164 | + | ``` pascal | |
| 165 | + | function UTF8CharacterLength(p: PChar): integer; | |
| 166 | + | function UTF8Length(const s: string): PtrInt; | |
| 167 | + | function UTF8Length(p: PChar; ByteCount: PtrInt): PtrInt; | |
| 168 | + | function UTF8CharacterToUnicode(p: PChar; out CharLen: integer): Cardinal; | |
| 169 | + | function UnicodeToUTF8(u: cardinal; Buf: PChar): integer; inline; | |
| 170 | + | function UnicodeToUTF8SkipErrors(u: cardinal; Buf: PChar): integer; | |
| 171 | + | function UnicodeToUTF8(u: cardinal): shortstring; inline; | |
| 172 | + | function UTF8ToDoubleByteString(const s: string): string; | |
| 173 | + | function UTF8ToDoubleByte(UTF8Str: PChar; Len: PtrInt; DBStr: PByte): PtrInt; | |
| 174 | + | function UTF8FindNearestCharStart(UTF8Str: PChar; Len: integer; | |
| 175 | + | BytePos: integer): integer; | |
| 176 | + | // поиск n-го символа UTF-8, без учёта двунаправленности (BIDI) | |
| 177 | + | function UTF8CharStart(UTF8Str: PChar; Len, CharIndex: PtrInt): PChar; | |
| 178 | + | // поск байтового индекса n-го символа UTF-8, без учёта двунаправленности (BIDI) (длина подстроки в байтах) | |
| 179 | + | function UTF8CharToByteIndex(UTF8Str: PChar; Len, CharIndex: PtrInt): PtrInt; | |
| 180 | + | procedure UTF8FixBroken(P: PChar); | |
| 181 | + | function UTF8CharacterStrictLength(P: PChar): integer; | |
| 182 | + | function UTF8CStringToUTF8String(SourceStart: PChar; SourceLen: PtrInt) : string; | |
| 183 | + | function UTF8Pos(const SearchForText, SearchInText: string): PtrInt; | |
| 184 | + | function UTF8Copy(const s: string; StartCharIndex, CharCount: PtrInt): string; | |
| 185 | + | procedure UTF8Delete(var s: String; StartCharIndex, CharCount: PtrInt); | |
| 186 | + | procedure UTF8Insert(const source: String; var s: string; StartCharIndex: PtrInt); | |
| 187 | + | ||
| 188 | + | function UTF8LowerCase(const AInStr: string; ALanguage: string=''): string; | |
| 189 | + | function UTF8UpperCase(const AInStr: string; ALanguage: string=''): string; | |
| 190 | + | function FindInvalidUTF8Character(p: PChar; Count: PtrInt; | |
| 191 | + | StopOnNonASCII: Boolean = false): PtrInt; | |
| 192 | + | function ValidUTF8String(const s: String): String; | |
| 193 | + | ||
| 194 | + | procedure AssignUTF8ListToAnsi(UTF8List, AnsiList: TStrings); | |
| 195 | + | ||
| 196 | + | //функции сравнения | |
| 197 | + | ||
| 198 | + | function UTF8CompareStr(const S1, S2: string): Integer; | |
| 199 | + | function UTF8CompareText(const S1, S2: string): Integer; | |
| 200 | + | ``` | |
| 201 | + | ||
| 202 | + | ### Работа с именами файлов и каталогов | |
| 203 | + | ||
| 204 | + | Элементы управления и функции Lazarus ожидают имена файлов и каталогов в кодировке UTF-8, но RTL использует для таких имён строки в ANSI. | |
| 205 | + | ||
| 206 | + | Например, пусть кнопка назначает текущий каталог свойству Directory элемента TFileListBox. Функция RTL [[doc:rtl/sysutils/getcurrentdir.html|GetCurrentDir]] использует ANSI, а не юникод, поэтому требуется преобразование: | |
| 207 | + | ||
| 208 | + | ``` pascal | |
| 209 | + | procedure TForm1.Button1Click(Sender: TObject); | |
| 210 | + | begin | |
| 211 | + | FileListBox1.Directory:=SysToUTF8(GetCurrentDir); | |
| 212 | + | // или с помощью функциий из модуля FileUtil | |
| 213 | + | FileListBox1.Directory:=GetCurrentDirUTF8; | |
| 214 | + | end; | |
| 215 | + | ``` | |
| 216 | + | ||
| 217 | + | Модуль FileUtil содержит общие файловые функции в варианте UTF-8: | |
| 218 | + | ||
| 219 | + | ``` pascal | |
| 220 | + | // основные функции как в RTL, но в кодировке UTF-8 | |
| 221 | + | // вместо системной | |
| 222 | + | ||
| 223 | + | // AnsiToUTF8 и UTF8ToAnsi требуют менеджер широких строк в Linux, BSD, macOS, | |
| 224 | + | // но эти ОС обычно используют в качестве системной кодировки UTF-8, | |
| 225 | + | // и тогда менеджер не нужен. | |
| 226 | + | function NeedRTLAnsi: boolean;// истина если системная кодировка не UTF-8 | |
| 227 | + | procedure SetNeedRTLAnsi(NewValue: boolean); | |
| 228 | + | function UTF8ToSys(const s: string): string;// как UTF8ToAnsi, но более независима от менеджера широких строк | |
| 229 | + | function SysToUTF8(const s: string): string;// как AnsiToUTF8, но более независима от менеджера широких строк | |
| 230 | + | ||
| 231 | + | // операции с файлами | |
| 232 | + | function FileExistsUTF8(const Filename: string): boolean; | |
| 233 | + | function FileAgeUTF8(const FileName: string): Longint; | |
| 234 | + | function DirectoryExistsUTF8(const Directory: string): Boolean; | |
| 235 | + | function ExpandFileNameUTF8(const FileName: string): string; | |
| 236 | + | function ExpandUNCFileNameUTF8(const FileName: string): string; | |
| 237 | + | {$IFNDEF VER2_2_0} | |
| 238 | + | function ExtractShortPathNameUTF8(Const FileName : String) : String; | |
| 239 | + | {$ENDIF} | |
| 240 | + | function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint; | |
| 241 | + | function FindNextUTF8(var Rslt: TSearchRec): Longint; | |
| 242 | + | procedure FindCloseUTF8(var F: TSearchrec); | |
| 243 | + | function FileSetDateUTF8(const FileName: String; Age: Longint): Longint; | |
| 244 | + | function FileGetAttrUTF8(const FileName: String): Longint; | |
| 245 | + | function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint; | |
| 246 | + | function DeleteFileUTF8(const FileName: String): Boolean; | |
| 247 | + | function RenameFileUTF8(const OldName, NewName: String): Boolean; | |
| 248 | + | function FileSearchUTF8(const Name, DirList : String): String; | |
| 249 | + | function FileIsReadOnlyUTF8(const FileName: String): Boolean; | |
| 250 | + | function GetCurrentDirUTF8: String; | |
| 251 | + | function SetCurrentDirUTF8(const NewDir: String): Boolean; | |
| 252 | + | function CreateDirUTF8(const NewDir: String): Boolean; | |
| 253 | + | function RemoveDirUTF8(const Dir: String): Boolean; | |
| 254 | + | function ForceDirectoriesUTF8(const Dir: string): Boolean; | |
| 255 | + | ||
| 256 | + | // окружение | |
| 257 | + | function ParamStrUTF8(Param: Integer): string; | |
| 258 | + | function GetEnvironmentStringUTF8(Index : Integer): String; | |
| 259 | + | function GetEnvironmentVariableUTF8(const EnvVar: String): String; | |
| 260 | + | function GetAppConfigDirUTF8(Global: Boolean): string; | |
| 261 | + | ``` | |
| 262 | + | ||
| 263 | + | ### Восточно-азиатские языки в Windows | |
| 264 | + | ||
| 265 | + | Шрифт элементов управления пользовательского интерфейса по умолчанию (Tahoma) в Windows XP в состоянии правильно отображать несколько письменностей/алфавитов/языков, включая арабский, русский (кириллица) и западноевропейские языки (латинский/греческий алфавиты), но не восточно-азиатские как китайский, японский и корейский. | |
| 266 | + | ||
| 267 | + | Для корректного отображения указанных языков стандартным шрифтом достаточно в Панели управления выбрать Региональные настройки, щёлкнуть вкладку Языки и установить поддержку восточно-азиатских языков. Естественно, что в версиях Windows XP на указанных языках этот пакет включён по умолчанию. Дополнительные инструкции [здесь](http://newton.uor.edu/Departments&Programs/AsianStudiesDept/Language/asianlanguageinstallation_XP.html). | |
| 268 | + | ||
| 269 | + | Более позднее версии Windows скорее всего не требуют установки дополнений для поддержки указанных языков. | |
| 270 | + | ||
| 271 | + | ## Особенности Free Pascal | |
| 272 | + | ||
| 273 | + | ### UTF-8 и исходные файлы -- отсутствие BOM | |
| 274 | + | ||
| 275 | + | Когда в созданном в Lazarus файле исходного кода используются не-ASCII символы, он сохраняется в UTF-8. При этом **не** используется **BOM** (Byte Order Mark -- маркер порядка байтов). Кодировку можно изменить при помощи правого щелчка по редактору кода / File Settings / Encoding. Помимо того, что файлы в UTF-8 не обязаны иметь BOM, причина его отсутствия скорее в том, как FPC обрабатывает строки AnsiString. LCL использует AnsiString для совместимости и UTF-8 для переносимости. | |
| 276 | + | ||
| 277 | + | Примечание. Некоторые текстовые редакторы в MS Windows могут полагать, что файлы используют системную кодовую страницу (кодировку OEM) и отображают в них неправильные символы. Не добавляйте BOM. Если добавить BOM, то придётся изменить все строковые присваивания. | |
| 278 | + | ||
| 279 | + | Пример: | |
| 280 | + | ||
| 281 | + | ``` pascal | |
| 282 | + | Button1.Caption := 'Über'; | |
| 283 | + | ``` | |
| 284 | + | ||
| 285 | + | Когда отсутствует BOM (и кодировка не задана параметром) компилятор считает, что строка в системной кодировке и копирует каждый байт в строку без преобразований. LCL ожидает строки именно в таком виде. | |
| 286 | + | ||
| 287 | + | ``` pascal | |
| 288 | + | // исходный файл сохранён в UTF без BOM | |
| 289 | + | if FileExists('Über.txt') then ; // неправильно, FileExists ожидает системную кодировку | |
| 290 | + | if FileExistsUTF8('Über.txt') then ; // правильно | |
| 291 | + | ``` | |
| 292 | + | ||
| 293 | + | ## Основы юникода | |
| 294 | + | ||
| 295 | + | Стандарт Unicode сопоставляет целые от 0 до 10FFFF(h) символам. Каждое такое сопоставление именуется кодовой точкой (code point). Другими словами, юникодные символы определены для кодовых точек от U+000000 до U+10FFFF (от 0 до 1 114 111). | |
| 296 | + | ||
| 297 | + | Для представления кодовых точек как уникальных байтовых последовательностей существуют три основных схемы. Эти схемы называют форматами юникодных преобразований: UTF-8, UTF-16 и UTF-32. Преобразование между всеми ними возможно. Их основные свойства: | |
| 298 | + | ||
| 299 | + | ` UTF-8 UTF-16 UTF-32`\ | |
| 300 | + | `Наименьшая кодовая точка [16] 000000 000000 000000`\ | |
| 301 | + | `Наибольшая кодовая точка [16] 10FFFF 10FFFF 10FFFF`\ | |
| 302 | + | `Размер кодового элемента [бит] 8 16 32`\ | |
| 303 | + | `Минимум байт на символ 1 2 4`\ | |
| 304 | + | `Максимум байт на символ 4 4 4` | |
| 305 | + | ||
| 306 | + | **UTF-8** обладает несколькими важными и полезными свойствами: Содержимое интерпретируется как последовательность байтов, поэтому понятие порядка байт "от младшего" или "от старшего" отсутствует. Юникодные символы от U+0000 до U+007F (ASCII) кодируются байтами от 00h до 7Fh (совместимо с ASCII). Это значит, что файлы и строки, содержащие только 7-битные символы ASCII, кодируются одинаково в ASCII и в UTF-8. Все символы больше U+007F кодируются как последовательность нескольких байтов, у каждого из которых старшие биты установлены в 1. Последовательность байт одного символа заведомо не содержится в более длинной последовательности байт другого символа. Это позволяет без затруднений искать подстроки. Первый байт мультибайтовой последовательности (представляющей не-ASCII символ) всегда принадлежит диапазону от C0h до FDh и показывает количество последующих байт для этого символа. Все последующие байты мультибайтной последовательности принадлежат диапазону от 80h до BFh. Это позволяет легко выполнять ресинхронизацию и способствует надежности. | |
| 307 | + | ||
| 308 | + | **UTF-16** обладает следующими важными свойствами: Используется одно 16-битное слово для кодирования символов от U+0000 до U+d7ff и два 16-битных слова для кодирования всех остальных символов. | |
| 309 | + | ||
| 310 | + | В заключение, любой юникодный символ возможно представить как один 4-байтный/32-битный элемент в **UTF-32**. | |
| 311 | + | ||
| 312 | + | См. также: [Unicode FAQ - Basic questions](http://www.unicode.org/faq/basic_q.html), [Unicode FAQ - UTF-8, UTF-16, UTF-32 & BOM](http://www.unicode.org/faq/utf_bom.html), [Wikipedia: UTF-8](http://en.wikipedia.org/wiki/UTF-8) [1](http://en.wikipedia.org/wiki/ISO-8859) | |
| 313 | + | ||
| 314 | + | # Детали реализации | |
| 315 | + | ||
| 316 | + | ## Lazarus/LCL обычно использует только UTF-8 | |
| 317 | + | ||
| 318 | + | Начиная с Lazarus 0.9.31 интерфейс GTK1 объявлен устаревшим, все интерфейсы LCL совместимы с юникодом, Lazarus и LCL используют строки только в кодировке UTF-8, если явно не указано, что функция/процедура принимает другие кодировки. | |
| 319 | + | ||
| 320 | + | ## Перевод интерфейса Win32 на юникод | |
| 321 | + | ||
| 322 | + | ### Обзор | |
| 323 | + | ||
| 324 | + | Сперва, и это самое важное, во избежание поломки существующего интерфейса ANSI следует все юникодные интерфейсные модификации заключить в IFDEF WindowsUnicodeSupport. После стабилизации модификаций все IFDEF-ы будут убраны и останется только юникодная часть. К тому моменту все существующие программы, использующие символы ANSI, потребуют миграции на юникод. | |
| 325 | + | ||
| 326 | + | ### Win9x не поддерживает юникод | |
| 327 | + | ||
| 328 | + | Платформы \<= Win9x основаны на стандартах кодовых страниц ISO и поддерживают юникод только частично. Платформы Windows, начиная с WinNT, и Windows CE юникод поддерживают полностью. | |
| 329 | + | ||
| 330 | + | Win 9x и NT предлагают два параллельных набора функций API: *A -- старый под ANSI, *W -- новый юникодный. Функции *W принимают в качестве параметров широкие строки, то есть строки в кодировке UTF-16. В Windows 9x присутствуют все функции *W, но имеют пустые реализации. Поэтому они ничего не делают. Но некоторые из них реализованы полностью даже в 9x и перечислены ниже в разделе "Широкие функции, присутствующие в Windows 9x". Это свойство актуально, поскольку позволяет иметь одно приложение для Win9x и WinNT и во время его исполнения определять, какой набор API использовать. | |
| 331 | + | ||
| 332 | + | Windows CE использует только широкие функции API. | |
| 333 | + | ||
| 334 | + | #### Широкие функции, присутствующие в Windows 9x | |
| 335 | + | ||
| 336 | + | Некоторые функции широкого API присутствуют в Windows 9x. Вот список таких функций: <http://support.microsoft.com/kb/210341> | |
| 337 | + | ||
| 338 | + | Пример преобразования: | |
| 339 | + | ||
| 340 | + | ``` pascal | |
| 341 | + | GetTextExtentPoint32(hdcNewBitmap, LPSTR(ButtonCaption), | |
| 342 | + | Length(ButtonCaption), TextSize); | |
| 343 | + | ``` | |
| 344 | + | ||
| 345 | + | Станет: | |
| 346 | + | ||
| 347 | + | ``` pascal | |
| 348 | + | {$ifdef WindowsUnicodeSupport} | |
| 349 | + | GetTextExtentPoint32W(hdcNewBitmap, PWideChar(Utf8Decode(ButtonCaption)), Length(WideCaption), TextSize); | |
| 350 | + | {$else} | |
| 351 | + | GetTextExtentPoint32(hdcNewBitmap, LPSTR(ButtonCaption), Length(ButtonCaption), TextSize); | |
| 352 | + | {$endif} | |
| 353 | + | ``` | |
| 354 | + | ||
| 355 | + | #### Функции, требующие версий Ansi и Wide | |
| 356 | + | ||
| 357 | + | Пример преобразования: | |
| 358 | + | ||
| 359 | + | ``` pascal | |
| 360 | + | function TGDIWindow.GetTitle: String; | |
| 361 | + | var | |
| 362 | + | l: Integer; | |
| 363 | + | begin | |
| 364 | + | l := Windows.GetWindowTextLength(Handle); | |
| 365 | + | SetLength(Result, l); | |
| 366 | + | Windows.GetWindowText(Handle, @Result[1], l); | |
| 367 | + | end; | |
| 368 | + | ``` | |
| 369 | + | ||
| 370 | + | Станет: | |
| 371 | + | ||
| 372 | + | ``` pascal | |
| 373 | + | function TGDIWindow.GetTitle: String; | |
| 374 | + | var | |
| 375 | + | l: Integer; | |
| 376 | + | AnsiBuffer: string; | |
| 377 | + | WideBuffer: WideString; | |
| 378 | + | begin | |
| 379 | + | ||
| 380 | + | {$ifdef WindowsUnicodeSupport} | |
| 381 | + | ||
| 382 | + | if UnicodeEnabledOS then | |
| 383 | + | begin | |
| 384 | + | l := Windows.GetWindowTextLengthW(Handle); | |
| 385 | + | SetLength(WideBuffer, l); | |
| 386 | + | l := Windows.GetWindowTextW(Handle, @WideBuffer[1], l); | |
| 387 | + | SetLength(WideBuffer, l); | |
| 388 | + | Result := Utf8Encode(WideBuffer); | |
| 389 | + | end | |
| 390 | + | else | |
| 391 | + | begin | |
| 392 | + | l := Windows.GetWindowTextLength(Handle); | |
| 393 | + | SetLength(AnsiBuffer, l); | |
| 394 | + | l := Windows.GetWindowText(Handle, @AnsiBuffer[1], l); | |
| 395 | + | SetLength(AnsiBuffer, l); | |
| 396 | + | Result := AnsiToUtf8(AnsiBuffer); | |
| 397 | + | end; | |
| 398 | + | ||
| 399 | + | {$else} | |
| 400 | + | ||
| 401 | + | l := Windows.GetWindowTextLength(Handle); | |
| 402 | + | SetLength(Result, l); | |
| 403 | + | Windows.GetWindowText(Handle, @Result[1], l); | |
| 404 | + | ||
| 405 | + | {$endif} | |
| 406 | + | ||
| 407 | + | end; | |
| 408 | + | ``` | |
| 409 | + | ||
| 410 | + | ### Снимки экрана | |
| 411 | + | ||
| 412 | + | <figure> | |
| 413 | + | <img src="Lazarus_Unicode_Test.png" title="Lazarus_Unicode_Test.png" /> | |
| 414 | + | <figcaption>Lazarus_Unicode_Test.png</figcaption> | |
| 415 | + | </figure> | |
| 416 | + | ||
| 417 | + | # Кодовые страницы FPC | |
| 418 | + | ||
| 419 | + | Почему LCL не использует кодовые страницы для исходных текстов? | |
| 420 | + | ||
| 421 | + | -Fcutf8 и {\$codepage utf8} существуют не первый год. | |
| 422 | + | ||
| 423 | + | Следующие программы требуют FPC 2.7.1. Описанные трудности существуют и на более старых компиляторах. | |
| 424 | + | ||
| 425 | + | Есть в использовании -Fcutf8 и {\$codepage utf8} несколько ловушек. Директивы работают, если DefaultSystemCodePage из RTL возвращает CP_UTF8. Иначе компилятор перекодирует строки. Например, в Linux умолчание для RTL -- CP_ACP, что по умолчанию ISO_8859-1. RTL самостоятельно **не** читает язык из переменных окружения. Поэтому умолчанием будет ISO_8859-1. Это означает, что строковые константы в UTF-8 будут преобразованы компилятором. | |
| 426 | + | ||
| 427 | + | Откомпилируйте с -Fcutf8 и запустите в Linux с LANG в utf-8: | |
| 428 | + | ||
| 429 | + | ``` pascal | |
| 430 | + | program project1; | |
| 431 | + | {$mode objfpc}{$H+} | |
| 432 | + | begin | |
| 433 | + | writeln(DefaultSystemCodePage,' ',CP_UTF8); | |
| 434 | + | writeln('ä'); | |
| 435 | + | end. | |
| 436 | + | ``` | |
| 437 | + | ||
| 438 | + | Результат: | |
| 439 | + | ||
| 440 | + | 0 65001 | |
| 441 | + | ä | |
| 442 | + | ||
| 443 | + | LCL использует widestringmanager -- менеджер широких строк (в настоящее время cwstring), который назначает DefaultSystemCodePage. Тоже самое можно сделать и в не-LCL программах: | |
| 444 | + | ||
| 445 | + | ``` pascal | |
| 446 | + | program project1; | |
| 447 | + | {$mode objfpc}{$H+} | |
| 448 | + | uses cwstring; | |
| 449 | + | begin | |
| 450 | + | writeln(DefaultSystemCodePage,' ',CP_UTF8); | |
| 451 | + | writeln('ä'); | |
| 452 | + | end. | |
| 453 | + | ``` | |
| 454 | + | ||
| 455 | + | Результат: | |
| 456 | + | ||
| 457 | + | 65001 65001 | |
| 458 | + | ä | |
| 459 | + | ||
| 460 | + | Приведённый выше код прост и понятен, жаль только, что работает подобное лишь в простейших случаях. Посмотрим на программу ниже: | |
| 461 | + | ||
| 462 | + | ``` pascal | |
| 463 | + | program project1; | |
| 464 | + | {$mode objfpc}{$H+} | |
| 465 | + | uses cwstring; | |
| 466 | + | var | |
| 467 | + | a,b,c: string; | |
| 468 | + | begin | |
| 469 | + | writeln(DefaultSystemCodePage,' ',CP_UTF8); | |
| 470 | + | a:='ä'; b:='='#$C3#$A4; // #$C3#$A4 это UTF-8 для ä | |
| 471 | + | c:= 'ä='#$C3#$A4; | |
| 472 | + | writeln(a,b); // writes ä=ä | |
| 473 | + | writeln(c); // writes ä=ä | |
| 474 | + | end. | |
| 475 | + | ``` | |
| 476 | + | ||
| 477 | + | 65001 65001 | |
| 478 | + | ä=ä | |
| 479 | + | ä=ä | |
| 480 | + | ||
| 481 | + | Посмотрите, работают строковые константы в UTF-8, строковые константы с кодами UTF-8, но не их комбинация. Приведённый выше код скомпилирован с -Fcutf8 и использует cwstring для установки DefaultSystemCodePage в CP_UTF8. | |
| 482 | + | ||
| 483 | + | Так что же пошло не так? | |
| 484 | + | ||
| 485 | + | Компилятор обрабатывает строковые не-ASCII константы (здесь -- ä) как широкие строки (UCS2, не UTF-16). | |
| 486 | + | ||
| 487 | + | Одурачить компилятор при помощи 'ä='+#\$C3#\$A4 не выйдет. Придётся определить две отдельные строковые константы. | |
| 488 | + | ||
| 489 | + | Использование символов не из диапазона UCS-2 приведёт к | |
| 490 | + | ||
| 491 | + | Fatal: illegal character "'�'" ($F0) | |
| 492 | + | ||
| 493 | + | Но можно указать их в кодах UTF-16: *\#\$D834#\$DD1E*. Да, прочитали правильно. Указание кодовой страницы при помощи -Fcutf8 или {\$codepage utf8} действительно определяет смесь UTF-8 и UTF-16. | |
| 494 | + | ||
| 495 | + | Теперь откомпилируйте приведённый выше код без -Fcutf8: | |
| 496 | + | ||
| 497 | + | 65001 65001 | |
| 498 | + | ä=ä | |
| 499 | + | ä=ä | |
| 500 | + | ||
| 501 | + | Вот это да, всё как хотелось. Можно даже смешивать строковые константы в ASCII и не-ASCII. Без указания кодовой страницы компилятор просто сохраняет строковые константы как последовательности байтов. А это и есть UTF-8. | |
| 502 | + | ||
| 503 | + | В этом одна из причин того, почему LCL приложения не используют флаги кодировок. | |
| 504 | + | ||
| 505 | + | В msegui реализована экосистема широких строк, которая работает лучше, чем кодовые страницы. | |
| 506 | + | ||
| 507 | + | # RTL с кодовой страницей UTF-8 по умолчанию | |
| 508 | + | ||
| 509 | + | **Эксперимент -- требуется тестирование под Windows!** | |
| 510 | + | ||
| 511 | + | Обычно RTL использует для строк системную кодовую страницу (например, FileExists или TStringList.LoadFromFile). Под Windows RTL не использует юникод, вам недоступны символы вне вашей языковой группы. LCL работает с кодировкой UTF-8, поддерживающей все символы юникода. Под Linux и macOS системной кодовой страницей обычно является UTF-8, поэтому для них RTL по умолчанию использует CP_UTF8. | |
| 512 | + | ||
| 513 | + | Начиная с FPC 2.7.1 кодовая страница, используемая RTL по умолчанию, может быть изменена на UTF-8 (CP_UTF8). Таким образом, пользователи Windows теперь смогут использовать в RTL строки UTF-8. Для тестирования добавьте -dEnableUTF8RTL в *Lazarus build options* (параметры сборки проекта) и пересоберите проект. | |
| 514 | + | ||
| 515 | + | - Например, FileExists и aStringList.LoadFromFile(Filename) теперь полностью поддерживают юникод. Полный перечень функций, уже поддерживающих юникод полностью, смотрите по ссылке: | |
| 516 | + | ||
| 517 | + | <http://wiki.freepascal.org/FPC_Unicode_support#RTL_changes> | |
| 518 | + | ||
| 519 | + | - *AnsiToUTF8, UTF8ToAnsi, SysToUTF8, UTF8ToAnsi* не действуют. Они использовались преимущественно для вышеуказанных функций, которые больше не требуют преобразований аргументов и результата. | |
| 520 | + | - Многочисленные явные вызовы *UTF8Encode* и *UTF8Decode* больше не нужны, поскольку при присваивании строки в *UnicodeString* строке String и наоборот компилятор сделает это автоматически. | |
| 521 | + | - При обращении к WinAPI необходимо использовать функции с "W" либо пользоваться функциями **UTF8ToWinCP** и **WinCPToUTF8**. | |
| 522 | + | - Включить новый режим можно перекомпилировав Lazarus с **-dEnableUTF8RTL**. | |
| 523 | + | - Если используете строковые литералы с **WideString**, **UnicodeString** или **UTF8String**, то теперь исходные тексты **должны** быть в соответствующей кодировке. Например, можно использовать исходные тексты в UTF-8 (поведение Lazarus по умолчанию) и передавать компилятору параметр **-FcUTF8**. | |
| 524 | + | - "String" и "UTF8String" -- разные типы. При присваивании *String* переменной в *UTF8String* компилятор добавляет код проверки соответствия кодировок. Это повлечёт увеличение времени исполнения и размера машинного кода. | |
| 525 | + | ||
| 526 | + | Дополнительную информацию о новой поддержке юникода в FPC см. по ссылке: <http://wiki.freepascal.org/FPC_Unicode_support> | |
| 527 | + | ||
| 528 | + | # См. также | |
| 529 | + | ||
| 530 | + | - [[UTF-8]] - Описание строк в UTF-8 | |
| 531 | + | ||
| 532 | + | --- | |
| 533 | + | *From the Free Pascal wiki, page "LCL Unicode Support/ru", as it stood on 2019-12-22 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* | |
| 534 | + | Category: FPC/ru | |
| 535 | + | Category: LCL/ru | |
| 536 | + | Category: Russian | |
| 537 | + | Category: Unicode/ru |
