Difference between revisions of "LCL Unicode Support/zh CN"

From Lazarus wiki
Jump to navigationJump to search
m (Fixed syntax highlighting; deleted category included in page template)
 
(11 intermediate revisions by 4 users not shown)
Line 3: Line 3:
 
== 介绍 ==
 
== 介绍 ==
  
As of 0.9.25, Lazarus has full Unicode support in all platforms except Gtk 1. In this page one can find instructions for Lazarus users, roadmaps, descriptions of basic concepts and implementation details.
+
自0.9.25版本开始,Lazarus已经完全支持所有平台上的Unicode编码,只有GTK1除外。本页包含辣子用户的使用说明、辣子对Unicode的计划,相关的基本概念以及目前辣子对于Unicode已有实现的细节。
  
自0.9.25版本开始,Lazarus已经完全支持所有平台上的Unicode编码,只有GTK1除外(译注:GTK2.0支持)。在本页面,能找到给Lazarus用户的介绍、路线图、执行细节。
+
(译者注:本文基本是对英文版的翻译,但是因为辣子对于Unicode的支持一直都在进展中,所以本文可能在你看到时已经过时)
  
本文初版由[[User:Dennies]]翻译正体中文页面转换而来。
+
==辣子用户的操作指南==
'元件'一词英文为:component,简体界面中译为'组件'。
 
<s>Lazarus对Unicode的支援还需要进一步开发,尤其是在Windows平台上。以下提供一些基本的资讯,让想要加强Lazarus对Unicode支援的人参考,如果您发现这些资讯有误、不足或过时了,请您不吝修正、补充或更新它,谢谢。
 
请注意: 实作的细节部分目前还在讨论中,这部分的文件随时都有可能更新。如果您已经初步了解Unicode的标准,且您已经在Delphi上面有使用过WideString这个型别来撰写程式,会有助于您理解Lazarus对Unicode支援的加强工作。如果您使用过非Latin编码的字元集来撰写脚本语言,也会有些帮助的。</s>
 
  
== Instructions for users 操作指南 ==
+
尽管辣子拥有一套Unicode的控件集,但应被指出的是,不是所有的东西都是Unicode的。开发人员应自己负责掌控自己的字符串是什么编码,并且在使用了不同编码方式的库之间做出正确的编码转换。
  
In unicode widgetsets it's important to note that not everything is unicode. The Free Pascal Runtime Library, the Free Pascal FCL library and the Fileutils unit all are ansi. It's the responsability of the developer to know what is the encoding of their strings and do the proper conversion between libraries which expect different encodings.
+
通常,每一个库自己会保持一致的字符编码(库指的是dll或者辣子的package)。一般要么是Unicode(对于Lazarus一直是UTF-8),要么是Ansi(系统编码的意思,可能是utf-8可能不是)。FPC2.4附带的RTL和FCL都使用Ansi。目前看来FPC2.5.X也是这样。
  
在Unicode面板设置(Widgetsets)中,重要的是note that not everything is unicode。嗯,FPC库、FP的FCL库和文件单元(Fileutils)都是ansi编码的。这是有效的,开发者了解他们的字符串是什么编码,并且做出正确的转换,在不同的编码库之间。
+
你能用System单元中的两个函数来转换这两种编码的字符串,UTF8ToAnsi和AnsiToUTF8。
 +
FileUtil单元中提供的UTF8ToSys和SysToUTF8也提供类似功能,不过它们更灵活一些,使用它们就得多写一些代码。
  
Usually the encoding is per-library. Each library will uniformally expect 1 kind of encoding, which will usually either be unicode (UTF-8 for Lazarus) or Ansi (which means the system encoding, and may be utf-8 or not). The fileutils unit from the LCL is an exception because it's destined in the future to be added to the fcl or the rtl. The RTL and the FCL expect ansi strings.
+
===FPC不使用Unicode===
 +
目前版本(截止到现在的FPC2.5.X)的Free Pascal的RTL(Runtime Library)和Free Pascal的FCL(Free Component Library)都是Ansi的,在和Unicode的库交互时注意转换。
  
通常,编码是per-library,每一个库都将保持一致编码,通常是 Unicode(UTF-8 for Lazarus)和Ansi(这是系统编码,或者是utf-8或者不是)二者之一。来自LCL的文件单元(fileutils)是个例外,因为它被设计为能够添加fcl或rtl。而RTL和FCL都是ansi字符串。(译注:似乎是坏消息)
+
===ANSI和Unicode互相转换的例子===
  
You can can convert between unicode and ansi using the UTF8ToAnsi and AnsiToUTF8 functions from the System unit.
+
话说你从TEdit得到一个字符串,然后你想把它当参数传给某些个RTL中文件处理相关的函数:
 
+
<syntaxhighlight lang=pascal>var
你能用两个函数转换系统单元,UFT8ToAnsi和AnsiToUTF8。例如:
 
 
 
Say you get a string from a TEdit and you want to give it to some rtl file routine:
 
 
 
话说你得到一个字符串,从TEdit,然后你想给它一些rtl文件例程(routine):
 
<delphi>var
 
 
   MyString: string; // utf-8 encoded
 
   MyString: string; // utf-8 encoded
 
begin
 
begin
 
   MyString := MyTEdit.Text;
 
   MyString := MyTEdit.Text;
 
   SomeRTLRoutine(UTF8ToAnsi(MyString));
 
   SomeRTLRoutine(UTF8ToAnsi(MyString));
end;</delphi>
+
end;</syntaxhighlight>
  
 
还有反向的:
 
还有反向的:
<delphi>var
+
<syntaxhighlight lang=pascal>var
 
   MyString: string; // ansi encoded
 
   MyString: string; // ansi encoded
 
