diff --git a/packages/fpvectorial/examples/fpvwritetest.lpi b/packages/fpvectorial/examples/fpvwritetest.lpi index 495db7bccf..0333775fac 100644 --- a/packages/fpvectorial/examples/fpvwritetest.lpi +++ b/packages/fpvectorial/examples/fpvwritetest.lpi @@ -13,7 +13,6 @@ <ResourceType Value="res"/> <UseXPManifest Value="True"/> - <Icon Value="0"/> </General> <i18n> <EnableI18N LFM="False"/> @@ -21,6 +20,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)"/> @@ -29,6 +31,7 @@ <RunParams> <local> <FormatVersion Value="1"/> + <LaunchingApplication PathPlusParams="\usr\bin\xterm -T 'Lazarus Run Output' -e $(LazarusDir)\tools\runwait.sh $(TargetCmdLine)"/> </local> </RunParams> <Units Count="1"> @@ -40,13 +43,13 @@ </Units> </ProjectOptions> <CompilerOptions> - <Version Value="9"/> + <Version Value="10"/> <PathDelim Value="\"/> <Target> <Filename Value="fpvwritetest"/> </Target> <SearchPaths> - <IncludeFiles Value="$(ProjOutDir)\"/> + <IncludeFiles Value="$(ProjOutDir)"/> <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> </SearchPaths> <Other> diff --git a/packages/fpvectorial/examples/fpvwritetest.pas b/packages/fpvectorial/examples/fpvwritetest.pas index 0244cde71a..393fde3694 100644 --- a/packages/fpvectorial/examples/fpvwritetest.pas +++ b/packages/fpvectorial/examples/fpvwritetest.pas @@ -22,7 +22,7 @@ program fpvwritetest; {$mode objfpc}{$H+} uses - fpvectorial, svgvectorialwriter; + fpvectorial, svgvectorialwriter, fpvutils; const cFormat = vfSVG; @@ -134,6 +134,51 @@ begin Vec.AddText(20, 20, 0, '20, 20 Mówić, cześć, Włosku, Parabéns.'); Vec.AddText(30, 30, 0, '30, 30 森林,是一个高密'); Vec.WriteToFile('multi_test_1' + cExtension, cFormat); + + // pen_test_1 Tests the properties of the Pen + Vec.Clear; + Vec.StartPath(0, 20); + Vec.AddLineToPath(30, 30); + Vec.SetPenWidth(10); + Vec.EndPath(); + Vec.StartPath(0, 0); + Vec.AddLineToPath(100, 0); + Vec.AddLineToPath(100, 100); + Vec.AddLineToPath(0, 100); + Vec.AddLineToPath(0, 0); + Vec.SetPenWidth(10); + Vec.EndPath(); + Vec.StartPath(0, 0); + Vec.AddLineToPath(10, 10); + Vec.AddBezierToPath(10, 20, 20, 20, 20, 10); + Vec.AddLineToPath(30, 0); + Vec.SetPenWidth(10); + Vec.EndPath(); + Vec.WriteToFile('pen_test_1' + cExtension, cFormat); + + // pen_test_2 Tests the properties of the Pen + Vec.Clear; + Vec.StartPath(0, 20); + Vec.AddLineToPath(30, 30); + Vec.SetPenWidth(10); + Vec.SetPenColor(RGBToVColor(255, 0, 0)); + Vec.EndPath(); + Vec.StartPath(0, 0); + Vec.AddLineToPath(100, 0); + Vec.AddLineToPath(100, 100); + Vec.AddLineToPath(0, 100); + Vec.AddLineToPath(0, 0); + Vec.SetPenWidth(10); + Vec.SetPenColor(RGBToVColor(0, 255, 0)); + Vec.EndPath(); + Vec.StartPath(0, 0); + Vec.AddLineToPath(10, 10); + Vec.AddBezierToPath(10, 20, 20, 20, 20, 10); + Vec.AddLineToPath(30, 0); + Vec.SetPenWidth(10); + Vec.SetPenColor(RGBToVColor(0, 0, 255)); + Vec.EndPath(); + Vec.WriteToFile('pen_test_2' + cExtension, cFormat); finally Vec.Free; end; diff --git a/packages/fpvectorial/src/fpvectorial.pas b/packages/fpvectorial/src/fpvectorial.pas index a505c9bab3..d61b28e727 100644 --- a/packages/fpvectorial/src/fpvectorial.pas +++ b/packages/fpvectorial/src/fpvectorial.pas @@ -45,6 +45,17 @@ type Red, Green, Blue, Alpha: Byte; end; + TvPen = record + Color: TvColor; + Style: TFPPenStyle; + Width: Integer; + end; + + TvBrush = record + Color: TvColor; + Style: TFPBrushStyle; + end; + const FPValphaTransparent = $00; FPValphaOpaque = $FF; @@ -59,7 +70,7 @@ type P3DPoint = ^T3DPoint; TSegmentType = ( - st2DLine, st2DBezier, + st2DLine, st2DLineWithPen, st2DBezier, st3DLine, st3DBezier, stMoveTo); {@@ @@ -75,10 +86,6 @@ type // Fields for linking the list Previous: TPathSegment; Next: TPathSegment; - // Data fields - PenColor: TvColor; - PenStyle: TFPPenStyle; - PenWidth: Integer; end; {@@ @@ -93,6 +100,11 @@ type X, Y: Double; end; + T2DSegmentWithPen = class(T2DSegment) + public + Pen: TvPen; + 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 @@ -124,6 +136,13 @@ type Points: TPathSegment; // Beginning of the double-linked list PointsEnd: TPathSegment; // End of the double-linked list CurPoint: TPathSegment; // Used in PrepareForSequentialReading and Next + {@@ The global Pen for the entire path. This Pen might be overriden by + individual elements of the polyline. } + Pen: TvPen; + {@@ Sets a Brush to paint the inner area inside the path. + There is no inner area if Brush.Style = bsClear, which is the default. } + Brush: TvBrush; + constructor Create(); procedure Assign(APath: TPath); function Count(): TPathSegment; procedure PrepareForSequentialReading; @@ -139,23 +158,18 @@ type TvText = class public X, Y, Z: Double; // Z is ignored in 2D formats + Value: utf8string; + FontColor: TvColor; FontSize: integer; FontName: utf8string; - Value: utf8string; - Color: TvColor; end; {@@ } TvEntity = class public - // Pen - PenColor: TvColor; - PenStyle: TFPPenStyle; - PenWidth: Integer; - // Brush - BrushStyle: TFPBrushStyle; - BrushColor: TvColor; + Pen: TvPen; + Brush: TvBrush; end; {@@ @@ -253,6 +267,11 @@ type procedure AddLineToPath(AX, AY, AZ: Double); overload; 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: TvColor); + procedure SetBrushStyle(AStyle: TFPBrushStyle); + procedure SetPenColor(AColor: TvColor); + procedure SetPenStyle(AStyle: TFPPenStyle); + procedure SetPenWidth(AWidth: Integer); procedure EndPath(); procedure AddText(AX, AY, AZ: Double; FontName: string; FontSize: integer; AText: utf8string); overload; procedure AddText(AX, AY, AZ: Double; AStr: utf8string); overload; @@ -550,20 +569,19 @@ begin segment.SegmentType := st2DLine; segment.X := AX; segment.Y := AY; - segment.PenColor := clvBlack; AppendSegmentToTmpPath(segment); end; procedure TvVectorialDocument.AddLineToPath(AX, AY: Double; AColor: TvColor); var - segment: T2DSegment; + segment: T2DSegmentWithPen; begin - segment := T2DSegment.Create; - segment.SegmentType := st2DLine; + segment := T2DSegmentWithPen.Create; + segment.SegmentType := st2DLineWithPen; segment.X := AX; segment.Y := AY; - segment.PenColor := AColor; + segment.Pen.Color := AColor; AppendSegmentToTmpPath(segment); end; @@ -623,6 +641,31 @@ begin AppendSegmentToTmpPath(segment); end; +procedure TvVectorialDocument.SetBrushColor(AColor: TvColor); +begin + FTmPPath.Brush.Color := AColor; +end; + +procedure TvVectorialDocument.SetBrushStyle(AStyle: TFPBrushStyle); +begin + FTmPPath.Brush.Style := AStyle; +end; + +procedure TvVectorialDocument.SetPenColor(AColor: TvColor); +begin + FTmPPath.Pen.Color := AColor; +end; + +procedure TvVectorialDocument.SetPenStyle(AStyle: TFPPenStyle); +begin + FTmPPath.Pen.Style := AStyle; +end; + +procedure TvVectorialDocument.SetPenWidth(AWidth: Integer); +begin + FTmPPath.Pen.Width := AWidth; +end; + {@@ Finishes writing a Path, which was created in multiple steps using StartPath and AddPointToPath, @@ -683,7 +726,7 @@ begin lCircularArc.Radius := ARadius; lCircularArc.StartAngle := AStartAngle; lCircularArc.EndAngle := AEndAngle; - lCircularArc.PenColor := AColor; + lCircularArc.Pen.Color := AColor; FEntities.Add(lCircularArc); end; @@ -728,12 +771,13 @@ begin for i := 0 to Length(GvVectorialFormats) - 1 do if GvVectorialFormats[i].Format = AFormat then begin - Result := GvVectorialFormats[i].WriterClass.Create; + if GvVectorialFormats[i].WriterClass <> nil then + Result := GvVectorialFormats[i].WriterClass.Create; Break; end; - if Result = nil then raise Exception.Create('Unsuported vector graphics format.'); + if Result = nil then raise Exception.Create('Unsupported vector graphics format.'); end; {@@ @@ -749,12 +793,13 @@ begin for i := 0 to Length(GvVectorialFormats) - 1 do if GvVectorialFormats[i].Format = AFormat then begin - Result := GvVectorialFormats[i].ReaderClass.Create; + if GvVectorialFormats[i].ReaderClass <> nil then + Result := GvVectorialFormats[i].ReaderClass.Create; Break; end; - if Result = nil then raise Exception.Create('Unsuported vector graphics format.'); + if Result = nil then raise Exception.Create('Unsupported vector graphics format.'); end; procedure TvVectorialDocument.ClearTmpPath(); @@ -1073,12 +1118,20 @@ end; { TPath } +constructor TPath.Create(); +begin + Brush.Style := bsClear; + inherited Create(); +end; + procedure TPath.Assign(APath: TPath); begin Len := APath.Len; Points := APath.Points; PointsEnd := APath.PointsEnd; CurPoint := APath.CurPoint; + Pen := APath.Pen; + Brush := APath.Brush; end; function TPath.Count(): TPathSegment; diff --git a/packages/fpvectorial/src/fpvtocanvas.pas b/packages/fpvectorial/src/fpvtocanvas.pas index 6c21fb7524..16c3a5126b 100644 --- a/packages/fpvectorial/src/fpvtocanvas.pas +++ b/packages/fpvectorial/src/fpvtocanvas.pas @@ -4,7 +4,7 @@ unit fpvtocanvas; interface -{.$define USE_LCL_CANVAS} +{$define USE_LCL_CANVAS} uses Classes, SysUtils, Math, @@ -115,7 +115,7 @@ end; DrawFPVectorialToCanvas(ASource, ADest, 0, ASource.Height, 1.0, -1.0); } -{$define FPVECTORIAL_TOCANVAS_DEBUG} +{.$define FPVECTORIAL_TOCANVAS_DEBUG} procedure DrawFPVectorialToCanvas(ASource: TvVectorialDocument; {$ifdef USE_LCL_CANVAS}ADest: TCanvas;{$else}ADest: TFPCustomCanvas;{$endif} ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); @@ -187,15 +187,22 @@ begin Write(Format(' M%d,%d', [CoordToCanvasX(Cur2DSegment.X), CoordToCanvasY(Cur2DSegment.Y)])); {$endif} end; - st2DLine, st3DLine: + st2DLineWithPen: begin - {$ifdef USE_LCL_CANVAS}ADest.Pen.Color := VColorToTColor(Cur2DSegment.PenColor);{$endif} + {$ifdef USE_LCL_CANVAS}ADest.Pen.Color := VColorToTColor(T2DSegmentWithPen(Cur2DSegment).Pen.Color);{$endif} ADest.LineTo(CoordToCanvasX(Cur2DSegment.X), CoordToCanvasY(Cur2DSegment.Y)); {$ifdef USE_LCL_CANVAS}ADest.Pen.Color := clBlack;{$endif} {$ifdef FPVECTORIAL_TOCANVAS_DEBUG} Write(Format(' L%d,%d', [CoordToCanvasX(Cur2DSegment.X), CoordToCanvasY(Cur2DSegment.Y)])); {$endif} end; + st2DLine, st3DLine: + begin + ADest.LineTo(CoordToCanvasX(Cur2DSegment.X), CoordToCanvasY(Cur2DSegment.Y)); + {$ifdef FPVECTORIAL_TOCANVAS_DEBUG} + Write(Format(' L%d,%d', [CoordToCanvasX(Cur2DSegment.X), CoordToCanvasY(Cur2DSegment.Y)])); + {$endif} + end; { To draw a bezier we need to divide the interval in parts and make lines between this parts } st2DBezier, st3DBezier: @@ -321,7 +328,7 @@ begin 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.Color := {$ifdef USE_LCL_CANVAS}VColorToTColor(CurArc.PenColor);{$else}VColorToFPColor(CurArc.PenColor);{$endif} + ADest.Pen.Color := {$ifdef USE_LCL_CANVAS}VColorToTColor(CurArc.Pen.Color);{$else}VColorToFPColor(CurArc.Pen.Color);{$endif} ADest.Arc( BoundsLeft, BoundsTop, BoundsRight, BoundsBottom, IntStartAngle, IntAngleLength diff --git a/packages/fpvectorial/src/svgvectorialwriter.pas b/packages/fpvectorial/src/svgvectorialwriter.pas index a1ae21495d..65a42f5f8d 100644 --- a/packages/fpvectorial/src/svgvectorialwriter.pas +++ b/packages/fpvectorial/src/svgvectorialwriter.pas @@ -13,7 +13,7 @@ unit svgvectorialwriter; interface uses - Classes, SysUtils, math, fpvectorial; + Classes, SysUtils, math, fpvectorial, fpvutils; type { TvSVGVectorialWriter } @@ -24,6 +24,7 @@ type procedure WriteDocumentSize(AStrings: TStrings; AData: TvVectorialDocument); procedure WriteDocumentName(AStrings: TStrings; AData: TvVectorialDocument); procedure WritePaths(AStrings: TStrings; AData: TvVectorialDocument); + procedure WritePath(AIndex: Integer; APath: TPath; AStrings: TStrings; AData: TvVectorialDocument); procedure WriteTexts(AStrings: TStrings; AData: TvVectorialDocument); procedure ConvertFPVCoordinatesToSVGCoordinates( const AData: TvVectorialDocument; @@ -60,6 +61,19 @@ begin AStrings.Add(' sodipodi:docname="New document 1">'); end; +procedure TvSVGVectorialWriter.WritePaths(AStrings: TStrings; AData: TvVectorialDocument); +var + i: Integer; + lPath: TPath; +begin + for i := 0 to AData.GetPathCount() - 1 do + begin + lPath := AData.GetPath(i); + lPath.PrepareForSequentialReading; + WritePath(i ,lPath, AStrings, AData); + end; +end; + {@@ SVG Coordinate system measures things only in pixels, so that we have to hardcode a DPI value for the screen, which is usually 72. @@ -74,90 +88,98 @@ end; 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); +procedure TvSVGVectorialWriter.WritePath(AIndex: Integer; APath: TPath; AStrings: TStrings; + AData: TvVectorialDocument); var - i, j: Integer; + j: Integer; PathStr: string; - lPath: TPath; PtX, PtY, OldPtX, OldPtY: double; BezierCP1X, BezierCP1Y, BezierCP2X, BezierCP2Y: double; segment: TPathSegment; l2DSegment: T2DSegment absolute segment; l2DBSegment: T2DBezierSegment absolute segment; + // Pen properties + lPenWidth: Integer; + lPenColor: string; begin - for i := 0 to AData.GetPathCount() - 1 do + OldPtX := 0; + OldPtY := 0; + PathStr := ''; + + APath.PrepareForSequentialReading(); + + for j := 0 to APath.Len - 1 do begin - OldPtX := 0; - OldPtY := 0; + segment := TPathSegment(APath.Next()); - PathStr := ''; - lPath := AData.GetPath(i); - lPath.PrepareForSequentialReading; + if (segment.SegmentType <> st2DLine) + and (segment.SegmentType <> stMoveTo) + and (segment.SegmentType <> st2DBezier) + then Break; // unsupported line type - for j := 0 to lPath.Len - 1 do + // Coordinate conversion from fpvectorial to SVG + ConvertFPVCoordinatesToSVGCoordinates( + AData, l2DSegment.X, l2DSegment.Y, PtX, PtY); + PtX := PtX - OldPtX; + PtY := PtY - OldPtY; + + if (segment.SegmentType = stMoveTo) then begin - 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 + PathStr := PathStr + 'm ' + + FloatToStr(PtX, FPointSeparator) + ',' + + FloatToStr(PtY, FPointSeparator) + ' '; + end + else if (segment.SegmentType = st2DLine) then + begin + PathStr := PathStr + 'l ' + + FloatToStr(PtX, FPointSeparator) + ',' + + FloatToStr(PtY, FPointSeparator) + ' '; + end + else if (segment.SegmentType = st2DBezier) then + begin + // Converts all coordinates to absolute values ConvertFPVCoordinatesToSVGCoordinates( - AData, l2DSegment.X, l2DSegment.Y, PtX, PtY); - PtX := PtX - OldPtX; - PtY := PtY - OldPtY; + AData, l2DBSegment.X2, l2DBSegment.Y2, BezierCP1X, BezierCP1Y); + ConvertFPVCoordinatesToSVGCoordinates( + AData, l2DBSegment.X3, l2DBSegment.Y3, BezierCP2X, BezierCP2Y); - if (segment.SegmentType = stMoveTo) then - begin - PathStr := PathStr + 'm ' - + FloatToStr(PtX, FPointSeparator) + ',' - + FloatToStr(PtY, FPointSeparator) + ' '; - end - else if (segment.SegmentType = st2DLine) then - begin - PathStr := PathStr + 'l ' - + FloatToStr(PtX, FPointSeparator) + ',' - + FloatToStr(PtY, FPointSeparator) + ' '; - end - else if (segment.SegmentType = st2DBezier) then - begin - // Converts all coordinates to absolute values - ConvertFPVCoordinatesToSVGCoordinates( - AData, l2DBSegment.X2, l2DBSegment.Y2, BezierCP1X, BezierCP1Y); - ConvertFPVCoordinatesToSVGCoordinates( - AData, l2DBSegment.X3, l2DBSegment.Y3, BezierCP2X, BezierCP2Y); + // Transforms them into values relative to the initial point + BezierCP1X := BezierCP1X - OldPtX; + BezierCP1Y := BezierCP1Y - OldPtY; + BezierCP2X := BezierCP2X - OldPtX; + BezierCP2Y := BezierCP2Y - OldPtY; - // Transforms them into values relative to the initial point - BezierCP1X := BezierCP1X - OldPtX; - BezierCP1Y := BezierCP1Y - OldPtY; - BezierCP2X := BezierCP2X - OldPtX; - BezierCP2Y := BezierCP2Y - OldPtY; + // PtX and PtY already contains the destination point - // PtX and PtY already contains the destination point - - // Now render our 2D cubic bezier - PathStr := PathStr + 'c ' - + FloatToStr(BezierCP1X, FPointSeparator) + ',' - + FloatToStr(BezierCP1Y, FPointSeparator) + ' ' - + FloatToStr(BezierCP2X, FPointSeparator) + ',' - + FloatToStr(BezierCP2Y, FPointSeparator) + ' ' - + FloatToStr(PtX, FPointSeparator) + ',' - + FloatToStr(PtY, FPointSeparator) + ' ' - ; - end; - - // Store the current position for future points - OldPtX := OldPtX + PtX; - OldPtY := OldPtY + PtY; + // Now render our 2D cubic bezier + PathStr := PathStr + 'c ' + + FloatToStr(BezierCP1X, FPointSeparator) + ',' + + FloatToStr(BezierCP1Y, FPointSeparator) + ' ' + + FloatToStr(BezierCP2X, FPointSeparator) + ',' + + FloatToStr(BezierCP2Y, FPointSeparator) + ' ' + + 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) + '" />'); + // Store the current position for future points + OldPtX := OldPtX + PtX; + OldPtY := OldPtY + PtY; end; + + // Get the Pen Width + if APath.Pen.Width >= 1 then lPenWidth := APath.Pen.Width + else lPenWidth := 1; + + // Get the Pen Color + lPenColor := VColorToRGBHexString(APath.Pen.Color); + + AStrings.Add(' <path'); + AStrings.Add(Format(' style="fill:none;stroke:#%s;stroke-width:%dpx;' + + 'stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"', + [lPenColor, lPenWidth])); + AStrings.Add(' d="' + PathStr + '"'); + AStrings.Add(' id="path' + IntToStr(AIndex) + '" />'); end; procedure TvSVGVectorialWriter.ConvertFPVCoordinatesToSVGCoordinates(