Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions source/shared/ICU.pas
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ implementation
InitLock: TRTLCriticalSection;
{$IFDEF LINUX}
UCHandle: TLibHandle;
ICUVersionSuffix: string;
{$ENDIF}

{$IFDEF LINUX}
Expand Down Expand Up @@ -66,6 +67,7 @@ function TryLoadLinuxICU(out AHandle: TLibHandle): Boolean;
AHandle := NilHandle;
Continue;
end;
ICUVersionSuffix := '_' + IntToStr(Version);
Result := True;
Exit;
end;
Expand All @@ -81,7 +83,10 @@ function TryLoadLinuxICU(out AHandle: TLibHandle): Boolean;
AHandle := NilHandle;
end
else
begin
ICUVersionSuffix := '';
Result := True;
end;
end;
end;
{$ENDIF}
Expand Down Expand Up @@ -134,14 +139,29 @@ function ICULibraryAvailable: Boolean;
function ICUGetProcAddress(const AName: string): Pointer;
var
Handle: TLibHandle;
{$IFDEF LINUX}
VersionedName: string;
{$ENDIF}
begin
Result := nil;
if not TryGetICULibraryHandle(Handle) then
Exit;
Result := GetProcAddress(Handle, AName);
{$IFDEF LINUX}
if (Result = nil) and (ICUVersionSuffix <> '') then
begin
VersionedName := AName + ICUVersionSuffix;
Result := GetProcAddress(Handle, VersionedName);
end;
if (Result = nil) and (UCHandle <> NilHandle) then
begin
Result := GetProcAddress(UCHandle, AName);
if (Result = nil) and (ICUVersionSuffix <> '') then
begin
VersionedName := AName + ICUVersionSuffix;
Result := GetProcAddress(UCHandle, VersionedName);
end;
end;
{$ENDIF}
end;

Expand All @@ -152,6 +172,7 @@ initialization
LoadSucceeded := False;
{$IFDEF LINUX}
UCHandle := NilHandle;
ICUVersionSuffix := '';
{$ENDIF}

finalization
Expand Down
197 changes: 196 additions & 1 deletion source/shared/IntlICU.pas
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,24 @@ implementation
UNUM_PERCENT_STYLE = 3;
UNUM_SCIENTIFIC_STYLE = 4;
UNUM_PATTERN_DECIMAL = 0;
UNUM_GROUPING_USED = 1;
UNUM_MAX_INTEGER_DIGITS = 3;
UNUM_MIN_INTEGER_DIGITS = 4;
UNUM_MAX_FRACTION_DIGITS = 6;
UNUM_MIN_FRACTION_DIGITS = 7;
UNUM_ROUNDING_MODE = 11;
UNUM_MIN_SIGNIFICANT_DIGITS = 23;
UNUM_MAX_SIGNIFICANT_DIGITS = 24;
UNUM_ROUND_CEILING = 0;
UNUM_ROUND_FLOOR = 1;
UNUM_ROUND_DOWN = 2;
UNUM_ROUND_UP = 3;
UNUM_ROUND_HALFEVEN = 4;
UNUM_ROUND_HALFDOWN = 5;
UNUM_ROUND_HALFUP = 6;
UNUM_ROUND_HALF_CEILING = 9;
UNUM_ROUND_HALF_FLOOR = 10;
UNUM_DATTR_ROUNDING_INCREMENT = 0;
UDAT_FULL = 0;
UDAT_LONG = 1;
UDAT_MEDIUM = 2;
Expand Down Expand Up @@ -173,6 +191,11 @@ implementation
var AStatus: TICUErrorCode): LongInt; cdecl;
TUnumSetAttribute = procedure(AFormat: Pointer; AAttr: LongInt;
ANewValue: LongInt); cdecl;
TUnumSetDoubleAttribute = procedure(AFormat: Pointer; AAttr: LongInt;
ANewValue: Double); cdecl;
TUnumApplyPattern = procedure(AFormat: Pointer; ALocalized: ByteBool;
const APattern: PUChar; APatternLength: LongInt;
AParseError: Pointer; var AStatus: TICUErrorCode); cdecl;
TUnumSetTextAttribute = procedure(AFormat: Pointer; ATag: LongInt;
const ANewValue: PUChar; ANewValueLength: LongInt;
var AStatus: TICUErrorCode); cdecl;
Expand Down Expand Up @@ -246,6 +269,8 @@ TIntlICUFunctions = record
UnumClose: TUnumClose;
UnumFormatDouble: TUnumFormatDouble;
UnumSetAttribute: TUnumSetAttribute;
UnumSetDoubleAttribute: TUnumSetDoubleAttribute;
UnumApplyPattern: TUnumApplyPattern;
UnumSetTextAttribute: TUnumSetTextAttribute;
UdatOpen: TUdatOpen;
UdatClose: TUdatClose;
Expand Down Expand Up @@ -380,6 +405,10 @@ function TryLoadIntlFunctions(const AHandle: TLibHandle): Boolean;

