unit Transformer;

interface

uses
	Windows, Classes, Contnrs, SysUtils, InSysUtils, Geomet, Complex;

type
	TTransformer = class
		procedure SetDestination(const aDest: TDPoint); virtual;
		function TransformPoint(const aPoint: TDPoint): TDPoint; virtual;
	end;

	TTranslationTransformer = class(TTransformer)
	private
		Source, Dest: TDPoint;
	public
		constructor Create(const aSource: TDPoint);
		procedure SetDestination(const aDest: TDPoint); override;
		function TransformPoint(const aPoint: TDPoint): TDPoint; override;
	end;

	TDumbTransformer = class(TTransformer)
	private
		Dest: TDPoint;
	public
		constructor Create(const aDest: TDPoint);
		procedure SetDestination(const aDest: TDPoint); override;
		function TransformPoint(const aPoint: TDPoint): TDPoint; override;
	end;

	TRotationTransformer = class(TTransformer)
	private
		Center: TDPoint;
		SourceCoordSys, DestCoordSys: TCoordSys;
	public
		constructor Create(const aCenter, aSource: TDPoint);
		procedure SetDestination(const aDest: TDPoint); override;
		function TransformPoint(const aPoint: TDPoint): TDPoint; override;
	end;

	TOrthoPointMover = class(TTranslationTransformer)
	private
		CoordSys: TCoordSys;
	public
		constructor Create(aContour: TContour; anIndex: Integer; aNext: Boolean);
		function TransformPoint(const aPoint: TDPoint): TDPoint; override;
	end;

	TVector = array [1..3] of Double;
	TMatrix = array [1..3] of TVector;

	TMatrixTransformer = object
		M: TMatrix;

		function TransformPoint(aPoint: TDPoint): TDPoint; overload;

		procedure Clear;
		procedure SetScaling(aDx, aDy: Double);
		procedure SetRotation(anAngle: Double);
		procedure SetTranslation(aSx, aSy: Double);
		procedure SetReflection(aX, aY: Boolean);
		procedure Append(aMatrix: TMatrix);
	end;

	TDXFormBase = object
		a{M11}, b{M21}, c{DX},
		d{M12}, e{M22}, f{DY}: Double;

		function Determinant: Double;
		function IsSingular: Boolean;
		procedure CheckSingular;
	end;

	TDXForm = object(TDXFormBase)
		procedure Assign(const aMatrix: TDXFormBase);

		procedure InitMapTri(const aDstP1, aDstP2, aDstP3: TDPoint);
		function  Mul(const m: TDXFormBase): TDXFormBase;
		function  Backward: TDXFormBase;
	end;

	TCoordSpace = object
	private
		FOrientationChanged: Boolean;

		procedure CalculateOrientationState;
	public
		DXFormTo,
		DXFormFrom: TDXForm;
		YMirror: Boolean;
		ScaleTo: TDPoint;

		function PointToSpace(const aP: TDPoint): TDPoint; overload;
		function PointToSpace(const aP: TPoint): TPoint; overload;
		function ConvexityToSpace(const aConvexity: Double): Double; overload;
		function PointFromSpace(const aP: TDPoint): TDPoint; overload;
		function PointFromSpace(const aP: TPoint): TPoint; overload;

		procedure MakeBackTransformation;

		function BoundsToSpace(const aBounds: TDRect): TDRect;
		function BoundsFromSpace(const aBounds: TDRect): TDRect;

		procedure Clear;
		procedure AddScaling(aX, aY: Double);
		procedure AddTranslation(aX, aY: Double);
		procedure AddRotation(anAngle: Double);
		procedure AddReflection(aX, aY: Boolean);
		procedure AddShear(aX, aY: Double);
		procedure AddRectToRect(const aSrc, aDst: TDRect; aProportional: Boolean);
		function  AddMapTri(const aSrcP1, aSrcP2, aSrcP3, aDstP1, aDstP2, aDstP3: TDPoint): Boolean; overload;
		function  AddMapTri(const aSrcP1, aSrcP2, aSrcP3, aDstP1, aDstP2, aDstP3: TPoint): Boolean; overload;
	end;

	TTransformItem = class;
	TTransformData = class
	private
		FList: TObjectList;

		FOnChanged: TNotifyEvent;
		FUpdateCount: Integer;
		FChanged: Boolean;

		function GetCount: Integer;
		function GetItem(Index: Integer): TTransformItem;

		procedure Changed;
	public
		constructor Create(anOnChanged: TNotifyEvent = nil);
		destructor Destroy; override;

		procedure BeginUpdate;
		procedure EndUpdate;

		procedure Assign(aData: TTransformData);

		procedure Clear;
		function  Add(anItem: TTransformItem): TTransformItem;
		procedure Remove(anItem: TTransformItem);

		procedure AddToCoordSpace(var aTransform: TCoordSpace);

		procedure LoadFromStream(aStream: TStream);
		procedure SaveToStream(aStream: TStream);

		procedure LoadFromStrings(aList: TStringList);
		procedure SaveToStrings(aList: TStringList);

		property Items[Index: Integer]: TTransformItem read GetItem; default;
		property Count: Integer read GetCount;
	end;

	TTransformItem = class
	public
		function  CreateCopy: TTransformItem; virtual; abstract;
		procedure AddTo(var aTransform: TCoordSpace); virtual; abstract;
		function  GetCaption: String; virtual; abstract;
		function  GetAsString: String; virtual; abstract;
		procedure SetAsString(const s: String); virtual; abstract;
	end;

	TTransformTranslationItem = class(TTransformItem)
	public
		X, Y: Double;

		function  CreateCopy: TTransformItem; override;

		procedure AddTo(var aTransform: TCoordSpace); override;
		function  GetCaption: String; override;
		function  GetAsString: String; override;
		procedure SetAsString(const s: String); override;
	end;

	TTransformScalingItem = class(TTransformItem)
	public
		X, Y: Double;

		constructor Create;
		function  CreateCopy: TTransformItem; override;

		procedure AddTo(var aTransform: TCoordSpace); override;
		function  GetCaption: String; override;
		function  GetAsString: String; override;
		procedure SetAsString(const s: String); override;
	end;

	TTransformRotationItem = class(TTransformItem)
	public
		Angle: Double;

		function  CreateCopy: TTransformItem; override;

		procedure AddTo(var aTransform: TCoordSpace); override;
		function GetCaption: String; override;
		function  GetAsString: String; override;
		procedure SetAsString(const s: String); override;
	end;


	function EmptyMatrix: TMatrix;
	function MxM(m1, m2: TMatrix): TMatrix;
	function MxV(m: TMatrix; v: TVector): TVector;
	function VxM(v: TVector; m: TMatrix): TVector;
	function P2V(p: TDPoint): TVector;
	function V2P(v: TVector): TDPoint;

