Browse Source

fpvectorial: Implements support for arc in the postscript reader

git-svn-id: trunk@18030 -
sekelsenmat 14 years ago
parent
commit
abdc672fe3

+ 41 - 9
packages/fpvectorial/src/epsvectorialreader.pas

@@ -1455,6 +1455,9 @@ function TvEPSVectorialReader.ExecutePathConstructionOperator(
 var
   Param1, Param2, Param3, Param4, Param5, Param6: TPSToken;
   PosX, PosY, PosX2, PosY2, PosX3, PosY3, BaseX, BaseY: Double;
+  // For Arc
+  P1, P2, P3, P4: T3DPoint;
+  startAngle, endAngle: Double;
 begin
   Result := False;
 
@@ -1506,7 +1509,7 @@ begin
     Param1 := TPSToken(Stack.Pop);
     Param2 := TPSToken(Stack.Pop);
     PostScriptCoordsToFPVectorialCoords(Param1, Param2, PosX, PosY);
-    AData.GetCurrenPathPenPos(BaseX, BaseY);
+    AData.GetCurrentPathPenPos(BaseX, BaseY);
     PosX := PosX + CurrentGraphicState.TranslateX;
     PosY := PosY + CurrentGraphicState.TranslateY;
     {$ifdef FPVECTORIALDEBUG_PATHS}
@@ -1534,7 +1537,8 @@ begin
     PostScriptCoordsToFPVectorialCoords(Param5, Param6, PosX, PosY);
     PostScriptCoordsToFPVectorialCoords(Param3, Param4, PosX2, PosY2);
     PostScriptCoordsToFPVectorialCoords(Param1, Param2, PosX3, PosY3);
-    AData.GetCurrenPathPenPos(BaseX, BaseY);
+    AData.GetCurrentPathPenPos(BaseX, BaseY);
+    // First move to the start of the arc
     BaseX := BaseX + CurrentGraphicState.TranslateX;
     BaseY := BaseY + CurrentGraphicState.TranslateY;
     {$ifdef FPVECTORIALDEBUG_PATHS}
@@ -1558,19 +1562,47 @@ begin
 
     Exit(True);
   end;
-  // x y r angle1 angle2 arc – Append counterclockwise arc
+  {
+    x y r angle1 angle2 arc – Append counterclockwise arc
+
+    Arcs in PostScript are described by a center (x, y), a radius r and
+    two angles, angle1 for the start and angle2 for the end. These two
+    angles are relative to the X axis growing to the right (positive direction).
+
+  }
   if AToken.StrValue = 'arc' then
   begin
-    Param1 := TPSToken(Stack.Pop);
-    Param2 := TPSToken(Stack.Pop);
-    Param3 := TPSToken(Stack.Pop);
-    Param4 := TPSToken(Stack.Pop);
-    Param5 := TPSToken(Stack.Pop);
+    Param1 := TPSToken(Stack.Pop); // angle2
+    Param2 := TPSToken(Stack.Pop); // angle1
+    Param3 := TPSToken(Stack.Pop); // r
+    Param4 := TPSToken(Stack.Pop); // y
+    Param5 := TPSToken(Stack.Pop); // x
     PostScriptCoordsToFPVectorialCoords(Param4, Param5, PosX, PosY);
+    PosX := PosX + CurrentGraphicState.TranslateX;
+    PosY := PosY + CurrentGraphicState.TranslateY;
+    startAngle := Param2.FloatValue * Pi / 180;
+    endAngle := Param1.FloatValue * Pi / 180;
+
+    // If the angle is too big we need to use two beziers
+    if endAngle - startAngle > Pi then
+    begin
+      CircularArcToBezier(PosX, PosY, Param3.FloatValue, startAngle, endAngle - Pi, P1, P2, P3, P4);
+      AData.AddMoveToPath(P1.X, P1.Y);
+      AData.AddBezierToPath(P2.X, P2.Y, P3.X, P3.Y, P4.X, P4.Y);
+
+      CircularArcToBezier(PosX, PosY, Param3.FloatValue, startAngle + Pi, endAngle, P1, P2, P3, P4);
+      AData.AddMoveToPath(P1.X, P1.Y);
+      AData.AddBezierToPath(P2.X, P2.Y, P3.X, P3.Y, P4.X, P4.Y);
+    end
+    else
+    begin
+      CircularArcToBezier(PosX, PosY, Param3.FloatValue, startAngle, endAngle, P1, P2, P3, P4);
+      AData.AddMoveToPath(P1.X, P1.Y);
+      AData.AddBezierToPath(P2.X, P2.Y, P3.X, P3.Y, P4.X, P4.Y);
+    end;
 //    {$ifdef FPVECTORIALDEBUG}
 //    WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] rcurveto %f, %f', [BaseX + PosX, BaseY + PosY]));
 //    {$endif}
-//    AData.AddBezierToPath(BaseX + PosX, BaseY + PosY, BaseX + PosX2, BaseY + PosY2, BaseX + PosX3, BaseY + PosY3);
     {$ifdef FPVECTORIALDEBUG_PATHS}
     WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] arc %f, %f', [PosX, PosY]));
     {$endif}

