Browse Source

fpvectorial: Merges large changes to the EPS reader and to the Canvas output from the Lazarus-ccr. Now it works perfectly with the testcases

git-svn-id: trunk@18977 -
sekelsenmat 14 years ago
parent
commit
50067bf42a

+ 306 - 57
packages/fpvectorial/src/epsvectorialreader.pas

@@ -7,6 +7,8 @@ License: The same modified LGPL as the Free Pascal RTL
 AUTHORS: Felipe Monteiro de Carvalho
 
 Documentation: http://www.tailrecursive.org/postscript/postscript.html
+
+Good reference: http://atrey.karlin.mff.cuni.cz/~milanek/PostScript/Reference/PSL2e.html
 }
 unit epsvectorialreader;
 
@@ -16,6 +18,11 @@ unit epsvectorialreader;
 {.$define FPVECTORIALDEBUG_COLORS}
 {.$define FPVECTORIALDEBUG_ROLL}
 {.$define FPVECTORIALDEBUG_CODEFLOW}
+{.$define FPVECTORIALDEBUG_INDEX}
+{.$define FPVECTORIALDEBUG_DICTIONARY}
+{.$define FPVECTORIALDEBUG_CONTROL}
+{.$define FPVECTORIALDEBUG_ARITHMETIC}
+{.$define FPVECTORIALDEBUG_CLIP_REGION}
 
 interface
 
@@ -51,7 +58,7 @@ type
     destructor Destroy; override;
   end;
 
-  TETType = (ettNamedElement, ettOperand, ettOperator);
+  TETType = (ettNamedElement, ettOperand, ettOperator, ettDictionary);
 
   { TExpressionToken }
 
@@ -71,8 +78,13 @@ type
   public
     Color: TFPColor;
     TranslateX, TranslateY: Double;
+    ScaleX, ScaleY: Double; // not used currently
     ClipPath: TPath;
     ClipMode: TvClipMode;
+    OverPrint: Boolean; // not used currently
+    //
+    PenWidth: Integer;
+    //
     function Duplicate: TGraphicState;
   end;
 
@@ -81,7 +93,8 @@ type
   TPSTokenizer = class
   public
     Tokens: TPSTokens;
-    constructor Create;
+    FCurLine: Integer;
+    constructor Create(ACurLine: Integer = -1);
     destructor Destroy; override;
     procedure ReadFromStream(AStream: TStream);
     procedure DebugOut();
@@ -146,7 +159,12 @@ begin
   Result.Color := Color;
   Result.TranslateX := TranslateX;
   Result.TranslateY := TranslateY;
+  Result.ScaleX := ScaleX;
+  Result.ScaleY := ScaleY;
   Result.ClipPath := ClipPath;
+  Result.ClipMode := ClipMode;
+  Result.OverPrint := OverPrint;
+  Result.PenWidth := PenWidth;
 end;
 
 { TPSToken }
@@ -202,10 +220,13 @@ end;
 
 { TPSTokenizer }
 
-constructor TPSTokenizer.Create;
+// ACurLine < 0 indicates that we should use the line of this list of strings
+// else we use ACurLine
+constructor TPSTokenizer.Create(ACurLine: Integer);
 begin
   inherited Create;
   Tokens := TPSTokens.Create;
+  FCurLine := ACurLine;
 end;
 
 destructor TPSTokenizer.Destroy;
@@ -243,6 +264,7 @@ begin
 
     lIsEndOfLine := IsEndOfLine(Byte(CurChar), AStream);
     if lIsEndOfLine then Inc(CurLine);
+    if FCurLine >= 0 then CurLine := FCurLine;
 
     case State of
       { Searching for a token }
@@ -540,7 +562,7 @@ begin
 
   if not AToken.Parsed then
   begin
-    ProcTokenizer := TPSTokenizer.Create;
+    ProcTokenizer := TPSTokenizer.Create(AToken.Line);
     lStream := TMemoryStream.Create;
     try
       // Copy the string to a Stream
@@ -773,13 +795,21 @@ begin
     Stack.Push(NewToken);
     Exit(True);
   end;
+  // anyn … any0 n index anyn … any0 anyn
   // Duplicate arbitrary element
   if AToken.StrValue = 'index' then
   begin
+    {$ifdef FPVECTORIALDEBUG_INDEX}
+    WriteLn('[TvEPSVectorialReader.ExecuteStackManipulationOperator] index');
+//    DebugStack();
+    {$endif}
+
     Param1 := TPSToken(Stack.Pop);
     lIndexN := Round(Param1.FloatValue);
     SetLength(lTokens, lIndexN+1);
 
+    if lIndexN < 0 then raise Exception.Create('[TvEPSVectorialReader.ExecuteStackManipulationOperator] index operator: n must be positive or zero');
+
     // Unroll all elements necessary
 
     for i := 0 to lIndexN do
@@ -788,8 +818,7 @@ begin
       Param2 := lTokens[i];
       if Param2 = nil then
       begin
-        // raise Exception.Create('[TvEPSVectorialReader.ExecuteStackManipulationOperator] nil element poped in operator index');
-        Exit(True);
+        raise Exception.Create(Format('[TvEPSVectorialReader.ExecuteStackManipulationOperator] Stack underflow in operation "index". Error at line %d', [AToken.Line]));
       end;
     end;
 
@@ -839,7 +868,7 @@ begin
     WriteLn(Format('[TvEPSVectorialReader] roll: N=%d J=%d', [lIndexN, lIndexJ]));
     {$endif}
 
-    if lIndexN < 0 then raise Exception.Create('[TvEPSVectorialReader.ExecuteStackManipulationOperator] rool operator: n must be positive');
+    if lIndexN < 0 then raise Exception.Create('[TvEPSVectorialReader.ExecuteStackManipulationOperator] rool operator: n must be positive or zero');
 
     if lIndexJ = 0 then Exit;
 
@@ -853,8 +882,8 @@ begin
       Param2 := lTokens[i];
       if Param2 = nil then
       begin
