{******************************************} { } { FastReport VCL } { SVG GDI+ Canvas } { } { Copyright (c) 1998-2021 } { by Fast Reports Inc. } { } {******************************************} unit frxSVGGDIPCanvas; interface {$I frx.inc} uses Windows, Graphics, Classes, Types, {$IFDEF Delphi16} Winapi.GDIPOBJ, Winapi.GDIPAPI, {$ELSE} frxGDIPOBJ, frxGDIPAPI, {$ENDIF} frxGDIPPathText, frxSVGColor, frxSVGCanvas, frxSVGHelpers, frxSVGComponents; type TSVGGDIPCanvas = class(TSVGCanvas) private FPen: TGPPen; FGPMatrix: TGPMatrix; FGraphics: TGPGraphics; protected FColors: packed array of ARGB; FPositions: packed array of Single; procedure CalcGradientData(SVGGradientArray: TSVGGradientArray); function GetStringSize(WS: WideString; Font: TGPFont): TSingleSize; procedure SetElementsGPMatrix(const Matrix: TSVGTransform); class function GetFont(FontData: TSVGFontData; out FontHeight: Single): TGPFont; class function GDIPLineJoin(SVGLineJoin: TSVGSpecificWord): TLineJoin; class function GDIPLineCap(SVGLineCap: TSVGSpecificWord): TLineCap; class function GDIPFontStyle(FontData: TSVGFontData): TFontStyle; class function GDIPFillMode(FillRule: TSVGSpecificWord): TFillMode; class function ConvertColor(SVGColor: TSVGColor): Cardinal; class function ProjectionOfPointToLine(L1, L2, P: TSinglePoint): TSinglePoint; class function Distance(const x1, y1, x2, y2: Single): Single; overload; class function Distance(const P1, P2: TSinglePoint): Single; overload; protected FSolidColor: array [TSVGDrawType] of TGPSolidBrush; FLinearGradient: array [TSVGDrawType] of TGPLinearGradientBrush; FRadialGradient: array [TSVGDrawType] of TGPPathGradientBrush; procedure ChangeSolidColorFiller(DrawType: TSVGDrawType); override; procedure ChangeLinearGradientFiller(DrawType: TSVGDrawType); override; procedure WidenTile(var GA: TSVGGradientArray; const WidenAddStart, WidenAddFinish: Integer; var WidenStart, WidenFinish: TSinglePoint); procedure ChangeRadialGradientFiller(DrawType: TSVGDrawType); override; procedure WidenRadius(var GA: TSVGGradientArray; out Radius: TSinglePoint; const ObjBounds: TSingleBounds; const cx, cy, r: Single); procedure StrokePath(Path: TSVGCanvasPath); override; procedure FillPath(Path: TSVGCanvasPath); override; public constructor Create(DC: HDC); override; destructor Destroy; override; procedure SetTransform(const Matrix: TSVGTransform); override; procedure GetTransform(out Matrix: TSVGTransform); override; procedure ResetTransform; override; procedure SetClip(Path: TSVGCanvasPath); override; procedure IntersectClip(Path: TSVGCanvasPath); override; procedure ResetClip; override; procedure SetStroke(StrokeData: TSVGStrokeData); override; function MeasureString(st: string; FontData: TSVGFontData; out FontHeight: Single): TSingleSize; override; procedure DrawImage(FImage: TSVGCanvasImage; SVGRect: TSingleBounds; FillOpacity: Single); override; class procedure AddStringToPath(st: string; Path: TSVGCanvasPath; Decorations: TTextDecorations; TextOrigin: TTextOrigin; FontData: TSVGFontData); override; class function GetFontFamily(const FontName: string): TGPFontFamily; class function CreateImage(SA: TStreamAdapter): TSVGCanvasImage; override; class function CreatePath: TSVGCanvasPath; override; class function CreatePathText(Path: TSVGCanvasPath): TSVGCanvasPathText; override; class function GetPathLength(const Path: TSVGCanvasPath): Single; override; end; implementation uses SysUtils, Math, frxGDIPKerning, frxHelpers, frxUtils; type TSVGGDIPImage = class(TSVGCanvasImage) private FGPImage: TGPImage; public constructor Create(SA: TStreamAdapter); override; destructor Destroy; override; function GetWidth: Cardinal; override; function GetHeight: Cardinal; override; end; TSVGGDIGraphicsPath = class(TSVGCanvasPath) private FGPGraphicsPath: TGPGraphicsPath; public constructor Create; override; destructor Destroy; override; function Clone: TSVGCanvasPath; override; procedure Transform(const Matrix: TSVGTransform); override; function Bounds: TSingleBounds; override; procedure AddArc(x, y, width, height, startAngle, sweepAngle: Single); override; procedure AddBezier(x1, y1, x2, y2, x3, y3, x4, y4: Single); override; procedure AddCircle(cx, cy, r: Single); override; procedure AddEllipse(cx, cy, rx, ry: Single); override; procedure AddLine(x1, y1, x2, y2: Single); override; procedure AddPath(addingPath: TSVGCanvasPath; connect: Bool); override; procedure AddRectangle(x, y, width, height, rx, ry: Single); override; procedure CloseFigure; override; procedure StartFigure; override; function Log: string; override; end; TSVGGDIPGraphicsPathText = class(TSVGCanvasPathText) private GPPathText: TGPPathText; public constructor Create(Path: TSVGCanvasPath); function AddPathText(const Path: TSVGCanvasPath; const Text: string; const Indent: Single; FontData: TSVGFontData; const DistanceFactor: Single = 1; const KerningFactor: Single = 1): Single; override; destructor Destroy; override; end; { Utility routines } function GetGPMatrix(const Matrix: TSVGTransform): TGPMatrix; begin with Matrix do Result := TGPMatrix.Create(a, b, c, d, e, f); end; { TSVGGDIPlusCanvas } procedure TSVGGDIPCanvas.CalcGradientData(SVGGradientArray: TSVGGradientArray); var Len, i: Integer; begin Len := Length(SVGGradientArray); SetLength(FColors, Len); SetLength(FPositions, Len); for i := 0 to Len - 1 do begin FColors[i] := ConvertColor(SVGGradientArray[i].SVGColor); FPositions[i] := SVGGradientArray[i].Offset; end; end; procedure TSVGGDIPCanvas.ChangeLinearGradientFiller(DrawType: TSVGDrawType); var GD: TSVGGradientData; Start, Intermediate, BaseStart, BaseFinish, Finish, Part1, Part2: TSinglePoint; WidenStart, WidenFinish: TSinglePoint; GA: TSVGGradientArray; i: Integer; AddStart, AddFinish: Integer; Base, Factor: Single; BoundsRect, GradRect: TSingleRect; M: TSVGTransform; begin GD := FGradientData[DrawType]; BoundsRect := ToSingleRect(GD.Bounds); GradRect := ToSingleRect(GD.x1, GD.y1, GD.x2, GD.y2); if GD.csu = svg_userSpaceOnUse then begin AddStart := IfInt(IsSameSingle(GradRect.Left, BoundsRect.Left) and IsSameSingle(GradRect.Top, BoundsRect.Top), 0, 1); AddFinish := IfInt(IsSameSingle(GradRect.Right, BoundsRect.Right) and IsSameSingle(GradRect.Bottom, BoundsRect.Bottom), 0, 1); Start := GradRect.TopLeft; Finish := GradRect.BottomRight; BaseStart := GradRect.TopLeft; BaseFinish := GradRect.BottomRight; end else begin if IsSameSingle(GradRect.Left, GradRect.Right) then begin AddStart := IfInt(IsSameSingle(GradRect.Top, BoundsRect.Top), 0, 1); AddFinish := IfInt(IsSameSingle(GradRect.Bottom, BoundsRect.Bottom), 0, 1); Start := BoundsRect.TopLeft; Finish := ToSinglePoint(BoundsRect.Left, BoundsRect.Bottom); BaseStart := GradRect.TopLeft; BaseFinish := GradRect.BottomRight; end else if IsSameSingle(GradRect.Top, GradRect.Bottom) then begin AddStart := IfInt(IsSameSingle(GradRect.Left, BoundsRect.Left), 0, 1); AddFinish := IfInt(IsSameSingle(GradRect.Right, BoundsRect.Right), 0, 1); Start := BoundsRect.TopLeft; Finish := ToSinglePoint(BoundsRect.Right, BoundsRect.Top); BaseStart := GradRect.TopLeft; BaseFinish := GradRect.BottomRight; end else begin AddStart := IfInt(IsSameSingle(GradRect.Left, BoundsRect.Left) and IsSameSingle(GradRect.Top, BoundsRect.Top), 0, 1); AddFinish := IfInt(IsSameSingle(GradRect.Right, BoundsRect.Right) and IsSameSingle(GradRect.Bottom, BoundsRect.Bottom), 0, 1); Start := BoundsRect.TopLeft; Part1.X := (GradRect.Top - BoundsRect.Top) / GD.Bounds.Height; // X <-> Y ! Part1.Y := (GradRect.Left - BoundsRect.Left) / GD.Bounds.Width; Part2.X := (GradRect.Bottom - BoundsRect.Top) / GD.Bounds.Height; Part2.Y := (GradRect.Right - BoundsRect.Left) / GD.Bounds.Width; Intermediate := ProjectionOfPointToLine( ToSinglePoint(BoundsRect.Left + Part1.X * GD.Bounds.Width, BoundsRect.Top + Part2.Y * GD.Bounds.Height), ToSinglePoint(BoundsRect.Left + Part2.X * GD.Bounds.Width, BoundsRect.Top + Part1.Y * GD.Bounds.Height), Start); Finish := ProjectionOfPointToLine(Start, Intermediate, BoundsRect.BottomRight); BaseStart := ProjectionOfPointToLine(Start, Intermediate, GradRect.TopLeft); BaseFinish := ProjectionOfPointToLine(Start, Intermediate, GradRect.BottomRight); end; end; Base := Distance(Start, BaseStart) / Distance(Start, Finish); Factor := Distance(BaseStart, BaseFinish) / Distance(Start, Finish); SetLength(GA, Length(GD.GradientArray) + AddStart + AddFinish); for i := 0 to High(GD.GradientArray) do begin GA[i + AddStart].SVGColor := GD.GradientArray[i].SVGColor; GA[i + AddStart].Offset := Base + GD.GradientArray[i].Offset * Factor; end; if High(GA) >= 0 then begin GA[0] := ToSVGGradientStopData(GA[0 + AddStart].SVGColor, 0.0); GA[High(GA)] := ToSVGGradientStopData(GA[High(GA) - AddFinish].SVGColor, 1.0); end; (******************************************************************************) WidenStart := Start; WidenFinish := Finish; WidenTile(GA, 1 - AddStart, 1 - AddFinish, WidenStart, WidenFinish); CalcGradientData(GA); FLinearGradient[DrawType].Free; FLinearGradient[DrawType] := TGPLinearGradientBrush.Create( MakePoint(WidenStart.X, WidenStart.Y), MakePoint(WidenFinish.X, WidenFinish.Y), 0, 0); // case GD.spreadMethod of // svg_pad: // FLinearGradient[DrawType] := TGPLinearGradientBrush.Create( // MakePoint(WidenStart.X, WidenStart.Y), MakePoint(WidenFinish.X, WidenFinish.Y), 0, 0); // svg_reflect: // FLinearGradient[DrawType] := TGPLinearGradientBrush.Create( // MakePoint(BaseStart.X, BaseStart.Y), MakePoint(BaseFinish.X, BaseFinish.Y), 0, 0); // svg_repeat: // FLinearGradient[DrawType] := TGPLinearGradientBrush.Create( // MakePoint(BaseStart.X, BaseStart.Y), MakePoint(BaseFinish.X, BaseFinish.Y), 0, 0); // end; // {$EXTERNALSYM WrapMode} // WrapMode = ( // WrapModeTile, // 0 // WrapModeTileFlipX, // 1 // WrapModeTileFlipY, // 2 // WrapModeTileFlipXY, // 3 // WrapModeClamp // 4 // ); // TWrapMode = WrapMode; // {$EXTERNALSYM TWrapMode} // case GD.spreadMethod of // svg_pad: FLinearGradient[DrawType].SetWrapMode(WrapModeTileFlipXY); // svg_reflect: FLinearGradient[DrawType].SetWrapMode(WrapModeTileFlipXY); // svg_repeat: FLinearGradient[DrawType].SetWrapMode(WrapModeTile); // end; FLinearGradient[DrawType].SetWrapMode(WrapModeTileFlipX); M := GD.Matrix; SetElementsGPMatrix(M); FLinearGradient[DrawType].MultiplyTransform(FGPMatrix, MatrixOrderAppend); FLinearGradient[DrawType].SetInterpolationColors(PGPColor(FColors), PSingle(FPositions), Length(FColors)); end; procedure TSVGGDIPCanvas.ChangeRadialGradientFiller(DrawType: TSVGDrawType); const InvertColors = True; var GD: TSVGGradientData; Path: TSVGGDIGraphicsPath; GA: TSVGGradientArray; Radius: TSinglePoint; Scale: TSinglePoint; M: TSVGTransform; NormalizedDiagonal: Single; begin GD := FGradientData[DrawType]; SetLength(GA, Length(GD.GradientArray)); Move(GD.GradientArray[0], GA[0], Length(GA) * SizeOf(GA[0])); WidenRadius(GA, Radius, GD.Bounds, GD.cx, GD.cy, GD.r); if GD.csu = svg_objectBoundingBox then begin NormalizedDiagonal := Sqrt((Sqr(GD.Bounds.Width) + Sqr(GD.Bounds.Height)) / 2); Scale := ToSinglePoint(GD.Bounds.Width / NormalizedDiagonal, GD.Bounds.Height / NormalizedDiagonal); Scale.X := ifReal(IsSameSingle(Scale.X, 0), 1.0, Scale.X); Scale.Y := ifReal(IsSameSingle(Scale.Y, 0), 1.0, Scale.Y); Radius := ToSinglePoint(Radius.X * Scale.X, Radius.Y * Scale.Y); end; Path := TSVGGDIGraphicsPath.Create; try Path.AddEllipse(GD.cx, GD.cy, Radius.X, Radius.Y); FRadialGradient[DrawType].Free; FRadialGradient[DrawType] := TGPPathGradientBrush.Create(Path.FGPGraphicsPath); finally Path.Free; end; // {$EXTERNALSYM WrapMode} // WrapMode = ( // WrapModeTile, // 0 // WrapModeTileFlipX, // 1 // WrapModeTileFlipY, // 2 // WrapModeTileFlipXY, // 3 // WrapModeClamp // 4 // ); // TWrapMode = WrapMode; // {$EXTERNALSYM TWrapMode} // case GD.spreadMethod of // svg_pad: FLinearGradient[DrawType].SetWrapMode(WrapModeTileFlipXY); // svg_reflect: FLinearGradient[DrawType].SetWrapMode(WrapModeTileFlipXY); { TODO : Тут размеры не увеличивать!? } // svg_repeat: FLinearGradient[DrawType].SetWrapMode(WrapModeTile); // end; FRadialGradient[DrawType].SetWrapMode(WrapModeTileFlipX); CalcGradientData(GA); FRadialGradient[DrawType].SetInterpolationColors(PARGB(FColors), PSingle(FPositions), Length(FColors)); M := GD.Matrix; SetElementsGPMatrix(M); FRadialGradient[DrawType].MultiplyTransform(FGPMatrix, MatrixOrderAppend); FRadialGradient[DrawType].SetCenterPoint(MakePoint(GD.fx, GD.fy)); end; procedure TSVGGDIPCanvas.ChangeSolidColorFiller(DrawType: TSVGDrawType); begin FSolidColor[DrawType].Free; FSolidColor[DrawType] := TGPSolidBrush.Create(ConvertColor(FSolidColorData[DrawType])); end; class function TSVGGDIPCanvas.ConvertColor(SVGColor: TSVGColor): Cardinal; begin with SVGColor do Result := MakeColor(Round(255 * Alpha), R, G, B); end; constructor TSVGGDIPCanvas.Create(DC: HDC); begin FGPMatrix := TGPMatrix.Create; FGraphics := TGPGraphics.Create(DC); FGraphics.SetSmoothingMode(SmoothingModeAntiAlias); FGraphics.SetPageUnit(UnitPixel); end; class function TSVGGDIPCanvas.CreateImage(SA: TStreamAdapter): TSVGCanvasImage; begin Result := TSVGGDIPImage.Create(SA); end; class function TSVGGDIPCanvas.CreatePath: TSVGCanvasPath; begin Result := TSVGGDIGraphicsPath.Create; end; class function TSVGGDIPCanvas.CreatePathText(Path: TSVGCanvasPath): TSVGCanvasPathText; begin Result := TSVGGDIPGraphicsPathText.Create(Path); end; destructor TSVGGDIPCanvas.Destroy; var dt: TSVGDrawType; begin FPen.Free; FGPMatrix.Free; for dt := Low(TSVGDrawType) to High(TSVGDrawType) do begin FSolidColor[dt].Free; FLinearGradient[dt].Free; FRadialGradient[dt].Free; end; FGraphics.Free; Finalize(FColors); Finalize(FPositions); inherited Destroy; end; class function TSVGGDIPCanvas.Distance(const P1, P2: TSinglePoint): Single; begin Result := Distance(P1.X, P1.Y, P2.X, P2.Y); end; class function TSVGGDIPCanvas.Distance(const x1, y1, x2, y2: Single): Single; begin Result := Sqrt(Sqr(x1 - x2) + Sqr(y1 - y2)); end; procedure TSVGGDIPCanvas.DrawImage(FImage: TSVGCanvasImage; SVGRect: TSingleBounds; FillOpacity: Single); var ImAtt: TGPImageAttributes; ColorMatrix: TColorMatrix; begin FillChar(ColorMatrix, Sizeof(ColorMatrix), 0); ColorMatrix[0, 0] := 1; ColorMatrix[1, 1] := 1; ColorMatrix[2, 2] := 1; ColorMatrix[3, 3] := FillOpacity; ColorMatrix[4, 4] := 1; ImAtt := TGPImageAttributes.Create; try ImAtt.SetColorMatrix(colorMatrix, ColorMatrixFlagsDefault, ColorAdjustTypeDefault); FGraphics.DrawImage(TSVGGDIPImage(FImage).FGPImage, MakeRect(SVGRect.X, SVGRect.Y, SVGRect.Width, SVGRect.Height), 0, 0, FImage.GetWidth, FImage.GetHeight, UnitPixel, ImAtt); finally ImAtt.Free; end; end; procedure TSVGGDIPCanvas.StrokePath(Path: TSVGCanvasPath); begin if Assigned(FPen) and (FPen.GetLastStatus = OK) then FGraphics.DrawPath(FPen, TSVGGDIGraphicsPath(Path).FGPGraphicsPath); end; procedure TSVGGDIPCanvas.WidenRadius(var GA: TSVGGradientArray; out Radius: TSinglePoint; const ObjBounds: TSingleBounds; const cx, cy, r: Single); const MaxFactor = 1000.0; MinRadius = 0.0001; var MaxDistance, Factor: Single; Temp: TSVGGradientStopData; i, WidenLen: Integer; WidenGA: TSVGGradientArray; begin MaxDistance := MaxValue([ Distance(cx, cy, ObjBounds.X, ObjBounds.Y), Distance(cx, cy, ObjBounds.X + ObjBounds.Width, ObjBounds.Y), Distance(cx, cy, ObjBounds.X, ObjBounds.Y + ObjBounds.Height), Distance(cx, cy, ObjBounds.X + ObjBounds.Width, ObjBounds.Y + ObjBounds.Height)]); if r >= MaxDistance then begin Factor := 1.0; Radius := ToSinglePoint(r, r); end else if r < MinRadius then begin Factor := MaxFactor; Radius := ToSinglePoint(MaxDistance, MaxDistance); end else begin Factor := MaxDistance / r; if not IsSameSingle(Min(ObjBounds.Width, ObjBounds.Height), 0.0) then if ObjBounds.Width < ObjBounds.Height then Factor := Factor * ObjBounds.Height / ObjBounds.Width else Factor := Factor * ObjBounds.Width / ObjBounds.Height; Factor := Min(Factor, MaxFactor); Radius := ToSinglePoint(r * Factor, r * Factor); end; for i := 0 to High(GA) div 2 do begin Temp := GA[i]; GA[i] := GA[High(GA) - i]; GA[High(GA) - i] := Temp; end; for i := 0 to High(GA) do GA[i].Offset := 1.0 - GA[i].Offset / Factor; WidenLen := Length(GA) + 2; SetLength(WidenGA, WidenLen); Move(GA[0], WidenGA[1], Length(GA) * SizeOf(GA[0])); WidenGA[0] := ToSVGGradientStopData(WidenGA[1].SVGColor, 0.0); WidenGA[WidenLen - 1] := ToSVGGradientStopData(WidenGA[WidenLen - 2].SVGColor, 1.0); SetLength(GA, WidenLen); Move(WidenGA[0], GA[0], WidenLen * SizeOf(GA[0])); end; procedure TSVGGDIPCanvas.WidenTile(var GA: TSVGGradientArray; const WidenAddStart, WidenAddFinish: Integer; var WidenStart, WidenFinish: TSinglePoint); const Factor = 10; var Center: TSinglePoint; i, WidenLen: Integer; WidenGA: TSVGGradientArray; begin Center := ToSinglePoint((WidenStart.X + WidenFinish.X) / 2.0, (WidenStart.Y + WidenFinish.Y) / 2.0); WidenStart := ToSinglePoint((WidenStart.X - Center.X) * Factor + Center.X, (WidenStart.Y - Center.Y) * Factor + Center.Y); WidenFinish := ToSinglePoint((WidenFinish.X - Center.X) * Factor + Center.X, (WidenFinish.Y - Center.Y) * Factor + Center.Y); WidenLen := Length(GA) + WidenAddStart + WidenAddFinish; SetLength(WidenGA, WidenLen); for i := 0 to High(GA) do begin WidenGA[i + WidenAddStart].Offset := (GA[i].Offset - 0.5) / Factor + 0.5; WidenGA[i + WidenAddStart].SVGColor := GA[i].SVGColor; end; WidenGA[0] := ToSVGGradientStopData(WidenGA[0 + WidenAddStart].SVGColor, 0.0); WidenGA[WidenLen - 1] := ToSVGGradientStopData(WidenGA[WidenLen - 1 - WidenAddFinish].SVGColor, 1.0); SetLength(GA, WidenLen); Move(WidenGA[0], GA[0], WidenLen * SizeOf(GA[0])); end; procedure TSVGGDIPCanvas.FillPath(Path: TSVGCanvasPath); procedure Fill(B: TGPBrush); var GPPlusPath: TSVGGDIGraphicsPath; GPPath: TGPGraphicsPath; begin if Assigned(B) and (B.GetLastStatus = OK) then begin GPPlusPath := TSVGGDIGraphicsPath(Path); GPPath := GPPlusPath.FGPGraphicsPath; GPPath.SetFillMode(GDIPFillMode(GPPlusPath.FillRule)); FGraphics.FillPath(B, GPPath); end; end; begin case FFillerType[dtFill] of ftSolidColor: Fill(FSolidColor[dtFill]); ftLinearGradient: Fill(FLinearGradient[dtFill]); ftRadialGradient: Fill(FRadialGradient[dtFill]); ftPattern: ; end; end; class function TSVGGDIPCanvas.GDIPFillMode(FillRule: TSVGSpecificWord): TFillMode; begin Result := FillModeWinding; case FillRule of svg_evenodd: Result := FillModeAlternate; svg_nonzero: Result := FillModeWinding; end; end; class function TSVGGDIPCanvas.GDIPFontStyle(FontData: TSVGFontData): TFontStyle; begin Result := FontStyleRegular; if FontData.Weight >= FW_SEMIBOLD then Result := Result or FontStyleBold; if FontData.Style in [svg_Italic, svg_Oblique] then Result := Result or FontStyleItalic; if svg_underline in FontData.Decoration then Result := Result or FontStyleUnderline; if svg_line_through in FontData.Decoration then Result := Result or FontStyleStrikeout; end; class function TSVGGDIPCanvas.GDIPLineCap(SVGLineCap: TSVGSpecificWord): TLineCap; begin case SVGLineCap of svg_butt: Result := LineCapFlat; svg_round: Result := LineCapRound; svg_square: Result := LineCapSquare; else raise Exception.Create('Unknown SVGLineCap'); end; end; class function TSVGGDIPCanvas.GDIPLineJoin(SVGLineJoin: TSVGSpecificWord): TLineJoin; begin case SVGLineJoin of svg_arcs: Result := LineJoinMiterClipped; svg_bevel: Result := LineJoinBevel; svg_miter: Result := LineJoinMiterClipped; // LineJoinMiter; svg_miter_clip: Result := LineJoinMiterClipped; svg_round: Result := LineJoinRound; else raise Exception.Create('Unknown SVGLineJoin'); end; end; class function TSVGGDIPCanvas.GetFont(FontData: TSVGFontData; out FontHeight: Single): TGPFont; var FF: TGPFontFamily; FontStyle: TFontStyle; begin FF := GetFontFamily(FontData.Names); FontStyle := GDIPFontStyle(FontData); FontHeight := FF.GetCellAscent(FontStyle) / FF.GetEmHeight(FontStyle); FontHeight := FontHeight * FontData.Size; Result := TGPFont.Create(FF, FontData.Size, FontStyle, UnitPixel); FF.Free; end; class function TSVGGDIPCanvas.GetFontFamily(const FontName: string): TGPFontFamily; begin Result := TGPFontFamily.Create(FontName); if Result.GetLastStatus <> OK then begin FreeAndNil(Result); Result := TGPFontFamily.Create(SuitableFont(FontName)); if Result.GetLastStatus <> OK then FreeAndNil(Result); end; if not Assigned(Result) then Result := TGPFontFamily.Create('Arial'); end; class function TSVGGDIPCanvas.GetPathLength(const Path: TSVGCanvasPath): Single; begin Result := TGPPathText.GetPathLength(TSVGGDIGraphicsPath(Path).FGPGraphicsPath); end; procedure TSVGGDIPCanvas.SetElementsGPMatrix(const Matrix: TSVGTransform); begin with Matrix do FGPMatrix.SetElements(a, b, c, d, e, f); end; procedure TSVGGDIPCanvas.GetTransform(out Matrix: TSVGTransform); var M: TGPMatrix; MA: TMatrixArray; begin M := TGPMatrix.Create; try FGraphics.GetTransform(M); Matrix := tmIdentity; M.GetElements(MA); Matrix.a := MA[0]; Matrix.b := MA[1]; Matrix.c := MA[2]; Matrix.d := MA[3]; Matrix.e := MA[4]; Matrix.f := MA[5]; finally M.Free; end; end; procedure TSVGGDIPCanvas.IntersectClip(Path: TSVGCanvasPath); begin FGraphics.SetClip(TSVGGDIGraphicsPath(Path).FGPGraphicsPath, CombineModeIntersect); end; function TSVGGDIPCanvas.GetStringSize(WS: WideString; Font: TGPFont): TSingleSize; var BoundingBox: TGPRectF; SF: TGPStringFormat; begin SF := TGPStringFormat.Create(StringFormatFlagsMeasureTrailingSpaces); try FGraphics.MeasureString(WS, -1, Font, MakePoint(0.0, 0), SF, BoundingBox); BoundingBox.Width := KerningText.MeasureText(WS, Font); finally SF.Free; end; with BoundingBox do Result := ToSingleSize(Width, Height); end; function TSVGGDIPCanvas.MeasureString(st: string; FontData: TSVGFontData; out FontHeight: Single): TSingleSize; var Font: TGPFont; begin Font := GetFont(FontData, {out -=>} FontHeight); try Result := GetStringSize(st, Font); finally Font.Free; end; end; class function TSVGGDIPCanvas.ProjectionOfPointToLine(L1, L2, P: TSinglePoint): TSinglePoint; var L, x, y: Single; begin x := L2.Y - L1.Y; y := L1.X - L2.X; L := (L1.X*L2.Y - L2.X*L1.Y + L1.Y*P.X - L2.Y*P.X + L2.X*P.Y - L1.X*P.Y) / (x*(L2.Y - L1.Y) + y*(L1.X - L2.X)); Result.X := P.X + x * L; Result.Y := P.Y + y * L; end; procedure TSVGGDIPCanvas.ResetClip; begin FGraphics.ResetClip; end; procedure TSVGGDIPCanvas.ResetTransform; begin FGraphics.ResetTransform; end; procedure TSVGGDIPCanvas.SetClip(Path: TSVGCanvasPath); begin FGraphics.SetClip(TSVGGDIGraphicsPath(Path).FGPGraphicsPath); end; procedure TSVGGDIPCanvas.SetStroke(StrokeData: TSVGStrokeData); var DashLen, i: Integer; DashArr: TSingleDynArray; LineCap: TLineCap; DashCap: TDashCap; begin if (FFillerType[dtStroke] = ftSolidColor) and Assigned(FSolidColor[dtStroke]) or (FFillerType[dtStroke] = ftLinearGradient) and Assigned(FLinearGradient[dtStroke]) or (FFillerType[dtStroke] = ftRadialGradient) and Assigned(FRadialGradient[dtStroke]) then begin LineCap := GDIPLineCap(StrokeData.LineCap); if LineCap = LineCapRound then DashCap := DashCapRound else DashCap := DashCapFlat; FPen := TGPPen.Create(0, StrokeData.Width); FPen.SetLineJoin(GDIPLineJoin(StrokeData.LineJoin)); FPen.SetMiterLimit(StrokeData.Miterlimit); FPen.SetLineCap(LineCap, LineCap, DashCap); DashLen := Length(StrokeData.Dash.Arr); if DashLen > 0 then begin SetLength(DashArr, DashLen); for i := 0 to DashLen - 1 do DashArr[i] := StrokeData.Dash.Arr[i] / StrokeData.Width; FPen.SetDashPattern(@DashArr[0], DashLen); FPen.SetDashStyle(DashStyleCustom); FPen.SetDashOffset(StrokeData.Dash.Offset); end; case FFillerType[dtStroke] of ftSolidColor: FPen.SetBrush(FSolidColor[dtStroke]); ftLinearGradient: FPen.SetBrush(FLinearGradient[dtStroke]); ftRadialGradient: FPen.SetBrush(FRadialGradient[dtStroke]); ftPattern: ; end; end else FreeAndNil(FPen); end; procedure TSVGGDIPCanvas.SetTransform(const Matrix: TSVGTransform); begin SetElementsGPMatrix(Matrix); FGraphics.SetTransform(FGPMatrix); end; class procedure TSVGGDIPCanvas.AddStringToPath(st: string; Path: TSVGCanvasPath; Decorations: TTextDecorations; TextOrigin: TTextOrigin; FontData: TSVGFontData); function ToGPGraphicsPath(CanvasPath: TSVGCanvasPath): TGPGraphicsPath; begin if CanvasPath = nil then Result := nil else Result := TSVGGDIGraphicsPath(CanvasPath).FGPGraphicsPath; end; var FF: TGPFontFamily; UnderlineGPPath, LineThrowGPPath, OwerlineGPPath: TGPGraphicsPath; begin FF := GetFontFamily(FontData.Names); UnderlineGPPath := ToGPGraphicsPath(Decorations[tdUnderline]); LineThrowGPPath := ToGPGraphicsPath(Decorations[tdLineThrow]); OwerlineGPPath := ToGPGraphicsPath(Decorations[tdOwerline]); try KerningText.AddToPath(TSVGGDIGraphicsPath(Path).FGPGraphicsPath, UnderlineGPPath, LineThrowGPPath, OwerlineGPPath, st, FF, GDIPFontStyle(FontData), FontData.Size, TextOrigin); finally FF.Free; end; end; { TSVGGDIPlusImage } constructor TSVGGDIPImage.Create(SA: TStreamAdapter); begin FGPImage := TGPImage.Create(SA); FGPImage.GetLastStatus; end; destructor TSVGGDIPImage.Destroy; begin FGPImage.Free; inherited Destroy; end; function TSVGGDIPImage.GetHeight: Cardinal; begin Result := FGPImage.GetHeight; end; function TSVGGDIPImage.GetWidth: Cardinal; begin Result := FGPImage.GetWidth; end; { TSVGGDIPlusGraphicsPath } procedure TSVGGDIGraphicsPath.AddArc(x, y, width, height, startAngle, sweepAngle: Single); begin FGPGraphicsPath.AddArc(x, y, width, height, startAngle, sweepAngle); end; procedure TSVGGDIGraphicsPath.AddBezier(x1, y1, x2, y2, x3, y3, x4, y4: Single); begin FGPGraphicsPath.AddBezier(x1, y1, x2, y2, x3, y3, x4, y4); end; procedure TSVGGDIGraphicsPath.AddCircle(cx, cy, r: Single); begin FGPGraphicsPath.AddEllipse(cx - r, cy - r, r * 2, r * 2); end; procedure TSVGGDIGraphicsPath.AddEllipse(cx, cy, rx, ry: Single); begin FGPGraphicsPath.AddEllipse(cx - rx, cy - ry, rx * 2, ry * 2); end; procedure TSVGGDIGraphicsPath.AddLine(x1, y1, x2, y2: Single); begin FGPGraphicsPath.AddLine(x1, y1, x2, y2); end; procedure TSVGGDIGraphicsPath.AddPath(addingPath: TSVGCanvasPath; connect: Bool); begin FGPGraphicsPath.AddPath(TSVGGDIGraphicsPath(addingPath).FGPGraphicsPath, connect); end; procedure TSVGGDIGraphicsPath.AddRectangle(x, y, width, height, rx, ry: Single); begin if IsZero(rx) or IsZero(ry) then FGPGraphicsPath.AddRectangle(MakeRect(x, y, width, height)) { TODO : Разобраться } else begin AddLine(x + rx, y, x + width - rx, y); AddArc(x + width - 2 * rx, y, 2 * rx, 2 * ry, 270, 90); AddLine(x + width, y + ry,x + width, y + height - ry); AddArc(x + width - 2 * rx, y + height - 2 * ry, 2 * rx, 2 * ry, 0, 90); AddLine(x + width - rx, y + height, x + rx, y + height); AddArc(x, y + height - 2 * ry, 2 * rx, 2 * ry, 90, 90); AddLine(x, y + height - ry, x, y + ry); AddArc(x, y, 2 * rx, 2 * ry, 180, 90); CloseFigure; end; end; function TSVGGDIGraphicsPath.Bounds: TSingleBounds; var GPBounds: TGPRectF; begin FGPGraphicsPath.GetBounds(GPBounds); with GPBounds do Result := ToSingleBounds(X, Y, Width, Height); end; function TSVGGDIGraphicsPath.Clone: TSVGCanvasPath; begin Result := TSVGGDIGraphicsPath.Create; TSVGGDIGraphicsPath(Result).Matrix := Matrix; TSVGGDIGraphicsPath(Result).FGPGraphicsPath := FGPGraphicsPath.Clone; end; procedure TSVGGDIGraphicsPath.CloseFigure; begin FGPGraphicsPath.CloseFigure; end; constructor TSVGGDIGraphicsPath.Create; begin inherited Create; FGPGraphicsPath := TGPGraphicsPath.Create(FillModeWinding); end; destructor TSVGGDIGraphicsPath.Destroy; begin FGPGraphicsPath.Free; inherited Destroy; end; function TSVGGDIGraphicsPath.Log: string; var PathData: TPathData; i: Integer; Points: PGPPointF; begin PathData := TPathData.Create; try FGPGraphicsPath.GetPathData(PathData); Result := IntToStr(PathData.Count) + ': '; Points := PathData.Points; for i := 0 to PathData.Count - 1 do begin Points := Ptr(Cardinal(Points) + SizeOf(Points)); Result := Result + '(' + frxFloatToStr(Points^.X) + ' ' + frxFloatToStr(Points^.Y) + ') '; end; finally PathData.Free; end; end; procedure TSVGGDIGraphicsPath.StartFigure; begin FGPGraphicsPath.StartFigure; end; procedure TSVGGDIGraphicsPath.Transform(const Matrix: TSVGTransform); var GPMatrix: TGPMatrix; begin GPMatrix := GetGPMatrix(Matrix); try FGPGraphicsPath.Transform(GPMatrix); finally GPMatrix.Free; end; end; { TSVGGDIPlusText } function TSVGGDIPGraphicsPathText.AddPathText(const Path: TSVGCanvasPath; const Text: string; const Indent: Single; FontData: TSVGFontData; const DistanceFactor, KerningFactor: Single): Single; var FF: TGPFontFamily; FontStyle: TFontStyle; SF: TGPStringFormat; begin FF := TSVGGDIPCanvas.GetFontFamily(FontData.Names); FontStyle := TSVGGDIPCanvas.GDIPFontStyle(FontData); SF := TGPStringFormat.Create(StringFormatFlagsMeasureTrailingSpaces); Result := GPPathText.AddPathText(TSVGGDIGraphicsPath(Path).FGPGraphicsPath, WideString(Text), Indent, FF, FontStyle, FontData.Size, SF ); SF.Free; FF.Free; end; constructor TSVGGDIPGraphicsPathText.Create(Path: TSVGCanvasPath); begin GPPathText := TGPPathText.Create(TSVGGDIGraphicsPath(Path).FGPGraphicsPath); end; destructor TSVGGDIPGraphicsPathText.Destroy; begin GPPathText.Free; inherited Destroy; end; end.