+ 3 - 3
packages/fpvectorial/src/fpvectorial.pas

@@ -295,7 +295,7 @@ type
     procedure AddLineToPath(AX, AY: Double); overload;
     procedure AddLineToPath(AX, AY: Double; AColor: TFPColor); overload;
     procedure AddLineToPath(AX, AY, AZ: Double); overload;
-    procedure GetCurrenPathPenPos(var AX, AY: Double);
+    procedure GetCurrentPathPenPos(var AX, AY: Double);
     procedure AddBezierToPath(AX1, AY1, AX2, AY2, AX3, AY3: Double); overload;
     procedure AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, AX3, AY3, AZ3: Double); overload;
     procedure SetBrushColor(AColor: TFPColor);
@@ -661,10 +661,10 @@ end;
 {@@
   Gets the current Pen Pos in the temporary path
 }
-procedure TvVectorialDocument.GetCurrenPathPenPos(var AX, AY: Double);
+procedure TvVectorialDocument.GetCurrentPathPenPos(var AX, AY: Double);
 begin
   // Check if we are the first segment in the tmp path
-  if FTmpPath.PointsEnd = nil then raise Exception.Create('[TvVectorialDocument.GetCurrenPathPenPos] One cannot obtain the Pen Pos if there are no segments in the temporary path');
+  if FTmpPath.PointsEnd = nil then raise Exception.Create('[TvVectorialDocument.GetCurrentPathPenPos] One cannot obtain the Pen Pos if there are no segments in the temporary path');
 
   AX := T2DSegment(FTmpPath.PointsEnd).X;
   AY := T2DSegment(FTmpPath.PointsEnd).Y;

+ 54 - 0
packages/fpvectorial/src/fpvutils.pas

@@ -31,6 +31,9 @@ function RGBToFPColor(AR, AG, AB: byte): TFPColor; inline;
 function CanvasCoordsToFPVectorial(AY: Integer; AHeight: Integer): Integer; inline;
 function CanvasTextPosToFPVectorial(AY: Integer; ACanvasHeight, ATextHeight: Integer): Integer;
 function SeparateString(AString: string; ASeparator: char): T10Strings;
+// Mathematical routines
+procedure EllipticalArcToBezier(Xc, Yc, Rx, Ry, startAngle, endAngle: Double; var P1, P2, P3, P4: T3DPoint);
+procedure CircularArcToBezier(Xc, Yc, R, startAngle, endAngle: Double; var P1, P2, P3, P4: T3DPoint);
 
 implementation
 
@@ -110,5 +113,56 @@ begin
   end;
 end;
 
+{ Considering a counter-clockwise arc, elliptical and alligned to the axises
+
+  An elliptical Arc can be converted to
+  the following Cubic Bezier control points:
+
+  P1 = E(startAngle)            <- start point
+  P2 = P1+alfa * dE(startAngle) <- control point
+  P3 = P4−alfa * dE(endAngle)   <- control point
+  P4 = E(endAngle)              <- end point
+
+  source: http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
+
+  The equation of an elliptical arc is:
+
+  X(t) = Xc + Rx * cos(t)
+  Y(t) = Yc + Ry * sin(t)
+
+  dX(t)/dt = - Rx * sin(t)
+  dY(t)/dt = + Ry * cos(t)
+}
+procedure EllipticalArcToBezier(Xc, Yc, Rx, Ry, startAngle, endAngle: Double;
+  var P1, P2, P3, P4: T3DPoint);
+var
+  halfLength, arcLength, alfa: Double;
+begin
+  arcLength := endAngle - startAngle;
+  halfLength := (endAngle - startAngle) / 2;
+  alfa := sin(arcLength) * (Sqrt(4 + 3*sqr(tan(halfLength))) - 1) / 3;
+
+  // Start point
+  P1.X := Xc + Rx * cos(startAngle);
+  P1.Y := Yc + Ry * sin(startAngle);
+
+  // End point
+  P4.X := Xc + Rx * cos(endAngle);
+  P4.Y := Yc + Ry * sin(endAngle);
+
+  // Control points
+  P2.X := P1.X + alfa * -1 * Rx * sin(startAngle);
+  P2.Y := P1.Y + alfa * Ry * cos(startAngle);
+
+  P3.X := P4.X - alfa * -1 * Rx * sin(endAngle);
+  P3.Y := P4.Y - alfa * Ry * cos(endAngle);
+end;
+
+procedure CircularArcToBezier(Xc, Yc, R, startAngle, endAngle: Double; var P1,
+  P2, P3, P4: T3DPoint);
+begin
+  EllipticalArcToBezier(Xc, Yc, R, R, startAngle, endAngle, P1, P2, P3, P4);
+end;
+
 end.