Merged revision(s) 51004 #884fddbf00, 51021 #3cab480c11, 51058 #c224456e7f, 51060 #c689e1582c from trunk:

fpvectorial: Correct calculation of ellipse center of elliptical arc path segment
........
fpvectorial: Fix TPath to avoid rendering of internal lines for bezier segments. Fix elliptic path segment with rotated axis.
........
fpvectorial: Reorganize TPath.Render. Support polygon even-odd and non-zero winding rules for brush fill.
........
fpvectorial: Fix svg reader memory leaks related to BrushDefs and tokenizer. Declare package as runtime package.
........

git-svn-id: branches/fixes_1_6@51074 -
This commit is contained in:
maxim 2015-12-28 22:11:54 +00:00
parent b2b3cd958d
commit 2dc436febb
5 changed files with 785 additions and 111 deletions

View File

@ -24,6 +24,7 @@ unit fpvectorial;
{.$define FPVECTORIAL_TOCANVAS_DEBUG} {.$define FPVECTORIAL_TOCANVAS_DEBUG}
{.$define FPVECTORIAL_DEBUG_BLOCKS} {.$define FPVECTORIAL_DEBUG_BLOCKS}
{$define FPVECTORIAL_AUTOFIT_DEBUG} {$define FPVECTORIAL_AUTOFIT_DEBUG}
{.$define FPVECTORIAL_TOCANVAS_ELLIPSE_VISUALDEBUG}
interface interface
@ -100,6 +101,7 @@ const
// Convenience constant to convert text size points to mm // Convenience constant to convert text size points to mm
FPV_TEXT_POINT_TO_MM = 0.35278; FPV_TEXT_POINT_TO_MM = 0.35278;
TWO_PI = 2.0 * pi;
type type
TvCustomVectorialWriter = class; TvCustomVectorialWriter = class;
@ -272,6 +274,9 @@ type
P3DPoint = ^T3DPoint; P3DPoint = ^T3DPoint;
T3DPointsArray = array of T3DPoint;
TPointsArray = array of TPoint;
TSegmentType = ( TSegmentType = (
st2DLine, st2DLineWithPen, st2DBezier, st2DLine, st2DLineWithPen, st2DBezier,
st3DLine, st3DBezier, stMoveTo, st3DLine, st3DBezier, stMoveTo,
@ -299,6 +304,8 @@ type
procedure Rotate(AAngle: Double; ABase: T3DPoint); virtual; // Angle in radians procedure Rotate(AAngle: Double; ABase: T3DPoint); virtual; // Angle in radians
procedure CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double); virtual; procedure CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double); virtual;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; virtual; function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; virtual;
// rendering
procedure AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); virtual;
end; end;
{@@ {@@
@ -321,6 +328,8 @@ type
procedure Move(ADeltaX, ADeltaY: Double); override; procedure Move(ADeltaX, ADeltaY: Double); override;
procedure Rotate(AAngle: Double; ABase: T3DPoint); override; procedure Rotate(AAngle: Double; ABase: T3DPoint); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override; function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
// rendering
procedure AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); override;
end; end;
T2DSegmentWithPen = class(T2DSegment) T2DSegmentWithPen = class(T2DSegment)
@ -352,6 +361,8 @@ type
// edition methods // edition methods
procedure Move(ADeltaX, ADeltaY: Double); override; procedure Move(ADeltaX, ADeltaY: Double); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override; function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
// rendering
procedure AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); override;
end; end;
{ T3DSegment } { T3DSegment }
@ -364,6 +375,8 @@ type
} }
X, Y, Z: Double; X, Y, Z: Double;
procedure Move(ADeltaX, ADeltaY: Double); override; procedure Move(ADeltaX, ADeltaY: Double); override;
// rendering
procedure AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); override;
end; end;
{ T3DBezierSegment } { T3DBezierSegment }
@ -389,9 +402,12 @@ type
LeftmostEllipse, ClockwiseArcFlag: Boolean; LeftmostEllipse, ClockwiseArcFlag: Boolean;
CX, CY: Double; // Ellipse center CX, CY: Double; // Ellipse center
CenterSetByUser: Boolean; // defines if we should use LeftmostEllipse to calculate the center, or if CX, CY is set directly CenterSetByUser: Boolean; // defines if we should use LeftmostEllipse to calculate the center, or if CX, CY is set directly
procedure BezierApproximate(var Points: T3dPointsArray);
procedure PolyApproximate(var Points: T3dPointsArray);
procedure CalculateCenter; procedure CalculateCenter;
procedure CalculateEllipseBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double); procedure CalculateEllipseBoundingBox(ADest: TFPCustomCanvas; out ALeft, ATop, ARight, ABottom: Double);
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override; function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
procedure AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); override;
end; end;
TvFindEntityResult = (vfrNotFound, vfrFound, vfrSubpartFound); TvFindEntityResult = (vfrNotFound, vfrFound, vfrSubpartFound);
@ -491,11 +507,14 @@ type
{ TvEntityWithPenAndBrush } { TvEntityWithPenAndBrush }
TvClipMode = (vcmNonzeroWindingRule, vcmEvenOddRule);
TvEntityWithPenAndBrush = class(TvEntityWithPen) TvEntityWithPenAndBrush = class(TvEntityWithPen)
public public
{@@ The global Brush for the entire entity. In the case of paths, individual {@@ The global Brush for the entire entity. In the case of paths, individual
elements might be able to override this setting. } elements might be able to override this setting. }
Brush: TvBrush; Brush: TvBrush;
WindingRule: TvClipMode;
constructor Create(APage: TvPage); override; constructor Create(APage: TvPage); override;
procedure ApplyBrushToCanvas(ADest: TFPCustomCanvas); overload; procedure ApplyBrushToCanvas(ADest: TFPCustomCanvas); overload;
procedure ApplyBrushToCanvas(ADest: TFPCustomCanvas; ABrush: TvBrush); overload; procedure ApplyBrushToCanvas(ADest: TFPCustomCanvas; ABrush: TvBrush); overload;
@ -536,8 +555,6 @@ type
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0; ADoDraw: Boolean = True); override; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0; ADoDraw: Boolean = True); override;
end; end;
TvClipMode = (vcmNonzeroWindingRule, vcmEvenOddRule);
TPath = class(TvEntityWithPenAndBrush) TPath = class(TvEntityWithPenAndBrush)
private private
// Used to speed up sequencial access in MoveSubpart // Used to speed up sequencial access in MoveSubpart
@ -1452,6 +1469,7 @@ type
procedure AddBezierToPath(AX1, AY1, AX2, AY2, AX3, AY3: Double); overload; procedure AddBezierToPath(AX1, AY1, AX2, AY2, AX3, AY3: Double); overload;
procedure AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, AX3, AY3, AZ3: Double); overload; procedure AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, AX3, AY3, AZ3: Double); overload;
procedure AddEllipticalArcToPath(ARadX, ARadY, AXAxisRotation, ADestX, ADestY: Double; ALeftmostEllipse, AClockwiseArcFlag: Boolean); // See http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands procedure AddEllipticalArcToPath(ARadX, ARadY, AXAxisRotation, ADestX, ADestY: Double; ALeftmostEllipse, AClockwiseArcFlag: Boolean); // See http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
procedure AddEllipticalArcWithCenterToPath(ARadX, ARadY, AXAxisRotation, ADestX, ADestY, ACenterX, ACenterY: Double; AClockwiseArcFlag: Boolean);
procedure SetBrushColor(AColor: TFPColor); procedure SetBrushColor(AColor: TFPColor);
procedure SetBrushStyle(AStyle: TFPBrushStyle); procedure SetBrushStyle(AStyle: TFPBrushStyle);
procedure SetPenColor(AColor: TFPColor); procedure SetPenColor(AColor: TFPColor);
@ -2625,6 +2643,7 @@ end;
{ T2DEllipticalArcSegment } { T2DEllipticalArcSegment }
// wp: no longer needed...
function T2DEllipticalArcSegment.AlignedEllipseCenterEquationT1( function T2DEllipticalArcSegment.AlignedEllipseCenterEquationT1(
AParam: Double): Double; AParam: Double): Double;
var var
@ -2639,6 +2658,94 @@ begin
if Result < 0 then Result := -1* Result; if Result < 0 then Result := -1* Result;
end; end;
procedure T2DEllipticalArcSegment.BezierApproximate(var Points: T3dPointsArray);
var
P1, P2, P3, P4: T3dPoint;
startangle, endangle: Double;
startanglePi2, endanglePi2: Double;
xstart, ystart: Double;
nextx, nexty: Double;
angle: Double;
n: Integer;
begin
SetLength(Points, 30);
n := 0;
xstart := T2DSegment(Previous).X;
ystart := T2DSegment(Previous).Y;
startangle := CalcEllipsePointAngle(xstart, ystart, RX, RY, CX, CY, XRotation);
endangle := CalcEllipsePointAngle(X, Y, RX, RY, CX, CY, XRotation);
if endangle < 0 then endangle := 2*pi + endangle;
angle := arctan2(-1,1);
angle := radtodeg(angle);
angle := radtodeg(startangle);
angle := radtodeg(endangle);
// Since the algorithm for bezier approximation requires that the angle
// between start and end is at most pi/3 we have to progress in pi/3 steps.
angle := startangle + pi/3;
while true do
begin
if angle >= endangle then begin
EllipticalArcToBezier(CX, CY, RX, RY, startAngle, endangle, Points[n], Points[n+1], Points[n+2], Points[n+3]);
inc(n, 4);
break;
end else
EllipticalArcToBezier(CX, CY, RX, RY, startangle, angle, Points[n], Points[n+1], Points[n+2], Points[n+3]);
inc(n, 4);
startangle := angle;
angle := angle + pi/2;
end;
SetLength(Points, n);
end;
procedure T2DEllipticalArcSegment.PolyApproximate(var Points: T3dPointsArray);
const
BUFSIZE = 100;
var
t, tstart, tend, dt: Double;
xstart, ystart: Double;
n: Integer;
done: Boolean;
begin
n := 0;
SetLength(Points, BUFSIZE);
dt := DegToRad(2.0); // 2-degree increments
xstart := T2DSegment(Previous).X;
ystart := T2DSegment(Previous).Y;
tstart := CalcEllipsePointAngle(xstart, ystart, RX, RY, CX, CY, XRotation);
tend := CalcEllipsePointAngle(X, Y, RX, RY, CX, CY, XRotation);
if ClockwiseArcFlag then
begin // tend must be smaller than tstart
if tend < tstart then tend := TWO_PI + tend;
end else begin // tstart must be smaller than tend
if tstart < tend then tstart := TWO_PI + tstart;
dt := -dt;
end;
done := false;
t := tstart;
while not done do begin
if (ClockwiseArcFlag and (t > tend)) or
(not ClockwiseArcFlag and (t < tend)) then
begin
t := tend;
done := true;
end;
if n >= Length(Points) then
SetLength(Points, Length(Points) + BUFSIZE);
CalcEllipsePoint(t, RX, RY, CX, CY, XRotation, Points[n].x, Points[n].y);
inc(n);
t := t + dt; // Note: dt is <0 in counter-clockwise case
end;
SetLength(Points, n);
end;
// wp: no longer needed...
procedure T2DEllipticalArcSegment.CalculateCenter; procedure T2DEllipticalArcSegment.CalculateCenter;
var var
XStart, YStart, lT1: Double; XStart, YStart, lT1: Double;
@ -2694,7 +2801,7 @@ begin
CX1 := RotatedCenter.X; CX1 := RotatedCenter.X;
CY1 := RotatedCenter.Y; CY1 := RotatedCenter.Y;
// The other ellipse is simetrically positioned // The other ellipse is symmetrically positioned
if (CX1 > Xstart) then if (CX1 > Xstart) then
CX2 := X - (CX1-Xstart) CX2 := X - (CX1-Xstart)
else else
@ -2734,7 +2841,7 @@ begin
end; end;
procedure T2DEllipticalArcSegment.CalculateEllipseBoundingBox(ADest: TFPCustomCanvas; procedure T2DEllipticalArcSegment.CalculateEllipseBoundingBox(ADest: TFPCustomCanvas;
var ALeft, ATop, ARight, ABottom: Double); out ALeft, ATop, ARight, ABottom: Double);
var var
t1, t2, t3: Double; t1, t2, t3: Double;
x1, x2, x3: Double; x1, x2, x3: Double;
@ -2777,41 +2884,49 @@ begin
begin begin
ALeft := CX-RX; ALeft := CX-RX;
ARight := CX+RX; ARight := CX+RX;
ATop := CY-RY; ATop := CY+RY;
ABottom := CY+RY; ABottom := CY-RY;
end end
else else
begin begin
// Search for the minimum and maximum X // Search for the minimum and maximum X
// There are two solutions in each 2pi range
t1 := arctan(-RY*tan(XRotation)/RX); t1 := arctan(-RY*tan(XRotation)/RX);
t2 := arctan(-RY*tan(XRotation)/RX) + Pi/2; t2 := arctan(-RY*tan(XRotation)/RX) + pi; //Pi/2; // why add pi/2 ??
t3 := arctan(-RY*tan(XRotation)/RX) + Pi; // t3 := arctan(-RY*tan(XRotation)/RX) + Pi;
x1 := Cx + RX*Cos(t1)*Cos(XRotation)-RY*Sin(t1)*Sin(XRotation); x1 := Cx + RX*Cos(t1)*Cos(XRotation)-RY*Sin(t1)*Sin(XRotation);
x2 := Cx + RX*Cos(t2)*Cos(XRotation)-RY*Sin(t2)*Sin(XRotation); x2 := Cx + RX*Cos(t2)*Cos(XRotation)-RY*Sin(t2)*Sin(XRotation);
x3 := Cx + RX*Cos(t3)*Cos(XRotation)-RY*Sin(t3)*Sin(XRotation); // x3 := Cx + RX*Cos(t3)*Cos(XRotation)-RY*Sin(t3)*Sin(XRotation);
ALeft := Min(x1, x2); ALeft := Min(x1, x2);
ALeft := Min(ALeft, x3); // ALeft := Min(ALeft, x3);
ARight := Max(x1, x2); ARight := Max(x1, x2);
ARight := Max(ARight, x3); //ARight := Max(ARight, x3);
// Now the same for Y // Now the same for Y
t1 := arctan(RY*cotan(XRotation)/RX); t1 := arctan(RY*cotan(XRotation)/RX);
t2 := arctan(RY*cotan(XRotation)/RX) + Pi/2; t2 := arctan(RY*cotan(XRotation)/RX) + pi; //Pi/2; // why add pi/2 ??
t3 := arctan(RY*cotan(XRotation)/RX) + 3*Pi/2; // t3 := arctan(RY*cotan(XRotation)/RX) + 3*Pi/2;
y1 := CY + RY*Sin(t1)*Cos(XRotation)+RX*Cos(t1)*Sin(XRotation); y1 := CY + RY*Sin(t1)*Cos(XRotation)+RX*Cos(t1)*Sin(XRotation);
y2 := CY + RY*Sin(t2)*Cos(XRotation)+RX*Cos(t2)*Sin(XRotation); y2 := CY + RY*Sin(t2)*Cos(XRotation)+RX*Cos(t2)*Sin(XRotation);
y3 := CY + RY*Sin(t3)*Cos(XRotation)+RX*Cos(t3)*Sin(XRotation); // y3 := CY + RY*Sin(t3)*Cos(XRotation)+RX*Cos(t3)*Sin(XRotation);
ATop := Max(y1, y2);
// ATop := Max(ATop, y3);
ABottom := Min(y1, y2);
// ABottom := Min(ABottom, y3);
{
ATop := Min(y1, y2); ATop := Min(y1, y2);
ATop := Min(ATop, y3); ATop := Min(ATop, y3);
ABottom := Max(y1, y2); ABottom := Max(y1, y2);
ABottom := Max(ABottom, y3); ABottom := Max(ABottom, y3);
}
end; end;
end; end;
@ -2830,6 +2945,24 @@ begin
Result := ADestRoutine(lStr, APageItem); Result := ADestRoutine(lStr, APageItem);
end; end;
procedure T2DEllipticalArcSegment.AddToPoints(ADestX, ADestY: Integer;
AMulX, AMulY: Double; var Points: TPointsArray);
var
pts3D: T3DPointsArray;
i, n: Integer;
begin
SetLength(pts3d, 0);
PolyApproximate(pts3D);
n := Length(Points);
SetLength(Points, n + Length(pts3D) - 1); // we don't need the start point --> -1
for i:=1 to High(pts3D) do // i=0 is end point of prev segment -> we can skip it.
begin
Points[n].X := CoordToCanvasX(pts3D[i].X, ADestX, AMulX);
Points[n].Y := CoordToCanvasY(pts3D[i].Y, ADestY, AMulY);
inc(n);
end;
end;
{ TvVerticalFormulaStack } { TvVerticalFormulaStack }
function TvVerticalFormulaStack.CalculateHeight(ADest: TFPCustomCanvas): Double; function TvVerticalFormulaStack.CalculateHeight(ADest: TFPCustomCanvas): Double;
@ -2970,6 +3103,13 @@ begin
Result := ADestRoutine(lStr, APageItem); Result := ADestRoutine(lStr, APageItem);
end; end;
procedure TPathSegment.AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double;
var Points: TPointsArray);
begin
// Override by descendants
end;
{ T2DSegment } { T2DSegment }
function T2DSegment.GetLength: Double; function T2DSegment.GetLength: Double;
@ -3018,6 +3158,17 @@ begin
Result := ADestRoutine(lStr, APageItem); Result := ADestRoutine(lStr, APageItem);
end; end;
procedure T2DSegment.AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double;
var Points: TPointsArray);
var
n: Integer;
begin
n := Length(Points);
SetLength(Points, n + 1);
Points[n].X := CoordToCanvasX(Points[n].X, ADestX, AMulX);
Points[n].Y := CoordToCanvasY(Points[n].Y, ADestY, AMulY);
end;
{ T2DBezierSegment } { T2DBezierSegment }
function T2DBezierSegment.GetLength: Double; function T2DBezierSegment.GetLength: Double;
@ -3060,6 +3211,42 @@ begin
Result := ADestRoutine(lStr, APageItem); Result := ADestRoutine(lStr, APageItem);
end; end;
procedure T2DBezierSegment.AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double;
var Points: TPointsArray);
var
pts: TPointsArray;
coordX, coordY, coord2X, coord2Y, coord3X, coord3Y, coord4X, coord4Y: Integer;
i, n: Integer;
begin
if not (Previous is T2DSegment) then
raise Exception.Create('T2DBezierSegment must follow a T2DSegment.');
coordX := CoordToCanvasX(T2DSegment(Previous).X, ADestX, AMulX); // start pt
coordY := CoordToCanvasY(T2DSegment(Previous).Y, ADestY, AMulY);
coord4X := CoordToCanvasX(X, ADestX, AMulX); // end pt
coord4Y := CoordToCanvasY(Y, ADestY, AMulY);
coord2X := CoordToCanvasX(X2, ADestX, AMulX); // ctrl pt 1
coord2Y := CoordToCanvasY(Y2, ADestY, AMulY);
coord3X := CoordToCanvasX(X3, ADestX, AMulX); // ctrl pt 2
coord3Y := CoordToCanvasY(Y3, ADestY, AMulY);
SetLength(pts, 0);
AddBezierToPoints(
Make2DPoint(coordX, coordY),
Make2DPoint(coord2X, coord2Y),
Make2DPoint(coord3X, coord3Y),
Make2DPoint(coord4X, coord4Y),
pts);
n := Length(Points);
SetLength(Points, n + Length(pts) - 1); // we don't need the start point --> -1
for i:=1 to High(pts) do // begin at 1 to skip the start point
begin
Points[n] := pts[i];
inc(n);
end;
end;
{ T3DSegment } { T3DSegment }
procedure T3DSegment.Move(ADeltaX, ADeltaY: Double); procedure T3DSegment.Move(ADeltaX, ADeltaY: Double);
@ -3068,6 +3255,18 @@ begin
Y := Y + ADeltaY; Y := Y + ADeltaY;
end; end;
{ This is preliminary... }
procedure T3DSegment.AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double;
var Points: TPointsArray);
var
n: Integer;
begin
n := Length(Points);
SetLength(Points, n + 1);
Points[n].X := CoordToCanvasX(Points[n].X, ADestX, AMulX);
Points[n].Y := CoordToCanvasY(Points[n].Y, ADestY, AMulY);
end;
{ T3DBezierSegment } { T3DBezierSegment }
procedure T3DBezierSegment.Move(ADeltaX, ADeltaY: Double); procedure T3DBezierSegment.Move(ADeltaX, ADeltaY: Double);
@ -4001,10 +4200,116 @@ begin
SetLength(Result, n); SetLength(Result, n);
end; end;
procedure TPath.Render(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo;
ADestX, ADestY: Integer; AMulX, AMulY: Double; ADoDraw: Boolean);
var
polygonPoints: TPointsArray;
polygonStart: TIntegerDynArray;
i: Integer;
j, n: Integer;
x1, y1, x2, y2: Integer;
ACanvas: TCanvas absolute ADest;
coordX, coordY: Integer;
curSegment: TPathSegment;
cur2DSegment: T2DSegment absolute curSegment;
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY, ADoDraw);
ConvertPathToPolygons(self, ADestX, ADestY, AMulX, AMulY, polygonPoints, polygonStart);
x1 := MaxInt;
y1 := maxInt;
x2 := -MaxInt;
y2 := -MaxInt;
for i := 0 to High(polygonPoints) do
begin
x1 := min(x1, polygonPoints[i].X);
y1 := min(y1, polygonPoints[i].Y);
x2 := max(x2, polygonPoints[i].X);
y2 := max(y2, polygonPoints[i].Y);
end;
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, x1, y1, x2, y2);
if ADoDraw then
begin
// (1) draw background only
ADest.Pen.Style := psClear;
if (Length(polygonPoints) > 2) then
case Brush.Kind of
bkSimpleBrush:
if Brush.Style <> bsClear then
begin
{$IFDEF USE_LCL_CANVAS}
for i := 0 to High(polygonStart) do
begin
j := polygonStart[i];
if i = High(polygonStart) then
n := Length(polygonPoints) - j
else
n := polygonStart[i+1] - polygonStart[i] + 1;
end;
ACanvas.Polygon(@polygonPoints[j], n, WindingRule = vcmNonZeroWindingRule);
{$ELSE}
ADest.Polygon(polygonPoints);
{$ENDIF}
end;
else // gradients
DrawBrushGradient(ADest, ARenderInfo, x1, y1, x2, y2, ADestX, ADestY, AMulX, AMulY);
// to do: multiple polygons!
end;
// (2) draw border, take care of the segments with modified pen
ADest.Brush.Style := bsClear; // We will paint no background
ApplyPenToCanvas(ADest, ARenderInfo, Pen); // Restore pen
PrepareForSequentialReading;
for j := 0 to Len - 1 do
begin
curSegment := TPathSegment(Next);
case curSegment.SegmentType of
stMoveTo:
begin
inc(i);
coordX := CoordToCanvasX(cur2DSegment.X, ADestX, AMulX);
coordY := CoordToCanvasY(cur2DSegment.Y, ADestY, AMulY);
ADest.MoveTo(coordX, coordY);
end;
st2DLineWithPen, st2DLine, st3DLine:
begin
coordX := CoordToCanvasX(cur2DSegment.X, ADestX, AMulX);
coordY := CoordToCanvasY(cur2DSegment.Y, ADestY, AMulY);
if curSegment.SegmentType = st2DLineWithPen then
begin
ADest.Pen.FPColor := AdjustColorToBackground(T2DSegmentWithPen(Cur2DSegment).Pen.Color, ARenderInfo);
ADest.Pen.Width := T2DSegmentWithPen(cur2DSegment).Pen.Width;
ADest.Pen.Style := T2DSegmentWithPen(cur2DSegment).Pen.Style;
ADest.LineTo(coordX, coordY);
ApplyPenToCanvas(ADest, ARenderInfo, Pen);
end else
ADest.LineTo(coordX, coordY);
end;
st2DBezier, st3DBezier, st2DEllipticalArc:
begin
coordX := CoordToCanvasX(T2DSegment(curSegment.Previous).X, ADestX, AMulX);
coordY := CoordToCanvasY(T2DSegment(curSegment.Previous).Y, ADestY, AMulY);
SetLength(PolygonPoints, 1);
PolygonPoints[0] := Point(coordX, coordY);
curSegment.AddToPoints(ADestX, ADestY, AMulX, AMulY, PolygonPoints);
ADest.PolyLine(PolygonPoints);
coordX := PolygonPoints[High(PolygonPoints)].X;
coordY := PolygonPoints[High(PolygonPoints)].Y;
ADest.MoveTo(coordX, coordY);
end;
end;
end;
end;
end;
(*
procedure TPath.Render(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; ADestX: Integer; procedure TPath.Render(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double; ADoDraw: Boolean); ADestY: Integer; AMulX: Double; AMulY: Double; ADoDraw: Boolean);
function HasStraightSegmentsOnly: Boolean; function CanFill: Boolean;
var var
seg: TPathSegment; seg: TPathSegment;
j: Integer; j: Integer;
@ -4022,8 +4327,10 @@ procedure TPath.Render(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; AD
end; end;
end; end;
const
POINT_BUFFER = 100;
var var
j: Integer; i, j: Integer;
PosX, PosY: Double; // Not modified by ADestX, etc PosX, PosY: Double; // Not modified by ADestX, etc
CoordX, CoordY: Integer; CoordX, CoordY: Integer;
CurSegment: TPathSegment; CurSegment: TPathSegment;
@ -4032,10 +4339,11 @@ var
Cur2DArcSegment: T2DEllipticalArcSegment absolute CurSegment; Cur2DArcSegment: T2DEllipticalArcSegment absolute CurSegment;
x1, y1, x2, y2: Integer; x1, y1, x2, y2: Integer;
// For bezier // For bezier
CoordX2, CoordY2, CoordX3, CoordY3, CoordX4, CoordY4: Integer; CoordX2, CoordY2, CoordX3, CoordY3, CoordX4, CoordY4, CoordX5, CoordY5: Integer;
//t: Double;
// For polygons // For polygons
lPoints: array of TPoint; lPoints, pts: array of TPoint;
NumPoints: Integer;
pts3d: T3dPointsArray = nil;
// for elliptical arcs // for elliptical arcs
BoxLeft, BoxTop, BoxRight, BoxBottom: Double; BoxLeft, BoxTop, BoxRight, BoxBottom: Double;
EllipseRect: TRect; EllipseRect: TRect;
@ -4084,38 +4392,46 @@ begin
{$endif} {$endif}
// useful in some paths, like stars! // useful in some paths, like stars!
{ -- wp: causes artifacts in case of concave path
if ADoDraw then if ADoDraw then
RenderInternalPolygon(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY); RenderInternalPolygon(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
}
{$IFDEF USE_LCL_CANVAS} if CanFill then
if ADoDraw and (Brush.Kind in [bkHorizontalGradient, bkVerticalGradient]) and
HasStraightSegmentsOnly then
begin begin
x1 := MaxInt; // Manually fill polygon with gradient
y1 := MaxInt; {$IFDEF USE_LCL_CANVAS}
x2 := -MaxInt; if ADoDraw and (Brush.Kind in [bkHorizontalGradient, bkVerticalGradient]) then
y2 := -MaxInt;
PrepareForSequentialReading;
for j := 0 to Len - 1 do
begin begin
CurSegment := TPathSegment(Next); x1 := MaxInt;
CoordX := CoordToCanvasX(Cur2DSegment.X, ADestX, AMulX); y1 := MaxInt;
CoordY := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY); x2 := -MaxInt;
x1 := Min(x1, CoordX); y2 := -MaxInt;
y1 := Min(y1, CoordY); PrepareForSequentialReading;
x2 := Max(x2, CoordX); for j := 0 to Len - 1 do
y2 := Max(y2, CoordY); begin
CurSegment := TPathSegment(Next);
CoordX := CoordToCanvasX(Cur2DSegment.X, ADestX, AMulX);
CoordY := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY);
x1 := Min(x1, CoordX);
y1 := Min(y1, CoordY);
x2 := Max(x2, CoordX);
y2 := Max(y2, CoordY);
end;
DrawBrushGradient(ADest, ARenderInfo, x1, y1, x2, y2, ADestX, ADestY, AMulX, AMulY);
end; end;
DrawBrushGradient(ADest, ARenderInfo, x1, y1, x2, y2, ADestX, ADestY, AMulX, AMulY); {$ENDIF}
end; end;
{$ENDIF}
// //
// For other paths, draw more carefully // For other paths, draw more carefully
// //
ApplyPenToCanvas(ADest, ARenderInfo, Pen); ApplyPenToCanvas(ADest, ARenderInfo, Pen); // Restore pen
PrepareForSequentialReading; PrepareForSequentialReading;
SetLength(lPoints, POINT_BUFFER);
NumPoints := 0;
for j := 0 to Len - 1 do for j := 0 to Len - 1 do
begin begin
//WriteLn('j = ', j); //WriteLn('j = ', j);
@ -4126,8 +4442,32 @@ begin
begin begin
CoordX := CoordToCanvasX(Cur2DSegment.X, ADestX, AMulX); CoordX := CoordToCanvasX(Cur2DSegment.X, ADestX, AMulX);
CoordY := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY); CoordY := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY);
if ADoDraw then
begin
// Draw previous polygon
if NumPoints > 0 then
begin
SetLength(lPoints, NumPoints);
if Length(lPoints) = 2 then
ADest.Line(lPoints[0].X, lPoints[0].Y, lPoints[1].X, lPoints[1].Y)
else
ADest.Polygon(lPoints);
// Start new polygon
SetLength(lPoints, POINT_BUFFER);
NumPoints := 0;
end;
lPoints[0].X := CoordX;
lPoints[0].Y := CoordY;
NumPoints := 1;
end;
{
if ADoDraw then if ADoDraw then
ADest.MoveTo(CoordX, CoordY); ADest.MoveTo(CoordX, CoordY);
}
CalcEntityCanvasMinMaxXY(ARenderInfo, CoordX, CoordY); CalcEntityCanvasMinMaxXY(ARenderInfo, CoordX, CoordY);
PosX := Cur2DSegment.X; PosX := Cur2DSegment.X;
PosY := Cur2DSegment.Y; PosY := Cur2DSegment.Y;
@ -4135,7 +4475,12 @@ begin
Write(Format(' M%d,%d', [CoordX, CoordY])); Write(Format(' M%d,%d', [CoordX, CoordY]));
{$endif} {$endif}
end; end;
// This element can override temporarely the Pen // This element can override temporarely the Pen
// TO DO: Paint these segments with correct pen at end !!!!
st2DLineWithPen: st2DLineWithPen:
begin begin
ADest.Pen.FPColor := AdjustColorToBackground(T2DSegmentWithPen(Cur2DSegment).Pen.Color, ARenderInfo); ADest.Pen.FPColor := AdjustColorToBackground(T2DSegmentWithPen(Cur2DSegment).Pen.Color, ARenderInfo);
@ -4145,7 +4490,14 @@ begin
CoordY2 := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY); CoordY2 := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY);
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2); CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2);
if ADoDraw then if ADoDraw then
ADest.Line(CoordX, CoordY, CoordX2, CoordY2); begin
if NumPoints >= Length(lPoints) then
SetLength(lPoints, Length(lPoints) + POINT_BUFFER);
lPoints[NumPoints].X := CoordX2;
lPoints[NumPoints].Y := CoordY2;
inc(NumPoints);
// ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
end;
PosX := Cur2DSegment.X; PosX := Cur2DSegment.X;
PosY := Cur2DSegment.Y; PosY := Cur2DSegment.Y;
@ -4156,6 +4508,7 @@ begin
Write(Format(' L%d,%d', [CoordX2, CoordY2])); Write(Format(' L%d,%d', [CoordX2, CoordY2]));
{$endif} {$endif}
end; end;
st2DLine, st3DLine: st2DLine, st3DLine:
begin begin
CoordX := CoordToCanvasX(PosX, ADestX, AMulX); CoordX := CoordToCanvasX(PosX, ADestX, AMulX);
@ -4164,13 +4517,21 @@ begin
CoordY2 := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY); CoordY2 := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY);
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2); CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2);
if ADoDraw then if ADoDraw then
ADest.Line(CoordX, CoordY, CoordX2, CoordY2); begin
if NumPoints >= Length(lPoints) then
SetLength(lPoints, Length(lPoints) + POINT_BUFFER);
lPoints[NumPoints].X := CoordX2;
lPoints[NumPoints].Y := CoordY2;
inc(NumPoints);
// ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
end;
PosX := Cur2DSegment.X; PosX := Cur2DSegment.X;
PosY := Cur2DSegment.Y; PosY := Cur2DSegment.Y;
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG} {$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
Write(Format(' L%d,%d', [CoordX2, CoordY2])); Write(Format(' L%d,%d', [CoordX2, CoordY2]));
{$endif} {$endif}
end; end;
{ To draw a bezier we need to divide the interval in parts and make { To draw a bezier we need to divide the interval in parts and make
lines between this parts } lines between this parts }
st2DBezier, st3DBezier: st2DBezier, st3DBezier:
@ -4183,21 +4544,35 @@ begin
CoordY3 := CoordToCanvasY(Cur2DBSegment.Y3, ADestY, AMulY); CoordY3 := CoordToCanvasY(Cur2DBSegment.Y3, ADestY, AMulY);
CoordX4 := CoordToCanvasX(Cur2DBSegment.X, ADestX, AMulX); CoordX4 := CoordToCanvasX(Cur2DBSegment.X, ADestX, AMulX);
CoordY4 := CoordToCanvasY(Cur2DBSegment.Y, ADestY, AMulY); CoordY4 := CoordToCanvasY(Cur2DBSegment.Y, ADestY, AMulY);
SetLength(lPoints, 0); // SetLength(lPoints, 0);
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2); CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2);
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX3, CoordY3, CoordX4, CoordY4); CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX3, CoordY3, CoordX4, CoordY4);
SetLength(pts, 0);
AddBezierToPoints( AddBezierToPoints(
Make2DPoint(CoordX, CoordY), Make2DPoint(CoordX, CoordY),
Make2DPoint(CoordX2, CoordY2), Make2DPoint(CoordX2, CoordY2),
Make2DPoint(CoordX3, CoordY3), Make2DPoint(CoordX3, CoordY3),
Make2DPoint(CoordX4, CoordY4), Make2DPoint(CoordX4, CoordY4),
lPoints pts //lPoints
); );
if ADoDraw then
begin
if NumPoints + Length(pts) >= POINT_BUFFER then
SetLength(lPoints, NumPoints + Length(pts));
for i:=0 to High(pts) do
begin
lPoints[NumPoints].X := pts[i].X;
lPoints[NumPoints].Y := pts[i].Y;
inc(numPoints);
end;
end;
ADest.Brush.Style := Brush.Style; ADest.Brush.Style := Brush.Style;
{
if (Length(lPoints) >= 3) and ADoDraw then if (Length(lPoints) >= 3) and ADoDraw then
ADest.Polygon(lPoints); ADest.Polygon(lPoints);
}
PosX := Cur2DSegment.X; PosX := Cur2DSegment.X;
PosY := Cur2DSegment.Y; PosY := Cur2DSegment.Y;
@ -4209,34 +4584,19 @@ begin
CoordToCanvasX(Cur2DBSegment.X, ADestX, AMulX), CoordToCanvasY(Cur2DBSegment.Y, ADestY, AMulY)])); CoordToCanvasX(Cur2DBSegment.X, ADestX, AMulX), CoordToCanvasY(Cur2DBSegment.Y, ADestY, AMulY)]));
{$endif} {$endif}
end; end;
// Alligned Ellipse equation:
// x^2 / RX^2 + Y^2 / RY^2 = 1
//
// Rotated Ellipse equation:
// (xcosθ+ysinθ)^2 / RX^2 + (ycosθxsinθ)^2 / RY^2 = 1
//
// parametrized:
// x = Cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi) [1]
// y = Cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi) [2]
// ...where ellipse has centre (h,k) semimajor axis a and semiminor axis b, and is rotated through angle phi.
//
// You can then differentiate and solve for gradient = 0:
// 0 = dx/dt = -a*sin(t)*cos(phi) - b*cos(t)*sin(phi)
// => tan(t) = -b*tan(phi)/a [3]
// => t = arctan(-b*tan(phi)/a) + n*Pi [4]
//
// calculate some values of t for n in -2, -1, 0, 1, 2 and see which are the smaller, bigger ones
// done!
st2DEllipticalArc: st2DEllipticalArc:
begin begin
CoordX := CoordToCanvasX(PosX, ADestX, AMulX); CoordX := CoordToCanvasX(PosX, ADestX, AMulX); // start point of segment
CoordY := CoordToCanvasY(PosY, ADestY, AMulY); CoordY := CoordToCanvasY(PosY, ADestY, AMulY);
CoordX2 := CoordToCanvasX(Cur2DArcSegment.RX, ADestX, AMulX); CoordX2 := CoordToCanvasX(Cur2DArcSegment.RX, ADestX, AMulX); // major axis radius
CoordY2 := CoordToCanvasY(Cur2DArcSegment.RY, ADestY, AMulY); CoordY2 := CoordToCanvasY(Cur2DArcSegment.RY, ADestY, AMulY); // minor axis radius
CoordX3 := CoordToCanvasX(Cur2DArcSegment.XRotation, ADestX, AMulX); CoordX3 := CoordToCanvasX(Cur2DArcSegment.XRotation, 0, sign(AMulX)); // axis rotation angle
CoordX4 := CoordToCanvasX(Cur2DArcSegment.X, ADestX, AMulX); CoordX4 := CoordToCanvasX(Cur2DArcSegment.X, ADestX, AMulX); // end point of segment
CoordY4 := CoordToCanvasY(Cur2DArcSegment.Y, ADestY, AMulY); CoordY4 := CoordToCanvasY(Cur2DArcSegment.Y, ADestY, AMulY);
SetLength(lPoints, 0); CoordX5 := CoordToCanvasX(Cur2DArcSegment.Cx, ADestX, AMulX); // Ellipse center
CoordY5 := CoordToCanvasY(Cur2DArcSegment.Cy, ADestY, AMulY);
// SetLength(lPoints, 0);
Cur2DArcSegment.CalculateEllipseBoundingBox(nil, BoxLeft, BoxTop, BoxRight, BoxBottom); Cur2DArcSegment.CalculateEllipseBoundingBox(nil, BoxLeft, BoxTop, BoxRight, BoxBottom);
@ -4248,27 +4608,63 @@ begin
{$ifdef FPVECTORIAL_TOCANVAS_ELLIPSE_VISUALDEBUG} {$ifdef FPVECTORIAL_TOCANVAS_ELLIPSE_VISUALDEBUG}
ACanvas.Pen.Color := clRed; ACanvas.Pen.Color := clRed;
ACanvas.Brush.Style := bsClear; ACanvas.Brush.Style := bsClear;
ACanvas.Rectangle( ACanvas.Rectangle( // Ellipse bounding box
EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom); EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom);
ACanvas.Line(CoordX5-5, CoordY5, CoordX5+5, CoordY5); // Ellipse center
ACanvas.Line(CoordX5, CoordY5-5, CoordX5, CoordY5+5);
ACanvas.Pen.Color := clBlue;
ACanvas.Line(CoordX-5, CoordY, CoordX+5, CoordY); // Start point
ACanvas.Line(CoordX, CoordY-5, CoordX, CoordY+5);
ACanvas.Line(CoordX4-5, CoordY4, CoordX4+5, CoordY4); // End point
ACanvas.Line(CoordX4, CoordY4-5, CoordX4, CoordY4+5);
{$endif} {$endif}
ADest.Brush.Style := Brush.Style; // ADest.Brush.Style := Brush.Style;
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX4, CoordY4); CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo,
EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom);
if ADoDraw then if ADoDraw then
begin begin
// Arc draws counterclockwise Cur2DArcSegment.PolyApproximate(pts3D);
if Cur2DArcSegment.ClockwiseArcFlag then // Cur2DArcSegment.BezierApproximate(pts3D);
if NumPoints + Length(pts3D) >= POINT_BUFFER then
SetLength(lPoints, NumPoints + Length(pts3D));
for i:=1 to High(pts3D) do // i=0 is end point of prev segment -> we can skip it.
begin begin
lPoints[NumPoints].X := CoordToCanvasX(pts3D[i].X, ADestX, AMulX);
lPoints[NumPoints].Y := CoordToCanvasY(pts3D[i].Y, ADestY, AMulY);
inc(numPoints);
end;
{
SetLength(lPoints, Length(pts3D));
for i:=0 to High(pts3D) do
begin
lPoints[i].X := CoordToCanvasX(pts3D[i].X, ADestX, AMulX);
lPoints[i].Y := CoordToCanvasY(pts3D[i].Y, ADestY, AMulY);
end;
ADest.Polygon(lPoints);
}
{
i := 0;
while i < Length(lPoints) do
begin
ADest.Polygon([lPoints[i], lPoints[i+1], lPoints[i+2], lPoints[i+3]]);
inc(i, 4);
end;
}
{
// Arc draws counterclockwise
if Cur2DArcSegment.ClockwiseArcFlag then
ACanvas.Arc( ACanvas.Arc(
EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom, EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom,
CoordX4, CoordY4, CoordX, CoordY); CoordX4, CoordY4, CoordX, CoordY)
end else else
begin
ACanvas.Arc( ACanvas.Arc(
EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom, EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom,
CoordX, CoordY, CoordX4, CoordY4); CoordX, CoordY, CoordX4, CoordY4);
end; end;
}
end; end;
PosX := Cur2DArcSegment.X; PosX := Cur2DArcSegment.X;
PosY := Cur2DArcSegment.Y; PosY := Cur2DArcSegment.Y;
@ -4287,6 +4683,15 @@ begin
WriteLn(''); WriteLn('');
{$endif} {$endif}
// Draw polygon
if ADoDraw then begin
SetLength(lPoints, NumPoints);
if Length(lPoints) = 2 then
ADest.Line(lPoints[0].X, lPoints[0].Y, lPoints[1].X, lPoints[1].Y)
else
ADest.Polygon(lPoints);
end;
// Restores the previous Clip Region // Restores the previous Clip Region
{$ifdef USE_CANVAS_CLIP_REGION} {$ifdef USE_CANVAS_CLIP_REGION}
if ClipPath <> nil then if ClipPath <> nil then
@ -4295,7 +4700,7 @@ begin
end; end;
{$endif} {$endif}
end; end;
*)
procedure TPath.RenderInternalPolygon(ADest: TFPCustomCanvas; procedure TPath.RenderInternalPolygon(ADest: TFPCustomCanvas;
ARenderInfo: TvRenderInfo; ADestX: Integer; ADestY: Integer; AMulX: Double; ARenderInfo: TvRenderInfo; ADestX: Integer; ADestY: Integer; AMulX: Double;
AMulY: Double); AMulY: Double);
@ -7692,6 +8097,27 @@ begin
AppendSegmentToTmpPath(segment); AppendSegmentToTmpPath(segment);
end; end;
procedure TvVectorialPage.AddEllipticalArcWithCenterToPath(ARadX, ARadY,
AXAxisRotation, ADestX, ADestY, ACenterX, ACenterY: Double;
AClockwiseArcFlag: Boolean);
var
segment: T2DEllipticalArcSegment;
begin
segment := T2DEllipticalArcSegment.Create;
segment.SegmentType := st2DEllipticalArc;
segment.X := ADestX;
segment.Y := ADestY;
segment.RX := ARadX;
segment.RY := ARadY;
segment.XRotation := AXAxisRotation;
segment.CX := ACenterX;
segment.CY := ACenterY;
segment.ClockwiseArcFlag := AClockwiseArcFlag;
segment.CenterSetByUser := true;
AppendSegmentToTmpPath(segment);
end;
procedure TvVectorialPage.SetBrushColor(AColor: TFPColor); procedure TvVectorialPage.SetBrushColor(AColor: TFPColor);
begin begin
FTmPPath.Brush.Color := AColor; FTmPPath.Brush.Color := AColor;

