diff --git a/packages/fpvectorial/examples/fpvc_mainform.lfm b/packages/fpvectorial/examples/fpvc_mainform.lfm index 7e9613739d..2ed06cdea4 100644 --- a/packages/fpvectorial/examples/fpvc_mainform.lfm +++ b/packages/fpvectorial/examples/fpvc_mainform.lfm @@ -10,26 +10,28 @@ object formVectorialConverter: TformVectorialConverter LCLVersion = '0.9.29' object Label1: TLabel Left = 8 - Height = 14 - Top = 104 - Width = 123 + Height = 17 + Top = 112 + Width = 160 Caption = 'Location of the Input file:' ParentColor = False end object Label2: TLabel Left = 11 - Height = 96 + Height = 104 Top = 8 - Width = 224 + Width = 229 AutoSize = False Caption = 'This converter application use the fpvectorial library to convert between various different vectorial graphics formats. The type is detected from the extension and the supported types are: PDF (*.pdf), SVG (*.svg) and Corel Draw file (*.cdr).' + Font.Height = -12 ParentColor = False + ParentFont = False WordWrap = True end object editInput: TFileNameEdit Left = 8 - Height = 21 - Top = 120 + Height = 22 + Top = 128 Width = 192 DialogOptions = [] FilterIndex = 0 @@ -41,16 +43,16 @@ object formVectorialConverter: TformVectorialConverter end object Label3: TLabel Left = 8 - Height = 14 - Top = 144 - Width = 132 + Height = 17 + Top = 152 + Width = 173 Caption = 'Full path of the Output file:' ParentColor = False end object editOutput: TFileNameEdit Left = 8 - Height = 21 - Top = 160 + Height = 22 + Top = 168 Width = 192 DialogOptions = [] FilterIndex = 0 diff --git a/packages/fpvectorial/examples/fpvectorialconverter.lpi b/packages/fpvectorial/examples/fpvectorialconverter.lpi index 2d8003a7b9..21b7cae3c8 100644 --- a/packages/fpvectorial/examples/fpvectorialconverter.lpi +++ b/packages/fpvectorial/examples/fpvectorialconverter.lpi @@ -8,6 +8,7 @@ + <UseXPManifest Value="True"/> <Icon Value="0"/> @@ -18,6 +19,9 @@ <VersionInfo> <StringTable ProductVersion=""/> </VersionInfo> + <BuildModes Count="1"> + <Item1 Name="default" Default="True"/> + </BuildModes> <PublishOptions> <Version Value="2"/> <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/> @@ -55,7 +59,7 @@ <Filename Value="fpvectorialconverter"/> </Target> <SearchPaths> - <IncludeFiles Value="$(ProjOutDir)\"/> + <IncludeFiles Value="$(ProjOutDir)"/> <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> </SearchPaths> <Linking> diff --git a/packages/fpvectorial/src/fpvectorial.pas b/packages/fpvectorial/src/fpvectorial.pas index c0b49f39ad..9fd35acc09 100644 --- a/packages/fpvectorial/src/fpvectorial.pas +++ b/packages/fpvectorial/src/fpvectorial.pas @@ -48,37 +48,79 @@ type the starting point is in the bottom-left corner of the document. The X grows to the right and the Y grows to the top. } - TPathSegment = record + { TPathSegment } + + TPathSegment = class + public SegmentType: TSegmentType; - X, Y, Z: Double; // Z is ignored in 2D segments - X2, Y2, Z2: Double; // Z is ignored in 2D segments - X3, Y3, Z3: Double; // Z is ignored in 2D segments + // Fields for linking the list + Previous: TPathSegment; + Next: TPathSegment; end; - TPath = record + {@@ + In a 2D segment, the X and Y coordinates represent usually the + final point of the segment, being that it starts where the previous + segment ends. The exception is for the first segment of all, which simply + holds the starting point for the drawing and should always be of the type + stMoveTo. + } + T2DSegment = class(TPathSegment) + public + X, Y: Double; + end; + + {@@ + In Bezier segments, we remain using the X and Y coordinates for the ending point. + The starting point is where the previous segment ended, so that the intermediary + bezier control points are [X2, Y2] and [X3, Y3]. + } + T2DBezierSegment = class(T2DSegment) + public + X2, Y2: Double; + X3, Y3: Double; + end; + + T3DSegment = class(TPathSegment) + public + {@@ + Coordinates of the end of the segment. + For the first segment, this is the starting point. + } + X, Y, Z: Double; + end; + + T3DBezierSegment = class(T3DSegment) + public + X2, Y2, Z2: Double; + X3, Y3, Z3: Double; + end; + + TPath = class Len: Integer; - // ToDo: make the array dynamic - Points: array[0..255] of TPathSegment; + Points: TPathSegment; // Beginning of the double-linked list + PointsEnd: TPathSegment; // End of the double-linked list + CurPoint: TPathSegment; // Used in PrepareForSequentialReading and Next + procedure Assign(APath: TPath); + function Count(): TPathSegment; + procedure PrepareForSequentialReading; + function Next(): TPathSegment; end; - PPath = ^TPath; - {@@ TvText represents a text in memory. At the moment fonts are unsupported, only simple texts up to 255 chars are supported. } - - TvText = record + TvText = class + public X, Y, Z: Double; // Z is ignored in 2D formats FontSize: integer; FontName: utf8string; Value: utf8string; end; - PText = ^TvText; - type TvCustomVectorialWriter = class; @@ -95,6 +137,8 @@ type procedure RemoveCallback(data, arg: pointer); function CreateVectorialWriter(AFormat: TvVectorialFormat): TvCustomVectorialWriter; function CreateVectorialReader(AFormat: TvVectorialFormat): TvCustomVectorialReader; + procedure ClearTmpPath(); + procedure AppendSegmentToTmpPath(ASegment: TPathSegment); public Name: string; Width, Height: Double; // in millimeters @@ -185,6 +229,9 @@ procedure RegisterVectorialWriter( implementation +const + Str_Error_Nil_Path = ' The program attempted to add a segment before creating a path'; + {@@ Registers a new reader for a format } @@ -276,7 +323,11 @@ end; } procedure TvVectorialDocument.RemoveCallback(data, arg: pointer); begin - if data <> nil then FreeMem(data); +{ if data <> nil then + begin + ldata := PObject(data); + ldata^.Free; + end;} end; {@@ @@ -288,6 +339,7 @@ begin FPaths := TFPList.Create; FTexts := TFPList.Create; + FTmpPath := TPath.Create; end; {@@ @@ -308,28 +360,27 @@ end; } procedure TvVectorialDocument.RemoveAllPaths; begin - FPaths.ForEachCall(RemoveCallback, nil); +// FPaths.ForEachCall(RemoveCallback, nil); FPaths.Clear; end; procedure TvVectorialDocument.RemoveAllTexts; begin - FTexts.ForEachCall(RemoveCallback, nil); +// FTexts.ForEachCall(RemoveCallback, nil); FTexts.Clear; end; procedure TvVectorialDocument.AddPath(APath: TPath); var - Path: PPath; + lPath: TPath; Len: Integer; begin - Len := SizeOf(TPath); + lPath := TPath.Create; + lPath.Assign(APath); + FPaths.Add(Pointer(lPath)); //WriteLn(':>TvVectorialDocument.AddPath 1 Len = ', Len); - Path := GetMem(Len); //WriteLn(':>TvVectorialDocument.AddPath 2'); - Move(APath, Path^, Len); //WriteLn(':>TvVectorialDocument.AddPath 3'); - FPaths.Add(Path); //WriteLn(':>TvVectorialDocument.AddPath 4'); end; @@ -341,11 +392,19 @@ end; @see StartPath, AddPointToPath } procedure TvVectorialDocument.StartPath(AX, AY: Double); +var + segment: T2DSegment; begin + ClearTmpPath(); + FTmpPath.Len := 1; - FTmpPath.Points[0].SegmentType := stMoveTo; - FTmpPath.Points[0].X := AX; - FTmpPath.Points[0].Y := AY; + segment := T2DSegment.Create; + segment.SegmentType := stMoveTo; + segment.X := AX; + segment.Y := AY; + + FTmpPath.Points := segment; + FTmpPath.PointsEnd := segment; end; {@@ @@ -360,60 +419,69 @@ end; } procedure TvVectorialDocument.AddLineToPath(AX, AY: Double); var - L: Integer; + segment: T2DSegment; begin - L := FTmpPath.Len; - Inc(FTmpPath.Len); - FTmpPath.Points[L].SegmentType := st2DLine; - FTmpPath.Points[L].X := AX; - FTmpPath.Points[L].Y := AY; + segment := T2DSegment.Create; + segment.SegmentType := st2DLine; + segment.X := AX; + segment.Y := AY; + + AppendSegmentToTmpPath(segment); end; procedure TvVectorialDocument.AddLineToPath(AX, AY, AZ: Double); var - L: Integer; + segment: T3DSegment; begin - L := FTmPPath.Len; - Inc(FTmPPath.Len); - FTmPPath.Points[L].SegmentType := st3DLine; - FTmPPath.Points[L].X := AX; - FTmPPath.Points[L].Y := AY; - FTmPPath.Points[L].Z := AZ; + segment := T3DSegment.Create; + segment.SegmentType := st3DLine; + segment.X := AX; + segment.Y := AY; + segment.Z := AZ; + + AppendSegmentToTmpPath(segment); end; +{@@ + Adds a bezier element to the path. It starts where the previous element ended + and it goes throw the control points [AX1, AY1] and [AX2, AY2] and ends + in [AX3, AY3]. +} procedure TvVectorialDocument.AddBezierToPath(AX1, AY1, AX2, AY2, AX3, AY3: Double); var - L: Integer; + segment: T2DBezierSegment; begin - L := FTmPPath.Len; - Inc(FTmPPath.Len); - FTmPPath.Points[L].SegmentType := st2DBezier; - FTmPPath.Points[L].X := AX3; - FTmPPath.Points[L].Y := AY3; - FTmPPath.Points[L].X2 := AX1; - FTmPPath.Points[L].Y2 := AY1; - FTmPPath.Points[L].X3 := AX2; - FTmPPath.Points[L].Y3 := AY2; + segment := T2DBezierSegment.Create; + segment.SegmentType := st2DBezier; + segment.X := AX3; + segment.Y := AY3; + segment.X2 := AX1; + segment.Y2 := AY1; + segment.X3 := AX2; + segment.Y3 := AY2; + + AppendSegmentToTmpPath(segment); end; procedure TvVectorialDocument.AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, AX3, AY3, AZ3: Double); var - L: Integer; + segment: T3DBezierSegment; begin - L := FTmPPath.Len; - Inc(FTmPPath.Len); - FTmPPath.Points[L].SegmentType := st3DBezier; - FTmPPath.Points[L].X := AX3; - FTmPPath.Points[L].Y := AY3; - FTmPPath.Points[L].Z := AZ3; - FTmPPath.Points[L].X2 := AX1; - FTmPPath.Points[L].Y2 := AY1; - FTmPPath.Points[L].Z2 := AZ1; - FTmPPath.Points[L].X3 := AX2; - FTmPPath.Points[L].Y3 := AY2; - FTmPPath.Points[L].Z3 := AZ2; + segment := T3DBezierSegment.Create; + segment.SegmentType := st3DBezier; + segment.X := AX3; + segment.Y := AY3; + segment.Z := AZ3; + segment.X2 := AX1; + segment.Y2 := AY1; + segment.Z2 := AZ1; + segment.X3 := AX2; + segment.Y3 := AY2; + segment.Z3 := AZ2; + + AppendSegmentToTmpPath(segment); end; {@@ @@ -430,15 +498,14 @@ procedure TvVectorialDocument.EndPath(); begin if FTmPPath.Len = 0 then Exit; AddPath(FTmPPath); - FTmPPath.Len := 0; + ClearTmpPath(); end; procedure TvVectorialDocument.AddText(AX, AY, AZ: Double; FontName: string; FontSize: integer; AText: utf8string); var - lText: PText; + lText: TvText; begin - lText := GetMem(SizeOf(TvText)); - FillChar(lText^, SizeOf(TvText), 0); + lText := TvText.Create; lText.Value := AText; lText.X := AX; lText.Y := AY; @@ -495,6 +562,40 @@ begin if Result = nil then raise Exception.Create('Unsuported vector graphics format.'); end; +procedure TvVectorialDocument.ClearTmpPath(); +var + segment, oldsegment: TPathSegment; +begin +// segment := FTmpPath.Points; +// Don't free segments, because they are used when the path is added +// while segment <> nil do +// begin +// oldsegment := segment; +// segment := segment^.Next; +// oldsegment^.Free; +// end; + + FTmpPath.Points := nil; + FTmpPath.PointsEnd := nil; + FTmpPath.Len := 0; +end; + +procedure TvVectorialDocument.AppendSegmentToTmpPath(ASegment: TPathSegment); +var + L: Integer; +begin + if FTmpPath.PointsEnd = nil then + Exception.Create('[TvVectorialDocument.AppendSegmentToTmpPath]' + Str_Error_Nil_Path); + + L := FTmpPath.Len; + Inc(FTmpPath.Len); + + // Adds the element to the end of the list + FTmpPath.PointsEnd.Next := ASegment; + ASegment.Previous := FTmpPath.PointsEnd; + FTmpPath.PointsEnd := ASegment; +end; + {@@ Writes the document to a file. @@ -624,7 +725,7 @@ begin if FPaths.Items[ANum] = nil then raise Exception.Create('TvVectorialDocument.GetPath: Invalid Path number'); - Result := PPath(FPaths.Items[ANum])^; + Result := TPath(FPaths.Items[ANum]); end; function TvVectorialDocument.GetPathCount: Integer; @@ -638,7 +739,7 @@ begin if FTexts.Items[ANum] = nil then raise Exception.Create('TvVectorialDocument.GetText: Invalid Text number'); - Result := PText(FTexts.Items[ANum])^; + Result := TvText(FTexts.Items[ANum]); end; function TvVectorialDocument.GetTextCount: Integer; @@ -751,6 +852,34 @@ begin end; +{ TPath } + +procedure TPath.Assign(APath: TPath); +begin + Len := APath.Len; + Points := APath.Points; + PointsEnd := APath.PointsEnd; + CurPoint := APath.CurPoint; +end; + +function TPath.Count(): TPathSegment; +begin + +end; + +procedure TPath.PrepareForSequentialReading; +begin + CurPoint := nil; +end; + +function TPath.Next(): TPathSegment; +begin + if CurPoint = nil then Result := Points + else Result := CurPoint.Next; + + CurPoint := Result; +end; + finalization SetLength(GvVectorialFormats, 0); diff --git a/packages/fpvectorial/src/fpvtocanvas.pas b/packages/fpvectorial/src/fpvtocanvas.pas index 396b6de7c5..d5977019a1 100644 --- a/packages/fpvectorial/src/fpvtocanvas.pas +++ b/packages/fpvectorial/src/fpvtocanvas.pas @@ -34,6 +34,8 @@ var i, j, k: Integer; PosX, PosY: Integer; // Not modified by ADestX, etc CurSegment: TPathSegment; + Cur2DSegment: T2DSegment absolute CurSegment; + Cur2DBSegment: T2DBezierSegment absolute CurSegment; // For bezier CurX, CurY: Integer; // Not modified by ADestX, etc CurveLength: Integer; @@ -51,15 +53,18 @@ begin for i := 0 to ASource.PathCount - 1 do begin //WriteLn('i = ', i); - for j := 0 to Length(ASource.Paths[i].Points) - 1 do + ASource.Paths[i].PrepareForSequentialReading; + + for j := 0 to ASource.Paths[i].Len - 1 do begin //WriteLn('j = ', j); - CurSegment := ASource.Paths[i].Points[j]; + CurSegment := TPathSegment(ASource.Paths[i].Next()); + case CurSegment.SegmentType of st2DLine, st3DLine: begin - PosX := Round(CurSegment.X); - PosY := Round(CurSegment.Y); + PosX := Round(Cur2DSegment.X); + PosY := Round(Cur2DSegment.Y); ADest.LineTo( Round(ADestX + AMulX * PosX), Round(ADestY + AMulY * PosY) @@ -70,21 +75,21 @@ begin st2DBezier, st3DBezier: begin CurveLength := - Round(sqrt(sqr(CurSegment.X3 - PosX) + sqr(CurSegment.Y3 - PosY))) + - Round(sqrt(sqr(CurSegment.X2 - CurSegment.X3) + sqr(CurSegment.Y2 - CurSegment.Y3))) + - Round(sqrt(sqr(CurSegment.X - CurSegment.X3) + sqr(CurSegment.Y - CurSegment.Y3))); + Round(sqrt(sqr(Cur2DBSegment.X3 - PosX) + sqr(Cur2DBSegment.Y3 - PosY))) + + Round(sqrt(sqr(Cur2DBSegment.X2 - Cur2DBSegment.X3) + sqr(Cur2DBSegment.Y2 - Cur2DBSegment.Y3))) + + Round(sqrt(sqr(Cur2DBSegment.X - Cur2DBSegment.X3) + sqr(Cur2DBSegment.Y - Cur2DBSegment.Y3))); for k := 1 to CurveLength do begin t := k / CurveLength; - 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); + 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); ADest.LineTo( Round(ADestX + AMulX * CurX), Round(ADestY + AMulY * CurY)); end; - PosX := Round(CurSegment.X); - PosY := Round(CurSegment.Y); + PosX := Round(Cur2DBSegment.X); + PosY := Round(Cur2DBSegment.Y); end; end; end; diff --git a/packages/fpvectorial/src/svgvectorialwriter.pas b/packages/fpvectorial/src/svgvectorialwriter.pas index 765dc78efd..a1ae21495d 100644 --- a/packages/fpvectorial/src/svgvectorialwriter.pas +++ b/packages/fpvectorial/src/svgvectorialwriter.pas @@ -81,6 +81,9 @@ var lPath: TPath; PtX, PtY, OldPtX, OldPtY: double; BezierCP1X, BezierCP1Y, BezierCP2X, BezierCP2Y: double; + segment: TPathSegment; + l2DSegment: T2DSegment absolute segment; + l2DBSegment: T2DBezierSegment absolute segment; begin for i := 0 to AData.GetPathCount() - 1 do begin @@ -89,38 +92,42 @@ begin PathStr := ''; lPath := AData.GetPath(i); + lPath.PrepareForSequentialReading; + for j := 0 to lPath.Len - 1 do begin - if (lPath.Points[j].SegmentType <> st2DLine) - and (lPath.Points[j].SegmentType <> stMoveTo) - and (lPath.Points[j].SegmentType <> st2DBezier) + segment := TPathSegment(lPath.Next()); + + if (segment.SegmentType <> st2DLine) + and (segment.SegmentType <> stMoveTo) + and (segment.SegmentType <> st2DBezier) then Break; // unsupported line type // Coordinate conversion from fpvectorial to SVG ConvertFPVCoordinatesToSVGCoordinates( - AData, lPath.Points[j].X, lPath.Points[j].Y, PtX, PtY); + AData, l2DSegment.X, l2DSegment.Y, PtX, PtY); PtX := PtX - OldPtX; PtY := PtY - OldPtY; - if (lPath.Points[j].SegmentType = stMoveTo) then + if (segment.SegmentType = stMoveTo) then begin PathStr := PathStr + 'm ' + FloatToStr(PtX, FPointSeparator) + ',' + FloatToStr(PtY, FPointSeparator) + ' '; end - else if (lPath.Points[j].SegmentType = st2DLine) then + else if (segment.SegmentType = st2DLine) then begin PathStr := PathStr + 'l ' + FloatToStr(PtX, FPointSeparator) + ',' + FloatToStr(PtY, FPointSeparator) + ' '; end - else if (lPath.Points[j].SegmentType = st2DBezier) then + else if (segment.SegmentType = st2DBezier) then begin // Converts all coordinates to absolute values ConvertFPVCoordinatesToSVGCoordinates( - AData, lPath.Points[j].X2, lPath.Points[j].Y2, BezierCP1X, BezierCP1Y); + AData, l2DBSegment.X2, l2DBSegment.Y2, BezierCP1X, BezierCP1Y); ConvertFPVCoordinatesToSVGCoordinates( - AData, lPath.Points[j].X3, lPath.Points[j].Y3, BezierCP2X, BezierCP2Y); + AData, l2DBSegment.X3, l2DBSegment.Y3, BezierCP2X, BezierCP2Y); // Transforms them into values relative to the initial point BezierCP1X := BezierCP1X - OldPtX;