Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2018年12月06日] [更新 2023年01月21日] Delphi 一般・その他
   
複数エレメント構成の文字 - サロゲートペア・結合文字・異体字
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
(一部は Windows 10.0.17134)
[整理番号 - 0730]







複数エレメント構成の文字


Delphi の String 型と PChar 型の文字列のコードページは UTF-16 です.また 1 文字の型である Char 型のコードページも UTF-16 です.UTF-16 では 2 バイト単位で文字データを扱います.

本ページで扱っているのは, 2 バイトのデータ (ワード値のデータ) を複数使用して 1 つの文字を表現する仕組みです.
文字を構成する値が 2 バイトではありませんから,正確には,この 2 バイトは文字そのものではなく,文字を構成するエレメント (要素) ということになります.文字列の長さを取得する Length 関数は,実際には文字列を構成するエレメント数の取得です.また,n 文字分領域を確保するという言い方は,n 個のエレメントを格納する領域を確保することを意味します.



Unicode に登録されている文字は,Unicodeコンソーシアムが公開しています.
以下のリンクの最初の PDF は,Unicode の文字のリストとなっています.2 番目は画数,部首順のリストです.3 番目は,漢字異体字データベース (PDF) ファイルのダウンロード用ページへのリンクです.






01_サロゲートペアの文字の例


サロゲートペアは,32 ビットの文字符号を 2 つのエレメント,つまり 2 バイトのコードポイント 2 つを使用して UTF-16 としての文字を表現する仕組みです.サロゲートとは代理とか代用という意味です.各々のエレメントに文字が割り当てられているわけではありません.文字コードという言葉を使用するならば,文字ではない文字コードということになります.

下に示す文字列はサロゲートペアを含む文字列の例です.
「𠮷野家吉」の最初の文字はサロゲートペアの文字で「ツチヨシ」と呼ばれ,部首は「つち」です.最後の「吉 (キチまたはヨシ)」 という文字の部首は「さむらい」です この 2 つの文字はは互いに異体字と言えます.



   𠮷野家吉

この「𠮷野家吉」の文字列のエレメントの構成は以下のようになっています.最初の「𠮷」は 2 つのエレメントで構成されていますが,後は全てエレメントが 1 つの文字です.



エレメントインデックス コードポイント 文字
1 U + D842 𠮷
2 U + DFB7
3 U + 91CE
4 U + 5BB6
5 U + 5409


以下はこの文字列「𠮷野家吉」の各々の文字がサロゲートペアであるかを判定するテストです.
VCL にもサロゲートペア判定の関数がありますが,ここでは MECSUtils の関数を使用しています.文字がサロゲートペアの文字であった場合は,その文字のデータを,別に用意した 4 バイトの文字列に格納して,その文字を表示します.



[備考 1]
「𠮷野家吉」の最初の文字が他の文字と字形が違うことがあります.これは代替えフォントを使用して描画されるためです.


図1
文字がサロゲートペアであるか判定するテスト

リスト1
文字がサロゲートペアであるか判定するテスト
implementation

uses
  MECSUtils;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  テスト用の文字列をセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := #$D842#$DFB7 + '野屋吉';
end;

//=============================================================================
//  サロゲートペアを含む文字列のテスト
//  MecsIsLeadElement,MecsIsTrailElement 関数を使用してサロゲートペアを検出 
//
//  このコードの実行には uses に MECSUtils が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LString  : string;
  LChar    : Char;
  LCharStr : string;
  LStrCnt  : Integer;
  LIndex   : Integer;
begin
  Memo1.Lines.Clear;

  LString := Edit1.Text;
  LStrCnt := 0;

  for LIndex := 1 to Length(LString) do begin
    LChar := LString[LIndex];
    if MecsIsLeadElement(LChar) then begin
      SetLength(LCharStr, 2);
      Move(LString[LIndex], LCharStr[1], SizeOf(Char) * 2);
      Memo1.Lines.Add(LCharStr);
      Memo1.Lines.Add('High Surrogate');
    end else
    if MecsIsTrailElement(LChar) then begin
      Memo1.Lines.Add('Low Surrogate');

      Inc(LStrCnt);
    end else begin
      Memo1.Lines.Add(LChar);
      Memo1.Lines.Add('Non Surrogate');

      Inc(LStrCnt);
    end;
  end;

  Memo1.Lines.Add('');
  Memo1.Lines.Add('エレメント : ' + IntToStr(Length(LString)));
  Memo1.Lines.Add('文字数 : ' + IntToStr(LStrCnt));