-        // raise Exception.Create('[TvEPSVectorialReader.ExecuteStackManipulationOperator] nil element poped in operator index');
-        Exit(True);
+        raise Exception.Create('[TvEPSVectorialReader.ExecuteStackManipulationOperator] nil element poped in operator index');
+        //Exit(True);
       end;
     end;
 
@@ -929,7 +958,7 @@ end;
 function TvEPSVectorialReader.ExecuteControlOperator(AToken: TExpressionToken;
   AData: TvVectorialDocument): Boolean;
 var
-  Param1, Param2, Param3, Param4: TPSToken;
+  Param1, Param2, Param3, Param4, CounterToken: TPSToken;
   NewToken: TExpressionToken;
   FloatCounter: Double;
 begin
@@ -972,9 +1001,33 @@ begin
 
     Exit(True);
   end;
-  // Establish context for catching stop
+  {
+    Establish context for catching stop
+
+     executes any, which is typically, but not necessarily, a procedure, executable file,
+     or executable string object. If any runs to completion normally, stopped returns false on the operand stack.
+
+     If any terminates prematurely as a result of executing stop, stopped returns
+     true on the operand stack. Regardless of the outcome, the interpreter resumes execution at the next object in normal sequence after stopped.
+     This mechanism provides an effective way for a PostScript language program
+     to "catch" errors or other premature terminations, retain control, and perhaps perform its own error recovery.
+
+     EXAMPLE:
+     { ... } stopped {handleerror} if
+
+     If execution of the procedure {...} causes an error,
+     the default error-reporting procedure is invoked (by handleerror).
+     In any event, normal execution continues at the token following the if.
+
+     ERRORS: stackunderflow
+  }
   if AToken.StrValue = 'stopped' then
   begin
+    {$ifdef FPVECTORIALDEBUG_CONTROL}
+    WriteLn('[TvEPSVectorialReader.ExecuteControlOperator] stopped');
+//    DebugStack();
+    {$endif}
+
     Param1 := TPSToken(Stack.Pop);
 
     if not (Param1 is TProcedureToken) then
@@ -1011,7 +1064,51 @@ begin
 
     Exit(True);
   end;
-  // initial increment limit proc for
+  { initial increment limit proc for -
+
+   executes proc repeatedly, passing it a sequence of values from initial
+   by steps of increment to limit. The for operator expects initial, increment,
+   and limit to be numbers. It maintains a temporary internal variable, known as
+   the control variable, which it first sets to initial. Then, before each
+   repetition, it compares the control variable with the termination value limit.
+   If limit has not been exceeded, it pushes the control variable on the operand
+   stack, executes proc, and adds increment to the control variable.
+
+   The termination condition depends on whether increment is positive or negative.
+   If increment is positive, for terminates when the control variable becomes
+   greater than limit. If increment is negative, for terminates when the control
+   variable becomes less than limit. If initial meets the termination condition,
+   for does not execute proc at all. If proc executes the exit operator,
+   for terminates prematurely.
+
+   Usually, proc will use the value on the operand stack for some purpose.
+   However, if proc does not remove the value, it will remain there.
+   Successive executions of proc will cause successive values of the control
+   variable to accumulate on the operand stack.
+
+   EXAMPLE:
+   0 1 1 4 {add} for -> 10
+   1 2 6 { } for -> 1 3 5
+   3 -.5 1 {-> } for -> 3.0 2.5 2.0 1.5 1.0
+
+   In the first example, the value of the control variable is added to whatever
+   is on the stack, so 1, 2, 3, and 4 are added in turn to a running sum whose
+   initial value is 0. The second example has an empty procedure, so the
+   successive values of the control variable are left on the stack. The
+   last example counts backward from 3 to 1 by halves, leaving the successive
+   values on the stack.
+
+   Beware of using reals instead of integers for any of the first three operands.
+   Most real numbers are not represented exactly. This can cause an error to
+   accumulate in the value of the control variable, with possibly surprising results.
+   In particular, if the difference between initial and limit is a multiple of
+   increment, as in the third line of the example, the control variable may not
+   achieve the limit value.
+
+   ERRORS: stackoverflow stackunderflow, typecheck
+
+   SEE ALSO: repeat, loop, forall, exit
+  }
   if AToken.StrValue = 'for' then
   begin
     Param1 := TPSToken(Stack.Pop);
@@ -1025,9 +1122,19 @@ begin
     FloatCounter := Param4.FloatValue;
     while FloatCounter < Param2.FloatValue do
     begin
+      CounterToken := Param4.Duplicate();
+      CounterToken.FloatValue := FloatCounter;
+      Stack.Push(CounterToken);
+
       ExecuteProcedureToken(TProcedureToken(Param1), AData);
 
       FloatCounter := FloatCounter + Param3.FloatValue;
+
+      if ExitCalled then
+      begin
+        ExitCalled := False;
+        Break;
+      end;
     end;
 
     Exit(True);
@@ -1036,6 +1143,11 @@ begin
   // if it is executable or false if it is literal
   if AToken.StrValue = 'xcheck' then
   begin
+//    {$ifdef FPVECTORIALDEBUG_CONTROL}
+//    WriteLn('[TvEPSVectorialReader.ExecuteControlOperator] xcheck');
+//    DebugStack();
+//    {$endif}
+
     Param1 := TPSToken(Stack.Pop);
 
     NewToken := TExpressionToken.Create;
@@ -1119,6 +1231,12 @@ begin
     {$ifdef FPVECTORIALDEBUG_PATHS}
     WriteLn('[TvEPSVectorialReader.ExecutePaintingOperator] stroke');
     {$endif}
+    AData.SetPenStyle(psSolid);
+    AData.SetBrushStyle(bsClear);
+    AData.SetPenColor(CurrentGraphicState.Color);
+    AData.SetClipPath(CurrentGraphicState.ClipPath, CurrentGraphicState.ClipMode);
+    AData.SetPenWidth(CurrentGraphicState.PenWidth);
+    AData.EndPath();
     Exit(True);
   end;
 
