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.

