Sign in Join
Wiki copytemporary

LCL Unicode Support/ru: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Введение
22
33Начиная с 0.9.25 Lazarus полностью поддерживает юникод (Unicode) на всех платформах, исключая Gtk 1. На этой странице размещены инструкции для пользователей Lazarus, планы на будущее, описаны основные концепции и детали реализации.
44
55## Инструкции для пользователей
66
77Хотя в Lazarus есть юникодные наборы виджетов, важно отметить, что не всё в юникоде. Кодировка строк и правильное преобразование между библиотеками, ожидающими разные кодировки, является ответственностью разработчика.
88
99Обычно библиотекой используется единая кодировка строк (речь о динамической библиотеке DLL или пакете Lazarus). Каждая библиотека обычно ожидает одну кодировку, которой может быть юникод (UTF-8 для Lazarus) или ANSI (что означает системную кодировку, которая может быть UTF-8 или другой). RTL и FCL в FPC версий 2.4-2.6 ожидают строки ANSI.
1010
1111Преобразование между юникодом и ANSI возможно при помощи функций **UTF8ToAnsi** и **AnsiToUTF8** модуля System или **UTF8ToSys** и **SysToUTF8** модуля FileUtil (Lazarus). Последние две мощнее (быстрее), но добавят к программе больше кода.
1212
1313### FPC не юникодный
1414
1515Библиотеки Free Pascal времени исполнения (RTL) и свободных компонентов (FCL) в текущих версиях FPC (по 2.6.1) используют ANSI, поэтому требуется преобразование строк, получаемых из юникодных или переделываемых в юникодные библиотек (например, LCL).
1616
1717### Преобразование между ANSI и юникодом
1818
1919Примеры:
2020
2121Допустим, нужно передать в файловую функцию из RTL строку, полученную из TEdit:
2222
2323``` pascal
2424var
2525 MyString: string; // в кодировке utf-8
2626begin
2727 MyString := MyTEdit.Text;
2828 SomeRTLRoutine(UTF8ToAnsi(MyString));
2929end;
3030```
3131
3232И в обратную сторону:
3333
3434``` pascal
3535var
3636 MyString: string; // в кодировке ANSI
3737begin
3838 MyString := SomeRTLRoutine;
3939 MyTEdit.Text := AnsiToUTF8(MyString);
4040end;
4141```
4242
4343**Важно**: UTF8ToAnsi возвратит пустую строку, если строка в UTF-8 содержит неверные символы.
4444
4545**Важно**: AnsiToUTF8 и UTF8ToAnsi требуют менеджера "широких" строк (widestring) в Linux, BSD и macOS. Можно воспользоваться функциями SysToUTF8 или UTF8ToSys (из модуля FileUtil) или добавить менеджер, включив cwstring одним из первых модулей в разделе uses программы.
4646
4747### Строки WideString и AnsiString
4848
4949WideString -- это строковый тип, у которого базовый элемент данных имеет размер 2 байта. Строки этого типа почти всегда в кодировке UTF-16. См. [[Widestrings]]
5050
5151Обратите внимание, что при доступе к WideString как к массиву, каждый его элемент представлен 2 байтами, а символ в UTF-16 может состоять из 1 или 2 элементов, занимающих соответственно 2 или 4 байта. Поэтому при доступе к WideString как к массиву **совершенно неправильно** ожидать соответствия "один элемент -- один символ", наличие в строке 4-х байтовых символов приведёт к ошибкам. Заметьте также, что UTF-16 как и UTF-8, допускает составные (decomposed) символы. Например, символ "Á" может быть представлен как одним элементом, так и парой: "A" + изменяющий акцент. Таким образом, включающий акцентированные буквы текст в юникоде часто может быть закодирован различными способами, Lazarus и FPC не обрабатывают такие случаи автоматически.
5252
5353При передаче AnsiString в качестве WideString требуется преобразование кодировки.
5454
5555``` pascal
5656var
5757 w: widestring;
5858begin
5959 w:='Über'; // неправильно, FPC преобразует системную кодовую страницу в UTF16
6060 w:=UTF8ToUTF16('Über'); // правильно
6161 Button1.Caption:=UTF16ToUTF8(w);
6262end;
6363```
6464
6565### Работа со строками и символами UTF-8
6666
6767В Lazarus до версии 0.9.30 включительно функции LCL для работы с UTF-8 находились в модуле LCLProc. В Lazarus 0.9.31 и последующих они в целях совместимости по-прежнему доступны при подключении LCLProc, но сам код работы с UTF-8 находится в модуле lazutf8 пакета lazutils.
6868
6969Для выполнения операций над строками в UTF-8 используйте функции модуля lazutf8 вместо предоставляемых модулем SysUtils из FPC, поскольку SysUtils не готов для работы с юникодом, а lazutf8 готов. Просто замените утилиты из SysUtils на эквивалентные из lazutf8, имеющие те же имена с префиксом UTF-8.
7070
7171Учтите также, что перебор символов как элементов строки-массива не подходит для юникода. Эта особенность присуща не только UTF-8, юникод не предполагает фиксированный размер каждого символа. Возможны два основных способа перебора символов строки в кодировке UTF-8:
7272
7373- побайтовый -- подходит для поиска подстроки или поиска ASCII символов в строке UTF-8, например, при разборе файлов XML.
7474- посимвольный -- подходит для экранных компонентов, таких как synedit, когда, например, нужно определить третий символ, отображаемый на экране.
7575
7676#### Поиск подстроки
7777
7878Благодаря особенностям UTF-8 для поиска подстроки подходят обычные функции поиска подстроки. Несмотря на мультибайтность, первый байт не может быть спутан со вторым. Поэтому поиск корректной подстроки в UTF-8 при помощи Pos всегда возвращает правильную позицию с точки зрения UTF-8:
7979
8080``` pascal
8181uses lazutf8; // LCLProc для Lazarus 0.9.30 и ранее
8282...
8383procedure Where(SearchFor, aText: string);
8484var
8585 BytePos: LongInt;
8686 CharacterPos: LongInt;
8787begin
8888 BytePos:=Pos(SearchFor,aText);
8989 CharacterPos:=UTF8Length(PChar(aText),BytePos-1);
9090 writeln('Подстрока "',SearchFor,'" начинается в тексте "',aText,'"',
9191 ' с байта ',BytePos,', с символа ',CharacterPos);
9292end;
9393```
9494
9595Из-за неоднозначности юникода возможно неожиданное поведение Pos() (как и любых сравнений), если одна из строк содержит составные (decomposed) символы, а другая -- их непосредственные коды. RTL не обрабатывает такие случаи автоматически.
9696
9797#### Доступ к символам UTF-8
9898
9999Юникодные символы могут различаться по длине, поэтому лучшее решение для посимвольного доступа -- последовательный перебор символов. Для перебора символов используйте следующий код:
100100
101101``` pascal
102102uses lazutf8; // LCLProc для Lazarus 0.9.30 и ранее
103103...
104104procedure DoSomethingWithString(AnUTF8String: string);
105105var
106106 p: PChar;
107107 CharLen: integer;
108108 FirstByte, SecondByte, ThirdByte: Char;
109109begin
110110 p := PChar(AnUTF8String);
111111 repeat
112112 CharLen := UTF8CharacterLength(p);
113113
114114 // У нас есть указатель на символ и его длина
115115 // Для побайтного доступа к UTF-8 символу:
116116 if CharLen >= 1 then FirstByte := P[0];
117117 if CharLen >= 2 then SecondByte := P[1];
118118 if CharLen >= 3 then ThirdByte := P[2];
119119
120120 inc(p,CharLen);
121121 until (CharLen=0) or (p^ = #0);
122122end;
123123```
124124
125125#### Доступ к n-му символу UTF-8
126126
127127Иногда помимо последовательного требуется и произвольный доступ к символам UTF-8.
128128
129129``` pascal
130130uses lazutf8; // LCLProc для Lazarus 0.9.30 и ранее
131131...
132132var
133133 AnUTF8String, NthChar: string;
134134begin
135135 NthChar := UTF8Copy(AnUTF8String, N, 1);
136136```
137137
138138#### Получение кодов символов при помощи UTF8CharacterToUnicode
139139
140140Пример демонстрирует получение 32-битного кода каждого символа строки в UTF-8:
141141
142142``` pascal
143143uses lazutf8; // LCLProc для Lazarus 0.9.30 и ранее
144144...
145145procedure IterateUTF8Characters(const AnUTF8String: string);
146146var
147147 p: PChar;
148148 unicode: Cardinal;
149149 CharLen: integer;
150150begin
151151 p:=PChar(AnUTF8String);
152152 repeat
153153 unicode:=UTF8CharacterToUnicode(p,CharLen);
154154 writeln('Unicode=',unicode);
155155 inc(p,CharLen);
156156 until (CharLen=0) or (unicode=0);
157157end;
158158```
159159
160160#### UTF-8 Copy, Length, LowerCase и т.п.
161161
162162В модуле lazutf8 (LCLProc для Lazarus 0.9.30 и ранее) реализованы практически все операции над строками в UTF-8. Ниже приведён список утилит из lazutf8.pas:
163163
164164``` pascal
165165function UTF8CharacterLength(p: PChar): integer;
166166function UTF8Length(const s: string): PtrInt;
167167function UTF8Length(p: PChar; ByteCount: PtrInt): PtrInt;
168168function UTF8CharacterToUnicode(p: PChar; out CharLen: integer): Cardinal;
169169function UnicodeToUTF8(u: cardinal; Buf: PChar): integer; inline;
170170function UnicodeToUTF8SkipErrors(u: cardinal; Buf: PChar): integer;
171171function UnicodeToUTF8(u: cardinal): shortstring; inline;
172172function UTF8ToDoubleByteString(const s: string): string;
173173function UTF8ToDoubleByte(UTF8Str: PChar; Len: PtrInt; DBStr: PByte): PtrInt;
174174function UTF8FindNearestCharStart(UTF8Str: PChar; Len: integer;
175175 BytePos: integer): integer;
176176// поиск n-го символа UTF-8, без учёта двунаправленности (BIDI)
177177function UTF8CharStart(UTF8Str: PChar; Len, CharIndex: PtrInt): PChar;
178178// поск байтового индекса n-го символа UTF-8, без учёта двунаправленности (BIDI) (длина подстроки в байтах)
179179function UTF8CharToByteIndex(UTF8Str: PChar; Len, CharIndex: PtrInt): PtrInt;
180180procedure UTF8FixBroken(P: PChar);
181181function UTF8CharacterStrictLength(P: PChar): integer;
182182function UTF8CStringToUTF8String(SourceStart: PChar; SourceLen: PtrInt) : string;
183183function UTF8Pos(const SearchForText, SearchInText: string): PtrInt;
184184function UTF8Copy(const s: string; StartCharIndex, CharCount: PtrInt): string;
185185procedure UTF8Delete(var s: String; StartCharIndex, CharCount: PtrInt);
186186procedure UTF8Insert(const source: String; var s: string; StartCharIndex: PtrInt);
187187
188188function UTF8LowerCase(const AInStr: string; ALanguage: string=''): string;
189189function UTF8UpperCase(const AInStr: string; ALanguage: string=''): string;
190190function FindInvalidUTF8Character(p: PChar; Count: PtrInt;
191191 StopOnNonASCII: Boolean = false): PtrInt;
192192function ValidUTF8String(const s: String): String;
193193
194194procedure AssignUTF8ListToAnsi(UTF8List, AnsiList: TStrings);
195195
196196//функции сравнения
197197
198198function UTF8CompareStr(const S1, S2: string): Integer;
199199function UTF8CompareText(const S1, S2: string): Integer;
200200```
201201
202202### Работа с именами файлов и каталогов
203203
204204Элементы управления и функции Lazarus ожидают имена файлов и каталогов в кодировке UTF-8, но RTL использует для таких имён строки в ANSI.
205205
206206Например, пусть кнопка назначает текущий каталог свойству Directory элемента TFileListBox. Функция RTL [[doc:rtl/sysutils/getcurrentdir.html|GetCurrentDir]] использует ANSI, а не юникод, поэтому требуется преобразование:
207207
208208``` pascal
209209procedure TForm1.Button1Click(Sender: TObject);
210210begin
211211 FileListBox1.Directory:=SysToUTF8(GetCurrentDir);
212212 // или с помощью функциий из модуля FileUtil
213213 FileListBox1.Directory:=GetCurrentDirUTF8;
214214end;
215215```
216216
217217Модуль FileUtil содержит общие файловые функции в варианте UTF-8:
218218
219219``` pascal
220220// основные функции как в RTL, но в кодировке UTF-8
221221// вместо системной
222222
223223// AnsiToUTF8 и UTF8ToAnsi требуют менеджер широких строк в Linux, BSD, macOS,
224224// но эти ОС обычно используют в качестве системной кодировки UTF-8,
225225// и тогда менеджер не нужен.
226226function NeedRTLAnsi: boolean;// истина если системная кодировка не UTF-8
227227procedure SetNeedRTLAnsi(NewValue: boolean);
228228function UTF8ToSys(const s: string): string;// как UTF8ToAnsi, но более независима от менеджера широких строк
229229function SysToUTF8(const s: string): string;// как AnsiToUTF8, но более независима от менеджера широких строк
230230
231231// операции с файлами
232232function FileExistsUTF8(const Filename: string): boolean;
233233function FileAgeUTF8(const FileName: string): Longint;
234234function DirectoryExistsUTF8(const Directory: string): Boolean;
235235function ExpandFileNameUTF8(const FileName: string): string;
236236function ExpandUNCFileNameUTF8(const FileName: string): string;
237237{$IFNDEF VER2_2_0}
238238function ExtractShortPathNameUTF8(Const FileName : String) : String;
239239{$ENDIF}
240240function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;
241241function FindNextUTF8(var Rslt: TSearchRec): Longint;
242242procedure FindCloseUTF8(var F: TSearchrec);
243243function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;
244244function FileGetAttrUTF8(const FileName: String): Longint;
245245function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;
246246function DeleteFileUTF8(const FileName: String): Boolean;
247247function RenameFileUTF8(const OldName, NewName: String): Boolean;
248248function FileSearchUTF8(const Name, DirList : String): String;
249249function FileIsReadOnlyUTF8(const FileName: String): Boolean;
250250function GetCurrentDirUTF8: String;
251251function SetCurrentDirUTF8(const NewDir: String): Boolean;
252252function CreateDirUTF8(const NewDir: String): Boolean;
253253function RemoveDirUTF8(const Dir: String): Boolean;
254254function ForceDirectoriesUTF8(const Dir: string): Boolean;
255255
256256// окружение
257257function ParamStrUTF8(Param: Integer): string;
258258function GetEnvironmentStringUTF8(Index : Integer): String;
259259function GetEnvironmentVariableUTF8(const EnvVar: String): String;
260260function GetAppConfigDirUTF8(Global: Boolean): string;
261261```
262262
263263### Восточно-азиатские языки в Windows
264264
265265Шрифт элементов управления пользовательского интерфейса по умолчанию (Tahoma) в Windows XP в состоянии правильно отображать несколько письменностей/алфавитов/языков, включая арабский, русский (кириллица) и западноевропейские языки (латинский/греческий алфавиты), но не восточно-азиатские как китайский, японский и корейский.
266266
267267Для корректного отображения указанных языков стандартным шрифтом достаточно в Панели управления выбрать Региональные настройки, щёлкнуть вкладку Языки и установить поддержку восточно-азиатских языков. Естественно, что в версиях Windows XP на указанных языках этот пакет включён по умолчанию. Дополнительные инструкции [здесь](http://newton.uor.edu/Departments&Programs/AsianStudiesDept/Language/asianlanguageinstallation_XP.html).
268268
269269Более позднее версии Windows скорее всего не требуют установки дополнений для поддержки указанных языков.
270270
271271## Особенности Free Pascal
272272
273273### UTF-8 и исходные файлы -- отсутствие BOM
274274
275275Когда в созданном в Lazarus файле исходного кода используются не-ASCII символы, он сохраняется в UTF-8. При этом **не** используется **BOM** (Byte Order Mark -- маркер порядка байтов). Кодировку можно изменить при помощи правого щелчка по редактору кода / File Settings / Encoding. Помимо того, что файлы в UTF-8 не обязаны иметь BOM, причина его отсутствия скорее в том, как FPC обрабатывает строки AnsiString. LCL использует AnsiString для совместимости и UTF-8 для переносимости.
276276
277277Примечание. Некоторые текстовые редакторы в MS Windows могут полагать, что файлы используют системную кодовую страницу (кодировку OEM) и отображают в них неправильные символы. Не добавляйте BOM. Если добавить BOM, то придётся изменить все строковые присваивания.
278278
279279Пример:
280280
281281``` pascal
282282Button1.Caption := 'Über';
283283```
284284
285285Когда отсутствует BOM (и кодировка не задана параметром) компилятор считает, что строка в системной кодировке и копирует каждый байт в строку без преобразований. LCL ожидает строки именно в таком виде.
286286
287287``` pascal
288288// исходный файл сохранён в UTF без BOM
289289if FileExists('Über.txt') then ; // неправильно, FileExists ожидает системную кодировку
290290if FileExistsUTF8('Über.txt') then ; // правильно
291291```
292292
293293## Основы юникода
294294
295295Стандарт Unicode сопоставляет целые от 0 до 10FFFF(h) символам. Каждое такое сопоставление именуется кодовой точкой (code point). Другими словами, юникодные символы определены для кодовых точек от U+000000 до U+10FFFF (от 0 до 1 114 111).
296296
297297Для представления кодовых точек как уникальных байтовых последовательностей существуют три основных схемы. Эти схемы называют форматами юникодных преобразований: UTF-8, UTF-16 и UTF-32. Преобразование между всеми ними возможно. Их основные свойства:
298298
299299`                                UTF-8 UTF-16 UTF-32`\
300300`Наименьшая кодовая точка [16]  000000 000000 000000`\
301301`Наибольшая кодовая точка [16]  10FFFF 10FFFF 10FFFF`\
302302`Размер кодового элемента [бит]      8     16     32`\
303303`Минимум байт на символ              1      2      4`\
304304`Максимум байт на символ             4      4      4`
305305
306306**UTF-8** обладает несколькими важными и полезными свойствами: Содержимое интерпретируется как последовательность байтов, поэтому понятие порядка байт "от младшего" или "от старшего" отсутствует. Юникодные символы от U+0000 до U+007F (ASCII) кодируются байтами от 00h до 7Fh (совместимо с ASCII). Это значит, что файлы и строки, содержащие только 7-битные символы ASCII, кодируются одинаково в ASCII и в UTF-8. Все символы больше U+007F кодируются как последовательность нескольких байтов, у каждого из которых старшие биты установлены в 1. Последовательность байт одного символа заведомо не содержится в более длинной последовательности байт другого символа. Это позволяет без затруднений искать подстроки. Первый байт мультибайтовой последовательности (представляющей не-ASCII символ) всегда принадлежит диапазону от C0h до FDh и показывает количество последующих байт для этого символа. Все последующие байты мультибайтной последовательности принадлежат диапазону от 80h до BFh. Это позволяет легко выполнять ресинхронизацию и способствует надежности.
307307
308308**UTF-16** обладает следующими важными свойствами: Используется одно 16-битное слово для кодирования символов от U+0000 до U+d7ff и два 16-битных слова для кодирования всех остальных символов.
309309
310310В заключение, любой юникодный символ возможно представить как один 4-байтный/32-битный элемент в **UTF-32**.
311311
312312См. также: [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)
313313
314314# Детали реализации
315315
316316## Lazarus/LCL обычно использует только UTF-8
317317
318318Начиная с Lazarus 0.9.31 интерфейс GTK1 объявлен устаревшим, все интерфейсы LCL совместимы с юникодом, Lazarus и LCL используют строки только в кодировке UTF-8, если явно не указано, что функция/процедура принимает другие кодировки.
319319
320320## Перевод интерфейса Win32 на юникод
321321
322322### Обзор
323323
324324Сперва, и это самое важное, во избежание поломки существующего интерфейса ANSI следует все юникодные интерфейсные модификации заключить в IFDEF WindowsUnicodeSupport. После стабилизации модификаций все IFDEF-ы будут убраны и останется только юникодная часть. К тому моменту все существующие программы, использующие символы ANSI, потребуют миграции на юникод.
325325
326326### Win9x не поддерживает юникод
327327
328328Платформы \<= Win9x основаны на стандартах кодовых страниц ISO и поддерживают юникод только частично. Платформы Windows, начиная с WinNT, и Windows CE юникод поддерживают полностью.
329329
330330Win 9x и NT предлагают два параллельных набора функций API: *A -- старый под ANSI, *W -- новый юникодный. Функции *W принимают в качестве параметров широкие строки, то есть строки в кодировке UTF-16. В Windows 9x присутствуют все функции *W, но имеют пустые реализации. Поэтому они ничего не делают. Но некоторые из них реализованы полностью даже в 9x и перечислены ниже в разделе "Широкие функции, присутствующие в Windows 9x". Это свойство актуально, поскольку позволяет иметь одно приложение для Win9x и WinNT и во время его исполнения определять, какой набор API использовать.
331331
332332Windows CE использует только широкие функции API.
333333
334334#### Широкие функции, присутствующие в Windows 9x
335335
336336Некоторые функции широкого API присутствуют в Windows 9x. Вот список таких функций: <http://support.microsoft.com/kb/210341>
337337
338338Пример преобразования:
339339
340340``` pascal
341341GetTextExtentPoint32(hdcNewBitmap, LPSTR(ButtonCaption),
342342Length(ButtonCaption), TextSize);
343343```
344344
345345Станет:
346346
347347``` pascal
348348{$ifdef WindowsUnicodeSupport}
349349 GetTextExtentPoint32W(hdcNewBitmap, PWideChar(Utf8Decode(ButtonCaption)), Length(WideCaption), TextSize);
350350{$else}
351351 GetTextExtentPoint32(hdcNewBitmap, LPSTR(ButtonCaption), Length(ButtonCaption), TextSize);
352352{$endif}
353353```
354354
355355#### Функции, требующие версий Ansi и Wide
356356
357357Пример преобразования:
358358
359359``` pascal
360360function TGDIWindow.GetTitle: String;
361361var
362362 l: Integer;
363363begin
364364 l := Windows.GetWindowTextLength(Handle);
365365 SetLength(Result, l);
366366 Windows.GetWindowText(Handle, @Result[1], l);
367367end;
368368```
369369
370370Станет:
371371
372372``` pascal
373373function TGDIWindow.GetTitle: String;
374374var
375375 l: Integer;
376376 AnsiBuffer: string;
377377 WideBuffer: WideString;
378378begin
379379
380380{$ifdef WindowsUnicodeSupport}
381381
382382if UnicodeEnabledOS then
383383begin
384384 l := Windows.GetWindowTextLengthW(Handle);
385385 SetLength(WideBuffer, l);
386386 l := Windows.GetWindowTextW(Handle, @WideBuffer[1], l);
387387 SetLength(WideBuffer, l);
388388 Result := Utf8Encode(WideBuffer);
389389end
390390else
391391begin
392392 l := Windows.GetWindowTextLength(Handle);
393393 SetLength(AnsiBuffer, l);
394394 l := Windows.GetWindowText(Handle, @AnsiBuffer[1], l);
395395 SetLength(AnsiBuffer, l);
396396 Result := AnsiToUtf8(AnsiBuffer);
397397end;
398398
399399{$else}
400400
401401 l := Windows.GetWindowTextLength(Handle);
402402 SetLength(Result, l);
403403 Windows.GetWindowText(Handle, @Result[1], l);
404404
405405{$endif}
406406
407407end;
408408```
409409
410410### Снимки экрана
411411
412-<figure>
413-<img src="Lazarus_Unicode_Test.png" title="Lazarus_Unicode_Test.png" />
414-<figcaption>Lazarus_Unicode_Test.png</figcaption>
415-</figure>
412+![](/wiki-images/4/49/Lazarus_Unicode_Test.png)
416413
417414# Кодовые страницы FPC
418415
419416Почему LCL не использует кодовые страницы для исходных текстов?
420417
421418-Fcutf8 и {\$codepage utf8} существуют не первый год.
422419
423420Следующие программы требуют FPC 2.7.1. Описанные трудности существуют и на более старых компиляторах.
424421
425422Есть в использовании -Fcutf8 и {\$codepage utf8} несколько ловушек. Директивы работают, если DefaultSystemCodePage из RTL возвращает CP_UTF8. Иначе компилятор перекодирует строки. Например, в Linux умолчание для RTL -- CP_ACP, что по умолчанию ISO_8859-1. RTL самостоятельно **не** читает язык из переменных окружения. Поэтому умолчанием будет ISO_8859-1. Это означает, что строковые константы в UTF-8 будут преобразованы компилятором.
426423
427424Откомпилируйте с -Fcutf8 и запустите в Linux с LANG в utf-8:
428425
429426``` pascal
430427program project1;
431428{$mode objfpc}{$H+}
432429begin
433430 writeln(DefaultSystemCodePage,' ',CP_UTF8);
434431 writeln('ä');
435432end.
436433```
437434
438435Результат:
439436
440437 0 65001
441438 ä
442439
443440LCL использует widestringmanager -- менеджер широких строк (в настоящее время cwstring), который назначает DefaultSystemCodePage. Тоже самое можно сделать и в не-LCL программах:
444441
445442``` pascal
446443program project1;
447444{$mode objfpc}{$H+}
448445uses cwstring;
449446begin
450447 writeln(DefaultSystemCodePage,' ',CP_UTF8);
451448 writeln('ä');
452449end.
453450```
454451
455452Результат:
456453
457454 65001 65001
458455 ä
459456
460457Приведённый выше код прост и понятен, жаль только, что работает подобное лишь в простейших случаях. Посмотрим на программу ниже:
461458
462459``` pascal
463460program project1;
464461{$mode objfpc}{$H+}
465462uses cwstring;
466463var
467464 a,b,c: string;
468465begin
469466 writeln(DefaultSystemCodePage,' ',CP_UTF8);
470467 a:='ä'; b:='='#$C3#$A4; // #$C3#$A4 это UTF-8 для ä
471468 c:= 'ä='#$C3#$A4;
472469 writeln(a,b); // writes ä=ä
473470 writeln(c); // writes ä=ä
474471end.
475472```
476473
477474 65001 65001
478475 ä=ä
479476 ä=ä
480477
481478Посмотрите, работают строковые константы в UTF-8, строковые константы с кодами UTF-8, но не их комбинация. Приведённый выше код скомпилирован с -Fcutf8 и использует cwstring для установки DefaultSystemCodePage в CP_UTF8.
482479
483480Так что же пошло не так?
484481
485482Компилятор обрабатывает строковые не-ASCII константы (здесь -- ä) как широкие строки (UCS2, не UTF-16).
486483
487484Одурачить компилятор при помощи 'ä='+#\$C3#\$A4 не выйдет. Придётся определить две отдельные строковые константы.
488485
489486Использование символов не из диапазона UCS-2 приведёт к
490487
491488 Fatal: illegal character "'�'" ($F0)
492489
493490Но можно указать их в кодах UTF-16: *\#\$D834#\$DD1E*. Да, прочитали правильно. Указание кодовой страницы при помощи -Fcutf8 или {\$codepage utf8} действительно определяет смесь UTF-8 и UTF-16.
494491
495492Теперь откомпилируйте приведённый выше код без -Fcutf8:
496493
497494 65001 65001
498495 ä=ä
499496 ä=ä
500497
501498Вот это да, всё как хотелось. Можно даже смешивать строковые константы в ASCII и не-ASCII. Без указания кодовой страницы компилятор просто сохраняет строковые константы как последовательности байтов. А это и есть UTF-8.
502499
503500В этом одна из причин того, почему LCL приложения не используют флаги кодировок.
504501
505502В msegui реализована экосистема широких строк, которая работает лучше, чем кодовые страницы.
506503
507504# RTL с кодовой страницей UTF-8 по умолчанию
508505
509506**Эксперимент -- требуется тестирование под Windows!**
510507
511508Обычно RTL использует для строк системную кодовую страницу (например, FileExists или TStringList.LoadFromFile). Под Windows RTL не использует юникод, вам недоступны символы вне вашей языковой группы. LCL работает с кодировкой UTF-8, поддерживающей все символы юникода. Под Linux и macOS системной кодовой страницей обычно является UTF-8, поэтому для них RTL по умолчанию использует CP_UTF8.
512509
513510Начиная с FPC 2.7.1 кодовая страница, используемая RTL по умолчанию, может быть изменена на UTF-8 (CP_UTF8). Таким образом, пользователи Windows теперь смогут использовать в RTL строки UTF-8. Для тестирования добавьте -dEnableUTF8RTL в *Lazarus build options* (параметры сборки проекта) и пересоберите проект.
514511
515512- Например, FileExists и aStringList.LoadFromFile(Filename) теперь полностью поддерживают юникод. Полный перечень функций, уже поддерживающих юникод полностью, смотрите по ссылке:
516513
517514<http://wiki.freepascal.org/FPC_Unicode_support#RTL_changes>
518515
519516- *AnsiToUTF8, UTF8ToAnsi, SysToUTF8, UTF8ToAnsi* не действуют. Они использовались преимущественно для вышеуказанных функций, которые больше не требуют преобразований аргументов и результата.
520517- Многочисленные явные вызовы *UTF8Encode* и *UTF8Decode* больше не нужны, поскольку при присваивании строки в *UnicodeString* строке String и наоборот компилятор сделает это автоматически.
521518- При обращении к WinAPI необходимо использовать функции с "W" либо пользоваться функциями **UTF8ToWinCP** и **WinCPToUTF8**.
522519- Включить новый режим можно перекомпилировав Lazarus с **-dEnableUTF8RTL**.
523520- Если используете строковые литералы с **WideString**, **UnicodeString** или **UTF8String**, то теперь исходные тексты **должны** быть в соответствующей кодировке. Например, можно использовать исходные тексты в UTF-8 (поведение Lazarus по умолчанию) и передавать компилятору параметр **-FcUTF8**.
524521- "String" и "UTF8String" -- разные типы. При присваивании *String* переменной в *UTF8String* компилятор добавляет код проверки соответствия кодировок. Это повлечёт увеличение времени исполнения и размера машинного кода.
525522
526523Дополнительную информацию о новой поддержке юникода в FPC см. по ссылке: <http://wiki.freepascal.org/FPC_Unicode_support>
527524
528525# См. также
529526
530527- [[UTF-8]] - Описание строк в UTF-8
531528
532529---
533530*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).*
534531Category: FPC/ru
535532Category: LCL/ru
536533Category: Russian
537534Category: Unicode/ru

Back to history