S := ResolveSymbol(AHandle, 'unum_setAttribute');
if Assigned(S) then F.UnumSetAttribute := TUnumSetAttribute(S);
S := ResolveSymbol(AHandle, 'unum_setDoubleAttribute');
if Assigned(S) then F.UnumSetDoubleAttribute := TUnumSetDoubleAttribute(S);
S := ResolveSymbol(AHandle, 'unum_applyPattern');
if Assigned(S) then F.UnumApplyPattern := TUnumApplyPattern(S);
S := ResolveSymbol(AHandle, 'unum_setTextAttribute');
if Assigned(S) then F.UnumSetTextAttribute := TUnumSetTextAttribute(S);
S := ResolveSymbol(AHandle, 'uplrules_open');
Expand Down Expand Up @@ -698,6 +727,168 @@ function NumberStyleToICU(AStyle: TIntlNumberStyle): LongInt;
end;
end;

function RoundingModeToICU(AMode: TIntlNumberRoundingMode): LongInt;
begin
case AMode of
inrmCeil: Result := UNUM_ROUND_CEILING;
inrmFloor: Result := UNUM_ROUND_FLOOR;
inrmExpand: Result := UNUM_ROUND_UP;
inrmTrunc: Result := UNUM_ROUND_DOWN;
inrmHalfCeil: Result := UNUM_ROUND_HALF_CEILING;
inrmHalfFloor: Result := UNUM_ROUND_HALF_FLOOR;
inrmHalfExpand: Result := UNUM_ROUND_HALFUP;
inrmHalfTrunc: Result := UNUM_ROUND_HALFDOWN;
inrmHalfEven: Result := UNUM_ROUND_HALFEVEN;
else
Result := UNUM_ROUND_HALFUP;
end;
end;

procedure ApplySignificantDigitsPattern(AFormatter: Pointer;
AMinSig, AMaxSig: Integer);
var
Pattern: UnicodeString;
Status: TICUErrorCode;
begin
if not Assigned(IntlFunctions.UnumApplyPattern) then
Exit;
if AMinSig < 1 then AMinSig := 1;
if AMaxSig < AMinSig then AMaxSig := AMinSig;
Pattern := UnicodeString(StringOfChar('@', AMinSig) +
StringOfChar('#', AMaxSig - AMinSig));
Status := ICU_SUCCESS;
IntlFunctions.UnumApplyPattern(AFormatter, False,
PWideChar(Pattern), Length(Pattern), nil, Status);
end;

procedure ConfigureNumberFormatter(AFormatter: Pointer;
const AOptions: TIntlNumberFormatOptions);
var
MinSig, MaxSig: Integer;
begin
if not Assigned(IntlFunctions.UnumSetAttribute) then
Exit;

if (AOptions.MinimumSignificantDigits > 0) or
(AOptions.MaximumSignificantDigits > 0) then
begin
MinSig := AOptions.MinimumSignificantDigits;
MaxSig := AOptions.MaximumSignificantDigits;
if MinSig < 1 then MinSig := 1;
if MaxSig < 1 then MaxSig := 21;
ApplySignificantDigitsPattern(AFormatter, MinSig, MaxSig);
end
else
begin
if AOptions.MinimumFractionDigits >= 0 then
IntlFunctions.UnumSetAttribute(AFormatter,
UNUM_MIN_FRACTION_DIGITS, AOptions.MinimumFractionDigits);
if AOptions.MaximumFractionDigits >= 0 then
IntlFunctions.UnumSetAttribute(AFormatter,
UNUM_MAX_FRACTION_DIGITS, AOptions.MaximumFractionDigits);
end;

IntlFunctions.UnumSetAttribute(AFormatter,
UNUM_MIN_INTEGER_DIGITS, AOptions.MinimumIntegerDigits);

if AOptions.UseGrouping = inugFalse then
IntlFunctions.UnumSetAttribute(AFormatter, UNUM_GROUPING_USED, 0);

if AOptions.RoundingIncrement > 1 then
IntlFunctions.UnumSetAttribute(AFormatter,
UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP)
else
IntlFunctions.UnumSetAttribute(AFormatter,
UNUM_ROUNDING_MODE, RoundingModeToICU(AOptions.RoundingMode));
end;