begin
 
begin
 
   MyString := SomeRTLRoutine;
 
   MyString := SomeRTLRoutine;
 
   MyTEdit.Text := AnsiToUTF8(MyString);
 
   MyTEdit.Text := AnsiToUTF8(MyString);
end;</delphi>
+
end;</syntaxhighlight>
 +
 
 +
'''注意''':UTF8ToAnsi如果探测到传入的UTF-8字符串中包含非法字符,会返回一个空string。
 +
'''注意''':在Linux,BSD和Mac OS X下,AnsiToUTF8和UTF8ToAnsi需要一个WideString Manager才能使用。这种情况下,你可以转而使用FileUtil单元提供的SysToUTF8和UTF8ToSys,或者通过把cwstring单元放到你的程序的uses第一个单元来添加这个WideString Manager。
 +
 
 +
===应付UTF8 String和字符===
 +
在辣子版本0.9.30的时候,UTF8的处理函数们还都在LCL的LCLProc单元中。在版本0.9.31+起,LCLProc中的的这些函数为了兼容老代码依然保留,但是真正的处理代码(LCLProc中的这些函数只剩下声明了)都转移到了lazutils这个package的lazutf8单元中了。
 +
 
 +
如果你要操作的字符串是UTF8的,以后应当尽量使用lazutf8单元而不是FPC附带的SysUtils单元来处理,毕竟SysUtils到现在也没准备好支持Unicode,但是lazutf8可以。lazutf8重新实现的SysUtils中的字符串处理函数,一般都是在原函数(SysUtils中的)的名字前面加了前缀UTF8。
 +
 
 +
另外要说明的是,在Unicode的语境下,用char为单位,把一个string当成一个char array来遍历是不行的。不只是UTF8会这样,目前Unicode的常见编码(UTF8、UTF16)中,一个字符用多少个char来编码都是不定的。如果你想遍历一个UTF-8 string中的所有字符,一般有两种办法:
 +
*以byte为单位遍历,如果你只是想处理这个UTF-8 string中的ASCII字符的话。例如在解析一个xml的时候。
 +
*以charactor为单位,这在synedit之类的可视化控件中很有用。例如你想知道某个控件中显示的第三个字符是个啥。(译者注:参看下文“遍历UTF8字符串”)
 +
 
 +
====查找子串====
 +
因为UTF8编码本身的特点(译注:兼容ASCII),你可以用普通的字符串处理函数来查找:
 +
<syntaxhighlight lang=pascal>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;</syntaxhighlight>
 +
 
 +
====遍历UTF8字符串====
 +
注:某个Unicode字符在UTF8中不一定需要几个byte(最多三个)来编码。
 +
下面代码按照charactor遍历了一个UTF8字符串:
 +
<syntaxhighlight lang=pascal>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;</syntaxhighlight>
 +
 
 +
====访问UTF8字符串中的第N个字符====
 +
lazutf8提供了一个函数(译注:因为UTF8编码本身的特点,这个函数本身应该也是遍历实现的):
 +
<syntaxhighlight lang=pascal>uses lazutf8; // LCLProc for Lazarus 0.9.30 or inferior
 +
...
 +
var
 +
  AnUTF8String, NthChar: string;
 +
begin
 +
  NthChar := UTF8Copy(AnUTF8String, N, 1);
 +
</syntaxhighlight>
  
===Dealing with directory and filenames 应付文件夹和文件名 ===
+
====使用UTF8CharactorToUnicode来得到每个字符的codepoint====
 +
以下演示如何得到一个UTF8字符串中每个字符的32位整数大小的codepoint:
 +
<syntaxhighlight lang=pascal>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;</syntaxhighlight>
  
Lazarus controls which expect filenames and directory names use utf-8 strings, but the RTL uses ansi strings for directories and filenames.
+
====UTF8版本的Copy, Length, LowerCase等等====
 +
你能想到的,lazutf8基本都提供了(前面也说过,0.9.30及以前版本使用LCLProc单元而不是新的lazutf8单元)。下面是lazutf8.pas中的一个片段:
 +
<syntaxhighlight lang=pascal>
 +
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);
  
辣子用utf-8字符串来控制文件名和文件夹名称,但RTL用ansi字符串来控制它们。(译注:这个RTL老是捣乱)
+
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;
  
For 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, and not unicode, so conversion is needed:
+
procedure AssignUTF8ListToAnsi(UTF8List, AnsiList: TStrings);
  
举个例子,来一个按钮,设置为文件夹属性,TFileListBox到当前文件夹。这个RTL函数[[doc:rtl/sysutils/getcurrentdir.html|GetCurrentDir]]就是ansi编码,而不是unicode,所以,转换就是必须的:
+
//compare functions
<delphi>procedure TForm1.Button1Click(Sender: TObject);
+
 
 +
function UTF8CompareStr(const S1, S2: string): Integer;
 +
function UTF8CompareText(const S1, S2: string): Integer;
 +
</syntaxhighlight>
 +
 
 +
===应付目录和文件名===
 +
 
 +
辣子的控件库处理的文件名和目录名都是utf-8编码的,但RTL中的文件名和目录名都用的ansi来编码。
 +
 
 +
举个例子,一个按钮控件,点击它来设置TFileListBox的文件夹属性为当前目录。RTL中函数[[doc:rtl/sysutils/getcurrentdir.html|GetCurrentDir]]是ansi编码的,而不是unicode,所以,转换就是必须的:
 +
<syntaxhighlight lang=pascal>procedure TForm1.Button1Click(Sender: TObject);
 
begin
 
begin
 
   FileListBox1.Directory:=AnsiToUTF8(GetCurrentDir);
 
   FileListBox1.Directory:=AnsiToUTF8(GetCurrentDir);
