Added transform syncronization, an animation module and made the app work faster

This commit is contained in:
Alice Vital
2022-06-23 13:22:32 +03:00
parent 25a72c3c86
commit b1552d0ebc
98 changed files with 11657 additions and 7788 deletions

View File

@ -3,15 +3,9 @@ unit CurvesControl;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Math, ControlPoint,
Windows, SysUtils, Classes, Math, ControlPoint,
Graphics, Controls, Forms, Bezier, CustomDrawControl, Vcl.ExtCtrls;
const
point_size: double = 8;
accurancy: double = 3;
channel_count: integer = 4;
padding = 3;
const
MAX_CHANNEL = 3;
@ -73,6 +67,9 @@ implementation
uses Main, Editor, Mutate, Adjust;
const
point_size: double = 8;
constructor TCurvesControl.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
@ -94,6 +91,7 @@ begin
FrameCreate;
end;
destructor TCurvesControl.Destroy;
begin
FCP.Destroy;
@ -101,7 +99,7 @@ begin
end;
procedure TCurvesControl.SetCp(cp: TControlPoint);
var i, j: integer;
var i, j: smallint;
begin
FCP.Copy(cp, true);
for i := 0 to 3 do
@ -113,6 +111,7 @@ begin
Invalidate;
FFrame.Invalidate;
end;
procedure TCurvesControl.UpdateFlame;
begin
MainForm.StopThread;
@ -130,6 +129,7 @@ procedure TCurvesControl.FrameMouseLeave(Sender: TObject);
begin
FrameMouseUp(nil, mbLeft, [], 0, 0);
end;
procedure TCurvesControl.FrameMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
ps_half: double;
@ -152,12 +152,12 @@ begin
Break;
end;
end;
procedure TCurvesControl.FrameMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
m: BezierPoints;
tmp: BezierPoint;
i: Integer;
j: Integer;
i, j: shortint;
begin
if (y < 0) then Exit;
@ -194,10 +194,10 @@ begin
FCP.curvePoints[i,j].y := FPoints[i,j].y;
end;
FFrame.Refresh;
end;
end;
procedure TCurvesControl.FrameMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
FDragIndex := -1;
@ -207,9 +207,9 @@ begin
end;
procedure TCurvesControl.FrameCreate;
var i: integer;
var i: shortint;
begin
for i := 0 to channel_count - 1 do
for i := 0 to MAX_CHANNEL do
begin
FPoints[i][0].x := 0.00; FPoints[i][0].y := 0.00; FWeights[i][0] := 1;
FPoints[i][1].x := 0.00; FPoints[i][1].y := 0.00; FWeights[i][1] := 1;
@ -220,12 +220,14 @@ begin
FDragIndex := -1;
FDragging := false;
end;
procedure TCurvesControl.FrameResize(Sender: TObject);
begin
FRect.x0 := 0; FRect.y0 := 0;
FRect.x1 := self.Width - 1;
FRect.y1 := self.Height - 1;
end;
procedure TCurvesControl.FramePaint(Sender: TObject);
var
clientRect: TRect;
@ -261,7 +263,7 @@ begin
LineTo(Round(FRect.x1), Round(0.25 * y * FRect.y1));
end;
for i := 0 to channel_count - 1 do begin
for i := 0 to MAX_CHANNEL do begin
for j := 0 to 3 do
wsum[i] := wsum[i] + FWeights[i][j];
for j := 0 to 3 do
@ -282,9 +284,11 @@ begin
end;
procedure TCurvesControl.PaintCurve(Bitmap: TBitmap; c: integer; p: BezierPoints; w: BezierWeights; widgets: boolean);
const
step = 0.001; // AV
var
pos0, pos1: BezierPoint;
t, step: Double;
t: Double;
r, g, b: array [0 .. MAX_CHANNEL] of integer;
rgbv: integer;
begin
@ -303,7 +307,6 @@ begin
rgbv := RGB(r[c], g[c], b[c]);
t := 0;
step := 0.001;
BezierSolve(0, p, w, pos1);
pos0.x := 0; pos0.y := pos1.y;
@ -332,18 +335,19 @@ begin
LineTo(Round(FRect.x1), Round(pos0.y));
if widgets then begin
t := point_size / 2.0; // AV
Brush.Color := rgbv;
Ellipse(
Round(p[1].x - point_size / 2.0),
Round(p[1].y - point_size / 2.0),
Round(p[1].x + point_size / 2.0),
Round(p[1].y + point_size / 2.0)
Round(p[1].x - t),
Round(p[1].y - t),
Round(p[1].x + t),
Round(p[1].y + t)
);
Ellipse(
Round(p[2].x - point_size / 2.0),
Round(p[2].y - point_size / 2.0),
Round(p[2].x + point_size / 2.0),
Round(p[2].y + point_size / 2.0)
Round(p[2].x - t),
Round(p[2].y - t),
Round(p[2].x + t),
Round(p[2].y + t)
);
end;
end;
@ -355,12 +359,14 @@ begin
FChannelIndex := Integer(value);
FFrame.Refresh;
end;
procedure TCurvesControl.SetWeightLeft(value: double);
begin
FWeights[FChannelIndex][1] := value;
FCP.curveWeights[FChannelIndex][1] := value;
FFrame.Refresh;
end;
procedure TCurvesControl.SetWeightRight(value: double);
begin
FWeights[FChannelIndex][2] := value;
@ -372,10 +378,12 @@ function TCurvesControl.GetChannel: TCurvesChannel;
begin
Result := FActiveChannel;
end;
function TCurvesControl.GetWeightLeft: double;
begin
Result := FWeights[FChannelIndex][1];
end;
function TCurvesControl.GetWeightRight: double;
begin
Result := FWeights[FChannelIndex][2];