@@ -1128,6 +1246,10 @@ begin
     WriteLn('[TvEPSVectorialReader.ExecutePaintingOperator] eofill');
     {$endif}
     AData.SetBrushStyle(bsSolid);
+    AData.SetPenStyle(psSolid);
+    AData.SetClipPath(CurrentGraphicState.ClipPath, CurrentGraphicState.ClipMode);
+    AData.SetPenWidth(CurrentGraphicState.PenWidth);
+    AData.EndPath();
 
     Exit(True);
   end;
@@ -1378,7 +1500,7 @@ begin
   Result := False;
 
   // Division
-  // Param2 Param1 div ==> Param2 div Param1
+  // Param2 Param1 div ==> (Param2 div Param1)
   if AToken.StrValue = 'div' then
   begin
     Param1 := TPSToken(Stack.Pop);
@@ -1386,12 +1508,15 @@ begin
     NewToken := TExpressionToken.Create;
     NewToken.ETType := ettOperand;
     NewToken.FloatValue := Param2.FloatValue / Param1.FloatValue;
-    NewToken.StrValue := FloatToStr(Param1.FloatValue);
+    NewToken.StrValue := FloatToStr(NewToken.FloatValue);
     Stack.Push(NewToken);
+    {$ifdef FPVECTORIALDEBUG_ARITHMETIC}
+    WriteLn(Format('[TvEPSVectorialReader.ExecuteArithmeticAndMathOperator] %f %f div %f', [Param2.FloatValue, Param1.FloatValue, NewToken.FloatValue]));
+    {$endif}
     Exit(True);
   end;
 
-  // Param2 Param1 mul ==> Param2 mul Param1
+  // Param2 Param1 mul ==> (Param2 mul Param1)
   if AToken.StrValue = 'mul' then
   begin
     Param1 := TPSToken(Stack.Pop);
@@ -1399,7 +1524,7 @@ begin
     NewToken := TExpressionToken.Create;
     NewToken.ETType := ettOperand;
     NewToken.FloatValue := Param2.FloatValue * Param1.FloatValue;
-    NewToken.StrValue := FloatToStr(Param1.FloatValue);
+    NewToken.StrValue := FloatToStr(NewToken.FloatValue);
     Stack.Push(NewToken);
     Exit(True);
   end;
@@ -1411,7 +1536,7 @@ begin
     Param1 := TPSToken(Stack.Pop); // num2
     Param2 := TPSToken(Stack.Pop); // num1
     NewToken.FloatValue := Param2.FloatValue - Param1.FloatValue;
-    NewToken.StrValue := FloatToStr(Param1.FloatValue);
+    NewToken.StrValue := FloatToStr(NewToken.FloatValue);
     Stack.Push(NewToken);
     Exit(True);
   end;
@@ -1464,62 +1589,71 @@ var
 begin
   Result := False;
 
-  //
+  // – newpath –              Initialize current path to be empty
   if AToken.StrValue = 'newpath' then
   begin
     {$ifdef FPVECTORIALDEBUG_PATHS}
     WriteLn('[TvEPSVectorialReader.ExecutePathConstructionOperator] newpath');
     {$endif}
-    AData.SetClipPath(CurrentGraphicState.ClipPath, CurrentGraphicState.ClipMode);
+//    AData.SetClipPath(CurrentGraphicState.ClipPath, CurrentGraphicState.ClipMode);
+//    AData.SetPenWidth(CurrentGraphicState.PenWidth);
+//    AData.SetClipPath(CurrentGraphicState.ClipPath, CurrentGraphicState.ClipMode);
+    AData.SetBrushStyle(bsClear);
+    AData.SetPenStyle(psClear);
     AData.EndPath();
     AData.StartPath();
 
     AData.SetPenColor(CurrentGraphicState.Color);
     AData.SetBrushColor(CurrentGraphicState.Color);
+    AData.SetPenStyle(psClear);
 
     Exit(True);
   end;
-  // Param2 Param1 moveto ===> moveto(X=Param2, Y=Param1);
+  // Param2 Param1 moveto - ===> moveto(X=Param2, Y=Param1);
   if AToken.StrValue = 'moveto' then
   begin
     Param1 := TPSToken(Stack.Pop);
     Param2 := TPSToken(Stack.Pop);
     PostScriptCoordsToFPVectorialCoords(Param1, Param2, PosX, PosY);
-    PosX := PosX + CurrentGraphicState.TranslateX;
-    PosY := PosY + CurrentGraphicState.TranslateY;
+    PosX2 := PosX + CurrentGraphicState.TranslateX;
+    PosY2 := PosY + CurrentGraphicState.TranslateY;
     {$ifdef FPVECTORIALDEBUG_PATHS}
-    WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] moveto %f, %f', [PosX, PosY]));
+    WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] moveto %f, %f CurrentGraphicState.Translate %f, %f Resulting Value %f, %f',
+      [PosX, PosY, CurrentGraphicState.TranslateX, CurrentGraphicState.TranslateY, PosX2, PosY2]));
     {$endif}
-    AData.AddMoveToPath(PosX, PosY);
+    AData.AddMoveToPath(PosX2, PosY2);
     Exit(True);
   end;
   // Absolute LineTo
+  // x y lineto –             Append straight line to (x, y)
   if AToken.StrValue = 'lineto' then
   begin
     Param1 := TPSToken(Stack.Pop);
     Param2 := TPSToken(Stack.Pop);
     PostScriptCoordsToFPVectorialCoords(Param1, Param2, PosX, PosY);
-    PosX := PosX + CurrentGraphicState.TranslateX;
-    PosY := PosY + CurrentGraphicState.TranslateY;
+    PosX2 := PosX + CurrentGraphicState.TranslateX;
+    PosY2 := PosY + CurrentGraphicState.TranslateY;
     {$ifdef FPVECTORIALDEBUG_PATHS}
