Sign in Join
Wiki copytemporary

Old LCL Unicode Support: what changed

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

OldNewLine
11**For historical reference only.**\
22For current information about Unicode support in Lazarus see [[Unicode Support in Lazarus]].
33
44## Introduction
55
66As of 0.9.25, Lazarus has full Unicode support in all platforms except Gtk 1. This page covers Unicode in Lazarus up to version 1.4 which still uses FPC 2.6.4.
77
88Starting from 2.0 Lazarus will have improved Unicode support using features of FPC 3.0+. See details here: [[Unicode Support in Lazarus]]
99
1010## Instructions for users
1111
1212Even though Lazarus has Unicode widgetsets, it's important to note that not everything is Unicode. It's the responsibility of the developer to know what is the encoding of their strings and do the proper conversion between libraries which expect different encodings.
1313
1414Usually the encoding is per library (e.g. a dynamic library DLL or a Lazarus package). Each library will uniformly expect 1 kind of encoding, which will usually either be Unicode (UTF-8 for Lazarus) or ANSI (which actually means the system encoding, and may be UTF-8 or not). The RTL and the FCL of FPC \<= 2.6 expect ANSI strings.
1515
1616You can convert between Unicode and ANSI using
1717
1818- the **UTF8ToAnsi** and **AnsiToUTF8** functions from the (FPC) System unit
1919- or the **UTF8ToSys** and **SysToUTF8** from the (Lazarus) FileUtil unit.
2020
2121The latter two are smarter (faster) but pull more code into your program.
2222
2323### FPC is not Unicode aware
2424
2525The Free Pascal Runtime Library (RTL), and the Free Pascal Free Component Library (FCL) in current FPC versions (\<= 2.6.x) are ANSI, so you will need to convert strings coming from Unicode libraries or going to Unicode libraries (e.g. the LCL).
2626
2727There are significant improvements to development branches of FPC 2.7.1 with regard to Strings. See RawByteString and UTF8String in [[FPC Unicode support]].
2828
2929### Converting between ANSI and Unicode
3030
3131> **Note:** AnsiToUTF8 and UTF8ToAnsi require a widestring manager under Linux, BSD and Mac OS X. You can use the SysToUTF8 and UTF8ToSys functions (unit FileUtil) or add the widestring manager by adding cwstring as one of the first units to your program's uses section.
3232
3333Examples:
3434
3535Say you get a string from a TEdit and you want to give it to some RTL file routine:
3636
3737``` pascal
3838var
3939 MyString: string; // utf-8 encoded
4040begin
4141 MyString := MyTEdit.Text;
4242 SomeRTLRoutine(UTF8ToAnsi(MyString));
4343end;
4444```
4545
4646And for the opposite direction:
4747
4848``` pascal
4949var
5050 MyString: string; // ANSI encoded
5151begin
5252 MyString := SomeRTLRoutine;
5353 MyTEdit.Text := AnsiToUTF8(MyString);
5454end;
5555```
5656
5757### Widestrings and Ansistrings
5858
5959A widestring is a string type whose basic data holding elements have a size of 2 bytes. Widestrings almost always hold data in the UTF-16 encoding. See [[Widestrings]]
6060
6161Note that while each data point accessible as an array of a widestring has 2 bytes, in UTF-16 a character may have 1 or 2 data points, which would then occupy 2 or 4 bytes. This means that accessing a Widestring as an array and expecting to obtain UTF-16 characters this way is **completely wrong** and will fail when a 4 byte character is present in the string. Note also that UTF-16, like UTF-8, may have decomposed characters. The character "Á" for example might be encoded as a single character or as 2 characters: "A" + a modifying accent. Thus in Unicode a text which involves accented letters can often be encoded in multiple ways and Lazarus and FPC do not handle this automatically.
6262
6363When passing Ansistrings to Widestrings you have to convert the encoding.
6464
6565``` pascal
6666var
6767 w: widestring;
6868begin
6969 w:='Über'; // wrong, because FPC will convert system codepage to UTF16
7070 w:=UTF8ToUTF16('Über'); // correct
7171 Button1.Caption:=UTF16ToUTF8(w);
7272end;
7373```
7474
7575### UTF8 strings and characters
7676
7777Until Lazarus 0.9.30 the UTF-8 handling routines were in the LCL in the unit LCLProc. In Lazarus 0.9.31+ the routines in LCLProc are still available for backwards compatibility but the real code to deal with UTF-8 is located in the lazutils package in the unit lazutf8.
7878
7979To execute operations on UTF-8 strings please use routines from the unit lazutf8 instead of routines from the SysUtils routine from Free Pascal, because SysUtils is not yet prepared to deal with Unicode, while lazutf8 is. Simply substitute the routines from SysUtils with their lazutf8 equivalent, which always has the same name except for an added "UTF8" prefix.
8080
8181#### UTF-8 String Copy, Length, LowerCase, etc
8282
8383Nearly all operations which one might want to execute with UTF-8 strings are covered by the routines in the unit lazutf8 (unit LCLProc for Lazarus 0.9.30 or lower). See the following list of routines taken from lazutf8.pas:
8484
8585``` pascal
8686function UTF8CharacterLength(p: PChar): integer;
8787function UTF8Length(const s: string): PtrInt;
8888function UTF8Length(p: PChar; ByteCount: PtrInt): PtrInt;
8989function UTF8CharacterToUnicode(p: PChar; out CharLen: integer): Cardinal;
9090function UnicodeToUTF8(u: cardinal; Buf: PChar): integer; inline;
9191function UnicodeToUTF8SkipErrors(u: cardinal; Buf: PChar): integer;
9292function UnicodeToUTF8(u: cardinal): shortstring; inline;
9393function UTF8ToDoubleByteString(const s: string): string;
9494function UTF8ToDoubleByte(UTF8Str: PChar; Len: PtrInt; DBStr: PByte): PtrInt;
9595function UTF8FindNearestCharStart(UTF8Str: PChar; Len: integer;
9696 BytePos: integer): integer;
9797// find the n-th UTF8 character, ignoring BIDI
9898function UTF8CharStart(UTF8Str: PChar; Len, CharIndex: PtrInt): PChar;
9999// find the byte index of the n-th UTF8 character, ignoring BIDI (byte len of substr)
100100function UTF8CharToByteIndex(UTF8Str: PChar; Len, CharIndex: PtrInt): PtrInt;
101101procedure UTF8FixBroken(P: PChar);
102102function UTF8CharacterStrictLength(P: PChar): integer;
103103function UTF8CStringToUTF8String(SourceStart: PChar; SourceLen: PtrInt) : string;
104104function UTF8Pos(const SearchForText, SearchInText: string): PtrInt;
105105function UTF8Copy(const s: string; StartCharIndex, CharCount: PtrInt): string;
106106procedure UTF8Delete(var s: String; StartCharIndex, CharCount: PtrInt);
107107procedure UTF8Insert(const source: String; var s: string; StartCharIndex: PtrInt);
108108
109109function UTF8LowerCase(const AInStr: string; ALanguage: string=''): string;
110110function UTF8UpperCase(const AInStr: string; ALanguage: string=''): string;
111111function FindInvalidUTF8Character(p: PChar; Count: PtrInt;
112112 StopOnNonASCII: Boolean = false): PtrInt;
113113function ValidUTF8String(const s: String): String;
114114
115115procedure AssignUTF8ListToAnsi(UTF8List, AnsiList: TStrings);
116116
117117//compare functions
118118
119119function UTF8CompareStr(const S1, S2: string): Integer;
120120function UTF8CompareText(const S1, S2: string): Integer;
121121```
122122
123123### Dealing with directory and filenames
124124
125125Lazarus controls and functions expect filenames and directory names in UTF-8 encoding, but the RTL uses ANSI strings for directories and filenames.
126126
127127For example, consider a button which sets the Directory property of the TFileListBox to the current directory. The RTL Function [[doc:rtl/sysutils/getcurrentdir.html|GetCurrentDir]] is ANSI, not Unicode, so conversion is needed:
128128
129129``` pascal
130130procedure TForm1.Button1Click(Sender: TObject);
131131begin
132132 FileListBox1.Directory:=SysToUTF8(GetCurrentDir);
133133 // or use the functions from the FileUtil unit
134134 FileListBox1.Directory:=GetCurrentDirUTF8;
135135end;
136136```
137137
138138The unit FileUtil defines common file functions with UTF-8 strings:
139139
140140``` pascal
141141// basic functions similar to the RTL but working with UTF-8 instead of the
142142// system encoding
143143
144144// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, Mac OS X
145145// but normally these OS use UTF-8 as system encoding so the widestringmanager
146146// is not needed.
147147function NeedRTLAnsi: boolean;// true if system encoding is not UTF-8
148148procedure SetNeedRTLAnsi(NewValue: boolean);
149149function UTF8ToSys(const s: string): string;// as UTF8ToAnsi but more independent of widestringmanager
150150function SysToUTF8(const s: string): string;// as AnsiToUTF8 but more independent of widestringmanager
151151
152152// file operations
153153function FileExistsUTF8(const Filename: string): boolean;
154154function FileAgeUTF8(const FileName: string): Longint;
155155function DirectoryExistsUTF8(const Directory: string): Boolean;
156156function ExpandFileNameUTF8(const FileName: string): string;
157157function ExpandUNCFileNameUTF8(const FileName: string): string;
158158{$IFNDEF VER2_2_0}
159159function ExtractShortPathNameUTF8(Const FileName : String) : String;
160160{$ENDIF}
161161function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;
162162function FindNextUTF8(var Rslt: TSearchRec): Longint;
163163procedure FindCloseUTF8(var F: TSearchrec);
164164function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;
165165function FileGetAttrUTF8(const FileName: String): Longint;
166166function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;
167167function DeleteFileUTF8(const FileName: String): Boolean;
168168function RenameFileUTF8(const OldName, NewName: String): Boolean;
169169function FileSearchUTF8(const Name, DirList : String): String;
170170function FileIsReadOnlyUTF8(const FileName: String): Boolean;
171171function GetCurrentDirUTF8: String;
172172function SetCurrentDirUTF8(const NewDir: String): Boolean;
173173function CreateDirUTF8(const NewDir: String): Boolean;
174174function RemoveDirUTF8(const Dir: String): Boolean;
175175function ForceDirectoriesUTF8(const Dir: string): Boolean;
176176
177177// environment
178178function ParamStrUTF8(Param: Integer): string;
179179function GetEnvironmentStringUTF8(Index : Integer): String;
180180function GetEnvironmentVariableUTF8(const EnvVar: String): String;
181181function GetAppConfigDirUTF8(Global: Boolean): string;
182182```
183183
184184### East Asian languages on Windows
185185
186186The default font (Tahoma) for user interface controls under Windows XP is capable of correctly displaying several scripts/alphabets/languages, including Arabic, Russian (Cyrillic alphabet) and Western languages (Latin/Greek alphabets), but not East Asian languages, like Chinese, Japanese and Korean.
187187
188188Simply by going to the Control Panel, choosing Regional Settings, clicking on the Languages Tab and installing the East Asia Language Pack, the standard user interface font will start showing those languages correctly. Obviously Windows XP versions localized for those languages will already have this language pack installed. Extended instructions [here](http://newton.uor.edu/Departments&Programs/AsianStudiesDept/Language/asianlanguageinstallation_XP.html).
189189
190190Later Windows versions presumably have support for these languages out of the box.
191191
192192### Usage of UTF-8
193193
194194See details in [[UTF8 strings and characters]]
195195
196196## Free Pascal Particularities
197197
198198### UTF8 and source files - the missing BOM
199199
200200When you create source files with Lazarus and type some non-ASCII characters the file is saved in UTF8. It does **not** use a **BOM** (Byte Order Mark). You can change the encoding via right click on source editor / File Settings / Encoding. Apart from the fact that UTF-8 files are not supposed to have BOMs, the reason for the lacking BOM is how FPC treats Ansistrings. For compatibility the LCL uses Ansistrings and for portability the LCL uses UTF8.
201201
202202Note: Some MS Windows text editors might treat the files as encoded with the system codepage (OEM codepage) and show them as invalid characters. **Do not add the BOM.** If you add the BOM you have to change all string assignments.
203203
204204For example:
205205
206206``` pascal
207207Button1.Caption := 'Über';
208208```
209209
210210When no BOM is given (and no codepage parameter was passed) the compiler treats the string as system encoding and copies each byte unconverted to the string. This is how the LCL expects strings.
211211
212212``` pascal
213213// source file saved as UTF without BOM
214214if FileExists('Über.txt') then ; // wrong, because FileExists expects system encoding
215215if FileExistsUTF8('Über.txt') then ; // correct
216216```
217217
218218## Unicode essentials
219219
220220The Unicode standard maps integers from 0 to 10FFFF(h) to characters. Each such mapping is called a code point. In other words, Unicode characters are in principle defined for code points from U+000000 to U+10FFFF (0 to 1 114 111).
221221
222222There are three major schemes for representing Unicode code points as unique byte sequences. These schemes are called Unicode transformation formats: UTF-8, UTF-16 and UTF-32. Conversions between all of them are possible. Here are their basic properties:
223223
224-`                           UTF-8 UTF-16 UTF-32`\
225-`Smallest code point [hex] 000000 000000 000000`\
226-`Largest code point  [hex] 10FFFF 10FFFF 10FFFF`\
227-`Code unit size [bits]          8     16     32`\
228-`Minimal bytes/character        1      2      4`\
229-`Maximal bytes/character        4      4      4`
224+``` pascal
225+ UTF-8 UTF-16 UTF-32
226+Smallest code point [hex] 000000 000000 000000
227+Largest code point [hex] 10FFFF 10FFFF 10FFFF
228+Code unit size [bits] 8 16 32
229+Minimal bytes/character 1 2 4
230+Maximal bytes/character 4 4 4
231+```
230232
231233**UTF-8** has several important and useful properties: It is interpreted as a sequence of bytes, so that the concept of lo- and hi-order byte does not exist. Unicode characters U+0000 to U+007F (ASCII) are encoded simply as bytes 00h to 7Fh (ASCII compatibility). This means that files and strings which contain only 7-bit ASCII characters have the same encoding under both ASCII and UTF-8. All characters \>U+007F are encoded as a sequence of several bytes, each of which has the two most significant bits set. No byte sequence of one character is contained within a longer byte sequence of another character. This allows easy searching for substrings. The first byte of a multibyte sequence (representing a non-ASCII character) is always in the range C0h to FDh and it indicates how many bytes follow for this character. All further bytes in a multibyte sequence are in the range 80h to BFh. This allows easy resynchronization and robustness.
232234
233235**UTF-16** has the following most important properties: It uses a single 16-bit word to encode characters from U+0000 to U+d7ff, and a pair of 16-bit words to encode any of the remaining Unicode characters.
234236
235237Finally, any Unicode character can be represented as a single 4 byte/32-bit unit in **UTF-32**.
236238
237239For more, see: [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)
238240
239241# Implementation Details
240242
241243## Lazarus/LCL generally uses only UTF-8
242244
243245Since the GTK1 interface was declared obsolete in Lazarus 0.9.31, all LCL interfaces are Unicode capable and the LCL uses and accepts only UTF-8 encoded strings, unless in routines explicitly marked as accepting other encodings.
244246
245247## Unicode-enabling the win32 interface
246248
247249### Overview
248250
249251First, and most importantly, all Unicode patches for the Win32 interface must be enclosed by IFDEF WindowsUnicodeSupport, to avoid breaking the existing ANSI interface. After this stabilizes, all ifdefs will be removed and only the Unicode part will remain. At his moment all existing programs that use ANSI characters will need migration to Unicode.
250252
251253### No Unicode support on Win9x
252254
253255Windows platforms \<=Win9x are based on ISO code page standards and only partially support Unicode. Windows platforms starting with Windows NT (e.g. Windows 2000, XP, Vista, 7, 8) and Windows CE fully support Unicode.
254256
255257Win 9x and NT offer two parallel sets of API functions: the old ANSI enabled *A and the new, Unicode enabled *W. *W functions accept wide strings - UTF-16 encoded strings - as parameters.
256258
257259Windows 9x has all *W functions but they mostly have empty implementations, so they do nothing. Only some some *W functions are fully implemented in 9x; these are listed below in the section "Wide functions present on Windows 9x". This property is relevant as it allows to have one single application for both Win9x and WinNT and detect at runtime which set of APIs to use.
258260
259261Windows CE only uses Wide API functions.
260262
261263#### Wide functions present on Windows 9x
262264
263265Some Wide API functions are present on Windows 9x. Here is a list of such functions: <http://support.microsoft.com/kb/210341>
264266
265267Conversion example:
266268
267269``` pascal
268270GetTextExtentPoint32(hdcNewBitmap, LPSTR(ButtonCaption),
269271Length(ButtonCaption), TextSize);
270272```
271273
272274Becomes:
273275
274276``` pascal
275277{$ifdef WindowsUnicodeSupport}
276278 GetTextExtentPoint32W(hdcNewBitmap, PWideChar(Utf8Decode(ButtonCaption)), Length(WideCaption), TextSize);
277279{$else}
278280 GetTextExtentPoint32(hdcNewBitmap, LPSTR(ButtonCaption), Length(ButtonCaption), TextSize);
279281{$endif}
280282```
281283
282284#### Functions that need Ansi and Wide versions
283285
284286First Conversion example:
285287
286288``` pascal
287289function TGDIWindow.GetTitle: String;
288290var
289291 l: Integer;
290292begin
291293 l := Windows.GetWindowTextLength(Handle);
292294 SetLength(Result, l);
293295 Windows.GetWindowText(Handle, @Result[1], l);
294296end;
295297```
296298
297299Becomes:
298300
299301``` pascal
300302function TGDIWindow.GetTitle: String;
301303var
302304 l: Integer;
303305 AnsiBuffer: string;
304306 WideBuffer: WideString;
305307begin
306308
307309{$ifdef WindowsUnicodeSupport}
308310
309311if UnicodeEnabledOS then
310312begin
311313 l := Windows.GetWindowTextLengthW(Handle);
312314 SetLength(WideBuffer, l);
313315 l := Windows.GetWindowTextW(Handle, @WideBuffer[1], l);
314316 SetLength(WideBuffer, l);
315317 Result := Utf8Encode(WideBuffer);
316318end
317319else
318320begin
319321 l := Windows.GetWindowTextLength(Handle);
320322 SetLength(AnsiBuffer, l);
321323 l := Windows.GetWindowText(Handle, @AnsiBuffer[1], l);
322324 SetLength(AnsiBuffer, l);
323325 Result := AnsiToUtf8(AnsiBuffer);
324326end;
325327
326328{$else}
327329
328330 l := Windows.GetWindowTextLength(Handle);
329331 SetLength(Result, l);
330332 Windows.GetWindowText(Handle, @Result[1], l);
331333
332334{$endif}
333335
334336end;
335337```
336338
337339### Screenshots
338340
339341![](/wiki-images/4/49/Lazarus_Unicode_Test.png)
340342
341343# FPC codepages
342344
343345The compiler (FPC) supports specifying the code page in which the source code has been written via the command option *-Fc* (e.g. *-Fcutf8*) and the equivalent *codepage* directive (e.g. *{\$codepage utf8}*). In this case, rather than literally copying the bytes that represent the string constants in your program, the compiler will interpret all character data according to that codepage. There are two things to watch out for though:
344346
345347- on Unix platforms, make sure you include a widestring manager by adding the *cwstring* unit to your uses-clause. When using unit LazUtf8, this is already done. Without it, the program will not be able to convert all character data correctly when running. It's not included by default because this unit makes your program dependent on libc, which makes cross-compilation harder.
346348- When using *-Fc* or *\$codepage* the compiler converts all string constants that contain non-ASCII characters to widestring constants. These are automatically converted back to ansistring (either at compile time or at run time), but this can cause one caveat if you try to mix both characters and ordinal values in a single string constant:
347349
348350For example:
349351
350352``` pascal
351353program project1;
352354{$codepage utf8}
353355{$mode objfpc}{$H+}
354356{$ifdef unix}
355357uses cwstring;
356358{$endif}
357359var
358360 a,b,c: string;
359361begin
360362 a:='ä';
361363 b:='='#$C3#$A4; // #$C3#$A4 is UTF-8 for ä
362364 c:='ä='#$C3#$A4;
363365 writeln(a,b); // writes ä=ä
364366 writeln(c); // writes ä=ä
365367end.
366368```
367369
368370When compiled and executed, this will write:
369371
370372 ä=ä
371373 ä=ä
372374
373375The reason is once the *ä* is encountered, as mentioned above the rest of the constant string assigned to 'c' will be parsed as a widestring. As a result the \#\$C3 and \#\$A4 are interpreted as widechar(#\$C3) and widechar(#\$A4), rather than as ansichars.
374376
375377# See Also
376378
377379- [[Character and string types]]
378380- [[UTF-8]] - Description of UTF-8 strings
379381- [[FPC Unicode support]]
380382- [[for-in loop|for-in_loop#Traversing_UTF-8_strings]] - Code snippet that shows how to traverse UTF8 strings
381383- [[unicode use cases]]
382384
383385---
384386*From the Free Pascal wiki, page "Old LCL Unicode Support", as it stood on 2020-02-21 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).*
385387Category: FPC
386388Category: LCL
387389Category: Unicode

Back to history