end;


テストで使用した文字「𠮷」のサロゲートペア U + D842, U + DFB7 は,Unicode の符号 U + 20BB7 のサロゲートです.
String 型の変数に,Unidoce の符号値 U + 20BB7 の文字表記である #$20BB7 を代入すると,自動的にサロゲートペアに変換することができます.その動作確認用のテストコードです.
MECSUtils の CodePointToUTF16 関数を使用すると,Unicode のコードポイントの値を String 型の文字にできます. 

サロゲートは,Unicode の符号位置 U + 10000 から U + 10FFFF の値を 2 つのワード値で表現します.上位ワードは U + D800 から U + DBFF の範囲の値,下位ワードは U + DC00 から U + DFFF の範囲の値になります.
上位ワードを high surrogate, 下位ワードを low surrogate と言っています.



図2
Unicode の符号 U + 20BB7 を文字として String 型の変数に代入
  • 自動的にサロゲートペアに変換される

リスト2
Unicode の 32 ビットの符号を文字として String 型の変数に代入してサロゲートペアに変換
//=============================================================================
//  Unicode (UTF-32) の文字符号の値ををサロゲートペアに分解
//  String 型 (UTF-16) の変数に代入すれば自動的に分解してくれる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LString : string;
  LCode   : Integer;
  LIndex  : Integer;
begin
  Memo1.Lines.Clear;

  LCode := $20BB7;
  Memo1.Lines.Add('$' + IntToHex(LCode, 8) + '  U+' + intToHex(LCode, 5));

  LString    := #$20BB7;
  Edit1.Text := LString;

  for LIndex := 1 to Length(LString) do begin
    LCode := Ord(LString[LIndex]);
    Memo1.Lines.Add('$' + IntToHex(LCode, 4));
  end;
end;


[備考 2]
UTF-8, UTF-32 にはサロゲートペアの概念はありません.UTF-8, UTF-32 には,サロゲートペアに相当する文字にも独立したコードポイントがあります.



1993年に UCS-2 が制定されました.UCS-2 は,2 バイトで文字を表します.世界で一般的に使用される文字,記号のほとんどが収録されています.UTF-16 は,UCS-2 で定義された文字は 2 バイト (16 ビット) を使用します.より多くの文字を扱うには 2 バイトでは十分ではありません.そこで UTF-16 では,更に 2 バイトを使用して,UCS-2 にはない文字を扱うようにしました.それがサロゲートペアです.

2022年9月13日リリースの Unicode 15.0.0 にば,149,186 の文字が登録されています.
以下は,今回使用した文字「𠮷」がある領域のサロゲートペアな文字です.「𠮷」は,ユニコードコンソーシアムが提供している PDF では 1697 ページにあります.
コードポイント $10000 以降がサロゲートペアな文字です.