implementation

uses
	Math;


function MxM(m1, m2: TMatrix): TMatrix;
var
	i, j, k: Integer;
begin
	for i := 1 to 3 do
		for j := 1 to 3 do begin
			Result[i, j] := 0;
			for k := 1 to 3 do
				Result[i, j] := Result[i, j] + m1[i, k]*m2[k, j]
		end
end;

function MxV(m: TMatrix; v: TVector): TVector;
var
	i, j: Integer;
begin
	for i := 1 to 3 do begin
		Result[i] := 0;
		for j := 1 to 3 do
			Result[i] := Result[i] + m[i, j]*v[j]
	end
end;

function VxM(v: TVector; m: TMatrix): TVector;
var
	i, j: Integer;
begin
	for i := 1 to 3 do begin
		Result[i] := 0;
		for j := 1 to 3 do
			Result[i] := Result[i] + m[j, i]*v[j]
	end
end;

function P2V(p: TDPoint): TVector;
begin
	Result[1] := p.x;
	Result[2] := p.y;
	Result[3] := 1;
end;

function V2P(v: TVector): TDPoint;
begin
	Result.x := v[1];
	Result.y := v[2];
end;

{ TTransformer }	

procedure TTransformer.SetDestination(const aDest: TDPoint);
begin
end;

