From bc906c5514c67342afa4048f56465f645b3c4262 Mon Sep 17 00:00:00 2001 From: wp Date: Fri, 1 Jan 2016 22:47:03 +0000 Subject: [PATCH] fpvectorial: Fix smooth bezier paths if previous segment is a relative bezier path git-svn-id: trunk@51108 - --- components/fpvectorial/fpvectorial.pas | 124 ++++++++++++++---- components/fpvectorial/fpvutils.pas | 2 +- components/fpvectorial/svgvectorialreader.pas | 52 +++++--- 3 files changed, 135 insertions(+), 43 deletions(-) diff --git a/components/fpvectorial/fpvectorial.pas b/components/fpvectorial/fpvectorial.pas index 0bf6919e03..ea8ffbdec6 100644 --- a/components/fpvectorial/fpvectorial.pas +++ b/components/fpvectorial/fpvectorial.pas @@ -283,7 +283,7 @@ type st2DEllipticalArc); {@@ - The coordinates in fpvectorial are given in millimiters and + The coordinates in fpvectorial are given in millimeters and 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. } @@ -521,7 +521,8 @@ type procedure AssignBrush(ABrush: TvBrush); procedure DrawBrushGradient(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; x1, y1, x2, y2: Integer; - ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); + ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); overload; +// procedure DrawBrushGradient(ADest: TFPCustomCanvas; x1,y1,x2,y2: Integer); overload; procedure Render(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0; ADoDraw: Boolean = True); override; function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override; @@ -561,6 +562,9 @@ type FCurMoveSubPartIndex: Integer; FCurMoveSubPartSegment: TPathSegment; // + protected + FPolyPoints: TPointsArray; + FPolyStarts: TIntegerDynArray; public Len: Integer; Points: TPathSegment; // Beginning of the double-linked list @@ -3217,6 +3221,7 @@ var pts: TPointsArray; coordX, coordY, coord2X, coord2Y, coord3X, coord3Y, coord4X, coord4Y: Integer; i, n: Integer; + prev: TPoint; begin if not (Previous is T2DSegment) then raise Exception.Create('T2DBezierSegment must follow a T2DSegment.'); @@ -3238,13 +3243,21 @@ begin Make2DPoint(coord4X, coord4Y), pts); + if Length(pts) = 0 then + exit; + n := Length(Points); - SetLength(Points, n + Length(pts) - 1); // we don't need the start point --> -1 - for i:=1 to High(pts) do // begin at 1 to skip the start point + prev := Points[n-1]; + SetLength(Points, n + Length(pts)); + for i:=0 to High(pts) do begin + if (pts[i].X = prev.X) and (pts[i].Y = prev.Y) then // skip subsequent coincident points + Continue; Points[n] := pts[i]; + prev := pts[i]; inc(n); end; + SetLength(Points, n); end; { T3DSegment } @@ -3595,7 +3608,63 @@ begin Brush.Style := ABrush.Style; Brush.Color := ABrush.Color; end; + (* +{ Fills the entity with a gradient. + Assumes that the boundary is already in canvas units } +procedure TvEntityWithPenAndBrush.DrawBrushGradient(ADest: TFPCustomCanvas; + x1, y1, x2, y2: Integer); +var + lColor1, lColor2: TFPColor; + i, j: Integer; +begin + if not (Brush.Kind in [bkVerticalGradient, bkHorizontalGradient]) then + Exit; + lColor1 := Brush.Gradient_colors[1]; + lColor2 := Brush.Gradient_colors[0]; + if Brush.Kind = bkVerticalGradient then + begin + for i := y1 to y2 do + begin + lPoints := GetLineIntersectionPoints(CanvasToCoordY(i), False); + if Length(lPoints) < 2 then Continue; + lColor := MixColors(lColor1, lColor2, i-y1, y2-y1); + ADest.Pen.FPColor := lColor; + ADest.Pen.Style := psSolid; + j := 0; + while j < Length(lPoints) do + begin + lCanvasPts[0] := CoordToCanvasX(lPoints[j]); + lCanvasPts[1] := CoordToCanvasX(lPoints[j+1]); + ADest.Line(lCanvasPts[0], i, lCanvasPts[1], i); + inc(j, 2); + end; + end; + end + else if Brush.Kind = bkHorizontalGradient then + begin + for i := x1 to x2 do + begin + lPoints := GetLineIntersectionPoints(CanvasToCoordX(i), True); + if Length(lPoints) < 2 then Continue; + lColor := MixColors(lColor1, lColor2, i-x1, x2-x1); + ADest.Pen.FPColor := lColor; + ADest.Pen.Style := psSolid; + j := 0; + while (j+1 < Length(lPoints)) do + begin + lCanvasPts[0] := CoordToCanvasY(lPoints[j]); + lCanvasPts[1] := CoordToCanvasY(lPoints[j+1]); + ADest.Line(i, lCanvasPts[0], i, lCanvasPts[1]); + inc(j , 2); + end; + end; + end; +end; *) + +{ Fills the entity's shape with a gradient. + Assumes that the boundary is in fpv units and provides parameters (ADestX, + ADestY, AMulX, AMulY) for conversion to canvas pixels. } procedure TvEntityWithPenAndBrush.DrawBrushGradient(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; x1, y1, x2, y2: Integer; ADestX: Integer; ADestY: Integer; AMulX: Double; AMulY: Double); @@ -4203,11 +4272,10 @@ end; procedure TPath.Render(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; ADestX, ADestY: Integer; AMulX, AMulY: Double; ADoDraw: Boolean); var - polygonPoints: TPointsArray; - polygonStart: TIntegerDynArray; i: Integer; j, n: Integer; x1, y1, x2, y2: Integer; + pts: TPointsArray; ACanvas: TCanvas absolute ADest; coordX, coordY: Integer; curSegment: TPathSegment; @@ -4215,17 +4283,17 @@ var begin inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY, ADoDraw); - ConvertPathToPolygons(self, ADestX, ADestY, AMulX, AMulY, polygonPoints, polygonStart); + ConvertPathToPolygons(self, ADestX, ADestY, AMulX, AMulY, FPolyPoints, FPolyStarts); x1 := MaxInt; y1 := maxInt; x2 := -MaxInt; y2 := -MaxInt; - for i := 0 to High(polygonPoints) do + for i := 0 to High(FPolyPoints) do begin - x1 := min(x1, polygonPoints[i].X); - y1 := min(y1, polygonPoints[i].Y); - x2 := max(x2, polygonPoints[i].X); - y2 := max(y2, polygonPoints[i].Y); + x1 := min(x1, FPolyPoints[i].X); + y1 := min(y1, FPolyPoints[i].Y); + x2 := max(x2, FPolyPoints[i].X); + y2 := max(y2, FPolyPoints[i].Y); end; CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, x1, y1, x2, y2); @@ -4233,23 +4301,24 @@ begin begin // (1) draw background only ADest.Pen.Style := psClear; - if (Length(polygonPoints) > 2) then + if (Length(FPolyPoints) > 2) then case Brush.Kind of bkSimpleBrush: if Brush.Style <> bsClear then begin {$IFDEF USE_LCL_CANVAS} - for i := 0 to High(polygonStart) do + for i := 0 to High(FPolyStarts) do begin - j := polygonStart[i]; - if i = High(polygonStart) then - n := Length(polygonPoints) - j + j := FPolyStarts[i]; + if i = High(FPolyStarts) then + n := Length(FPolyPoints) - j else - n := polygonStart[i+1] - polygonStart[i] + 1; + n := FPolyStarts[i+1] - FPolyStarts[i] + 1; end; - ACanvas.Polygon(@polygonPoints[j], n, WindingRule = vcmNonZeroWindingRule); + if (FPolyPoints[0].X = FPolyPoints[n-1].X) and (FPolyPoints[0].Y = FPolyPoints[n-1].Y) then + ACanvas.Polygon(@FPolyPoints[j], n, WindingRule = vcmNonZeroWindingRule); {$ELSE} - ADest.Polygon(polygonPoints); + ADest.Polygon(FPolyPoints); {$ENDIF} end; else // gradients @@ -4291,13 +4360,14 @@ begin begin coordX := CoordToCanvasX(T2DSegment(curSegment.Previous).X, ADestX, AMulX); coordY := CoordToCanvasY(T2DSegment(curSegment.Previous).Y, ADestY, AMulY); - SetLength(PolygonPoints, 1); - PolygonPoints[0] := Point(coordX, coordY); - curSegment.AddToPoints(ADestX, ADestY, AMulX, AMulY, PolygonPoints); - ADest.PolyLine(PolygonPoints); - coordX := PolygonPoints[High(PolygonPoints)].X; - coordY := PolygonPoints[High(PolygonPoints)].Y; - ADest.MoveTo(coordX, coordY); + SetLength(pts, 1); + pts[0] := Point(coordX, coordY); + curSegment.AddToPoints(ADestX, ADestY, AMulX, AMulY, pts); + if Length(pts) > 0 then + begin + ADest.PolyLine(pts); + ADest.MoveTo(pts[High(pts)].X, pts[High(pts)].Y); + end; end; end; end; diff --git a/components/fpvectorial/fpvutils.pas b/components/fpvectorial/fpvutils.pas index e733894106..49bae89dbb 100644 --- a/components/fpvectorial/fpvutils.pas +++ b/components/fpvectorial/fpvutils.pas @@ -254,7 +254,7 @@ begin EllipticalArcToBezier(Xc, Yc, R, R, startAngle, endAngle, P1, P2, P3, P4); end; -{ This routine converts a Bezier to a Polygon and adds the points of this poligon +{ This routine converts a Bezier to a Polygon and adds the points of this polygon to the end of the provided Points output variables } procedure AddBezierToPoints(P1, P2, P3, P4: T3DPoint; var Points: TPointsArray); var diff --git a/components/fpvectorial/svgvectorialreader.pas b/components/fpvectorial/svgvectorialreader.pas index 739484e5e0..e9546b644c 100644 --- a/components/fpvectorial/svgvectorialreader.pas +++ b/components/fpvectorial/svgvectorialreader.pas @@ -221,11 +221,12 @@ var lToken: TSVGToken; lStr: string; begin - lToken := TSVGToken.Create; +// lToken := TSVGToken.Create; lStr := Trim(AStr); if lStr = '' then Exit; + lToken := TSVGToken.Create; // Moves if lStr[1] = 'M' then lToken.TokenType := sttMoveTo else if lStr[1] = 'm' then lToken.TokenType := sttRelativeMoveTo @@ -2001,6 +2002,7 @@ var lDebugStr: String; lToken5Before, lToken7Before: TSVGTokenType; lCorrectPreviousToken: Boolean; + lPrevRelative, lCurRelative: Boolean; begin lCurTokenType := ACurTokenType; // -------------- @@ -2043,7 +2045,8 @@ begin CurY := Y; AData.AddLineToPath(CurX, CurY); - Inc(i, 3); + Inc(i, 1); +// Inc(i, 3); end // -------------- // Lines @@ -2091,6 +2094,7 @@ begin else if lCurTokenType in [sttBezierTo, sttRelativeBezierTo, sttSmoothBezierTo, sttRelativeSmoothBezierTo] then begin + lPrevRelative := false; if lCurTokenType in [sttBezierTo, sttRelativeBezierTo] then begin X2 := FSVGPathTokenizer.Tokens.Items[i+1].Value; @@ -2116,10 +2120,11 @@ begin lCorrectPreviousToken := lToken5Before in [sttSmoothBezierTo, sttRelativeSmoothBezierTo]; lCorrectPreviousToken := lCorrectPreviousToken or (lToken7Before in [sttBezierTo, sttRelativeBezierTo]); + lPrevRelative := (lToken5Before = sttRelativeSmoothBezierTo) or (lToken7Before = sttRelativeBezierTo); end; if (i >= 7) and (lCorrectPreviousToken) then begin - if lCurTokenType = sttRelativeSmoothBezierTo then + if (lCurTokenType = sttRelativeSmoothBezierTo) or lPrevRelative then begin X2 := FSVGPathTokenizer.Tokens.Items[i-2].Value - FSVGPathTokenizer.Tokens.Items[i-4].Value; Y2 := FSVGPathTokenizer.Tokens.Items[i-1].Value - FSVGPathTokenizer.Tokens.Items[i-3].Value; @@ -2138,17 +2143,32 @@ begin end; // Careful that absolute coordinates require using ConvertSVGCoordinatesToFPVCoordinates - if lCurTokenType in [sttRelativeBezierTo, sttRelativeSmoothBezierTo] then + lCurRelative := lCurTokenType in [sttRelativeBezierTo, sttRelativeSmoothBezierTo]; + if lPrevRelative then begin ConvertSVGDeltaToFPVDelta(AData, X2, Y2, X2, Y2); - ConvertSVGDeltaToFPVDelta(AData, X3, Y3, X3, Y3); - ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y); - end - else + if lCurRelative then + begin + ConvertSVGDeltaToFPVDelta(AData, X3, Y3, X3, Y3); + ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y); + end else + begin + ConvertSVGCoordinatesToFPVCoordinates(AData, X3, Y3, X3, Y3); + ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y); + end; + end else begin - ConvertSVGCoordinatesToFPVCoordinates(AData, X2, Y2, X2, Y2); - ConvertSVGCoordinatesToFPVCoordinates(AData, X3, Y3, X3, Y3); - ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y); + if lCurRelative then + begin + ConvertSVGDeltaToFPVDelta(AData, X2, Y2, X2, Y2); + ConvertSVGDeltaToFPVDelta(AData, X3, Y3, X3, Y3); + ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y); + end else + begin + ConvertSVGCoordinatesToFPVCoordinates(AData, X2, Y2, X2, Y2); + ConvertSVGCoordinatesToFPVCoordinates(AData, X3, Y3, X3, Y3); + ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y); + end; end; // Covers the case where there is no valid first control point in smooth bezier @@ -2177,14 +2197,16 @@ begin end else begin - AData.AddBezierToPath(X2, Y2, X3, Y3, X, Y); + if lPrevRelative then + AData.AddBezierToPath(X2 + CurX, Y2 + CurY, X3, Y3, X, Y) else + AData.AddBezierToPath(X2, Y2, X3, Y3, X, Y); CurX := X; CurY := Y; end; if lCurTokenType in [sttBezierTo, sttRelativeBezierTo] then - Inc(i, 7) - else Inc(i, 5); + Inc(i, 7) else + Inc(i, 5); end // -------------- // Quadratic Bezier @@ -3194,7 +3216,7 @@ begin AData.Height := ly2 - ly; end; - // Make sure the latest page size is syncronized with auto-detected + // Make sure the latest page size is synchronized with auto-detected // or ViewBox-only obtained size Page_Width := AData.Width; Page_Height := AData.Height;