16 進数 0 1 2 3 4 5 6 7 8 9 A B C D E F
20B00 𠬀 𠬁 𠬂 𠬃 𠬄 𠬅 𠬆 𠬇 𠬈 𠬉 𠬊 𠬋 𠬌 𠬍 𠬎 𠬏
20B10 𠬐 𠬑 𠬒 𠬓 𠬔 𠬕 𠬖 𠬗 𠬘 𠬙 𠬚 𠬛 𠬜 𠬝 𠬞 𠬟
20B20 𠬠 𠬡 𠬢 𠬣 𠬤 𠬥 𠬦 𠬧 𠬨 𠬩 𠬪 𠬫 𠬬 𠬭 𠬮 𠬯
20B30 𠬰 𠬱 𠬲 𠬳 𠬴 𠬵 𠬶 𠬷 𠬸 𠬹 𠬺 𠬻 𠬼 𠬽 𠬾 𠬿
20B40 𠭀 𠭁 𠭂 𠭃 𠭄 𠭅 𠭆 𠭇 𠭈 𠭉 𠭊 𠭋 𠭌 𠭍 𠭎 𠭏
20B50 𠭐 𠭑 𠭒 𠭓 𠭔 𠭕 𠭖 𠭗 𠭘 𠭙 𠭚 𠭛 𠭜 𠭝 𠭞 𠭟
20B60 𠭠 𠭡 𠭢 𠭣 𠭤 𠭥 𠭦 𠭧 𠭨 𠭩 𠭪 𠭫 𠭬 𠭭 𠭮 𠭯
20B70 𠭰 𠭱 𠭲 𠭳 𠭴 𠭵 𠭶 𠭷 𠭸 𠭹 𠭺 𠭻 𠭼 𠭽 𠭾 𠭿
20B80 𠮀 𠮁 𠮂 𠮃 𠮄 𠮅 𠮆 𠮇 𠮈 𠮉 𠮊 𠮋 𠮌 𠮍 𠮎 𠮏
20B90 𠮐 𠮑 𠮒 𠮓 𠮔 𠮕 𠮖 𠮗 𠮘 𠮙 𠮚 𠮛 𠮜 𠮝 𠮞 𠮟
20BA0 𠮠 𠮡 𠮢 𠮣 𠮤 𠮥 𠮦 𠮧 𠮨 𠮩 𠮪 𠮫 𠮬 𠮭 𠮮 𠮯
20BB0 𠮰 𠮱 𠮲 𠮳 𠮴 𠮵 𠮶 𠮷 𠮸 𠮹 𠮺 𠮻 𠮼 𠮽 𠮾 𠮿
20BC0 𠯀 𠯁 𠯂 𠯃 𠯄 𠯅 𠯆 𠯇 𠯈 𠯉 𠯊 𠯋 𠯌 𠯍 𠯎 𠯏
20BD0 𠯐 𠯑 𠯒 𠯓 𠯔 𠯕 𠯖 𠯗 𠯘 𠯙 𠯚 𠯛 𠯜 𠯝 𠯞 𠯟
20BE0 𠯠 𠯡 𠯢 𠯣 𠯤 𠯥 𠯦 𠯧 𠯨 𠯩 𠯪 𠯫 𠯬 𠯭 𠯮 𠯯
20BF0 𠯰 𠯱 𠯲 𠯳 𠯴 𠯵 𠯶 𠯷 𠯸 𠯹 𠯺 𠯻 𠯼 𠯽 𠯾 𠯿


MECSUtils の CodePointToUTF16 関数を使用すれば,コードポイントから文字を取得できます.2 バイト文字であれば Char 関数でも十分です.
以下のサンプルでは,単に文字を順番に表示しているだけですが,TStringGrid などのセルに文字を描画すれで,上のような表を作成することもできす.




図3
コードポイントの文字を取得

リスト3
コードポイントの文字を取得
implementation

uses
  MECSUtils;

{$R *.dfm}

//=============================================================================
//  コードポイントから文字を取得
//  MECSUtils の CodePointToUTF16 関数を使用する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBase : Integer;
  LRow  : Integer;
  LCol  : Integer;
  LCode : Integer;
  LText : string;
begin
  Memo1.Lines.Clear;
  Memo1.Lines.BeginUpdate;

  LBase := $2A60;
  for LRow := LBase to LBase + $F do begin
    for LCol := 0 to $F do begin
      LCode := (LRow shl 4) + LCol;

      LText := '$' + IntToHex(LCode, 5) + '  ' + CodePointToUTF16(LCode);
      Memo1.Lines.Add(Ltext);
    end;
  end;

  Memo1.Lines.EndUpdate;
end;




02_結合文字を使用する文字の例