-    WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] lineto %f, %f', [PosX, PosY]));
+    WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] lineto %f, %f Resulting value %f, %f', [PosX, PosY, PosX2, PosY2]));
     {$endif}
-    AData.AddLineToPath(PosX, PosY);
+    AData.AddLineToPath(PosX2, PosY2);
     Exit(True);
   end;
   // Relative LineTo
+  // dx dy rlineto –          Perform relative lineto
   if AToken.StrValue = 'rlineto' then
   begin
     Param1 := TPSToken(Stack.Pop);
     Param2 := TPSToken(Stack.Pop);
     PostScriptCoordsToFPVectorialCoords(Param1, Param2, PosX, PosY);
     AData.GetCurrentPathPenPos(BaseX, BaseY);
-    PosX := PosX + CurrentGraphicState.TranslateX;
-    PosY := PosY + CurrentGraphicState.TranslateY;
+    PosX2 := PosX + BaseX;
+    PosY2 := PosY + BaseY;
     {$ifdef FPVECTORIALDEBUG_PATHS}
-    WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] rlineto %f, %f', [BaseX + PosX, BaseY + PosY]));
+    WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] rlineto %f, %f Base %f, %f Resulting %f, %f',
+      [PosX, PosY, BaseX, BaseY, PosX2, PosY2]));
     {$endif}
-    AData.AddLineToPath(BaseX + PosX, BaseY + PosY);
+    AData.AddLineToPath(PosX2, PosY2);
     Exit(True);
   end;
   // dx1 dy1 dx2 dy2 dx3 dy3 rcurveto –
@@ -1543,10 +1677,10 @@ begin
     PostScriptCoordsToFPVectorialCoords(Param1, Param2, PosX3, PosY3);
     AData.GetCurrentPathPenPos(BaseX, BaseY);
     // First move to the start of the arc
-    BaseX := BaseX + CurrentGraphicState.TranslateX;
-    BaseY := BaseY + CurrentGraphicState.TranslateY;
+//    BaseX := BaseX + CurrentGraphicState.TranslateX;
+//    BaseY := BaseY + CurrentGraphicState.TranslateY;
     {$ifdef FPVECTORIALDEBUG_PATHS}
-    WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] translate %f, %f',
+    WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] rcurveto translate %f, %f',
       [CurrentGraphicState.TranslateX, CurrentGraphicState.TranslateY]));
     WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] rcurveto from %f, %f via %f, %f %f, %f to %f, %f',
       [BaseX, BaseY, BaseX + PosX, BaseY + PosY, BaseX + PosX2, BaseY + PosY2, BaseX + PosX3, BaseY + PosY3]));
@@ -1554,6 +1688,7 @@ begin
     AData.AddBezierToPath(BaseX + PosX, BaseY + PosY, BaseX + PosX2, BaseY + PosY2, BaseX + PosX3, BaseY + PosY3);
     Exit(True);
   end;
+  // – closepath –
   //
   // Don't do anything, because a stroke or fill might come after closepath
   // and newpath will be called after stroke and fill anyway
@@ -1604,18 +1739,33 @@ begin
       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}
     {$ifdef FPVECTORIALDEBUG_PATHS}
-    WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] arc %f, %f', [PosX, PosY]));
+    WriteLn(Format('[TvEPSVectorialReader.ExecutePathConstructionOperator] arc X,Y=%f, %f Resulting X,Y=%f, %f R=%f Angles Start,End=%f,%f',
+      [Param5.FloatValue, Param4.FloatValue, PosX, PosY, Param3.FloatValue, Param2.FloatValue, Param1.FloatValue]));
     {$endif}
     Exit(True);
   end;
   // – eoclip – Clip using even-odd rule
+  //
+  // intersects the inside of the current clipping path with the inside
+  // of the current path to produce a new, smaller current clipping path.
+  // The inside of the current path is determined by the even-odd rule,
+  // while the inside of the current clipping path is determined by whatever
+  // rule was used at the time that path was created.
+  //
+  // Except for the choice of insideness rule, the behavior of eoclip is identical to that of clip.
+  //
+  // ERRORS: limitcheck
+  //
   if AToken.StrValue = 'eoclip' then
   begin
+    {$ifdef FPVECTORIALDEBUG_PATHS}
+    WriteLn('[TvEPSVectorialReader.ExecutePathConstructionOperator] eoclip');
+    {$endif}
+    {$ifndef FPVECTORIALDEBUG_CLIP_REGION}
     AData.SetPenStyle(psClear);
+    {$endif}
+    AData.SetBrushStyle(bsClear);
     AData.EndPath();
     CurrentGraphicState.ClipPath := AData.GetPath(AData.GetPathCount()-1);
     CurrentGraphicState.ClipMode := vcmEvenOddRule;
@@ -1681,7 +1831,7 @@ var
 begin
   Result := False;
 
-  //
+  // – gsave – Push graphics state
   if AToken.StrValue = 'gsave' then
   begin
     GraphicStateStack.Push(CurrentGraphicState.Duplicate());
@@ -1690,7 +1840,7 @@ begin
     {$endif}
     Exit(True);
   end;
-  //
+  // – grestore -                 Pop graphics state
   if AToken.StrValue = 'grestore' then
   begin
     lGraphicState := TGraphicState(GraphicStateStack.Pop());
@@ -1702,19 +1852,22 @@ begin
     {$endif}
     Exit(True);
   end;
-  //
+  // num setlinewidth –           Set line width
   if AToken.StrValue = 'setlinewidth' then
   begin
     Param1 := TPSToken(Stack.Pop);
+    CurrentGraphicState.PenWidth := Round(Param1.FloatValue);
     Exit(True);
   end;
-  //
+  // int setlinecap –             Set shape of line ends for stroke (0 = butt,
+  //                             1 = round, 2 = square)
   if AToken.StrValue = 'setlinecap' then
   begin
     Param1 := TPSToken(Stack.Pop);
     Exit(True);
   end;
