From 18d443c47d91f169f58f32921974f0d4a5478ba7 Mon Sep 17 00:00:00 2001 From: twindev Date: Wed, 10 Jun 2026 17:40:44 +0900 Subject: [PATCH] fix: truncate strings on UTF-8 boundaries in StrEllipsis StrEllipsis truncated by byte count (SetLength/Copy), which can split a multi-byte UTF-8 character and produce invalid UTF-8. On the Cocoa widgetset such a string becomes a nil NSString, which crashes -[NSMenuItem initWithTitle:] when the result is used as a menu caption (e.g. the data grid quick-filter items built from long non-ASCII values). Use UTF8Length/UTF8Copy (LazUTF8, already used here) to cut on codepoint boundaries. --- source/apphelpers.pas | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/source/apphelpers.pas b/source/apphelpers.pas index c97f390ff..8068ba493 100644 --- a/source/apphelpers.pas +++ b/source/apphelpers.pas @@ -530,16 +530,17 @@ function Explode(Separator, Text: String): TStringList; } function StrEllipsis(const S: String; MaxLen: Integer; FromLeft: Boolean=True): String; begin + // Truncate on UTF-8 codepoint boundaries, not raw bytes. A byte-wise cut (SetLength/Copy) + // can split a multi-byte character and produce invalid UTF-8. On the Cocoa widgetset such a + // string converts to a nil NSString, which crashes -[NSMenuItem initWithTitle:] when the + // result is used as a menu caption (e.g. quick filter items). Result := S; - if Length(Result) <= MaxLen then + if UTF8Length(Result) <= MaxLen then Exit; - if FromLeft then begin - SetLength(Result, MaxLen); - Result := Result + '…'; - end else begin - Result := Copy(Result, Length(Result)-MaxLen, Length(Result)); - Result := '…' + Result; - end; + if FromLeft then + Result := UTF8Copy(Result, 1, MaxLen) + '…' + else + Result := '…' + UTF8Copy(Result, UTF8Length(Result) - MaxLen + 1, MaxLen); end;