次のように,ある文字に複数の文字を結合した形式の文字があります.この場合の結合文字はコードポイントのことですが,それらが単独の文字として表示できます.つまり,実際の文字が結合されて 1 つの文字になっています.この結合された文字のことを単に「結合文字」あるいは「結合文字列」と言うことがあります.
詳細はリンクの記事で確認してください.

    結合形式の文字 (結合文字列形式の文字) = 基底文字 + 結合文字 + 結合文字 + ...


  €ǖǖǖ

上の 3 つの文字は全く同じ字体の文字ですが,2 番目と 3 番目は結合形式の文字です.最初の文字は 1 つのエレメントだけで文字となっています.2 番目は 2 つのエレメントで 1 つの文字になっていて,3 番目は 3 つのエレメントで構成された文字です.
この例のように,結合形式の文字には 1 つのエレメントで表示可能な合成済みの文字としての符号 (コードポイント) が存在することがあります.

  €ǖ €ǖ= ü +̄ ǖ= u +̈ +̄

上の 3 つの文字のエレメントの構成は以下のようになっています.


エレメントインデックス コードポイント 文字
1 U + 01D6 ǖ
2 U + 00FC ǖ
3 U + 0304
4 U + 0075 ǖ
5 U + 0308
6 U + 0304


以下はこの文字列の各々の文字が結合文字であるかを判定するテストです.
ここでは MECSUtils の関数を使用しています.文字が結合形式の文字であった場合は,その文字のデータを,別に用意した String 型の文字列に格納して,その文字を表示します.



図4
結合文字であるかを判定するテスト

リスト4
結合文字であるかを判定するテスト
implementation

uses
  MECSUtils;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  テスト用の文字列をセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  // $0075 は単なるアルファベットの u
  Edit1.Text := #$01D6
              + #$00FC#$0304
              + #$0075#$0308#$0304 + 'ABC';
end;

//=============================================================================
//  結合文字の判定テスト
//  このコードの実行には uses に MECSUtils が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LString    : string;
  LCode      : Integer;
  LStrCnt    : Integer;
  LStrIndex  : Integer;
  LNextIndex : Integer;
  LEachStr   : string;
  LIndex     : Integer;
begin
  LString := Edit1.Text;

  Memo1.Lines.Clear;

  LStrCnt := 0;
  for LIndex := 1 to Length(LString) do begin
    // LIndex 番目のエレメントのコードポイントの値を取得
    LCode := Ord(LString[LIndex]);

    if MecsCombiningType(LCode) = ctBase then begin
      // 文字のインデックスと次の文字のエレメントインデックスを取得
      LStrIndex := MecsElementToCCSIndex(LString, LIndex);
      LNextIndex := MecsNextCCSIndex(LString, LIndex);

      // 現在のエレメントインデックス位置の文字を取得
      SetLength(LEachStr, LNextIndex - LIndex);
      Move(LString[LIndex], LEachStr[1], SizeOf(Char) * (LNextIndex - LIndex));

      // 文字を表示
      Memo1.Lines.Add(IntToStr(LStrIndex) + ' 番目の文字 : ' + LEachStr);

      // 文字数をインクリメント
      Inc(LStrCnt);
    end else begin
      // 結合文字の 2 番目以降のエレメントの場合
      // 本サンプルの場合は具体的な文字なので表示可能
    end;
  end;

  Memo1.Lines.Add('');
  Memo1.Lines.Add('エレメント : ' + IntToStr(Length(LString)));
  Memo1.Lines.Add('文字数 : ' + IntToStr(LStrCnt));
end;




03_異体字と異体字セレクタ ( IVS )


意味と読みが同じで字体が異な る文字を「異体字」と言います.
以下は異体字がある漢字の例です.これらはお互いに異体字ですが,Unicode では各々の漢字に対して異体字が登録されていいます.


  辺 邊 邉

異体字は,ある文字に異体字セレクタと呼ばれる符号を結合した形式です.この 異体字セレクタにはいくつかの種類があります.


    異体字 = 基底文字 + 異体字セレクタ

