Difference between revisions of "LCL Unicode Support/ja"

From Lazarus wiki
Jump to navigationJump to search
m (Fixed syntax highlighting)
 
(13 intermediate revisions by 4 users not shown)
Line 5: Line 5:
 
== イントロダクション ==
 
== イントロダクション ==
  
Lazarus は、バージョン0.9.25で、Gtk1 を除くすべてのプラットフォームにおいてユニコードに完全対応しました。このページでは Lazarus ユーザのために、ロードマップ、基本的なコンセプト及び実装における詳細を記述します。
+
Lazarus は、バージョン0.9.25で、Gtk1 を除くすべてのプラットフォームにおいてユニコードに完全対応しました。 このページは、FPC 2.6.4 を使用している Lazarus バージョン1.4 までについての内容です。
 +
 
 +
Lazarus は バージョン2.0 から、FPC 3.0+ の機能を用いたユニコードへの対応の改善がなされています。
 +
その詳細は、右のリンク先を見てください。[[Unicode Support in Lazarus/ja]]
  
 
== ユーザー向けの解説 ==
 
== ユーザー向けの解説 ==
  
ユニコードに対応したウイジェットセットであっても、すべてがユニコードであるわけではない、ということを知っておくことが重要です。
+
Lazurus がユニコードに対応したウイジェットセットを持つといっても、すべてがユニコードに対応しているわけではないことに注意して下さい。
FreePascal Runtime LibraryとFreePascal FCL LibraryはANSIが前提となっています。異なるエンコーディングのライブラリの間で期待される正しい変換をおこなうのは、開発者の責任です。
+
開発者は、使用する文字列のエンコーディングを知り、異なるエンコーディングを用いるライブラリに対しても正しい変換を行う必要があります。
 +
 
 +
エンコーディングは普通ライブラリ(例えば、DLL や Lazarus のパッケージなど)ごとに定まっています。 各ライブラリは、1種類のエンコーディングに対応しており、大抵は unicode ( Lazarus では UTF-8 ) か、 ANSI (正確にはシステムのエンコーディングであり、 UTF-8 の可能性もあります) のいずれかです。 FPC のバージョンが2.6より古い場合、 RTL と FCL は  ANSI エンコーディングが前提になっています。
 +
 
 +
Unicode と ANSI 間の変換は、以下の関数でできます。
 +
* FPC の System ユニットの'''UTF8ToAnsi'''関数 と '''AnsiToUTF8''' 関数
 +
* Lazarus の FileUtil ユニットの'''UTF8ToSys'''関数と'''SysToUTF8''' 関数
  
大抵、ライブラリ毎にエンコーディングが決められています。それぞれのライブラリは、1つのエンコーディング上での動作を期待され、それは、大抵はunicode(LazarusではUTF-8)か、Ansi(システムのエンコーディング)のいずれかです。FPC 2.4のRTLとFCLでは Ansi文字列です。FPC 2.5.xも今のところはAnsi文字列です。
+
後者がよりスマート(早い)ですが、プログラムにいくつかのコードを書く必要があるかもしれません。
  
unicodeとansiの変換は、SystemユニットのUTF8ToAnsi関数とAnsiToUTF8関数、あるいは、FileUtilユニットのUTF8ToSys関数とSysToUTF8関数でおこなうことができます。
+
=== FPC は ユニコードis not Unicode aware===
あとの2つはよりスマートですが、あなたのプログラムにいくつかのコードが必要になるかもしれません。
+
Free Pascal Runtime Library (RTL) と 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).
 +
 
 +
There are significant improvements to development branches of FPC 2.7.1 with regard to Strings.  See RawByteString and UTF8String in [[FPC Unicode support]].
 +
 
 +