function ApplyRoundingIncrement(AValue: Double;
const AOptions: TIntlNumberFormatOptions): Double;
var
Scale, ScaledInt, Remainder: Double;
Lower, Upper: Double;
I, Inc: Integer;
IsNeg: Boolean;
begin
Result := AValue;
Inc := AOptions.RoundingIncrement;
if Inc <= 1 then
Exit;
if AOptions.MaximumFractionDigits < 0 then
Exit;

Scale := 1.0;
for I := 1 to AOptions.MaximumFractionDigits do
Scale := Scale * 10.0;

IsNeg := AValue < 0;
ScaledInt := Abs(AValue) * Scale;
if Abs(ScaledInt - System.Round(ScaledInt)) < 1e-6 then
ScaledInt := System.Round(ScaledInt);
Remainder := ScaledInt - Trunc(ScaledInt / Inc) * Inc;
if Abs(Remainder - Inc) < 1e-9 then
Remainder := 0;

if Remainder = 0 then
Exit;

Lower := ScaledInt - Remainder;
Upper := Lower + Inc;

case AOptions.RoundingMode of
inrmCeil:
if IsNeg then ScaledInt := Lower else ScaledInt := Upper;
inrmFloor:
if IsNeg then ScaledInt := Upper else ScaledInt := Lower;
inrmTrunc:
ScaledInt := Lower;
inrmExpand:
ScaledInt := Upper;
inrmHalfExpand:
if Remainder * 2 >= Inc then ScaledInt := Upper else ScaledInt := Lower;
inrmHalfTrunc:
if Remainder * 2 > Inc then ScaledInt := Upper else ScaledInt := Lower;
inrmHalfEven:
begin
if Remainder * 2 > Inc then
ScaledInt := Upper
else if Remainder * 2 < Inc then
ScaledInt := Lower
else if Trunc(Lower / Inc) mod 2 = 0 then
ScaledInt := Lower
else
ScaledInt := Upper;
end;
inrmHalfCeil:
begin
if Remainder * 2 > Inc then
ScaledInt := Upper
else if Remainder * 2 < Inc then
ScaledInt := Lower
else if IsNeg then
ScaledInt := Lower
else
ScaledInt := Upper;
end;
inrmHalfFloor:
begin
if Remainder * 2 > Inc then
ScaledInt := Upper
else if Remainder * 2 < Inc then
ScaledInt := Lower
else if IsNeg then
ScaledInt := Upper
else
ScaledInt := Lower;
end;
end;

if IsNeg then
Result := -(ScaledInt / Scale)
else
Result := ScaledInt / Scale;
end;

function TryICUFormatNumber(const ALocale: string; AValue: Double;
const AOptions: TIntlNumberFormatOptions; out AFormatted: string): Boolean;
var
Expand All @@ -707,6 +898,7 @@ function TryICUFormatNumber(const ALocale: string; AValue: Double;
ResultLen: LongInt;
LocaleAnsi: AnsiString;
ICUStyle: LongInt;
FormattedValue: Double;
begin
Result := False;
AFormatted := '';
Expand All @@ -724,9 +916,12 @@ function TryICUFormatNumber(const ALocale: string; AValue: Double;
Exit;

try
ConfigureNumberFormatter(Formatter, AOptions);
FormattedValue := ApplyRoundingIncrement(AValue, AOptions);

FillChar(Buffer, SizeOf(Buffer), 0);
Status := ICU_SUCCESS;
ResultLen := IntlFunctions.UnumFormatDouble(Formatter, AValue,
ResultLen := IntlFunctions.UnumFormatDouble(Formatter, FormattedValue,
@Buffer[0], FORMAT_BUFFER_CAPACITY, nil, Status);
if not ICUSucceeded(Status) or (ResultLen <= 0) then
Exit;
Expand Down
4 changes: 2 additions & 2 deletions source/units/Goccia.Intl.Helpers.pas
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function FormatPartsToArray(const AParts: TIntlFormatPartArray): TGocciaArrayVal
// Reads a string-valued property from AOptions. Returns True and sets AValue
// when the property exists and is not undefined; returns False otherwise.
function TryReadStringOption(const AOptions: TGocciaObjectValue;
const AName: string; out AValue: string): Boolean;
const AName: string; var AValue: string): Boolean;

// Like TryReadStringOption but additionally rejects NUL characters via
// ThrowRangeError (SErrorIntlInvalidOption).
Expand Down Expand Up @@ -49,7 +49,7 @@ function FormatPartsToArray(const AParts: TIntlFormatPartArray): TGocciaArrayVal
end;

function TryReadStringOption(const AOptions: TGocciaObjectValue;
const AName: string; out AValue: string): Boolean;
const AName: string; var AValue: string): Boolean;
var
V: TGocciaValue;
begin
Expand Down
Loading
Loading