View File

@ -5,6 +5,7 @@
Apophysis "3D hack" Copyright (C) 2007-2008 Peter Sdobnov
Apophysis "7X" Copyright (C) 2009-2010 Georg Kiehne
Apophysis AV "Phoenix Edition" Copyright (C) 2021-2022 Alice V. Koryagina
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

337
System/MathExpressions.pas Normal file
View File

@ -0,0 +1,337 @@
{ Apophysis AV "Phoenix Edition" Copyright (C) 2021-2022 Alice V. Koryagina }
unit MathExpressions;
interface
uses
System.Rtti, System.SysUtils, System.Bindings.EvalProtocol,
System.Bindings.Evaluator, System.Bindings.EvalSys, System.Bindings.Methods;
function CalculateExpression(const Expr: string): string;
var
InDegrees: boolean;
implementation
uses
System.Math, Windows, Translation;
var
LScope: IScope;
function Sine: IInvokable;
begin
Result := MakeInvokable(
function(Args: TArray<IValue>): IValue
var
IAValue: IValue;
ANum: Double;
begin
// AV: check the number of passed parameters
if Length(Args) <> 1 then begin
MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
['Sin()', 1, Length(Args)])), 'Apophysis AV', 48);
exit;
end;
IAValue := Args[0];
try
ANum := IAValue.GetValue.AsExtended; // AV: check the parameter type
if InDegrees then // AV: translate the parameter into radians
ANum := DegToRad(ANum);
Exit(TValueWrapper.Create(RoundTo(sin(ANum), -6)));
except
MessageBox(0, PChar('Sin(): ' + TextByKey('formula-wrongdatatype')),
'Apophysis AV', 16);
end;
end
);
end;
function CoSine: IInvokable;
begin
Result := MakeInvokable(
function(Args: TArray<IValue>): IValue
var
IAValue: IValue;
ANum: Double;
begin
// AV: check the number of passed parameters
if Length(Args) <> 1 then begin
MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
['Cos()', 1, Length(Args)])), 'Apophysis AV', 48);
exit;
end;
IAValue := Args[0];
try
ANum := IAValue.GetValue.AsExtended;
if InDegrees then // AV: translate the parameter into radians
ANum := DegToRad(ANum);
Exit(TValueWrapper.Create(RoundTo(cos(ANum), -6)));
except
MessageBox(0, PChar('Cos(): ' + TextByKey('formula-wrongdatatype')),
'Apophysis AV', 16);
end;
end
);
end;
function ArcSine: IInvokable;
begin
Result := MakeInvokable(
function(Args: TArray<IValue>): IValue
var
IAValue: IValue;
AValue: Double;
begin
// AV: check the number of passed parameters
if Length(Args) <> 1 then begin
MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
['ArcSin()', 1, Length(Args)])), 'Apophysis AV', 48);
exit;
end;
IAValue := Args[0];
try
AValue := IAValue.GetValue.AsExtended;
if InRange(AValue, -1, 1) then
begin
AValue := arcsin(AValue);
if InDegrees then
AValue := RadToDeg(AValue);
Exit(TValueWrapper.Create(RoundTo(AValue, -6)));
end
else
MessageBox(0, PChar('ArcSin(): ' + TextByKey('formula-outofrange')),
'Apophysis AV', 48);
except
MessageBox(0, PChar('ArcSin(): ' + TextByKey('formula-wrongdatatype')),
'Apophysis AV', 16);
end;
end
);
end;
function ArcCoSine: IInvokable;
begin
Result := MakeInvokable(
function(Args: TArray<IValue>): IValue
var
IAValue: IValue;
AValue: Double;
begin
//AV: check the number of passed parameters
if Length(Args) <> 1 then begin
MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
['ArcCos()', 1, Length(Args)])), 'Apophysis AV', 48);
exit;
end;
IAValue := Args[0];
try
AValue := IAValue.GetValue.AsExtended;
if InRange(AValue, -1, 1) then
begin
AValue := arccos(AValue);
if InDegrees then
AValue := RadToDeg(AValue);
Exit(TValueWrapper.Create(RoundTo(AValue, -6)));
end
else
MessageBox(0, PChar('ArcCos(): ' + TextByKey('formula-outofrange')),
'Apophysis AV', 48);
except
MessageBox(0, PChar('ArcCos(): ' + TextByKey('formula-wrongdatatype')),
'Apophysis AV', 16);
end;
end
);
end;
function ArcTangentYX: IInvokable;
begin
Result := MakeInvokable(
function(Args: TArray<IValue>): IValue
var
IYValue, IXValue: IValue;
AValue: Double;
begin
// AV: check the number of passed parameters
if Length(Args) <> 2 then begin
MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
['ArcTan2()', 2, Length(Args)])), 'Apophysis AV', 48);
exit;
end;
IYValue := Args[0];
IXValue := Args[1];
try
AValue := arctan2(IYValue.GetValue.AsExtended,
IXValue.GetValue.AsExtended);
if InDegrees then
AValue := RadToDeg(AValue);
Exit(TValueWrapper.Create(RoundTo(AValue, -6)));
except
MessageBox(0, PChar('ArcTan2(): ' + TextByKey('common-invalidformat')),
'Apophysis AV', 16);
end;
end
);
end;
function SquareRoot: IInvokable;
begin
Result := MakeInvokable(
function(Args: TArray<IValue>): IValue
var
IAValue: IValue;
ANum: Double;
begin
// AV: check the number of passed parameters
if Length(Args) <> 1 then begin
MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
['Sqrt()', 1, Length(Args)])), 'Apophysis AV', 48);
exit;
end;
IAValue := Args[0];
try
ANum := IAValue.GetValue.AsExtended;
if ANum >= 0 then
Exit(TValueWrapper.Create(RoundTo(sqrt(ANum), -6)))
else
MessageBox(0, PChar('Sqrt(): ' + TextByKey('formula-unsigned')),
'Apophysis AV', 48);
except
MessageBox(0, PChar('Sqrt(): ' + TextByKey('formula-wrongdatatype')),
'Apophysis AV', 16);
end;
end
);
end;
function NatLog: IInvokable;
begin
Result := MakeInvokable(
function(Args: TArray<IValue>): IValue
var
IAValue: IValue;
ANum: Double;
begin
// AV: check the number of passed parameters
if Length(Args) <> 1 then begin
MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
['Ln()', 1, Length(Args)])), 'Apophysis AV', 48);
exit;
end;
IAValue := Args[0];
try
ANum := IAValue.GetValue.AsExtended;
if ANum > 0 then
Exit(TValueWrapper.Create(RoundTo(ln(ANum), -6)))
else
MessageBox(0, PChar('Ln(): ' + TextByKey('formula-unsigned')),
'Apophysis AV', 48);
except
MessageBox(0, PChar('Ln(): ' + TextByKey('formula-wrongdatatype')),
'Apophysis AV', 16);
end;
end
);
end;
function PowerXY: IInvokable;
begin
Result := MakeInvokable(
function(Args: TArray<IValue>): IValue
var
IYValue, IXValue: IValue;
ANum: Double;
begin
// AV: check the number of passed parameters
if Length(Args) <> 2 then begin
MessageBox(0, PChar(Format(TextByKey('formula-wrongargscount'),
['Power()', 2, Length(Args)])), 'Apophysis AV', 48);
exit;
end;
IXValue := Args[0];
IYValue := Args[1];
try
ANum := IXValue.GetValue.AsExtended;
if ANum >= 0 then
Result := TValueWrapper.Create(RoundTo(power(ANum,
IYValue.GetValue.AsExtended), -6))
else
MessageBox(0, PChar('Power(): ' + TextByKey('formula-unsigned')),
'Apophysis AV', 48);
except
MessageBox(0, PChar('Power(): ' + TextByKey('common-invalidformat')),
'Apophysis AV', 16);
end;
end
);
end;
procedure RegisterMathFunctions;
begin
TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
Sine, 'sin', 'sin', '', True, '', nil));
TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
CoSine, 'cos', 'cos', '', True, '', nil));
TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
ArcSine, 'arcsin', 'arcsin', '', True, '', nil));
TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
ArcCoSine, 'arccos', 'arccos', '', True, '', nil));
TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
ArcTangentYX, 'arctan2', 'arctan2', '', True, '', nil));
TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
SquareRoot, 'sqrt', 'sqrt', '', True, '', nil));
TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
PowerXY, 'power', 'power', '', True, '', nil));
TBindingMethodsFactory.RegisterMethod(TMethodDescription.Create(
NatLog, 'ln', 'ln', '', True, '', nil));
LScope := BasicOperators;
TDictionaryScope(LScope).Map.Add('pi', TValueWrapper.Create(pi));
TDictionaryScope(LScope).Map.Add('exp', TValueWrapper.Create(exp(1)));
// AV: add the registered methods
LScope := TNestedScope.Create(LScope, TBindingMethodsFactory.GetMethodScope);
end;
function CalculateExpression(const Expr: string): string;
var
LCompiledExpr : ICompiledBinding;
LResult : TValue;
begin
Result := '';
try
LCompiledExpr := Compile(Expr, LScope);
LResult := LCompiledExpr.Evaluate(LScope, nil, nil).GetValue;
if not LResult.IsEmpty then
Result := LResult.ToString;
except
Result := '';
MessageBox(0, PChar(TextByKey('formula-cannotevaluate')),
'Apophysis AV', 16);
end;
end;
///////////////////////////////////////////////////////////////////////////////
initialization
RegisterMathFunctions;
end.