function TTransformer.TransformPoint(const aPoint: TDPoint): TDPoint;
begin
	Result := aPoint
end;

constructor TTranslationTransformer.Create(const aSource: TDPoint);
begin
	Source := aSource
end;

procedure TTranslationTransformer.SetDestination(const aDest: TDPoint);
begin
	Dest := aDest
end;

function TTranslationTransformer.TransformPoint(const aPoint: TDPoint): TDPoint;
begin
	Result := aPoint;
	Result.Offset(Dest.x - Source.x, Dest.y - Source.y);
end;

constructor TDumbTransformer.Create(const aDest: TDPoint);
begin
	Dest := aDest
end;

procedure TDumbTransformer.SetDestination(const aDest: TDPoint);
begin
	Dest := aDest
end;

function TDumbTransformer.TransformPoint(const aPoint: TDPoint): TDPoint;
begin
	Result := Dest;
end;

constructor TRotationTransformer.Create(const aCenter, aSource: TDPoint);
begin
	Center := aCenter;
	SourceCoordSys.InitPoints(Center, aSource);
end;

procedure TRotationTransformer.SetDestination(const aDest: TDPoint);
begin
	DestCoordSys.InitPoints(Center, aDest);
end;

function TRotationTransformer.TransformPoint(const aPoint: TDPoint): TDPoint;
begin
	Result := SourceCoordSys.ToLocal(aPoint);
	DestCoordSys.MakeGlobal(Result);
end;

constructor TOrthoPointMover.Create(aContour: TContour; anIndex: Integer; aNext: Boolean);
var
	anIndex1, anIndex2: Integer;
begin
	anIndex1 := -1;
	if aNext then begin
		anIndex2 := aContour.GetNextPointIndex(anIndex);
		if anIndex2 > -1 then
			anIndex1 := aContour.GetNextPointIndex(anIndex2);
	end else begin
		anIndex2 := aContour.GetPrevPointIndex(anIndex);
		if anIndex2 > -1 then
			anIndex1 := aContour.GetPrevPointIndex(anIndex2);
	end;
	if anIndex2 > -1 then
		inherited Create(aContour.Commands[anIndex2].Point);
	CoordSys.SetInvalid;
	if anIndex1 > -1 then
		CoordSys.InitPoints(aContour.Commands[anIndex1].Point, aContour.Commands[anIndex2].Point)
end;

function TOrthoPointMover.TransformPoint(const aPoint: TDPoint): TDPoint;
begin
	if CoordSys.IsValid then begin
		SetDestination(CoordSys.ToGlobal(DPoint(CoordSys.ToLocal(Dest).x, 0)));
		Result := inherited TransformPoint(aPoint)
	end else
		Result := aPoint;
end;

const
	EM: TMatrix = (
		({a}1, {d}0, 0),
		({b}0, {e}1, 0),
		({c}0, {f}0, 1)
	);

function EmptyMatrix: TMatrix;
begin
	Result := EM;
end;

procedure TMatrixTransformer.Clear;
begin
	m := EM;
end;

procedure TMatrixTransformer.Append(aMatrix: TMatrix);
begin
	m := MxM(m, aMatrix);
end;

function TMatrixTransformer.TransformPoint(aPoint: TDPoint): TDPoint;
begin
//	Result := V2P(VxM(P2V(aPoint), m));
	Result.X := aPoint.X * M[1,1] + aPoint.Y * M[2,1] + M[3,1];
	Result.Y := aPoint.X * M[1,2] + aPoint.Y * M[2,2] + M[3,2];
end;

