unit Img32.SVG.Reader; (******************************************************************************* * Author : Angus Johnson * * Version : 4.8 * * Date : 12 January 2025 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2019-2025 * * * * 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, {$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 TBaseElement = class; TElementClass = class of TBaseElement; 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; matrix : TMatrixD; visible : Boolean; useEl : TBaseElement; // to check for and prevent recursion bounds : TRectD; end; PSvgIdNameHashMapItem = ^TSvgIdNameHashMapItem; TSvgIdNameHashMapItem = record Hash: Cardinal; Next: Integer; Name: UTF8String; Element: TBaseElement; end; TSvgIdNameHashMap = class(TObject) private FItems: array of TSvgIdNameHashMapItem; FBuckets: TArrayOfInteger; FCount: Integer; FMod: Cardinal; procedure Grow; function FindItemIndex(const Name: UTF8String): Integer; public procedure AddOrIgnore(const idName: UTF8String; element: TBaseElement); function FindElement(const idName: UTF8String): TBaseElement; procedure Clear; end; TSvgReader = class; TBaseElement = class private fParent : TBaseElement; fXmlEl : TSvgXmlEl; fSvgReader : TSvgReader; {$IFDEF XPLAT_GENERICS} fChilds : TList; {$ELSE} fChilds : TList; {$ENDIF} fId : UTF8String; fDrawData : TDrawData; // currently both static and dynamic vars function FindRefElement(const refname: UTF8String): TBaseElement; function GetChildCount: integer; function GetChild(index: integer): TBaseElement; function FindChild(const idName: UTF8String): TBaseElement; 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; const drawDat: TDrawData); public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); virtual; destructor Destroy; override; property Child[index: integer]: TBaseElement read GetChild; default; property ChildCount: integer read GetChildCount; property DrawData: TDrawData read fDrawData write fDrawData; property Id: UTF8String read fId; end; TShapeElement = class(TBaseElement) protected hasPaths : Boolean; pathsLoaded : Boolean; drawPathsO : TPathsD; //open only drawPathsC : TPathsD; //closed only function GetBounds: TRectD; virtual; function HasMarkers: Boolean; procedure GetPaths(const drawDat: TDrawData); virtual; // GetSimplePath: is only required for markers function GetSimplePath(const drawDat: TDrawData): TPathsD; virtual; procedure DrawFilled(img: TImage32; const paths: TPathsD; drawDat: TDrawData); procedure DrawStroke(img: TImage32; const paths: TPathsD; drawDat: TDrawData; isClosed: Boolean); procedure DrawMarkers(img: TImage32; drawDat: TDrawData); procedure Draw(image: TImage32; drawDat: TDrawData); override; public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); override; end; TSvgElement = class(TShapeElement) protected procedure Draw(image: TImage32; drawDat: TDrawData); override; public viewboxWH : TRectWH; function Width : TValue; function Height : TValue; end; TSvgReader = class private fSvgParser : TSvgParser; fBkgndColor : TColor32; fBackgndImage : TImage32; fTempImage : TImage32; fBlurQuality : integer; fIdList : TSvgIdNameHashMap; fLinGradRenderer : TLinearGradientRenderer; fRadGradRenderer : TSvgRadialGradientRenderer; fCustomRendererCache: TCustomRendererCache; fRootElement : TSvgElement; fFontCache : TFontCache; fUsePropScale : Boolean; fSimpleDraw : Boolean; fSimpleDrawList : TList; function LoadInternal: Boolean; function GetIsEmpty: Boolean; function GetTempImage: TImage32; procedure SetBlurQuality(quality: integer); protected userSpaceBounds : TRectD; currentColor : TColor32; procedure GetBestFont(const svgFontInfo: TSVGFontInfo); property RadGradRenderer: TSvgRadialGradientRenderer read fRadGradRenderer; property LinGradRenderer: TLinearGradientRenderer read fLinGradRenderer; property BackgndImage : TImage32 read fBackgndImage; property TempImage : TImage32 read GetTempImage; public constructor Create; destructor Destroy; override; procedure Clear; 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): TBaseElement; 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 : TSvgElement read fRootElement; // RecordSimpleDraw: record simple drawing instructions property RecordSimpleDraw: Boolean read fSimpleDraw write fSimpleDraw; // SimpleDrawList: list of PSimpleDrawData records; property SimpleDrawList : TList read fSimpleDrawList; end; PSimpleDrawData = ^TSimpleDrawData; TSimpleDrawData = record paths : TPathsD; fillRule : TFillRule; color : TColor32; tag : integer; end; var // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/width defaultSvgWidth: integer = 300; // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/height defaultSvgHeight: integer = 150; implementation uses Img32.Extra, Img32.Clipper2; type TFourDoubles = array [0..3] of double; TDefsElement = class(TBaseElement) public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); override; end; TStyleElement = class(TBaseElement) public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); override; end; // TImageElement only supports *embedded* jpg & png images. // And it requires Img32.Fmt.JPG & Img32.Fmt.PNG to be included // in the USES clause of at least one of the application's units. // (nb: If using the FMX framework, then add Img32.FMX instead of // Img32.Fmt.JPG & Img32.Fmt.PNG to the USES clause.) TImageElement = class(TBaseElement) private fRefEl: UTF8String; fImage: TImage32; fTransparent: Boolean; protected procedure Draw(image: TImage32; drawDat: TDrawData); override; public destructor Destroy; 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: TBaseElement; function ValidateNonRecursion(el: TBaseElement): Boolean; protected fRefEl: 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: TBaseElement; svgEl: TSvgXmlEl); 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: TBaseElement; svgEl: TSvgXmlEl); 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: TBaseElement; svgEl: TSvgXmlEl); override; end; TCircleElement = class(TShapeElement) protected bounds : TRectD; centerPt : TValuePt; radius : TValue; function GetBounds: TRectD; override; procedure GetPaths(const drawDat: TDrawData); override; public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); override; end; TEllipseElement = class(TShapeElement) protected bounds : TRectD; centerPt : TValuePt; radius : TValuePt; function GetBounds: TRectD; override; procedure GetPaths(const drawDat: TDrawData); override; public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); 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: TBaseElement; svgEl: TSvgXmlEl); override; end; TTSpanElement = class; // TTextElement: although a TShapeElement descendant, it's really just // a container for other TShapeElements (, etc). TTextElement = class(TShapeElement) protected offset : TValuePt; textDx : double; angle : TArrayOfDouble; currentPt : TPointD; currSpanEl : TTSpanElement; //the current 'real' lastChrSpc : Boolean; procedure Draw(img: TImage32; drawDat: TDrawData); override; public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); override; end; TTextSubElement = class(TShapeElement) protected offset : TValuePt; textEl : TTextElement; function GetTextEl: TTextElement; end; TTSpanElement = class(TTextSubElement) protected chunkDx : double; angle : TArrayOfDouble; procedure GetPaths(const drawDat: TDrawData); override; public procedure Draw(image: TImage32; drawDat: TDrawData); override; end; TTextPathElement = class(TTextSubElement) private pathEl: TPathElement; scale: double; protected pathName : UTF8String; //name (id) of path element procedure GetPathsInternal(el: TBaseElement; const drawDat: TDrawData); procedure GetPaths(const drawDat: TDrawData); override; function GetBounds: TRectD; override; public procedure Draw(image: TImage32; drawDat: TDrawData); override; end; TTextAreaElement = class(TShapeElement) protected 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: TBaseElement; svgEl: TSvgXmlEl); override; end; TSvgColorStop = record offset : double; color : TColor32; end; TSvgColorStops = array of TSvgColorStop; TFillElement = class(TBaseElement) protected refEl : UTF8String; units : Cardinal; function GetRelFracLimit: double; override; end; TPatternElement = class(TFillElement) protected ImgRenderer : TImageRenderer; pattBoxWH : TRectWH; function PrepareRenderer(renderer: TImageRenderer; drawDat: TDrawData): Boolean; virtual; public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); override; destructor Destroy; 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: TBaseElement); virtual; function PrepareRenderer(renderer: TCustomGradientRenderer; drawDat: TDrawData): Boolean; virtual; procedure AddColorStopsToRenderer(renderer: TCustomGradientRenderer); end; TRadGradElement = class(TGradientElement) protected radius: TValuePt; F, C: TValuePt; procedure AssignTo(other: TBaseElement); override; function PrepareRenderer(renderer: TCustomGradientRenderer; drawDat: TDrawData): Boolean; override; public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); override; end; TLinGradElement = class(TGradientElement) protected startPt, endPt: TValuePt; procedure AssignTo(other: TBaseElement); override; function PrepareRenderer(renderer: TCustomGradientRenderer; drawDat: TDrawData): Boolean; override; public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); override; end; TGradStopElement = class(TBaseElement) protected offset: double; color: TColor32; public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); override; end; TFilterElement = class(TBaseElement) 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; isIn: Boolean): TImage32; procedure Apply(img: TImage32; const filterBounds: TRect; const matrix: TMatrixD); public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); override; destructor Destroy; override; end; TFeBaseElement = class(TBaseElement) 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; TFeImageElement = class(TFeBaseElement) private refEl: UTF8String; fImage: TImage32; protected procedure Apply; override; public destructor Destroy; 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: TBaseElement; svgEl: TSvgXmlEl); override; end; TFeColorMatrixElement = class(TFeBaseElement) protected values: TArrayOfDouble; procedure Apply; override; end; TFuncType = (ftIdentity, ftTable, ftDiscrete, ftLinear, ftGamma); TFeComponentTransferElement = class(TFeBaseElement) protected procedure Apply; override; end; TFeComponentTransferChild = class(TBaseElement) protected bytes: TArrayOfByte; protected funcType: TFuncType; intercept: double; slope: double; tableValues: TArrayOfDouble; end; TFeFuncRElement = class(TFeComponentTransferChild) end; TFeFuncGElement = class(TFeComponentTransferChild) end; TFeFuncBElement = class(TFeComponentTransferChild) end; TFeFuncAElement = class(TFeComponentTransferChild) 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: TBaseElement; svgEl: TSvgXmlEl); override; end; TFeFloodElement = class(TFeBaseElement) protected floodColor : TColor32; procedure Apply; override; public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); override; end; TFeGaussElement = class(TFeBaseElement) protected stdDev: double; procedure Apply; override; public constructor Create(parent: TBaseElement; svgEl: TSvgXmlEl); 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: TBaseElement; svgEl: TSvgXmlEl); 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: frNegative; fillEl: ''; strokeColor: clInvalid; strokeOpacity: InvalidD; strokeWidth: (rawVal: InvalidD; unitType: utNumber); strokeCap: esPolygon; strokeJoin: jsMiter; strokeMitLim: 0.0; strokeEl : ''; dashArray: nil; dashOffset: 0; fontInfo: (family: tfUnknown; familyNames: nil; size: 0; spacing: 0.0; spacesInText: sitUndefined; textLength: 0; italic: sfsUndefined; weight: -1; align: staUndefined; decoration: fdUndefined; baseShift: (rawVal: InvalidD; unitType: utNumber)); markerStart: ''; markerMiddle: ''; markerEnd: ''; filterElRef: ''; maskElRef: ''; clipElRef: ''; 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; hFeComponentTransfer : Result := TFeComponentTransferElement; hFeFuncR : Result := TFeFuncRElement; hFeFuncG : Result := TFeFuncGElement; hFeFuncB : Result := TFeFuncBElement; hFeFuncA : Result := TFeFuncAElement; hfeComposite : Result := TFeCompositeElement; hfeDefuseLighting : Result := TFeDefuseLightElement; hfeDropShadow : Result := TFeDropShadowElement; hfeFlood : Result := TFeFloodElement; hFeGaussianBlur : Result := TFeGaussElement; hFeImage : Result := TFeImageElement; hfeMerge : Result := TFeMergeElement; hfeMergeNode : Result := TFeMergeNodeElement; hfeOffset : Result := TFeOffsetElement; hfePointLight : Result := TFePointLightElement; hfeSpecularLighting : Result := TFeSpecLightElement; hG : Result := TGroupElement; hImage : Result := TImageElement; 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; hStyle : Result := TStyleElement; hSvg : Result := TSvgElement; hSwitch : Result := TSwitchElement; hSymbol : Result := TSymbolElement; hText : Result := TTextElement; hTextArea : Result := TTextAreaElement; hTextPath : Result := TTextPathElement; hTSpan : Result := TTSpanElement; hUse : Result := TUseElement; else Result := TBaseElement; //use generic class end; end; //------------------------------------------------------------------------------ procedure UpdateDrawInfo(var drawDat: TDrawData; thisElement: TBaseElement); begin with thisElement.fDrawData do begin if currentColor <> clInvalid then thisElement.fSvgReader.currentColor := currentColor; if fillRule <> frNegative then drawDat.fillRule := fillRule; if (fillColor = clCurrent) then drawDat.fillColor := thisElement.fSvgReader.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.fSvgReader.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 <> jsMiter 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 (clipElRef <> '') then drawDat.clipElRef := clipElRef; if (maskElRef <> '') then drawDat.maskElRef := maskElRef; if (filterElRef <> '') then drawDat.filterElRef := filterElRef; if not IsIdentityMatrix(matrix) then MatrixMultiply2(matrix, drawDat.matrix); end; end; //------------------------------------------------------------------------------ procedure UpdateFontInfo(var drawDat: TDrawData; thisElement: TBaseElement); begin with thisElement.fDrawData do begin if fontInfo.family <> tfUnknown then begin drawDat.fontInfo.family := fontInfo.family; drawDat.fontInfo.familyNames := nil; end; if Assigned(fontInfo.familyNames) then drawDat.fontInfo.familyNames := fontInfo.familyNames; 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 fontInfo.spacesInText <> sitUndefined then drawDat.fontInfo.spacesInText := fontInfo.spacesInText; if (thisElement is TTextElement) or (fontInfo.decoration <> fdUndefined) then drawDat.fontInfo.decoration := fontInfo.decoration; if fontInfo.baseShift.IsValid then drawDat.fontInfo.baseShift := fontInfo.baseShift; 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 = '') and ((strokeColor = clNone32) or (strokeColor = clInvalid)) 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 or $FF000000 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; //------------------------------------------------------------------------------ // Note: This MatrixApply() is a function, whereas in Img32.Transform it's a procedure. 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]); NewPointDArray(Result[i], len2, True); 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; //------------------------------------------------------------------------------ function FixSpaces(const text: UnicodeString; trimLeadingSpace: Boolean): UnicodeString; var i,j, len: integer; begin //changes \r\n\t chars to spaces //and trims consecutive spaces len := Length(text); SetLength(Result, len); if len = 0 then Exit; if trimLeadingSpace then begin i := 1; while (i <= len) and (text[i] <= #32) do inc(i); if i > len then begin Result := ''; Exit; end; Result[1] := text[i]; inc(i); end else begin // allow a single leading space char if text[1] <= #32 then Result[1] := #32 else Result[1] := text[1]; i := 2; end; j := 1; for i := i to len do begin if (text[i] <= #32) then begin if (Result[j] = #32) then Continue; inc(j); Result[j] := #32; end else begin inc(j); Result[j] := text[i]; end; end; SetLength(Result, j); end; //------------------------------------------------------------------------------ function IsBlankText(const text: UnicodeString): Boolean; var i: integer; begin Result := false; for i := 1 to Length(text) do if (text[i] > #32) and (text[i] <> #160) then Exit; Result := true; end; //------------------------------------------------------------------------------ function SvgTextAlignToTextAlign(svgAlign: TSvgTextAlign): TTextAlign; begin case svgAlign of staCenter: Result := taCenter; staRight: Result := taRight; staJustify: Result := taJustify; else Result := taLeft; end; end; //------------------------------------------------------------------------------ // TSvgIdNameHashMap //------------------------------------------------------------------------------ procedure TSvgIdNameHashMap.Grow; var Len, I: Integer; Index: Integer; begin Len := Length(FItems); if Len < 5 then Len := 5 else Len := Len * 2; SetLength(FItems, Len); FMod := Cardinal(Len); if not Odd(FMod) then Inc(FMod); SetLengthUninit(FBuckets, FMod); FillChar(FBuckets[0], FMod * SizeOf(FBuckets[0]), $FF); // Rehash for I := 0 to FCount - 1 do begin Index := (FItems[I].Hash and $7FFFFFFF) mod FMod; FItems[I].Next := FBuckets[Index]; FBuckets[Index] := I; end; end; //------------------------------------------------------------------------------ function TSvgIdNameHashMap.FindItemIndex(const Name: UTF8String): Integer; var hash: Cardinal; begin Result := -1; if FMod = 0 then Exit; Hash := GetHash(Name); Result := FBuckets[(Hash and $7FFFFFFF) mod FMod]; while (Result <> -1) and ((FItems[Result].Hash <> Hash) or not IsSameUTF8String(FItems[Result].Name, Name)) do Result := FItems[Result].Next; end; //------------------------------------------------------------------------------ procedure TSvgIdNameHashMap.AddOrIgnore(const idName: UTF8String; element: TBaseElement); var Index: Integer; Hash: Cardinal; Item: PSvgIdNameHashMapItem; Bucket: PInteger; begin Index := FindItemIndex(idName); if Index >= 0 then Exit; // already exists so ignore; // add new item if FCount = Length(FItems) then Grow; Index := FCount; Inc(FCount); Hash := GetHash(idName); Bucket := @FBuckets[(Hash and $7FFFFFFF) mod FMod]; Item := @FItems[Index]; Item.Next := Bucket^; Item.Hash := Hash; Item.Name := idName; Item.Element := element; Bucket^ := Index; end; //------------------------------------------------------------------------------ function TSvgIdNameHashMap.FindElement(const idName: UTF8String): TBaseElement; var Index: Integer; begin if FCount = 0 then Result := nil else begin Index := FindItemIndex(idName); if Index < 0 then Result := nil else Result := FItems[Index].Element end; end; //------------------------------------------------------------------------------ procedure TSvgIdNameHashMap.Clear; begin FCount := 0; FMod := 0; FItems := nil; FBuckets := nil; end; //------------------------------------------------------------------------------ // TDefsElement //------------------------------------------------------------------------------ constructor TDefsElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); begin inherited; fDrawData.visible := false; end; //------------------------------------------------------------------------------ // TStyleElement //------------------------------------------------------------------------------ constructor TStyleElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); begin inherited; fDrawData.visible := false; // See ParseStyleElementContent in Img32.Core. end; //------------------------------------------------------------------------------ // TImageElement //------------------------------------------------------------------------------ function TrimAnySpaces(const s: UTF8String): UTF8String; var i, j, len: integer; dst: PUTF8Char; begin len := Length(s); SetLength(Result, len); dst := PUTF8Char(Pointer(Result)); j := 0; for i := 1 to len do if s[i] > #32 then begin dst[j] := s[i]; inc(j); end; if j <> len then SetLength(Result, j); end; //------------------------------------------------------------------------------ procedure ReadRefElImage(const refEl: UTF8String; out img: TImage32); var len, offset: integer; s: UTF8String; ms: TMemoryStream; c: PUTF8Char; begin img := nil; // unfortunately white spaces are sometimes found inside encoded base64 s := TrimAnySpaces(refEl); len := Length(s); // currently only accepts **embedded** images if (len = 0) then Exit; c := PUTF8Char(s); if not Match(c, 'data:image/') then Exit; if Match(@c[11], 'jpg;base64,') then offset := 22 else if Match(@c[11], 'jpeg;base64,') then offset := 23 else if Match(@c[11], 'png;base64,') then offset := 22 else Exit; ms := TMemoryStream.Create; try if not Base64Decode(@c[offset], len -offset, ms) then Exit; img := TImage32.Create; if not img.LoadFromStream(ms) then begin FreeAndNil(img); Exit; end; finally ms.Free; end; end; //------------------------------------------------------------------------------ // TImageElement //------------------------------------------------------------------------------ destructor TImageElement.Destroy; begin if Assigned(fImage) then fImage.Free; inherited Destroy; end; //------------------------------------------------------------------------------ procedure TImageElement.Draw(image: TImage32; drawDat: TDrawData); var dstRecD: TRectD; tmp: TImage32; tmpScale: TPointD; begin dstRecD := Self.elRectWH.GetRectD(0,0); MatrixMultiply2(fDrawData.matrix, drawDat.matrix); MatrixApply(drawDat.matrix, dstRecD); if (fRefEl <> '') and not Assigned(fImage) then begin ReadRefElImage(fRefEl, fImage); if Assigned(fImage) then begin fRefEl := ''; // ie avoid reloading fImage fTransparent := fImage.HasTransparency; end; end; if fImage <> nil then begin if elRectWH.IsValid then begin tmpScale.X := elRectWH.width.rawVal / fImage.Width; tmpScale.Y := elRectWH.Height.rawVal / fImage.Height; MatrixScale(drawDat.matrix, tmpScale.X, tmpScale.Y); end; tmp := TImage32.Create(); try tmp.AssignSettings(fImage); MatrixApply(drawDat.matrix, fImage, tmp); // CopyBlend is slower than Copy, so only use it if we have a // transparent image. if fTransparent then image.CopyBlend(tmp, tmp.Bounds, Rect(dstRecD), BlendToAlphaLine) else image.Copy(tmp, tmp.Bounds, Rect(dstRecD)); finally tmp.Free; end; end; end; //------------------------------------------------------------------------------ // TGroupElement //------------------------------------------------------------------------------ procedure TGroupElement.Draw(image: TImage32; drawDat: TDrawData); var clipEl : TClipPathElement; maskEl : TMaskElement; tmpImg : TImage32; clipPaths : TPathsD; clipRec : TRect; dstClipRec: TRect; offsetX, offsetY: integer; fr: TFillRule; begin if fChilds.Count = 0 then Exit; UpdateDrawInfo(drawDat, self); UpdateFontInfo(drawDat, self); if drawDat.fillRule = frNegative then drawDat.fillRule := frNonZero; maskEl := TMaskElement(FindRefElement(drawDat.maskElRef)); clipEl := TClipPathElement(FindRefElement(drawDat.clipElRef)); if Assigned(clipEl) then begin drawDat.clipElRef := ''; with clipEl do begin GetPaths(drawDat); clipPaths := CopyPaths(drawPathsC); AppendPath(clipPaths, drawPathsO); MatrixApply(drawDat.matrix, clipPaths); clipRec := Img32.Vector.GetBounds(clipPaths); end; if IsEmptyRect(clipRec) then Exit; dstClipRec := clipRec; // save for blending tmpImg to image // Translate the clipPaths, clipRec and matrix // to minimize the size of the mask image. offsetX := clipRec.Left; offsetY := clipRec.Top; if offsetX < 0 then offsetX := 0; if offsetY < 0 then offsetY := 0; if (offsetX > 0) or (offsetY > 0) then begin MatrixTranslate(drawDat.matrix, -offsetX, -offsetY); // for DrawChildren clipPaths := TranslatePath(clipPaths, -offsetX, -offsetY); TranslateRect(clipRec, -offsetX, -offsetY); end; //nb: it's not safe to use fReader.TempImage when calling DrawChildren tmpImg := TImage32.Create(Min(image.Width, clipRec.Right), Min(image.Height, clipRec.Bottom)); try DrawChildren(tmpImg, drawDat); if clipEl.fDrawData.fillRule = frNegative then fr := frNonZero else fr := clipEl.fDrawData.fillRule; EraseOutsidePaths(tmpImg, clipPaths, fr, clipRec, fSvgReader.fCustomRendererCache); image.CopyBlend(tmpImg, clipRec, dstClipRec, BlendToAlphaLine); finally tmpImg.Free; end; end else if Assigned(maskEl) then begin drawDat.maskElRef := ''; with maskEl do begin GetPaths(drawDat); clipRec := maskRec; end; // Translate the maskRec, the matix and the clipRec to minimize // the size of the mask image. dstClipRec := clipRec; // save for blending tmpImg to image offsetX := -clipRec.Left; offsetY := -clipRec.Top; if offsetX > 0 then offsetX := 0; if offsetY > 0 then offsetY := 0; if (offsetX < 0) or (offsetY < 0) then begin MatrixTranslate(drawDat.matrix, offsetX, offsetY); // for DrawChildren TranslateRect(clipRec, offsetX, offsetY); TranslateRect(maskEl.maskRec, offsetX, offsetY); end; tmpImg := TImage32.Create(Min(image.Width, clipRec.Right), Min(image.Height, clipRec.Bottom)); try DrawChildren(tmpImg, drawDat); TMaskElement(maskEl).ApplyMask(tmpImg, drawDat); image.CopyBlend(tmpImg, clipRec, dstClipRec, BlendToAlphaLine); 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 TBaseElement(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: TBaseElement; dx, dy: double; begin if pathsLoaded or (fRefEl = '') then Exit; el := FindRefElement(fRefEl); if not Assigned(el) or not (el is TShapeElement) then Exit; pathsLoaded := true; 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 := TranslatePath(drawPathsC, dx, dy); drawPathsO := TranslatePath(drawPathsO, dx, dy); end; end; //------------------------------------------------------------------------------ function TUseElement.ValidateNonRecursion(el: TBaseElement): 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: TBaseElement; s, dx, dy: double; scale, scale2: TPointD; 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(fRefEl); if not Assigned(el) then Exit; UpdateDrawInfo(drawDat, self); //nb: attribs override el's. MatrixExtractScale(drawDat.matrix, scale.X, scale.Y); 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); MatrixMultiply2(mat, drawDat.matrix); 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.X := elRectWH.width.rawVal / viewboxWH.Width; scale2.Y := elRectWH.height.rawVal / viewboxWH.Height; if scale2.Y < scale2.X then s := scale2.Y else s := scale2.X; //the following 3 lines will scale without translating mat := IdentityMatrix; MatrixScale(mat, s, s); MatrixMultiply2(mat, drawDat.matrix); 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.X := self.elRectWH.width.rawVal / Width; scale2.Y := self.elRectWH.height.rawVal / Height; if scale2.Y < scale2.X then s := scale2.Y else s := scale2.X; end; mat := IdentityMatrix; MatrixScale(mat, s, s); MatrixTranslate(mat, dx, dy); MatrixMultiply2(mat, drawDat.matrix); //now center after scaling if scale2.X > scale2.Y then begin if scale2.X > 1 then begin s := (self.elRectWH.width.rawVal - viewboxWH.Width) * 0.5; MatrixTranslate(drawDat.matrix, s * scale.X, 0); end; end else if scale2.Y > 1 then begin s := (self.elRectWH.height.rawVal - viewboxWH.Height) * 0.5; MatrixTranslate(drawDat.matrix, 0, s * scale.Y); end; end; end; DrawChildren(img, drawDat); end; end else if el is TImageElement then el.Draw(img, drawDat) 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 TBaseElement(fChilds[i]) is TShapeElement then begin el := TShapeElement(fChilds[i]); el.GetPaths(drawDat); maskRec := Img32.Vector.UnionRect(maskRec, Img32.Vector.GetBounds(el.drawPathsC)); Img32.Vector.UnionRect(maskRec, Img32.Vector.GetBounds(el.drawPathsO)); end; MatrixApply(drawDat.matrix, maskRec); end; //------------------------------------------------------------------------------ procedure TMaskElement.ApplyMask(img: TImage32; const drawDat: TDrawData); var tmpImg: TImage32; begin tmpImg := TImage32.Create(Min(img.Width, maskRec.Right), Min(img.Height, maskRec.Bottom)); try DrawChildren(tmpImg, drawDat); img.CopyBlend(tmpImg, maskRec, maskRec, BlendBlueChannelLine); finally tmpImg.Free; end; end; //------------------------------------------------------------------------------ // TSymbolElement //------------------------------------------------------------------------------ constructor TSymbolElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); 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 TBaseElement(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: TBaseElement); 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: TBaseElement; 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; //------------------------------------------------------------------------------ procedure TGradientElement.AddColorStopsToRenderer(renderer: TCustomGradientRenderer); var i, hiStops: Integer; begin hiStops := High(stops); if (hiStops = 0) or (renderer = nil) then Exit; // If vector boundary-stops are implicit, then boundary // and adjacent inner stop (explicit) should have the // same color if stops[0].offset > 0 then with stops[0] do renderer.InsertColorStop(offset, color); for i := 1 to hiStops -1 do with stops[i] do renderer.InsertColorStop(offset, color); // If vector boundary-stops are implicit, then boundary // and adjacent inner stop (explicit) should have the // same color if stops[hiStops].offset < 1 then with stops[hiStops] do renderer.InsertColorStop(offset, color); end; //------------------------------------------------------------------------------ // TRadGradElement //------------------------------------------------------------------------------ constructor TRadGradElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); begin inherited; radius.Init; F.Init; C.Init; end; //------------------------------------------------------------------------------ procedure TRadGradElement.AssignTo(other: TBaseElement); 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 hiStops: integer; cp, fp, r: TPointD; scale, scale2: TPointD; rec2, rec3: TRectD; begin inherited PrepareRenderer(renderer, drawDat); hiStops := High(stops); Result := hiStops >= 0; if not Result then Exit; if units = hUserSpaceOnUse then rec2 := fSvgReader.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; MatrixExtractScale(drawDat.matrix, scale.X, scale.Y); MatrixExtractScale(fDrawData.matrix, scale2.X, scale2.Y); r := ScalePoint(r, scale.X * scale2.X, scale.Y * scale2.Y); if C.IsValid then begin if C.X.HasFontUnits then cp := C.GetPoint(drawDat.fontInfo.size, GetRelFracLimit) else cp := C.GetPoint(rec2, GetRelFracLimit); cp := TranslatePoint(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 := TranslatePoint(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); AddColorStopsToRenderer(renderer); end; end; //------------------------------------------------------------------------------ // TLinGradElement //------------------------------------------------------------------------------ constructor TLinGradElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); begin inherited; startPt.Init; endPt.Init; end; //------------------------------------------------------------------------------ procedure TLinGradElement.AssignTo(other: TBaseElement); 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; 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 := fSvgReader.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 := TranslatePoint(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); AddColorStopsToRenderer(renderer); end; end; //------------------------------------------------------------------------------ // TGradStopElement //------------------------------------------------------------------------------ constructor TGradStopElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); begin inherited; color := clBlack32; end; //------------------------------------------------------------------------------ // TFilterElement //------------------------------------------------------------------------------ constructor TFilterElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); 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 // assume fractional values below 2.5 are always 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; if (delta.cx = InvalidD) or (delta.cy = InvalidD) then Exit; //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; isIn: Boolean): TImage32; begin Result := FindNamedImage(name); if not Assigned(Result) then Result := AddNamedImage(name) else if not isIn then Exit; case GetHash(name) of hBackgroundImage: Result.Copy(fSvgReader.BackgndImage, fFilterBounds, Result.Bounds); hBackgroundAlpha: begin Result.Copy(fSvgReader.BackgndImage, fFilterBounds, Result.Bounds); Result.SetRGB(clNone32, Result.Bounds); end; hSourceGraphic: Result.Copy(fSrcImg, fFilterBounds, Result.Bounds); hSourceAlpha: begin Result.Copy(fSrcImg, fFilterBounds, Result.Bounds); Result.SetRGB(clBlack32, Result.Bounds); end; end; end; //------------------------------------------------------------------------------ procedure TFilterElement.Apply(img: TImage32; const filterBounds: TRect; const matrix: TMatrixD); var i: integer; begin MatrixExtractScale(matrix, fScale); fFilterBounds := filterBounds; Types.IntersectRect(fObjectBounds, fObjectBounds, img.Bounds); fSrcImg := img; try for i := 0 to fChilds.Count -1 do begin case TBaseElement(fChilds[i]).fXmlEl.hash of hfeBlend : TFeBlendElement(fChilds[i]).Apply; hfeColorMatrix : TFeColorMatrixElement(fChilds[i]).Apply; hFeComponentTransfer : TFeComponentTransferElement(fChilds[i]).Apply; hfeComposite : TFeCompositeElement(fChilds[i]).Apply; hfeDefuseLighting : TFeDefuseLightElement(fChilds[i]).Apply; hfeDropShadow : TFeDropShadowElement(fChilds[i]).Apply; hfeFlood : TFeFloodElement(fChilds[i]).Apply; hfeImage : TFeImageElement(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; if Assigned(fLastImg) then fSrcImg.Copy(fLastImg, fLastImg.Bounds, fFilterBounds); finally Clear; end; end; //------------------------------------------------------------------------------ // TFeBaseElement //------------------------------------------------------------------------------ function TFeBaseElement.GetParentAsFilterEl: TFilterElement; var el: TBaseElement; 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, true) else if Assigned(pfe.fLastImg) then srcImg := pfe.fLastImg else srcImg := pfe.GetNamedImage(SourceImage, false); if (res <> '') then dstImg := pfe.GetNamedImage(res, false) else dstImg := pfe.GetNamedImage(SourceImage, false); 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, true); srcRec2 := GetBounds(srcImg2); dstImg2.CopyBlend(srcImg2, srcRec2, dstRec2, BlendToAlphaLine); dstImg2.CopyBlend(srcImg, srcRec, dstRec2, BlendToAlphaLine); if dstImg = srcImg then dstImg.Copy(dstImg2, dstRec2, dstRec); end; //------------------------------------------------------------------------------ // TFeImageElement //------------------------------------------------------------------------------ destructor TFeImageElement.Destroy; begin fImage.Free; inherited Destroy; end; //------------------------------------------------------------------------------ procedure TFeImageElement.Apply; begin if GetSrcAndDst then begin if refEl <> '' then ReadRefElImage(refEl, fImage); // also clears refEl if fImage <> nil then dstImg.Copy(fImage, fImage.Bounds, dstRec); end; end; //------------------------------------------------------------------------------ // TFeCompositeElement //------------------------------------------------------------------------------ constructor TFeCompositeElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); 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, true); 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, BlendMaskLine); end; coOut: begin dstImg2.Copy(srcImg, srcRec, dstRec2); dstImg2.CopyBlend(srcImg2, srcRec2, dstRec2, BlendInvertedMaskLine); end; coAtop: begin dstImg2.Copy(srcImg2, srcRec2, dstRec2); dstImg2.CopyBlend(srcImg, srcRec, dstRec2, BlendToAlphaLine); dstImg2.CopyBlend(srcImg2, srcRec2, dstRec2, BlendMaskLine); end; coXOR: begin dstImg2.Copy(srcImg2, srcRec2, dstRec2); dstImg2.CopyBlend(srcImg, srcRec, dstRec2, BlendToAlphaLine); dstImg2.CopyBlend(srcImg2, srcRec2, dstRec2, BlendInvertedMaskLine); end; coArithmetic: begin ArithmeticBlend(srcImg, srcImg2, dstImg2, srcRec, srcRec2, dstRec2, fourKs); end; else //coOver begin dstImg2.CopyBlend(srcImg2, srcRec2, dstRec2, BlendToAlphaLine); dstImg2.CopyBlend(srcImg, srcRec, dstRec2, BlendToAlphaLine); 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(Integer(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; //------------------------------------------------------------------------------ // TFeComponentTransferElement //------------------------------------------------------------------------------ procedure TFeComponentTransferElement.Apply; var i,j,k, dx1,dx2: integer; d: double; rangeSize: integer; p1: PColor32; p2: PARGB; childFuncs: array[0..3] of TFeComponentTransferChild; begin if not GetSrcAndDst or (ChildCount = 0) then Exit; for i := 0 to 3 do childFuncs[i] := nil; for i := 0 to ChildCount -1 do begin if Child[i] is TFeFuncBElement then childFuncs[0] := TFeFuncBElement(Child[i]) else if Child[i] is TFeFuncGElement then childFuncs[1] := TFeFuncGElement(Child[i]) else if Child[i] is TFeFuncRElement then childFuncs[2] := TFeFuncRElement(Child[i]) else if Child[i] is TFeFuncAElement then childFuncs[3] := TFeFuncAElement(Child[i]); end; // build each childFuncs' bytes array for k := 0 to 3 do with childFuncs[k] do begin if not Assigned(childFuncs[k]) then Continue; case funcType of ftDiscrete: begin if Length(tableValues) = 0 then Continue; SetLength(bytes, 256); rangeSize := 256 div Length(tableValues); for i:= 0 to High(tableValues) do for j:= 0 to rangeSize -1 do bytes[i*rangeSize + j] := ClampByte(tableValues[i] * 255); end; ftTable: begin if Length(tableValues) < 2 then Continue; SetLength(bytes, 256); rangeSize := 256 div (Length(tableValues) -1); for i:= 0 to High(tableValues) -1 do begin intercept := tableValues[i]; slope := (tableValues[i+1] - intercept) / rangeSize; for j:= 0 to rangeSize -1 do bytes[i*rangeSize + j] := ClampByte((j * slope + intercept) * 255); end; end; ftLinear: begin SetLength(bytes, 256); d := intercept * 255; for i:= 0 to 255 do bytes[i] := ClampByte(i * slope + d); end; end; end; for k := 0 to 3 do if Assigned(childFuncs[k]) and not Assigned(childFuncs[k].bytes) then childFuncs[k] := nil; 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.Color := p1^; if Assigned(childFuncs[0]) then p2.B := childFuncs[0].bytes[p2.B]; if Assigned(childFuncs[1]) then p2.G := childFuncs[1].bytes[p2.G]; if Assigned(childFuncs[2]) then p2.R := childFuncs[2].bytes[p2.R]; if Assigned(childFuncs[3]) then p2.A := childFuncs[3].bytes[p2.A]; 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: TBaseElement; svgEl: TSvgXmlEl); 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, false); dropShadImg.Copy(srcImg, srcRec, dropShadImg.Bounds); off := offset.GetPoint(RectD(pfe.fObjectBounds), GetRelFracLimit); off := ScalePoint(off, pfe.fScale); dstOffRec := dstRec; with Point(off) do TranslateRect(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) , 1); dstImg.CopyBlend(dropShadImg, dropShadImg.Bounds, dstRec, BlendToAlphaLine); end; //------------------------------------------------------------------------------ // TFeFloodElement //------------------------------------------------------------------------------ constructor TFeFloodElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); 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: TBaseElement; svgEl: TSvgXmlEl); begin inherited; stdDev := InvalidD; end; //------------------------------------------------------------------------------ procedure TFeGaussElement.Apply; begin if (stdDev = InvalidD) or not GetSrcAndDst then Exit; if srcImg <> dstImg then dstImg.Copy(srcImg, srcRec, dstRec); //GaussianBlur(dstImg, dstRec, Round(stdDev * ParentFilterEl.fScale)); // FastGaussianBlur is a very good approximation and also much faster. // However, empirically stdDev/2 more closely emulates other renderers. FastGaussianBlur(dstImg, dstRec, Ceil(stdDev/2 * ParentFilterEl.fScale)); 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 TBaseElement(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, BlendToAlphaLine) else if srcImg = pfe.fSrcImg then tmpImg := pfe.GetNamedImage(SourceImage, false) 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 TranslateRect(dstOffRec, X, Y); if srcImg = dstImg then begin tmpImg := pfe.GetNamedImage(tmpFilterImg, false); 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: TBaseElement; svgEl: TSvgXmlEl); begin inherited; fDrawData.visible := false; end; //------------------------------------------------------------------------------ procedure TClipPathElement.GetPaths(const drawDat: TDrawData); var i: integer; begin if pathsLoaded then Exit; pathsLoaded := true; for i := 0 to fChilds.Count -1 do if TBaseElement(fChilds[i]) is TShapeElement then with TShapeElement(fChilds[i]) do begin GetPaths(drawDat); AppendPath(self.drawPathsO, drawPathsO); AppendPath(self.drawPathsC, drawPathsC); // apply child's matrix ... MatrixApply(DrawData.matrix, self.drawPathsC); MatrixApply(DrawData.matrix, self.drawPathsO); end; // apply 's matrix ... MatrixApply(DrawData.matrix, drawPathsC); MatrixApply(DrawData.matrix, drawPathsO); end; //------------------------------------------------------------------------------ // TShapeElement //------------------------------------------------------------------------------ constructor TShapeElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); begin inherited; elRectWH.Init; hasPaths := true; fDrawData.visible := true; if fXmlEl.name = '' then Exit; end; //------------------------------------------------------------------------------ function TShapeElement.GetBounds: TRectD; var i: integer; begin Result := UnionRect(GetBoundsD(drawPathsC), GetBoundsD(drawPathsO)); if Result.IsEmpty then begin Result := NullRectD; for i := 0 to fChilds.Count -1 do if TBaseElement(Child[i]) is TShapeElement then Result := UnionRect(Result, TShapeElement(Child[i]).GetBounds); end; 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; tmpRec : TRectD; clipRec : TRectD; clipRec2 : TRect; clipPathEl : TBaseElement; filterEl : TBaseElement; maskEl : TBaseElement; clipPaths : TPathsD; fillPaths : TPathsD; di : TDrawData; useTmpImage : Boolean; begin UpdateDrawInfo(drawDat, self); filled := IsFilled(drawDat); stroked := IsStroked(drawDat); GetPaths(drawDat); if not (filled or stroked) or not hasPaths then begin inherited; Exit; end; tmpRec := GetBounds; if not tmpRec.IsEmpty then drawDat.bounds := tmpRec; img := image; clipRec2 := NullRect; maskEl := FindRefElement(drawDat.maskElRef); clipPathEl := FindRefElement(drawDat.clipElRef); filterEl := FindRefElement(drawDat.filterElRef); useTmpImage := Assigned(clipPathEl) or Assigned(filterEl) or Assigned(maskEl); if useTmpImage then begin img := fSvgReader.TempImage; //get special effects bounds if Assigned(clipPathEl) then begin drawDat.clipElRef := ''; di := drawDat; with TClipPathElement(clipPathEl) do begin GetPaths(di); clipPaths := drawPathsC; AppendPath(clipPaths, drawPathsO); clipPaths := MatrixApply(clipPaths, 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 clipRec.IsEmpty and (drawDat.fontInfo.textLength > 0) and (self is TTextPathElement) then begin clipRec.Left := fParent.elRectWH.left.rawVal; clipRec.Bottom := fParent.elRectWH.top.rawVal; clipRec.Right := clipRec.Left + drawDat.fontInfo.textLength; clipRec.Top := clipRec.Bottom - drawDat.fontInfo.size; end; 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 MatrixExtractScale(DrawData.matrix, fScale); 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 <> fSvgReader.TempImage then img.Clear(clipRec2); end; if not IsValidMatrix(drawDat.matrix) then raise Exception.Create('Invalid matrix found when drawing element'); if Assigned(drawPathsC) or Assigned(drawPathsO) then begin if filled then begin // it's slightly more efficient to apply the matrix here // rather than inside DrawFilled(). fillPaths := drawPathsC; if Assigned(drawPathsO) then AppendPath(fillPaths, drawPathsO); fillPaths := MatrixApply(fillPaths, drawDat.matrix); DrawFilled(img, fillPaths, drawDat); end; if stroked then begin // it's slightly more efficient to apply the matrix // inside DrawStroke() rather than here. if Assigned(drawPathsC) then DrawStroke(img, drawPathsC, drawDat, true); if stroked and Assigned(drawPathsO) then DrawStroke(img, drawPathsO, drawDat, false); end; end; if Assigned(filterEl) then with TFilterElement(filterEl) do Apply(img, clipRec2, drawDat.matrix); if Assigned(maskEl) then TMaskElement(maskEl).ApplyMask(img, drawDat) else if Assigned(clipPathEl) then with TClipPathElement(clipPathEl) do begin if fDrawData.fillRule = frNegative then EraseOutsidePaths(img, clipPaths, frNonZero, clipRec2, fSvgReader.fCustomRendererCache) else EraseOutsidePaths(img, clipPaths, fDrawData.fillRule, clipRec2, fSvgReader.fCustomRendererCache); end; if useTmpImage and (img <> image) then image.CopyBlend(img, clipRec2, clipRec2, BlendToAlphaLine); //todo: enable "paint-order" to change filled/stroked/marker paint order if HasMarkers then DrawMarkers(img, drawDat); inherited; // DrawChildren end; //------------------------------------------------------------------------------ procedure TShapeElement.DrawMarkers(img: TImage32; drawDat: TDrawData); var i,j: integer; scale, sw: double; markerEl: TBaseElement; 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); MatrixExtractScale(drawDat.matrix, scale); MatrixScale(di.matrix, sw * scale); 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); var i: integer; begin for i := 0 to fChilds.Count -1 do if TBaseElement(fChilds[i]) is TShapeElement then TShapeElement(fChilds[i]).GetPaths(drawDat); end; //------------------------------------------------------------------------------ function TShapeElement.GetSimplePath(const drawDat: TDrawData): TPathsD; begin Result := nil; end; //------------------------------------------------------------------------------ procedure TShapeElement.DrawFilled(img: TImage32; const paths: TPathsD; drawDat: TDrawData); var refEl: TBaseElement; rec: TRect; opacity: Byte; begin if not assigned(paths) then Exit; if drawDat.fillColor = clCurrent then drawDat.fillColor := fSvgReader.currentColor; if drawDat.fillRule = frNegative then drawDat.fillRule := frNonZero; if not IsValid(drawDat.fillOpacity) then opacity := 255 else opacity := ClampByte(drawDat.fillOpacity * 255); 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) do begin fSvgReader.RadGradRenderer.Opacity := opacity; if PrepareRenderer(fSvgReader.RadGradRenderer, drawDat) then DrawPolygon(img, paths, drawDat.fillRule, fSvgReader.RadGradRenderer); end; end else if refEl is TLinGradElement then begin with TLinGradElement(refEl) do begin fSvgReader.LinGradRenderer.Opacity := opacity; if PrepareRenderer(fSvgReader.LinGradRenderer, drawDat) then DrawPolygon(img, paths, drawDat.fillRule, fSvgReader.LinGradRenderer); end; end else if refEl is TPatternElement then begin with TPatternElement(refEl) do if PrepareRenderer(ImgRenderer, drawDat) then begin rec := img32.Vector.GetBounds(paths); ImgRenderer.Offset := rec.TopLeft; DrawPolygon(img, paths, drawDat.fillRule, ImgRenderer); end; end; end; end else if drawDat.fillColor = clInvalid then begin DrawPolygon(img, paths, drawDat.fillRule, MergeColorAndOpacity(clBlack32, drawDat.fillOpacity), fSvgReader.fCustomRendererCache); end else with drawDat do begin DrawPolygon(img, paths, fillRule, MergeColorAndOpacity(fillColor, fillOpacity), fSvgReader.fCustomRendererCache); end; end; //------------------------------------------------------------------------------ procedure TShapeElement.DrawStroke(img: TImage32; const paths: TPathsD; drawDat: TDrawData; isClosed: Boolean); var i: integer; dashOffset, sw: double; dashArray: TArrayOfDouble; miterLim, scale: Double; strokeClr: TColor32; strokePaths: TPathsD; refEl: TBaseElement; endStyle: TEndStyle; joinStyle: TJoinStyle; bounds: TRectD; paths2: TPathsD; opacity: Byte; begin if not Assigned(paths) then Exit; MatrixExtractScale(drawDat.matrix, scale); joinStyle := fDrawData.strokeJoin; bounds := fSvgReader.userSpaceBounds; with drawDat.strokeWidth do begin if not IsValid then sw := 1 else if HasFontUnits then sw := GetValue(drawDat.fontInfo.size, GetRelFracLimit) else sw := GetValueXY(bounds, 0); end; miterLim := drawDat.strokeMitLim; if drawDat.strokeColor = clCurrent then drawDat.strokeColor := fSvgReader.currentColor; if Length(drawDat.dashArray) > 0 then dashArray := ScaleDashArray(drawDat.dashArray, scale) else dashArray := nil; dashOffset := drawDat.dashOffset; with drawDat do strokeClr := MergeColorAndOpacity(strokeColor, strokeOpacity); if not IsValid(drawDat.strokeOpacity) then opacity := 255 else opacity := ClampByte(drawDat.strokeOpacity * 255); if isClosed then begin if Assigned(dashArray) then begin if joinStyle = jsRound then endStyle := esRound else endStyle := esButt; dashArray := ScaleDashArray(drawDat.dashArray, 1); // ie. don't scale yet! strokePaths := nil; for i := 0 to High(paths) do begin paths2 := GetDashedPath(paths[i], true, dashArray, @dashOffset); AppendPath(strokePaths, paths2); end; strokePaths := RoughOutline(strokePaths, sw, joinStyle, endStyle, miterLim, scale); end else begin endStyle := esPolygon; strokePaths := RoughOutline(paths, sw, joinStyle, endStyle, miterLim, scale); end; end else begin if fDrawData.strokeCap = esPolygon then endStyle := esButt else endStyle := fDrawData.strokeCap; if Assigned(dashArray) then begin strokePaths := MatrixApply(paths, drawDat.matrix); DrawDashedLine(img, strokePaths, dashArray, @dashOffset, sw * scale, strokeClr, endStyle, jsAuto, fSvgReader.fCustomRendererCache); Exit; end; strokePaths := RoughOutline(paths, sw, joinStyle, endStyle, miterLim, scale); end; strokePaths := MatrixApply(strokePaths, drawDat.matrix); 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 begin fSvgReader.RadGradRenderer.Opacity := opacity; PrepareRenderer(fSvgReader.RadGradRenderer, drawDat); end; DrawPolygon(img, strokePaths, frNonZero, fSvgReader.RadGradRenderer); end else if refEl is TLinGradElement then begin with TLinGradElement(refEl) do begin fSvgReader.LinGradRenderer.Opacity := opacity; PrepareRenderer(fSvgReader.LinGradRenderer, drawDat); end; DrawPolygon(img, strokePaths, frNonZero, fSvgReader.LinGradRenderer); end else if refEl is TPatternElement then with TPatternElement(refEl) do begin imgRenderer.Opacity := opacity; PrepareRenderer(imgRenderer, drawDat); DrawLine(img, strokePaths, 1, imgRenderer, esPolygon, joinStyle, scale); DrawPolygon(img, strokePaths, frNonZero, imgRenderer); end; end else begin DrawPolygon(img, strokePaths, frNonZero, strokeClr, fSvgReader.fCustomRendererCache); end; end; //------------------------------------------------------------------------------ // TPathElement //------------------------------------------------------------------------------ constructor TPathElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); 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 pathsLoaded then Exit; pathsLoaded := true; MatrixExtractScale(drawDat.matrix, scalePending); 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; 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 pathsLoaded or not Assigned(path) then Exit; pathsLoaded := true; if (fXmlEl.hash = hPolygon) then begin AppendPath(drawPathsC, path); //hPolygon end else begin AppendPath(drawPathsO, path); //hPolyline 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: TBaseElement; svgEl: TSvgXmlEl); begin inherited; NewPointDArray(path, 2, True); path[0] := NullPointD; path[1] := NullPointD; end; //------------------------------------------------------------------------------ function TLineElement.GetBounds: TRectD; begin Result := GetBoundsD(path); end; //------------------------------------------------------------------------------ procedure TLineElement.GetPaths(const drawDat: TDrawData); begin if pathsLoaded then Exit; pathsLoaded := true; AppendPath(drawPathsO, path); end; //------------------------------------------------------------------------------ function TLineElement.GetSimplePath(const drawDat: TDrawData): TPathsD; begin Result := nil; AppendPath(Result, path); end; //------------------------------------------------------------------------------ // TCircleElement //------------------------------------------------------------------------------ constructor TCircleElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); begin inherited; bounds := NullRectD; centerPt.Init; radius.Init; end; //------------------------------------------------------------------------------ function TCircleElement.GetBounds: TRectD; begin Result := bounds; end; //------------------------------------------------------------------------------ procedure TCircleElement.GetPaths(const drawDat: TDrawData); var scalePending : double; pt : TPointD; path : TPathD; r: double; begin if pathsLoaded or not radius.IsValid then Exit; pathsLoaded := true; r := radius.GetValueXY(drawDat.bounds, GetRelFracLimit); pt := centerPt.GetPoint(drawDat.bounds, GetRelFracLimit); MatrixExtractScale(drawDat.matrix, scalePending); bounds := RectD(pt.X -r, pt.Y -r, pt.X +r, pt.Y +r); path := Ellipse(bounds, scalePending); AppendPath(drawPathsC, path); end; //------------------------------------------------------------------------------ // TEllipseElement //------------------------------------------------------------------------------ constructor TEllipseElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); begin inherited; centerPt.Init; radius.Init; end; //------------------------------------------------------------------------------ function TEllipseElement.GetBounds: TRectD; begin Result := bounds; end; //------------------------------------------------------------------------------ procedure TEllipseElement.GetPaths(const drawDat: TDrawData); var scalePending : double; path : TPathD; rad : TPointD; centPt : TPointD; begin if pathsLoaded then Exit; pathsLoaded := true; rad := radius.GetPoint(drawDat.bounds, GetRelFracLimit); centPt := centerPt.GetPoint(drawDat.bounds, GetRelFracLimit); with centPt do bounds := RectD(X -rad.X, Y -rad.Y, X +rad.X, Y +rad.Y); MatrixExtractScale(drawDat.matrix, scalePending); path := Ellipse(bounds, scalePending); AppendPath(drawPathsC, path); end; //------------------------------------------------------------------------------ // TRectElement //------------------------------------------------------------------------------ constructor TRectElement.Create(parent: TBaseElement; svgEl: TSvgXmlEl); 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 pathsLoaded 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; pathsLoaded := true; 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); 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: TBaseElement; svgEl: TSvgXmlEl); begin inherited; offset.Init; hasPaths := false; end; //------------------------------------------------------------------------------ procedure TTextElement.Draw(img: TImage32; drawDat: TDrawData); begin UpdateDrawInfo(drawDat, self); UpdateFontInfo(drawDat, self); fSvgReader.GetBestFont(drawDat.FontInfo); if not Assigned(fSvgReader.fFontCache) then Exit; if drawDat.fontInfo.size = 0 then drawDat.fontInfo.size := 16; if offset.X.IsValid then currentPt.X := offset.X.rawVal else if elRectWH.left.IsValid then currentPt.X := elRectWH.left.rawVal else currentPt.X := 0; if offset.Y.IsValid then currentPt.Y := offset.Y.rawVal else if elRectWH.top.IsValid then currentPt.Y := elRectWH.top.rawVal else currentPt.Y := 0; lastChrSpc := false; textDx := 0; currSpanEl := nil; //get child paths (which also updates currentPt) GetPaths(drawDat); DrawChildren(img, drawDat); end; //------------------------------------------------------------------------------ // TTextSubElement //------------------------------------------------------------------------------ function TTextSubElement.GetTextEl: TTextElement; var el: TBaseElement; begin if not Assigned(textEl) then begin el := fParent; while Assigned(el) and not (el is TTextElement) do el := el.fParent; if Assigned(el) then textEl := TTextElement(el); end; Result := textEl; end; //------------------------------------------------------------------------------ // TTSpanElement //------------------------------------------------------------------------------ procedure TTSpanElement.GetPaths(const drawDat: TDrawData); var tmpX, startX, fontScale, fontSize, bs: double; i,j, len : integer; di : TDrawData; s : UnicodeString; mat : TMatrixD; tmpPaths : TPathsD; codepoints: TArrayOfCardinal; angles : TArrayOfDouble; glyphInfo : PGlyphInfo; glyphRec : TRectD; begin // 1. We only want to process this method once even though it's called twice, // first indirectly by TTextElement.Draw, and then by TTSpanElement.Draw. // 2. This method isn't called when is a sub-element of . if pathsLoaded then Exit; pathsLoaded := true; di := drawDat; if ChildCount > 0 then begin UpdateDrawInfo(di, self); UpdateFontInfo(di, self); end; if drawDat.FontInfo.size = 0 then fontSize := 16.0 else fontSize := drawDat.FontInfo.size; fSvgReader.GetBestFont(di.FontInfo); if not Assigned(fSvgReader.fFontCache) then Exit; GetTextEl; if not Assigned(textEl) or (textEl.currentPt.X = InvalidD) or (textEl.currentPt.Y = InvalidD) then Exit; //by not changing the fontCache.FontHeight, the quality of //small font render improves very significantly (though of course //this requires additional glyph scaling and offsetting). fontScale := fontSize / fSvgReader.fFontCache.FontHeight; if elRectWH.left.IsValid then textEl.currentPt.X := elRectWH.left.rawVal; if elRectWH.top.IsValid then textEl.currentPt.Y := elRectWH.top.rawVal; if offset.X.IsValid then textEl.currentPt.X := textEl.currentPt.X + offset.X.GetValue(0, 0); if offset.Y.IsValid then textEl.currentPt.Y := textEl.currentPt.Y + offset.Y.GetValue(0, 0); // only 'virtual' (dummy) elements are self-closing, and // mostly their parents are 'real' elements. However, // virtual elements can also have element parents. if not fXmlEl.selfClosed then begin textEl.currSpanEl := self; angles := nil; end else if (fParent is TTSpanElement) then begin if Assigned(TTSpanElement(fParent).angle) then angles := TTSpanElement(fParent).angle else angles := textEl.angle; end else begin angles := textEl.angle; textEl.currSpanEl := nil; end; chunkDx := 0; if (Length(fXmlEl.text) > 0) and (fontSize > 1) then begin // this should be a virtual (dummy) element //assert(fXmlEl.selfClosed); s := DecodeUtf8ToUnicode(HtmlDecode(fXmlEl.text)); // don't allow a dup. spaces or a space at the beginning of a text s := FixSpaces(s, textEl.lastChrSpc or ((fParent = textEl) and (self = textEl.Child[0]))); if IsBlankText(s) then begin drawPathsC := nil; // don't allow duplicate spaces or a space at the beginning of text if textEl.lastChrSpc or (self = textEl.Child[0]) then Exit; tmpX := fSvgReader.fFontCache.GetSpaceWidth; textEl.lastChrSpc := true; end else if Assigned(angles) then begin drawPathsC := nil; tmpPaths := nil; tmpX := 0; codepoints := fSvgReader.fFontCache.GetTextCodePoints(s); // make sure 'angles' is at least the length of codepoints len := Length(codepoints); if len > Length(angles) then begin j := High(angles); SetLength(angles, len); // extend angles for i := j +1 to len -1 do angles[i] := angles[j]; end; textEl.lastChrSpc := (codepoints[len -1] = 32); // now get each rotated glyph and append to drawPathsC ... for i := 0 to len -1 do begin glyphInfo := fSvgReader.fFontCache.GetGlyphInfo(codepoints[i]); if Assigned(glyphInfo.paths) then begin glyphRec := GetBoundsD(glyphInfo.paths); tmpPaths := RotatePath(glyphInfo.paths, glyphRec.MidPoint, angles[i]); if i > 0 then tmpPaths := TranslatePath(tmpPaths, tmpX, 0); AppendPath(drawPathsC, tmpPaths); end; tmpX := tmpX + glyphInfo.hmtx.advanceWidth * fSvgReader.fFontCache.Scale; end; end else begin drawPathsC := fSvgReader.fFontCache.GetTextOutline(0, 0, s, tmpX); textEl.lastChrSpc := s[length(s)] = space; end; chunkDx := tmpX * fontScale; if Assigned(textEl.currSpanEl) then with textEl.currSpanEl do chunkDx := chunkDx + self.chunkDx; textEl.textDx := textEl.textDx + chunkDx; with textEl.currentPt do begin startX := X; X := X + chunkDx; end; if Assigned(drawPathsC) then // eg. unassigned if a space char begin with drawDat.fontInfo do if not baseShift.IsValid then bs := 0 else bs := baseShift.GetValue(size, GetRelFracLimit); mat := IdentityMatrix; MatrixScale(mat, fontScale); MatrixTranslate(mat, startX, textEl.currentPt.Y - bs); MatrixApply(mat, drawPathsC); end; end; // nested elements are always possible, // except when self is a pseudo 'selfClosed' element inherited GetPaths(di); // gets any children paths end; //------------------------------------------------------------------------------ procedure TTSpanElement.Draw(image: TImage32; drawDat: TDrawData); var stroked : Boolean; filled : Boolean; tmpRec : TRect; fillPaths : TPathsD; begin if ChildCount = 0 then fDrawData := fParent.fDrawData else begin UpdateDrawInfo(drawDat, self); UpdateFontInfo(drawDat, self); end; if not fXmlEl.selfClosed then begin // DrawChildren and exit ... inherited; Exit; end; filled := IsFilled(drawDat); stroked := IsStroked(drawDat); if Assigned(drawPathsC) and Assigned(textEl) then begin // a element that contains text (and a path) must be virtual. // But its parent may be another , or a or a