-  //
+  // int setlinejoin –            Set shape of corners for stroke (0 = miter,
+  //                             1 = round, 2 = bevel)
   if AToken.StrValue = 'setlinejoin' then
   begin
     Param1 := TPSToken(Stack.Pop);
@@ -1830,34 +1983,80 @@ var
 begin
   Result := False;
 
-  //
+  // bool setoverprint – Set overprint parameter
+  if AToken.StrValue = 'setoverprint' then
+  begin
+    Param1 := TPSToken(Stack.Pop);
+
+    CurrentGraphicState.OverPrint := Param1.BoolValue;
+
+    Exit(True);
+  end;
+  // sx sy scale – Scale user space by sx and sy
   if AToken.StrValue = 'scale' then
   begin
     Param1 := TPSToken(Stack.Pop);
     Param2 := TPSToken(Stack.Pop);
+
+    if Param2 = nil then
+    begin
+      Exit(True);
+    end;
+
+    CurrentGraphicState.ScaleX := Param2.FloatValue;
+    CurrentGraphicState.ScaleY := Param1.FloatValue;
+    {$ifdef FPVECTORIALDEBUG_PATHS}
+    WriteLn(Format('[TvEPSVectorialReader.ExecuteGraphicStateOperatorsDI] scale %f %f',
+     [CurrentGraphicState.ScaleX, CurrentGraphicState.ScaleY]));
+    {$endif}
+
     Exit(True);
   end;
-  // tx ty translate –        Translate user space by (tx , ty)
+  {
+    translate tx ty translate
+    - tx ty matrix translate matrix
+
+    With no matrix operand, translate builds a temporary matrix and concatenates
+    this matrix with the current transformation matrix (CTM). Precisely, translate
+    replaces the CTM by T x CTM. The effect of this is to move the origin of the
+    user coordinate system by tx units in the x direction and ty units in the y
+    direction relative to the former user coordinate system. The sizes of the x
+    and y units and the orientation of the axes are unchanged.
+
+    If the matrix operand is supplied, translate replaces the value of matrix by
+    T and pushes the modified matrix back on the operand stack.
+    In this case, translate does not affect the CTM.
+  }
   if AToken.StrValue = 'translate' then
   begin
     Param1 := TPSToken(Stack.Pop); // ty
     Param2 := TPSToken(Stack.Pop); // tx
 
-    if Param2 = nil then Exit(True);
+    if Param2 = nil then
+    begin
+      raise Exception.Create('[TvEPSVectorialReader.ExecuteGraphicStateOperatorsDI] Stack underflow in operator "translate"');
+    end;
 
     {$ifdef FPVECTORIALDEBUG_PATHS}
-    WriteLn(Format('[TvEPSVectorialReader.ExecuteGraphicStateOperatorsDI] translate %f, %f', [Param2.FloatValue, Param1.FloatValue]));
+    WriteLn(Format('[TvEPSVectorialReader.ExecuteGraphicStateOperatorsDI] translate %f, %f CurrentGraphicState.Translate %f %f',
+      [Param2.FloatValue, Param1.FloatValue, CurrentGraphicState.TranslateX, CurrentGraphicState.TranslateY]));
     {$endif}
 
-    CurrentGraphicState.TranslateX := Param2.FloatValue;
-    CurrentGraphicState.TranslateY := Param1.FloatValue;
+    CurrentGraphicState.TranslateX := CurrentGraphicState.TranslateX + Param2.FloatValue;
+    CurrentGraphicState.TranslateY := CurrentGraphicState.TranslateY + Param1.FloatValue;
 
     Exit(True);
   end;
-  //
+  // angle rotate – Rotate user space by angle degrees
   if AToken.StrValue = 'rotate' then
   begin
     Param1 := TPSToken(Stack.Pop);
+
+    {$ifdef FPVECTORIALDEBUG_PATHS}
+    WriteLn(Format('[TvEPSVectorialReader.ExecuteGraphicStateOperatorsDI] rotate angle=%f', [Param1.FloatValue]));
+    DebugStack();
+    {$endif}
+
     Exit(True);
   end;
 end;
@@ -1906,6 +2105,7 @@ begin
   Result := False;
 
   // Adds a dictionary definition
+  // key value def –       Associate key and value in current dictionary
   if AToken.StrValue = 'def' then
   begin
     Param1 := TPSToken(Stack.Pop);
@@ -1914,20 +2114,61 @@ begin
     Exit(True);
   end;
 
-  // Can be ignored
+  // Can be ignored, because in the files found it only loads
+  // standard routines, like /moveto ...
+  //
+  // key load value        Search dictionary stack for key and return
+  //                      associated value
   if AToken.StrValue = 'load' then
   begin
+//    {$ifdef FPVECTORIALDEBUG_DICTIONARY}
+//    WriteLn('[TvEPSVectorialReader.ExecuteDictionaryOperators] load');
+//    DebugStack();
+//    {$endif}
+
     Exit(True);
   end;
 
   // Find dictionary in which key is defined
+  //key where dict true   Find dictionary in which key is defined
+  //           or false
   if AToken.StrValue = 'where' then
   begin
+    {$ifdef FPVECTORIALDEBUG_DICTIONARY}
+    WriteLn('[TvEPSVectorialReader.ExecuteDictionaryOperators] where');
+    DebugStack();
+    {$endif}
+
     Param1 := TPSToken(Stack.Pop);
-    NewToken := TExpressionToken.Create;
-    NewToken.ETType := ettOperand;
-    NewToken.BoolValue := False;
-    Stack.Push(NewToken);
+
+    if Dictionary.IndexOf(Param1.StrValue) >= 0 then
+    begin
+      // We use only 1 dictionary, so this is just a representation of our single dictionary
+      NewToken := TExpressionToken.Create;
+      NewToken.ETType := ettDictionary;
+      Stack.Push(NewToken);
+
+      NewToken := TExpressionToken.Create;
+      NewToken.ETType := ettOperand;
+      NewToken.BoolValue := True;
+      Stack.Push(NewToken);
+
+      {$ifdef FPVECTORIALDEBUG_DICTIONARY}
+      WriteLn('[TvEPSVectorialReader.ExecuteDictionaryOperators] where True');
+      {$endif}
+    end
+    else
+    begin
+      NewToken := TExpressionToken.Create;
+      NewToken.ETType := ettOperand;
+      NewToken.BoolValue := False;
+      Stack.Push(NewToken);
+
+      {$ifdef FPVECTORIALDEBUG_DICTIONARY}
+      WriteLn('[TvEPSVectorialReader.ExecuteDictionaryOperators] where False');
+      {$endif}
+    end;
+
     Exit(True);
   end;
 end;