procedure TMatrixTransformer.SetScaling(aDx, aDy: Double);
var
	aM: TMatrix;
begin
	aM := EM;
	aM[1, 1] := aDx;
	aM[2, 2] := aDy;
	m := MxM(m, aM)
end;

procedure TMatrixTransformer.SetRotation(anAngle: Double);
var
	aCos, aSin: Extended;
	aM: TMatrix;
begin
	SinCos(anAngle, aSin, aCos);
	aM := EM;
	aM[1, 1] := aCos;
	aM[1, 2] := aSin;
	aM[2, 2] := aCos;
	aM[2, 1] := -aSin;
	m := MxM(m, aM)
end;

procedure TMatrixTransformer.SetTranslation(aSx, aSy: Double);
var
	aM: TMatrix;
begin
	aM := EM;
	aM[3, 1] := aSx;
	aM[3, 2] := aSy;
	m := MxM(m, aM)
end;

procedure TMatrixTransformer.SetReflection(aX, aY: Boolean);
var
	aM: TMatrix;
begin
	aM := EM;
	if aX then aM[1, 1] := -1 else aM[1, 1] := 1;
	if aY then aM[2, 2] := -1 else aM[2, 2] := 1;
	M := MxM(M, aM);
end;


//	DXFROM routins
const
	EmptyDXForm: TDXFORM = (
		a:1; b:0; c:0;
		d:0; e:1; f:0
	);


{ TDXFormBase }

procedure TDXFormBase.CheckSingular;
begin
	if IsSingular then
		raise Exception.Create('Singular matrix');
end;

function TDXFormBase.Determinant: Double;
begin
	Result := (a*e - d*b)
end;

function TDXFormBase.IsSingular: Boolean;
begin
	Result := Determinant = 0;
end;

{ TDXForm }

procedure TDXForm.InitMapTri(const aDstP1, aDstP2, aDstP3: TDPoint);
var
	aM: TDXForm;
begin
	aM.a := aDstP2.x - aDstP1.x;
	aM.d := aDstP2.y - aDstP1.y;
	aM.b := aDstP3.x - aDstP1.x;
	aM.e := aDstP3.y - aDstP1.y;
	aM.c := aDstP1.x;
	aM.f := aDstP1.y;
	Assign(aM);
end;

function TDXFORM.Mul(const m: TDXFORMBase): TDXFORMBase;
begin
	Result.a := a*m.a + d*m.b;
	Result.b := b*m.a + e*m.b;
	Result.c := c*m.a + f*m.b + m.c;
	Result.d := a*m.d + d*m.e;
	Result.e := b*m.d + e*m.e;
	Result.f := c*m.d + f*m.e + m.f;
end;

function TDXFORM.Backward: TDXFORMBase;
var
	aD: Double;
begin
	if IsSingular then
		Result := EmptyDXFORM
	else begin
		aD := Determinant;
		
		Result.a := e/aD;
		Result.b := -b/aD;
		Result.c := (b*f - c*e)/aD;
		Result.d := -d/aD;
		Result.e := a/aD;
		Result.f := (c*d - a*f)/aD;
	end;
end;

procedure TDXForm.Assign(const aMatrix: TDXFormBase);
begin
	a := aMatrix.a;
	b := aMatrix.b;
	c := aMatrix.c;
	d := aMatrix.d;
	e := aMatrix.e;
	f := aMatrix.f;
end;

{ TCoordSpace }

procedure TCoordSpace.MakeBackTransformation;
var
	aDX: TDXForm;
begin
	ScaleTo.X := 1/ScaleTo.X;
	ScaleTo.Y := 1/ScaleTo.Y;
	aDX := DXFormFrom;
	DXFormFrom := DXFormTo;
	DXFormTo := aDX;
end;

function TCoordSpace.PointToSpace(const aP: TDPoint): TDPoint;
begin
	with DXFormTo do begin
		Result.x := a*aP.x + b*aP.y + c;
		Result.y := d*aP.x + e*aP.y + f
	end;
