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_DEBUG_BLOCKS}
{$define FPVECTORIAL_AUTOFIT_DEBUG}
{.$define FPVECTORIAL_TOCANVAS_ELLIPSE_VISUALDEBUG}
interface
@ -100,6 +101,7 @@ const
// Convenience constant to convert text size points to mm
FPV_TEXT_POINT_TO_MM = 0.35278;
TWO_PI = 2.0 * pi;
type
TvCustomVectorialWriter = class;
@ -272,6 +274,9 @@ type
P3DPoint = ^T3DPoint;
T3DPointsArray = array of T3DPoint;
TPointsArray = array of TPoint;
TSegmentType = (
st2DLine, st2DLineWithPen, st2DBezier,
st3DLine, st3DBezier, stMoveTo,
@ -299,6 +304,8 @@ type
procedure Rotate(AAngle: Double; ABase: T3DPoint); virtual; // Angle in radians
procedure CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double); virtual;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; virtual;
// rendering
procedure AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); virtual;
end;
{@@
@ -321,6 +328,8 @@ type
procedure Move(ADeltaX, ADeltaY: Double); override;
procedure Rotate(AAngle: Double; ABase: T3DPoint); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
// rendering
procedure AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); override;
end;
T2DSegmentWithPen = class(T2DSegment)
@ -352,6 +361,8 @@ type
// edition methods
procedure Move(ADeltaX, ADeltaY: Double); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
// rendering
procedure AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); override;
end;
{ T3DSegment }
@ -364,6 +375,8 @@ type
}
X, Y, Z: Double;
procedure Move(ADeltaX, ADeltaY: Double); override;
// rendering
procedure AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); override;
end;
{ T3DBezierSegment }
@ -389,9 +402,12 @@ type
LeftmostEllipse, ClockwiseArcFlag: Boolean;
CX, CY: Double; // Ellipse center
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 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;
procedure AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); override;
end;
TvFindEntityResult = (vfrNotFound, vfrFound, vfrSubpartFound);
@ -491,11 +507,14 @@ type
{ TvEntityWithPenAndBrush }
TvClipMode = (vcmNonzeroWindingRule, vcmEvenOddRule);
TvEntityWithPenAndBrush = class(TvEntityWithPen)
public
{@@ The global Brush for the entire entity. In the case of paths, individual
elements might be able to override this setting. }
Brush: TvBrush;
WindingRule: TvClipMode;
constructor Create(APage: TvPage); override;
procedure ApplyBrushToCanvas(ADest: TFPCustomCanvas); 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;
end;
TvClipMode = (vcmNonzeroWindingRule, vcmEvenOddRule);
TPath = class(TvEntityWithPenAndBrush)
private
// 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, 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 AddEllipticalArcWithCenterToPath(ARadX, ARadY, AXAxisRotation, ADestX, ADestY, ACenterX, ACenterY: Double; AClockwiseArcFlag: Boolean);
procedure SetBrushColor(AColor: TFPColor);
procedure SetBrushStyle(AStyle: TFPBrushStyle);
procedure SetPenColor(AColor: TFPColor);
@ -2625,6 +2643,7 @@ end;
{ T2DEllipticalArcSegment }
// wp: no longer needed...
function T2DEllipticalArcSegment.AlignedEllipseCenterEquationT1(
AParam: Double): Double;
var
@ -2639,6 +2658,94 @@ begin
if Result < 0 then Result := -1* Result;
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;
var
XStart, YStart, lT1: Double;
@ -2694,7 +2801,7 @@ begin
CX1 := RotatedCenter.X;
CY1 := RotatedCenter.Y;
// The other ellipse is simetrically positioned
// The other ellipse is symmetrically positioned
if (CX1 > Xstart) then
CX2 := X - (CX1-Xstart)
else
@ -2734,7 +2841,7 @@ begin
end;
procedure T2DEllipticalArcSegment.CalculateEllipseBoundingBox(ADest: TFPCustomCanvas;
var ALeft, ATop, ARight, ABottom: Double);
out ALeft, ATop, ARight, ABottom: Double);
var
t1, t2, t3: Double;
x1, x2, x3: Double;
@ -2777,41 +2884,49 @@ begin
begin
ALeft := CX-RX;
ARight := CX+RX;
ATop := CY-RY;
ABottom := CY+RY;
ATop := CY+RY;
ABottom := CY-RY;
end
else
begin
// Search for the minimum and maximum X
// There are two solutions in each 2pi range
t1 := arctan(-RY*tan(XRotation)/RX);
t2 := arctan(-RY*tan(XRotation)/RX) + Pi/2;
t3 := arctan(-RY*tan(XRotation)/RX) + Pi;
t2 := arctan(-RY*tan(XRotation)/RX) + pi; //Pi/2; // why add pi/2 ??
// t3 := arctan(-RY*tan(XRotation)/RX) + Pi;
x1 := Cx + RX*Cos(t1)*Cos(XRotation)-RY*Sin(t1)*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(ALeft, x3);
// ALeft := Min(ALeft, x3);
ARight := Max(x1, x2);
ARight := Max(ARight, x3);
//ARight := Max(ARight, x3);
// Now the same for Y
t1 := arctan(RY*cotan(XRotation)/RX);
t2 := arctan(RY*cotan(XRotation)/RX) + Pi/2;
t3 := arctan(RY*cotan(XRotation)/RX) + 3*Pi/2;
t2 := arctan(RY*cotan(XRotation)/RX) + pi; //Pi/2; // why add pi/2 ??
// t3 := arctan(RY*cotan(XRotation)/RX) + 3*Pi/2;
y1 := CY + RY*Sin(t1)*Cos(XRotation)+RX*Cos(t1)*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(ATop, y3);
ABottom := Max(y1, y2);
ABottom := Max(ABottom, y3);
}
end;
end;
@ -2830,6 +2945,24 @@ begin
Result := ADestRoutine(lStr, APageItem);
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 }
function TvVerticalFormulaStack.CalculateHeight(ADest: TFPCustomCanvas): Double;
@ -2970,6 +3103,13 @@ begin
Result := ADestRoutine(lStr, APageItem);
end;
procedure TPathSegment.AddToPoints(ADestX, ADestY: Integer; AMulX, AMulY: Double;
var Points: TPointsArray);
begin
// Override by descendants
end;
{ T2DSegment }
function T2DSegment.GetLength: Double;
@ -3018,6 +3158,17 @@ begin
Result := ADestRoutine(lStr, APageItem);
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 }
function T2DBezierSegment.GetLength: Double;
@ -3060,6 +3211,42 @@ begin
Result := ADestRoutine(lStr, APageItem);
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 }
procedure T3DSegment.Move(ADeltaX, ADeltaY: Double);
@ -3068,6 +3255,18 @@ begin
Y := Y + ADeltaY;
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 }
procedure T3DBezierSegment.Move(ADeltaX, ADeltaY: Double);
@ -4001,10 +4200,116 @@ begin
SetLength(Result, n);
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;
ADestY: Integer; AMulX: Double; AMulY: Double; ADoDraw: Boolean);
function HasStraightSegmentsOnly: Boolean;
function CanFill: Boolean;
var
seg: TPathSegment;
j: Integer;
@ -4022,8 +4327,10 @@ procedure TPath.Render(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; AD
end;
end;
const
POINT_BUFFER = 100;
var
j: Integer;
i, j: Integer;
PosX, PosY: Double; // Not modified by ADestX, etc
CoordX, CoordY: Integer;
CurSegment: TPathSegment;
@ -4032,10 +4339,11 @@ var
Cur2DArcSegment: T2DEllipticalArcSegment absolute CurSegment;
x1, y1, x2, y2: Integer;
// For bezier
CoordX2, CoordY2, CoordX3, CoordY3, CoordX4, CoordY4: Integer;
//t: Double;
CoordX2, CoordY2, CoordX3, CoordY3, CoordX4, CoordY4, CoordX5, CoordY5: Integer;
// For polygons
lPoints: array of TPoint;
lPoints, pts: array of TPoint;
NumPoints: Integer;
pts3d: T3dPointsArray = nil;
// for elliptical arcs
BoxLeft, BoxTop, BoxRight, BoxBottom: Double;
EllipseRect: TRect;
@ -4084,38 +4392,46 @@ begin
{$endif}
// useful in some paths, like stars!
{ -- wp: causes artifacts in case of concave path
if ADoDraw then
RenderInternalPolygon(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
}
{$IFDEF USE_LCL_CANVAS}
if ADoDraw and (Brush.Kind in [bkHorizontalGradient, bkVerticalGradient]) and
HasStraightSegmentsOnly then
if CanFill then
begin
x1 := MaxInt;
y1 := MaxInt;
x2 := -MaxInt;
y2 := -MaxInt;
PrepareForSequentialReading;
for j := 0 to Len - 1 do
// Manually fill polygon with gradient
{$IFDEF USE_LCL_CANVAS}
if ADoDraw and (Brush.Kind in [bkHorizontalGradient, bkVerticalGradient]) then
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);
x1 := MaxInt;
y1 := MaxInt;
x2 := -MaxInt;
y2 := -MaxInt;
PrepareForSequentialReading;
for j := 0 to Len - 1 do
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;
DrawBrushGradient(ADest, ARenderInfo, x1, y1, x2, y2, ADestX, ADestY, AMulX, AMulY);
{$ENDIF}
end;
{$ENDIF}
//
// For other paths, draw more carefully
//
ApplyPenToCanvas(ADest, ARenderInfo, Pen);
ApplyPenToCanvas(ADest, ARenderInfo, Pen); // Restore pen
PrepareForSequentialReading;
SetLength(lPoints, POINT_BUFFER);
NumPoints := 0;
for j := 0 to Len - 1 do
begin
//WriteLn('j = ', j);
@ -4126,8 +4442,32 @@ begin
begin
CoordX := CoordToCanvasX(Cur2DSegment.X, ADestX, AMulX);
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
ADest.MoveTo(CoordX, CoordY);
}
CalcEntityCanvasMinMaxXY(ARenderInfo, CoordX, CoordY);
PosX := Cur2DSegment.X;
PosY := Cur2DSegment.Y;
@ -4135,7 +4475,12 @@ begin
Write(Format(' M%d,%d', [CoordX, CoordY]));
{$endif}
end;
// This element can override temporarely the Pen
// TO DO: Paint these segments with correct pen at end !!!!
st2DLineWithPen:
begin
ADest.Pen.FPColor := AdjustColorToBackground(T2DSegmentWithPen(Cur2DSegment).Pen.Color, ARenderInfo);
@ -4145,7 +4490,14 @@ begin
CoordY2 := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY);
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2);
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;
PosY := Cur2DSegment.Y;
@ -4156,6 +4508,7 @@ begin
Write(Format(' L%d,%d', [CoordX2, CoordY2]));
{$endif}
end;
st2DLine, st3DLine:
begin
CoordX := CoordToCanvasX(PosX, ADestX, AMulX);
@ -4164,13 +4517,21 @@ begin
CoordY2 := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY);
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2);
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;
PosY := Cur2DSegment.Y;
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
Write(Format(' L%d,%d', [CoordX2, CoordY2]));
{$endif}
end;
{ To draw a bezier we need to divide the interval in parts and make
lines between this parts }
st2DBezier, st3DBezier:
@ -4183,21 +4544,35 @@ begin
CoordY3 := CoordToCanvasY(Cur2DBSegment.Y3, ADestY, AMulY);
CoordX4 := CoordToCanvasX(Cur2DBSegment.X, ADestX, AMulX);
CoordY4 := CoordToCanvasY(Cur2DBSegment.Y, ADestY, AMulY);
SetLength(lPoints, 0);
// SetLength(lPoints, 0);
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2);
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX3, CoordY3, CoordX4, CoordY4);
SetLength(pts, 0);
AddBezierToPoints(
Make2DPoint(CoordX, CoordY),
Make2DPoint(CoordX2, CoordY2),
Make2DPoint(CoordX3, CoordY3),
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;
{
if (Length(lPoints) >= 3) and ADoDraw then
ADest.Polygon(lPoints);
}
PosX := Cur2DSegment.X;
PosY := Cur2DSegment.Y;
@ -4209,34 +4584,19 @@ begin
CoordToCanvasX(Cur2DBSegment.X, ADestX, AMulX), CoordToCanvasY(Cur2DBSegment.Y, ADestY, AMulY)]));
{$endif}
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:
begin
CoordX := CoordToCanvasX(PosX, ADestX, AMulX);
CoordX := CoordToCanvasX(PosX, ADestX, AMulX); // start point of segment
CoordY := CoordToCanvasY(PosY, ADestY, AMulY);
CoordX2 := CoordToCanvasX(Cur2DArcSegment.RX, ADestX, AMulX);
CoordY2 := CoordToCanvasY(Cur2DArcSegment.RY, ADestY, AMulY);
CoordX3 := CoordToCanvasX(Cur2DArcSegment.XRotation, ADestX, AMulX);
CoordX4 := CoordToCanvasX(Cur2DArcSegment.X, ADestX, AMulX);
CoordX2 := CoordToCanvasX(Cur2DArcSegment.RX, ADestX, AMulX); // major axis radius
CoordY2 := CoordToCanvasY(Cur2DArcSegment.RY, ADestY, AMulY); // minor axis radius
CoordX3 := CoordToCanvasX(Cur2DArcSegment.XRotation, 0, sign(AMulX)); // axis rotation angle
CoordX4 := CoordToCanvasX(Cur2DArcSegment.X, ADestX, AMulX); // end point of segment
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);
@ -4248,27 +4608,63 @@ begin
{$ifdef FPVECTORIAL_TOCANVAS_ELLIPSE_VISUALDEBUG}
ACanvas.Pen.Color := clRed;
ACanvas.Brush.Style := bsClear;
ACanvas.Rectangle(
ACanvas.Rectangle( // Ellipse bounding box
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}
ADest.Brush.Style := Brush.Style;
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX4, CoordY4);
// ADest.Brush.Style := Brush.Style;
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo,
EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom);
if ADoDraw then
begin
// Arc draws counterclockwise
if Cur2DArcSegment.ClockwiseArcFlag then
Cur2DArcSegment.PolyApproximate(pts3D);
// 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
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(
EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom,
CoordX4, CoordY4, CoordX, CoordY);
end else
begin
CoordX4, CoordY4, CoordX, CoordY)
else
ACanvas.Arc(
EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom,
CoordX, CoordY, CoordX4, CoordY4);
end;
}
end;
PosX := Cur2DArcSegment.X;
PosY := Cur2DArcSegment.Y;
@ -4287,6 +4683,15 @@ begin
WriteLn('');
{$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
{$ifdef USE_CANVAS_CLIP_REGION}
if ClipPath <> nil then
@ -4295,7 +4700,7 @@ begin
end;
{$endif}
end;
*)
procedure TPath.RenderInternalPolygon(ADest: TFPCustomCanvas;
ARenderInfo: TvRenderInfo; ADestX: Integer; ADestY: Integer; AMulX: Double;
AMulY: Double);
@ -7692,6 +8097,27 @@ begin
AppendSegmentToTmpPath(segment);
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);
begin
FTmPPath.Brush.Color := AColor;

