The SVG control package offers a number of controls to display SVG graphics. But if you want more control over how SVG graphics are rendered or if you want to post process graphics, you can also use the lower level classes, interfaces and functions of the package.
Two of these interfaces are
ISVGRenderContext
and
ISVGRoot
ISVGRenderContext is a high quality canvas, very much like the Delphi Firemonkey canvas, but it has the advantage that you can use it in Delphi VCL also. It always draws to a bitmap which you have to supply.
It has most of the drawing functions that the Firemonkey canvas has. Functions like DrawCircle, DrawRect, FillRect, FillPath, ApplyFill, ApplyStroke, MultiplyMatrix BeginScene, EndScene and so on.
You can use the render context (RC) for example to add drawings before or after you render an SVG graphic, or you could just use it for high quality drawings, also supporting transparency, something Delphi VCL does not excel in.
ISVGRoot is the root of the tree of SVG elements of the SVG graphic, equivalent to the Document Object Model (DOM) in an internet browser. This is created after parsing the SVG xml text and allows you to manipulate elements and parameters before actually rendering the graphic. You can also use it to measure element dimensions.
I’ll give an example here how you can use both to render an SVG graphic an then to post process the graphic to draw some outlines around some of the elements in the SVG graphic.
We use the following simple SVG graphic and we will draw outlines around the text, star group an rotated ellipse.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" id="test_svg" version="1.1" height="320" width="480" viewBox="0 215 120 80"> <g id="layer1"> <text id="txt" x="55" y="225" font-family="sans-serif" font-size="8"> <tspan id="tspan1">A star group</tspan> <tspan id="tspan2" x="55" dy="1em">an ellipse</tspan> <tspan id="tspan3" x="55" dy="1em">and some text</tspan> </text> <ellipse id="ellipse" transform="rotate(25)" cx="200" cy="215" rx="25" ry="7.5" fill="#00ff00" stroke="#000000" stroke-width="0.75" /> <g id="star_group"> <path id="star1" fill="#ff2a2a" stroke="#000000" stroke-width="0.75" d="m 41.010416,262.03718 -4.104352,-0.60791 -3.016279,2.84909 -0.690161,-4.09132 -3.641728,-1.98824 3.677809,-1.92067 0.765567,-4.07789 2.963173,2.90429 4.114874,-0.53204 -1.846468,3.71562 z" /> <path id="star2" fill="#ffcc00" stroke="#000000" stroke-width="0.75" d="m 25.31441,257.03553 -3.967414,-6.11754 -7.330735,-0.71095 4.676652,-5.60961 -1.579348,-7.09179 6.857743,2.6506 6.354646,-3.67203 -0.438334,7.24778 5.506734,4.82236 -7.128647,1.82876 z" /> </g> </g> </svg>
This SVG graphic looks like this
Now for the code.
In a new Delphi VCL application, using one form with a standard TButton and TImage control, on the OnClick of the button we do the following:
- First we will create an SVGRoot object and parse the SVG text
- Then we will calculate the size of the entire SVG and set the bitmap size accordingly
- Then we will render the SVG to the bitmap
- Then we will draw rectangles around the three elements
- Last we assign the bitmap to the TImage control
Step 1, create SVGRoot object and parse the SVG text.
uses System.UITypes, BVE.SVG2SaxParser, BVE.SVG2Intf, BVE.SVG2Context, BVE.SVG2Types, BVE.SVG2Elements, BVE.SVG2Elements.VCL; const svg_text = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' + '<svg xmlns="http://www.w3.org/2000/svg" id="test_svg" version="1.1" width="480" height="320" viewBox="0 215 120 80">' + '<g id="layer1">' + '<text id="txt" x="55" y="225" font-family="sans-serif" font-size="8">' + '<tspan id="tspan1">A star group</tspan>' + '<tspan id="tspan2" x="55" dy="1em">an ellipse</tspan>' + '<tspan id="tspan3" x="55" dy="1em">and some text</tspan>' + '</text>' + '<ellipse id="ellipse" transform="rotate(25)" cx="200" cy="215" rx="25" ry="7.5" fill="#00ff00" stroke="#000000" stroke-width="0.75" />' + '<g id="star_group">' + ' <path id="star1" fill="#ff2a2a" stroke="#000000" stroke-width="0.75"' + ' d="m 41.010416,262.03718 -4.104352,-0.60791 -3.016279,2.84909 -0.690161,-4.09132 -3.641728,-1.98824 3.677809,-1.92067 0.765567,-4.07789 2.963173,2.90429 4.114874,-0.53204 -1.846468,3.71562 z" />' + '<path id="star2" fill="#ffcc00" stroke="#000000" stroke-width="0.75"' + ' d="m 25.31441,257.03553 -3.967414,-6.11754 -7.330735,-0.71095 4.676652,-5.60961 -1.579348,-7.09179 6.857743,2.6506 6.354646,-3.67203 -0.438334,7.24778 5.506734,4.82236 -7.128647,1.82876 z" />' + '</g>' + '</g>' + '</svg>'; procedure TForm1.Button1Click(Sender: TObject); var SVGRoot: ISVGRoot; sl: TStringList; SVGParser: TSVGSaxParser; Bitmap: TBitmap; RC: ISVGRenderContext; ParentBounds, Bounds: TSVGRect; begin // Step 1 // Create a VCL SVG Root object SVGRoot := TSVGRootVCL.Create; // Put the SVG text in a stringlist and parse sl := TStringList.Create; SVGParser := TSVGSaxParser.Create(nil); try sl.Text := svg_text; SVGParser.Parse(sl, SVGRoot); finally SVGParser.Free; sl.Free; end; //.. end;
Step 2, calculate the size of the entire SVG and set the bitmap size accordingly
Next we want to know the dimensions of the SVG graphic. In this case it is not very difficult because the svg element has a with (480) and a height(320) defined. But for this example I’ll show how to calculate the size of an arbitrary SVG graphic.
The ISVGRoot interface has a method “CalcIntrinsicSize” for calculating the outer dimensions of an SVG graphic.
This method is defined as follows:
function CalcIntrinsicSize(aRenderContext: ISVGRenderContext; const aParentBounds: TSVGRect): TSVGRect;
So it needs a render context and the bounds of a parent object, this is the outer container. The function returns a rectangle with the dimensions of the SVG graphic.
Why does it need a render context? Because the SVG graphic may contain text and it needs a render context to have access to the font system.
The aParentBounds is used in cases where the SVG size is a percentage of it’s parent or container object.
So to use this we need to create a render context first. Since we don’t have to actually render the SVG yet, we can create a render context based on an arbitrary sized bitmap, so we take a bitmap of size 1 by 1.
procedure TForm1.Button1Click(Sender: TObject); var SVGRoot: ISVGRoot; sl: TStringList; SVGParser: TSVGSaxParser; Bitmap: TBitmap; RC: ISVGRenderContext; ParentBounds, Bounds: TSVGRect; begin // Step 1 //.. // Step 2 // Create a bitmap, we don't know how big it needs to be, so initially // we make it the minimum size: 1 by 1 Bitmap := TBitmap.Create; try Bitmap.SetSize(1, 1); // Now create a render context for the bitmap RC := TSVGRenderContextManager.CreateRenderContextBitmap(Bitmap); // Define some parent bounds, in case the SVG graphic has dimensions in percentages ParentBounds := SVGRect(0, 0, Image1.Width, Image1.Height); // Now we can calculate the dimensions of the SVG graphic Bounds := SVGRoot.CalcIntrinsicSize(RC, ParentBounds); // Resize the bitmap and make it 32bit transparent Bitmap.SetSize(Round(Bounds.Width), Round(Bounds.Height)); Bitmap.PixelFormat := TPixelFormat.pf32bit; Bitmap.AlphaFormat := TAlphaFormat.afPremultiplied; // Recreate the render context with the newly sized Bitmap!! RC := TSVGRenderContextManager.CreateRenderContextBitmap(Bitmap); //.. finally Bitmap.Free; end; end;
Step 3, render the SVG to the bitmap
Now we are going to render the SVG to the bitmap using ISVGRoot and ISVGRenderContext.
procedure TForm1.Button1Click(Sender: TObject); var SVGRoot: ISVGRoot; sl: TStringList; SVGParser: TSVGSaxParser; Bitmap: TBitmap; RC: ISVGRenderContext; ParentBounds, Bounds: TSVGRect; begin // Step 1 //.. // Step 2 //.. // Step 3 // Draw the SVG on the bitmap RC.BeginScene; try RC.Clear(0); // Clear the bitmap with transparent color RC.Matrix := TSVGMatrix.CreateIdentity; SVGRenderToRenderContext( SVGRoot, RC, Bounds.Width* 2, Bounds.Height, [sroEvents], // Force the creation of an object state tree FALSE // No auto scaling in this case ); finally RC.EndScene; end; // .. finally Bitmap.Free; end; end;
So just as with drawing on a Firemonkey canvas, we need to enclose any drawing with BeginScene and EndScene commands. We could also modify the matrix of the render context to scale or rotate or translate the SVG graphic.
The global procedure “SVGRenderToRenderContext” is used to draw the SVG contained in “SVGRoot” to the render context “RC”.
This procedure is defined as follows:
procedure SVGRenderToRenderContext( aSVGRoot: ISVGRoot; aRenderContext: ISVGRenderContext; const aWidth, aHeight: TSVGFloat; const aRenderOptions: TSVGRenderOptions = [sroFilters, sroClippath]; const aAutoViewBox: boolean = True; const aAspectRatioAlign: TSVGAspectRatioAlign = arXMidYMid; const aAspectRatioMeetOrSlice: TSVGAspectRatioMeetOrSlice = arMeet);
The parameter “aRenderOptions” can have a combination of the following settings:
- sroFilters: SVG filters will be rendered if these are defined in the SVG graphic
- sroClippath: clippaths will be rendered if these are defined in the SVG graphic
- sroEvents: mouse pointer events will be enabled for the SVG graphic, for this a Object state tree will be generated
Because filters, clippaths en events need extra, in some cases a lot of resources, they are made optional.
The last option “sroEvents” is interesting if we want to have measurements of individual elements in the SVG graphic. If we want to enable mouse pointer events, we need to know the dimensions of each visible element. The renderer will in that case create a so called “ObjectState” tree , while rendering the SVG graphic.
The “ObjectState” tree will contain a ScreenBBox (bounding box in screen dimensions) of every element visible on the screen. Note that sometimes an element can be drawn multiple times on the screen, if it is referenced by a “use” element. In that case it will be present more than once in the Object State tree.
So we will use the “ObjectState” tree to draw outlines around elements on the rendered SVG.
Step 4, draw rectangles around the three elements and step 5, assign bitmap to TImage control
procedure TForm1.Button1Click(Sender: TObject); var SVGRoot: ISVGRoot; sl: TStringList; SVGParser: TSVGSaxParser; Bitmap: TBitmap; RC: ISVGRenderContext; ParentBounds, Bounds: TSVGRect; StateRoot, State: ISVGObjectState; Stroke: TSVGBrush; function FindID(aParent: ISVGObjectState; const aID: string): ISVGObjectState; var Child: ISVGObjectState; begin // Find first occurence of aID in object state tree for Child in aParent.Children do begin if Child.SVGObject.ID = aID then begin Result := Child; Exit; end else Result := FindID(Child, aID); end; end; begin // Step 1 // .. // Step 2 // .. // Step 3 // Draw the SVG on the bitmap RC.BeginScene; try RC.Clear(0); // Clear the bitmap with transparent color RC.Matrix := TSVGMatrix.CreateIdentity; SVGRenderToRenderContext( SVGRoot, RC, Bounds.Width* 2, Bounds.Height, [sroEvents], // Force the creation of an object state tree FALSE // No auto scaling in this case ); // Step 4 // Create a brush for stroking the outline // Get a reference to the object state tree StateRoot := SVGRoot.SVG.ObjectStateRoot; // Find the text element on ID and return the screen bounding box State := FindID(StateRoot, 'txt'); if assigned(State) then begin // Get the screen bounds of the element Bounds := State.ScreenBBox; // Grow the box a bit Bounds.Inflate(5, 5); RC.ApplyStroke(TSVGSolidBrush.Create(SVGColorRed), 2.0); // Draw a rectangle with rounded corners around the element RC.DrawRect(Bounds, 5, 5); end; // Find the star group element on ID and return the screen bounding box State := FindID(StateRoot, 'star_group'); if assigned(State) then begin Bounds := State.ScreenBBox; Bounds.Inflate(5, 5); RC.ApplyStroke(TSVGSolidBrush.Create(SVGColorBlue), 2.0); RC.DrawRect(Bounds, 5, 5); end; // Find the ellipse element on ID and return the screen bounding box State := FindID(StateRoot, 'ellipse'); if assigned(State) then begin Bounds := State.ScreenBBox; Bounds.Inflate(5, 5); RC.ApplyStroke(TSVGSolidBrush.Create(SVGColorGreen), 2.0); RC.DrawRect(Bounds, 5, 5); end; finally RC.EndScene; end; // Step 5 // Assign to Image1 Image1.Picture.Assign(Bitmap); finally Bitmap.Free; end; end;
This produces the following output on the VCL form.
Sources can be found on github. To compile the examples, you need the demo or the full version of the SVG control package.