end;

function TCoordSpace.PointFromSpace(const aP: TDPoint): TDPoint;
begin
	with DXFormFrom do begin
		Result.x := a*aP.x + b*aP.y + c;
		Result.y := d*aP.x + e*aP.y + f
	end;
end;

function TCoordSpace.PointFromSpace(const aP: TPoint): TPoint;
begin
	Result := PointFromDPoint(PointFromSpace(DPointFromPoint(aP)));
end;

function TCoordSpace.PointToSpace(const aP: TPoint): TPoint;
begin
	Result := PointFromDPoint(PointToSpace(DPointFromPoint(aP)));
end;

function TCoordSpace.ConvexityToSpace(const aConvexity: Double): Double;
begin
	if FOrientationChanged then
		Result := -aConvexity
	else
		Result := aConvexity;
end;

function TCoordSpace.BoundsToSpace(const aBounds: TDRect): TDRect;
var
	p: TDPoint;
begin
	p := PointToSpace(DPoint(aBounds.X1, aBounds.Y1));
	Result.Assign(p.X, p.Y, p.X, p.Y);
	Result.Expand(PointToSpace(DPoint(aBounds.X1, aBounds.Y2)));
	Result.Expand(PointToSpace(DPoint(aBounds.X2, aBounds.Y1)));
	Result.Expand(PointToSpace(DPoint(aBounds.X2, aBounds.Y2)));
end;

function TCoordSpace.BoundsFromSpace(const aBounds: TDRect): TDRect;
var
	p: TDPoint;
begin
	p := PointFromSpace(DPoint(aBounds.X1, aBounds.Y1));
	Result.Assign(p.X, p.Y, p.X, p.Y);
	Result.Expand(PointFromSpace(DPoint(aBounds.X1, aBounds.Y2)));
	Result.Expand(PointFromSpace(DPoint(aBounds.X2, aBounds.Y1)));
	Result.Expand(PointFromSpace(DPoint(aBounds.X2, aBounds.Y2)));
end;

procedure TCoordSpace.Clear;
begin
	DXFormTo := EmptyDXForm;
	DXFormFrom := EmptyDXForm;
	ScaleTo.Assign(1, 1);
	YMirror := False;
	FOrientationChanged := False;
end;

var 
	Scaling: TDXForm = (
		a:{aX}1; b:0; c:0;
		d:0; e:{aY}1; f:0);

procedure TCoordSpace.AddScaling(aX, aY: Double);
begin
	with Scaling do begin
		a := aX;
		e := aY;
	end;
	TDXFormBase(DXFormTo) := DXFormTo.Mul(Scaling);
	TDXFormBase(DXFormFrom) := DXFormTo.Backward;
	ScaleTo.X := Abs(ScaleTo.X * aX);
	ScaleTo.Y := Abs(ScaleTo.Y * aY);
	if aY < 0 then
		YMirror := not YMirror;

	CalculateOrientationState;
end;

var
	Translation: TDXForm = (
		a:1; b:0; c:{aX}0;
		d:0; e:1; f:{aY}0);

procedure TCoordSpace.AddTranslation(aX, aY: Double);
begin
	with Translation do begin
		c := aX;
		f := aY;
	end;
	TDXFormBase(DXFormTo) := DXFormTo.Mul(Translation);
	TDXFormBase(DXFormFrom) := DXFormTo.Backward;
end;

var
	Rotation: TDXForm = (
		a:1; b:1; c:0;
		d:1; e:1; f:0);

procedure TCoordSpace.AddRotation(anAngle: Double);
var
	aSin, aCos: Extended;
begin
	SinCos(anAngle, aSin, aCos);
	with Rotation do begin
		a := aCos;
		b := -aSin;
		d := aSin;
		e := aCos;
	end;
	TDXFormBase(DXFormTo) := DXFormTo.Mul(Rotation);
	TDXFormBase(DXFormFrom) := DXFormTo.Backward;
