diff --git a/source/apphelpers.pas b/source/apphelpers.pas index 2cb67f42b..299ec6eba 100644 --- a/source/apphelpers.pas +++ b/source/apphelpers.pas @@ -535,18 +535,28 @@ function Explode(Separator, Text: String): TStringList; @return string } function StrEllipsis(const S: String; MaxLen: Integer; FromLeft: Boolean=True): String; +var + CutPos: PtrInt; 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 UTF8Length(Result) <= MaxLen then + // A string with <= MaxLen bytes cannot contain more than MaxLen codepoints + if Length(Result) <= MaxLen then Exit; - if FromLeft then - Result := UTF8Copy(Result, 1, MaxLen) + '…' - else + if FromLeft then begin + CutPos := UTF8CodepointToByteIndex(PChar(Result), Length(Result), MaxLen); + if (CutPos < 0) or (CutPos >= Length(Result)) then + Exit; + SetLength(Result, CutPos); + Result := Result + '…'; + end else begin + if UTF8Length(Result) <= MaxLen then + Exit; Result := '…' + UTF8Copy(Result, UTF8Length(Result) - MaxLen + 1, MaxLen); + end; end;