LCL Unicode Support/fr

From Lazarus wiki
Revision as of 20:31, 24 January 2012 by Mendahor (talk | contribs) (Free Pascal Particularities)

Deutsch (de) English (en) español (es) français (fr) 日本語 (ja) 한국어 (ko) русский (ru) 中文(中国大陆)‎ (zh_CN) 中文(台灣)‎ (zh_TW)

Introduction

A partir de la version 0.9.25, Lazarus supporte pleinement Unicode pour toutes les plateformes, excepté Gtk 1. Dans cette page, vous pouvez trouver des instructions pour les utilisateurs de Lazarus, des feuilles de route, des descriptions de concepts basiques et des détails d'implémentation.

Instructions pour les utilisateurs

Même si Lazarus possède des ensembles de widgets Unicode, il est important de noter que tout n'est pas en Unicode. Il est de la responsabilité du développeur de connaitre l'encodage de ses chaines de caractère, et d'effectuer la conversion appropriée entre les bibliothèques qui attendent des encodages différents.

Habituellement, l'encodage est défini bibliothèque par bibliothèque (une bibliothèque dynamique (dll) ou un package Lazarus). Chaque bibliothèque attendra uniformément un type d'encodage, qui sera habituellement soit Unicode (UTF-8 pour Lazarus), soit ANSI (qui signifie l'encodage du système, et peut être UTF-8 ou non).

La RTL et la FCL de FPC 2.4 attendent des chaines ansi. FPC 2.5.x aussi actuellement.

Vous pouvez convertir entre unicode et ansi en utilisant les fonctions UTF8ToAnsi et AnsiToUTF8 de l'unité System, ou les fonctions UTF8ToSys et SysToUTF8 de l'unité FileUtil. Les deux dernières sont plus intelligentes mais engendrent plus de code dans votre programme.

FPC ne travaille pas en Unicode

Le Runtime Free Pascal (RTL), et la bibliothèque de composants Free Pascal (FCL), dans les versions actuelles de FPC (jusqu'à la 2.5.x) sont ANSI, vous devrez donc convertir les chaines venant des bibliothèques Unicode, ou allant vers des bibliothèques Unicode (comme la LCL).

Convertir entre ANSI et Unicode

Exemples:

Disons que vous récupérez une chaine d'un TEdit et que vous voulez la passer à une fonction de fichier de la RTL :

<delphi>var

 MyString: string; // utf-8 encoded

begin

 MyString := MyTEdit.Text;
 SomeRTLRoutine(UTF8ToAnsi(MyString));

end;</delphi>

Et pour le sens inverse :

<delphi>var

 MyString: string; // ansi encoded

begin

 MyString := SomeRTLRoutine;
 MyTEdit.Text := AnsiToUTF8(MyString);

end;</delphi>

Important: UTF8ToAnsi retournera une chaine vide si la chaine UTF8 contient des caractères invalides.

Important: AnsiToUTF8 et UTF8ToAnsi nécessitent un gestionnaire de widestring sous Linux, BSD et Mac OS X. Vous pouvez utiliser les fonctions SysToUTF8 et UTF8ToSys (unité FileUtil) ou ajouter le gestionnaire de widestring en ajoutant cwstring comme l'une des premières unités dans la section use de votre programme.

Widestrings et Ansistrings

Quand vous passez de Ansistrings à Widestrings, vous devez convertir l'encodage.

<Delphi>var

 w: widestring;

begin

 w:='Über'; // wrong, because FPC will convert system codepage to UTF16
 w:=UTF8ToUTF16('Über'); // correct
 Button1.Caption:=UTF16ToUTF8(w);

end;</Delphi>

Travailler avec les caractères et les chaines UTF8

Jusqu'à Lazarus 0.9.30, les routines de gestion de l'UTF-8 étaient dans la LCL dans l'unité LCLProc. Dans Lazarus 0.9.31 (et supérieur), les routines de LCLProc sont toujours disponibles pour la compatibilité ascendante, mais le vrai code qui gère UTF-8 est localisé dans le package lazutils, dans l'unité lazutf8.

Pour exécuter des opérations sur des chaines UTF-8, il est préférable d'utiliser les routines de l'unité lazutf8 plutôt que celles de SysUtils de Free Pascal, car SysUtils n'est pas encore prêt à travailler avec Unicode, alors que lazutf8 l'est. Substituez simplement les routines de SysUtils avec leur équivalent lazutf8, qui ont toujours le même nom exception faite du préfixe "UTF8" ajouté.

Notez également que simplement itérer sur les caractères comme si la chaine était un tableau ne fonctionne pas en Unicode. Ceci n'est pas quelque chose de spécifique à UTF-8 et vous ne pouvez simplement pas supposer qu'un caractère aura une taille fixe en Unicode. Si vous voulez itéré sur les caractères d'une chaine UFT-8, il y a basiquement deux moyens :

  • itérer sur les octets - utile pour chercher une sous chaine ou quand vous ne vous intéressez qu'aux caractères ASCII de la chaine UTF8. Par exemple pour analyser (parser) des fichier xml.
  • itérer sur les caractères - utile pour les composants graphiques tel synedit. Par exemple lorsque vous souhaitez connaître le troisième caractère affiché à l'écran.

Rechercher une sous chaine

Du fait de la nature particulière d'UTF8, vous pouvez simplement utiliser les fonctions de manipulations de chaine classiques:

<Delphi>uses lazutf8; // LCLProc for Lazarus 0.9.30 or inferior ... procedure Where(SearchFor, aText: string); var

 BytePos: LongInt;
 CharacterPos: LongInt;

begin

 BytePos:=Pos(SearchFor,aText);
 CharacterPos:=UTF8Length(PChar(aText),BytePos-1);
 writeln('The substring "',SearchFor,'" is in the text "',aText,'"',
   ' at byte position ',BytePos,' and at character position ',CharacterPos);

end;</Delphi>

Accéder aux caractères UTF8

Les caractères Unicode peuvent varier en longueur, donc la meilleure solution pour y accéder lorsque vous avez l'intention de le faire dans l'ordre dans lequel ils sont consiste à utiliser une itération. Pour itérer les caractères, utilisez ce code :

<Delphi>uses lazutf8; // LCLProc for Lazarus 0.9.30 or inferior ... procedure DoSomethingWithString(AnUTF8String: string); var

 p: PChar;
 CharLen: integer;
 FirstByte, SecondByte, ThirdByte: Char;

begin

 p:=PChar(AnUTF8String);
 repeat
   CharLen := UTF8CharacterLength(p);
   // here you have a pointer to the char and it's length
   // You can access the bytes of the UTF-8 Char like this:
   if CharLen >= 1 then FirstByte := P[0];
   if CharLen >= 2 then SecondByte := P[1];
   if CharLen >= 3 then ThirdByte := P[2];
   inc(p,CharLen);
 until (CharLen=0) or (p^ = #0);

end;</Delphi>

Accéder au Nième caractère UTF8

Vous pouvez également vouloir effectuer un accès direct aux caractères UTF-8.

<Delphi>uses lazutf8; // LCLProc for Lazarus 0.9.30 or inferior ... var

 AnUTF8String, NthChar: string;

begin

 NthChar := UTF8Copy(AnUTF8String, N, 1);

</Delphi>

Itérer les points de code en utilisant UTF8CharacterToUnicode

Ce qui suit montre comment itérer les valeurs des points de code 32bits de chacun des caractères dans une chaine UTF-8:

<Delphi>uses lazutf8; // LCLProc for Lazarus 0.9.30 or inferior ... procedure IterateUTF8Characters(const AnUTF8String: string); var

 p: PChar;
 unicode: Cardinal;
 CharLen: integer;

begin

 p:=PChar(AnUTF8String);
 repeat
   unicode:=UTF8CharacterToUnicode(p,CharLen);
   writeln('Unicode=',unicode);
   inc(p,CharLen);
 until (CharLen=0) or (unicode=0);

end;</Delphi>

Copie, longueur, mise en minuscule, etc des chaines UTF-8

Quasiment toutes les opérations que vous pouvez vouloir exécuter avec les chaines UTF-8 sont couvertes par les routines de l'unité lazutf8 (unité LCLProc pour Lazarus 0.9.30 ou inférieur). Regardez la liste suivante de routines prises de lazutf8.pas:

<Delphi> function UTF8CharacterLength(p: PChar): integer; function UTF8Length(const s: string): PtrInt; function UTF8Length(p: PChar; ByteCount: PtrInt): PtrInt; function UTF8CharacterToUnicode(p: PChar; out CharLen: integer): Cardinal; function UnicodeToUTF8(u: cardinal; Buf: PChar): integer; inline; function UnicodeToUTF8SkipErrors(u: cardinal; Buf: PChar): integer; function UnicodeToUTF8(u: cardinal): shortstring; inline; function UTF8ToDoubleByteString(const s: string): string; function UTF8ToDoubleByte(UTF8Str: PChar; Len: PtrInt; DBStr: PByte): PtrInt; function UTF8FindNearestCharStart(UTF8Str: PChar; Len: integer;

                                 BytePos: integer): integer;

// find the n-th UTF8 character, ignoring BIDI function UTF8CharStart(UTF8Str: PChar; Len, CharIndex: PtrInt): PChar; // find the byte index of the n-th UTF8 character, ignoring BIDI (byte len of substr) function UTF8CharToByteIndex(UTF8Str: PChar; Len, CharIndex: PtrInt): PtrInt; procedure UTF8FixBroken(P: PChar); function UTF8CharacterStrictLength(P: PChar): integer; function UTF8CStringToUTF8String(SourceStart: PChar; SourceLen: PtrInt) : string; function UTF8Pos(const SearchForText, SearchInText: string): PtrInt; function UTF8Copy(const s: string; StartCharIndex, CharCount: PtrInt): string; procedure UTF8Delete(var s: String; StartCharIndex, CharCount: PtrInt); procedure UTF8Insert(const source: String; var s: string; StartCharIndex: PtrInt);

function UTF8LowerCase(const AInStr: string; ALanguage: string=): string; function UTF8UpperCase(const AInStr: string; ALanguage: string=): string; function FindInvalidUTF8Character(p: PChar; Count: PtrInt;

                                 StopOnNonASCII: Boolean = false): PtrInt;

function ValidUTF8String(const s: String): String;

procedure AssignUTF8ListToAnsi(UTF8List, AnsiList: TStrings);

//compare functions

function UTF8CompareStr(const S1, S2: string): Integer; function UTF8CompareText(const S1, S2: string): Integer; </Delphi>

Travailler avec les répertoires et les noms de fichier

Les contrôles et fonctions de Lazarus attendent des noms de fichier et de répertoire encodés en utf-8, mais la RTL utilise des chaines ansi pour ces noms de fichier et répertoire.

Par exemple, considérez un bouton, qui positionne la propriété Directory d'une TFileListBox au répertoire courant. La fonction RTL GetCurrentDir travaille en ansi, et non en unicode, donc une conversion est nécessaire:

<delphi>procedure TForm1.Button1Click(Sender: TObject); begin

 FileListBox1.Directory:=SysToUTF8(GetCurrentDir);
 // or use the functions from the FileUtil unit
 FileListBox1.Directory:=GetCurrentDirUTF8;

end;</delphi>

L'unité FileUtil définit les fonctions classiques de fichiers travaillant avec des chaines UTF-8:

<Delphi>// basic functions similar to the RTL but working with UTF-8 instead of the // system encoding

// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, Mac OS X // but normally these OS use UTF-8 as system encoding so the widestringmanager // is not needed. function NeedRTLAnsi: boolean;// true if system encoding is not UTF-8 procedure SetNeedRTLAnsi(NewValue: boolean); function UTF8ToSys(const s: string): string;// as UTF8ToAnsi but more independent of widestringmanager function SysToUTF8(const s: string): string;// as AnsiToUTF8 but more independent of widestringmanager

// file operations function FileExistsUTF8(const Filename: string): boolean; function FileAgeUTF8(const FileName: string): Longint; function DirectoryExistsUTF8(const Directory: string): Boolean; function ExpandFileNameUTF8(const FileName: string): string; function ExpandUNCFileNameUTF8(const FileName: string): string; {$IFNDEF VER2_2_0} function ExtractShortPathNameUTF8(Const FileName : String) : String; {$ENDIF} function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint; function FindNextUTF8(var Rslt: TSearchRec): Longint; procedure FindCloseUTF8(var F: TSearchrec); function FileSetDateUTF8(const FileName: String; Age: Longint): Longint; function FileGetAttrUTF8(const FileName: String): Longint; function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint; function DeleteFileUTF8(const FileName: String): Boolean; function RenameFileUTF8(const OldName, NewName: String): Boolean; function FileSearchUTF8(const Name, DirList : String): String; function FileIsReadOnlyUTF8(const FileName: String): Boolean; function GetCurrentDirUTF8: String; function SetCurrentDirUTF8(const NewDir: String): Boolean; function CreateDirUTF8(const NewDir: String): Boolean; function RemoveDirUTF8(const Dir: String): Boolean; function ForceDirectoriesUTF8(const Dir: string): Boolean;

// environment function ParamStrUTF8(Param: Integer): string; function GetEnvironmentStringUTF8(Index : Integer): String; function GetEnvironmentVariableUTF8(const EnvVar: String): String; function GetAppConfigDirUTF8(Global: Boolean): string;</Delphi>

Mac OS X

Les fonctions de fichier de l'unité FileUtil font également attention à une particularité de Mac OS X: OS X normalise les noms de fichier. Par exemple, le nom de fichier 'ä.txt' peut être encodé en unicode avec deux séquences différentes (#$C3#$A4 et 'a'#$CC#$88). Sous Linux et BSD, vous pouvez utiliser les deux représentations. OS X convertit automatiquement le a tréma dans la séquence de trois octets. Ce qui signifie:

<Delphi>if Filename1 = Filename2 then ... // n'est pas suffisant sous OS X if AnsiCompareFileName(Filename1, Filename2) = 0 then ... // insuffisant sous fpc 2.2.2, même avec cwstring if CompareFilenames(Filename1, Filename2) = 0 then ... // Ceci fonctionne toujours (unité FileUtil ou FileProcs)</Delphi>

Langages Est-asiatiques sous Windows

La police par défaut (Tahoma) utilisée pour les contrôles d'interface utilisateur sous Windows XP est capable d'afficher correctement plusieurs langages, incluant l'arable, le russe et les langages occidentaux, mais pas les langages d'Asis de l'est, comme le chinois, le japonais et le koréen. En allant dans le Panneau de Configuration, dans les Options régionales, sur l'onglet Langages et en installant le East Asia Language Pack, la police standard de l'interface utilisateur commencera simplement à afficher correctement ces langages. Bien évidemment, les versions de Windows XP localisées pour ces langages contiendront déjà ce pack de langage. Instructions détaillées ici.

Particularités de Free Pascal

UTF8 and source files - the missing BOM

When you create source files with Lazarus and type some non ascii characters the file is saved in UTF8. It does not use BOM (Byte Order Mark). You can change the encoding via right click on source editor / File Settings / Encoding. The reason for the lacking BOM is how FPC treats Ansistrings. For compatibility the LCL uses Ansistrings and for portability the LCL uses UTF8.

Note: Some MS Windows text editors might treat the files as system codepage and show them as invalid characters. Do not add the BOM. If you add the BOM you have to change all string assignments.

For example:

<Delphi>Button1.Caption := 'Über';</Delphi>

When 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.

<Delphi>// source file saved as UTF without BOM if FileExists('Über.txt') then ; // wrong, because FileExists expects system encoding if FileExistsUTF8('Über.txt') then ; // correct</Delphi>

Unicode essentials

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).

There are three schemes for representing Unicode code points as unique byte sequences. These schemes are called Unicode transformation formats: UTF-8, UTF-16 and UTF-32. The conversions between all of them are possible. Here are their basic properties:

                           UTF-8 UTF-16 UTF-32
Smallest code point [hex] 000000 000000 000000
Largest code point  [hex] 10FFFF 10FFFF 10FFFF
Code unit size [bits]          8     16     32
Minimal bytes/character        1      2      4
Maximal bytes/character        4      4      4

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 search for substrings. The first byte of a multibyte sequence that represents 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.

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.

Finally, any Unicode character can be represented as a single 32-bit unit in UTF-32.

For more, see: Unicode FAQ - Basic questions, Unicode FAQ - UTF-8, UTF-16, UTF-32 & BOM, Wikipedia: UTF-8 [1]

Implementation Details

Since the gtk1 interface was obsoleted in Lazarus 0.9.31 all LCL interfaces are Unicode capable and Lazarus and the LCL use and accept only UTF-8 encoded strings, unless in routines explicitly marked as accepting other encodings.

Unicode-enabling the win32 interface

Guidelines

First, 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 this moment all existing programs that use ANSI characters will need migration to Unicode.

Windows platforms <=Win9x are based on ISO code page standards and only partially support Unicode. Windows platforms starting with WinNT and Windows CE fully support Unicode. Win 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, i.e. UTF-16 encoded strings, as parameters. Windows CE only uses Wide API functions.

Wide functions present on Windows 9x

Some Wide API functions are present on Windows 9x. Here is a list of such functions: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/mslu/winprog/other_existing_unicode_support.asp

Conversion example:

<delphi>GetTextExtentPoint32(hdcNewBitmap, LPSTR(ButtonCaption), Length(ButtonCaption), TextSize);</delphi>

Becomes:

<delphi>{$ifdef WindowsUnicodeSupport}

 GetTextExtentPoint32W(hdcNewBitmap, PWideChar(Utf8Decode(ButtonCaption)), Length(WideCaption), TextSize);

{$else}

 GetTextExtentPoint32(hdcNewBitmap, LPSTR(ButtonCaption), Length(ButtonCaption), TextSize);

{$endif}</delphi>

Functions that need Ansi and Wide versions

First Conversion example:

<delphi>function TGDIWindow.GetTitle: String; var

l: Integer;

begin

  l := Windows.GetWindowTextLength(Handle);
  SetLength(Result, l);
  Windows.GetWindowText(Handle, @Result[1], l);

end;</delphi>

Becomes:

<delphi>function TGDIWindow.GetTitle: String; var

 l: Integer;
 AnsiBuffer: string;
 WideBuffer: WideString;

begin

{$ifdef WindowsUnicodeSupport}

if UnicodeEnabledOS then begin

 l := Windows.GetWindowTextLengthW(Handle);
 SetLength(WideBuffer, l);
 l := Windows.GetWindowTextW(Handle, @WideBuffer[1], l);
 SetLength(WideBuffer, l);
 Result := Utf8Encode(WideBuffer);

end else begin

 l := Windows.GetWindowTextLength(Handle);
 SetLength(AnsiBuffer, l);
 l := Windows.GetWindowText(Handle, @AnsiBuffer[1], l);
 SetLength(AnsiBuffer, l);
 Result := AnsiToUtf8(AnsiBuffer);

end;

{$else}

  l := Windows.GetWindowTextLength(Handle);
  SetLength(Result, l);
  Windows.GetWindowText(Handle, @Result[1], l);

{$endif}

end;</delphi>


Screenshots

Lazarus Unicode Test.png

See Also

  • UTF-8 - Description of UTF-8 strings