end;

var
	Reflection: TDXForm = (
		a:{aX}1; b:0; c:0;
		d:0; e:{aY}1; f:0);

procedure TCoordSpace.AddReflection(aX, aY: Boolean);
begin
	with Reflection do begin
		if aX then a := -1 else a := 1;
		if aY then e := -1 else e := 1;
	end;
	TDXFormBase(DXFormTo) := DXFormTo.Mul(Reflection);
	TDXFormBase(DXFormFrom) := DXFormTo.Backward;
	if aY then
		YMirror := not YMirror;

	CalculateOrientationState;	
end;

var
	Shear: TDXForm = (
		a:1; b:{aY}1; c:0;
		d:{aX}1; e:1; f:0);

procedure TCoordSpace.AddShear(aX, aY: Double);
begin
	with Shear do begin
		b := aY;
		d := aX;
	end;
	TDXFormBase(DXFormTo) := DXFormTo.Mul(Shear);
	TDXFormBase(DXFormFrom) := DXFormTo.Backward;
end;

(*	TDXFormBase = object
		a{M11}, b{M21}, c{DX},
		d{M12}, e{M22}, f{DY}: Double;
	end; *)

function TCoordSpace.AddMapTri(const aSrcP1, aSrcP2, aSrcP3, aDstP1,
	aDstP2, aDstP3: TDPoint): Boolean;
var
	a, b, c: TDXForm;
begin
	Result := False;
	a.InitMapTri(aSrcP1, aSrcP2, aSrcP3);
	if a.IsSingular then
		Exit;

	TDXFormBase(a) := a.Backward;

	b.InitMapTri(aDstP1, aDstP2, aDstP3);
	if b.IsSingular then
		Exit;

	TDXFormBase(c) := a.Mul(b);
	TDXFormBase(DXFormTo) := DXFormTo.Mul(c);
	TDXFormBase(DXFormFrom) := DXFormTo.Backward;

	{todo: нужно обновлять значения Scalig и YMirror}
	CalculateOrientationState;
	Result := True;
end;

function TCoordSpace.AddMapTri(const aSrcP1, aSrcP2, aSrcP3, aDstP1,
	aDstP2, aDstP3: TPoint): Boolean;
begin
	Result := AddMapTri(DPointFromPoint(aSrcP1), DPointFromPoint(aSrcP2), DPointFromPoint(aSrcP3),
		DPointFromPoint(aDstP1), DPointFromPoint(aDstP2), DPointFromPoint(aDstP3));
end;

procedure TCoordSpace.AddRectToRect(const aSrc, aDst: TDRect; aProportional: Boolean);
var
	aScaling: TDPoint;
begin
	AddTranslation(-aSrc.X1, -aSrc.Y1);
	aScaling.X := aDst.DX/aSrc.DX;
	aScaling.Y := aDst.DY/aSrc.DY;
	if aProportional then
		if aScaling.X < aScaling.Y then
			aScaling.Y := aScaling.X
		else
			aScaling.X := aScaling.Y;
	AddScaling(aScaling.X, aScaling.Y);
	AddTranslation(-aDst.X1, -aDst.Y1);
end;

procedure TCoordSpace.CalculateOrientationState;
begin
	FOrientationChanged := DXFormTo.Determinant < 0;
end;

{ TTransformData }

constructor TTransformData.Create(anOnChanged: TNotifyEvent = nil);
begin
	inherited Create;
	FList := TObjectList.Create(True);
	FOnChanged := anOnChanged;
end;

destructor TTransformData.Destroy;
begin
	FList.Free;
	inherited;
end;

procedure TTransformData.Assign(aData: TTransformData);
var
	i: Integer;
begin
	BeginUpdate;
	try
		Clear;
		for i := 0 to aData.Count - 1 do
			Add(aData[i].CreateCopy);
	finally
    EndUpdate;
	end;
end;

procedure TTransformData.Clear;
begin
	FList.Clear;
	Changed;