View File

@ -8,9 +8,6 @@
<SearchPaths> <SearchPaths>
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths> </SearchPaths>
<Other>
<CompilerPath Value="$(CompPath)"/>
</Other>
</CompilerOptions> </CompilerOptions>
<Files Count="21"> <Files Count="21">
<Item1> <Item1>
@ -98,7 +95,6 @@
<UnitName Value="htmlvectorialreader"/> <UnitName Value="htmlvectorialreader"/>
</Item21> </Item21>
</Files> </Files>
<Type Value="RunAndDesignTime"/>
<RequiredPkgs Count="2"> <RequiredPkgs Count="2">
<Item1> <Item1>
<PackageName Value="LCL"/> <PackageName Value="LCL"/>
@ -114,5 +110,8 @@
<PublishOptions> <PublishOptions>
<Version Value="2"/> <Version Value="2"/>
</PublishOptions> </PublishOptions>
<CustomOptions Items="ExternHelp" Version="2">
<_ExternHelp Items="Count"/>
</CustomOptions>
</Package> </Package>
</CONFIG> </CONFIG>

View File

@ -13,14 +13,8 @@ uses
lazvectorialreader, mathmlvectorialreader, odgvectorialreader, lazvectorialreader, mathmlvectorialreader, odgvectorialreader,
rawvectorialreadwrite, svgvectorialreader, svgvectorialwriter, rawvectorialreadwrite, svgvectorialreader, svgvectorialwriter,
svgzvectorialreader, odtvectorialwriter, docxvectorialwriter, svgzvectorialreader, odtvectorialwriter, docxvectorialwriter,
htmlvectorialreader, LazarusPackageIntf; htmlvectorialreader;
implementation implementation
procedure Register;
begin
end;
initialization
RegisterPackage('fpvectorialpkg', @Register);
end. end.