end;</delphi>
+
end;</syntaxhighlight>
 +
 
 +
现在FileUtil单元定义了很多UTF8版本的文件处理函数。(译注:总结,如果想用UTF8,处理字符串请用lazutf8而不是SysUtil,处理文件和目录,使用FileUtil中的UTF8函数,而不是RTL)
 +
<syntaxhighlight lang=pascal>// 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;</syntaxhighlight>
  
== 程式实作规范 ==
+
====Mac OS X====
 +
FileUtil单元中的文件处理函数也考虑到了Mac OS X的特殊情况:OS X的normalizes filenames。例如'ä.txt'这个文件名,可以用两个不同的Unicode codepoint序列来编码(#$C3#$A4 and 'a'#$CC#$88)。在Linux和BSD下创建文件'ä.txt',文件系统存储成哪种序列都有可能。但是OS X只会使用第二种序列。这意味着:
 +
<syntaxhighlight lang=pascal>if Filename1 = Filename2 then ... // is not sufficient under OS X
 +
if AnsiCompareFileName(Filename1, Filename2) = 0 then ... // not sufficient under fpc 2.2.2, even not with cwstring
 +
if CompareFilenames(Filename1, Filename2) = 0 then ... // this always works (unit FileUtil or FileProcs)</syntaxhighlight>
 +
 
 +
==UTF8和源代码文件 之 那个消失的BOM==
 +
(译注:这个题目碉堡了)
 +
你在Lazarus IDE中创建的源代码都是用UTF8编码的(译注:意味着你在代码中硬编码的字符串都是utf8编码的),不过这些文件并没有按照Windows平台的习惯去写入BOM(Byte Order Mark)。你可以在IDE的Source Editor上点击右键->File Settings->Encoding上改变这个源代码文件的编码。之所以不写入BOM是因为FPC应付Ansi String的方式。为了兼容FPC,LCL使用Ansi String,为了移植性,LCL使用UTF8的Ansi String,也就是不能带有BOM了。
 +
因为没有写入BOM,那么例如Windows的“记事本”之类的程序可能在打开你的源代码后,ASCII之外的字符都显示成乱码,这是正常的。
 +
例如,你在源代码中:
 +
<syntaxhighlight lang=pascal>Button1.Caption := 'Über';</syntaxhighlight>
 +
如果没有BOM(或者编译器也没通过其他途径知道这个文件使用了什么编码),那么编译器认为源代码文件是ansi的,'Über'也是Ansi的(尽管它其实是UTF8的),而LCL号称自己也要一个Ansi(事实上是UTF8这种Ansi),'Über'就会原封不动地传给LCL(如果编译器知道这个文件使用的不是系统的Ansi,那么就会执行一个转换,转换为系统的ansi之后再传给LCL),所以一切都没有问题。
 +
<syntaxhighlight lang=pascal>// source file saved as UTF without BOM
 +
if FileExists('Über.txt') then ; // wrong, because FileExists expects system encoding
 +
if FileExistsUTF8('Über.txt') then ; // correct</syntaxhighlight>
 +
 
 +
==WideStrings和AnsiStrings==
 +
这么转换:
 +
<syntaxhighlight lang=pascal>var
 +
  w: widestring;
 +
begin
 +
  w:='Über'; // wrong, because FPC will convert from system codepage(译注:尽管其实是UTF8) to UTF16
 +
  w:=UTF8ToUTF16('Über'); // correct
 +
  Button1.Caption:=UTF16ToUTF8(w);
 +
end;</syntaxhighlight>
 +
 
 +
==写入了BOM的源代码文件==
 +
编译器允许不同单元使用不同的编码,所以你的某个单元可以有BOM,同时别的单元没有BOM。
 +
 
 +
== 辣子对于Unicode应该支持成什么样子(Guidline) ==
  
 
=== 需求 ===
 
=== 需求 ===
  
Lazarus的精神是”一次编写,到处'编译'” (JAVA的精神则是‘一次编写,到处‘执行’)。根据Lazarus的精神,表示在理想状态下,一个支援Unicode的应用程式,应该只有一份能够支援Unicode的程式码,而不用为各种不同的语系作不同的原始码,或者资源档。更不用在程式码里面使用条件定义(IFDEF)为不同的作业系统/不同语系作编译的定义。
+
Lazarus的精神是“一次编写,到处编译”。这意味着,在理想状态下,一个支持Unicode的应用程序,应该只有一份只支持Unicode的源代码,而不用为各种不同的语言环境去定义一些编译开关之类的龌龊玩意儿。
  
在LCL的 “interface”宣告部分,已经可以支援各种相容于Unicode的作业系统,让程式人员可以不用去管每个系统上面对Unicode要怎么处理的繁琐细节。
+
在LCL的外界调用接口,已经可以支援各种相容于Unicode的作业系统,让程式人员可以不用去管每个系统上面对Unicode要怎么处理的繁琐细节。
  
 
而Lazarus本身需要注意的,则是在Lazarus内部用来沟通的字串 (例如应用程式跟LCL,以及 LCL跟视窗元件之间),都是透过Pascal的原始string (string里的每个字元都是1个Byte,而Unicode是多个Byte的字元集)。因此,逻辑上来说,Lazarus的程式码就必须以[[UTF-8]]编码来储存,才能保留住所有Unicode的相关资讯。
 
而Lazarus本身需要注意的,则是在Lazarus内部用来沟通的字串 (例如应用程式跟LCL,以及 LCL跟视窗元件之间),都是透过Pascal的原始string (string里的每个字元都是1个Byte,而Unicode是多个Byte的字元集)。因此,逻辑上来说,Lazarus的程式码就必须以[[UTF-8]]编码来储存,才能保留住所有Unicode的相关资讯。
  
=== 和Unicode进行整合 ===
+
=== 移植到Unicode ===
  
 
目前绝大多数版本的Lazarus使用的是Ansi编码,因为这是Gtk1预设的编码法,也是Win32目前的预设编码法,Windows 2000以后的Windows作业系统虽然都已经能够相容于Unicode,但预设的编码法仍然是Ansi,这个情形在不久的未来恐怕会有所改变,所有的视窗元件都会支援UTF-8,而所有的应用程式在传递资料给介面时,也都需要先转换成 UTF-8了,当然,这得依赖各种IDE的作者在元件检视器里面更改程式码才能作到。
 
目前绝大多数版本的Lazarus使用的是Ansi编码,因为这是Gtk1预设的编码法,也是Win32目前的预设编码法,Windows 2000以后的Windows作业系统虽然都已经能够相容于Unicode,但预设的编码法仍然是Ansi,这个情形在不久的未来恐怕会有所改变,所有的视窗元件都会支援UTF-8,而所有的应用程式在传递资料给介面时,也都需要先转换成 UTF-8了,当然,这得依赖各种IDE的作者在元件检视器里面更改程式码才能作到。
Line 316: Line 498:
 
==== 要支援Unicode的话,目前已知的问题: ====
 
==== 要支援Unicode的话,目前已知的问题: ====
  
* SynEdit不支援由右到左显示的字元
+
* SynEdit不支援由右到左显示的字元(RTL)
 +
 
 +
* <s>Is OpenFileDialogCallBack tested with selection large numbers of files?
 +
** Is this problem unicode specific? I think it's a generic problem. --[[User:Sekelsenmat|Sekelsenmat]] 13:40, 14 February 2008 (CET)
 +
*** Maybe. I know I tested it with large number of files before the Unicode version was added. If it is a generic problem, then the the non-Unicode version got broken, when the Unicode version was added. [[User:Vincent|Vincent]] 21:45, 15 February 2008 (CET)
 +
**** Associated bugtracker item: http://bugs.freepascal.org/view.php?id=10918</s> Fixed and implemented.
 +
* <s>class function TWin32WSSelectDirectoryDialog.CreateHandle: Title, FileName and InitialDir should be made Unicode aware.
 +
** Associated bugtracker item: http://bugs.freepascal.org/view.php?id=10919</s> Implemented.
 
* 在编辑器上面双击非ANSI编码的字元时,被双击的字不会被选取,反而是被双击的字左方的其他字会被选取起来
 
* 在编辑器上面双击非ANSI编码的字元时,被双击的字不会被选取,反而是被双击的字左方的其他字会被选取起来
 
* 复制贴上Unicode字元的时候,如果当时的视窗环境不是使用Unicode为编码字元的话,复制贴上的动作会不正确
 
* 复制贴上Unicode字元的时候,如果当时的视窗环境不是使用Unicode为编码字元的话,复制贴上的动作会不正确
Line 327: Line 516:
 
* (还有一些问题是没有被确认的,如果经过确认的话,就会一道列在上述的清单里面)
 
* (还有一些问题是没有被确认的,如果经过确认的话,就会一道列在上述的清单里面)
  
==== 需要检查的Unit档的清单: ====
 
  
*"win32callback.inc"
 
*"win32def.pp"
 
*"win32int.pp"
 
*"win32lclintf.inc"
 
*"win32lclintfh.inc"
 
*"win32listsl.inc"
 
*"win32listslh.inc"
 
*"win32memostrings.inc"
 
*"win32object.inc"
 
*"win32proc.pp"
 
*"win32winapi.inc"
 
*"win32winapih.inc"
 
*"win32wsactnlist.pp"
 
*"win32wsarrow.pp"
 
*"win32wsbuttons.pp"
 
*"win32wscalendar.pp"
 
*"win32wschecklst.pp"
 
*"win32wsclistbox.pp"
 
*"win32wscomctrls.pp"
 
*"win32wscontrols.pp"
 
*"win32wscustomlistview.inc"
 
*"win32wsdbctrls.pp"
 
*"win32wsdbgrids.pp"
 
*"win32wsdialogs.pp"
 
*<s>"win32wsdirsel.pp"</s> - Felipe
 
*<s>"win32wseditbtn.pp"</s> - Felipe
 
*<s>"win32wsextctrls.pp"</s> - Felipe
 
*<s>"win32wsextdlgs.pp"</s> - Felipe
 
*<s>"win32wsfilectrl.pp"</s> - Felipe
 
*<s>"win32wsforms.pp"</s> - Felipe
 
*<s>"win32wsgrids.pp"</s> - Felipe
 
*<s>"win32wsimglist.pp"</s> - Felipe
 
*<s>"win32wsmaskedit.pp"</s> - Felipe
 
*<s>"win32wsmenus.pp"</s> - Felipe
 
*<s>"win32wspairsplitter.pp"</s> - Felipe
 
*<s>"win32wsspin.pp"</s> - Felipe
 
*<s>"win32wsstdctrls.pp"</s> - Felipe
 
*<s>"win32wstoolwin.pp"</s> - Felipe
 
*<s>"winext.pas"</s> - Felipe
 
  
 
=== 荧幕截图 ===
 
=== 荧幕截图 ===
Line 375: Line 524:
 
== 额外参考 ==
 
== 额外参考 ==
 
* [[UTF-8]] - Description of UTF-8 strings
 
* [[UTF-8]] - Description of UTF-8 strings
 
 
[[Category:zh]]
 

Latest revision as of 01:21, 19 February 2020

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

介绍

自0.9.25版本开始,Lazarus已经完全支持所有平台上的Unicode编码,只有GTK1除外。本页包含辣子用户的使用说明、辣子对Unicode的计划,相关的基本概念以及目前辣子对于Unicode已有实现的细节。

(译者注:本文基本是对英文版的翻译,但是因为辣子对于Unicode的支持一直都在进展中,所以本文可能在你看到时已经过时)

辣子用户的操作指南

尽管辣子拥有一套Unicode的控件集,但应被指出的是,不是所有的东西都是Unicode的。开发人员应自己负责掌控自己的字符串是什么编码,并且在使用了不同编码方式的库之间做出正确的编码转换。

通常,每一个库自己会保持一致的字符编码(库指的是dll或者辣子的package)。一般要么是Unicode(对于Lazarus一直是UTF-8),要么是Ansi(系统编码的意思,可能是utf-8可能不是)。FPC2.4附带的RTL和FCL都使用Ansi。目前看来FPC2.5.X也是这样。

你能用System单元中的两个函数来转换这两种编码的字符串,UTF8ToAnsi和AnsiToUTF8。 FileUtil单元中提供的UTF8ToSys和SysToUTF8也提供类似功能,不过它们更灵活一些,使用它们就得多写一些代码。

FPC不使用Unicode

目前版本(截止到现在的FPC2.5.X)的Free Pascal的RTL(Runtime Library)和Free Pascal的FCL(Free Component Library)都是Ansi的,在和Unicode的库交互时注意转换。

ANSI和Unicode互相转换的例子

话说你从TEdit得到一个字符串,然后你想把它当参数传给某些个RTL中文件处理相关的函数:

var
  MyString: string; // utf-8 encoded
begin
  MyString := MyTEdit.Text;
  SomeRTLRoutine(UTF8ToAnsi(MyString));
end;

还有反向的:

var
  MyString: string; // ansi encoded
begin
  MyString := SomeRTLRoutine;
  MyTEdit.Text := AnsiToUTF8(MyString);
end;

注意:UTF8ToAnsi如果探测到传入的UTF-8字符串中包含非法字符,会返回一个空string。 注意:在Linux,BSD和Mac OS X下,AnsiToUTF8和UTF8ToAnsi需要一个WideString Manager才能使用。这种情况下,你可以转而使用FileUtil单元提供的SysToUTF8和UTF8ToSys,或者通过把cwstring单元放到你的程序的uses第一个单元来添加这个WideString Manager。

应付UTF8 String和字符

在辣子版本0.9.30的时候,UTF8的处理函数们还都在LCL的LCLProc单元中。在版本0.9.31+起,LCLProc中的的这些函数为了兼容老代码依然保留,但是真正的处理代码(LCLProc中的这些函数只剩下声明了)都转移到了lazutils这个package的lazutf8单元中了。

如果你要操作的字符串是UTF8的,以后应当尽量使用lazutf8单元而不是FPC附带的SysUtils单元来处理,毕竟SysUtils到现在也没准备好支持Unicode,但是lazutf8可以。lazutf8重新实现的SysUtils中的字符串处理函数,一般都是在原函数(SysUtils中的)的名字前面加了前缀UTF8。

另外要说明的是,在Unicode的语境下,用char为单位,把一个string当成一个char array来遍历是不行的。不只是UTF8会这样,目前Unicode的常见编码(UTF8、UTF16)中,一个字符用多少个char来编码都是不定的。如果你想遍历一个UTF-8 string中的所有字符,一般有两种办法:

  • 以byte为单位遍历,如果你只是想处理这个UTF-8 string中的ASCII字符的话。例如在解析一个xml的时候。
  • 以charactor为单位,这在synedit之类的可视化控件中很有用。例如你想知道某个控件中显示的第三个字符是个啥。(译者注:参看下文“遍历UTF8字符串”)

查找子串

因为UTF8编码本身的特点(译注:兼容ASCII),你可以用普通的字符串处理函数来查找:

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;

遍历UTF8字符串

注:某个Unicode字符在UTF8中不一定需要几个byte(最多三个)来编码。 下面代码按照charactor遍历了一个UTF8字符串:

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;

访问UTF8字符串中的第N个字符

lazutf8提供了一个函数(译注:因为UTF8编码本身的特点,这个函数本身应该也是遍历实现的):

uses lazutf8; // LCLProc for Lazarus 0.9.30 or inferior
...
var
  AnUTF8String, NthChar: string;
begin
  NthChar := UTF8Copy(AnUTF8String, N, 1);

使用UTF8CharactorToUnicode来得到每个字符的codepoint

以下演示如何得到一个UTF8字符串中每个字符的32位整数大小的codepoint:

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;

UTF8版本的Copy, Length, LowerCase等等

你能想到的,lazutf8基本都提供了(前面也说过,0.9.30及以前版本使用LCLProc单元而不是新的lazutf8单元)。下面是lazutf8.pas中的一个片段:

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;

应付目录和文件名

辣子的控件库处理的文件名和目录名都是utf-8编码的,但RTL中的文件名和目录名都用的ansi来编码。

举个例子,一个按钮控件,点击它来设置TFileListBox的文件夹属性为当前目录。RTL中函数GetCurrentDir是ansi编码的,而不是unicode,所以,转换就是必须的:

procedure TForm1.Button1Click(Sender: TObject);
begin
  FileListBox1.Directory:=AnsiToUTF8(GetCurrentDir);
end;

现在FileUtil单元定义了很多UTF8版本的文件处理函数。(译注:总结,如果想用UTF8,处理字符串请用lazutf8而不是SysUtil,处理文件和目录,使用FileUtil中的UTF8函数,而不是RTL)

// 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;

Mac OS X

FileUtil单元中的文件处理函数也考虑到了Mac OS X的特殊情况:OS X的normalizes filenames。例如'ä.txt'这个文件名,可以用两个不同的Unicode codepoint序列来编码(#$C3#$A4 and 'a'#$CC#$88)。在Linux和BSD下创建文件'ä.txt',文件系统存储成哪种序列都有可能。但是OS X只会使用第二种序列。这意味着:

if Filename1 = Filename2 then ... // is not sufficient under OS X
if AnsiCompareFileName(Filename1, Filename2) = 0 then ... // not sufficient under fpc 2.2.2, even not with cwstring
if CompareFilenames(Filename1, Filename2) = 0 then ... // this always works (unit FileUtil or FileProcs)

UTF8和源代码文件 之 那个消失的BOM

(译注:这个题目碉堡了) 你在Lazarus IDE中创建的源代码都是用UTF8编码的(译注:意味着你在代码中硬编码的字符串都是utf8编码的),不过这些文件并没有按照Windows平台的习惯去写入BOM(Byte Order Mark)。你可以在IDE的Source Editor上点击右键->File Settings->Encoding上改变这个源代码文件的编码。之所以不写入BOM是因为FPC应付Ansi String的方式。为了兼容FPC,LCL使用Ansi String,为了移植性,LCL使用UTF8的Ansi String,也就是不能带有BOM了。 因为没有写入BOM,那么例如Windows的“记事本”之类的程序可能在打开你的源代码后,ASCII之外的字符都显示成乱码,这是正常的。 例如,你在源代码中:

Button1.Caption := 'Über';

如果没有BOM(或者编译器也没通过其他途径知道这个文件使用了什么编码),那么编译器认为源代码文件是ansi的,'Über'也是Ansi的(尽管它其实是UTF8的),而LCL号称自己也要一个Ansi(事实上是UTF8这种Ansi),'Über'就会原封不动地传给LCL(如果编译器知道这个文件使用的不是系统的Ansi,那么就会执行一个转换,转换为系统的ansi之后再传给LCL),所以一切都没有问题。

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

WideStrings和AnsiStrings

这么转换:

var 
  w: widestring;
begin
  w:='Über'; // wrong, because FPC will convert from system codepage(译注:尽管其实是UTF8) to UTF16
  w:=UTF8ToUTF16('Über'); // correct
  Button1.Caption:=UTF16ToUTF8(w);
end;

写入了BOM的源代码文件

编译器允许不同单元使用不同的编码,所以你的某个单元可以有BOM,同时别的单元没有BOM。

辣子对于Unicode应该支持成什么样子(Guidline)

需求

Lazarus的精神是“一次编写,到处编译”。这意味着,在理想状态下,一个支持Unicode的应用程序,应该只有一份只支持Unicode的源代码,而不用为各种不同的语言环境去定义一些编译开关之类的龌龊玩意儿。

在LCL的外界调用接口,已经可以支援各种相容于Unicode的作业系统,让程式人员可以不用去管每个系统上面对Unicode要怎么处理的繁琐细节。

而Lazarus本身需要注意的,则是在Lazarus内部用来沟通的字串 (例如应用程式跟LCL,以及 LCL跟视窗元件之间),都是透过Pascal的原始string (string里的每个字元都是1个Byte,而Unicode是多个Byte的字元集)。因此,逻辑上来说,Lazarus的程式码就必须以UTF-8编码来储存,才能保留住所有Unicode的相关资讯。

移植到Unicode

目前绝大多数版本的Lazarus使用的是Ansi编码,因为这是Gtk1预设的编码法,也是Win32目前的预设编码法,Windows 2000以后的Windows作业系统虽然都已经能够相容于Unicode,但预设的编码法仍然是Ansi,这个情形在不久的未来恐怕会有所改变,所有的视窗元件都会支援UTF-8,而所有的应用程式在传递资料给介面时,也都需要先转换成 UTF-8了,当然,这得依赖各种IDE的作者在元件检视器里面更改程式码才能作到。

当我们在还没完全支援Unicode的视窗元件上面开发程式的时候(例如 Gtk 2, Qt, WinCE, 或许也包含将会出现的Win32U),我们是使用IDE来对比较稳定的视窗元件进行编译的 (例如Gtk跟Win32)。为了保持一致性(例如以ISO字元编码把资料传递给UTF-8的视窗元件),让IDE跟视窗元件使用一致的编码是必要的,这也就表示在能够制作出Unicode的应用程式前,我们将会需要一个稳定的UTF-8的IDE程式。

目前我们有几组不同的视觉元件,分别使用以下的编码法:

  • 使用ANSI编码法: win32跟 gtk (1) 介面
  • 使用 UTF-8 编码法: gtk (1), gtk2, qt, fpGUI, carbin
  • 目前还使用ANSI编码,但需要升级到UTF-8编码: win32, wince

请留意,gtk 1同时属于ANSI跟UTF-8的阵营喔,这是因为gtk 1的编码法,是可以从Gtk 1的环境变数里面加以控制的。

正如目前的Lazarus一样,大多数的应用程式目前都能正常运作,如果用win32, wince或gtk介面重新编译,就得面对要编译其他视窗元件时,使用不同编码的窘境了。而支援UTF-8的应用程式在重新编译给使用Unicode的视窗元件时,就没有这个问题。

很重要的一点是,当您要编译程式时,请记得使用跟您要编译的程式相同编码法的IDE来作。这是因为IDE在进行程式编译的时候,IDE是用它被编译时的编码法来产生LFM跟LRS档案的,而不是我们想要什么编码法,它就能自动切换过去的,这点非常重要,不可不察。

发展路线

目前我们已经有了准则,所以该建立发展路线,并加以实现的时候了。 为此,我们建立了以下的计划,我们的计划是把工作分为两个群组,一个是主要工作,另一个是次要工作。

所有的主要工作都必须在我们宣布Lazarus支援之前完成,这些工作会被当成我们工作中的主要确认部分。

次要工作则是该作,但没有自愿者想作,或者为这些工作定出范围之前不会进行的。


主要工作

使 Win32 视窗元件支援 UTF-8

备注: 在这个步骤中,我们会将所有的32 bits Windows作业系统同时当成目标,这阶段中所有写出来的程式码都会在目前的win32介面中用IFDEF来区隔,以避免在主要的介面中产生问题。在过渡时期结束之后,IFDEF的宣告就会全部移除,只留下Unicode的支援。

状态: 部分已完成。


更新 Gtk 2 的键盘程式码,使得UTF-8能够被支援

备注:

状态: 几乎已完成。部分gtk2预先定义的功能,让使用者自订的部分还没有完成,但我不知道哪几个语系会使用者这些功能。


让Lazarus IDE能正确的跟Win32 Unicode视窗元件运作并支援UTF-8

备注:

状态: 已完成。除了字元对应表,目前字元对应表仍旧只显示255个字元,但反正目前所有的OS都已经提供了很好的Unicode字元对应表,所以这应该也不是那么重要了。

让Lazarus IDE能正确的跟Gtk 2视窗元件运作并支援UTF-8

备注:

状态: 已完成。还有些gtk2介面的问题,但这已经跟UTF-8无关了

次要工作

升级Windows CE的视窗元件,使它能使用UTF-8

备注: 字串转换的程式码已经集中在winceproc.pp这个档案里面,还需要许多测试。

状态: 尚未开始


升级Gtk 1键盘功能,让它能使用UTF-8

备注:

状态: 尚未开始


完成synedit对由右到左(RTL)的文字显示的支援

备注: RTL是指由右到左的输入法,例如阿拉伯文。

状态: 尚未开始

Unicode 须知

Unicode的标准是将整数的0到10FFFF(十六进位)对应为文字。每一个对应关系,称为一个个的编码点(code point)。换句话说,Unicode的字元原则上是定义了U+000000到U+10FFFF个编码点(用十进位来算,是 0到1,114,111)。

要表现Unicode的编码点的位元顺序,一共有三种方法。这些方法被称为Unicode转换格式(Unicode transformation formats)分别是: UTF-8, UTF-16和UTF-32。这三种格式之间是可以相互转换的,以下是这些格式的基本说明:

(原文)

                           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 UTF-16 UTF-32
最小编码点 [十六进位] 000000 000000 000000
最大编码点  [十六进位] 10FFFF 10FFFF 10FFFF
单位编码Size [bits]          8     16     32
占用位元组量(最少)        1      2      4
占用位元组量(最多)        4      4      4


UTF-8 包含几个重要且有用的属性:它是以Byte的顺序进行解译的,所以没有Hi-Byte跟Lo-Byte的差别(其它双位元组的多国语系字元编码都有这样的问题)。 Unicode对字元的定义中,从U+0000到U+007F (ASCII)正好就是直接对应到00h到7Fh,可以跟ASCII直接相容。这意味着使用7-bit ASCII字元的档案或字串,在传递ASCII跟UTF-8的时候,是完全相同的编码方式。而编码点大于U+007F的字元则是依照位元组的顺序进行编码,正好每两个Byte就能代表一个Unicode的字元。因此不会出现一个字元的Byte使用或涵盖到别的字元的资料,也使得制作子字串的搜寻功能简单多了。代表非ASCII字元的位元组中,第一个Byte的内容一定必须在C0h到FDh之间,并且说明这个Unicode字元是使用了几个Bytes来记录的。所有在第一个Byte之后的资料,都一定落在80h到BFh之间,这样的规则也使得自动化与重新修复文件的程序变得更简单。

UTF-16则包含了以下重要的属性: 它只使用16 bit的Word对从U+0000到U+d7ff这些字元进行编码,而其余的Unicode字元编码,则会使用成对的16-bit Words。

最后,任何一个Unicode字元都可以用32-bit为单位的资料来表现,就称为UTF-32.

如果您需要更多的资料,请参阅: Unicode FAQ - Basic questions, Unicode FAQ - UTF-8, UTF-16, UTF-32 & BOM, Wikipedia: UTF-8 [1]

Lazarus 元件库架构须知

LCL包含了两个部分:

  1. 跟编译目标作业平台无关的部分,这部分是使用跟Delphi VCL相似的类别架构来实现的。
  2. "Interfaces" – 使用各编译目标作业平台相关的API来实现的。

介于两个部分之间的沟通,则是透过TWidgetset这个抽象类别来达成的,每个Widgetset的实现都是从TWidgetset这个父类别衍生而来的。

GTK 1的视觉元件是最旧的版本。在该版本的视觉元件当中,字元的编码是以”LANG”这个环境变数来决定的,通常是” ISO-8859-n”这一类的单位元字串编码。近几年内建GTK 1的系统,已经开始把预设字元编码设定为UTF-8了,例如2007年的Mandriva就是一例。在Lazarus的Gtk 1介面中,还少了支援UTF-8的键盘控制功能,这是个大问题,如果不解决的话,Lazarus将无法真正支援跨平台Unicode相容的目标。

Gtk2的视觉元件只能使用UTF-8编码,并且完全支援UTF-8的各项要求。

Win32介面在预设时,使用的是ANSI编码的视觉元件,目前已经开始进行修改,让它能够支援UTF-8,但因为还没完成,所以预设是不使用UTF-8的,也因此目前在Win32介面的视觉元件上,暂时还无法使用Unicode。

Qt介面已经完成了支援UTF-8的准备,在Qt介面中,原生的字元编码是使用UTF-16,但Lazarus的Qt介面会把UTF-8转换为UTF-16,所以在支援上没有问题。

Windows CE只支援UTF-16的字元编码,但Lazarus的Windows CE介面则是在呼叫Windows API之前,先把ISO字元转换为UTF-16编码,所以这部分还算容易修改,就像目前我们把所有的字元转换函式集中放在winceproc.pp里面一样。

如果您需要更多资讯,请参考: LCL内部资讯

让win32介面相容于Unicode

编译能使用Unicode的LCL-Win32函式库

要使Windows版的LCL函式库能使用Unicode,您得先到Lazarus的选单中"Tools" --> "Configure Build Lazarus"进行设定。 请在"Options"的栏位中加上” –dWindowsUnicodeSupport”这个设定值,并把所有的建置对象都选为NONE,只留下LCL的设定选项是Clean+Build,然后再设定win32为要建置的视觉元件(widgetset),按下”Build”按键,即可将LCL重新编译为与Unicode相容的版本了。 完成后,您就可以把您已完成的应用程式重新编译,编译完成后,它们也都能够支援Unicode了。

Note: Since Lazarus version 0.9.25 revision 14883 this operation is not 
      needed anymore as Lazarus is unicode enabled by default.
注意: 自Lazarus 0.9.25 14883版本后,这些操作已经不需要了。因为Lazarus已经默认支持unicode(好消息!!)。

准则

首先,也是最重要的,所有为Win32介面进行的增补工作,都必须被包在 IFDEF WindowsUnicodeSupport这个编译条件式里面,以避免破坏了现有的ANSI介面,等到Unicode相容修正的专案稳定之后,这些IFDEF的编译条件式就会一起被拿掉,而只留下Unicode相容的程式码。在目前这个时间点上,所有现存使用ANSI标准编码的程式,都还得由程式设计人员将之升级,才能与Unicode相容。

Windows平台 <= Win9x只支援ISO系列的字元编码标准,并只有部分功能支援Unicode。Windows系列的平台,是从Windows NT跟Windows CE开始完全支援Unicode的(也同时还支援ISO系列编码,算是双轨并行),Win9x跟NT的API里面,每一个功能都同时提供了两个函式,一个是ANSI编码的(函式名称结尾都会加个A),另一个则是Unicode相容的(函式名称结尾则是会加个W)。

  • W的函式中如果需要传递字串参数,接受的字串必须是WideString,例如UTF-16字串,而Windows CE只使用 *W的API。

在Windows 9x上面,Unicode系列函式的呈现

有些Unicode系列的API也会呈现在Windows 9x平台上,以下就是这些Unicode系列API的列表: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/mslu/winprog/other_existing_unicode_support.asp

转换范例:

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

要改为:

  {$ifdef WindowsUnicodeSupport}
    GetTextExtentPoint32W(hdcNewBitmap, PWideChar(Utf8Decode(ButtonCaption)), Length(WideCaption), TextSize);
  {$else}
    GetTextExtentPoint32(hdcNewBitmap, LPSTR(ButtonCaption), Length(ButtonCaption), TextSize);
  {$endif}

需要转换ANSI跟Unicode版本的函式

第一个简单的转换范例:

function TGDIWindow.GetTitle: String;
var
 l: Integer;
begin
   l := Windows.GetWindowTextLength(Handle);
   SetLength(Result, l);
   Windows.GetWindowText(Handle, @Result[1], l);
end;

必须转换为:

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;

Roadmap 未来发展计划

在相容于Unicode的延伸计划中,什么是必须要作的:

  • TForm, TButton, TLabel
  • 大多数的控制项
  • 选单元件
  • LCLIntf.ExtTextOut 以及大多数和字串相关的WinAPI
  • 以TStrings为基础的控制项. 例如: TComboBox, TListBox等等
  • SynEdit 显示与输入UTF-8字元必须正确

要支援Unicode的话,目前已知的问题:

  • SynEdit不支援由右到左显示的字元(RTL)
  • Is OpenFileDialogCallBack tested with selection large numbers of files?
    • Is this problem unicode specific? I think it's a generic problem. --Sekelsenmat 13:40, 14 February 2008 (CET)
      • Maybe. I know I tested it with large number of files before the Unicode version was added. If it is a generic problem, then the the non-Unicode version got broken, when the Unicode version was added. Vincent 21:45, 15 February 2008 (CET)
  • class function TWin32WSSelectDirectoryDialog.CreateHandle: Title, FileName and InitialDir should be made Unicode aware.
  • 在编辑器上面双击非ANSI编码的字元时,被双击的字不会被选取,反而是被双击的字左方的其他字会被选取起来
  • 复制贴上Unicode字元的时候,如果当时的视窗环境不是使用Unicode为编码字元的话,复制贴上的动作会不正确
  • MessageBox上面的按键文字就算已经翻译完成,也无法正确显示Unicode字元。这已经在IDE上面测试过了,不过也可能单纯的就只是IDE的问题而已。
  • 在Project option设定应用程式Title的时候,如果设定为Unicode的字串,显示可能不正确。

在重新检视程式码之后,下列的问题需要进行测试,因为这些程式码似乎无法相容于Unicode:

  • 在PrepareCreateWindow (Win32WSControls这个单元档里面) 的这行程式:
StrCaption := PChar(AWinControl.Caption);
  • (还有一些问题是没有被确认的,如果经过确认的话,就会一道列在上述的清单里面)


荧幕截图

Lazarus Unicode Test.png

额外参考

  • UTF-8 - Description of UTF-8 strings