名称 異体字セレクタ 説明
Mongolian Free Variation Selector U + 180B ~ U + 180D
(3 個)
モンゴル文字専用
モンゴル自由字形選択子
Standardized Variation Sequence
略称 SVS
U + FE00 ~ U + FE0F
(16 個)
非漢字や CJK 互換漢字用
Ideographic Variation Sequence
略称 IVS
U + E0100 ~ U + E01EF
(240 個)
漢字専用
サロゲートペア
上位ワードは U + DB40 の固定値
下位ワードは U + DD00 ~ U + DDEF


同じ字体の異体字であっても,1 つのエレメントの文字と SVS 符号の文字や IVS の符号の文字が存在することがあります.異体字関係は少し複雑です.


Windows 7 のフォントは IVS に対応しています.IVS に対応していても,該当する字体のフォントデータがなければ異体字としての表示にはなりません.
以下はそのことを確認した結果です.この結果を見ると,Windows 10 になってようやく東京都葛飾区 (かつしかく) と奈良県葛城市 (かつらぎし) が文字として区別がつくようになったと言えます.
邊と邉は辺の異体字ですが,邊と邉にも異体字が複数あります.


図5
異体字の表示テスト
  • Windows 7(SP1) における結果
  • フォントは「メイリオ」
  • 「葛」も「邊」も「邉」も全て同じ字体
図6
異体字の表示テスト
  • Windows 10.0.17134 における結果
  • フォントは「メイリオ」
  • 「葛󠄀」「葛」は別の字体になっている
  • Windows XP までは左側の字体だったが,Vista になって右側の字体だけになっていた
  • 「邊」と「邉」は全て同じ字体

$8089, $908A の異体字は,Unicode に複数登録されています.下図は,ユニコードコンソーシアムが提供している Ideographic Variation Database (IVD - 漢字異体字データベース - PDF) の $9089 の部分です.
以下の Unihan data のページからは,ページ最下部の, CHISE Project をクリックして確認できます.また,GlyphWiki 部分をクリックして,GlyphWiki でも確認できます.



図7
$9089 の異体字

リスト5
異体字の表示テスト用コード
//=============================================================================
//  フォーム生成時の処理
// テスト用の文字をTEditにセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Font.Name := 'メイリオ';
  Edit2.Font.Name := 'メイリオ';
  Edit3.Font.Name := 'メイリオ';
  Edit1.Font.Size := 30;
  Edit2.Font.Size := 30;
  Edit3.Font.Size := 30;

  // 東京都葛飾区の「葛」の異体字
  // 基底文字のコードポイントは U+845B
  // 実際にはもっとある
  Edit1.Text := #$845B#$E0100
              + #$845B#$E0101;

  // ワタナベの「邉」の異体字
  // 基底文字のコードポイントは U+9089
  // 実際にはもっとある
  Edit2.Text := #$9089#$E0100
              + #$9089#$E0101
              + #$9089#$E0102
              + #$9089#$E0103;

  // ワタナベの「邊」の異体字
  // 基底文字のコードポイントは U+908A
  // 実際にはもっとある
  Edit3.Text := #$908A#$E0100
              + #$908A#$E0101
              + #$908A#$E0102
              + #$908A#$E0103;
end;

//=============================================================================
//  フォントを変更してみる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Edit1.Font.Name := 'MS P明朝';
  Edit2.Font.Name := 'MS P明朝';
  Edit3.Font.Name := 'MS P明朝';
  Edit1.Font.Size := 34;
  Edit2.Font.Size := 34;
  Edit3.Font.Size := 34;
end;


文字が異体字セレクタを使用しているかは,例えば以下のようなコードで確認できます.
異体字セレクタ IVS のサロゲートペアの値は,通常のサロゲートペアの値と重複する範囲があります.したがって,通常のサロゲートペアの文字であるかを調べる前に,異体字セレクタの存在を調べないと正しく検出できません.

このテストで使用している「侮 (アナドル)」と言う文字の「毋」の中が点の「母」である異体字「侮」のコードポイントは U + FA30 です.




図8
異体字セレクタの使用有無を調べるテスト
  • 異体字かどうかの判定テストではない