View File

@ -33,27 +33,31 @@ begin
Regex.Subject := text;
if Regex.Match and (Regex.GroupCount >= group) then
Result := String(Regex.Groups[group])
Result := Regex.Groups[group]
else Result := def;
Regex.Free;
end;
function GetBoolPart(text, expression: string; group: integer; def: boolean): boolean;
begin
Result := GetFloatPart(text, expression, group, StrToFloat(IfThen(def, '1', '0'))) <> 0;
end;
function GetIntPart(text, expression: string; group: integer; def: integer): integer;
var str: string;
begin
str := GetStringPart(text, expression, group, '');
Result := StrToIntDef(str, def);
end;
function GetFloatPart(text, expression: string; group: integer; def: extended): extended;
var str: string;
begin
str := GetStringPart(text, expression, group, '');
Result := StrToFloatDef(str, def);
end;
function Get2IntPart(text, expression: string; group: integer; def: integer): T2Int;
const expr : string = '(\d+)\s+(\d+)';
var str, s1, s2: string;
@ -64,6 +68,7 @@ begin
Result.i1 := StrToIntDef(s1, def);
Result.i2 := StrToIntDef(s2, def);
end;
function Get2FloatPart(text, expression: string; group: integer; def: extended): T2Float;
const expr : string = '([\d.eE+-]+)\s+([\d.eE+-]+)';
var str, s1, s2: string;
@ -74,6 +79,7 @@ begin
Result.f1 := StrToFloatDef(s1, def);
Result.f2 := StrToFloatDef(s2, def);
end;
function GetRGBPart(text, expression: string; group: integer; def: integer): TRGB;
const expr : string = '(\d+)\s+(\d+)\s+(\d+)';
var str, s1, s2, s3: string;