@@ -1954,8 +2195,16 @@ begin
   Result := False;
 
   // Just a hint for more efficient parsing, we can ignore
+  //
+  // proc bind proc Replace operator names in proc with
+  // operators; perform idiom recognition
   if AToken.StrValue = 'bind' then
   begin
+    {$ifdef FPVECTORIALDEBUG_CONTROL}
+    WriteLn('[TvEPSVectorialReader.ExecuteControlOperator] bind');
+    DebugStack();
+    {$endif}
+
     Exit(True);
   end;
 end;
@@ -2003,7 +2252,7 @@ begin
   FPointSeparator.DecimalSeparator := '.';
   FPointSeparator.ThousandSeparator := ',';
 
-  Tokenizer := TPSTokenizer.Create;
+  Tokenizer := TPSTokenizer.Create(-1);
   Stack := TObjectStack.Create;
   GraphicStateStack := TObjectStack.Create;
   Dictionary := TStringList.Create;

+ 67 - 38
packages/fpvectorial/src/fpvtocanvas.pas

@@ -5,6 +5,13 @@ unit fpvtocanvas;
 interface
 
 {.$define USE_LCL_CANVAS}
+{$ifdef USE_LCL_CANVAS}
+  {$define USE_CANVAS_CLIP_REGION}
+  {.$define DEBUG_CANVAS_CLIP_REGION}
+{$endif}
+{$ifndef Windows}
+{.$define FPVECTORIAL_TOCANVAS_DEBUG}
+{$endif}
 
 uses
   Classes, SysUtils, Math,
@@ -30,10 +37,6 @@ procedure DrawFPVTextToCanvas(ASource: TvVectorialDocument; CurText: TvText;
 
 implementation
 
-{$ifndef Windows}
-{.$define FPVECTORIAL_TOCANVAS_DEBUG}
-{$endif}
-
 function Rotate2DPoint(P,Fix :TPoint; alpha:double): TPoint;
 var
   sinus, cosinus : Extended;
@@ -98,7 +101,6 @@ end;
 
   DrawFPVectorialToCanvas(ASource, ADest, 0, ASource.Height, 1.0, -1.0);
 }
