diff --git a/components/fpvectorial/fpvutils.pas b/components/fpvectorial/fpvutils.pas index 6d5cb24333..5a81f1e5cb 100644 --- a/components/fpvectorial/fpvutils.pas +++ b/components/fpvectorial/fpvutils.pas @@ -54,6 +54,7 @@ function SeparateString(AString: string; ASeparator: char): T10Strings; function Make3DPoint(AX, AY, AZ: Double): T3DPoint; overload; inline; function Make3DPoint(AX, AY: Double): T3DPoint; overload; inline; function Point2D(AX, AY: Double): T2DPoint; inline; +function IsGradientBrush(ABrush: TvBrush): Boolean; // Mathematical routines function LineEquation_GetPointAndTangentForLength(AStart, AEnd: T3DPoint; ADistance: Double; out AX, AY, ATangentAngle: Double): Boolean; procedure EllipticalArcToBezier(Xc, Yc, Rx, Ry, startAngle, endAngle: Double; var P1, P2, P3, P4: T3DPoint); @@ -275,6 +276,12 @@ begin Result.Z := 0; end; +function IsGradientBrush(ABrush: TvBrush): Boolean; +begin + Result := ABrush.Kind in [bkHorizontalGradient, bkVerticalGradient, + bkOtherLinearGradient, bkRadialGradient]; +end; + { Considering a counter-clockwise arc, elliptical and alligned to the axises An elliptical Arc can be converted to diff --git a/components/fpvectorial/svgvectorialwriter.pas b/components/fpvectorial/svgvectorialwriter.pas index 4769e81fa2..87e780a71d 100644 --- a/components/fpvectorial/svgvectorialwriter.pas +++ b/components/fpvectorial/svgvectorialwriter.pas @@ -25,6 +25,7 @@ type FPointSeparator, FCommaSeparator: TFormatSettings; FLayerIndex: Integer; FPathIndex: Integer; + FGradientIndex: Integer; // helper routines procedure ConvertFPVCoordinatesToSVGCoordinates(APage: TvVectorialPage; const ASrcX, ASrcY: Double; var ADestX, ADestY: double); @@ -32,7 +33,10 @@ type var ADestX, ADestY: double); function FloatToSVGStr(x: Double): String; function GetBrushAsXMLStyle(ABrush: TvBrush): String; + function GetGradientBrushAsXML(ABrush: TvBrush): String; function GetPenAsXMLStyle(APen: TvPen): String; + procedure PrepareGradients(AStrings: TStrings; ADoc: TvVectorialDocument; + APage: TvVectorialPage); procedure WriteDocumentSize(AStrings: TStrings; AData: TvVectorialDocument); procedure WriteDocumentName(AStrings: TStrings; AData: TvVectorialDocument); @@ -105,15 +109,76 @@ end; function TvSVGVectorialWriter.GetBrushAsXMLStyle(ABrush: TvBrush): String; var - colorStr: String; + fillStr: String; begin - if ABrush.Style = bsClear then - colorStr := 'none' - else - colorStr := '#' + FPColorToRGBHexString(ABrush.Color); + if ABrush.Kind = bkSimpleBrush then begin + if ABrush.Style = bsClear then + fillStr := 'none' + else + fillStr := '#' + FPColorToRGBHexString(ABrush.Color) + end else begin + inc(FGradientIndex); + fillStr := Format('url(#gradient%d)', [FGradientIndex]); + end; - Result := Format('fill:%s;', [ - colorStr]); + Result := Format('fill:%s;', [fillStr]); +end; + +function TvSVGVectorialWriter.GetGradientBrushAsXML(ABrush: TvBrush): String; +var + gradientCol: TvGradientColor; + colorStr, gradientColors, gradientParams: String; + x1Str, y1Str, x2Str, y2Str: String; + cxstr, cystr, rstr, fxstr, fystr: String; + gradientTag: String; +begin + if ABrush.Kind = bkRadialGradient then begin + gradientTag := 'radialGradient'; + if ABrush.Gradient_cx_Unit = vcuPercentage then + cxstr := Format('%f%%', [ABrush.Gradient_cx*100], FPointSeparator) else + cxstr := FloatToSVGStr(ABrush.Gradient_cx); + if ABrush.Gradient_cy_Unit = vcuPercentage then + cystr := Format('%f%%', [ABrush.Gradient_cy*100], FPointSeparator) else + cystr := FloatToSVGStr(ABrush.Gradient_cy); + if ABrush.Gradient_r_Unit = vcuPercentage then + rstr := Format('%f%%', [ABrush.Gradient_r*100], FPointSeparator) else + rstr := FloatToSVGStr(ABrush.Gradient_r); + if ABrush.Gradient_fx_Unit = vcuPercentage then + fxstr := Format('%f%%', [ABrush.Gradient_fx*100], FPointSeparator) else + fxstr := FloatToSVGStr(ABrush.Gradient_fx); + if ABrush.Gradient_fy_Unit = vcuPercentage then + fystr := Format('%f%%', [ABrush.Gradient_fy*100], FPointSeparator) else + fystr := FloatToSVGStr(ABrush.Gradient_fy); + gradientParams := Format('cx="%s" cy="%s" r="%s" fx="%s" fy="%s"', + [cxstr, cystr, rstr, fxstr, fystr]); + end else begin + gradientTag := 'linearGradient'; + if gfRelStartX in ABrush.Gradient_flags then + x1Str := Format('%f%%', [ABrush.Gradient_start.X*100], FPointSeparator) else + x1Str := FloatToSVGStr(ABrush.Gradient_start.X); + if gfRelEndX in ABrush.Gradient_flags then + x2Str := Format('%f%%', [ABrush.Gradient_end.X*100], FPointSeparator) else + x2Str := FloatToSVGStr(ABrush.Gradient_end.X); + if gfRelStartY in ABrush.Gradient_flags then + y1Str := Format('%f%%', [ABrush.Gradient_start.Y*100], FPointSeparator) else + y1Str := FloatToSVGStr(ABrush.Gradient_start.Y); + if gfRelEndY in ABrush.Gradient_flags then + y2Str := Format('%f%%', [ABrush.Gradient_end.Y*100], FPointSeparator) else + y2Str := FloatToSVGStr(ABrush.Gradient_end.Y); + gradientParams := Format('x1="%s" y1="%s" x2="%s" y2="%s"', + [x1Str, y1Str, x2Str, y2Str]); + end; + + gradientColors := ''; + for gradientCol in ABrush.Gradient_colors do begin + colorStr := '#' + FPColorToRGBHexString(gradientCol.Color); + gradientColors := gradientColors + Format('', [ + gradientCol.Position*100, colorStr], FPointSeparator); + end; + + Result := Format( + ' <%s id="gradient%d" %s>%s', [ + gradientTag, FGradientIndex, gradientParams, gradientColors, gradientTag]); end; function TvSVGVectorialWriter.GetPenAsXMLStyle(APen: TvPen): String; @@ -143,6 +208,56 @@ begin end; end; +{ Iterates through all entities of the page and creates a node containing + all gradient definitions. Gradients are identified by a continuous number + reset before processing. } +procedure TvSVGVectorialWriter.PrepareGradients(AStrings: TStrings; + ADoc: TvVectorialDocument; APage: TvVectorialPage); + + procedure ProcessGradient(ABrush: TvBrush); + var + gradient: String; + begin + if FGradientIndex = 0 then + AStrings.Add(' '); + inc(FGradientIndex); + AStrings.Add(GetGradientBrushAsXML(ABrush)); + end; + + procedure ProcessEntity(AEntity: TvEntity); + var + entity: TvEntity; + brush: TvBrush; + begin + if AEntity is TvEntityWithPenAndBrush then begin + brush := TvEntityWithPenAndBrush(AEntity).Brush; + if IsGradientBrush(brush) then + ProcessGradient(TvEntityWithPenAndBrush(AEntity).Brush); + end; + if AEntity is TvLayer then begin + entity := TvLayer(AEntity).GetFirstEntity; + while entity <> nil do begin + ProcessEntity(entity); + entity := TvLayer(AEntity).GetNextEntity; + end; + end; + end; + +var + entity: TvEntity; + i: Integer; +begin + FGradientIndex := 0; + for i := 0 to APage.GetEntitiesCount() - 1 do + begin + entity := APage.GetEntity(i); + ProcessEntity(entity); + end; + if FGradientIndex > 0 then + AStrings.Add(' '); + FGradientIndex := 0; +end; + procedure TvSVGVectorialWriter.WriteDocumentSize(AStrings: TStrings; AData: TvVectorialDocument); begin AStrings.Add(' width="' + FloatToSVGStr(AData.Width) + 'mm"'); @@ -439,11 +554,15 @@ begin AStrings.Add(' version="1.1"'); WriteDocumentName(AStrings, AData); + lPage := AData.GetPageAsVectorial(0); + + // Prepare gradient definitions + PrepareGradients(AStrings, AData, lPage); + // Now data FLayerIndex := 1; FPathIndex := 1; AStrings.Add(' '); - lPage := AData.GetPageAsVectorial(0); WriteEntities(AStrings, AData, lPage); AStrings.Add(' ');