394 lines
9.8 KiB
ObjectPascal
394 lines
9.8 KiB
ObjectPascal
|
|
|||
|
{******************************************}
|
|||
|
{ }
|
|||
|
{ FastReport VCL }
|
|||
|
{ CSS + SVG Transform }
|
|||
|
{ }
|
|||
|
{ Copyright (c) 1998-2021 }
|
|||
|
{ by Fast Reports Inc. }
|
|||
|
{ }
|
|||
|
{******************************************}
|
|||
|
unit frxCSSTransform;
|
|||
|
|
|||
|
interface
|
|||
|
|
|||
|
{$I frx.inc}
|
|||
|
|
|||
|
uses
|
|||
|
Types, Math,
|
|||
|
frxHelpers, frxSVGParse, frxSVGHelpers, frxSVGComponents;
|
|||
|
|
|||
|
type
|
|||
|
TCSSSVGTransformList = class (TOwnObjList)
|
|||
|
public
|
|||
|
constructor Create(const S: string);
|
|||
|
function CloneItem(i: Integer): TObject;
|
|||
|
function GetMatrix: TSVGTransform;
|
|||
|
procedure SetTransform(Matrix: TSVGTransform);
|
|||
|
procedure GetRawData(out RawData: TSVGLengthArray; out Direction: TSVGDirectionArray);
|
|||
|
procedure SetData(Data: TSingleDynArray);
|
|||
|
end;
|
|||
|
|
|||
|
implementation
|
|||
|
|
|||
|
uses
|
|||
|
SysUtils;
|
|||
|
|
|||
|
type
|
|||
|
TCSSTransformFunction = (
|
|||
|
tf_matrix,
|
|||
|
tf_rotate,
|
|||
|
tf_scale,
|
|||
|
tf_scaleX,
|
|||
|
tf_scaleY,
|
|||
|
tf_skew,
|
|||
|
tf_skewX,
|
|||
|
tf_skewY,
|
|||
|
tf_translate,
|
|||
|
tf_translateX,
|
|||
|
tf_translateY,
|
|||
|
tf_unknown
|
|||
|
);
|
|||
|
|
|||
|
const
|
|||
|
CSSTransformFunctionName: array[TCSSTransformFunction] of string = (
|
|||
|
'matrix',
|
|||
|
'rotate',
|
|||
|
'scale',
|
|||
|
'scaleX',
|
|||
|
'scaleY',
|
|||
|
'skew',
|
|||
|
'skewX',
|
|||
|
'skewY',
|
|||
|
'translate',
|
|||
|
'translateX',
|
|||
|
'translateY',
|
|||
|
'unknown'
|
|||
|
);
|
|||
|
|
|||
|
UseX = MaxSingle;
|
|||
|
|
|||
|
type
|
|||
|
TCSSTransformObj = class
|
|||
|
private
|
|||
|
FFunc: TCSSTransformFunction;
|
|||
|
FRawData: TSVGLengthArray;
|
|||
|
FData: TSingleDynArray;
|
|||
|
FDirection: TSVGDirectionArray;
|
|||
|
|
|||
|
procedure SetDirection(D: array of TSVGDirection);
|
|||
|
protected
|
|||
|
procedure ParseMatrix(const Values: string);
|
|||
|
procedure ParseRotate(const Values: string);
|
|||
|
procedure ParseScale(const Values: string; DefaultY: Single = UseX);
|
|||
|
procedure ParseScaleX(const Values: string);
|
|||
|
procedure ParseScaleY(const Values: string);
|
|||
|
procedure ParseSkew(const Values: string);
|
|||
|
procedure ParseSkewX(const Values: string);
|
|||
|
procedure ParseSkewY(const Values: string);
|
|||
|
procedure ParseTranslate(const Values: string);
|
|||
|
procedure ParseTranslateX(const Values: string);
|
|||
|
procedure ParseTranslateY(const Values: string);
|
|||
|
public
|
|||
|
constructor Create(const Name, Values: string);
|
|||
|
constructor CreateMatrix(Matrix: TSVGTransform);
|
|||
|
constructor CreateCopy(Twin: TCSSTransformObj);
|
|||
|
|
|||
|
function GetMatrix: TSVGTransform;
|
|||
|
procedure GetRawData(RawData: TSVGLengthArray; Direction: TSVGDirectionArray; var Total: Integer);
|
|||
|
procedure SetData(Data: TSingleDynArray; var Current: integer); { TODO : <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>? }
|
|||
|
end;
|
|||
|
|
|||
|
{ Utility routines }
|
|||
|
|
|||
|
function FuncByName(const Name: string): TCSSTransformFunction;
|
|||
|
var
|
|||
|
f: TCSSTransformFunction;
|
|||
|
begin
|
|||
|
Result := tf_unknown;
|
|||
|
for f := Low(f) to High(f) do
|
|||
|
if Name = CSSTransformFunctionName[f] then
|
|||
|
begin
|
|||
|
Result := f;
|
|||
|
Break;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{ TCSSSVGTransformObj }
|
|||
|
|
|||
|
constructor TCSSTransformObj.Create(const Name, Values: string);
|
|||
|
begin
|
|||
|
FFunc := FuncByName(Name);
|
|||
|
case FFunc of
|
|||
|
tf_matrix: ParseMatrix(Values);
|
|||
|
|
|||
|
tf_rotate: ParseRotate(Values);
|
|||
|
|
|||
|
tf_scale: ParseScale(Values);
|
|||
|
tf_scaleX: ParseScaleX(Values);
|
|||
|
tf_scaleY: ParseScaleY(Values);
|
|||
|
|
|||
|
tf_skew: ParseSkew(Values);
|
|||
|
tf_skewX: ParseSkewX(Values);
|
|||
|
tf_skewY: ParseSkewY(Values);
|
|||
|
|
|||
|
tf_translate: ParseTranslate(Values);
|
|||
|
tf_translateX: ParseTranslateX(Values);
|
|||
|
tf_translateY: ParseTranslateY(Values);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
constructor TCSSTransformObj.CreateCopy(Twin: TCSSTransformObj);
|
|||
|
var
|
|||
|
Len: Integer;
|
|||
|
begin
|
|||
|
FFunc := Twin.FFunc;
|
|||
|
|
|||
|
Len := Length(Twin.FRawData);
|
|||
|
|
|||
|
SetLength(FRawData, Len);
|
|||
|
Move(Twin.FRawData[0], FRawData[0], Len * SizeOf(FRawData[0]));
|
|||
|
|
|||
|
SetLength(FDirection, Len);
|
|||
|
Move(Twin.FDirection[0], FDirection[0], Len * SizeOf(FDirection[0]));
|
|||
|
end;
|
|||
|
|
|||
|
constructor TCSSTransformObj.CreateMatrix(Matrix: TSVGTransform);
|
|||
|
begin
|
|||
|
FFunc := tf_matrix;
|
|||
|
|
|||
|
SetLength(FRawData, 6);
|
|||
|
FRawData[0] := ToSVGLength(Matrix.a);
|
|||
|
FRawData[1] := ToSVGLength(Matrix.b);
|
|||
|
FRawData[2] := ToSVGLength(Matrix.c);
|
|||
|
FRawData[3] := ToSVGLength(Matrix.d);
|
|||
|
FRawData[4] := ToSVGLength(Matrix.e);
|
|||
|
FRawData[5] := ToSVGLength(Matrix.f);
|
|||
|
|
|||
|
SetDirection([sdNo, sdNo, sdNo, sdNo, sdHorizontal, sdVertical]);
|
|||
|
end;
|
|||
|
|
|||
|
function TCSSTransformObj.GetMatrix: TSVGTransform;
|
|||
|
begin
|
|||
|
case FFunc of
|
|||
|
tf_matrix: Result := tmMatrix(FData);
|
|||
|
|
|||
|
tf_rotate: Result := tmRotation(FData);
|
|||
|
|
|||
|
tf_scale: Result := tmScaling(FData);
|
|||
|
tf_scaleX: Result := tmScalingX(FData[0]);
|
|||
|
tf_scaleY: Result := tmScalingY(FData[0]);
|
|||
|
|
|||
|
tf_skew: Result := tmSkewing(FData);
|
|||
|
tf_skewX: Result := tmSkewingX(FData[0]);
|
|||
|
tf_skewY: Result := tmSkewingY(FData[0]);
|
|||
|
|
|||
|
tf_translate: Result := tmTranslation(FData);
|
|||
|
tf_translateX: Result := tmTranslation(FData[0]);
|
|||
|
tf_translateY: Result := tmTranslation(FData[0]);
|
|||
|
|
|||
|
tf_unknown: Result := tmIdentity;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.GetRawData(RawData: TSVGLengthArray; Direction: TSVGDirectionArray; var Total: Integer);
|
|||
|
var
|
|||
|
Len: Integer;
|
|||
|
begin
|
|||
|
Len := Length(FRawData);
|
|||
|
Move(FRawData[0], RawData[Total], Len * SizeOf(FRawData[0]));
|
|||
|
Move(FDirection[0], Direction[Total], Len * SizeOf(FDirection[0]));
|
|||
|
Total := Total + Len;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.ParseMatrix(const Values: string);
|
|||
|
begin
|
|||
|
ParseLengthArray(Values, FRawData);
|
|||
|
if Length(FRawData) <> 6 then
|
|||
|
FFunc := tf_unknown
|
|||
|
else
|
|||
|
SetDirection([sdNo, sdNo, sdNo, sdNo, sdHorizontal, sdVertical]);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.ParseRotate(const Values: string);
|
|||
|
begin
|
|||
|
ParseLengthArray(Values, FRawData);
|
|||
|
if not Length(FRawData) in [1, 3] then
|
|||
|
FFunc := tf_unknown
|
|||
|
else
|
|||
|
SetDirection([sdNo, sdHorizontal, sdVertical]);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.ParseScale(const Values: string; DefaultY: Single = UseX);
|
|||
|
begin
|
|||
|
ParseLengthArray(Values, FRawData);
|
|||
|
if not Length(FRawData) in [1, 2] then
|
|||
|
FFunc := tf_unknown
|
|||
|
else
|
|||
|
begin
|
|||
|
if Length(FRawData) = 1 then
|
|||
|
begin
|
|||
|
SetLength(FRawData, 2);
|
|||
|
if IsSameSingle(DefaultY, UseX) then
|
|||
|
FRawData[1] := FRawData[0]
|
|||
|
else
|
|||
|
FRawData[1] := ToSVGLength(DefaultY);
|
|||
|
end;
|
|||
|
SetDirection([sdHorizontal, sdVertical]);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.ParseScaleX(const Values: string);
|
|||
|
begin
|
|||
|
ParseLengthArray(Values, FRawData);
|
|||
|
if not Length(FRawData) in [1] then
|
|||
|
FFunc := tf_unknown
|
|||
|
else
|
|||
|
SetDirection([sdHorizontal]);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.ParseScaleY(const Values: string);
|
|||
|
begin
|
|||
|
ParseLengthArray(Values, FRawData);
|
|||
|
if not Length(FRawData) in [1] then
|
|||
|
FFunc := tf_unknown
|
|||
|
else
|
|||
|
SetDirection([sdVertical]);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.ParseSkew(const Values: string);
|
|||
|
begin
|
|||
|
ParseScale(Values, 0);
|
|||
|
SetDirection([sdNo, sdNo]);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.ParseSkewX(const Values: string);
|
|||
|
begin
|
|||
|
ParseScaleX(Values);
|
|||
|
SetDirection([sdNo, sdNo]);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.ParseSkewY(const Values: string);
|
|||
|
begin
|
|||
|
ParseScaleY(Values);
|
|||
|
SetDirection([sdNo, sdNo]);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.ParseTranslate(const Values: string);
|
|||
|
begin
|
|||
|
ParseScale(Values, 0);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.ParseTranslateX(const Values: string);
|
|||
|
begin
|
|||
|
ParseScaleX(Values);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.ParseTranslateY(const Values: string);
|
|||
|
begin
|
|||
|
ParseScaleY(Values);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.SetData(Data: TSingleDynArray; var Current: integer);
|
|||
|
var
|
|||
|
Len, i: Integer;
|
|||
|
begin
|
|||
|
Len := Length(FRawData); // !
|
|||
|
SetLength(FData, Len);
|
|||
|
Move(Data[Current], FData[0], Len * SizeOf(FData[0]));
|
|||
|
Current := Current + Len;
|
|||
|
|
|||
|
case FFunc of
|
|||
|
tf_rotate:
|
|||
|
FData[0] := DegToRad(FData[0]);
|
|||
|
tf_skew, tf_skewX, tf_skewY:
|
|||
|
for i := 0 to Len - 1 do
|
|||
|
FData[i] := Tan(DegToRad(FData[i]));
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{ TCSSSVGTransformList }
|
|||
|
|
|||
|
function TCSSSVGTransformList.CloneItem(i: Integer): TObject;
|
|||
|
begin
|
|||
|
Result := TCSSTransformObj.CreateCopy(TCSSTransformObj(Items[i]));
|
|||
|
end;
|
|||
|
|
|||
|
constructor TCSSSVGTransformList.Create(const S: string);
|
|||
|
var
|
|||
|
Start, Stop: Integer;
|
|||
|
st, FunctionName, Values: string;
|
|||
|
TransformObj: TCSSTransformObj;
|
|||
|
begin
|
|||
|
inherited Create;
|
|||
|
st := Trim(S);
|
|||
|
while st <> '' do
|
|||
|
begin
|
|||
|
Start := Pos('(', st);
|
|||
|
Stop := Pos(')', st);
|
|||
|
if (Start = 0) or (Stop = 0) then
|
|||
|
Exit;
|
|||
|
FunctionName := Copy(st, 1, Start - 1);
|
|||
|
Values := Trim(Copy(st, Start + 1, Stop - Start - 1));
|
|||
|
Values := StringReplace(Values, ' ', ',', [rfReplaceAll]);
|
|||
|
|
|||
|
TransformObj := TCSSTransformObj.Create(FunctionName, Values);
|
|||
|
if TransformObj.FFunc = tf_unknown then
|
|||
|
TransformObj.Free
|
|||
|
else
|
|||
|
Add(TransformObj);
|
|||
|
|
|||
|
st := Trim(Copy(st, Stop + 1, Length(st)));
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TCSSSVGTransformList.GetMatrix: TSVGTransform;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
Result := tmIdentity;
|
|||
|
for i := Count - 1 downto 0 do
|
|||
|
Result := tmMultiply(Result, TCSSTransformObj(Items[i]).GetMatrix);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSSVGTransformList.GetRawData(out RawData: TSVGLengthArray; out Direction: TSVGDirectionArray);
|
|||
|
var
|
|||
|
i, Total: Integer;
|
|||
|
begin
|
|||
|
Total := 0;
|
|||
|
SetLength(RawData, 6 * Count); // max possible length
|
|||
|
SetLength(Direction, Length(RawData));
|
|||
|
|
|||
|
for i := 0 to Count - 1 do
|
|||
|
TCSSTransformObj(Items[i]).GetRawData(RawData, Direction, Total);
|
|||
|
|
|||
|
SetLength(RawData, Total); // actual length
|
|||
|
SetLength(Direction, Length(RawData));
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSSVGTransformList.SetData(Data: TSingleDynArray);
|
|||
|
var
|
|||
|
i, Current: Integer;
|
|||
|
begin
|
|||
|
Current := 0;
|
|||
|
|
|||
|
for i := 0 to Count - 1 do
|
|||
|
TCSSTransformObj(Items[i]).SetData(Data, Current);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSSVGTransformList.SetTransform(Matrix: TSVGTransform);
|
|||
|
begin
|
|||
|
Add(TCSSTransformObj.CreateMatrix(Matrix));
|
|||
|
end;
|
|||
|
|
|||
|
procedure TCSSTransformObj.SetDirection(D: array of TSVGDirection);
|
|||
|
var
|
|||
|
Len: Integer;
|
|||
|
begin
|
|||
|
Len := Length(D);
|
|||
|
SetLength(FDirection, Len);
|
|||
|
Move(D[0], FDirection[0], Len * SizeOf(D[0]));
|
|||
|
end;
|
|||
|
|
|||
|
end.
|