リスト6
異体字セレクタを使用している文字かを調べるテスト.異体字かどうかの判定テストではない
//=============================================================================
//  フォーム生成時の処理
//  テスト用の文字列をセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := #$845B#$E0100 + 'AB'
              + #$4FAE#$FE01
              + #$9089#$E0101;
end;

//=============================================================================
//  異体字セレクタを使用している文字かを調べるテスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LString   : string;
  LLen      : Integer;
  LCodeHigh : Cardinal;
  LCodeLow  : Cardinal;
  LStrCnt   : Integer;
  LIndex    : Integer;
begin
  Memo1.Lines.Clear;

  LString := Edit1.Text;
  LLen    := Length(LString);

  LIndex  := 1;
  LStrCnt := 0;
  while LIndex <= LLen do begin
    LCodeHigh := 0;
    LCodeLow  := 0;
    if LLen >= (LIndex + 1) then begin
      LCodeHigh := Ord(LString[LIndex + 1]);
    end;
    if LLen >= (LIndex + 2) then begin
      LCodeLow  := Ord(LString[LIndex + 2]);
    end;

    if (LCodeHigh >= $180B) and (LCodeHigh <= $180D) then begin
      Memo1.Lines.Add('use Variation Selector');
      Memo1.Lines.Add('$' + IntToHex(LCodeHigh, 4));

      Inc(LIndex);
    end else
    if (LCodeHigh >= $FE00) and (LCodeHigh <= $FE0F) then begin
      Memo1.Lines.Add('use Variation Selector');
      Memo1.Lines.Add('$' + IntToHex(LCodeHigh, 4));

      Inc(LIndex);
    end else
    // サロゲートペアかの判定
    if (LCodeHigh = $DB40)
        and (LCodeLow >= $DB00) and (LCodeLow <= $DDEF) then begin
      Memo1.Lines.Add('use Variation Selector');
      Memo1.Lines.Add('$' + IntToHex(LCodeHigh, 4) + '   High');
      Memo1.Lines.Add('$' + IntToHex(LCodeLow, 4)  + '   Low');

      Inc(LIndex, 2);
    end else begin
      // 異体字セレクタを使用していない文字の場合
      Memo1.Lines.Add('Other');
    end;

    Inc(LIndex);
    Inc(LStrCnt)
  end;

  Memo1.Lines.Add('');
  Memo1.Lines.Add('エレメント : ' + IntToStr(LLen));
  Memo1.Lines.Add('文字数 : ' + IntToStr(LStrCnt));
end;




04_専用フォントを使用して異体字を列挙


MS Pゴシック,MS P明朝 などのフォントでは「邉」などの異体字は扱えません.社団法人文字情報技術促進協議会が提供している「IPAmj明朝」フォントには多くの異体字が収納されています.このフォントは,以下の 3 番目のリンクの窓の杜からダウンロードできます.
ダウンロードした ZIP 書庫を解凍展開します.ipamjm.ttf ファイルのポップアップメニューから [インストール] または [すべてのユーザに対してインストール] をクリックします.コンピュータの再起動は必要ありません.


IPAmj明朝を使用して異体字を列挙した結果です.
このフォントで使用可能はな異体字は,IVD_Charts_Moji_Joho.pdf に記載されています.「邉」の場合,異体字セレクタとして有効なのは $E010F ~ $E011F です.
IPAmj明朝で使用可能はな異体字は,[文字情報基盤検索システム] でも調べることができます.検索結果のリストから漢字をクリックすると,その漢字の情報のページになります.そのページの [コピーフィールド] の文字をコピーして利用します.



図9
「邉」の異体字を列挙
  • フォントは「IPAmj明朝」
  • 異体字セレクタは
    $E010F ~ $E011F

Windows 10 には,メイリオ, Yu Gothic UI, 游ゴシック, 游明朝,Segoe UI XXXX というフォントがインストールされています.これらのフォントには異体字が収納されています.
これらのフォントで使用可能はな異体字は,IVD_Charts_Adobe-Japan1.pdf に記載されています.「邉」の場合,異体字セレクタとして有効なのは $E0100 ~ $E010E です.



