今日はMicrosoftのセキュリティアップデートの日です。
2024 年 5 月のセキュリティ更新プログラム - リリース ノート - セキュリティ更新プログラム ガイド - Microsoft
2024 年 5 月のセキュリティ更新プログラム (月例) | MSRC Blog | Microsoft Security Response Center
2024年5月15日
2024年5月1日
2024/05開催のセミナー
- Web Seminar
- 2024/05/28 17:00-17:45(JST) 「Delphi/C++Builder活用術」 - エンバカデロ・デベロッパーTV(2024年5月28日)
- Live Seminar
- Community Event
2024年4月22日
[書籍]改訂第5版 C++ポケットリファレンス
紀伊國屋書店新宿本店でC++ポケットリファレンス、改訂新版 C++ポケットリファレンス、改訂第3版 C++ポケットリファレンス、改訂第4版 C++ポケットリファレンスのC++23対応改訂版になる
改訂第5版 C++ポケットリファレンス (Amazon)/ 高橋晶、安藤敏彦、一戸優介、楠田真矢、湯朝剛介著/技術評論社/ISBN 978-4-297-14165-3/3,520円
を購入。
改訂第5版 C++ポケットリファレンス (Amazon)/ 高橋晶、安藤敏彦、一戸優介、楠田真矢、湯朝剛介著/技術評論社/ISBN 978-4-297-14165-3/3,520円
を購入。
2024年4月18日
RAD Studio/Delphi/C++Builder 12.1 Athens Patch 1
RAD Studio/Delphi/C++Builder 12.1 AthensのPatch 1がリリースされています。現時点(2024/04/18)ではGetItからのみダウンロード、インストールすることができます。IDEとコンパイラの不具合修正のようです。
RAD Studio 12 Athens Patch 1 のリリース (en)
RAD Studio 12 Athens Patch 1 のリリース (en)
2024年4月10日
2024年4月9日
[書籍]はじめて学ぶ最新サイバーセキュリティ講義
紀伊國屋書店新宿本店で
Cybersecurity Myths and Misconceptions (Amazon US)の翻訳である
はじめて学ぶ最新サイバーセキュリティ講義 (Amazon)/Eugene H. Spafford、Leigh Metcalf、Josiah Dykstra著/金井哲夫訳/徳丸浩監訳/日経BP/ISBN 9784296070695/3,9604,400円
を購入。
はじめて学ぶ最新サイバーセキュリティ講義 (Amazon)/Eugene H. Spafford、Leigh Metcalf、Josiah Dykstra著/金井哲夫訳/徳丸浩監訳/日経BP/ISBN 9784296070695/3,9604,400円
を購入。
2024年4月4日
RAD Studio/Delphi/C++Builder 12 Athens Release 1 (12.1)
RAD Studio/Delphi/C++Builder 12 AthensのRelease 1 (12.1)がリリースされています。インストールには有効なUpdate Subscriptionが必要です。ポータルサイトからダウンロードできます。
RAD Studio, Delphi, C++Builder 12.1 Web Install
RAD Studio, Delphi, C++Builder 12.1 ISO
12 Athens - Release 1 - RAD Studio (en)
New features and customer reported issues fixed in RAD Studio 12.1
List of publicly reported bugs fixed in 12.1 - RAD Studio
RAD Studio 12.1 Athensの提供開始 (en)
What’s New in RAD Studio 12.1 Athens - YouTube
C++Builder 12.1の新しいClangツールチェイン (en)
How to achieve common tasks with the new Clang toolchain in 12.1
bcc64x リリースモードでビルドしたアプリがクラッシュ時における問題解決のケーススタディ (en)
RAD Studio 12.1の新機能: 分割エディタビュー (en)
Delphi 12.1 Released Today
RAD Studio, Delphi, C++Builder 12.1 Web Install
RAD Studio, Delphi, C++Builder 12.1 ISO
12 Athens - Release 1 - RAD Studio (en)
New features and customer reported issues fixed in RAD Studio 12.1
List of publicly reported bugs fixed in 12.1 - RAD Studio
RAD Studio 12.1 Athensの提供開始 (en)
What’s New in RAD Studio 12.1 Athens - YouTube
C++Builder 12.1の新しいClangツールチェイン (en)
How to achieve common tasks with the new Clang toolchain in 12.1
bcc64x リリースモードでビルドしたアプリがクラッシュ時における問題解決のケーススタディ (en)
RAD Studio 12.1の新機能: 分割エディタビュー (en)
Delphi 12.1 Released Today
2024年4月1日
2024/04開催のセミナー
- Web Seminar
- 2024/04/23 17:00-17:45(JST) 「RAD Studio 12.1アップデート情報」 - エンバカデロ・デベロッパーTV(2024年4月23日)
- Live Seminar
- Community Event
2024年3月21日
Firebirdロードマップ(2024/03)
Firebird 6の開発が正式に決まったとのことで、ロードマップが更新されています。
Firebird: Roadmap v6
適当な要約
Firebird: Roadmap v6
適当な要約
- Firebird 3.0 - 安定版。最新版は2023年08月の3.0.11で、次回(3.0.12)は2024年第2四半期内を予定。
- Firebird 4.0 - 安定版。最新版は2023年11月の4.0.4で、次回(4.0.5)は2024年第2四半期内を予定。
- Firebird 5.0 - 安定版。最新版は2024年01月の5.0で、次回(5.0.1)は2024年第2四半期内を予定。
- Firebird 6.0 - 2024年03月に開発開始、2025年第1四半期に最初のアルファ版を、ベータ版を2025年第3四半期に、正式版を2026年第1四半期にリリース予定。
- SQL準拠のJSON関数 issue #5431
- テーブルスペースのサポート issue #1055
- 共有メタデータキャッシュ issue #7954
- SQL標準に準拠したROWデータ型 issue #1115, issue #3031
- SQLスキーマのサポート issue #1113
2024年3月13日
2024年3月4日
[書籍]Expert Delphi - Second Edition
Amazon(JP)で注文した
Expert Delphi - Second Edition (Amazon US, Amazon JP)/Marco Cantù、Paweł Głowacki著/Packt Publishing/ISBN 9781805121107/7,596JPY(printed)
が配送されてきました。2024/03/02に注文して2日目の到着でした(日本国内でのプリントオンデマンドでした)。
Expert Delphi - Second Edition (Amazon US, Amazon JP)/Marco Cantù、Paweł Głowacki著/Packt Publishing/ISBN 9781805121107/7,596JPY(printed)
が配送されてきました。2024/03/02に注文して2日目の到着でした(日本国内でのプリントオンデマンドでした)。
2024年3月1日
2024/03開催のセミナー
- Web Seminar
- 2024/03/12 17:00-17:45(JST) 「Delphi 29歳バースディ!私のDelphi推しプレゼン」 - エンバカデロ・デベロッパーTV(2024年3月12日)
- Live Seminar
- Community Event
2024年2月14日
2024年2月7日
RAD Studio/Delphi/C++Builder 12 Athens Patch 1
RAD Studio/Delphi/C++Builder 12 AthensのPatch 1がリリースされています。多数の不具合修正が含まれています。現時点(2024/02/08)では(やっと復旧した)GetItからのみダウンロード、インストールすることができます。2024/02/10現在でGetItまたはポータルサイトからダウンロード、インストールすることができます。またwebインストーラもPatch 1適用済のみものがダウンロードできるようになっています。
RAD Studio 12.0 Patch 1
RAD Studio, Delphi, C++Builder 12.0 Inline with Patch 1 Web Install
なおオリジナルのwebインストーラからインストールした場合はPatch 1がインストールされているかどうかにかかわらず、IDEのツール→機能の管理で言語やターゲットプラットフォームなどを追加することはできず、一旦アンインストール後にPatch 1適用済のwebインストーラからインストールし直す必要があるため注意が必要です。
RAD Studio 12 Athens Patch 1 のリリース (en)
GetIt アップデート: RAD Studio 12 向けのGetItパッケージインストールが利用可能に! (en)
GetItアップデート: RAD Studio 12 GetIt オンラインインストールが利用可能に! (en)
2024/02/10追記: Patch 1がポータルサイトからダウンロードできるようになったこととオリジナルのwebインストーラからインストールした場合の注意事項を追記しました。
RAD Studio 12.0 Patch 1
RAD Studio, Delphi, C++Builder 12.0 Inline with Patch 1 Web Install
なおオリジナルのwebインストーラからインストールした場合はPatch 1がインストールされているかどうかにかかわらず、IDEのツール→機能の管理で言語やターゲットプラットフォームなどを追加することはできず、一旦アンインストール後にPatch 1適用済のwebインストーラからインストールし直す必要があるため注意が必要です。
RAD Studio 12 Athens Patch 1 のリリース (en)
GetIt アップデート: RAD Studio 12 向けのGetItパッケージインストールが利用可能に! (en)
GetItアップデート: RAD Studio 12 GetIt オンラインインストールが利用可能に! (en)
2024/02/10追記: Patch 1がポータルサイトからダウンロードできるようになったこととオリジナルのwebインストーラからインストールした場合の注意事項を追記しました。
2024年2月1日
2024/02開催のセミナー
- Web Seminar
2024/02/20 17:00-17:45(JST) 「Delphi 29歳バースディ!私のDelphi推しプレゼン」 - エンバカデロ・デベロッパーTV(2024年2月)2024/03/12に延期
- Live Seminar
- Community Event
2024年1月12日
Firebird 5.0
Firebird 5.0がリリースされています。ODSバージョンは13.1になっています。
Firebird: Firebird 5.0 is released
Firebird: Firebird 5.0.0
Firebird 5.0 Release Notes (PDF)
Firebird 5.0 Language Reference (PDF)
Detailed New Features of Firebird 5
Firebird: Firebird 5.0 is released
Firebird: Firebird 5.0.0
Firebird 5.0 Release Notes (PDF)
Firebird 5.0 Language Reference (PDF)
Detailed New Features of Firebird 5
- Detailed New Features of Firebird 5. Part 1: Improvements in Optimizer
- Detailed New Features of Firebird 5. Part 2: SQLs
- Detailed New Features of Firebird 5, Part 3: Using new clause SKIP LOCKED
- Detailed New Features of Firebird 5, Part 4: Parallel Features: backup/restore/sweep, creation of indices
- Detailed New Features Of Firebird 5, Part 5: Better compression, cache of compiled statements, and more
- Detailed New Features Of Firebird 5, Part 6: Profiling
- Parallel Reading Of Data In Firebird
2024年1月10日
2024年1月1日
2024/01開催のセミナー
- Web Seminar
- 2024/01/30 17:00-17:45(JST) 新年事始め!三浦珠美の「挑戦!はじめてのDelphi」 - エンバカデロ・デベロッパーTV(2024年1月)
- Live Seminar
- Community Event
2023年12月27日
文字列の表示幅を取得する
前回はSkia4DelphiのISkUnicodeを使って文字列を書記素クラスタ(grapheme cluster)に分割する方法を扱いましたが、(等幅フォントでの表記を前提として)文字列が何文字分の幅を占めるのか(いわゆる"表示幅")を取得するためには、それぞれの書記素クラスタ(の基底文字)がいわゆる"半角幅"(halfwidth、1/2 Em)なのか"全角幅"(fullwidth、1 Em)なのかを知る必要があります(Emは文字の高さを基準にした単位で、1/2 Emは高さの半分の文字幅、1 Emは高さと同じ文字幅になる、という意味)。これに関するUnicodeの規格がUnicode Standard Annex #11 East Asian Width(UAX #11)になります。
UAX #11では既存の実装に配慮して、文字をその占める幅によって
では実際にどの文字(コードポイント)がどの分類になるのか、ですが、これはUnicodeデータベースの一部としてhttps://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txtにリストされています。基本的にこれを何らかの形で配列化しておいて参照すればいいのですが、Unicode(UCS4)で扱えるコードポイントは最大でU+0000からU+10FFFFの1,114,112個あり、単純にテーブル化すると約1MBになってしまいます。しかしプレーン4~13は未割当で定義も存在しません(この場合はデフォルトで"N"である(
まずはUAX #11で規定されている分類を列挙型として定義し、レコードヘルパでそれぞれに対応する文字幅を返すようにします。
次にEastAsianWidth.txtをテーブル化します。まずそれぞれのプレーンを格納するテーブルのレコード型と、このテーブルへのポインタと代表値をセットにしたレコード型を定義します。
あとはUnicodeのバージョンアップに簡単に追随できるようにするために、EastAsianWidth.txtから上記のテーブル部分を自動生成する処理を別ユニットに作っていきます。
まず読み込んだプレーン毎のデータを格納するレコード型と、関係するメソッド(初期化、値の指定、代表値だけでテーブルを省略可能かどうかの判定)を用意します。
このConvertEastAsianWidthを使ってEastAsianWidth.txtをEastAsianWidth.incとして変換したら、上記のTPlaneの配列部分を
これで前回のサンプルにチェックボックスを1つ追加して、
誰ですか、絵文字が混ざると意味がないじゃないか、とかいう人は!そう、絵文字がZWJ(ゼロ幅接合子)を使ってグリフを増やすようになったあたりから、実際にレンダリングしてみないとどのくらいの表示幅になるのかはわからなくなっているのです。
最終的なプロジェクト全体をGitHubに上げてあります。今後Unicodeのバージョンが上がっても、更新されたEastAsianWidth.txtを取り込むことで最新のものに準拠することができます。
参考: 東アジアの文字幅 - Wikipedia
UAX #11では既存の実装に配慮して、文字をその占める幅によって
- Fullwidth("F"/全角)
- Halfwidth ("H"/半角)
- Wide ("W"/広)
- Narrow ("Na"/狭)
- Ambiguous ("A"/曖昧)
- Neutral ("N"/中立)(Not East Asian)
では実際にどの文字(コードポイント)がどの分類になるのか、ですが、これはUnicodeデータベースの一部としてhttps://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txtにリストされています。基本的にこれを何らかの形で配列化しておいて参照すればいいのですが、Unicode(UCS4)で扱えるコードポイントは最大でU+0000からU+10FFFFの1,114,112個あり、単純にテーブル化すると約1MBになってしまいます。しかしプレーン4~13は未割当で定義も存在しません(この場合はデフォルトで"N"である(
All code points, assigned or unassigned, that are not listed explicitly are given the value "N".)と明記されています)。そこで今回はプレーン毎に分割して、フルマッピングするプレーンと1つの値で代表させるプレーン、という形で領域を節約した実装を考えてみます。
まずはUAX #11で規定されている分類を列挙型として定義し、レコードヘルパでそれぞれに対応する文字幅を返すようにします。
type
TEastAsianWidth = (Neutral, // N
Fullwidth, // F
Halfwidth, // H
Wide, // W
Narrow, // Na
Ambiguous); // A
TEastAsianWidthHelper = record helper for TEastAsianWidth
private
const
Width: array [Boolean,TEastAsianWidth] of Integer =
((1, // Neutral
2, // Fullwidth
1, // Halfwidth
2, // Wide
1, // Narrow
1), // Ambiguous (same as Neutral)
(1, // Neutral
2, // Fullwidth
1, // Halfwidth
2, // Wide
1, // Narrow
2)); // Ambiguous (same as Fullwidth)
private
class var
FEastAsian: Boolean;
public
function GetWidth: Integer; overload; inline;
class property EastAsian: Boolean read FEastAsian write FEastAsian;
end;
function TEastAsianWidthHelper.GetWidth: Integer;
begin
Result := Width[FEastAsian,Self];
end;
ここでクラスプロパティTEastAsianWidth.EastAsianは"A"の扱いを決めるもので、Trueなら東アジアの組版(フォントが日本語など)、Falseなら欧文の組版(フォントが欧文)であることを示します。次にEastAsianWidth.txtをテーブル化します。まずそれぞれのプレーンを格納するテーブルのレコード型と、このテーブルへのポインタと代表値をセットにしたレコード型を定義します。
type
TPlaneData = packed record
public
Data: array [0..65535] of Byte;
end;
PPlaneData = ^TPlaneData;
TPlane = record
PlaneDefault: Byte;
PlaneData: PPlaneData;
end;
このTPlaneの配列(0..16)を今回はEastAsianWidth.txtから生成しますが、とりあえず仮に
const
Plane0: TPlaneData = (Data: (
$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,
...
$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00));
Plane0Default = $00;
Plane1Default = $00;
...
Plane16Default = $00;
Planes: array [0..16] of TPlane =
((PlaneDefault: Plane0Default; PlaneData: @Plane0),
(PlaneDefault: Plane1Default; PlaneData: nil),
...
(PlaneDefault: Plane16Default; PlaneData: nil));
こんな定数定義があるものとします。これを使用する形でレコードヘルパTEastAsianWidthHelperにクラスメソッドGetEastAsianWidthを追加します。
uses
System.SysUtils, System.RTLConsts;
type
TEastAsianWidthHelper = record helper for TEastAsianWidth
public
...
class function GetEastAsianWidth(C: UCS4Char): TEastAsianWidth; static;
end;
class function TEastAsianWidthHelper.GetEastAsianWidth(C: UCS4Char): TEastAsianWidth;
var
PlaneNum: Integer;
Plane: TPlane;
B: Byte;
begin
PlaneNum := (C shr 16) and $FFFF;
if (PlaneNum < Low(Planes)) and (PlaneNum > High(Planes)) then
begin
raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);
end;
Plane := Planes[PlaneNum];
if Plane.PlaneData <> nil then
begin
B := Plane.PlaneData^.Data[C and $FFFF];
end
else
begin
B := Plane.PlaneDefault;
end;
Result := TEastAsianWidth(B);
end;
クラスメソッドGetEastAsianWidthでは、プレーン毎のテーブルがあればそこから、テーブルがなければ代表値を取得し、列挙型TEastAsianWidthとして返します。あとはUnicodeのバージョンアップに簡単に追随できるようにするために、EastAsianWidth.txtから上記のテーブル部分を自動生成する処理を別ユニットに作っていきます。
まず読み込んだプレーン毎のデータを格納するレコード型と、関係するメソッド(初期化、値の指定、代表値だけでテーブルを省略可能かどうかの判定)を用意します。
type
TPlaneData = record
Table: array [$0000..$FFFF] of Byte;
procedure Init;
procedure Fill(StartIndex: UInt16; EndIndex: UInt16; Data: Byte);
function IsAllSame: Boolean;
end;
procedure TPlaneData.Init;
var
I: Integer;
begin
for I := Low(Table) to High(Table) do
begin
Table[I] := 0; // 'Neutral'
end;
end;
procedure TPlaneData.Fill(StartIndex: UInt16; EndIndex: UInt16; Data: Byte);
var
I: Integer;
begin
for I := StartIndex to EndIndex do
begin
Table[I] := Data;
end;
end;
function TPlaneData.IsAllSame: Boolean;
var
I: Integer;
B: Byte;
begin
B := Table[Low(Table)];
for I := Low(Table) + 1 to High(Table) do
begin
if Table[I] <> B then
begin
Result := False;
Exit;
end;
end;
Result := True;
end;
これを使ってUCS4のコード範囲全体を扱うレコード型を用意します。
type
TEastAsianWidthProperties = record
Planes: array [0..16] of TPlaneData;
procedure Init;
end;
PEastAsianWidthProperties = ^TEastAsianWidthProperties;
procedure TEastAsianWidthProperties.Init;
var
I: Integer;
begin
for I := Low(Planes) to High(Planes) do
begin
Planes[I].Init;
end;
end;
あとはEastAsianWidth.txtを解析して値を格納し、テーブル化するメソッドを用意します。
uses
System.Classes, System.SysUtils, System.RegularExpressions, System.SysConst;
function SymbolToEastAsianWidth(const S: String): Byte;
const
EastAsianWidthSymbols: array [0..5] of String =
('N',
'F',
'H',
'W',
'Na',
'A');
begin
for Result := Low(EastAsianWidthSymbols) to High(EastAsianWidthSymbols) do
begin
if EastAsianWidthSymbols[Result] = S then
begin
Exit;
end;
end;
raise EConvertError.CreateRes(@SRangeError);
end;
procedure ConvertEastAsianWidth(Input, Output: TStrings);
var
PEAWP: PEastAsianWidthProperties;
RegEx1: TRegEx;
RegEx2: TRegEx;
Match: TMatch;
I: Integer;
S: String;
Position: Integer;
StartCodePoint: String;
EndCodePoint: String;
Name: String;
Plane: UInt16;
CPLow: UInt16;
CPLow2: UInt16;
PlaneDefault: Byte;
begin
New(PEAWP);
try
PEAWP^.Init;
RegEx1 := TRegEx.Create('([0-9A-Fa-f]{4,6})[.][.]([0-9A-Fa-f]{4,6})\s*[;]\s*(.{1,2})\s*');
RegEx2 := TRegEx.Create('([0-9A-Fa-f]{4,6})\s*[;]\s*(.{1,2})\s*');
for I := 0 to Input.Count - 1 do
begin
S := Input.Strings[I];
Position := Pos('#',S);
if Position > 0 then
begin
Delete(S,Position,Length(S));
end;
S := Trim(S);
if S = '' then
begin
Continue;
end;
Match := RegEx1.Match(S);
if Match.Success = True then
begin
StartCodePoint := Match.Groups[1].Value;
EndCodePoint := Match.Groups[2].Value;
Name := Match.Groups[3].Value;
end
else
begin
Match := RegEx2.Match(S);
if Match.Success = True then
begin
StartCodePoint := Match.Groups[1].Value;
EndCodePoint := StartCodePoint;
Name := Match.Groups[2].Value;
end;
end;
if Match.Success = True then
begin
Plane := (StrToInt64('$' + StartCodePoint) shr 16) and $FFFF;
if Plane <= 16 then
begin
CPLow := (StrToInt64('$' + StartCodePoint)) and $FFFF;
CPLow2 := (StrToInt64('$' + EndCodePoint)) and $FFFF;
PEAWP^.Planes[Plane].Fill(CPLow,CPLow2,Ord(SymbolToEastAsianWidth(Name)));
end;
end;
end;
for Plane := Low(PEAWP^.Planes) to High(PEAWP^.Planes) do
begin
Output.Add(Format(' { Plane%d }',[Plane]));
if PEAWP^.Planes[Plane].IsAllSame = False then
begin
Output.Add(Format(' Plane%d: TPlaneData = (Data: (',[Plane]));
S := '';
for I := Low(PEAWP^.Planes[Plane].Table) to High(PEAWP^.Planes[Plane].Table) do
begin
if (I mod 16) = 0 then
begin
S := ' ';
end;
S := S + Format('$%.02X,',[PEAWP^.Planes[Plane].Table[I]]);
if (I mod 16) = 15 then
begin
if I = High(PEAWP^.Planes[Plane].Table) then
begin
Delete(S,Length(S),1);
S := S + '));';
end
else
begin
S := S + ' ';
end;
S := S + Format(' // U+%.4X',[(Plane shl 16) or (I and $FFF0)]);
Output.Add(S);
S := '';
end;
end;
PlaneDefault := 0;
end
else
begin
PlaneDefault := PEAWP^.Planes[Plane].Table[0];
end;
Output.Add(Format(' Plane%dDefault = $%.2X;',[Plane,PlaneDefault]));
Output.Add('');
end;
Output.Add(' { Plane data table }');
Output.Add(' Planes: array [0..16] of TPlane =');
for Plane := Low(PEAWP^.Planes) to High(PEAWP^.Planes) do
begin
if PEAWP^.Planes[Plane].IsAllSame = True then
begin
S := Format(' (PlaneDefault: Plane%dDefault; PlaneData: nil),',[Plane]);
end
else
begin
S := Format(' (PlaneDefault: Plane%dDefault; PlaneData: @Plane%d),',[Plane,Plane]);
end;
if Plane = Low(PEAWP^.Planes) then
begin
S[5] := '(';
end
else if Plane = High(PEAWP^.Planes) then
begin
Delete(S,Length(S),1);
S := S + ');';
end;
Output.Add(S);
end;
finally
Dispose(PEAWP);
end;
end;
レコード型TEastAsianWidthPropertiesはサイズが大きく、デフォルトの設定ではスタックオーバフローするため、New/Disposeでヒープ上に確保するようにしています。読み込んだEastAsianWidth.txtの各行が
0000..001F ; N # Cc [32] <control-0000>..
0020 ; Na # Zs SPACE このようになっているものを、正規表現で複数指定、単独指定のどちらかのパターンにマッチングさせて、コードポイント(の範囲)と属性を取り込んでテーブルに格納し、最後にテーブルの内容をDelphiのコードの一部として出力しています。このConvertEastAsianWidthを使ってEastAsianWidth.txtをEastAsianWidth.incとして変換したら、上記のTPlaneの配列部分を
const
{$I 'EastAsianWidth.inc'}
と置き換えれば完成です。これで前回のサンプルにチェックボックスを1つ追加して、
procedure TForm1.Button2Click(Sender: TObject);
var
SkUnicode: ISkUnicode;
L: Integer;
TotalL: Integer;
W: Integer;
TotalW: Integer;
begin
TEastAsianWidth.EastAsian := CheckBox1.Checked;
SkLabel1.Words.Items[0].Caption := Edit1.Text;
Memo1.Lines.Clear;
SkLabel2.Words.Clear;
TotalL := 0;
TotalW := 0;
SkUnicode := TSkUnicode.Create;
for var S in SkUnicode.GetBreaks(Edit1.Text,TSkBreakType.Graphemes) do
begin
L := Length(S);
W := TEastAsianWidth.GetEastAsianWidth(Char.ConvertToUtf32(S,0)).GetWidth;
Memo1.Lines.Add(S + Format(' (L=%d,W=%d)',[L,W]));
SkLabel2.Words.Add(S + sLineBreak);
TotalL := TotalL + L;
TotalW := TotalW + W;
end;
Memo1.Lines.Add(Format('Total: L=%d, W=%d',[L,W]));
end;
こんな感じで全体の文字数を得ることができるようになります。チェックボックスのチェック状態で"á̂̃̄"のWが変化するのがわかりますね。誰ですか、絵文字が混ざると意味がないじゃないか、とかいう人は!そう、絵文字がZWJ(ゼロ幅接合子)を使ってグリフを増やすようになったあたりから、実際にレンダリングしてみないとどのくらいの表示幅になるのかはわからなくなっているのです。
最終的なプロジェクト全体をGitHubに上げてあります。今後Unicodeのバージョンが上がっても、更新されたEastAsianWidth.txtを取り込むことで最新のものに準拠することができます。
参考: 東アジアの文字幅 - Wikipedia
2023年12月25日
文字列を書記素クラスタで分割する
このアーティクルはDelphi Advent Calendar 2023の25日目の記事です(12日ぶり11回目)。
Unicodeの世界では、1つの"文字"(書記素、grapheme)が1つのコードポイント(code point)で表されるとは限りません。サロゲートペアとか結合文字とか絵文字とか、文字列に入っているものがどれくらいの"文字数"になっているのかを知るのは意外に難しい話です。
現在一般的と考えられる方法として、
ところがDelphi 12で標準サポートされたSkia4Delphiでは簡単に文字列を書記素クラスタで分割できるようになりました(ICUを取り込んでいるようです)。試しに正規表現(PCRE)による分割と比較してみましょう。
なおDelphi 11 Alexandriaおよびそれ以前のバージョンではSkia4Delphiをインストールする必要があります(現時点(2023/12)の最新は6.0.0)。
新規プロジェクトを作成し、フォーム(フォントサイズを大きめにしておくと結果が見やすくなります)にTEditを1つ、TButtonを2つ、結果表示のためのTMemoを1つ配置します。またプロジェクトツールウィンドウでプロジェクトを右クリック→Skiaを有効化を選択して、実行ファイルと同じ場所にsk4d.dllが配置されるようにします。
usesにSystem.Skiaを追加し、フォームのOnShowイベントで
正規表現による分割では絵文字のZWJ(ゼロ幅接合子)による結合はうまく処理できないようです。これはPCRE 8.45がだいぶ古いバージョンで、最新のUnicodeに対応できていないことが原因と考えられます。
次にSkia4Delphiです。
一方でSkia4Delphiによる分割は正しく処理できていますね。ちなみにWindowsでは国旗の絵文字は絵文字としてではなくRegional indicator symbol2文字での表現になるようです(サンプル最後の"🇯🇵"が"JP"となる)。Unicodeの世界では、1つの"文字"(書記素、grapheme)が1つのコードポイント(code point)で表されるとは限りません。サロゲートペアとか結合文字とか絵文字とか、文字列に入っているものがどれくらいの"文字数"になっているのかを知るのは意外に難しい話です。
現在一般的と考えられる方法として、
- ICU( International Components for Unicode、https://icu.unicode.org/)を使う
- 正規表現で"\X"でマッチする
- Unicodeの規格(Unicode Standard Annex #29)を元に自前で実装するr
ところがDelphi 12で標準サポートされたSkia4Delphiでは簡単に文字列を書記素クラスタで分割できるようになりました(ICUを取り込んでいるようです)。試しに正規表現(PCRE)による分割と比較してみましょう。
なおDelphi 11 Alexandriaおよびそれ以前のバージョンではSkia4Delphiをインストールする必要があります(現時点(2023/12)の最新は6.0.0)。
新規プロジェクトを作成し、フォーム(フォントサイズを大きめにしておくと結果が見やすくなります)にTEditを1つ、TButtonを2つ、結果表示のためのTMemoを1つ配置します。またプロジェクトツールウィンドウでプロジェクトを右クリック→Skiaを有効化を選択して、実行ファイルと同じ場所にsk4d.dllが配置されるようにします。
usesにSystem.Skiaを追加し、フォームのOnShowイベントで
procedure TForm1.FormShow(Sender: TObject);
begin
Edit1.Text := #$20BB7 + '野屋のコピペ' +
#$00E5 + #$00E1 + #$0302 + #$0303 + #$0304 +
#$1F62D +
#$1F937 + #$1F3FD + #$200D + #$2640 + #$FE0F +
#$1F468 + #$200D + #$1F469 + #$200D + #$1F467 + #$200D + #$1F466 +
#$1F469 + #$1F3FD + #$200D + #$1F4BB +
#$1F1EF + #$1F1F5;
end;
とEdit1.Textにサンプル文字列を設定し、Button1のOnClickイベントで
procedure TForm1.Button1Click(Sender: TObject);
begin
for var Match in TRegEx.Matches(Edit1.Text,'\X') do
begin
Memo1.Lines.Add(Match.Value);
end;
end;
Button2のOnClickイベントで
procedure TForm1.Button2Click(Sender: TObject);
var
SkUnicode: ISkUnicode;
begin
SkUnicode := TSkUnicode.Create;
for var S in SkUnicode.GetBreaks(Edit1.Text,TSkBreakType.Graphemes) do
begin
Memo1.Lines.Add(S);
end;
end;
とします。では実行してみましょう。まず正規表現です。𠮷
野
屋
の
コ
ピ
ペ
å
á̂̃̄
😭
🤷
🏽
♀️
👨
👩
👧
👦
👩
🏽
💻
🇯🇵
(Windows上の表示とは若干異なります)正規表現による分割では絵文字のZWJ(ゼロ幅接合子)による結合はうまく処理できないようです。これはPCRE 8.45がだいぶ古いバージョンで、最新のUnicodeに対応できていないことが原因と考えられます。
次にSkia4Delphiです。
𠮷
野
屋
の
コ
ピ
ペ
å
á̂̃̄
😭
🤷🏽♀️
👨👩👧👦
👩🏽💻
🇯🇵
書記素クラスタによる分割は文字列のレンダリングに必須なためSkia(Skia4Delphi)に実装されていると考えられますが、プログラムで書記素クラスタによる分割が必要になるのは(帳票などで)文字列がどのくらいの幅を占めるかを知りたいときではないでしょうか。そのためには書記素クラスタによる分割だけでなく、Unicode Standard Annex #11 East Asian Widthも必要になります。これはDelphi標準のSystem.CharacterユニットにもSkia4Delphiにも実装されておらず、EastAsianWidth.txtを取り込んで自前で実装する必要があります。これについては後日別のアーティクルで扱うことにします。
Skia4Delphを使って見やすくしたバージョンをGistに上げてあります。
登録:
投稿 (Atom)