View File

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

View File

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

View File

@ -22,7 +22,7 @@ unit fpvutils;
interface
uses
Classes, SysUtils, Math,
Classes, SysUtils, Math, Types,
{$ifdef USE_LCL_CANVAS}
Graphics, LCLIntf, LCLType,
{$endif}
@ -31,7 +31,7 @@ uses
type
T10Strings = array[0..9] of shortstring;
TPointsArray = array of TPoint;
// TPointsArray = array of TPoint;
TFPVUByteArray = array of Byte;
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_GetPointAndTangentForLength(P1, P2, P3, P4: T3DPoint;
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);
function Rotate2DPoint(P, RotCenter: TPoint; alpha:double): TPoint;
function Rotate3DPointInXY(P, RotCenter: T3DPoint; alpha:double): T3DPoint;
@ -376,6 +382,197 @@ begin
Result := True;
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);
var
i, LastPoint: Integer;
@ -439,6 +636,8 @@ end;
// Rotates a point P around RotCenter
// 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;
var
sinus, cosinus : Extended;
@ -508,14 +707,12 @@ function SolveNumericallyAngle(ANumericalEquation: TNumericalEquation;
var
lError, lErr1, lErr2, lErr3, lErr4: Double;
lParam1, lParam2: Double;
lIterations: Integer;
lCount: Integer;
begin
lErr1 := ANumericalEquation(0);
lErr2 := ANumericalEquation(Pi/2);
lErr3 := ANumericalEquation(Pi);
lErr4 := ANumericalEquation(3*Pi/2);
// Choose the place to start
if (lErr1 < lErr2) and (lErr1 < lErr3) and (lErr1 < lErr4) then
begin
@ -527,7 +724,7 @@ begin
lParam1 := 0;
lParam2 := Pi;
end
else if (lErr2 < lErr3) and (lErr2 < lErr4) then
else if (lErr2 < lErr3) and (lErr2 < lErr4) then // wp: same as above!
begin
lParam1 := Pi/2;
lParam2 := 3*Pi/2;
@ -535,7 +732,7 @@ begin
else
begin
lParam1 := Pi;
lParam2 := 2*Pi;
lParam2 := TWO_PI;
end;
// Iterate as many times necessary to get the best answer!

View File

@ -67,13 +67,15 @@ type
{ TSVGPathTokenizer }
TSVGPathTokenizer = class
protected
Tokens: TSVGTokenList;
public
FPointSeparator, FCommaSeparator: TFormatSettings;
Tokens: TSVGTokenList;
ExtraDebugStr: string;
constructor Create;
Destructor Destroy; override;
destructor Destroy; override;
procedure AddToken(AStr: string);
procedure ClearTokens;
procedure TokenizePathString(AStr: string);
procedure TokenizeFunctions(AStr: string);
function DebugOutTokensAsString: string;
@ -208,13 +210,9 @@ begin
end;
destructor TSVGPathTokenizer.Destroy;
var
i: Integer;
begin
for i:=Tokens.Count-1 downto 0 do
Tokens[i].Free;
ClearTokens;
Tokens.Free;
inherited Destroy;
end;
@ -278,6 +276,15 @@ begin
Tokens.Add(lToken);
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);
const
Str_Space: Char = ' ';
@ -879,6 +886,13 @@ begin
Result := Result + [spbfBrushColor, spbfBrushStyle];
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
ADestEntity.Brush.Color.Alpha := StringFloatZeroToOneToWord(AValue)
// For linear gradient => stop-color:rgb(255,255,0);stop-opacity:1
@ -1947,7 +1961,7 @@ var
lDebugStr: String;
lTmpTokenType: TSVGTokenType;
begin
FSVGPathTokenizer.Tokens.Clear;
FSVGPathTokenizer.ClearTokens;
FSVGPathTokenizer.TokenizePathString(AStr);
//lDebugStr := FSVGPathTokenizer.DebugOutTokensAsString();
CurX := 0;
@ -1981,7 +1995,7 @@ procedure TvSVGVectorialReader.ReadNextPathCommand(ACurTokenType: TSVGTokenType;
var i: Integer; var CurX, CurY: Double; AData: TvVectorialPage;
ADoc: TvVectorialDocument);
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;
lCurTokenType: TSVGTokenType;
lDebugStr: String;
@ -2225,29 +2239,68 @@ begin
begin
X2 := FSVGPathTokenizer.Tokens.Items[i+1].Value; // RX
Y2 := FSVGPathTokenizer.Tokens.Items[i+2].Value; // RY
X3 := FSVGPathTokenizer.Tokens.Items[i+3].Value; // RotationX
X3 := X3 * Pi / 180; // degrees to radians conversion
phi := FSVGPathTokenizer.Tokens.Items[i+3].Value; // RotationX
phi := DegToRad(phi); // degrees to radians conversion
LargeArcFlag := Round(FSVGPathTokenizer.Tokens.Items[i+4].Value) = 1;
SweepFlag := Round(FSVGPathTokenizer.Tokens.Items[i+5].Value) = 1;
X := FSVGPathTokenizer.Tokens.Items[i+6].Value; // X
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);
if X2 < 0 then X2 := -X2;
if Y2 < 0 then Y2 := -Y2;
// Careful that absolute coordinates require using ConvertSVGCoordinatesToFPVCoordinates
if lCurTokenType in [sttRelativeEllipticArcTo] then
begin
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
end
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y)
else
begin
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;
// 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
LeftmostEllipse := (LargeArcFlag and (not 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;
if lCurTokenType = sttRelativeEllipticArcTo then
@ -2262,6 +2315,7 @@ begin
CurX := X;
CurY := Y;
end;
}
Inc(i, 8);
end
@ -2278,7 +2332,7 @@ var
X, Y: Double;
FirstPtX, FirstPtY, CurX, CurY: Double;
begin
FSVGPathTokenizer.Tokens.Clear;
FSVGPathTokenizer.ClearTokens;
FSVGPathTokenizer.TokenizePathString(AStr);
CurX := 0;
CurY := 0;
@ -3012,9 +3066,13 @@ begin
end;
destructor TvSVGVectorialReader.Destroy;
var
i: Integer;
begin
FLayerStylesKeys.Free;
FLayerStylesValues.Free;
for i:=FBrushDefs.Count-1 downto 0 do TObject(FBrushDefs[i]).Free;
FBrushDefs.Free;
FSVGPathTokenizer.Free;