図10
「邉」の異体字を列挙
  • フォントは「Yu Gothic UI」
  • 異体字セレクタは
    $E0100 ~ $E010E

リスト7
異体字を収納しているフォントを使用して異体字を列挙
implementation  

uses
  MECSUtils;

{$R *.dfm}

//=============================================================================
//  邉 の異体字を列挙
//
//  フォントが IPAmj明朝 の場合は,異体字セレクタとして
//  漢字異体字データベースの Moji_Joho collection の値を使用する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LLastPt : Integer;
  LText   : string;
  LIndex  : Integer;
  LColCnt : Integer;
begin
  Memo1.Lines.Clear;

  Memo1.Font.Name := 'IPAmj明朝';
  Memo1.Font.Size := 36;

  LColCnt := 0;
  LText   := '';
  LLastPt := $E011F;
  for LIndex := $E010F to LLastPt do begin
    LText := LText + Char($9089) + CodePointToUTF16(LIndex) + '   ';
    Inc(LColCnt);

    if (LColCnt = 4) or (LIndex = LLastPt) then begin
      Memo1.Lines.Add(Trim(LText));
      LColCnt := 0;
      LText   := '';
    end;
  end;
end;

//=============================================================================
//  邉 の異体字を列挙
//
//  フォントが Yu Gothic UI, 游ゴシック, 游明朝 の場合は,異体字セレクタとして
//  漢字異体字データベースの Adobe-Japan1 collection の値を使用する
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LLastPt : Integer;
  LText   : string;
  LIndex  : Integer;
  LColCnt : Integer;
begin
  Memo1.Lines.Clear;

  Memo1.Font.Name := 'Yu Gothic UI';
  Memo1.Font.Size := 36;

  LColCnt := 0;
  LText   := '';
  LLastPt := $E010E;
  for LIndex := $E0100 to LLastPt do begin
    LText := LText + Char($9089) + CodePointToUTF16(LIndex) + '   ';
    Inc(LColCnt);

    if (LColCnt = 4) or (LIndex = LLastPt) then begin
      Memo1.Lines.Add(Trim(LText));
      LColCnt := 0;
      LText   := '';
    end;
  end;
end;


Adobe が無料で提供している「源ノ角ゴシック」にも異体字が含まれています.その源ノ角ゴシックを使用する例です.
このフォントで使用可能はな異体字は,IVD_Charts_Adobe-Japan1.pdf に記載されています.「邉」の場合,異体字セレクタとして有効なのは $E0100 ~ $E010E です.



図11
「邉」の異体字を列挙
  • フォントは「源ノ角ゴシック」
  • 異体字セレクタは
    $E0100 ~ $E010E

リスト8
「源ノ角ゴシック」フォントを使用して異体字を列挙
implementation  

uses
  MECSUtils;

{$R *.dfm}

//=============================================================================
//  邉 の異体字を列挙
//
//  フォントが 源ノ角ゴシック の場合は,異体字セレクタとして
//  漢字異体字データベースの Adobe-Japan1 collection の値を使用する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LLastPt : Integer;
  LText   : string;
  LIndex  : Integer;
  LColCnt : Integer;
begin
  Memo1.Lines.Clear;

  Memo1.Font.Name := '源ノ角ゴシック';
  Memo1.Font.Size := 36;

  LColCnt := 0;
  LText   := '';
  LLastPt := $E010E;
  for LIndex := $E0100 to LLastPt do begin
    LText := LText + Char($9089) + CodePointToUTF16(LIndex) + '   ';
    Inc(LColCnt);

    if (LColCnt = 4) or (LIndex = LLastPt) then begin
      Memo1.Lines.Add(Trim(LText));
      LColCnt := 0;
      LText   := '';
    end;
  end;
end;


[備考 1]
Windows が提供しているフォントだけでは異体字や古代文字等の多くの文字を表示することはできません.本格的に漢字や文字を扱うのであれば,超漢字というソフトがあります.また,フォントとして Tフォントというのもあります.これらの文字コードの体系は Unicode の符号化方式との互換性はありません.
超漢字には文字入力,文字選択の機能が実装されています.