-{.$define FPVECTORIAL_TOCANVAS_DEBUG}
 procedure DrawFPVectorialToCanvas(ASource: TvVectorialDocument;
   ADest: TFPCustomCanvas;
   ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
@@ -151,6 +153,7 @@ var
   Cur2DBSegment: T2DBezierSegment absolute CurSegment;
   // For bezier
   CurX, CurY: Integer; // Not modified by ADestX, etc
+  CoordX2, CoordY2, CoordX3, CoordY3, CoordX4, CoordY4: Integer;
   CurveLength: Integer;
   t: Double;
   // For polygons
@@ -169,12 +172,13 @@ begin
 
   // Set the path Pen and Brush options
   ADest.Pen.Style := CurPath.Pen.Style;
-  ADest.Pen.Width := CurPath.Pen.Width;
+  ADest.Pen.Width := Round(CurPath.Pen.Width * AMulX);
+  if ADest.Pen.Width < 1 then ADest.Pen.Width := 1;
   ADest.Pen.FPColor := CurPath.Pen.Color;
   ADest.Brush.FPColor := CurPath.Brush.Color;
 
   // Prepare the Clipping Region, if any
-  {$ifdef USE_LCL_CANVAS}
+  {$ifdef USE_CANVAS_CLIP_REGION}
   if CurPath.ClipPath <> nil then
   begin
     OldClipRegion := LCLIntf.CreateEmptyRegion();
@@ -182,16 +186,24 @@ begin
     ClipRegion := ConvertPathToRegion(CurPath.ClipPath, ADestX, ADestY, AMulX, AMulY);
     SelectClipRgn(ACanvas.Handle, ClipRegion);
     DeleteObject(ClipRegion);
+    // debug info
+    {$ifdef DEBUG_CANVAS_CLIP_REGION}
+    ConvertPathToPoints(CurPath.ClipPath, ADestX, ADestY, AMulX, AMulY, Points);
+    ACanvas.Polygon(Points);
+    {$endif}
   end;
   {$endif}
 
   //
-  // For solid paths, draw a polygon instead
+  // For solid paths, draw a polygon for the main internal area
   //
-  CurPath.PrepareForSequentialReading;
-
-  if CurPath.Brush.Style = bsSolid then
+  if CurPath.Brush.Style <> bsClear then
   begin
+    CurPath.PrepareForSequentialReading;
+
+    {$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
+    Write(' Solid Path Internal Area');
+    {$endif}
     ADest.Brush.Style := CurPath.Brush.Style;
 
     SetLength(Points, CurPath.Len);
@@ -206,16 +218,24 @@ begin
 
       Points[j].X := CoordX;
       Points[j].Y := CoordY;
+
+      {$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
+      Write(Format(' P%d,%d', [CoordY, CoordY]));
+      {$endif}
     end;
 
     ADest.Polygon(Points);
 
-    Exit;
+    {$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
+    Write(' Now the details ');
+    {$endif}
   end;
 
   //
   // For other paths, draw more carefully
   //
+  CurPath.PrepareForSequentialReading;
+
   for j := 0 to CurPath.Len - 1 do
   begin
     //WriteLn('j = ', j);
@@ -238,9 +258,12 @@ begin
     begin
       ADest.Pen.FPColor := T2DSegmentWithPen(Cur2DSegment).Pen.Color;
 
-      CoordX := CoordToCanvasX(Cur2DSegment.X);
-      CoordY := CoordToCanvasY(Cur2DSegment.Y);
-      ADest.LineTo(CoordX, CoordY);
+      CoordX := CoordToCanvasX(PosX);
+      CoordY := CoordToCanvasY(PosY);
+      CoordX2 := CoordToCanvasX(Cur2DSegment.X);
+      CoordY2 := CoordToCanvasY(Cur2DSegment.Y);
+      ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
+
       PosX := Cur2DSegment.X;
       PosY := Cur2DSegment.Y;
 
@@ -252,9 +275,11 @@ begin
     end;
     st2DLine, st3DLine:
     begin
-      CoordX := CoordToCanvasX(Cur2DSegment.X);
-      CoordY := CoordToCanvasY(Cur2DSegment.Y);
-      ADest.LineTo(CoordX, CoordY);
+      CoordX := CoordToCanvasX(PosX);
+      CoordY := CoordToCanvasY(PosY);
+      CoordX2 := CoordToCanvasX(Cur2DSegment.X);
+      CoordY2 := CoordToCanvasY(Cur2DSegment.Y);
+      ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
       PosX := Cur2DSegment.X;
       PosY := Cur2DSegment.Y;
       {$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
@@ -265,23 +290,27 @@ begin
       lines between this parts }
     st2DBezier, st3DBezier:
     begin
-      CurveLength :=
-        Round(sqrt(sqr(Cur2DBSegment.X2 - PosX) + sqr(Cur2DBSegment.Y2 - PosY))) +
-        Round(sqrt(sqr(Cur2DBSegment.X3 - Cur2DBSegment.X2) + sqr(Cur2DBSegment.Y3 - Cur2DBSegment.Y2))) +
-        Round(sqrt(sqr(Cur2DBSegment.X - Cur2DBSegment.X3) + sqr(Cur2DBSegment.Y - Cur2DBSegment.Y3)));
+      CoordX := CoordToCanvasX(PosX);
+      CoordY := CoordToCanvasY(PosY);
+      CoordX2 := CoordToCanvasX(Cur2DBSegment.X2);
+      CoordY2 := CoordToCanvasY(Cur2DBSegment.Y2);
+      CoordX3 := CoordToCanvasX(Cur2DBSegment.X3);
+      CoordY3 := CoordToCanvasY(Cur2DBSegment.Y3);
+      CoordX4 := CoordToCanvasX(Cur2DBSegment.X);
+      CoordY4 := CoordToCanvasY(Cur2DBSegment.Y);
+      SetLength(Points, 0);
+      AddBezierToPoints(
+        Make2DPoint(CoordX, CoordY),
+        Make2DPoint(CoordX2, CoordY2),
+        Make2DPoint(CoordX3, CoordY3),
+        Make2DPoint(CoordX4, CoordY4),
+        Points
+      );
+
+      ADest.Brush.Style := CurPath.Brush.Style;
+      if Length(Points) >= 3 then
+        ADest.Polygon(Points);
 
-      for k := 1 to CurveLength do
-      begin
-        t := k / CurveLength;
-        CurX := Round(sqr(1 - t) * (1 - t) * PosX + 3 * t * sqr(1 - t) * Cur2DBSegment.X2 + 3 * t * t * (1 - t) * Cur2DBSegment.X3 + t * t * t * Cur2DBSegment.X);
-        CurY := Round(sqr(1 - t) * (1 - t) * PosY + 3 * t * sqr(1 - t) * Cur2DBSegment.Y2 + 3 * t * t * (1 - t) * Cur2DBSegment.Y3 + t * t * t * Cur2DBSegment.Y);
-        CoordX := CoordToCanvasX(CurX);
-        CoordY := CoordToCanvasY(CurY);
-        ADest.LineTo(CoordX, CoordY);
-//        {$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
-//        Write(Format(' CL%d,%d', [CoordX, CoordY]));
-//        {$endif}
-      end;
       PosX := Cur2DSegment.X;
       PosY := Cur2DSegment.Y;
 
@@ -300,7 +329,7 @@ begin
   {$endif}
 
   // Restores the previous Clip Region
-  {$ifdef USE_LCL_CANVAS}
+  {$ifdef USE_CANVAS_CLIP_REGION}
   if CurPath.ClipPath <> nil then
   begin
     SelectClipRgn(ACanvas.Handle, OldClipRegion); //Using OldClipRegion crashes in Qt
@@ -407,9 +436,9 @@ begin
       BoundsBottom := IntTmp;
     end;
     // Arc(ALeft, ATop, ARight, ABottom, Angle16Deg, Angle16DegLength: Integer);
-    {$ifdef FPVECTORIALDEBUG}
-    WriteLn(Format('Drawing Arc Center=%f,%f Radius=%f StartAngle=%f AngleLength=%f',
-      [CurArc.CenterX, CurArc.CenterY, CurArc.Radius, IntStartAngle/16, IntAngleLength/16]));
+    {$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
+//    WriteLn(Format('Drawing Arc Center=%f,%f Radius=%f StartAngle=%f AngleLength=%f',
+//      [CurArc.CenterX, CurArc.CenterY, CurArc.Radius, IntStartAngle/16, IntAngleLength/16]));
     {$endif}
     ADest.Pen.FPColor := CurArc.Pen.Color;
     ALCLDest.Arc(

+ 81 - 9
packages/fpvectorial/src/fpvutils.pas

@@ -12,6 +12,7 @@ AUTHORS: Felipe Monteiro de Carvalho
 unit fpvutils;
 
 {.$define USE_LCL_CANVAS}
+{.$define FPVECTORIAL_BEZIERTOPOINTS_DEBUG}
 
 {$ifdef fpc}
   {$mode delphi}
@@ -28,6 +29,7 @@ uses
 
 type
   T10Strings = array[0..9] of shortstring;
+  TPointsArray = array of TPoint;
 
 // Color Conversion routines
 function FPColorToRGBHexString(AColor: TFPColor): string;
@@ -42,6 +44,8 @@ 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);
+procedure AddBezierToPoints(P1, P2, P3, P4: T3DPoint; var Points: TPointsArray);
+procedure ConvertPathToPoints(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray);
 // LCL-related routines
 {$ifdef USE_LCL_CANVAS}
 function ConvertPathToRegion(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double): HRGN;
@@ -186,20 +190,53 @@ begin
   EllipticalArcToBezier(Xc, Yc, R, R, startAngle, endAngle, P1, P2, P3, P4);
 end;
 
-{$ifdef USE_LCL_CANVAS}
-function ConvertPathToRegion(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double): HRGN;
+{ This routine converts a Bezier to a Polygon and adds the points of this poligon
+  to the end of the provided Points output variables }
+procedure AddBezierToPoints(P1, P2, P3, P4: T3DPoint; var Points: TPointsArray);
 var
-  i: Integer;
-  WindingMode: Integer;
-  Points: array of TPoint;
+  CurveLength, k, CurX, CurY, LastPoint: Integer;
+  t: Double;
+begin
+  {$ifdef FPVECTORIAL_BEZIERTOPOINTS_DEBUG}
+  Write(Format('[AddBezierToPoints] P1=%f,%f P2=%f,%f P3=%f,%f P4=%f,%f =>', [P1.X, P1.Y, P2.X, P2.Y, P3.X, P3.Y, P4.X, P4.Y]));
+  {$endif}
+
+  CurveLength :=
+    Round(sqrt(sqr(P2.X - P1.X) + sqr(P2.Y - P1.Y))) +
+    Round(sqrt(sqr(P3.X - P2.X) + sqr(P3.Y - P2.Y))) +
+    Round(sqrt(sqr(P4.X - P4.X) + sqr(P4.Y - P3.Y)));
+
+  LastPoint := Length(Points)-1;
+  SetLength(Points, Length(Points)+CurveLength);
+  for k := 1 to CurveLength do
+  begin
+    t := k / CurveLength;
+    CurX := Round(sqr(1 - t) * (1 - t) * P1.X + 3 * t * sqr(1 - t) * P2.X + 3 * t * t * (1 - t) * P3.X + t * t * t * P4.X);
+    CurY := Round(sqr(1 - t) * (1 - t) * P1.Y + 3 * t * sqr(1 - t) * P2.Y + 3 * t * t * (1 - t) * P3.Y + t * t * t * P4.Y);
+    Points[LastPoint+k].X := CurX;
+    Points[LastPoint+k].Y := CurY;
+    {$ifdef FPVECTORIAL_BEZIERTOPOINTS_DEBUG}
+    Write(Format(' P=%d,%d', [CurX, CurY]));
+    {$endif}
+  end;
+  {$ifdef FPVECTORIAL_BEZIERTOPOINTS_DEBUG}
+  WriteLn(Format(' CurveLength=%d', [CurveLength]));
+  {$endif}
+end;
+
+procedure ConvertPathToPoints(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray);
+var
+  i, LastPoint: Integer;
   CoordX, CoordY: Integer;
+  CoordX2, CoordY2, CoordX3, CoordY3, CoordX4, CoordY4: Integer;
   // Segments
   CurSegment: TPathSegment;
   Cur2DSegment: T2DSegment absolute CurSegment;
+  Cur2DBSegment: T2DBezierSegment absolute CurSegment;
 begin
   APath.PrepareForSequentialReading;
 
-  SetLength(Points, APath.Len);
+  SetLength(Points, 0);
 
   for i := 0 to APath.Len - 1 do
   begin
@@ -208,14 +245,49 @@ begin
     CoordX := CoordToCanvasX(Cur2DSegment.X, ADestX, AMulX);
     CoordY := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY);
 
-    Points[i].X := CoordX;
-    Points[i].Y := CoordY;
+    case CurSegment.SegmentType of
+    st2DBezier, st3DBezier:
+    begin
+      LastPoint := Length(Points)-1;
+      CoordX4 := CoordX;
+      CoordY4 := CoordY;
+      CoordX := Points[LastPoint].X;
+      CoordY := Points[LastPoint].Y;
+      CoordX2 := CoordToCanvasX(Cur2DBSegment.X2, ADestX, AMulX);
+      CoordY2 := CoordToCanvasY(Cur2DBSegment.Y2, ADestY, AMulY);
+      CoordX3 := CoordToCanvasX(Cur2DBSegment.X3, ADestX, AMulX);
+      CoordY3 := CoordToCanvasY(Cur2DBSegment.Y3, ADestY, AMulY);
+      AddBezierToPoints(
+        Make2DPoint(CoordX, CoordY),
+        Make2DPoint(CoordX2, CoordY2),
+        Make2DPoint(CoordX3, CoordY3),
+        Make2DPoint(CoordX4, CoordY4),
+        Points);
+    end;
+    else
+      LastPoint := Length(Points);
+      SetLength(Points, Length(Points)+1);
+      Points[LastPoint].X := CoordX;
+      Points[LastPoint].Y := CoordY;
+    end;
   end;
+end;
+
+{$ifdef USE_LCL_CANVAS}
+function ConvertPathToRegion(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double): HRGN;
+var
+  WindingMode: Integer;
+  Points: array of TPoint;
+begin
+  APath.PrepareForSequentialReading;
+
+  SetLength(Points, 0);
+  ConvertPathToPoints(APath, ADestX, ADestY, AMulX, AMulY, Points);
 
   if APath.ClipMode = vcmEvenOddRule then WindingMode := LCLType.ALTERNATE
   else WindingMode := LCLType.WINDING;
 
-  Result := LCLIntf.CreatePolygonRgn(@Points[0], APath.Len, WindingMode);
+  Result := LCLIntf.CreatePolygonRgn(@Points[0], Length(Points), WindingMode);
 end;
 {$endif}