View File

@ -1,633 +0,0 @@
{ unit sdStringTable
Author: Nils Haeck M.Sc. (n.haeck@simdesign.nl)
Original Date: 28 May 2007
Version: 1.1
Copyright (c) 2007 - 2010 Simdesign BV
It is NOT allowed under ANY circumstances to publish or copy this code
without accepting the license conditions in accompanying LICENSE.txt
first!
This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
ANY KIND, either express or implied.
Please visit http://www.simdesign.nl/xml.html for more information.
}
unit sdStringTable;
interface
{$i NativeXml.inc}
uses
Classes, SysUtils, Contnrs;
type
// A record describing a string by its first position and length (Count)
TsdStringRec = record
First: Pbyte;
Count: integer;
end;
// A string reference item used in string reference lists (do not use directly)
TsdRefString = class
private
FID: integer;
FFrequency: integer;
FFirst: Pbyte;
FCharCount: integer;
protected
procedure SetString(const SR: TsdStringRec);
function CompareToSR(const SR: TsdStringRec): integer;
function StringRec: TsdStringRec;
public
destructor Destroy; override;
function AsString: UTF8String;
property CharCount: integer read FCharCount;
property Frequency: integer read FFrequency;
end;
// A list of string reference items (do not use directly)
TsdRefStringList = class(TObjectList)
private
function GetItems(Index: integer): TsdRefString;
protected
// Assumes list is sorted by StringID
function IndexOfID(AID: integer; var Index: integer): boolean;
// Assumes list is sorted by string rec
function IndexOfSR(const AStringRec: TsdStringRec; var Index: integer): boolean;
public
property Items[Index: integer]: TsdRefString read GetItems; default;
end;
// A string table, holding a collection of unique strings, sorted in 2 ways
// for fast access. Strings can be added with AddString or AddStringRec,
// and should be updated with SetString. When a string is added or updated,
// an ID is returned which the application can use to retrieve the string,
// using GetString.
TsdStringTable = class(TPersistent)
private
FByID: TsdRefStringList;
FBySR: TsdRefStringList;
protected
procedure DecFrequency(AItem: TsdRefString; ByIdIndex: integer);
function NextUniqueID: integer;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
// Add a new string rec, return fresh ID or ID of existing item, and increase
// the existing item's ref count
function AddStringRec(const SR: TsdStringRec): integer;
// Add a new string S to the table, the function returns its ID.
function AddString(const S: UTF8String): integer;
// Get the refstring by ID
function ById(index: integer): TsdRefString;
// Delete refstring by ID
procedure Delete(ByIdIndex: integer);
// determine if the stringrec exists
function ExistStringRec(const SR: TsdStringRec): boolean;
// Get the string of refstring with ID
function GetString(ID: integer): UTF8String;
// Set the string value of refstring with ID.
procedure SetString(var ID: integer; const S: UTF8String);
// Number of refstrings
function StringCount: integer;
procedure SaveToFile(const AFileName: string);
procedure SaveToStream(S: TStream);
end;
{utility functions}
// convert a string into a string rec
function sdStringToSR(const S: Utf8String): TsdStringRec;
// convert a string rec into a string
function sdSRToString(const SR: TsdStringRec): Utf8String;
// compare two string recs. This is NOT an alphabetic compare. SRs are first
// compared by length, then by first byte, then last byte then second, then
// N-1, until all bytes are compared.
function sdCompareSR(const SR1, SR2: TsdStringRec): integer;
// compare 2 bytes
function sdCompareByte(Byte1, Byte2: byte): integer;
// compare 2 integers
function sdCompareInteger(Int1, Int2: integer): integer;
function sdUtf16ToUtf8Mem(Src: Pword; Dst: Pbyte; Count: integer): integer;
function sdUtf8ToUtf16Mem(var Src: Pbyte; Dst: Pword; Count: integer): integer;
procedure sdStreamWrite(S: TStream; const AString: AnsiString);
procedure sdStreamWriteStringRec(S: TStream; const AStringRec: TsdStringRec);
procedure sdStreamWriteRefString(S: TStream; ARefString: TsdRefString);
implementation
{ TsdRefString }
function TsdRefString.AsString: UTF8String;
begin
Result := sdSRToString(StringRec);
end;
function TsdRefString.CompareToSR(const SR: TsdStringRec): integer;
begin
if SR.Count = 0 then
begin
// shortcut
Result := 1;
exit;
end;
Result := sdCompareSR(StringRec, SR);
end;
destructor TsdRefString.Destroy;
begin
FreeMem(FFirst);
inherited;
end;
procedure TsdRefString.SetString(const SR: TsdStringRec);
begin
FCharCount := SR.Count;
ReallocMem(FFirst, FCharCount);
Move(SR.First^, FFirst^, FCharCount);
end;
function TsdRefString.StringRec: TsdStringRec;
begin
Result.First := FFirst;
Result.Count := FCharCount;
end;
{ TsdRefStringList }
function TsdRefStringList.GetItems(Index: integer): TsdRefString;
begin
Result := Get(Index);
end;
function TsdRefStringList.IndexOfID(AID: integer; var Index: integer): boolean;
var
Min, Max: integer;
begin
Result := False;
// Find position - binary method
Index := 0;
Min := 0;
Max := Count;
while Min < Max do
begin
Index := (Min + Max) div 2;
case sdCompareInteger(Items[Index].FID, AID) of
-1: Min := Index + 1;
0: begin
Result := True;
exit;
end;
1: Max := Index;
end;
end;
Index := Min;
end;
function TsdRefStringList.IndexOfSR(const AStringRec: TsdStringRec; var Index: integer): boolean;
var
Min, Max: integer;
SR: TsdStringRec;
begin
Result := False;
// Find position - binary method
Index := 0;
Min := 0;
Max := Count;
while Min < Max do
begin
Index := (Min + Max) div 2;
SR := TsdRefString(Get(Index)).StringRec;
case sdCompareSR(SR, AStringRec) of
-1: Min := Index + 1;
0: begin
Result := True;
exit;
end;
1: Max := Index;
end;
end;
Index := Min;
end;
{ TsdStringTable }
function TsdStringTable.AddString(const S: UTF8String): integer;
var
SR: TsdStringRec;
begin
SR := sdStringToSR(S);
Result := AddStringRec(SR);
end;
function TsdStringTable.AddStringRec(const SR: TsdStringRec): integer;
var
BySRIndex: integer;
Item: TsdRefString;
NewSR: TsdStringRec;
Res: boolean;
begin
// zero-length string
if SR.Count = 0 then
begin
Result := 0;
exit;
end;
// Try to find the new string
if FBySR.IndexOfSR(SR, BySRIndex) then
begin
Item := FBySR.Items[BySRIndex];
inc(Item.FFrequency);
Result := Item.FID;
exit;
end;
// Not found.. must make new item
Item := TsdRefString.Create;
Item.SetString(SR);
NewSR := Item.StringRec;
Item.FID := NextUniqueID;
FById.Add(Item);
Item.FFrequency := 1;
// debug:
//SetLength(Item.FValue, Item.FCount);
//Move(Item.FirstPtr(FBase)^, Item.FValue[1], Item.FCount);
// Insert in BySR lists
Res := FBySR.IndexOfSR(NewSR, BySRIndex);
assert(Res = False);
FBySR.Insert(BySRIndex, Item);
Result := Item.FID;
end;
function TsdStringTable.ById(index: integer): TsdRefString;
begin
Result := FById[Index];
end;
procedure TsdStringTable.Clear;
begin
FByID.Clear;
FBySR.Clear;
end;
constructor TsdStringTable.Create;
begin
inherited Create;
FByID := TsdRefStringList.Create(False);
FBySR := TsdRefStringList.Create(True);
end;
procedure TsdStringTable.DecFrequency(AItem: TsdRefString; ByIdIndex: integer);
var
BySRIndex: integer;
Res: boolean;
begin
dec(AItem.FFrequency);
assert(AItem.FFrequency >= 0);
if AItem.FFrequency = 0 then
begin
// We must remove it
FById.Delete(ByIdIndex);
Res := FBySR.IndexOfSR(AItem.StringRec, BySRIndex);
assert(Res = True);
FBySR.Delete(BySRIndex);
end;
end;
procedure TsdStringTable.Delete(ByIdIndex: integer);
var
Item: TsdRefString;
BySRIndex: integer;
Res: boolean;
begin
Item := FById[ByIdIndex];
if Item = nil then
exit;
FById.Delete(ByIdIndex);
Res := FBySR.IndexOfSR(Item.StringRec, BySRIndex);
assert(Res = True);
FBySR.Delete(BySRIndex);
end;
destructor TsdStringTable.Destroy;
begin
FreeAndNil(FByID);
FreeAndNil(FBySR);
inherited;
end;
function TsdStringTable.ExistStringRec(const SR: TsdStringRec): boolean;
var
BySRIndex: integer;
begin
// zero-length string
if SR.Count = 0 then
begin
Result := False;
exit;
end;
// Try to find the new string
Result := FBySR.IndexOfSR(SR, BySRIndex);
end;
function TsdStringTable.GetString(ID: integer): UTF8String;
var
Index, Count: integer;
Item: TsdRefString;
begin
if ID = 0 then
begin
Result := '';
exit;
end;
// Find the ID
if FByID.IndexOfID(ID, Index) then
begin
Item := FById[Index];
Count := Item.FCharCount;
SetLength(Result, Count);
Move(Item.FFirst^, Result[1], Count);
exit;
end;
Result := '';
end;
function TsdStringTable.NextUniqueID: integer;
begin
if FById.Count = 0 then
Result := 1
else
Result := FByID[FByID.Count - 1].FID + 1;
end;
procedure TsdStringTable.SaveToFile(const AFileName: string);
var
F: TFileStream;
begin
F := TFileStream.Create(AFileName, fmCreate);
try
SaveToStream(F);
finally
F.Free;
end;
end;
procedure TsdStringTable.SaveToStream(S: TStream);
var
i: integer;
R: UTF8String;
begin
for i := 0 to FBySR.Count - 1 do
begin
R := FBySR[i].AsString + #13#10;
S.Write(R[1], length(R));
end;
end;
procedure TsdStringTable.SetString(var ID: integer; const S: UTF8String);
var
ByIdIndex: integer;
Item: TsdRefString;
SR: TsdStringRec;
begin
// Make temp string record
SR := sdStringtoSR(S);
// Do we have a ref string with this ID?
if (ID > 0) and FByID.IndexOfID(ID, ByIdIndex) then
begin
// Is the string still the same?
Item := FById[ByIdIndex];
if Item.CompareToSR(SR) = 0 then
exit;
// The string changed..
DecFrequency(Item, ByIdIndex);
end;
ID := AddStringRec(SR);
end;
{utility functions}
function TsdStringTable.StringCount: integer;
begin
Result := FBySR.Count;
end;
function sdStringToSR(const S: UTF8String): TsdStringRec;
begin
Result.Count := length(S);
if Result.Count = 0 then
Result.First := nil
else
Result.First := @S[1];
end;
function sdSRToString(const SR: TsdStringRec): UTF8String;
begin
SetLength(Result, SR.Count);
if SR.Count > 0 then
Move(SR.First^, Result[1], SR.Count);
end;
function sdCompareByte(Byte1, Byte2: byte): integer;
begin
if Byte1 < Byte2 then
Result := -1
else
if Byte1 > Byte2 then
Result := 1
else
Result := 0;
end;
function sdCompareInteger(Int1, Int2: integer): integer;
begin
if Int1 < Int2 then
Result := -1
else
if Int1 > Int2 then
Result := 1
else
Result := 0;
end;
function sdCompareSR(const SR1, SR2: TsdStringRec): integer;
var
Count: integer;
First1, First2, Last1, Last2: Pbyte;
begin
// Compare string length first
Result := sdCompareInteger(SR1.Count, SR2.Count);
if Result <> 0 then
exit;
// Compare first
Result := sdCompareByte(SR1.First^, SR2.First^);
if Result <> 0 then
exit;
Count := SR1.Count;
// Setup First & Last pointers
First1 := SR1.First;
First2 := SR2.First;
Last1 := First1; inc(Last1, Count);
Last2 := First2; inc(Last2, Count);
// Compare each time last ptrs then first ptrs, until they meet in the middle
repeat
dec(Last1);
dec(Last2);
if First1 = Last1 then
exit;
Result := sdCompareByte(Last1^, Last2^);
if Result <> 0 then
exit;
inc(First1); inc(First2);
if First1 = Last1 then
exit;
Result := sdCompareByte(First1^, First2^);
if Result <> 0 then
exit;
until False;
end;
function sdUtf16ToUtf8Mem(Src: Pword; Dst: Pbyte; Count: integer): integer;
// Convert an Unicode (UTF16 LE) memory block to UTF8. This routine will process
// Count wide characters (2 bytes size) to Count UTF8 characters (1-3 bytes).
// Therefore, the block at Dst must be at least 1.5 the size of the source block.
// The function returns the number of *bytes* written.
var
W: word;
DStart: Pbyte;
begin
DStart := Dst;
while Count > 0 do
begin
W := Src^;
inc(Src);
if W <= $7F then
begin
Dst^ := byte(W);
inc(Dst);
end else
begin
if W > $7FF then
begin
Dst^ := byte($E0 or (W shr 12));
inc(Dst);
Dst^ := byte($80 or ((W shr 6) and $3F));
inc(Dst);
Dst^ := byte($80 or (W and $3F));
inc(Dst);
end else
begin // $7F < W <= $7FF
Dst^ := byte($C0 or (W shr 6));
inc(Dst);
Dst^ := byte($80 or (W and $3F));
inc(Dst);
end;
end;
dec(Count);
end;
Result := integer(Dst) - integer(DStart);
end;
function sdUtf8ToUtf16Mem(var Src: Pbyte; Dst: Pword; Count: integer): integer;
// Convert an UTF8 memory block to Unicode (UTF16 LE). This routine will process
// Count *bytes* of UTF8 (each character 1-3 bytes) into UTF16 (each char 2 bytes).
// Therefore, the block at Dst must be at least 2 times the size of Count, since
// many UTF8 characters consist of just one byte, and are mapped to 2 bytes. The
// function returns the number of *wide chars* written. Note that the Src block must
// have an exact number of UTF8 characters in it, if Count doesn't match then
// the last character will be converted anyway (going past the block boundary!)
var
W: word;
C: byte;
DStart: Pword;
SClose: Pbyte;
begin
DStart := Dst;
SClose := Src;
inc(SClose, Count);
while integer(Src) < integer(SClose) do
begin
// 1st byte
W := Src^;
inc(Src);
if W and $80 <> 0 then
begin
W := W and $3F;
if W and $20 <> 0 then
begin
// 2nd byte
C := Src^;
inc(Src);
if C and $C0 <> $80 then
// malformed trail byte or out of range char
Continue;
W := (W shl 6) or (C and $3F);
end;
// 2nd or 3rd byte
C := Src^;
inc(Src);
if C and $C0 <> $80 then
// malformed trail byte
Continue;
Dst^ := (W shl 6) or (C and $3F);
inc(Dst);
end else
begin
Dst^ := W;
inc(Dst);
end;
end;
Result := (integer(Dst) - integer(DStart)) div 2;
end;
procedure sdStreamWrite(S: TStream; const AString: AnsiString);
var
L: integer;
begin
L := Length(AString);
if L > 0 then
begin
S.Write(AString[1], L);
end;
end;
procedure sdStreamWriteStringRec(S: TStream; const AStringRec: TsdStringRec);
begin
S.Write(PAnsiChar(AStringRec.First)^, AStringRec.Count);
end;
procedure sdStreamWriteRefString(S: TStream; ARefString: TsdRefString);
begin
if ARefString = nil then
exit;
S.Write(PAnsiChar(ARefString.FFirst)^, ARefString.FCharCount);
end;
end.