Windows では 1 つのフォントファイルに格納できる字体数 (グリフ数) は 65535 ($FFFF) までです.それ以上の字体を扱えるフォントは複数のフォントファイルで提供されています.

[備考 2]
文字は人類の大きな発明で,コミュニケーションの重要な道具です.異体字を調べると,漢字を含めて文字の文化の深さを思い知らされます.




05_ユニコードの正規化と文字列の検索


下に示す「同じ文字」という左側の文字列の中の「じ」は,単独の「じ」という文字です.一方,右側の文字列の中の「じ」は結合形式の文字です.どちらも字体が同じで見た目の区別はありません.

文字または文字列の検索というのは,文字の字体を検索するわけではありません.文字を構成しているコードポイントの値を検索します.そのため,この「同じ文字」を検索してもどちらか一方しか検索できないことになります.


   同じ文字   同じ文字

そのような現象を回避するには Unicode の正規化の処理をします.
以下はこの正規化の処理の効果を確認するためのテストサンプルです.正規化した後に「同じ文字」を検索すると,両方の文字列を検索することができます.



[備考 1]
Google Chrome 70.0 ブラウザでは,どちらの「同じ文字」も同じ文字列として検索されます.

[備考 2]
上の結合文字「じ」の濁点のコードポイントは結合文字の U + 3099 ですが,U + 309B にすると独立した濁点としての文字になります.


図12
正規化する前の検索結果
  • 最初の「同じ文字」だけを検索して検索が終了する
図13
正規化した後の検索結果
  • 2 番目の「同じ文字」を検索してから検索が終了する

リスト9
正規化の効果を確認するテスト.文字列を検索する例
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    SpeedButton1: TSpeedButton;
    SpeedButton2: TSpeedButton;
    procedure FormCreate(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure SpeedButton2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 Wide}
    FTestText   : string;
    FSearchText : string;
    FSelStart   : Integer;
  end;

var
  Form1: TForm1;

implementation

uses
  MECSUtils,
  System.StrUtils;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
// テスト用の文字列をTEditにセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FTestText  := '同じ文字と同' + #$3057#$3099 +'文字の検索';
  Edit1.Text := FTestText;

  FSearchText := '同じ文字';
  FSelStart := 1;
  Edit1.AutoSelect := False;
end;

//=============================================================================
//  検索文字列を現在エレメントの位置から検索(逐次検索となる)
//  検索した文字列をハイライト表示
//
//  このコードの実行には uses に System.StrUtils が必要
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  LIndex : Integer;
begin
  Edit1.SetFocus;

  LIndex := PosEx(FSearchText, Edit1.Text, FSelStart);
  if LIndex >= 1 then begin
    Edit1.SelStart  := LIndex - 1;
    Edit1.SelLength := Length(FSearchText);
    FSelStart := LIndex + Length(FSearchText);
  end else begin
    MessageBox(Handle, '検索が終了しました.', '終了', MB_ICONINFORMATION);
    FSelStart := 1;
  end;
end;

//=============================================================================
//  ユニコードの正規化
//  このコードの実行には uses に MECSUtils が必要
//=============================================================================
procedure TForm1.SpeedButton2Click(Sender: TObject);
var
  LTempStr : WideString;
begin
  LTempStr := FTestText;
  MECSUtils.MecsNormalize(FTestText, LTempStr, NormalizationC);
  Edit1.Text := LTempStr;
end;

end.


下図はテストで使用した「同じ文字」と「同じ文字」を TMemo と TRichEdit コントロールに表示した結果です.TRichEdit コントロールでは結合文字は表示されますが,後ろに空白が表示されています.NFC 形式で正規化すれば TMemo と同じように表示できるようになります.



図14
TMemo (上) と TRichEdit (下) における結合文字の表示
  • 例えば NFC で正規化すれば TRichEdit でも TMemo と同じ表示になる