=== ANSI と ユニコード間の変換 ===
 +
{{Note|AnsiToUTF8 と UTF8ToAnsi は Linux, BSD そして Mac OS X においてはrequire a widestring managerを必要とします。 代わりに、 SysToUTF8 と UTF8ToSys 関数(FileUtil ユニットの) 、または add the widestring manager by adding cwstring as one of the first units to your program's uses section.}}
  
 
例:
 
例:
  
TEditからの文字列をRTLのファイル関数の引数として与えたい、としましょう。:
+
TEdit からの文字列を RTL のファイル関数の引数として与えたい、としましょう。:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
var
 
var
   MyString: string; // utf-8エンコード
+
   MyString: string; // utf-8 エンコード
 
begin
 
begin
 
   MyString := MyTEdit.Text;
 
   MyString := MyTEdit.Text;
 
   SomeRTLRoutine(UTF8ToAnsi(MyString));
 
   SomeRTLRoutine(UTF8ToAnsi(MyString));
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
 
逆ならば、
 
逆ならば、
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
var
 
var
   MyString: string; // ansiエンコード
+
   MyString: string; // ANSI エンコード
 
begin
 
begin
 
   MyString := SomeRTLRoutine;
 
   MyString := SomeRTLRoutine;
 
   MyTEdit.Text := AnsiToUTF8(MyString);
 
   MyTEdit.Text := AnsiToUTF8(MyString);
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
 
'''重要''': UTF8ToAnsi はUTF8文字列が正しくないキャラクタを含むとき、空の文字列を返します。
 
'''重要''': UTF8ToAnsi はUTF8文字列が正しくないキャラクタを含むとき、空の文字列を返します。
Line 57: Line 71:
 
Due to the special nature of UTF8 you can simply use the normal string functions:
 
Due to the special nature of UTF8 you can simply use the normal string functions:
  
<Delphi>procedure Where(SearchFor, aText: string);
+
<syntaxhighlight lang=pascal>procedure Where(SearchFor, aText: string);
 
var
 
var
 
   BytePos: LongInt;
 
   BytePos: LongInt;
Line 66: Line 80:
 
   writeln('The substring "',SearchFor,'" is in the text "',aText,'"',
 
   writeln('The substring "',SearchFor,'" is in the text "',aText,'"',
 
     ' at byte position ',BytePos,' and at character position ',CharacterPos);
 
     ' at byte position ',BytePos,' and at character position ',CharacterPos);
end;</Delphi>
+
end;</syntaxhighlight>
  
  
Line 77: Line 91:
 
The following demonstrates how to iterate the 32bit unicode value of each character in an UTF8 string:
 
The following demonstrates how to iterate the 32bit unicode value of each character in an UTF8 string:
  
<Delphi>uses LCLProc;
+
<syntaxhighlight lang=pascal>uses LCLProc;
 
...
 
...
 
procedure IterateUTF8Characters(const AnUTF8String: string);
 
procedure IterateUTF8Characters(const AnUTF8String: string);
Line 91: Line 105:
 
     inc(p,CharLen);
 
     inc(p,CharLen);
 
   until (CharLen=0) or (unicode=0);
 
   until (CharLen=0) or (unicode=0);
end;</Delphi>
+
end;</syntaxhighlight>
  
 
===ディレクトリやファイル名の扱い===
 
===ディレクトリやファイル名の扱い===
  
Lazarusのコントロールや関数はファイル名とディレクトリ名であっても、utf-8エンコーディングとして取り扱いますが、RTLはansi文字列として扱います。
+
Lazarus のコントロールや関数はファイル名とディレクトリ名であっても、 utf-8 エンコーディングとして取り扱いますが、 RTL は ANSI 文字列として扱います。
  
たとえば、TFileListBoxのDirectoryプロパティにカレントディレクトリをセットするボタンがあるとしましょう。
+
たとえば、 TFileListBox の Directory プロパティにカレントディレクトリをセットするボタンがあるとしましょう。
RTL関数 [[doc:rtl/sysutils/getcurrentdir.html|GetCurrentDir]] はansiエンコーディングの文字列を返します。unicodeではありませんので、変換が必要です。
+
RTL の関数 [[doc:rtl/sysutils/getcurrentdir.html|GetCurrentDir]] は ANSI エンコーディングの文字列を返します。 ユニコードではないので、変換が必要です。
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
procedure TForm1.Button1Click(Sender: TObject);
 
procedure TForm1.Button1Click(Sender: TObject);
 
begin
 
begin
Line 107: Line 121:
 
   FileListBox1.Directory:=GetCurrentDirUTF8;
 
   FileListBox1.Directory:=GetCurrentDirUTF8;
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
FileUtilユニットはUTF-8文字列による共通ファイル関数を定義しています。
+
FileUtil ユニットは UTF-8 文字列による共通ファイル関数を定義しています。
  
<Delphi>
+
<syntaxhighlight lang=pascal>
 
// basic functions similar to the RTL but working with UTF-8 instead of the
 
// basic functions similar to the RTL but working with UTF-8 instead of the
 
// system encoding
 
// system encoding
Line 153: Line 167:
 
function GetEnvironmentVariableUTF8(const EnvVar: String): String;
 
function GetEnvironmentVariableUTF8(const EnvVar: String): String;
 
function GetAppConfigDirUTF8(Global: Boolean): string;
 
function GetAppConfigDirUTF8(Global: Boolean): string;
</Delphi>
+
</syntaxhighlight>
  
 
====Mac OS X====
 
====Mac OS X====
Line 163: Line 177:
 
これはすなわち、単にエンコードのバイト列の一致では判断できない、ということを意味しています。コード上では次の点に注意します。
 
これはすなわち、単にエンコードのバイト列の一致では判断できない、ということを意味しています。コード上では次の点に注意します。
  
<Delphi>
+
<syntaxhighlight lang=pascal>
 
if Filename1=Filename2 then ... // これは充分ではありません。
 
if Filename1=Filename2 then ... // これは充分ではありません。
 
if AnsiCompareFileName(Filename1,Filename2)=0 then ... // cwstringを使わない環境で fpc 2.2.2では上手く動きません。
 
if AnsiCompareFileName(Filename1,Filename2)=0 then ... // cwstringを使わない環境で fpc 2.2.2では上手く動きません。
 
if CompareFilenames(Filename1,Filename2)=0 then ... // FileUtilやFileProcsを使う場合、これはいつでも上手く行きます。  
 
if CompareFilenames(Filename1,Filename2)=0 then ... // FileUtilやFileProcsを使う場合、これはいつでも上手く行きます。  
</Delphi>
+
</syntaxhighlight>
  
 
==UTF8 and source files - the missing BOM==
 
==UTF8 and source files - the missing BOM==
Line 177: Line 191:
 
For example:
 
For example:
  
<Delphi>Button1.Caption := 'Über';</Delphi>
+
<syntaxhighlight lang=pascal>Button1.Caption := 'Über';</syntaxhighlight>
  
 
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.  
 
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
+
<syntaxhighlight lang=pascal>// source file saved as UTF without BOM
 
if FileExists('Über.txt') then ; // wrong, because FileExists expects system encoding
 
if FileExists('Über.txt') then ; // wrong, because FileExists expects system encoding
if FileExistsUTF8('Über.txt') then ; // correct</Delphi>
+
if FileExistsUTF8('Über.txt') then ; // correct</syntaxhighlight>
  
 
==Widestrings and Ansistrings==
 
==Widestrings and Ansistrings==
Line 189: Line 203:
 
When passing Ansistrings to Widestrings you have to convert the encoding.
 
When passing Ansistrings to Widestrings you have to convert the encoding.
  
<Delphi>var  
+
<syntaxhighlight lang=pascal>var  
 
   w: widestring;
 
   w: widestring;
 
begin
 
begin
Line 195: Line 209:
 
   w:=UTF8ToUTF16('Über'); // correct
 
   w:=UTF8ToUTF16('Über'); // correct
 
   Button1.Caption:=UTF16ToUTF8(w);
 
   Button1.Caption:=UTF16ToUTF8(w);
end;</Delphi>
+
end;</syntaxhighlight>
  
 
==Source file with UTF8 BOM==
 
==Source file with UTF8 BOM==
Line 204: Line 218:
 
===Windows環境での東アジア言語===
 
===Windows環境での東アジア言語===
  
Windows XPのユーザーインターフェース用のデフォルトフォント(Tahoma)は、アラビア語、ロシア語、西欧言語などのいくつかの言語で正しく表示がおこなえますが、中国、韓国、日本などの東アジア諸国の言語では正しく表示できません。
+
WindowsXP のユーザーインターフェース用のデフォルトフォント(Tahoma)は、アラビア語、ロシア語、西欧言語などのいくつかの言語で正しく表示がおこなえますが、中国、韓国、日本などの東アジア諸国の言語では正しく表示できません。
  
 
コントロールパネルで、地域の設定を選び、言語タブをクリックして、EastAsiaLanguageパックをインストールし、それらの言語の標準のユーザーインターフェースフォントを正しくつかいましょう。
 
コントロールパネルで、地域の設定を選び、言語タブをクリックして、EastAsiaLanguageパックをインストールし、それらの言語の標準のユーザーインターフェースフォントを正しくつかいましょう。
Line 499: Line 513:
  
 
== 日本語版特別情報 ==
 
== 日本語版特別情報 ==
* 現在 TStringListUTF8 や TFileStreamUTF8 などの実装が進められています。興味のある人は、lcl\lazutf8classes.pas や components\lazutils\lazutf8.pas を参照してください。
+
* 現在 TStringListUTF8 や TFileStreamUTF8 などの実装が進められています。興味のある人は、components\lazutils\lazutf8classes.pas や components\lazutils\lazutf8.pas を参照してください。
 
ただ、現実装にはやや不安な点があることも確かですので、ここにいくつか情報をあげておきます。
 
ただ、現実装にはやや不安な点があることも確かですので、ここにいくつか情報をあげておきます。
  
 
TStringListUTF8 を大文字小文字を区別しないでソートしたときの実装がやや不安定です。そこで、次のような TStringListUTF8_mod クラスを作成して使用するとよいかもしれません。
 
TStringListUTF8 を大文字小文字を区別しないでソートしたときの実装がやや不安定です。そこで、次のような TStringListUTF8_mod クラスを作成して使用するとよいかもしれません。
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TStringListUTF8_mod = class(TStringListUTF8)
 
   TStringListUTF8_mod = class(TStringListUTF8)
Line 519: Line 533:
 
     Result:= WideCompareText(UTF8Decode(s1), UTF8Decode(s2));
 
     Result:= WideCompareText(UTF8Decode(s1), UTF8Decode(s2));
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
 
こちらの方が信頼性が高い可能性があります。特に Windows では API で処理するので、速度も速い可能性もあります(実装では、UTF8LowerCase を実行後に CompareStr を実行しているだけですので。速度的に改善すべき余地があると思うのですが外国の開発者の理解を得るのは容易ではないです)。
 
こちらの方が信頼性が高い可能性があります。特に Windows では API で処理するので、速度も速い可能性もあります(実装では、UTF8LowerCase を実行後に CompareStr を実行しているだけですので。速度的に改善すべき余地があると思うのですが外国の開発者の理解を得るのは容易ではないです)。
 +
 +
Windows 限定になりますが、次のコードも試す価値があります。
 +
<syntaxhighlight lang=pascal>
 +
uses
 +
  lazutf8, Windows;
 +
 +
type
 +
  TStringListUTF8_mod = class(TStringListUTF8)
 +
  protected
 +
    function DoCompareText(const s1,s2 : string) : PtrInt; override;
 +
  public
 +
  end;
 +
 +
{
 +
Copyright(C)malcome Lazarusユーザーの方はこのコードを自由に使用して構いません。
 +
}
 +
function _UTF8CompareText(const S1, S2: utf8string): Integer;
 +
var
 +
  l1, l2: Integer;
 +
  c1, c2: LongWord;
 +
  p1, p2: PChar;
 +
begin
 +
  p1 := @S1[1];
 +
  p2 := @S2[1];
 +
  if (p1 <> nil) and (p2 <> nil) then begin
 +
    while (p1^ <> #0) and (p2^ <> #0) do begin
 +
      c1:= UTF8CharacterToUnicode(p1, l1);
 +
      c2:= UTF8CharacterToUnicode(p2, l2);
 +
      if (c1 = 0) or (c2 = 0) then Break;
 +
      if c1 <> c2 then begin
 +
        c1:= LongWord(Windows.CharLowerW(PWideChar(c1)));
 +
        c2:= LongWord(Windows.CharLowerW(PWideChar(c2)));
 +
        if c1 <> c2 then Break;
 +
      end;
 +
      Inc(p1, l1); Inc(p2, l2);
 +
    end;
 +
    if (p1^ = #0) or (p2^ = #0) then
 +
      Result := Byte(p1^) - Byte(p2^)
 +
    else
 +
      Result := c1 - c2
 +
  end else
 +
    Result := p1 - p2;
 +
end;
 +
 +
function TStringListUTF8_mod.DoCompareText(const s1, s2: string): PtrInt;
 +
begin
 +
  if CaseSensitive then
 +
    Result:= CompareStr(s1, s2)
 +
  else
 +
    Result:= _UTF8CompareText(s1, s2);
 +
end;
 +
</syntaxhighlight>
 +
 +
この方が、文字列すべてを操作対象としない分だけ速度が上がり、文字列リストが大きくなれば大きくなるほど、オリジナル版と顕著な速度差が生じると思われます。UTF8 を一度 UTF16 に変換しなくてもよいようにすればさらに速度が上がるとともに、Windows 以外の OS にも対応できるようになりますが、現時点ではソースが長くなりすぎますのでここには掲載できません。
  
 
さらに、アプリケーションを日本や英語圏など半角アルファベット以外の文字の大文字小文字についてはさほど気にしない国でしか使用しないのであるならば、
 
さらに、アプリケーションを日本や英語圏など半角アルファベット以外の文字の大文字小文字についてはさほど気にしない国でしか使用しないのであるならば、
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
type
 
type
 
   TStringListUTF8_mod = class(TStringListUTF8)
 
   TStringListUTF8_mod = class(TStringListUTF8)
Line 540: Line 608:
 
     Result:= CompareText(s1, s2);
 
     Result:= CompareText(s1, s2);
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
 
とすることでかなり高速化できます(漢字などのUTF8コードがASCIIコードに重なることがないため、CompareText でもうまく処理できるため)。
 
とすることでかなり高速化できます(漢字などのUTF8コードがASCIIコードに重なることがないため、CompareText でもうまく処理できるため)。
  
 
関連しますが、lazutf8.pas の UTF8LowerCase 関数、UTF8UpperCase 関数も、
 
関連しますが、lazutf8.pas の UTF8LowerCase 関数、UTF8UpperCase 関数も、
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
function UTF8LowerCase(const s: String): String;
 
function UTF8LowerCase(const s: String): String;
 
begin
 
begin
Line 555: Line 623:
 
   Result := UTF8Encode(WideUpperCase(UTF8Decode(s)));
 
   Result := UTF8Encode(WideUpperCase(UTF8Decode(s)));
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
 
の方(LCLProc にもともとあったコードの方)が信頼性も速度も上かもしれません。仮にクライアントから問題を報告されても、「Windows API の仕様です」と答えられるというのもメリットかもしれません。
 
の方(LCLProc にもともとあったコードの方)が信頼性も速度も上かもしれません。仮にクライアントから問題を報告されても、「Windows API の仕様です」と答えられるというのもメリットかもしれません。
  
  
* Windowsにおいて、TPicture.LoadFromFile のように、内部で UTF8ToSys 関数をコールすることでユニコード対応を謳っているだけのものには、注意が必要です。Ansiに変換できない文字コードがUTF8に含まれていた場合、うまく処理できないからです。ParamStrUTF8 のように内部で SysToUTF8を使用しているでだけなのに、ユニコード対応を謳っているものも同様です。徐々に改善されてきているみたいですが、外国の開発者達の理解を得にくい問題ですので、アプリケーション作成の際に注意して対応してください。
+
* Windowsにおいて、TPicture.LoadFromFile のように、内部で UTF8ToSys 関数をコールすることでユニコード対応を謳っているだけのものには、注意が必要です。Ansiに変換できない文字コードがUTF8に含まれていた場合、うまく処理できないからです(たとえば平仮名とハングルを混ぜたファイル名を使った場合や旧漢字体を使った場合など)。ParamStrUTF8 のように内部で SysToUTF8を使用しているでだけなのに、ユニコード対応を謳っているものも同様です。徐々に改善されてきているみたいですが、外国の開発者達の理解を得にくい問題ですので、アプリケーション作成の際に注意して対応してください。
  
 
== 参考 ==
 
== 参考 ==
  
 
* [[UTF-8]] - UTF-8文字列についての記述
 
* [[UTF-8]] - UTF-8文字列についての記述

Latest revision as of 01:20, 19 February 2020

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

日本語版メニュー
メインページ - Lazarus Documentation日本語版 - 翻訳ノート - 日本語障害情報

イントロダクション

Lazarus は、バージョン0.9.25で、Gtk1 を除くすべてのプラットフォームにおいてユニコードに完全対応しました。 このページは、FPC 2.6.4 を使用している Lazarus バージョン1.4 までについての内容です。

Lazarus は バージョン2.0 から、FPC 3.0+ の機能を用いたユニコードへの対応の改善がなされています。 その詳細は、右のリンク先を見てください。Unicode Support in Lazarus/ja

ユーザー向けの解説

Lazurus がユニコードに対応したウイジェットセットを持つといっても、すべてがユニコードに対応しているわけではないことに注意して下さい。 開発者は、使用する文字列のエンコーディングを知り、異なるエンコーディングを用いるライブラリに対しても正しい変換を行う必要があります。

エンコーディングは普通ライブラリ(例えば、DLL や Lazarus のパッケージなど)ごとに定まっています。 各ライブラリは、1種類のエンコーディングに対応しており、大抵は unicode ( Lazarus では UTF-8 ) か、 ANSI (正確にはシステムのエンコーディングであり、 UTF-8 の可能性もあります) のいずれかです。 FPC のバージョンが2.6より古い場合、 RTL と FCL は ANSI エンコーディングが前提になっています。

Unicode と ANSI 間の変換は、以下の関数でできます。

  • FPC の System ユニットのUTF8ToAnsi関数 と AnsiToUTF8 関数
  • Lazarus の FileUtil ユニットのUTF8ToSys関数とSysToUTF8 関数

後者がよりスマート(早い)ですが、プログラムにいくつかのコードを書く必要があるかもしれません。

FPC は ユニコードis not Unicode aware

Free Pascal Runtime Library (RTL) と 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).

There are significant improvements to development branches of FPC 2.7.1 with regard to Strings. See RawByteString and UTF8String in FPC Unicode support.

ANSI と ユニコード間の変換

Light bulb  Note: AnsiToUTF8 と UTF8ToAnsi は Linux, BSD そして Mac OS X においてはrequire a widestring managerを必要とします。 代わりに、 SysToUTF8 と UTF8ToSys 関数(FileUtil ユニットの) 、または add the widestring manager by adding cwstring as one of the first units to your program's uses section.

例:

TEdit からの文字列を RTL のファイル関数の引数として与えたい、としましょう。:

var
  MyString: string; // utf-8 エンコード
begin
  MyString := MyTEdit.Text;
  SomeRTLRoutine(UTF8ToAnsi(MyString));
end;

逆ならば、

var
  MyString: string; // ANSI エンコード
begin
  MyString := SomeRTLRoutine;
  MyTEdit.Text := AnsiToUTF8(MyString);
end;

重要: UTF8ToAnsi はUTF8文字列が正しくないキャラクタを含むとき、空の文字列を返します。

重要: AnsiToUTF8 と UTF8ToAnsi はLinux、BSD、Mac OS X環境では、widestringマネージャを必要とします。 (FileUtilユニットの)SysToUTF8 や UTF8ToSys 関数や、プログラムのusesセクションの最初のユニットにcwstringを加えることで、widestringマネージャを使うことができます。


Dealing with UTF8 strings and characters

If you want to iterate over the characters of an UTF8 string, there are basically two ways.

  • iterate over the bytes - useful for searching a substring or when looking only at the ASCII characters of the UTF8 string. For example when parsing xml files.
  • iterate over the characters - useful for graphical components like synedit. For example when you want to know the third printed character on the screen.

Searching a substring

Due to the special nature of UTF8 you can simply use the normal string functions:

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;


Accessing an UTF8 string as array

An array consists of elements with the same size. An UTF8 character can have 1 to 4 bytes. So you have to convert the UTF8 string to either an array of unicode values (longwords) or to an array of PChar with a pointer to each character.

Iterating over characters using UTF8CharacterToUnicode

The following demonstrates how to iterate the 32bit unicode value of each character in an UTF8 string:

uses LCLProc;
...
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;

ディレクトリやファイル名の扱い

Lazarus のコントロールや関数はファイル名とディレクトリ名であっても、 utf-8 エンコーディングとして取り扱いますが、 RTL は ANSI 文字列として扱います。

たとえば、 TFileListBox の Directory プロパティにカレントディレクトリをセットするボタンがあるとしましょう。 RTL の関数 GetCurrentDir は ANSI エンコーディングの文字列を返します。 ユニコードではないので、変換が必要です。

procedure TForm1.Button1Click(Sender: TObject);
begin
  FileListBox1.Directory:=SysToUTF8(GetCurrentDir);
  // または、FileUtilユニットの関数を利用できます。
  FileListBox1.Directory:=GetCurrentDirUTF8;
end;

FileUtil ユニットは UTF-8 文字列による共通ファイル関数を定義しています。

// 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, MacOSX
// 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はファイル名を(1つのファイル名になるように)正規化します。 たとえば、'ä.txt'というファイル名をユニコードでエンコードすると(訳注 aのウムラウトの部分のみで)「#$C3#$A4 」と 「'a'#$CC#$88」の、2つの異なる順序でエンコードできます。 Linux とBSDでは両方のどちらのエンコードでも作ることができます。しかしながらOS Xでは自動的に3バイトのシーケンスで作ります。

これはすなわち、単にエンコードのバイト列の一致では判断できない、ということを意味しています。コード上では次の点に注意します。

if Filename1=Filename2 then ... // これは充分ではありません。
if AnsiCompareFileName(Filename1,Filename2)=0 then ... // cwstringを使わない環境で fpc 2.2.2では上手く動きません。
if CompareFilenames(Filename1,Filename2)=0 then ... // FileUtilやFileProcsを使う場合、これはいつでも上手く行きます。

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:

Button1.Caption := 'Über';

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.

// 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 and Ansistrings

When passing Ansistrings to Widestrings you have to convert the encoding.

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

Source file with UTF8 BOM

If you do a lot of widestring conversions in a unit, the code might become more readable saving the source as UTF8 with BOM. The compiler allows one encoding per unit.


Windows環境での東アジア言語

WindowsXP のユーザーインターフェース用のデフォルトフォント(Tahoma)は、アラビア語、ロシア語、西欧言語などのいくつかの言語で正しく表示がおこなえますが、中国、韓国、日本などの東アジア諸国の言語では正しく表示できません。

コントロールパネルで、地域の設定を選び、言語タブをクリックして、EastAsiaLanguageパックをインストールし、それらの言語の標準のユーザーインターフェースフォントを正しくつかいましょう。 明らかにWindowsXPではこれらの言語がすでにインストールされている言語パックに含まれています。より詳しい情報はこちらをどうぞ。

実装のガイドライン

要求事項

Lazarusの精神は、"Write once, Compile everywhere."です。 これは、理想的には、ユニコードを使うアプリケーションでは、ターゲットにむけた条件定義などがなく、1つのユニコードをサポートするソースコードで、すべてのターゲットのプログラムを生成できる、ということです。

LCLの"interface"の部分はターゲットプラットホームがユニコードをサポートするよう、ターゲットプラットホームのためのユニコードサポートを実装するべきです。そして、同時に、アプリケーションプログラマからは、プラットホームに依存している実装を隠蔽しなくてはなりません。

Lazarusに関して言えることは、アプリケーションコードとLCLの境界における内部的な文字列の通信は、LCLとWidgetsetsと同様に、古典的な(バイト並びの)stringで成り立っている、ということです。 論理的には、それらの保持には、UTF-8を使ってエンコードされているべきです。

Unicodeへの移行

LazarusではAnsiエンコーディングが、もっともよく利用されています。なぜなら、0.9.24までのGtk1とWin32のインターフェースでは、それがデフォルトだからです。With 0.9.25では全てのウィジェットセットがデフォルトで UTF-8 を利用します。gtk1 は例外で、UTF-8 をサポートするのはシステムの文字コードが UTF-8 である場合に限られ、しかも制限があります。そこで、インターフェースに直接文字列を送る(プログラムコードの中であれオブジェクトインスペクタの中であれ)タイプのアプリケーションでは、UTF-8への変換が必要になります。現在、私達はエンコーディングに関して、いくつかのウイジェットセットのグループをもっています。

  • ANSIエンコーディングのインターフェース: gtk1(ANSI用)
  • UTF-8エンコーディングのインターフェース: gtk1(UTF-8用), gtk2, qt, fpGUI, carbon, win32, wince, そのほかすべて

gtk1 は、ANSIとUTF-8の両方のグループであることに注意してください。理由は、Gtk1では、環境変数でそれらを決めることができるからです。

IDEは複数のエンコーディングを用いたファイルの読み込み、保存、編集を行えるよう拡張されました(ファイル毎にエンコーディングが決まります)。ファイルがどのエンコーディングによっているかを自動判定するルーチンが内蔵されており、ユーザがエンコーディングを変更することも常時可能です(Source Editor / Popup Menu / File Settings/ Encoding)。よって、古いファイルやプロジェクトをIDEで開き、エンコーディングを変更して保存することも可能です。

ロードマップ

今、ロードマップを作成して、実行に移す時です。私達は、ガイドラインをもっています。 次のような実現可能な計画をたてました。2つのグループに仕事を分ける計画です。 1つ目は、優先的に主要な柱となる仕事を、2番目はそれ以外の仕事を計画しています。

私達が、Lazarusはユニコードが使えるよ、といえる前に、すべての主要な仕事は完全に実装されていなくてはなりません。そして、そのことが、私達の努力の最も向かう方向です。

2番目の仕事は、あったらいいな、というものです。しかし、誰かがそれらのために奉仕をしない限り、実装されることはないでしょう。

1番目に重要な仕事

Win32 Widgetset に UTF-8をサポートさせる

ノート:このステップでは、私達はすべての32ビットWindowsのバージョンを同時にターゲットとしています。この努力でつくられるすべてのコードは、この作業によって発生するバグを避けられるよう、現在のwin32インターフェースとIFDEFによって分離されるでしょう。 移行する時期になれば、IFDEFはユニコードだけが残るように除去されます。

状態:完全に実装されました。


Gtk 2 キーボード関数をUTF-8で動くように更新する

ノート:

状態:ほとんど完了しました。いくつかのgtk2上での編集前の機能がまだサポートされていません。しかし、どの言語がそれを必要とするのかは分かりません。(訳注:漢字入力のことかも?)

Lazarus IDE が正しくWin32 ウイジェットセットで動いてUTF-8をサポートするようにする

ノート:

状態:完了しました。文字コードマップで255キャラクタしか表示できないという制限がありますが、最近のOSの機能ではユニコードでの文字コードマップを提供しています。


Lazarus IDE が正しくGtk2 ウイジェットセットで動いてUTF-8をサポートするようにする

ノート: 状態:完了しました。 gtk2 intfにバグがありますが、utf-8とは関係がありません。

2番目に重要な仕事

Windows CE ウイジェットセットをUTF-8を使うように更新する


ノート:文字列の変換ルーチンは winceproc.ppに集約されています。多くのテストが必要です。

状態:完了しました。


Gtk 1 キーボード関数をUTF-8で動くように更新する

Notes:

状態:まだ実装されていない

syneditでRTL(右から左へ)の記法

Notes: RTLはたとえば、アラビア語のように右から左への記法を意味します。

状態:まだ実装されていない

ユニコードの要点

ユニコード標準は、文字を0から10FFFF(h)までの整数に割り当てています。それぞれの割り当ては、コードポイントと呼ばれています。いいかえると、ユニコード文字列は、原理的には、U+000000 から U+10FFFF(0から1 114 111)までのコードポイントで定義されます。

一意のバイト順になるように、現在のユニコードのコードポイントには、3つの枠組みがあります。 これらの枠組みには、UTF-8,UTF-16,UTF-32とよばれるユニコード変換形式(訳注:Unicode transformation formats=UTF)があります。 これらの間の相互変換は可能です。

基本となる要素を示します。

                             UTF-8 UTF-16 UTF-32
最小 code point [16進]    000000 000000 000000
最大 code point  [16進]   10FFFF 10FFFF 10FFFF
Code unit size [ビット数]      8     16     32
1文字の最小バイト数              1      2      4
1文字の最大バイト数              4      4      4

UTF-8 には、いくつかの重要で便利な特徴があります。 つまり、バイト順で解釈されるので、バイトのlo-やhi-オーダーのようなものは存在しません。

(訳注:バイトのオーダーについて、8ビットを超える複数のバイトで表現する場合、バイト順をどう表現/解釈するか、プロセッサによってことなっています。CPUやハードウエア系の用語ではビッグエンディアン、リトルエンディアンとも呼ばれます。インテルのx86系プロセッサは、リトルエンディアン、PowerPCやMIPS系、Cellプロセッサはビッグエンディアンです。また、両方のエンディアンを扱えるバイエンディアンというプロセッサも存在します。ネットワーク上はビッグエンディアンが主流なため、インテルはバイトオーダーを高速にあわせるため、486以降に、BSWAP命令が追加されました。ソフトウエアでは、自由にメーカーが決めることができるため、どちらのフォーマットも存在しますが、Windowsで最初作られたソフトはリトルエンディアン、Macで最初作られたソフトはビッグエンディアンが多いようです。しかし、ユニコードのテキストファイルなどは、これらの区別をするためにBOMが設定されることがあります。)

Unicode文字のU+0000から+007F(ASCII)は、単に00hから7Fhになり、ASCIIと互換性があります。 これはすなわち、7bitのASCII文字セットしか含まないファイルや文字はASCIいとUTF-8は同じである、ということを意味します。 U+007F以上の文字は、複数のバイトの並びでエンコードされ、それぞれには、2つの最も意味のあるビットセットを持っています。(訳注:most significant bitは通常はMSB(最上位ビット)と略されます)

1文字のバイト列には、その中に他の文字の、より長いバイト列を含んでいます。 このことは、部分文字列の検索を容易にします。非アスキー文字をあらわすマルチバイトの最初のバイトは、必ずC0hからFDhの範囲にあり、これは、この文字のために、どれくらいのバイト長さがあるのか示しています。マルチバイト列における、全ての続くバイトは、80hからBFhにあります。このおかげで、再構成をしたり、堅牢なプログラムを作ることができます。

(訳注: 上記の訳の説明ではよく分からないと思いますが、デバッグの際などに知っておいたほうがいいと思うので説明すると、 utf-8形式のユニコード文字列のバイト表現は、

  • 最初のバイトが(00-7f)hは1バイト、
  • 最初のバイトが(c0-df)hの場合は2バイト、
  • 最初のバイトが(e0-ef)hの場合は3バイト、
  • 最初のバイトが(f0-f7)hの場合は4バイトで構成されています。

2文字目以降のバイトは、常に(80-bf)hであって、 JISやEUCに比べると判定などがしやすい、というを上の文章が言っている(再構成や、堅牢)のだろうと思います。

ちなみに、漢字の表現には3バイト、新拡張JISコード文字には、4バイトが必要。

日本語のテキストファイルはS-JISの1.5倍のサイズになってしまいますが、ソースコードなどは、ほとんど英文字が多いので、ファイルサイズはそれほど増えないでしょう。

訳注ここまで )


UTF-16は次のような特徴があります。: UTF-16はU+0000からU+d7ffまでの文字は1つの16bitワードを使い、その他のユニコード文字は2つの16ビットワードを使います。

最後に、UTF-32では、すべてのユニコード文字は1つの32ビットとして表現できます。

更なる情報は、こちらをみてください: Unicode FAQ - Basic questions, Unicode FAQ - UTF-8, UTF-16, UTF-32 & BOM, Wikipedia: UTF-8 [1]

Lazarusコンポーネントライブラリのアーキテクチャの要点

LCL(Lazarusコンポーネントライブラリ)は2つの部分からできています。

  1. DelphiのVCLのように、クラス階層を実装していて、ターゲットプラットホームに依存しない部分 
  2. "Interfaces" つまり、それぞれのターゲットのAPIをインターフェースするために実装している部分。

2つの部分の橋渡しの仕組みは、抽象クラスである TWidgetsetにあります。 それぞれのwidgetsetは、TWidgetsetから派生したクラスの中で実装されています。

Gtk1 widgetsetが最も古いものです。このwidgetsetにおいていは文字列のエンコードはLANG環境変数によって決定されます。このエンコードは、通常はisoのシングルバイトであるISO-8859-nが使われています。最近の多くのディストリビューション(たとえば、Mandriva 2007など)は、Gtk1をUTF-8として設定して出荷されています。ところが、LazarusのGtk1インターフェースは、キーボードの扱いでUTF-8を適切に扱っていません。この問題が最近大きくなり、Lazarusに対するクロスプラットホームでのユニコードサポートの要望が日増しにつよくなっています。

Gtk2 widgetsetはUTF-8エンコードでのみ動作し、UTF-8 に完全対応しています。

Lazarus 0.9.28 Windows と Windows CE インターフェースはユニコードに完全対応しています。

Qtインターフェースは、UTF-8として準備されています。Qtそのものは、UTF-16をネイティブなエンコードとして採用しています。LazarusのQtインターフェースはUTF-8をUTF-16に変換しています。

こちらも参照してください。: Internals of the LCL

win32インターフェースでユニコードを有効にする

方針

まず最初に、最も重要なこととして、Win32インターフェースのすべてのユニコードパッチは、これまでのANSIインターフェースを壊してしまわないように、IFDEF WindowsUnicodeSupportによって囲まれていなければなりません。これらの作業が安定してきてから、すべてのifdefが取り除かれて、ユニコード部分だけが残るようになります。この時点で、すべての既存のANSI文字を使うプログラムが、Unicodeへ移行できるようになるでしょう。

Win9xでのWindowsプラットホームは、ISOコードページ標準でなりたっており、Unicodeは部分的なサポートしかありません。WinNTとWindowsCEを起源とするWindowsプラットホームは、完全にUnicodeをサポートしています。Win9xとNTには、古いANSI文字を引数にとる*Aという関数と、Unicodeワイド文字列(つまり、UTF-16でエンコードされた文字列)を引数にとる*Wという関数の、2つの同じ機能をもったAPI関数を提供しています。Windows CEでは、ワイドAPI関数しか使いません。

Windows 9xに存在するワイド文字列関数

いくつかのWide系API関数はWindows9xにも存在します。ここに、そのような関数のリストを示します。: 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とWideのバージョンが必要な関数

最初の変換例:

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;

ロードマップ

Unicodeに対応し、動作しているもの

  • TForm, TButton, TLabel
  • Most controls
  • Menus
  • LCLIntf.ExtTextOut and most other text related winapis
  • TStrings based controls. Examples: TComboBox, TListBox, etc
  • SynEdit shows and can input UTF-8 characters correctly
  • Setting/Getting unicode strings to/from the ClipBoard
  • Setting the Application Title in the project options to (for example) 'Minha Aplicação'.
  • Double clicking words with non-ascii chars in the editor to select them

Unicode化における既知の問題

  • SynEdit does not support RTL (right to left)
  • 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.

Unicode化で起こりうる問題

Based on a code review, the following needs to be tested, because the code doesn't seem to be Unicode aware:

  • class procedure TWin32WSCustomComboBox.SetText
  • TWin32WSCustomTrayIcon.Show: ATrayIcon.Hint is not Unicode aware
  • TWin32WidgetSet.MessageBox doesn't call MessageBoxW.
  • TWin32WidgetSet.TextOut: Is Windows.TextOut supported on windows 9X?
  • MessageBox buttons don't show unicode correctly when they are translated. Tested on the IDE. Could be a problem on the IDE however.
    • Note: I couldn't reproduce using the portuguese translation --Sekelsenmat 22:20, 12 January 2008 (CET)
  • (list of unconfirmed problems, if confirmed can be moved to the list above)

スクリーンショット

Lazarus Unicode Test.png

日本語版特別情報

  • 現在 TStringListUTF8 や TFileStreamUTF8 などの実装が進められています。興味のある人は、components\lazutils\lazutf8classes.pas や components\lazutils\lazutf8.pas を参照してください。

ただ、現実装にはやや不安な点があることも確かですので、ここにいくつか情報をあげておきます。

TStringListUTF8 を大文字小文字を区別しないでソートしたときの実装がやや不安定です。そこで、次のような TStringListUTF8_mod クラスを作成して使用するとよいかもしれません。

type
  TStringListUTF8_mod = class(TStringListUTF8)
  protected
    function DoCompareText(const s1,s2 : string) : PtrInt; override;
  public
  end;

function TStringListUTF8_mod.DoCompareText(const s1, s2: string): PtrInt;
begin
  if CaseSensitive then
    Result:= CompareStr(s1, s2)
  else
    Result:= WideCompareText(UTF8Decode(s1), UTF8Decode(s2));
end;

こちらの方が信頼性が高い可能性があります。特に Windows では API で処理するので、速度も速い可能性もあります(実装では、UTF8LowerCase を実行後に CompareStr を実行しているだけですので。速度的に改善すべき余地があると思うのですが外国の開発者の理解を得るのは容易ではないです)。

Windows 限定になりますが、次のコードも試す価値があります。

uses
  lazutf8, Windows;

type
  TStringListUTF8_mod = class(TStringListUTF8)
  protected
    function DoCompareText(const s1,s2 : string) : PtrInt; override;
  public
  end;

{
 Copyright(C)malcome Lazarusユーザーの方はこのコードを自由に使用して構いません。
}
function _UTF8CompareText(const S1, S2: utf8string): Integer;
var
  l1, l2: Integer;
  c1, c2: LongWord;
  p1, p2: PChar;
begin
  p1 := @S1[1];
  p2 := @S2[1];
  if (p1 <> nil) and (p2 <> nil) then begin
    while (p1^ <> #0) and (p2^ <> #0) do begin
      c1:= UTF8CharacterToUnicode(p1, l1);
      c2:= UTF8CharacterToUnicode(p2, l2);
      if (c1 = 0) or (c2 = 0) then Break;
      if c1 <> c2 then begin
        c1:= LongWord(Windows.CharLowerW(PWideChar(c1)));
        c2:= LongWord(Windows.CharLowerW(PWideChar(c2)));
        if c1 <> c2 then Break;
      end;
      Inc(p1, l1); Inc(p2, l2);
    end;
    if (p1^ = #0) or (p2^ = #0) then
      Result := Byte(p1^) - Byte(p2^)
    else
      Result := c1 - c2
  end else
    Result := p1 - p2;
end;

function TStringListUTF8_mod.DoCompareText(const s1, s2: string): PtrInt;
begin
  if CaseSensitive then
    Result:= CompareStr(s1, s2)
  else
    Result:= _UTF8CompareText(s1, s2);
end;

この方が、文字列すべてを操作対象としない分だけ速度が上がり、文字列リストが大きくなれば大きくなるほど、オリジナル版と顕著な速度差が生じると思われます。UTF8 を一度 UTF16 に変換しなくてもよいようにすればさらに速度が上がるとともに、Windows 以外の OS にも対応できるようになりますが、現時点ではソースが長くなりすぎますのでここには掲載できません。

さらに、アプリケーションを日本や英語圏など半角アルファベット以外の文字の大文字小文字についてはさほど気にしない国でしか使用しないのであるならば、

type
  TStringListUTF8_mod = class(TStringListUTF8)
  protected
    function DoCompareText(const s1,s2 : string) : PtrInt; override;
  public
  end;

function TStringListUTF8_mod.DoCompareText(const s1, s2: string): PtrInt;
begin
  if CaseSensitive then
    Result:= CompareStr(s1, s2)
  else
    Result:= CompareText(s1, s2);
end;

とすることでかなり高速化できます(漢字などのUTF8コードがASCIIコードに重なることがないため、CompareText でもうまく処理できるため)。

関連しますが、lazutf8.pas の UTF8LowerCase 関数、UTF8UpperCase 関数も、

function UTF8LowerCase(const s: String): String;
begin
  Result := UTF8Encode(WideLowerCase(UTF8Decode(s)));
end;

function UTF8UpperCase(const s: String): String;
begin
  Result := UTF8Encode(WideUpperCase(UTF8Decode(s)));
end;

の方(LCLProc にもともとあったコードの方)が信頼性も速度も上かもしれません。仮にクライアントから問題を報告されても、「Windows API の仕様です」と答えられるというのもメリットかもしれません。


  • Windowsにおいて、TPicture.LoadFromFile のように、内部で UTF8ToSys 関数をコールすることでユニコード対応を謳っているだけのものには、注意が必要です。Ansiに変換できない文字コードがUTF8に含まれていた場合、うまく処理できないからです(たとえば平仮名とハングルを混ぜたファイル名を使った場合や旧漢字体を使った場合など)。ParamStrUTF8 のように内部で SysToUTF8を使用しているでだけなのに、ユニコード対応を謳っているものも同様です。徐々に改善されてきているみたいですが、外国の開発者達の理解を得にくい問題ですので、アプリケーション作成の際に注意して対応してください。

参考

  • UTF-8 - UTF-8文字列についての記述