diff --git a/packages/fpvectorial/examples/cdr2svg_mainform.lfm b/packages/fpvectorial/examples/cdr2svg_mainform.lfm index aa720d40e2..526e178ad8 100644 --- a/packages/fpvectorial/examples/cdr2svg_mainform.lfm +++ b/packages/fpvectorial/examples/cdr2svg_mainform.lfm @@ -1,10 +1,11 @@ -object Form1: TForm1 +object formCDR2SVG: TformCDR2SVG Left = 216 - Height = 240 + Height = 439 Top = 192 Width = 240 + BorderStyle = bsSingle Caption = 'cdr2svg' - ClientHeight = 240 + ClientHeight = 439 ClientWidth = 240 LCLVersion = '0.9.29' object Label1: TLabel @@ -60,21 +61,36 @@ object Form1: TForm1 TabOrder = 1 end object buttonConvert: TButton - Left = 32 + Left = 87 Height = 25 Top = 200 - Width = 75 + Width = 67 Caption = 'Convert' OnClick = buttonConvertClick TabOrder = 2 end object buttonQuit: TButton - Left = 136 + Left = 176 Height = 25 Top = 200 - Width = 75 + Width = 59 Caption = 'Quit' OnClick = buttonQuitClick TabOrder = 3 end + object imagePreview: TImage + Left = 8 + Height = 202 + Top = 232 + Width = 224 + end + object buttonVisualize: TButton + Left = 8 + Height = 25 + Top = 200 + Width = 59 + Caption = 'Visualize' + OnClick = buttonVisualizeClick + TabOrder = 4 + end end diff --git a/packages/fpvectorial/examples/cdr2svg_mainform.pas b/packages/fpvectorial/examples/cdr2svg_mainform.pas index 9096afe2e2..62a9f393d7 100644 --- a/packages/fpvectorial/examples/cdr2svg_mainform.pas +++ b/packages/fpvectorial/examples/cdr2svg_mainform.pas @@ -6,57 +6,85 @@ interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, - EditBtn; + EditBtn, ExtCtrls; type - { TForm1 } + { TformCDR2SVG } - TForm1 = class(TForm) + TformCDR2SVG = class(TForm) + buttonVisualize: TButton; buttonConvert: TButton; buttonQuit: TButton; editInput: TFileNameEdit; editOutput: TFileNameEdit; + imagePreview: TImage; Label1: TLabel; Label2: TLabel; Label3: TLabel; procedure buttonConvertClick(Sender: TObject); procedure buttonQuitClick(Sender: TObject); + procedure buttonVisualizeClick(Sender: TObject); private { private declarations } + function CheckInput(): Boolean; public { public declarations } end; var - Form1: TForm1; + formCDR2SVG: TformCDR2SVG; implementation uses - fpvectorial, cdrvectorialreader, svgvectorialwriter; + fpvectorial, cdrvectorialreader, svgvectorialwriter, pdfvectorialreader, + fpvtocanvas; {$R *.lfm} -{ TForm1 } +{ TformCDR2SVG } -procedure TForm1.buttonQuitClick(Sender: TObject); +procedure TformCDR2SVG.buttonQuitClick(Sender: TObject); begin Close; end; -procedure TForm1.buttonConvertClick(Sender: TObject); +procedure TformCDR2SVG.buttonVisualizeClick(Sender: TObject); var Vec: TvVectorialDocument; begin // First check the in input + if not CheckInput() then Exit; + + Vec := TvVectorialDocument.Create; + try + Vec.ReadFromFile(editInput.FileName, vfPDF); + imagePreview.Canvas.Brush.Color := clWhite; + imagePreview.Canvas.FillRect(0, 0, imagePreview.Width, imagePreview.Height); + DrawFPVectorialToCanvas(Vec, imagePreview.Canvas, 0, 0, 0.25, 0.25); + finally + Vec.Free; + end; +end; + +function TformCDR2SVG.CheckInput(): Boolean; +begin // todo... +end; + +procedure TformCDR2SVG.buttonConvertClick(Sender: TObject); +var + Vec: TvVectorialDocument; +begin + // First check the in input + if not CheckInput() then Exit; // Now convert Vec := TvVectorialDocument.Create; try Vec.ReadFromFile(editInput.FileName, vfPDF); - Vec.WriteToFile(editOutPut.FileName, vfGCodeAvisoCNCPrototipoV5); + Vec.WriteToFile(editOutPut.FileName, vfSVG); finally Vec.Free; end; diff --git a/packages/fpvectorial/examples/cdr2svg_visual.lpi b/packages/fpvectorial/examples/cdr2svg_visual.lpi index add68249c1..338b07f8ef 100644 --- a/packages/fpvectorial/examples/cdr2svg_visual.lpi +++ b/packages/fpvectorial/examples/cdr2svg_visual.lpi @@ -4,6 +4,9 @@ + + + <UseXPManifest Value="True"/> @@ -39,7 +42,7 @@ <Unit1> <Filename Value="cdr2svg_mainform.pas"/> <IsPartOfProject Value="True"/> - <ComponentName Value="Form1"/> + <ComponentName Value="formCDR2SVG"/> <ResourceBaseClass Value="Form"/> <UnitName Value="cdr2svg_mainform"/> </Unit1> @@ -70,7 +73,7 @@ </Other> </CompilerOptions> <Debugging> - <Exceptions Count="3"> + <Exceptions Count="4"> <Item1> <Name Value="EAbort"/> </Item1> @@ -80,6 +83,9 @@ <Item3> <Name Value="EFOpenError"/> </Item3> + <Item4> + <Name Value="EConvertError"/> + </Item4> </Exceptions> </Debugging> </CONFIG> diff --git a/packages/fpvectorial/examples/cdr2svg_visual.lpr b/packages/fpvectorial/examples/cdr2svg_visual.lpr index aef4846c5f..3551fefa2b 100644 --- a/packages/fpvectorial/examples/cdr2svg_visual.lpr +++ b/packages/fpvectorial/examples/cdr2svg_visual.lpr @@ -14,7 +14,7 @@ uses begin Application.Initialize; - Application.CreateForm(TForm1, Form1); + Application.CreateForm(TformCDR2SVG, formCDR2SVG); Application.Run; end. diff --git a/packages/fpvectorial/src/fpvectorial.pas b/packages/fpvectorial/src/fpvectorial.pas index 0b18f768d5..d2a50a2a9d 100644 --- a/packages/fpvectorial/src/fpvectorial.pas +++ b/packages/fpvectorial/src/fpvectorial.pas @@ -38,6 +38,11 @@ type st2DLine, st2DBezier, st3DLine, st3DBezier); + {@ + The coordinates in fpvectorial are given in millimiters and + the starting point is in the top-left corner of the document + and it grows to the bottom and to the right. + } TPathSegment = record SegmentType: TSegmentType; X, Y, Z: Double; // Z is ignored in 2D segments @@ -617,10 +622,21 @@ begin end; end; +{@@ + The default stream writer just uses WriteToStrings +} procedure TvCustomVectorialWriter.WriteToStream(AStream: TStream; AData: TvVectorialDocument); +var + lStringList: TStringList; begin - + lStringList := TStringList.Create; + try + WriteToStrings(lStringList, AData); + lStringList.SaveToStream(AStream); + finally + lStringList.Free; + end; end; procedure TvCustomVectorialWriter.WriteToStrings(AStrings: TStrings; diff --git a/packages/fpvectorial/src/fpvtocanvas.pas b/packages/fpvectorial/src/fpvtocanvas.pas index c7b5ff1a4a..a75c7dc8bf 100644 --- a/packages/fpvectorial/src/fpvtocanvas.pas +++ b/packages/fpvectorial/src/fpvtocanvas.pas @@ -10,12 +10,12 @@ uses fpvectorial; procedure DrawFPVectorialToCanvas(ASource: TvVectorialDocument; ADest: TFPCustomCanvas; - ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Integer = 1; AMulY: Integer = 1); + ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); implementation procedure DrawFPVectorialToCanvas(ASource: TvVectorialDocument; ADest: TFPCustomCanvas; - ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Integer = 1; AMulY: Integer = 1); + ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); var i, j, k: Integer; PosX, PosY: Integer; // Not modified by ADestX, etc @@ -47,8 +47,8 @@ begin PosX := Round(CurSegment.X); PosY := Round(CurSegment.Y); ADest.LineTo( - ADestX + AMulX * PosX, - ADestY + AMulY * PosY + Round(ADestX + AMulX * PosX), + Round(ADestY + AMulY * PosY) ); end; { To draw a bezier we need to divide the interval in parts and make @@ -66,8 +66,8 @@ begin CurX := Round(sqr(1 - t) * (1 - t) * PosX + 3 * t * sqr(1 - t) * CurSegment.X2 + 3 * t * t * (1 - t) * CurSegment.X3 + t * t * t * CurSegment.X); CurY := Round(sqr(1 - t) * (1 - t) * PosY + 3 * t * sqr(1 - t) * CurSegment.Y2 + 3 * t * t * (1 - t) * CurSegment.Y3 + t * t * t * CurSegment.Y); ADest.LineTo( - ADestX + AMulX * CurX, - ADestY + AMulY * CurY); + Round(ADestX + AMulX * CurX), + Round(ADestY + AMulY * CurY)); end; PosX := Round(CurSegment.X); PosY := Round(CurSegment.Y); diff --git a/packages/fpvectorial/src/pdfvrsemantico.pas b/packages/fpvectorial/src/pdfvrsemantico.pas index 421e0d6175..bf2c80edfe 100644 --- a/packages/fpvectorial/src/pdfvrsemantico.pas +++ b/packages/fpvectorial/src/pdfvrsemantico.pas @@ -8,11 +8,16 @@ uses Classes, SysUtils, pdfvrlexico, fpvectorial; type + + { AnSemantico } + AnSemantico = class public + FPointSeparator, FCommaSeparator: TFormatSettings; close_path_x: String; close_path_y: String; cm_a, cm_b, cm_c, cm_d, cm_e, cm_f: Real; // coordinate spaces constants + function StringToFloat(AStr: string): Double; function generate(c: Command; AData: TvVectorialDocument): String; function convert(x: String; y: String; Axis: Char): String; function startMachine(): String; @@ -22,6 +27,14 @@ type implementation +{ PDF doesn't seam very consistent when it comes to using commas or + points as decimal separator, so we just try both } +function AnSemantico.StringToFloat(AStr: string): Double; +begin + if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator) + else Result := StrToFloat(AStr, FCommaSeparator); +end; + function AnSemantico.generate(c: Command; AData: TvVectorialDocument): String; var enter_line : String; @@ -29,6 +42,7 @@ begin {$ifdef FPVECTORIALDEBUG} WriteLn(':> AnSemantico.generate'); {$endif} + enter_line:= LineEnding; //chr(13) + chr(10); // CR and LF if ((c.code = cc_H_CLOSE_PATH) or (c.code = cc_hS_CLOSE_AND_END_PATH)) then // command h or s @@ -69,7 +83,7 @@ begin // Correcao para programas de desenho que geram um novo inicio no // fim do desenho, terminamos qualquer desenho inacabado AData.EndPath(); - AData.StartPath(StrToFloat(c.cord_x), StrToFloat(c.cord_y)); + AData.StartPath(StringToFloat(c.cord_x), StringToFloat(c.cord_y)); close_path_x:=c.cord_x; close_path_y:=c.cord_y; @@ -81,7 +95,7 @@ begin {$endif} // Result:='G01' + ' ' + 'X' + c.cord_x + ' ' + 'Y' + c.cord_y; - AData.AddLineToPath(StrToFloat(c.cord_x), StrToFloat(c.cord_y)); + AData.AddLineToPath(StringToFloat(c.cord_x), StringToFloat(c.cord_y)); end; cc_h_CLOSE_PATH: // command h begin @@ -90,7 +104,7 @@ begin {$endif} //Result:='G01' + ' ' + 'X' + c.cord_x + ' ' + 'Y' + c.cord_y; - AData.AddLineToPath(StrToFloat(c.cord_x), StrToFloat(c.cord_y)); + AData.AddLineToPath(StringToFloat(c.cord_x), StringToFloat(c.cord_y)); end; cc_S_END_PATH: // command S begin @@ -108,7 +122,7 @@ begin //Result:='G01' + ' ' + 'X' + c.cord_x + ' ' + 'Y' + c.cord_y + enter_line // +'G01 Z0 // Sobe a cabeça de gravação' + enter_line; - AData.AddLineToPath(StrToFloat(c.cord_x), StrToFloat(c.cord_y)); + AData.AddLineToPath(StringToFloat(c.cord_x), StringToFloat(c.cord_y)); AData.EndPath(); end; cc_c_BEZIER_TO_X_Y_USING_X2_Y2_AND_X3_Y3: // command c @@ -120,9 +134,9 @@ begin // +'G01 Z0 // Sobe a cabeça de gravação' + enter_line; AData.AddBezierToPath( - StrToFloat(c.cord_x3), StrToFloat(c.cord_y3), - StrToFloat(c.cord_x2), StrToFloat(c.cord_y2), - StrToFloat(c.cord_x), StrToFloat(c.cord_y) + StringToFloat(c.cord_x3), StringToFloat(c.cord_y3), + StringToFloat(c.cord_x2), StringToFloat(c.cord_y2), + StringToFloat(c.cord_x), StringToFloat(c.cord_y) ); end; cc_CONCATENATE_MATRIX: // command cm @@ -131,12 +145,12 @@ begin WriteLn(':> AnSemantico.cc_CONCATENATE_MATRIX'); {$endif} - cm_a := StrToFloat(c.cord_x3); - cm_b := StrToFloat(c.cord_y3); - cm_c := StrToFloat(c.cord_x2); - cm_d := StrToFloat(c.cord_y2); - cm_e := StrToFloat(c.cord_x); - cm_f := StrToFloat(c.cord_y); + cm_a := StringToFloat(c.cord_x3); + cm_b := StringToFloat(c.cord_y3); + cm_c := StringToFloat(c.cord_x2); + cm_d := StringToFloat(c.cord_y2); + cm_e := StringToFloat(c.cord_x); + cm_f := StringToFloat(c.cord_y); end; cc_RESTORE_MATRIX: // command Q begin @@ -169,13 +183,13 @@ begin if (Axis = 'y') then begin // y' = b * x + d * y + f - Result:=FloatToStr((cm_b*StrToFloat(x)+cm_d*StrToFloat(y)+cm_f)*(25.40/72)); + Result:=FloatToStr((cm_b*StringToFloat(x)+cm_d*StringToFloat(y)+cm_f)*(25.40/72)); end else // Axis = 'x' begin // x' = a * x + c * y + e - Result:=FloatToStr((cm_a*StrToFloat(x)+cm_c*StrToFloat(y)+cm_e)*(25.40/72)); + Result:=FloatToStr((cm_a*StringToFloat(x)+cm_c*StringToFloat(y)+cm_e)*(25.40/72)); end; end; @@ -209,12 +223,21 @@ end; constructor AnSemantico.Create; begin inherited Create; + cm_a:=1; cm_b:=0; cm_c:=0; cm_d:=1; cm_e:=0; cm_f:=0; + + // Format seetings to convert a string to a float + FPointSeparator := DefaultFormatSettings; + FPointSeparator.DecimalSeparator := '.'; + FPointSeparator.ThousandSeparator := '#';// disable the thousand separator + FCommaSeparator := DefaultFormatSettings; + FCommaSeparator.DecimalSeparator := ','; + FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator end; end. diff --git a/packages/fpvectorial/src/svgvectorialwriter.pas b/packages/fpvectorial/src/svgvectorialwriter.pas index cafd26c782..782590afe6 100644 --- a/packages/fpvectorial/src/svgvectorialwriter.pas +++ b/packages/fpvectorial/src/svgvectorialwriter.pas @@ -20,19 +20,102 @@ type { TvSVGVectorialWriter } TvSVGVectorialWriter = class(TvCustomVectorialWriter) + private + FPointSeparator, FCommaSeparator: TFormatSettings; + procedure WriteDocumentSize(AStrings: TStrings; AData: TvVectorialDocument); + procedure WriteDocumentName(AStrings: TStrings; AData: TvVectorialDocument); + procedure WritePaths(AStrings: TStrings; AData: TvVectorialDocument); public { General reading methods } - procedure WriteToStream(AStream: TStream; AData: TvVectorialDocument); virtual; + procedure WriteToStrings(AStrings: TStrings; AData: TvVectorialDocument); override; end; implementation { TvSVGVectorialWriter } -procedure TvSVGVectorialWriter.WriteToStream(AStream: TStream; +procedure TvSVGVectorialWriter.WriteDocumentSize(AStrings: TStrings; AData: TvVectorialDocument); +begin + AStrings.Add(' width="744.09448819"'); + AStrings.Add(' height="1052.3622047"'); +end; + +procedure TvSVGVectorialWriter.WriteDocumentName(AStrings: TStrings; AData: TvVectorialDocument); +begin + AStrings.Add(' sodipodi:docname="New document 1">'); +end; + +{@@ + SVG Coordinate system measures things in millimiters. + The initial point is in the bottom-left corner of the document and it grows + to the top and to the right. + + SVG uses commas "," to separate the X,Y coordinates, so it always uses points + "." as decimal separators and uses no thousand separators +} +procedure TvSVGVectorialWriter.WritePaths(AStrings: TStrings; AData: TvVectorialDocument); +var + i, j: Integer; + PathStr: string; + lPath: TPath; + PtX, PtY: double; +begin + for i := 0 to AData.GetPathCount() - 1 do + begin + PathStr := 'm '; + lPath := AData.GetPath(i); + for j := 0 to lPath.Len - 1 do + begin + if lPath.Points[j].SegmentType <> st2DLine then Break; // unsupported line type + + PtX := lPath.Points[j].X; + PtY := lPath.Points[j].Y; + PtY := AData.Height - PtY; + PathStr := PathStr + FloatToStr(PtX, FPointSeparator) + ',' + + FloatToStr(PtY, FPointSeparator) + ' '; + end; + + AStrings.Add(' <path'); + AStrings.Add(' style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"'); + AStrings.Add(' d="' + PathStr + '"'); + AStrings.Add(' id="path' + IntToStr(i) + '" />'); + end; +end; + +procedure TvSVGVectorialWriter.WriteToStrings(AStrings: TStrings; AData: TvVectorialDocument); begin + // Format seetings to convert a string to a float + FPointSeparator := DefaultFormatSettings; + FPointSeparator.DecimalSeparator := '.'; + FPointSeparator.ThousandSeparator := '#';// disable the thousand separator + FCommaSeparator := DefaultFormatSettings; + FCommaSeparator.DecimalSeparator := ','; + FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator + // Headers + AStrings.Add('<?xml version="1.0" encoding="UTF-8" standalone="no"?>'); + AStrings.Add('<!-- Created with fpVectorial (http://wiki.lazarus.freepascal.org/fpvectorial) -->'); + AStrings.Add(''); + AStrings.Add('<svg'); + AStrings.Add(' xmlns:dc="http://purl.org/dc/elements/1.1/"'); + AStrings.Add(' xmlns:cc="http://creativecommons.org/ns#"'); + AStrings.Add(' xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"'); + AStrings.Add(' xmlns:svg="http://www.w3.org/2000/svg"'); + AStrings.Add(' xmlns="http://www.w3.org/2000/svg"'); + AStrings.Add(' xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"'); + WriteDocumentSize(AStrings, AData); + AStrings.Add(' id="svg2"'); + AStrings.Add(' version="1.1"'); + WriteDocumentName(AStrings, AData); + + // Now data + AStrings.Add(' <g id="layer1">'); + WritePaths(AStrings, AData); + AStrings.Add(' </g>'); + + // finalization + AStrings.Add('</svg>'); end; initialization