end;

function TTransformData.Add(anItem: TTransformItem): TTransformItem;
begin
	FList.Add(anItem);
	Result := anItem;
	Changed;
end;

function TTransformData.GetCount: Integer;
begin
	Result := FList.Count;
end;

function TTransformData.GetItem(Index: Integer): TTransformItem;
begin
	Result := TTransformItem(FList[Index]);
end;

procedure TTransformData.Remove(anItem: TTransformItem);
begin
	FList.Remove(anItem);
	Changed;
end;

procedure TTransformData.AddToCoordSpace(var aTransform: TCoordSpace);
var
	i: Integer;
begin
	for i := 0 to Count - 1 do
		Items[i].AddTo(aTransform);
end;

function TransformItemTypeToString(aTransformItem: TTransformItem): String;
begin
	if aTransformItem is TTransformTranslationItem then
		Result := 'T'
	else if aTransformItem is TTransformScalingItem then
		Result := 'S'
	else if aTransformItem is TTransformRotationItem then
		Result := 'R'
	else
		raise EInternal.Create('TransformItemTypeToString', 'Ошибка конвертации');
end;

function CreateTransformItemByType(const s: String): TTransformItem;
begin
	if s = 'T' then
		Result := TTransformTranslationItem.Create
	else if s = 'S' then
		Result := TTransformScalingItem.Create
	else if s = 'R' then
		Result := TTransformRotationItem.Create
	else
		raise EInternal.Create('CreateTransformItemByType', 'Ошибка конвертации');
end;

procedure TTransformData.LoadFromStream(aStream: TStream);
var
	aTransformItem: TTransformItem;
	aLexeme: String;
	c: Char;
begin
	BeginUpdate;
	try
		Clear;
		aStream.ReadBuffer(c, SizeOf(c));
		if c <> '<' then
			raise Exception.Create('Ошибка при считывании трансформации. Ожидается "<"');
		while True do begin
			aStream.ReadBuffer(c, SizeOf(c));
			if c = '>' then
				Break;

			aLexeme := c;
			while True do begin
				aStream.ReadBuffer(c, SizeOf(c));
				if aLexeme = '(' then
					Break;
				aLexeme := aLexeme + c;
			end;
			aTransformItem := CreateTransformItemByType(aLexeme);
			try
				aLexeme := '';
				while True do begin
					aStream.ReadBuffer(c, SizeOf(c));
					if c = ')' then
						break;
					aLexeme := aLexeme + c;
				end;
				aTransformItem.SetAsString(aLexeme);
				Add(aTransformItem);
			except
				aTransformItem.Free;
				raise;
			end;
		end;
	finally
    EndUpdate;
	end;
end;

procedure TTransformData.SaveToStream(aStream: TStream);
var
	aTransformItem: TTransformItem;
	aData: String;
	i: Integer;
begin
	aData := '<';
	for i := 0 to Count - 1 do begin
		aTransformItem := Items[i];
		aData := aData + TransformItemTypeToString(aTransformItem) +
			'(' + aTransformItem.GetAsString + ')';
	end;
	aData := aData + '>';
	aStream.WriteBuffer(Pointer(aData)^, Length(aData));
end;


procedure TTransformData.LoadFromStrings(aList: TStringList);
var
	aTransformItem: TTransformItem;

	aData: String;
	aTypeName: String;
	anItemData: String;

	i, j, k: Integer;
begin
	BeginUpdate;
	try
		Clear;
		for i := 0 to aList.Count - 1 do begin
			aData := aList[i];
			j := Pos('(', aData);
			if j = 0 then
				Continue;

			aTypeName := Copy(aData, 1, j-1);

			k := Pos(')', aData);
			if k = 0 then
				Continue;

			anItemData := Copy(aData, j + 1, k - j - 1);

			aTransformItem := CreateTransformItemByType(aTypeName);
			try
				aTransformItem.SetAsString(anItemData);
				Add(aTransformItem);
			except
				aTransformItem.Free;
				raise;
			end;
		end;
	finally
    EndUpdate;
	end;
