unit Img32.SVG.Reader; (******************************************************************************* * Author : Angus Johnson * * Version : 4.3 * * Date : 27 September 2022 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2019-2022 * * * * Purpose : Read SVG 2.0 files * * * * License : Use, modification & distribution is subject to * * Boost Software License Ver 1 * * http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************) interface {$I Img32.inc} uses SysUtils, Classes, Types, Math, StrUtils, {$IFDEF XPLAT_GENERICS} Generics.Collections, Generics.Defaults,{$ENDIF} Img32, Img32.SVG.Core, Img32.SVG.Path, Img32.Vector, Img32.Draw, Img32.Text, Img32.Transform; {$IFDEF ZEROBASEDSTR} {$ZEROBASEDSTRINGS OFF} {$ENDIF} type TSvgElement = class; TDrawData = record currentColor : TColor32; fillColor : TColor32; fillOpacity : double; fillRule : TFillRule; fillEl : UTF8String; strokeColor : TColor32; strokeOpacity : double; strokeWidth : TValue; strokeCap : TEndStyle; strokeJoin : TJoinStyle; strokeMitLim : double; strokeEl : UTF8String; dashArray : TArrayOfDouble; dashOffset : double; fontInfo : TSVGFontInfo; markerStart : UTF8String; markerMiddle : UTF8String; markerEnd : UTF8String; filterElRef : UTF8String; maskElRef : UTF8String; clipElRef : UTF8String; opacity : integer; matrix : TMatrixD; visible : Boolean; useEl : TSvgElement; //to check for and prevent recursion bounds : TRectD; end; TSvgReader = class; TElementClass = class of TSvgElement; TSvgElement = class private fParent : TSvgElement; fParserEl : TSvgTreeEl; fReader : TSvgReader; {$IFDEF XPLAT_GENERICS} fChilds : TList; {$ELSE} fChilds : TList; {$ENDIF} fId : UTF8String; fDrawData : TDrawData; //currently both static and dynamic vars function FindRefElement(refname: UTF8String): TSvgElement; function GetChildCount: integer; function GetChild(index: integer): TSvgElement; function FindChild(const idName: UTF8String): TSvgElement; protected elRectWH : TValueRecWH; //multifunction variable function IsFirstChild: Boolean; procedure LoadAttributes; procedure LoadAttribute(attrib: PSvgAttrib); function LoadContent: Boolean; virtual; //GetRelFracLimit: ie when to assume untyped vals are relative vals function GetRelFracLimit: double; virtual; procedure Draw(image: TImage32; drawDat: TDrawData); virtual; procedure DrawChildren(image: TImage32; drawDat: TDrawData); virtual; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); virtual; destructor Destroy; override; property Child[index: integer]: TSvgElement read GetChild; default; property ChildCount: integer read GetChildCount; property DrawData: TDrawData read fDrawData write fDrawData; property Id: UTF8String read fId; end; TSvgRootElement = class(TSvgElement) protected viewboxWH : TRectWH; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TSvgReader = class private fSvgParser : TSvgParser; fBkgndColor : TColor32; fBackgndImage : TImage32; fTempImage : TImage32; fBlurQuality : integer; fIdList : TStringList; fClassStyles : TClassStylesList; fLinGradRenderer : TLinearGradientRenderer; fRadGradRenderer : TSvgRadialGradientRenderer; fImgRenderer : TImageRenderer; fRootElement : TSvgRootElement; fFontCache : TFontCache; fUsePropScale : Boolean; function LoadInternal: Boolean; function GetIsEmpty: Boolean; procedure SetBlurQuality(quality: integer); protected userSpaceBounds : TRectD; currentColor : TColor32; procedure GetBestFontForFontCache(const svgFontInfo: TSVGFontInfo); property RadGradRenderer: TSvgRadialGradientRenderer read fRadGradRenderer; property LinGradRenderer: TLinearGradientRenderer read fLinGradRenderer; property ImageRenderer : TImageRenderer read fImgRenderer; property BackgndImage : TImage32 read fBackgndImage; property TempImage : TImage32 read fTempImage; public constructor Create; destructor Destroy; override; procedure Clear; function GetViewbox(containerWidth, containerHeight: integer): TRectWH; procedure DrawImage(img: TImage32; scaleToImage: Boolean); function LoadFromStream(stream: TStream): Boolean; function LoadFromFile(const filename: string): Boolean; function LoadFromString(const str: string): Boolean; //The following two methods are deprecated and intended only for ... //https://github.com/EtheaDev/SVGIconImageList procedure SetOverrideFillColor(color: TColor32); //deprecated; procedure SetOverrideStrokeColor(color: TColor32); //deprecated; function FindElement(const idName: UTF8String): TSvgElement; property BackgroundColor : TColor32 read fBkgndColor write fBkgndColor; property BlurQuality : integer read fBlurQuality write SetBlurQuality; property IsEmpty : Boolean read GetIsEmpty; //KeepAspectRatio: this property has also been added for the convenience of //the third-party SVGIconImageList. (IMHO it should always = true) property KeepAspectRatio: Boolean read fUsePropScale write fUsePropScale; property RootElement : TSvgRootElement read fRootElement; end; implementation uses Img32.Extra; type TFourDoubles = array [0..3] of double; TDefsElement = class(TSvgElement) public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; //------------------------------------- TShapeElement = class(TSvgElement) protected hasPaths : Boolean; drawPathsO : TPathsD; //open only drawPathsC : TPathsD; //closed only drawPathsF : TPathsD; //both open and closed (for filling) function GetBounds: TRectD; virtual; function HasMarkers: Boolean; procedure GetPaths(const drawDat: TDrawData); virtual; //GetSimplePath: required only for markers function GetSimplePath(const drawDat: TDrawData): TPathsD; virtual; procedure DrawFilled(img: TImage32; drawDat: TDrawData); procedure DrawStroke(img: TImage32; drawDat: TDrawData; isClosed: Boolean); procedure DrawMarkers(img: TImage32; drawDat: TDrawData); procedure Draw(image: TImage32; drawDat: TDrawData); override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TGroupElement = class(TShapeElement) protected procedure Draw(image: TImage32; drawDat: TDrawData); override; end; TSwitchElement = class(TShapeElement) protected procedure Draw(image: TImage32; drawDat: TDrawData); override; end; TUseElement = class(TShapeElement) private callerUse: TSvgElement; function ValidateNonRecursion(el: TSvgElement): Boolean; protected refEl: UTF8String; procedure GetPaths(const drawDat: TDrawData); override; procedure Draw(img: TImage32; drawDat: TDrawData); override; end; TMaskElement = class(TShapeElement) protected maskRec: TRect; procedure GetPaths(const drawDat: TDrawData); override; procedure ApplyMask(img: TImage32; const drawDat: TDrawData); end; TSymbolElement = class(TShapeElement) protected viewboxWH: TRectWH; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; //------------------------------------- TPathElement = class(TShapeElement) private fSvgPaths : TSvgPath; procedure Flatten(index: integer; scalePending: double; out path: TPathD; out isClosed: Boolean); protected function GetBounds: TRectD; override; procedure ParseDAttrib(const value: UTF8String); procedure GetPaths(const drawDat: TDrawData); override; function GetSimplePath(const drawDat: TDrawData): TPathsD; override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; destructor Destroy; override; end; TPolyElement = class(TShapeElement) //polyline or polygon protected path : TPathD; function GetBounds: TRectD; override; procedure ParsePoints(const value: UTF8String); procedure GetPaths(const drawDat: TDrawData); override; function GetSimplePath(const drawDat: TDrawData): TPathsD; override; end; TLineElement = class(TShapeElement) protected path : TPathD; function GetBounds: TRectD; override; procedure GetPaths(const drawDat: TDrawData); override; function GetSimplePath(const drawDat: TDrawData): TPathsD; override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TCircleElement = class(TShapeElement) protected centerPt : TValuePt; radius : TValue; function GetBounds: TRectD; override; procedure GetPaths(const drawDat: TDrawData); override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TEllipseElement = class(TShapeElement) protected centerPt : TValuePt; radius : TValuePt; function GetBounds: TRectD; override; procedure GetPaths(const drawDat: TDrawData); override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TRectElement = class(TShapeElement) protected radius : TValuePt; function GetBounds: TRectD; override; procedure GetPaths(const drawDat: TDrawData); override; function GetSimplePath(const drawDat: TDrawData): TPathsD; override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; //TTextElement: although this is a TShapeElement descendant, it's really //only a container for 'tspan' and 'subtext' elements. (See Draw method.) TTextElement = class(TShapeElement) protected offset : TValuePt; startX : double; currentPt : TPointD; function GetTopTextElement: TTextElement; procedure DoOffsetX(dx: double); procedure ResetTmpPt; procedure GetPaths(const drawDat: TDrawData); override; function LoadContent: Boolean; override; procedure Draw(img: TImage32; drawDat: TDrawData); override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TTSpanElement = class(TTextElement) public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TSubtextElement = class(TShapeElement) protected text : UTF8String; procedure GetPaths(const drawDat: TDrawData); override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; //------------------------------------- TTextPathElement = class(TSubtextElement) protected pathEl: UTF8String; //name (id) of path element procedure GetPaths(const drawDat: TDrawData); override; end; TMarkerElement = class(TShapeElement) private fPoints : TPathD; protected refPt : TValuePt; angle : double; angle2 : double; markerBoxWH : TRectWH; autoStartReverse : Boolean; procedure SetEndPoint(const pt: TPointD; angle: double); function SetMiddlePoints(const points: TPathD): Boolean; procedure Draw(img: TImage32; drawDat: TDrawData); override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TSvgColorStop = record offset : double; color : TColor32; end; TSvgColorStops = array of TSvgColorStop; TFillElement = class(TSvgElement) protected refEl : UTF8String; units : Cardinal; function GetRelFracLimit: double; override; end; TPatternElement = class(TFillElement) protected pattBoxWH : TRectWH; function PrepareRenderer(renderer: TImageRenderer; drawDat: TDrawData): Boolean; virtual; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; //nb: gradients with objectBoundingBox should not be applied to //elements without width and height. TGradientElement = class(TFillElement) protected stops : TSvgColorStops; spreadMethod : TGradientFillStyle; function LoadContent: Boolean; override; procedure AddStop(color: TColor32; offset: double); procedure AssignTo(other: TSvgElement); virtual; function PrepareRenderer(renderer: TCustomGradientRenderer; drawDat: TDrawData): Boolean; virtual; end; TRadGradElement = class(TGradientElement) protected radius: TValuePt; F, C: TValuePt; procedure AssignTo(other: TSvgElement); override; function PrepareRenderer(renderer: TCustomGradientRenderer; drawDat: TDrawData): Boolean; override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TLinGradElement = class(TGradientElement) protected startPt, endPt: TValuePt; procedure AssignTo(other: TSvgElement); override; function PrepareRenderer(renderer: TCustomGradientRenderer; drawDat: TDrawData): Boolean; override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TGradStopElement = class(TSvgElement) protected offset: double; color: TColor32; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TFilterElement = class(TSvgElement) private fSrcImg : TImage32; fLastImg : TImage32; fScale : double; fFilterBounds : TRect; fObjectBounds : TRect; fImages : array of TImage32; fNames : array of UTF8String; protected procedure Clear; function GetRelFracLimit: double; override; function GetAdjustedBounds(const bounds: TRectD): TRectD; function FindNamedImage(const name: UTF8String): TImage32; function AddNamedImage(const name: UTF8String): TImage32; function GetNamedImage(const name: UTF8String): TImage32; procedure Apply(img: TImage32; const filterBounds: TRect; const matrix: TMatrixD); public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; destructor Destroy; override; end; TFeBaseElement = class(TSvgElement) private function GetParentAsFilterEl: TFilterElement; protected in1: UTF8String; in2: UTF8String; res: UTF8String; srcImg, dstImg: TImage32; srcRec, dstRec: TRect; function GetSrcAndDst: Boolean; function GetBounds(img: TImage32): TRect; procedure Apply; virtual; abstract; property ParentFilterEl: TFilterElement read GetParentAsFilterEl; end; TFeBlendElement = class(TFeBaseElement) protected procedure Apply; override; end; TCompositeOp = (coOver, coIn, coOut, coAtop, coXOR, coArithmetic); TFeCompositeElement = class(TFeBaseElement) protected fourKs: TFourDoubles; //arithmetic constants compositeOp: TCompositeOp; procedure Apply; override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TFeColorMatrixElement = class(TFeBaseElement) protected values: TArrayOfDouble; procedure Apply; override; end; TFeDefuseLightElement = class(TFeBaseElement) protected color : TColor32; surfaceScale : double; diffuseConst : double; kernelSize : integer; procedure Apply; override; end; TFeDropShadowElement = class(TFeBaseElement) protected stdDev : double; offset : TValuePt; floodColor : TColor32; procedure Apply; override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TFeFloodElement = class(TFeBaseElement) protected floodColor : TColor32; procedure Apply; override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TFeGaussElement = class(TFeBaseElement) protected stdDev: double; procedure Apply; override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; TFeMergeElement = class(TFeBaseElement) protected procedure Apply; override; end; TFeMergeNodeElement = class(TFeBaseElement) protected procedure Apply; override; end; TFeOffsetElement = class(TFeBaseElement) protected offset : TValuePt; procedure Apply; override; end; TFePointLightElement = class(TFeBaseElement) protected z : double; end; TFeSpecLightElement = class(TFeBaseElement) protected exponent : double; color : TColor32; procedure Apply; override; end; TClipPathElement = class(TShapeElement) protected units: Cardinal; procedure GetPaths(const drawDat: TDrawData); override; public constructor Create(parent: TSvgElement; svgEl: TSvgTreeEl); override; end; //------------------------------------- const buffSize = 32; clAlphaSet = $00010101; SourceImage : UTF8String = 'SourceGraphic'; //SourceAlpha : UTF8String = 'SourceAlpha'; tmpFilterImg : UTF8String = 'tmp'; //https://www.w3.org/TR/css-fonts-3/#font-family-prop emptyDrawInfo: TDrawData = (currentColor: clInvalid; fillColor: clInvalid; fillOpacity: InvalidD; fillRule: frNonZero; fillEl: ''; strokeColor: clInvalid; strokeOpacity: InvalidD; strokeWidth: (rawVal: InvalidD; unitType: utNumber); strokeCap: esPolygon; strokeJoin: jsAuto; strokeMitLim: 0.0; strokeEl : ''; dashArray: nil; dashOffset: 0; fontInfo: (family: ttfUnknown; size: 0; spacing: 0.0; textLength: 0; italic: sfsUndefined; weight: -1; align: staUndefined; decoration: fdUndefined; baseShift: (rawVal: InvalidD; unitType: utNumber)); markerStart: ''; markerMiddle: ''; markerEnd: ''; filterElRef: ''; maskElRef: ''; clipElRef: ''; opacity: MaxInt; matrix: ((1, 0, 0),(0, 1, 0),(0, 0, 1)); visible: true; useEl: nil; bounds: (Left:0; Top:0; Right:0; Bottom:0)); var //defaultFontHeight: this size will be used to retrieve all glyph contours //(and later scaled as necessary). This relatively large default ensures //that contours will have adequate detail. defaultFontHeight: double = 20.0; //------------------------------------------------------------------------------ // Miscellaneous functions ... //------------------------------------------------------------------------------ function HashToElementClass(hash: Cardinal): TElementClass; begin case hash of hClippath : Result := TClipPathElement; hCircle : Result := TCircleElement; hDefs : Result := TDefsElement; hEllipse : Result := TEllipseElement; hFilter : Result := TFilterElement; hfeBlend : Result := TFeBlendElement; hfeColorMatrix : Result := TFeColorMatrixElement; hfeComposite : Result := TFeCompositeElement; hfeDefuseLighting : Result := TFeDefuseLightElement; hfeDropShadow : Result := TFeDropShadowElement; hfeFlood : Result := TFeFloodElement; hFeGaussianBlur : Result := TFeGaussElement; hfeMerge : Result := TFeMergeElement; hfeMergeNode : Result := TFeMergeNodeElement; hfeOffset : Result := TFeOffsetElement; hfePointLight : Result := TFePointLightElement; hfeSpecularLighting : Result := TFeSpecLightElement; hG : Result := TGroupElement; hLine : Result := TLineElement; hLineargradient : Result := TLinGradElement; hMarker : Result := TMarkerElement; hMask : Result := TMaskElement; hPath : Result := TPathElement; hPattern : Result := TPatternElement; hPolyline : Result := TPolyElement; hPolygon : Result := TPolyElement; hRadialgradient : Result := TRadGradElement; hRect : Result := TRectElement; hStop : Result := TGradStopElement; hSvg : Result := TSvgRootElement; hSwitch : Result := TSwitchElement; hSymbol : Result := TSymbolElement; hText : Result := TTextElement; hTextPath : Result := TTextPathElement; hTSpan : Result := TTSpanElement; hUse : Result := TUseElement; else Result := TSvgElement; //use generic class end; end; //------------------------------------------------------------------------------ procedure UpdateDrawInfo(var drawDat: TDrawData; thisElement: TSvgElement); begin with thisElement.fDrawData do begin if currentColor <> clInvalid then thisElement.fReader.currentColor := currentColor; drawDat.fillRule := fillRule; if (fillColor = clCurrent) then drawDat.fillColor := thisElement.fReader.currentColor else if (fillColor <> clInvalid) then drawDat.fillColor := fillColor; if fillOpacity <> InvalidD then drawDat.fillOpacity := fillOpacity; if (fillEl <> '') then drawDat.fillEl := fillEl; if (strokeColor = clCurrent) then drawDat.strokeColor := thisElement.fReader.currentColor else if strokeColor <> clInvalid then drawDat.strokeColor := strokeColor; if strokeOpacity <> InvalidD then drawDat.strokeOpacity := strokeOpacity; if strokeWidth.IsValid then drawDat.strokeWidth := strokeWidth; if strokeCap = esPolygon then drawDat.strokeCap := strokeCap; if strokeJoin = jsAuto then drawDat.strokeJoin := strokeJoin; if strokeMitLim > 0 then drawDat.strokeMitLim := strokeMitLim; if Assigned(dashArray) then drawDat.dashArray := Copy(dashArray, 0, Length(dashArray)); if dashOffset <> 0 then drawDat.dashOffset := dashOffset; if (strokeEl <> '') then drawDat.strokeEl := strokeEl; if opacity < MaxInt then drawDat.opacity := opacity; if (clipElRef <> '') then drawDat.clipElRef := clipElRef; if (maskElRef <> '') then drawDat.maskElRef := maskElRef; if (filterElRef <> '') then drawDat.filterElRef := filterElRef; if fontInfo.family <> ttfUnknown then drawDat.fontInfo.family := fontInfo.family; if fontInfo.size > 0 then drawDat.fontInfo.size := fontInfo.size; if fontInfo.spacing <> 0 then drawDat.fontInfo.spacing := fontInfo.spacing; if fontInfo.textLength > 0 then drawDat.fontInfo.textLength := fontInfo.textLength; if (fontInfo.italic <> sfsUndefined) then drawDat.fontInfo.italic := fontInfo.italic; if (fontInfo.weight <> -1) then drawDat.fontInfo.weight := fontInfo.weight; if fontInfo.align <> staUndefined then drawDat.fontInfo.align := fontInfo.align; if (thisElement is TTextElement) or (fontInfo.decoration <> fdUndefined) then drawDat.fontInfo.decoration := fontInfo.decoration; if fontInfo.baseShift.IsValid then drawDat.fontInfo.baseShift := fontInfo.baseShift; if not IsIdentityMatrix(matrix) then drawDat.matrix := MatrixMultiply(drawDat.matrix, matrix); end; end; //------------------------------------------------------------------------------ function IsFilled(const drawDat: TDrawData): Boolean; begin with drawDat do Result := (fillOpacity <> 0) and ((fillColor <> clNone32) or (fillEl <> '')); end; //------------------------------------------------------------------------------ function IsStroked(const drawDat: TDrawData): Boolean; begin with drawDat do if (strokeOpacity = 0) then Result := false else if (strokeEl <> '') then Result := ((strokeWidth.rawVal = InvalidD) or (strokeWidth.rawVal > 0)) else if (strokeColor = clNone32) or ((strokeColor = clInvalid) and (strokeWidth.rawVal = InvalidD)) then Result := false else Result := ((strokeWidth.rawVal = InvalidD) or (strokeWidth.rawVal > 0)); end; //------------------------------------------------------------------------------ function MergeColorAndOpacity(color: TColor32; opacity: double): TColor32; begin if (opacity < 0) or (opacity >= 1.0) then Result := color else if opacity = 0 then Result := clNone32 else Result := (color and $FFFFFF) + Round(opacity * 255) shl 24; end; //------------------------------------------------------------------------------ function UTF8StringToFloat(const ansiValue: UTF8String; out value: double): Boolean; var c: PUTF8Char; begin c := PUTF8Char(ansiValue); Result := ParseNextNum(c, c + Length(ansiValue), false, value); end; //------------------------------------------------------------------------------ function UTF8StringToFloatEx(const ansiValue: UTF8String; var value: double; out measureUnit: TUnitType): Boolean; var c: PUTF8Char; begin c := PUTF8Char(ansiValue); Result := ParseNextNumEx(c, c + Length(ansiValue), false, value, measureUnit); end; //------------------------------------------------------------------------------ procedure UTF8StringToOpacity(const ansiValue: UTF8String; var color: TColor32); var opacity: double; begin if color = clNone32 then begin color := clAlphaSet; Exit; end; if color = clInvalid then color := clNone32; if not UTF8StringToFloat(ansiValue, opacity) then Exit; with TARGB(color) do if (opacity <= 0) then begin if Color = clNone32 then Color := clAlphaSet else A := 0; end else if (opacity >= 1) then A := 255 else A := Round(255 * opacity); end; //------------------------------------------------------------------------------ function MatrixApply(const paths: TPathsD; const matrix: TMatrixD): TPathsD; overload; var i,j,len,len2: integer; pp,rr: PPointD; begin if not Assigned(paths) then Result := nil else if IsIdentityMatrix(matrix) then Result := CopyPaths(paths) else begin len := Length(paths); SetLength(Result, len); for i := 0 to len -1 do begin len2 := Length(paths[i]); SetLength(Result[i], len2); if len2 = 0 then Continue; pp := @paths[i][0]; rr := @Result[i][0]; for j := 0 to High(paths[i]) do begin rr.X := pp.X * matrix[0, 0] + pp.Y * matrix[1, 0] + matrix[2, 0]; rr.Y := pp.X * matrix[0, 1] + pp.Y * matrix[1, 1] + matrix[2, 1]; inc(pp); inc(rr); end; end; end; end; //------------------------------------------------------------------------------ // TDefsElement //------------------------------------------------------------------------------ constructor TDefsElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; fDrawData.visible := false; end; //------------------------------------------------------------------------------ // TGroupElement //------------------------------------------------------------------------------ procedure TGroupElement.Draw(image: TImage32; drawDat: TDrawData); var clipEl : TSvgElement; maskEl : TSvgElement; tmpImg : TImage32; clipPaths : TPathsD; clipRec : TRect; begin if fChilds.Count = 0 then Exit; UpdateDrawInfo(drawDat, self); maskEl := FindRefElement(drawDat.maskElRef); clipEl := FindRefElement(drawDat.clipElRef); if Assigned(clipEl) then begin with TClipPathElement(clipEl) do begin drawDat.clipElRef := ''; GetPaths(drawDat); clipPaths := CopyPaths(drawPathsF); MatrixApply(drawDat.matrix, clipPaths); clipRec := Img32.Vector.GetBounds(clipPaths); end; if IsEmptyRect(clipRec) then Exit; //nb: it's not safe to use fReader.TempImage when calling DrawChildren tmpImg := TImage32.Create(Image.Width, Image.Height); try DrawChildren(tmpImg, drawDat); with TClipPathElement(clipEl) do EraseOutsidePaths(tmpImg, clipPaths, fDrawData.fillRule, clipRec); image.CopyBlend(tmpImg, clipRec, clipRec, BlendToAlpha); finally tmpImg.Free; end; end else if Assigned(maskEl) then begin drawDat.maskElRef := ''; with TMaskElement(maskEl) do begin GetPaths(drawDat); clipRec := maskRec; end; tmpImg := TImage32.Create(image.Width, image.Height); try DrawChildren(tmpImg, drawDat); TMaskElement(maskEl).ApplyMask(tmpImg, drawDat); image.CopyBlend(tmpImg, clipRec, clipRec, BlendToAlpha); finally tmpImg.Free; end; end else DrawChildren(image, drawDat); end; //------------------------------------------------------------------------------ // TSwitchElement //------------------------------------------------------------------------------ procedure TSwitchElement.Draw(image: TImage32; drawDat: TDrawData); var i: integer; begin for i := 0 to fChilds.Count -1 do if TSvgElement(fChilds[i]) is TShapeElement then with TShapeElement(fChilds[i]) do if fDrawData.visible then begin Draw(image, drawDat); break; //break after the first successful drawing end; end; //------------------------------------------------------------------------------ // TUseElement //------------------------------------------------------------------------------ procedure TUseElement.GetPaths(const drawDat: TDrawData); var el: TSvgElement; dx, dy: double; begin if Assigned(drawPathsF) or (refEl = '') then Exit; el := FindRefElement(refEl); if not Assigned(el) or not (el is TShapeElement) then Exit; with TShapeElement(el) do begin GetPaths(drawDat); self.drawPathsC := CopyPaths(drawPathsC); self.drawPathsO := CopyPaths(drawPathsO); end; if elRectWH.left.IsValid then dx := elRectWH.left.rawVal else dx := 0; if elRectWH.top.IsValid then dy := elRectWH.top.rawVal else dy := 0; if (dx <> 0) or (dy <> 0) then begin drawPathsC := OffsetPath(drawPathsC, dx, dy); drawPathsO := OffsetPath(drawPathsO, dx, dy); end; drawPathsF := CopyPaths(drawPathsC); AppendPath(drawPathsF, drawPathsO); end; //------------------------------------------------------------------------------ function TUseElement.ValidateNonRecursion(el: TSvgElement): Boolean; begin Result := false; while assigned(el) do begin if (el = Self) then Exit; if not (el is TUseElement) then break; //shouldn't happen el := TUseElement(el).callerUse; end; Result := true; end; //------------------------------------------------------------------------------ procedure TUseElement.Draw(img: TImage32; drawDat: TDrawData); var el: TSvgElement; s, dx, dy: double; scale, scale2: TSizeD; mat: TMatrixD; begin //make sure there's not recursion, either directly or indirectly if not ValidateNonRecursion(drawDat.useEl) then Exit; callerUse := drawDat.useEl; drawDat.useEl := self; el := FindRefElement(refEl); if not Assigned(el) then Exit; UpdateDrawInfo(drawDat, self); //nb: attribs override el's. scale := ExtractScaleFromMatrix(drawDat.matrix); if elRectWH.left.IsValid then dx := elRectWH.left.rawVal else dx := 0; if elRectWH.top.IsValid then dy := elRectWH.top.rawVal else dy := 0; if (dx <> 0) or (dy <> 0) then begin mat := IdentityMatrix; MatrixTranslate(mat, dx, dy); drawDat.matrix := MatrixMultiply(drawDat.matrix, mat); end; if el is TSymbolElement then begin with TSymbolElement(el) do begin if not viewboxWH.IsEmpty then begin //scale the symbol according to its width and height attributes if elRectWH.width.IsValid and elRectWH.height.IsValid then begin scale2.cx := elRectWH.width.rawVal / viewboxWH.Width; scale2.cy := elRectWH.height.rawVal / viewboxWH.Height; if scale2.cy < scale2.cx then s := scale2.cy else s := scale2.cx; //the following 3 lines will scale without translating mat := IdentityMatrix; MatrixScale(mat, s, s); drawDat.matrix := MatrixMultiply(drawDat.matrix, mat); drawDat.bounds := RectD(0,0,viewboxWH.Width, viewboxWH.Height); end; if self.elRectWH.width.IsValid and self.elRectWH.height.IsValid then begin with viewboxWH do begin dx := -Left/Width * self.elRectWH.width.rawVal; dy := -Top/Height * self.elRectWH.height.rawVal; //scale proportionally to fill the element scale2.cx := self.elRectWH.width.rawVal / Width; scale2.cy := self.elRectWH.height.rawVal / Height; if scale2.cy < scale2.cx then s := scale2.cy else s := scale2.cx; end; mat := IdentityMatrix; MatrixScale(mat, s, s); MatrixTranslate(mat, dx, dy); drawDat.matrix := MatrixMultiply(drawDat.matrix, mat); //now center after scaling if scale2.cx > scale2.cy then begin if scale2.cx > 1 then begin s := (self.elRectWH.width.rawVal - viewboxWH.Width) * 0.5; MatrixTranslate(drawDat.matrix, s * scale.cx, 0); end; end else if scale2.cy > 1 then begin s := (self.elRectWH.height.rawVal - viewboxWH.Height) * 0.5; MatrixTranslate(drawDat.matrix, 0, s * scale.cy); end; end; end; DrawChildren(img, drawDat); end; end else if el is TShapeElement then el.Draw(img, drawDat); end; //------------------------------------------------------------------------------ // TMaskElement //------------------------------------------------------------------------------ procedure TMaskElement.GetPaths(const drawDat: TDrawData); var i : integer; el : TShapeElement; begin maskRec := NullRect; for i := 0 to fChilds.Count -1 do if TSvgElement(fChilds[i]) is TShapeElement then begin el := TShapeElement(fChilds[i]); el.GetPaths(drawDat); maskRec := Img32.Vector.UnionRect(maskRec, Img32.Vector.GetBounds(el.drawPathsF)); end; MatrixApply(drawDat.matrix, maskRec); end; //------------------------------------------------------------------------------ procedure TMaskElement.ApplyMask(img: TImage32; const drawDat: TDrawData); var tmpImg: TImage32; begin tmpImg := TImage32.Create(img.Width, img.Height); try DrawChildren(tmpImg, drawDat); img.CopyBlend(tmpImg, maskRec, maskRec, BlendBlueChannel); finally tmpImg.Free; end; end; //------------------------------------------------------------------------------ // TSymbolElement //------------------------------------------------------------------------------ constructor TSymbolElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; fDrawData.visible := false; end; //------------------------------------------------------------------------------ // TGradElement //------------------------------------------------------------------------------ function TGradientElement.LoadContent: Boolean; var i: integer; begin Result := inherited LoadContent; for i := 0 to fChilds.Count -1 do if TSvgElement(fChilds[i]) is TGradStopElement then with TGradStopElement(fChilds[i]) do AddStop(color, offset); end; //------------------------------------------------------------------------------ procedure TGradientElement.AddStop(color: TColor32; offset: double); var len: integer; begin //if a stop is less than previous stops, it is set equal to the largest stop. //If two stops are equal the last stop controls the color from that point. len := Length(stops); if (len > 0) and (stops[len-1].offset > offset) then offset := stops[len-1].offset; setLength(stops, len+1); stops[len].offset := Min(1,Max(0, offset)); stops[len].color := color; end; //------------------------------------------------------------------------------ procedure TGradientElement.AssignTo(other: TSvgElement); var i, len: integer; begin if not Assigned(other) or not (other is TGradientElement) then Exit; inherited; with TGradientElement(other) do begin if units = 0 then units := Self.units; if Length(stops) = 0 then begin len := Length(self.stops); SetLength(stops, len); for i := 0 to len -1 do stops[i] := Self.stops[i]; end; if IsIdentityMatrix(fDrawData.matrix) then fDrawData.matrix := self.fDrawData.matrix; end; end; //------------------------------------------------------------------------------ function TGradientElement.PrepareRenderer( renderer: TCustomGradientRenderer; drawDat: TDrawData): Boolean; var el: TSvgElement; begin if (refEl <> '') then begin el := FindRefElement(refEl); if Assigned(el) and (el is TGradientElement) then TGradientElement(el).AssignTo(self); end; Result := Length(stops) > 0; end; //------------------------------------------------------------------------------ // TRadGradElement //------------------------------------------------------------------------------ constructor TRadGradElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; radius.Init; F.Init; C.Init; end; //------------------------------------------------------------------------------ procedure TRadGradElement.AssignTo(other: TSvgElement); begin if not Assigned(other) or not (other is TGradientElement) then Exit; inherited; if other is TRadGradElement then with TRadGradElement(other) do begin if not radius.IsValid then radius := self.radius; if not C.IsValid then C := self.C; if not F.IsValid then F := self.F; end; end; //------------------------------------------------------------------------------ function TRadGradElement.PrepareRenderer(renderer: TCustomGradientRenderer; drawDat: TDrawData): Boolean; var i, hiStops: integer; cp, fp, r: TPointD; scale, scale2: TSizeD; rec2, rec3: TRectD; begin inherited PrepareRenderer(renderer, drawDat); hiStops := High(stops); Result := hiStops >= 0; if not Result then Exit; if units = hUserSpaceOnUse then rec2 := fReader.userSpaceBounds else rec2 := drawDat.bounds; if radius.IsValid then begin if radius.X.HasFontUnits then r := radius.GetPoint(drawDat.fontInfo.size, GetRelFracLimit) else r := radius.GetPoint(rec2, GetRelFracLimit); end else begin r.X := rec2.Width * 0.5; r.Y := rec2.Height * 0.5; end; scale := ExtractScaleFromMatrix(drawDat.matrix); scale2 := ExtractScaleFromMatrix(fDrawData.matrix); r := ScalePoint(r, scale.cx * scale2.cx, scale.cy * scale2.cy); if C.IsValid then begin if C.X.HasFontUnits then cp := C.GetPoint(drawDat.fontInfo.size, GetRelFracLimit) else cp := C.GetPoint(rec2, GetRelFracLimit); cp := OffsetPoint(cp, rec2.Left, rec2.Top); end else cp := rec2.MidPoint; MatrixApply(fDrawData.matrix, cp); MatrixApply(drawDat.matrix, cp); rec3 := RectD(cp.X-r.X, cp.Y-r.Y, cp.X+r.X, cp.Y+r.Y); if F.IsValid then begin if F.X.HasFontUnits then fp := F.GetPoint(drawDat.fontInfo.size, GetRelFracLimit) else fp := F.GetPoint(rec2, GetRelFracLimit); fp := OffsetPoint(fp, rec2.Left, rec2.Top); MatrixApply(fDrawData.matrix, fp); MatrixApply(drawDat.matrix, fp); end else fp := MidPoint(rec3); with renderer as TSvgRadialGradientRenderer do begin SetParameters(Rect(rec3), Point(fp), stops[0].color, stops[hiStops].color, spreadMethod); for i := 1 to hiStops -1 do with stops[i] do renderer.InsertColorStop(offset, color); end; end; //------------------------------------------------------------------------------ // TLinGradElement //------------------------------------------------------------------------------ constructor TLinGradElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; startPt.Init; endPt.Init; end; //------------------------------------------------------------------------------ procedure TLinGradElement.AssignTo(other: TSvgElement); begin if not Assigned(other) or not (other is TGradientElement) then Exit; inherited; if other is TLinGradElement then with TLinGradElement(other) do begin if not startPt.IsValid then startPt := self.startPt; if not endPt.IsValid then endPt := self.endPt; end; end; //------------------------------------------------------------------------------ function TLinGradElement.PrepareRenderer( renderer: TCustomGradientRenderer; drawDat: TDrawData): Boolean; var pt1, pt2: TPointD; i, hiStops: integer; rec2: TRectD; begin inherited PrepareRenderer(renderer, drawDat); hiStops := High(stops); Result := (hiStops >= 0); if not Result then Exit; //w3c-coords-units-01-b.svg //if gradientUnits=objectBoundingBox (default) then all values must be //percentages. Also... when the object's bounding box is not square, the //gradient may render non-perpendicular relative to the gradient vector //unless the gradient vector is vertical or horizontal. //https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits if units = hUserSpaceOnUse then rec2 := fReader.userSpaceBounds else rec2 := drawDat.bounds; with TLinearGradientRenderer(renderer) do begin if startPt.X.HasFontUnits then pt1 := startPt.GetPoint(drawDat.fontInfo.size, GetRelFracLimit) else pt1 := startPt.GetPoint(rec2, GetRelFracLimit); if (startPt.X.unitType <> utPixel) or (units <> hUserSpaceOnUse) then pt1.X := pt1.X + rec2.Left; if (startPt.Y.unitType <> utPixel) or (units <> hUserSpaceOnUse) then pt1.Y := pt1.Y + rec2.Top; MatrixApply(fDrawData.matrix, pt1); MatrixApply(drawDat.matrix, pt1); if not endPt.X.IsValid then pt2.X := rec2.Width else pt2.X := endPt.X.GetValue(rec2.Width, GetRelFracLimit); pt2.Y := endPt.Y.GetValue(rec2.Height, GetRelFracLimit); pt2 := OffsetPoint(pt2, rec2.Left, rec2.Top); MatrixApply(fDrawData.matrix, pt2); MatrixApply(drawDat.matrix, pt2); if (units <> hUserSpaceOnUse) and ((pt2.X <> pt1.X) or (pt2.Y <> pt1.Y)) then begin //skew the gradient end; SetParameters(pt1, pt2, stops[0].color, stops[hiStops].color, spreadMethod); for i := 1 to hiStops -1 do with stops[i] do renderer.InsertColorStop(offset, color); end; end; //------------------------------------------------------------------------------ // TGradStopElement //------------------------------------------------------------------------------ constructor TGradStopElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; color := clBlack32; end; //------------------------------------------------------------------------------ // TFilterElement //------------------------------------------------------------------------------ constructor TFilterElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; fDrawData.visible := false; elRectWH.Init; end; //------------------------------------------------------------------------------ destructor TFilterElement.Destroy; begin Clear; inherited; end; //------------------------------------------------------------------------------ procedure TFilterElement.Clear; var i: integer; begin for i := 0 to High(fImages) do fImages[i].Free; fImages := nil; fNames := nil; fLastImg := nil; end; //------------------------------------------------------------------------------ function TFilterElement.GetRelFracLimit: double; begin //always assume fractional values below 2.5 are relative Result := 2.5; end; //------------------------------------------------------------------------------ function TFilterElement.GetAdjustedBounds(const bounds: TRectD): TRectD; var recWH: TRectWH; delta: TSizeD; d: double; pt: TPointD; i: integer; hasOffset: Boolean; begin fObjectBounds := Rect(bounds); if elRectWH.IsValid then begin recWH := elRectWH.GetRectWH(bounds, GetRelFracLimit); Result.Left := bounds.Left + recWH.Left; Result.Top := bounds.Top + recWH.Top; Result.Right := Result.Left + recWH.Width; Result.Bottom := Result.Top + recWH.Height; end else begin Result := bounds; //when the filter's width and height are undefined then limit the filter //margin to 20% of the bounds when just blurring, not also offsetting. hasOffset := false; delta.cx := 0; delta.cy := 0; for i := 0 to ChildCount -1 do begin if Child[i] is TFeGaussElement then begin d := TFeGaussElement(Child[i]).stdDev * 3 * fScale; delta.cx := delta.cx + d; delta.cy := delta.cy + d; end else if Child[i] is TFeDropShadowElement then with TFeDropShadowElement(Child[i]) do begin d := stdDev * 0.75 * fScale; pt := offset.GetPoint(bounds, 1); delta.cx := delta.cx + d + Abs(pt.X) * fScale; delta.cy := delta.cy + d + Abs(pt.Y) * fScale; hasOffset := true; end else if Child[i] is TFeOffsetElement then with TFeOffsetElement(Child[i]) do begin pt := offset.GetPoint(bounds, 1); delta.cx := delta.cx + Abs(pt.X) * fScale; delta.cy := delta.cy + Abs(pt.Y) * fScale; hasOffset := true; end; end; //limit the filter margin to 20% if only blurring if not hasOffset then with delta, bounds do begin if cx > Width * 0.2 then cx := Width * 0.2; if cy > Height * 0.2 then cy := Height * 0.2; end; Img32.Vector.InflateRect(Result, delta.cx, delta.cy); end; end; //------------------------------------------------------------------------------ function TFilterElement.FindNamedImage(const name: UTF8String): TImage32; var i, len: integer; begin Result := nil; len := Length(fNames); for i := 0 to len -1 do if name = fNames[i] then begin Result := fImages[i]; Break; end; end; //------------------------------------------------------------------------------ function TFilterElement.AddNamedImage(const name: UTF8String): TImage32; var len, w, h: integer; begin len := Length(fNames); SetLength(fNames, len+1); SetLength(fImages, len+1); RectWidthHeight(fFilterBounds, w, h); Result := TImage32.Create(w, h); fImages[len] := Result; fNames[len] := name; end; //------------------------------------------------------------------------------ function TFilterElement.GetNamedImage(const name: UTF8String): TImage32; var i, len: integer; hash: Cardinal; begin hash := GetHash(name); case hash of hBackgroundImage: begin Result := FindNamedImage(name); if not Assigned(Result) then Result := AddNamedImage(name); Result.Copy(fReader.BackgndImage, fFilterBounds, Result.Bounds); Exit; end; hBackgroundAlpha: begin Result := FindNamedImage(name); if not Assigned(Result) then Result := AddNamedImage(name); Result.Copy(fReader.BackgndImage, fFilterBounds, Result.Bounds); Result.SetRGB(clNone32, Result.Bounds); Exit; end; hSourceGraphic: begin Result := FindNamedImage(name); if not Assigned(Result) then Result := AddNamedImage(name); Result.Copy(fSrcImg, fFilterBounds, Result.Bounds); Exit; end; hSourceAlpha: begin Result := FindNamedImage(name); if not Assigned(Result) then begin Result := AddNamedImage(name); Result.Copy(fSrcImg, fFilterBounds, Result.Bounds); Result.SetRGB(clNone32, Result.Bounds); end; Exit; end; end; len := Length(fNames); for i := 0 to len -1 do if name = fNames[i] then begin Result := fImages[i]; Exit; end; Result := AddNamedImage(name); end; //------------------------------------------------------------------------------ procedure TFilterElement.Apply(img: TImage32; const filterBounds: TRect; const matrix: TMatrixD); var i: integer; begin fScale := ExtractAvgScaleFromMatrix(matrix); fFilterBounds := filterBounds; Types.IntersectRect(fObjectBounds, fObjectBounds, img.Bounds); fSrcImg := img; try for i := 0 to fChilds.Count -1 do begin case TSvgElement(fChilds[i]).fParserEl.hash of hfeBlend : TFeBlendElement(fChilds[i]).Apply; hfeColorMatrix : TFeColorMatrixElement(fChilds[i]).Apply; hfeComposite : TFeCompositeElement(fChilds[i]).Apply; hfeDefuseLighting : TFeDefuseLightElement(fChilds[i]).Apply; hfeDropShadow : TFeDropShadowElement(fChilds[i]).Apply; hfeFlood : TFeFloodElement(fChilds[i]).Apply; hFeGaussianBlur : TFeGaussElement(fChilds[i]).Apply; hfeMerge : TFeMergeElement(fChilds[i]).Apply; hfeOffset : TFeOffsetElement(fChilds[i]).Apply; hfeSpecularLighting : TFeSpecLightElement(fChilds[i]).Apply; end; end; fSrcImg.Copy(fLastImg, fLastImg.Bounds, fFilterBounds); finally Clear; end; end; //------------------------------------------------------------------------------ // TFeBaseElement //------------------------------------------------------------------------------ function TFeBaseElement.GetParentAsFilterEl: TFilterElement; var el: TSvgElement; begin el := fParent; while Assigned(el) and not (el is TFilterElement) do el := el.fParent; if not Assigned(el) then Result := nil else Result := TFilterElement(el); end; //------------------------------------------------------------------------------ function TFeBaseElement.GetBounds(img: TImage32): TRect; var pfe: TFilterElement; begin pfe := ParentFilterEl; if img = pfe.fSrcImg then Result := pfe.fFilterBounds else Result := img.Bounds; end; //------------------------------------------------------------------------------ function TFeBaseElement.GetSrcAndDst: Boolean; var pfe: TFilterElement; begin pfe := ParentFilterEl; if (in1 <> '') then srcImg := pfe.GetNamedImage(in1) else if Assigned(pfe.fLastImg) then srcImg := pfe.fLastImg else srcImg := pfe.GetNamedImage(SourceImage); if (res <> '') then dstImg := pfe.GetNamedImage(res) else dstImg := pfe.GetNamedImage(SourceImage); Result := Assigned(srcImg) and Assigned(dstImg); if not Result then Exit; pfe.fLastImg := dstImg; srcRec := GetBounds(srcImg); dstRec := GetBounds(dstImg); end; //------------------------------------------------------------------------------ // TFeBlendElement //------------------------------------------------------------------------------ procedure TFeBlendElement.Apply; var pfe: TFilterElement; srcImg2, dstImg2: TImage32; srcRec2, dstRec2: TRect; begin if not GetSrcAndDst then Exit; pfe := ParentFilterEl; if (in2 = '') then Exit; if dstImg = srcImg then dstImg2 := pfe.AddNamedImage(tmpFilterImg) else dstImg2 := dstImg; dstRec2 := GetBounds(dstImg2); srcImg2 := pfe.GetNamedImage(in2); srcRec2 := GetBounds(srcImg2); dstImg2.CopyBlend(srcImg2, srcRec2, dstRec2, BlendToAlpha); dstImg2.CopyBlend(srcImg, srcRec, dstRec2, BlendToAlpha); if dstImg = srcImg then dstImg.Copy(dstImg2, dstRec2, dstRec); end; //------------------------------------------------------------------------------ // TFeCompositeElement //------------------------------------------------------------------------------ constructor TFeCompositeElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; fourKs[0] := InvalidD; fourKs[1] := InvalidD; fourKs[2] := InvalidD; fourKs[3] := InvalidD; end; //------------------------------------------------------------------------------ procedure Arithmetic(p1, p2, r: PColor32; const ks: array of byte); var c1 : PARGB absolute p1; c2 : PARGB absolute p2; res : PARGB absolute r; begin res.A := (((c1.A xor 255) * (c2.A xor 255)) shr 8) xor 255; res.R := ClampByte((ks[0] * c1.R * c2.R + ks[1] * c1.R * 255 + ks[2] * c2.R * 255 + ks[3] * 65025) shr 16); res.G := ClampByte((ks[0] * c1.G * c2.G + ks[1] * c1.G * 255 + ks[2] * c2.G * 255 + ks[3] * 65025) shr 16); res.B := ClampByte((ks[0] * c1.B * c2.B + ks[1] * c1.B * 255 + ks[2] * c2.B * 255 + ks[3] * 65025) shr 16); end; //------------------------------------------------------------------------------ procedure ArithmeticBlend(src1, src2, dst: TImage32; const recS1, recS2, recDst: TRect; const ks: TFourDoubles); var kk: array[0..3] of byte; w,h, w2,h2, w3,h3, i,j: integer; p1,p2,r: PColor32; begin RectWidthHeight(recS1, w, h); RectWidthHeight(recS2, w2, h2); RectWidthHeight(recDst, w3, h3); if (w2 <> w) or (w3 <> w) or (h2 <> h) or (h3 <> h) or (ks[0] = InvalidD) or (ks[1] = InvalidD) or (ks[2] = InvalidD) or (ks[3] = InvalidD) then Exit; for i := 0 to 3 do kk[i] := ClampByte(ks[i]*255); for i := 0 to h -1 do begin p1 := @src1.Pixels[(recS1.Top + i) * src1.Width + recS1.Left]; p2 := @src2.Pixels[(recS2.Top + i) * src2.Width + recS2.Left]; r := @dst.Pixels[(recDst.Top + i) * dst.Width + recDst.Left]; for j := 0 to w -1 do begin Arithmetic(p1, p2, r, kk); inc(p1); inc(p2); inc(r); end; end; end; //------------------------------------------------------------------------------ procedure TFeCompositeElement.Apply; var pfe: TFilterElement; srcImg2, dstImg2: TImage32; srcRec2, dstRec2: TRect; begin if not GetSrcAndDst then Exit; pfe := ParentFilterEl; if (in2 = '') then Exit; srcImg2 := pfe.GetNamedImage(in2); srcRec2 := GetBounds(srcImg2); //either filter bounds or image bounds if (dstImg = srcImg) or (dstImg = srcImg2) then dstImg2 := pfe.AddNamedImage(tmpFilterImg) else dstImg2 := dstImg; dstRec2 := GetBounds(dstImg2); //either filter bounds or image bounds case compositeOp of coIn: begin dstImg2.Copy(srcImg, srcRec, dstRec2); dstImg2.CopyBlend(srcImg2, srcRec2, dstRec2, BlendMask); end; coOut: begin dstImg2.Copy(srcImg, srcRec, dstRec2); dstImg2.CopyBlend(srcImg2, srcRec2, dstRec2, BlendInvertedMask); end; coAtop: begin dstImg2.Copy(srcImg2, srcRec2, dstRec2); dstImg2.CopyBlend(srcImg, srcRec, dstRec2, BlendToAlpha); dstImg2.CopyBlend(srcImg2, srcRec2, dstRec2, BlendMask); end; coXOR: begin dstImg2.Copy(srcImg2, srcRec2, dstRec2); dstImg2.CopyBlend(srcImg, srcRec, dstRec2, BlendToAlpha); dstImg2.CopyBlend(srcImg2, srcRec2, dstRec2, BlendInvertedMask); end; coArithmetic: begin ArithmeticBlend(srcImg, srcImg2, dstImg2, srcRec, srcRec2, dstRec2, fourKs); end; else //coOver begin dstImg2.CopyBlend(srcImg2, srcRec2, dstRec2, BlendToAlpha); dstImg2.CopyBlend(srcImg, srcRec, dstRec2, BlendToAlpha); end; end; if (dstImg <> dstImg2) then dstImg.Copy(dstImg2, dstRec2, dstRec); end; //------------------------------------------------------------------------------ // TFeColorMatrixElement //------------------------------------------------------------------------------ type TColorMatrix = array[0..19] of Byte; function ApplyColorMatrix(color: TColor32; const mat: TColorMatrix): TColor32; var clrIn : TARGB absolute color; clrOut: TARGB absolute Result; begin clrOut.R := ClampByte(MulBytes(mat[0],clrIn.R) + MulBytes(mat[1],clrIn.G) + MulBytes(mat[2],clrIn.B) + MulBytes(mat[3],clrIn.A) + mat[4]); clrOut.G := ClampByte(MulBytes(mat[5],clrIn.R) + MulBytes(mat[6],clrIn.G) + MulBytes(mat[7],clrIn.B) + MulBytes(mat[8],clrIn.A) + mat[9]); clrOut.B := ClampByte(MulBytes(mat[10],clrIn.R) + MulBytes(mat[11],clrIn.G) + MulBytes(mat[12],clrIn.B) + MulBytes(mat[13],clrIn.A) + mat[14]); clrOut.A := ClampByte(MulBytes(mat[15],clrIn.R) + MulBytes(mat[16],clrIn.G) + MulBytes(mat[17],clrIn.B) + MulBytes(mat[18],clrIn.A) + mat[19]); end; //------------------------------------------------------------------------------ procedure TFeColorMatrixElement.Apply; var i,j, dx1,dx2: integer; colorMatrix: TColorMatrix; p1, p2: PColor32; begin if not GetSrcAndDst or not Assigned(values) then Exit; for i := 0 to 19 do colorMatrix[i] := ClampByte(Round(values[i]*255)); dx1 := srcImg.Width - RectWidth(srcRec); dx2 := dstImg.Width - RectWidth(dstRec); p1 := @srcImg.Pixels[srcRec.Top * srcImg.Width + srcRec.Left]; p2 := @dstImg.Pixels[dstRec.Top * dstImg.Width + dstRec.Left]; for i := srcRec.Top to srcRec.Bottom -1 do begin for j := srcRec.Left to srcRec.Right -1 do begin p2^ := ApplyColorMatrix(p1^, colorMatrix); inc(p1); inc(p2); end; inc(p1, dx1); inc(p2, dx2); end; end; //------------------------------------------------------------------------------ // TFeDefuseLightElement //------------------------------------------------------------------------------ procedure TFeDefuseLightElement.Apply; begin //not implemented if not GetSrcAndDst then Exit; if srcImg <> dstImg then dstImg.Copy(srcImg, srcRec, dstRec); end; //------------------------------------------------------------------------------ // TFeDropShadowElement //------------------------------------------------------------------------------ constructor TFeDropShadowElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; stdDev := InvalidD; floodColor := clInvalid; offset.X.SetValue(0); offset.Y.SetValue(0); end; //------------------------------------------------------------------------------ procedure TFeDropShadowElement.Apply; var alpha: Byte; off: TPointD; dstOffRec: TRect; pfe: TFilterElement; dropShadImg: TImage32; begin if not GetSrcAndDst then Exit; pfe := ParentFilterEl; dropShadImg := pfe.GetNamedImage(tmpFilterImg); dropShadImg.Copy(srcImg, srcRec, dropShadImg.Bounds); off := offset.GetPoint(RectD(pfe.fObjectBounds), GetRelFracLimit); off := ScalePoint(off, pfe.fScale); dstOffRec := dstRec; with Point(off) do Types.OffsetRect(dstOffRec, X, Y); dstImg.Copy(srcImg, srcRec, dstOffRec); dstImg.SetRGB(floodColor); alpha := GetAlpha(floodColor); if (alpha > 0) and (alpha < 255) then dstImg.ReduceOpacity(alpha); if stdDev > 0 then FastGaussianBlur(dstImg, dstRec, Ceil(stdDev *0.75 * ParentFilterEl.fScale) , 0); dstImg.CopyBlend(dropShadImg, dropShadImg.Bounds, dstRec, BlendToAlpha); end; //------------------------------------------------------------------------------ // TFeFloodElement //------------------------------------------------------------------------------ constructor TFeFloodElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; floodColor := clInvalid; end; //------------------------------------------------------------------------------ procedure TFeFloodElement.Apply; var rec: TRect; begin if not GetSrcAndDst then Exit; if elRectWH.IsValid then rec := Rect(elRectWH.GetRectD(RectD(srcRec), GetRelFracLimit)) else rec := dstRec; dstImg.FillRect(rec, floodColor); end; //------------------------------------------------------------------------------ // TFeGaussElement //------------------------------------------------------------------------------ constructor TFeGaussElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; stdDev := InvalidD; end; //------------------------------------------------------------------------------ procedure TFeGaussElement.Apply; begin if not GetSrcAndDst then Exit; if srcImg <> dstImg then dstImg.Copy(srcImg, srcRec, dstRec); ////True GaussianBlur is visually optimal, but it's also *extremely* slow. //GaussianBlur(dstImg, dstRec, Ceil(stdDev *PI * ParentFilterEl.fScale)); //FastGaussianBlur is a very good approximation and also very much faster. //Empirically stdDev * PI/4 more closely emulates other renderers. FastGaussianBlur(dstImg, dstRec, Ceil(stdDev * PI/4 * ParentFilterEl.fScale), fReader.fBlurQuality); end; //------------------------------------------------------------------------------ // TFeMergeElement //------------------------------------------------------------------------------ procedure TFeMergeElement.Apply; var i: integer; tmpImg: TImage32; pfe: TFilterElement; begin tmpImg := nil; if not GetSrcAndDst then Exit; pfe := ParentFilterEl; for i := 0 to fChilds.Count -1 do if TSvgElement(fChilds[i]) is TFeMergeNodeElement then with TFeMergeNodeElement(fChilds[i]) do begin if not GetSrcAndDst then Continue; if Assigned(tmpImg) then tmpImg.CopyBlend(srcImg, srcRec, tmpImg.Bounds, BlendToAlpha) else if srcImg = pfe.fSrcImg then tmpImg := pfe.GetNamedImage(SourceImage) else tmpImg := srcImg; end; dstImg.Copy(tmpImg, tmpImg.Bounds, dstRec); pfe.fLastImg := dstImg; end; //------------------------------------------------------------------------------ // TFeMergeNodeElement //------------------------------------------------------------------------------ procedure TFeMergeNodeElement.Apply; begin //should never get here ;) end; //------------------------------------------------------------------------------ // TFeOffsetElement //------------------------------------------------------------------------------ procedure TFeOffsetElement.Apply; var off: TPointD; dstOffRec: TRect; tmpImg: TImage32; pfe: TFilterElement; begin if not GetSrcAndDst then Exit; pfe := ParentFilterEl; off := offset.GetPoint(RectD(pfe.fObjectBounds), GetRelFracLimit); off := ScalePoint(off, pfe.fScale); dstOffRec := dstRec; with Point(off) do Types.OffsetRect(dstOffRec, X, Y); if srcImg = dstImg then begin tmpImg := pfe.GetNamedImage(tmpFilterImg); tmpImg.Copy(srcImg, srcRec, tmpImg.Bounds); dstImg.Clear(dstRec); dstImg.Copy(tmpImg, tmpImg.Bounds, dstOffRec); end else begin dstImg.Clear(dstRec); dstImg.Copy(srcImg, srcRec, dstOffRec); end; end; //------------------------------------------------------------------------------ // TFeSpecLightElement //------------------------------------------------------------------------------ procedure TFeSpecLightElement.Apply; begin //not implemented if not GetSrcAndDst then Exit; if srcImg <> dstImg then dstImg.Copy(srcImg, srcRec, dstRec); end; //------------------------------------------------------------------------------ // TClipPathElement //------------------------------------------------------------------------------ constructor TClipPathElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; fDrawData.visible := false; end; //------------------------------------------------------------------------------ procedure TClipPathElement.GetPaths(const drawDat: TDrawData); var i: integer; begin if Assigned(drawPathsC) or Assigned(drawPathsO) then Exit; for i := 0 to fChilds.Count -1 do if TSvgElement(fChilds[i]) is TShapeElement then with TShapeElement(fChilds[i]) do begin GetPaths(drawDat); AppendPath(self.drawPathsO, drawPathsO); AppendPath(self.drawPathsC, drawPathsC); end; drawPathsF := CopyPaths(drawPathsC); AppendPath(drawPathsF, drawPathsO); end; //------------------------------------------------------------------------------ // TShapeElement //------------------------------------------------------------------------------ constructor TShapeElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; elRectWH.Init; hasPaths := true; fDrawData.visible := true; if fParserEl.name = '' then Exit; end; //------------------------------------------------------------------------------ function TShapeElement.GetBounds: TRectD; var i: integer; begin Result := NullRectD; for i := 0 to fChilds.Count -1 do if TSvgElement(fChilds[i]) is TShapeElement then Result := UnionRect(Result, TShapeElement(fChilds[i]).GetBounds); end; //------------------------------------------------------------------------------ function TShapeElement.HasMarkers: Boolean; begin Result := IsStroked(fDrawData) and ((fDrawData.markerStart <> '') or (fDrawData.markerMiddle <> '') or (fDrawData.markerEnd <> '')); end; //------------------------------------------------------------------------------ procedure TShapeElement.Draw(image: TImage32; drawDat: TDrawData); var d : double; img : TImage32; stroked : Boolean; filled : Boolean; clipRec : TRectD; clipRec2 : TRect; clipPathEl : TSvgElement; filterEl : TSvgElement; maskEl : TSvgElement; clipPaths : TPathsD; di : TDrawData; usingTempImage: Boolean; begin UpdateDrawInfo(drawDat, self); filled := IsFilled(drawDat); stroked := IsStroked(drawDat); GetPaths(drawDat); if not (filled or stroked) or not hasPaths then Exit; drawDat.bounds := GetBoundsD(drawPathsF); img := image; clipRec2 := NullRect; maskEl := FindRefElement(drawDat.maskElRef); clipPathEl := FindRefElement(drawDat.clipElRef); filterEl := FindRefElement(drawDat.filterElRef); if (drawDat.fillEl <> '') and (drawDat.fillOpacity > 0) and (drawDat.fillOpacity < 1) then drawDat.opacity := Round(drawDat.fillOpacity * 255); usingTempImage := Assigned(clipPathEl) or Assigned(filterEl) or Assigned(maskEl) or (drawDat.opacity < 255); if usingTempImage then begin img := fReader.TempImage; //get special effects bounds if Assigned(clipPathEl) then begin drawDat.clipElRef := ''; di := drawDat; with TClipPathElement(clipPathEl) do begin GetPaths(di); clipPaths := MatrixApply(drawPathsF, di.matrix); clipRec := GetBoundsD(clipPaths); end; end else if Assigned(maskEl) then begin drawDat.maskElRef := ''; with TMaskElement(maskEl) do begin GetPaths(drawDat); clipRec := RectD(maskRec); end; end else begin clipRec := drawDat.bounds; if stroked and drawDat.strokeWidth.IsValid then begin with drawDat.strokeWidth do if HasFontUnits then d := GetValue(drawDat.fontInfo.size, GetRelFracLimit) else d := GetValueXY(clipRec, GetRelFracLimit); Img32.Vector.InflateRect(clipRec, d * 0.5, d * 0.5); end; if Assigned(filterEl) then begin drawDat.filterElRef := ''; with TFilterElement(filterEl) do begin fScale := ExtractAvgScaleFromMatrix(DrawData.matrix); clipRec := GetAdjustedBounds(clipRec); end; end; MatrixApply(drawDat.matrix, clipRec); end; clipRec2 := Rect(clipRec); Types.IntersectRect(clipRec2, clipRec2, img.Bounds); if IsEmptyRect(clipRec2) then Exit; if image <> fReader.TempImage then img.Clear(clipRec2); end; if not IsValidMatrix(drawDat.matrix) then raise Exception.Create('Invalid matrix found when drawing element'); if filled then DrawFilled(img, drawDat); if stroked then begin if Assigned(drawPathsC) then DrawStroke(img, drawDat, true); if stroked and Assigned(drawPathsO) then DrawStroke(img, drawDat, false); end; if Assigned(filterEl) then with TFilterElement(filterEl) do Apply(img, clipRec2, drawDat.matrix); if drawDat.opacity < 255 then img.ReduceOpacity(drawDat.opacity, clipRec2); if Assigned(maskEl) then TMaskElement(maskEl).ApplyMask(img, drawDat) else if Assigned(clipPathEl) then with TClipPathElement(clipPathEl) do EraseOutsidePaths(img, clipPaths, fDrawData.fillRule, clipRec2); if usingTempImage and (img <> image) then image.CopyBlend(img, clipRec2, clipRec2, BlendToAlpha); //todo: enable "paint-order" to change filled/stroked/marker paint order if HasMarkers then DrawMarkers(img, drawDat); end; //------------------------------------------------------------------------------ procedure TShapeElement.DrawMarkers(img: TImage32; drawDat: TDrawData); var i,j: integer; sw: double; markerEl: TSvgElement; markerPaths: TPathsD; pt1, pt2: TPointD; di: TDrawData; begin markerPaths := GetSimplePath(drawDat); markerPaths := StripNearDuplicates(markerPaths, 0.01, false); if not Assigned(markerPaths) then Exit; MatrixApply(drawDat.matrix, markerPaths); di := emptyDrawInfo; //prepare to scale the markers by the stroke width with fDrawData.strokeWidth do if not IsValid then sw := 1 else if HasFontUnits then sw := GetValue(drawDat.fontInfo.size, GetRelFracLimit) else sw := GetValueXY(drawDat.bounds, GetRelFracLimit); MatrixScale(di.matrix, sw * ExtractAvgScaleFromMatrix(drawDat.matrix)); if (fDrawData.markerStart <> '') then begin markerEl := FindRefElement(fDrawData.markerStart); if Assigned(markerEl) and (markerEl is TMarkerElement) then with TMarkerElement(markerEl) do begin for i := 0 to High(markerPaths) do begin if Length(markerPaths[i]) < 2 then Continue; pt1 := markerPaths[i][0]; pt2 := markerPaths[i][1]; if autoStartReverse then SetEndPoint(pt1, GetAngle(pt2, pt1)) else SetEndPoint(pt1, GetAngle(pt1, pt2)); Draw(img, di); end; end; end; if (fDrawData.markerMiddle <> '') then begin markerEl := FindRefElement(fDrawData.markerMiddle); if Assigned(markerEl) and (markerEl is TMarkerElement) then with TMarkerElement(markerEl) do for i := 0 to High(markerPaths) do if SetMiddlePoints(markerPaths[i]) then Draw(img, di); end; if (fDrawData.markerEnd <> '') then begin markerEl := FindRefElement(fDrawData.markerEnd); if Assigned(markerEl) and (markerEl is TMarkerElement) then with TMarkerElement(markerEl) do begin for i := 0 to High(markerPaths) do begin j := High(markerPaths[i]); if j < 1 then Continue; pt1 := markerPaths[i][j]; pt2 := markerPaths[i][j-1]; SetEndPoint(pt1, GetAngle(pt2, pt1)); Draw(img, di); end; end; end; end; //------------------------------------------------------------------------------ procedure TShapeElement.GetPaths(const drawDat: TDrawData); begin drawPathsO := nil; drawPathsC := nil; drawPathsF := nil; end; //------------------------------------------------------------------------------ function TShapeElement.GetSimplePath(const drawDat: TDrawData): TPathsD; begin Result := nil; end; //------------------------------------------------------------------------------ procedure TShapeElement.DrawFilled(img: TImage32; drawDat: TDrawData); var refEl: TSvgElement; fillPaths: TPathsD; begin if not assigned(drawPathsF) then Exit; if drawDat.fillColor = clCurrent then drawDat.fillColor := fReader.currentColor; fillPaths := MatrixApply(drawPathsF, drawDat.matrix); if (drawDat.fillEl <> '') then begin refEl := FindRefElement(drawDat.fillEl); if Assigned(refEl) and (refEl is TFillElement) then begin if refEl is TRadGradElement then begin with TRadGradElement(refEl), fReader do if PrepareRenderer(RadGradRenderer, drawDat) then DrawPolygon(img, fillPaths, drawDat.fillRule, RadGradRenderer); end else if refEl is TLinGradElement then begin with TLinGradElement(refEl), fReader do if PrepareRenderer(LinGradRenderer, drawDat) then DrawPolygon(img, fillPaths, drawDat.fillRule, LinGradRenderer); end else if refEl is TPatternElement then begin with TPatternElement(refEl), fReader do if PrepareRenderer(ImageRenderer, drawDat) then DrawPolygon(img, fillPaths, drawDat.fillRule, ImageRenderer); end; end; end else if drawDat.fillColor = clInvalid then DrawPolygon(img, fillPaths, drawDat.fillRule, clBlack32) else with drawDat do DrawPolygon(img, fillPaths, fillRule, MergeColorAndOpacity(fillColor, fillOpacity)); end; //------------------------------------------------------------------------------ procedure TShapeElement.DrawStroke(img: TImage32; drawDat: TDrawData; isClosed: Boolean); var dashOffset, scaledStrokeWidth, roundingScale: double; dashArray: TArrayOfInteger; scale: Double; strokeClr: TColor32; strokePaths: TPathsD; refEl: TSvgElement; endStyle: TEndStyle; joinStyle: TJoinStyle; bounds: TRectD; begin if isClosed then begin strokePaths := MatrixApply(drawPathsC, drawDat.matrix); endStyle := esPolygon; end else begin strokePaths := MatrixApply(drawPathsO, drawDat.matrix); if fDrawData.strokeCap = esPolygon then endStyle := esButt else endStyle := fDrawData.strokeCap; end; if not Assigned(strokePaths) then Exit; joinStyle := fDrawData.strokeJoin; if drawDat.strokeColor = clCurrent then drawDat.strokeColor := fReader.currentColor; scale := ExtractAvgScaleFromMatrix(drawDat.matrix); bounds := fReader.userSpaceBounds; with drawDat.strokeWidth do begin if not IsValid then scaledStrokeWidth := scale else if HasFontUnits then scaledStrokeWidth := GetValue(drawDat.fontInfo.size, GetRelFracLimit) * scale else scaledStrokeWidth := GetValueXY(bounds, 0) * scale; end; roundingScale := scale; if Length(drawDat.dashArray) > 0 then dashArray := MakeDashArray(drawDat.dashArray, scale) else dashArray := nil; with drawDat do strokeClr := MergeColorAndOpacity(strokeColor, strokeOpacity); if Assigned(dashArray) then begin dashOffset := drawDat.dashOffset * scale; DrawDashedLine(img, strokePaths, dashArray, @dashOffset, scaledStrokeWidth, strokeClr, endStyle); end else if (drawDat.strokeEl <> '') then begin refEl := FindRefElement(drawDat.strokeEl); if not Assigned(refEl) then Exit; if refEl is TRadGradElement then begin with TRadGradElement(refEl) do PrepareRenderer(fReader.RadGradRenderer, drawDat); DrawLine(img, strokePaths, scaledStrokeWidth, fReader.RadGradRenderer, endStyle, joinStyle, roundingScale); end else if refEl is TLinGradElement then begin with TLinGradElement(refEl) do PrepareRenderer(fReader.LinGradRenderer, drawDat); DrawLine(img, strokePaths, scaledStrokeWidth, fReader.LinGradRenderer, endStyle, joinStyle, roundingScale); end else if refEl is TPatternElement then begin with TPatternElement(refEl) do PrepareRenderer(fReader.ImageRenderer, drawDat); DrawLine(img, strokePaths, scaledStrokeWidth, fReader.ImageRenderer, endStyle, joinStyle, roundingScale); end; end else if (joinStyle = jsMiter) then DrawLine(img, strokePaths, scaledStrokeWidth, strokeClr, endStyle, joinStyle, drawDat.strokeMitLim) else DrawLine(img, strokePaths, scaledStrokeWidth, strokeClr, endStyle, joinStyle, roundingScale); end; //------------------------------------------------------------------------------ // TPathElement //------------------------------------------------------------------------------ constructor TPathElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; fSvgPaths := TSvgPath.Create; end; //------------------------------------------------------------------------------ destructor TPathElement.Destroy; begin fSvgPaths.Free; inherited; end; //------------------------------------------------------------------------------ function TPathElement.GetBounds: TRectD; var i: integer; begin Result := NullRectD; for i := 0 to fSvgPaths.Count -1 do Result := UnionRect(Result, fSvgPaths[i].GetBounds); end; //------------------------------------------------------------------------------ procedure TPathElement.ParseDAttrib(const value: UTF8String); begin fSvgPaths.Parse(value); end; //------------------------------------------------------------------------------ procedure TPathElement.Flatten(index: integer; scalePending: double; out path: TPathD; out isClosed: Boolean); begin isClosed := fSvgPaths[index].isClosed; path := fSvgPaths[index].GetFlattenedPath(scalePending); end; //------------------------------------------------------------------------------ procedure TPathElement.GetPaths(const drawDat: TDrawData); var i: integer; scalePending: double; isClosed: Boolean; path: TPathD; begin if Assigned(drawPathsC) or Assigned(drawPathsO) then inherited; scalePending := ExtractAvgScaleFromMatrix(drawDat.matrix); for i := 0 to fSvgPaths.Count -1 do begin Flatten(i, scalePending, path, isClosed); if not Assigned(path) then Continue; if isClosed then AppendPath(drawPathsC, path) else AppendPath(drawPathsO, path); end; AppendPath(drawPathsF, drawPathsO); AppendPath(drawPathsF, drawPathsC); end; //------------------------------------------------------------------------------ function TPathElement.GetSimplePath(const drawDat: TDrawData): TPathsD; var i: integer; begin Result := nil; SetLength(Result, fSvgPaths.Count); for i := 0 to fSvgPaths.Count -1 do Result[i] := fSvgPaths[i].GetSimplePath; end; //------------------------------------------------------------------------------ // TPolyElement //------------------------------------------------------------------------------ function TPolyElement.GetBounds: TRectD; begin Result := GetBoundsD(path); end; //------------------------------------------------------------------------------ procedure TPolyElement.GetPaths(const drawDat: TDrawData); begin if Assigned(drawPathsC) or Assigned(drawPathsO) then Exit; if not Assigned(path) then Exit; if (fParserEl.hash = hPolygon) then begin AppendPath(drawPathsC, path); //hPolygon drawPathsF := drawPathsC; end else begin AppendPath(drawPathsO, path); //hPolyline drawPathsF := drawPathsO; end; end; //------------------------------------------------------------------------------ function TPolyElement.GetSimplePath(const drawDat: TDrawData): TPathsD; begin Result := nil; AppendPath(Result, path); end; //------------------------------------------------------------------------------ procedure TPolyElement.ParsePoints(const value: UTF8String); var currCnt, currCap: integer; procedure AddPoint(const pt: TPointD); begin if currCnt = currCap then begin currCap := currCap + buffSize; SetLength(path, currCap); end; path[currCnt] := pt; inc(currCnt); end; var pt: TPointD; c, endC: PUTF8Char; begin currCnt := 0; currCap := buffSize; c := PUTF8Char(value); endC := c + Length(value); SetLength(path, currCap); while IsNumPending(c, endC, true) and ParseNextNum(c, endC, true, pt.X) and ParseNextNum(c, endC, true, pt.Y) do AddPoint(pt); SetLength(path, currCnt); end; //------------------------------------------------------------------------------ // TLineElement //------------------------------------------------------------------------------ constructor TLineElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; SetLength(path, 2); path[0] := NullPointD; path[1] := NullPointD; end; //------------------------------------------------------------------------------ function TLineElement.GetBounds: TRectD; begin Result := GetBoundsD(path); end; //------------------------------------------------------------------------------ procedure TLineElement.GetPaths(const drawDat: TDrawData); begin if Assigned(drawPathsO) then Exit; AppendPath(drawPathsO, path); drawPathsF := drawPathsO; end; //------------------------------------------------------------------------------ function TLineElement.GetSimplePath(const drawDat: TDrawData): TPathsD; begin Result := nil; AppendPath(Result, path); end; //------------------------------------------------------------------------------ // TCircleElement //------------------------------------------------------------------------------ constructor TCircleElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; centerPt.Init; radius.Init; end; //------------------------------------------------------------------------------ function TCircleElement.GetBounds: TRectD; var cp : TPointD; r : double; begin Result := NullRectD; if not radius.IsValid then Exit; r := radius.GetValue(1, GetRelFracLimit); cp := centerPt.GetPoint(NullRectD, GetRelFracLimit); Result := RectD(cp.X -r, cp.Y -r, cp.X +r, cp.Y +r); end; //------------------------------------------------------------------------------ procedure TCircleElement.GetPaths(const drawDat: TDrawData); var scalePending : double; rec : TRectD; pt : TPointD; path : TPathD; r: double; begin if Assigned(drawPathsC) then inherited; if not radius.IsValid then Exit; r := radius.GetValueXY(drawDat.bounds, GetRelFracLimit); pt := centerPt.GetPoint(drawDat.bounds, GetRelFracLimit); scalePending := ExtractAvgScaleFromMatrix(drawDat.matrix); rec := RectD(pt.X -r, pt.Y -r, pt.X +r, pt.Y +r); path := Ellipse(rec, scalePending); AppendPath(drawPathsC, path); drawPathsF := drawPathsC; end; //------------------------------------------------------------------------------ // TEllipseElement //------------------------------------------------------------------------------ constructor TEllipseElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; centerPt.Init; radius.Init; end; //------------------------------------------------------------------------------ function TEllipseElement.GetBounds: TRectD; var cp : TPointD; r : TPointD; begin Result := NullRectD; if not radius.IsValid then Exit; r := radius.GetPoint(NullRectD, GetRelFracLimit); cp := centerPt.GetPoint(NullRectD, GetRelFracLimit); Result := RectD(cp.X -r.X, cp.Y -r.Y, cp.X +r.X, cp.Y +r.X); end; //------------------------------------------------------------------------------ procedure TEllipseElement.GetPaths(const drawDat: TDrawData); var scalePending : double; rec : TRectD; path : TPathD; rad : TPointD; centPt : TPointD; begin if Assigned(drawPathsC) then inherited; rad := radius.GetPoint(drawDat.bounds, GetRelFracLimit); centPt := centerPt.GetPoint(drawDat.bounds, GetRelFracLimit); with centPt do rec := RectD(X -rad.X, Y -rad.Y, X +rad.X, Y +rad.Y); scalePending := ExtractAvgScaleFromMatrix(drawDat.matrix); path := Ellipse(rec, scalePending); AppendPath(drawPathsC, path); drawPathsF := drawPathsC; end; //------------------------------------------------------------------------------ // TRectElement //------------------------------------------------------------------------------ constructor TRectElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; radius.Init; elRectWH.width.SetValue(100, utPercent); elRectWH.height.SetValue(100, utPercent); end; //------------------------------------------------------------------------------ function TRectElement.GetBounds: TRectD; begin Result := elRectWH.GetRectD(NullRectD, GetRelFracLimit); end; //------------------------------------------------------------------------------ procedure TRectElement.GetPaths(const drawDat: TDrawData); var radXY : TPointD; bounds: TRectD; path : TPathD; begin if Assigned(drawPathsC) then Exit; if elRectWH.width.HasFontUnits then bounds := elRectWH.GetRectD(drawDat.fontInfo.size, GetRelFracLimit) else bounds := elRectWH.GetRectD(drawDat.bounds, GetRelFracLimit); if bounds.IsEmpty then Exit; radXY := radius.GetPoint(bounds, GetRelFracLimit); if (radXY.X > 0) or (radXY.Y > 0) then begin if (radXY.X <= 0) then radXY.X := radXY.Y else if (radXY.Y <= 0) then radXY.Y := radXY.X; path := RoundRect(bounds, radXY); end else path := Rectangle(bounds); AppendPath(drawPathsC, path); drawPathsF := drawPathsC; end; //------------------------------------------------------------------------------ function TRectElement.GetSimplePath(const drawDat: TDrawData): TPathsD; var rec: TRectD; begin Result := nil; rec := elRectWH.GetRectD(drawDat.bounds, GetRelFracLimit); if not rec.IsEmpty then AppendPath(Result, Rectangle(rec)); end; //------------------------------------------------------------------------------ // TTextElement //------------------------------------------------------------------------------ constructor TTextElement.Create(parent: TSvgElement; svgEl: TSvgTreeEl); begin inherited; offset.Init; hasPaths := false; end; //------------------------------------------------------------------------------ function TTextElement.LoadContent: Boolean; var i : integer; svgEl : TSvgTreeEl; elClass : TElementClass; el : TSvgElement; begin Result := false; for i := 0 to fParserEl.childs.Count -1 do begin svgEl := TSvgTreeEl(fParserEl.childs[i]); if svgEl.hash = 0 then begin el := TSubtextElement.Create(self, svgEl); Self.fChilds.Add(el); if svgEl.text <> '' then TSubtextElement(el).text := svgEl.text; end else begin elClass := HashToElementClass(svgEl.hash); if elClass = TSvgElement then Continue; el := elClass.Create(self, svgEl); Self.fChilds.Add(el); el.LoadAttributes; if not el.LoadContent then Exit; //error end; end; Result := true; end; //------------------------------------------------------------------------------ function TTextElement.GetTopTextElement: TTextElement; var el: TSvgElement; begin el := self; while Assigned(el.fParent) and (el.fParent is TTextElement) do el := el.fParent; Result := TTextElement(el); end; //------------------------------------------------------------------------------ procedure TTextElement.DoOffsetX(dx: double); var i: integer; begin for i := 0 to fChilds.Count -1 do if TSvgElement(fChilds[i]) is TTextElement then TTextElement(fChilds[i]).DoOffsetX(dx) else if TSvgElement(fChilds[i]) is TSubTextElement then with TSubTextElement(fChilds[i]) do begin drawPathsC := OffsetPath(drawPathsC, dx, 0); drawPathsO := OffsetPath(drawPathsO, dx, 0); drawPathsF := OffsetPath(drawPathsF, dx, 0); end; end; //------------------------------------------------------------------------------ procedure TTextElement.GetPaths(const drawDat: TDrawData); var i : integer; el : TSvgElement; di : TDrawData; topTextEl : TTextElement; begin di := drawDat; if self <> GetTopTextElement then UpdateDrawInfo(di, self); if Self is TTSpanElement then begin el := fParent; while (el is TTSpanElement) do el := el.fParent; if not (el is TTextElement) then Exit; //ie error (eg