View File

@ -22,7 +22,7 @@ unit fpvutils;
interface interface
uses uses
Classes, SysUtils, Math, Classes, SysUtils, Math, Types,
{$ifdef USE_LCL_CANVAS} {$ifdef USE_LCL_CANVAS}
Graphics, LCLIntf, LCLType, Graphics, LCLIntf, LCLType,
{$endif} {$endif}
@ -31,7 +31,7 @@ uses
type type
T10Strings = array[0..9] of shortstring; T10Strings = array[0..9] of shortstring;
TPointsArray = array of TPoint; // TPointsArray = array of TPoint;
TFPVUByteArray = array of Byte; TFPVUByteArray = array of Byte;
TNumericalEquation = function (AParameter: Double): Double of object; // return the error TNumericalEquation = function (AParameter: Double): Double of object; // return the error
@ -60,6 +60,12 @@ function BezierEquation_GetLength(P1, P2, P3, P4: T3DPoint; AMaxT: Double = 1; A
function BezierEquation_GetT_ForLength(P1, P2, P3, P4: T3DPoint; ALength: Double; ASteps: Integer = 30): Double; function BezierEquation_GetT_ForLength(P1, P2, P3, P4: T3DPoint; ALength: Double; ASteps: Integer = 30): Double;
function BezierEquation_GetPointAndTangentForLength(P1, P2, P3, P4: T3DPoint; function BezierEquation_GetPointAndTangentForLength(P1, P2, P3, P4: T3DPoint;
ADistance: Double; out AX, AY, ATangentAngle: Double; ASteps: Integer = 30): Boolean; ADistance: Double; out AX, AY, ATangentAngle: Double; ASteps: Integer = 30): Boolean;
function CalcEllipseCenter(x1,y1, x2,y2, rx,ry, phi: Double; fa, fs: Boolean;
out cx,cy, lambda: Double): Boolean;
function CalcEllipsePointAngle(x,y, rx,ry, cx,cy, phi: Double): Double;
procedure CalcEllipsePoint(angle, rx,ry, cx,cy, phi: Double; out x,y: Double);
procedure ConvertPathToPolygons(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double;
var PolygonPoints: TPointsArray; var PolygonStartIndexes: TIntegerDynArray);
procedure ConvertPathToPoints(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); procedure ConvertPathToPoints(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray);
function Rotate2DPoint(P, RotCenter: TPoint; alpha:double): TPoint; function Rotate2DPoint(P, RotCenter: TPoint; alpha:double): TPoint;
function Rotate3DPointInXY(P, RotCenter: T3DPoint; alpha:double): T3DPoint; function Rotate3DPointInXY(P, RotCenter: T3DPoint; alpha:double): T3DPoint;
@ -376,6 +382,197 @@ begin
Result := True; Result := True;
end; end;
// Calculate center of ellipse defined by two points on its perimeter, the
// major and minor axes, and the "sweep" and "large-angle" flags.
// Calculation follows the SVG implementation notes
// see: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
// - (x1, y1) absolute coordinates of start point of arc
// - (x2, y2) absolute coordinates of end point of arc
// - rx, ry: radii of major and minor ellipse axes. Must be > 0. Use abs() if necessary.
// - phi: rotation angle of ellipse
// - fa: large arc flag (false = small arc, true = large arc)
// - fs: sweep flag (false = counterclockwise, true = clockwise)
// - cx, cy: Center coordinates of ellipse
// - Function result is false if the center cannot be calculated
function CalcEllipseCenter(x1,y1, x2,y2, rx,ry, phi: Double; fa, fs: Boolean;
out cx,cy, lambda: Double): Boolean;
const
EPS = 1E-9;
var
sinphi, cosphi: Extended;
x1p, x2p, y1p, y2p: Double; // x1', x2', y1', y2'
cxp, cyp: Double; // cx', cy'
m: Double;
begin
Result := false;
if (rx = 0) or (ry = 0) then
exit;
rx := abs(rx); // only positive radii!
ry := abs(ry);
SinCos(phi, sinphi, cosphi);
// (F.6.5.1) in above document
x1p := ( cosphi*(x1-x2) + sinphi*(y1-y2)) / 2;
y1p := (-sinphi*(x1-x2) + cosphi*(y1-y2)) / 2;
lambda := sqr(x1p/rx) + sqr(y1p/ry);
if lambda > 1 then
begin
// If the distance of the points is too large in relation to the ellipse
// size there is no solution. SVG Implemantation Notes request in this case
// that the ellipse is magnified so much that a solution exists.
lambda := sqrt(lambda);
rx := rx * lambda;
ry := ry * lambda;
end else
lambda := 1.0;
// (F.6.5.2)
m := (sqr(rx*ry) - sqr(rx*y1p) - sqr(ry*x1p)) / (sqr(rx*y1p) + sqr(ry*x1p));
if SameValue(m, 0.0, EPS) then
// Prevent a crash caused by a tiny negative sqrt argument due to rounding error.
m := 0
else if m < 0 then
exit;
// Exit if point distance is too large and return "false" - but this
// should no happen after having applied lambda!
m := sqrt(m); // Positive root for fa <> fs
if fa = fs then m := -m; // Negative root for fa = fs.
cxp := m * rx / ry * y1p;
cyp := -m * ry / rx * x1p;
// (F.6.5.3)
cx := cosphi*cxp - sinphi*cyp + (x1 + x2) / 2;
cy := sinphi*cxp + cosphi*cyp + (y1 + y2) / 2;
// If the function gets here we have a valid ellipse center in cx,cy
Result := true;
end;
{ Calculates the arc angle (in radians) of the point (x,y) on the perimeter of
an ellipse with radii rx,ry and center cx,cy. phi is the rotation angle of
the ellipse major axis with the x axis.
The result is in the range 0 .. 2pi}
function CalcEllipsePointAngle(x,y, rx,ry, cx,cy, phi: Double): Double;
var
p: T3DPoint;
begin
// Rotate ellipse back to align its major axis with the x axis
P := Rotate3dPointInXY(Make3dPoint(x-cx, y-cy, 0), Make3dPoint(0, 0, 0), phi);
// Correctly speaking, above line should use -phi, instead of phi. But
// Make3DPointInXY seems to define the angle in the opposite way.
Result := arctan2(P.Y, P.X);
if Result < 0 then Result := TWO_PI + Result;
end;
{ Calculates the x,y coordinates of a point on an ellipse defined by these
parameters:
- rx, ry: major and minor radius
- phi: rotation angle of the ellipse (angle between major axis and x axis)
- angle: angle from ellipse center between x axis and the point
parameterized:
x = Cx + RX*cos(t)*cos(phi) - RY*sin(t)*sin(phi) [1]
y = Cy + RY*sin(t)*cos(phi) + RX*cos(t)*sin(phi) [2] }
procedure CalcEllipsePoint(angle, rx,ry, cx,cy, phi: Double; out x,y: Double);
var
P: T3dPoint;
cost, sint: Extended;
cosphi, sinphi: Extended;
begin
SinCos(angle, sint, cost);
SinCos(phi, sinphi, cosphi);
x := cx + rx*cost*cosphi - ry*sint*sinphi;
y := cy + ry*sint*cosphi + rx*cost*sinphi;
end;
{ Converts a path to one or more polygons. The polygon vertices are returned
in "PolygonPoints"; they are given in canvas units (pixels).
Since the path can contain several polygons the start index of each polygon
is returned in "PolygonStartIndexes". }
procedure ConvertPathToPolygons(APath: TPath;
ADestX, ADestY: Integer; AMulX, AMulY: Double;
var PolygonPoints: TPointsArray;
var PolygonStartIndexes: TIntegerDynArray);
const
POINT_BUFFER = 100;
var
i, j: Integer;
numPoints: Integer;
numPolygons: Integer;
coordX, coordY: Integer;
coordX2, coordY2, coordX3, coordY3, coordX4, coordY4: Integer;
// temporary point arrays
pts: array of TPoint;
pts3D: T3dPointsArray;
// Segments
curSegment: TPathSegment;
cur2DSegment: T2DSegment absolute curSegment;
cur2DBSegment: T2DBezierSegment absolute curSegment;
cur2DArcSegment: T2DEllipticalArcSegment absolute curSegment;
begin
if (APath = nil) then
begin
SetLength(PolygonPoints, 0);
SetLength(PolygonStartIndexes, 0);
exit;
end;
SetLength(PolygonPoints, POINT_BUFFER);
SetLength(PolygonStartIndexes, POINT_BUFFER);
numPoints := 0;
numPolygons := 0;
APath.PrepareForSequentialReading;
for i := 0 to APath.Len - 1 do
begin
curSegment := TPathSegment(APath.Next);
case curSegment.SegmentType of
stMoveTo:
begin
if i <> 0 then
raise Exception.Create('Path must start with a "MoveTo" command');
// Store current length of points array as polygon start index
if numPolygons >= Length(PolygonStartIndexes) then
SetLength(PolygonstartIndexes, Length(PolygonStartIndexes) + POINT_BUFFER);
PolygonStartIndexes[numPolygons] := numPoints;
inc(numPolygons);
// Store current point as first point of a new polygon
coordX := CoordToCanvasX(cur2DSegment.X, ADestX, AMulX);
coordY := CoordToCanvasY(cur2DSegment.Y, ADestY, AMulY);
if numPoints >= Length(PolygonPoints) then
SetLength(PolygonPoints, Length(PolygonPoints) + POINT_BUFFER);
PolygonPoints[numPoints] := Point(coordX, coordY);
inc(numPoints);
end;
st2DLine, st3DLine, st2DLineWithPen:
begin
// Add current point to current polygon
coordX := CoordToCanvasX(cur2DSegment.X, ADestX, AMulX);
coordY := CoordToCanvasY(cur2DSegment.Y, ADestY, AMulY);
if numPoints >= Length(PolygonPoints) then
SetLength(PolygonPoints, Length(PolygonPoints) + POINT_BUFFER);
PolygonPoints[numPoints] := Point(coordX, coordY);
inc(numPoints);
end;
st2DBezier, st3DBezier, st2DEllipticalArc:
begin
SetLength(PolygonPoints, numPoints);
curSegment.AddToPoints(ADestX, ADestY, AMulX, AMulY, PolygonPoints);
numPoints := Length(PolygonPoints);
end;
end;
end;
SetLength(PolygonPoints, numPoints);
SetLength(PolygonStartIndexes, numPolygons);
end;
procedure ConvertPathToPoints(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); procedure ConvertPathToPoints(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray);
var var
i, LastPoint: Integer; i, LastPoint: Integer;
@ -439,6 +636,8 @@ end;
// Rotates a point P around RotCenter // Rotates a point P around RotCenter
// alpha angle in radians // alpha angle in radians
// Be CAREFUL: the angle used here grows in clockwise direction. This is
// against mathematical convention!
function Rotate3DPointInXY(P, RotCenter: T3DPoint; alpha:double): T3DPoint; function Rotate3DPointInXY(P, RotCenter: T3DPoint; alpha:double): T3DPoint;
var var
sinus, cosinus : Extended; sinus, cosinus : Extended;
@ -508,14 +707,12 @@ function SolveNumericallyAngle(ANumericalEquation: TNumericalEquation;
var var
lError, lErr1, lErr2, lErr3, lErr4: Double; lError, lErr1, lErr2, lErr3, lErr4: Double;
lParam1, lParam2: Double; lParam1, lParam2: Double;
lIterations: Integer;
lCount: Integer; lCount: Integer;
begin begin
lErr1 := ANumericalEquation(0); lErr1 := ANumericalEquation(0);
lErr2 := ANumericalEquation(Pi/2); lErr2 := ANumericalEquation(Pi/2);
lErr3 := ANumericalEquation(Pi); lErr3 := ANumericalEquation(Pi);
lErr4 := ANumericalEquation(3*Pi/2); lErr4 := ANumericalEquation(3*Pi/2);
// Choose the place to start // Choose the place to start
if (lErr1 < lErr2) and (lErr1 < lErr3) and (lErr1 < lErr4) then if (lErr1 < lErr2) and (lErr1 < lErr3) and (lErr1 < lErr4) then
begin begin
@ -527,7 +724,7 @@ begin
lParam1 := 0; lParam1 := 0;
lParam2 := Pi; lParam2 := Pi;
end end
else if (lErr2 < lErr3) and (lErr2 < lErr4) then else if (lErr2 < lErr3) and (lErr2 < lErr4) then // wp: same as above!
begin begin
lParam1 := Pi/2; lParam1 := Pi/2;
lParam2 := 3*Pi/2; lParam2 := 3*Pi/2;
@ -535,7 +732,7 @@ begin
else else
begin begin
lParam1 := Pi; lParam1 := Pi;
lParam2 := 2*Pi; lParam2 := TWO_PI;
end; end;
// Iterate as many times necessary to get the best answer! // Iterate as many times necessary to get the best answer!

View File

@ -67,13 +67,15 @@ type
{ TSVGPathTokenizer } { TSVGPathTokenizer }
TSVGPathTokenizer = class TSVGPathTokenizer = class
protected
Tokens: TSVGTokenList;
public public
FPointSeparator, FCommaSeparator: TFormatSettings; FPointSeparator, FCommaSeparator: TFormatSettings;
Tokens: TSVGTokenList;
ExtraDebugStr: string; ExtraDebugStr: string;
constructor Create; constructor Create;
Destructor Destroy; override; destructor Destroy; override;
procedure AddToken(AStr: string); procedure AddToken(AStr: string);
procedure ClearTokens;
procedure TokenizePathString(AStr: string); procedure TokenizePathString(AStr: string);
procedure TokenizeFunctions(AStr: string); procedure TokenizeFunctions(AStr: string);
function DebugOutTokensAsString: string; function DebugOutTokensAsString: string;
@ -208,13 +210,9 @@ begin
end; end;
destructor TSVGPathTokenizer.Destroy; destructor TSVGPathTokenizer.Destroy;
var
i: Integer;
begin begin
for i:=Tokens.Count-1 downto 0 do ClearTokens;
Tokens[i].Free;
Tokens.Free; Tokens.Free;
inherited Destroy; inherited Destroy;
end; end;
@ -278,6 +276,15 @@ begin
Tokens.Add(lToken); Tokens.Add(lToken);
end; end;
procedure TSVGPathTokenizer.ClearTokens;
var
i: Integer;
begin
for i := Tokens.Count-1 downto 0 do
Tokens[i].Free;
Tokens.Clear;
end;
procedure TSVGPathTokenizer.TokenizePathString(AStr: string); procedure TSVGPathTokenizer.TokenizePathString(AStr: string);
const const
Str_Space: Char = ' '; Str_Space: Char = ' ';
@ -879,6 +886,13 @@ begin
Result := Result + [spbfBrushColor, spbfBrushStyle]; Result := Result + [spbfBrushColor, spbfBrushStyle];
end end
else if AKey = 'fill-rule' then
begin
if AValue = 'evenodd' then
ADestEntity.WindingRule := vcmEvenOddRule else
if AValue = 'nonzero' then
ADestEntity.WindingRule := vcmNonzeroWindingRule; // to do: "inherit" missing here
end
else if AKey = 'fill-opacity' then else if AKey = 'fill-opacity' then
ADestEntity.Brush.Color.Alpha := StringFloatZeroToOneToWord(AValue) ADestEntity.Brush.Color.Alpha := StringFloatZeroToOneToWord(AValue)
// For linear gradient => stop-color:rgb(255,255,0);stop-opacity:1 // For linear gradient => stop-color:rgb(255,255,0);stop-opacity:1
@ -1947,7 +1961,7 @@ var
lDebugStr: String; lDebugStr: String;
lTmpTokenType: TSVGTokenType; lTmpTokenType: TSVGTokenType;
begin begin
FSVGPathTokenizer.Tokens.Clear; FSVGPathTokenizer.ClearTokens;
FSVGPathTokenizer.TokenizePathString(AStr); FSVGPathTokenizer.TokenizePathString(AStr);
//lDebugStr := FSVGPathTokenizer.DebugOutTokensAsString(); //lDebugStr := FSVGPathTokenizer.DebugOutTokensAsString();
CurX := 0; CurX := 0;
@ -1981,7 +1995,7 @@ procedure TvSVGVectorialReader.ReadNextPathCommand(ACurTokenType: TSVGTokenType;
var i: Integer; var CurX, CurY: Double; AData: TvVectorialPage; var i: Integer; var CurX, CurY: Double; AData: TvVectorialPage;
ADoc: TvVectorialDocument); ADoc: TvVectorialDocument);
var var
X, Y, X2, Y2, X3, Y3, XQ, YQ, tmp: Double; X, Y, X2, Y2, X3, Y3, XQ, YQ, Xnew, Ynew, cx, cy, phi, tmp: Double;
LargeArcFlag, SweepFlag, LeftmostEllipse, ClockwiseArc: Boolean; LargeArcFlag, SweepFlag, LeftmostEllipse, ClockwiseArc: Boolean;
lCurTokenType: TSVGTokenType; lCurTokenType: TSVGTokenType;
lDebugStr: String; lDebugStr: String;
@ -2225,29 +2239,68 @@ begin
begin begin
X2 := FSVGPathTokenizer.Tokens.Items[i+1].Value; // RX X2 := FSVGPathTokenizer.Tokens.Items[i+1].Value; // RX
Y2 := FSVGPathTokenizer.Tokens.Items[i+2].Value; // RY Y2 := FSVGPathTokenizer.Tokens.Items[i+2].Value; // RY
X3 := FSVGPathTokenizer.Tokens.Items[i+3].Value; // RotationX phi := FSVGPathTokenizer.Tokens.Items[i+3].Value; // RotationX
X3 := X3 * Pi / 180; // degrees to radians conversion phi := DegToRad(phi); // degrees to radians conversion
LargeArcFlag := Round(FSVGPathTokenizer.Tokens.Items[i+4].Value) = 1; LargeArcFlag := Round(FSVGPathTokenizer.Tokens.Items[i+4].Value) = 1;
SweepFlag := Round(FSVGPathTokenizer.Tokens.Items[i+5].Value) = 1; SweepFlag := Round(FSVGPathTokenizer.Tokens.Items[i+5].Value) = 1;
X := FSVGPathTokenizer.Tokens.Items[i+6].Value; // X X := FSVGPathTokenizer.Tokens.Items[i+6].Value; // X
Y := FSVGPathTokenizer.Tokens.Items[i+7].Value; // Y Y := FSVGPathTokenizer.Tokens.Items[i+7].Value; // Y
// non-coordinate values {
if lCurTokenType = sttRelativeEllipticArcTo then
begin
Xnew := CurX + X;
Ynew := CurY + Y;
end else
begin
Xnew := CurX;
Ynew := CurY;
end;
CalcEllipseCenter(CurX, CurY, Xnew, Ynew, X2, Y2, phi, LargeArcFlag, SweepFlag, cx, cy, tmp);
ConvertSVGCoordinatesToFPVCoordinates(AData, cx, cy, cx, cy);
}
// non-coordinate values (radii)
ConvertSVGDeltaToFPVDelta(AData, X2, Y2, X2, Y2); ConvertSVGDeltaToFPVDelta(AData, X2, Y2, X2, Y2);
if X2 < 0 then X2 := -X2;
if Y2 < 0 then Y2 := -Y2;
// Careful that absolute coordinates require using ConvertSVGCoordinatesToFPVCoordinates // Careful that absolute coordinates require using ConvertSVGCoordinatesToFPVCoordinates
if lCurTokenType in [sttRelativeEllipticArcTo] then if lCurTokenType in [sttRelativeEllipticArcTo] then
begin ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y)
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
end
else else
begin
ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y); ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y);
if lCurTokenType = sttRelativeEllipticArcTo then
begin
Xnew := CurX + X;
Ynew := CurY + Y;
end else
begin
Xnew := X;
Ynew := Y;
end; end;
// in svg the y axis increases downward, in fpv upward. Therefore, angles
// change their sign!
phi := -phi;
SweepFlag := not SweepFlag; // i.e. "clockwise" turns into "counter-clockwise"!
if CalcEllipseCenter(CurX, CurY, Xnew, Ynew, X2, Y2, phi, LargeArcFlag, SweepFlag, cx, cy, tmp) then
AData.AddEllipticalArcWithCenterToPath(X2*tmp, Y2*tmp, phi, Xnew, Ynew, cx, cy, SweepFlag)
else
// Use a straight segment in case of no solution existing for the ellipse center
AData.AddLineToPath(Xnew, Ynew);
CurX := Xnew;
CurY := Ynew;
{
// Convert SVG flags to fpvectorial flags // Convert SVG flags to fpvectorial flags
LeftmostEllipse := (LargeArcFlag and (not SweepFlag)) LeftmostEllipse := (LargeArcFlag and (not SweepFlag))
or ((not LargeArcFlag) and SweepFlag); or ((not LargeArcFlag) and SweepFlag);
if (Y > CurY) or ((Y = CurY) and (X > CurX)) then
LeftMostEllipse := not LeftMostEllipse;
// if Y = CurY then "LeftMost" is to be understood as "TopMost"
ClockwiseArc := SweepFlag; ClockwiseArc := SweepFlag;
if lCurTokenType = sttRelativeEllipticArcTo then if lCurTokenType = sttRelativeEllipticArcTo then
@ -2262,6 +2315,7 @@ begin
CurX := X; CurX := X;
CurY := Y; CurY := Y;
end; end;
}
Inc(i, 8); Inc(i, 8);
end end
@ -2278,7 +2332,7 @@ var
X, Y: Double; X, Y: Double;
FirstPtX, FirstPtY, CurX, CurY: Double; FirstPtX, FirstPtY, CurX, CurY: Double;
begin begin
FSVGPathTokenizer.Tokens.Clear; FSVGPathTokenizer.ClearTokens;
FSVGPathTokenizer.TokenizePathString(AStr); FSVGPathTokenizer.TokenizePathString(AStr);
CurX := 0; CurX := 0;
CurY := 0; CurY := 0;
@ -3012,9 +3066,13 @@ begin
end; end;
destructor TvSVGVectorialReader.Destroy; destructor TvSVGVectorialReader.Destroy;
var
i: Integer;
begin begin
FLayerStylesKeys.Free; FLayerStylesKeys.Free;
FLayerStylesValues.Free; FLayerStylesValues.Free;
for i:=FBrushDefs.Count-1 downto 0 do TObject(FBrushDefs[i]).Free;
FBrushDefs.Free; FBrushDefs.Free;
FSVGPathTokenizer.Free; FSVGPathTokenizer.Free;