end;

procedure TTransformData.SaveToStrings(aList: TStringList);
var
	i: Integer;
begin
	aList.Clear;
	for i := 0 to Count - 1 do
		aList.Add(format('%s(%s)', [TransformItemTypeToString(Items[i]), Items[i].GetAsString]));
end;

procedure TTransformData.BeginUpdate;
begin
	Inc(FUpdateCount);
end;

procedure TTransformData.EndUpdate;
begin
	Assert(FUpdateCount > 0);
	Dec(FUpdateCount);
	if FUpdateCount = 0 then
    Changed;
end;

procedure TTransformData.Changed;
begin
	if not Assigned(FOnChanged) then
		Exit;

	if FUpdateCount > 0 then
		FChanged := True
	else begin
		FChanged := False;
		FOnChanged(Self);
	end;
end;


{ TTransformTranslationItem }

function TTransformTranslationItem.CreateCopy: TTransformItem;
var
	anItem: TTransformTranslationItem;
begin
	anItem := TTransformTranslationItem.Create;
	anItem.X := X;
	anItem.Y := Y;
	Result := anItem;
end;


procedure TTransformTranslationItem.AddTo(var aTransform: TCoordSpace);
begin
	aTransform.AddTranslation(X, Y);
end;

function TTransformTranslationItem.GetAsString: String;
begin
	Result := Format('%g %g', [X, Y]);
end;

procedure TTransformTranslationItem.SetAsString(const s: String);
	var
	aPos: Integer;
begin
	aPos := Pos(' ', s);
	X := StringToFloat(Copy(s, 1, aPos - 1));
	Y := StringToFloat(Copy(s, aPos + 1, Length(s) - aPos));
end;

function TTransformTranslationItem.GetCaption: String;
begin
	Result := Format('Смещение: по X = %f; по Y = %f', [X, Y]);
end;

{ TTransformScalingItem }

constructor TTransformScalingItem.Create;
begin
	inherited Create;
	X := 1;
	Y := 1;
end;

procedure TTransformScalingItem.AddTo(var aTransform: TCoordSpace);
begin
	aTransform.AddScaling(X, Y);
end;

function TTransformScalingItem.GetAsString: String;
begin
	Result := Format('%g %g', [X, Y]);
end;

procedure TTransformScalingItem.SetAsString(const s: String);
var
	aPos: Integer;
begin
	aPos := Pos(' ', s);
	X := StringToFloat(Copy(s, 1, aPos - 1));
	Y := StringToFloat(Copy(s, aPos + 1, Length(s) - aPos));
end;

function TTransformScalingItem.CreateCopy: TTransformItem;
var
	anItem: TTransformScalingItem;
begin
	anItem := TTransformScalingItem.Create;
	anItem.X := X;
	anItem.Y := Y;
	Result := anItem;
end;

function TTransformScalingItem.GetCaption: String;
begin
	Result := Format('Увел./Сжатие: по X = %f; по Y = %f', [X, Y]);
end;

{ TTransformRotationItem }

procedure TTransformRotationItem.AddTo(var aTransform: TCoordSpace);
begin
	aTransform.AddRotation(Angle);
end;

function TTransformRotationItem.CreateCopy: TTransformItem;
var
	anItem: TTransformRotationItem;
begin
	anItem := TTransformRotationItem.Create;
	anItem.Angle := Angle;
	Result := anItem;
end;

function TTransformRotationItem.GetAsString: String;
begin
	Result := Format('%g', [Angle]);
end;

function TTransformRotationItem.GetCaption: String;
begin
	Result := Format('Поворот: Угол = %s', [AngleToStr(Angle)]);
end;

procedure TTransformRotationItem.SetAsString(const s: String);
begin
	Angle := StringToFloat(s);
end;





end.

