Home Compatibility with version 2.3 Delphi installation FPC Lazarus installation Folders and units Components and controls Design Global defines Render contexts Text rendering Text layouts Core functionality Supported SVG features Programming examples SVG Viewer applications Contact and bug reporting Licensing

SVG control package v2.4

SVG (Scalable Vector Graphics) is an XML-based vector image format for two dimensional graphics developed by the World Wide Web Consortium (W3C).

The "SVG control package" enables the use of Scalable Vector Graphics (SVG) in Delphi and FPC Lazarus applications. The current version of the package covers most of the SVG 1.1 specification, with the exception of scripting.

The architecture of the software allows for use on multiple platforms. The software can be used in Delphi VCL, FMX and Lazarus applications for Windows, OsX and Linux targets. Mobile platforms iOS and Android are to some extend possible but with limitations. Other targets might also be possible but these are not tried or tested.

The requirements for the package are the following:

You can just use the controls provided in the package or you can go a bit deeper and try the programming examples. The programming examples show how to interact with the SVG content.

In this help file you will find the following:

Sources for the examples an de viewer applications can be found on github: delphi-svg-control-examples

Please read the installation instructions carefully!


Compatibility with version 2.3

The SVG v2.3 components and controls are compatible with v2.4, some properties and methods are added to the v2.4 controls to support animation.

Most changes between version 2.3 and 2.4 are in the data structure representing the XML DOM. In version 2.3 this was a custom made data structure, although elements implemented the IXMLNode interface, the 2.4 version is based on Delphi's TXMLNode and TXMLDocument. This means that for all direct interaction with the DOM, like adding elements, removing elements changing attributes, you should use the methods and properties of IXMLNode and IXMLDocument. The package uses the XML Data Binding functionality to create nodes of the correct class (TSVGRect, TSVGUse and so on). See "Add and remove elements" for a programming example.

A define is introduced for differentiating between version, for version 2.4 this is "SVGLibVersion2400" and "SVGLibVersion2400Up" for all version from 2.4 onwards. The define is located in file "CompilerSettings.inc".

Item Version 2.3 Version 2.4 Remark
Components and controls
TSVG2Doc not available in FPC Available for all platforms.
TSVG2AnimationTimer N.a. New
TSVG2WinControl N.a. New for VCL and FPC
Property SVGDoc N.a. for TSVG2Image Added to TSVG2Image For linking to a TSVG2Doc component.
Property AnimationTimer N.a. New For linking to a TSVG2AnimationTimer component.
Property AnimationTime N.a. New For setting the animation time.
XML DOM data structure
Data structure Custom made Based on TXMLDocument The DocumentElement of TXMLDocument represents the outer SVG element.
Access to all SVG's contained in a SVGRoot object. SVGList: TList<IXMLNode> DocList: TList<ISVGXMLDocument>
Rendering to a commandfile Only GDI+ EMF file GDI+ EMF File, Direct2d command list From 2.4.Update 8
Rendering to a printer Only GDI+ (printer) DC GDI+ and Direct2D From 2.4.Update 8
Custom fonts Only SVG fonts TTF, OTF, WOFF, WOFF2, SVG From 2.4.Update 8

Delphi installation

Installation packages in Delphi

Remove old versions

If older versions of the SVG control version packages are already installed, these have to be removed first.

Choose "Install Packages..." in the IDE.


Then deselect and remove the packages named DclSVG2PackageFmx.bpl, DclSVG2PackageVcl.bpl and DclSVG2PackageCommon.bpl. Remove the Fmx and Vcl package first because these are dependent on the Common package.


It might be necessary to check if really all old files are deleted and no old package files or object files remain on the file path. Otherwise you might experience strange behaviour in Delphi, for example exception errors on closing Delphi.

Next, before you reinstall the new packages RESTART DELPHI!

Install the new versions.

For each Delphi version there are in total 6 packages, 3 run-time packages and 3 design-time packages.

Depending on your Delphi version, you can find find the package files in the sub folder indicated by the following table.

Folder Delphi name ID Compiler version
XE2 Delphi XE2 XE2 23
XE3 Delphi XE3 XE3 24
XE4 Delphi XE4 XE4 25
XE5 Delphi XE5 XE5 26
XE6 Delphi XE6 XE6 27
XE7 Delphi XE7 XE7 28
XE8 Delphi XE8 XE8 29
DX Delphi 10 Seattle DX 30
DX1 Delphi 10.1 Berlin DX1 31
DX2 Delphi 10.2 Tokyo DX2 32
DX3 Delphi 10.3 Rio DX3 33
DX4 Delphi 10.4 Sydney DX4 34

So, the package that ends with ..XE5.dproj should be installed in Delphi XE5 and ..DX1.dproj should be installed in Delphi DX1 Berlin.

The run-time packages contain the actual SVG source code units, there is a common package, a package for Vcl and one for FMX. The run-time packages must at least be build to target Win32 and if you want to use the SVG package in 64 bit applications, you have to build the run time packages to target Win64 also.

  1. SVG2PackageCommon<ID>
  2. SVG2PackageFmx<ID>
  3. SVG2PackageVcl<ID>

The design-time packages start with "Dcl..." and contain the units to register the package in the Delphi IDE and the property editors. These packages can only be build to target Win32, because the Delphi IDE is a 32bit application.

  1. DclSVG2PackageCommon<ID>
  2. DclSVG2PackageFmx<ID>
  3. DclSVG2PackageVcl<ID>

Here is an example how to build the packages and in case of the design-time packages how to install them. In this example I use Delphi DX1 Berlin and only build the packages to the default Win32 target.

The run-time common package SVG2PackageCommonDX1 has to be build first because the other packages are dependent.

1. In the IDE choose "File - Open Project..." and open the project file SVG2PackageCommonDX1.dproj in de Package folder of relevant Delphi version.


2. Build SVG2PackageCommonDX1, you can save the changes if asked for

3. Open project file DclSVG2PackageCommonDX1.dproj.

4. Build and then Install package DclSVG2PackageCommon.DX1.

After installation there will be a notification that one component has been registered: TSVG2Doc, TSVG2AnimationTimer.


5. Next open the project file SVG2PackageFmxDX1.dproj in the sub folder Fmx of the package folder.


6. Build package SVG2PackageFmxDX1.

7. Open the project file DclSVG2PackageFmxDX1.dproj.

8. Build an then Install package DclSVG2PackageFmxDX1.

After installation there will be a notification that four new components have been registered: TSVG2Control, TSVG2Image, TSVG2ImageList and TSVG2LinkedImage.


9. Next open the project file SVG2PackageVclDX1.dproj in the sub folder Vcl of the package folder.


10. Build package SVG2PackageVclDX1.

11. Open the project file DclSVG2PackageVclDX1.dproj.

12. Build and then Install package DclSVG2PackageVclDX1.

After installation there will be a notification that four new components have been registered: TSVG2Control, TSVG2Image, TSVG2ImageList and TSVG2LinkedImage.


After installing these packages, there should be a group called SVG2 in the Tool palette 8 components in VCL: TSVG2Doc, TSVG2AnimationTimer, TSVG2Control, TSVG2WinControl, TSVG2Image, TSVG2ImageList, TSVG2LinkedImageList, TSVG2LinkedImage.

FMX from XE8 and higher have 7 components, there is no TSVG2WinControl.

FMX version below XE8 also don't have the image list controls, because the image list for FMX was introduced in XE8.




Setting up the Delphi project environment

To be able to compile your Delphi projects with the SVG package, Delphi needs to know where the source code of the package is located. There are three ways to set this up:

  1. Add folders to your library path once.
  2. Add a search path to your new projects in the project options
  3. Add the SVG package units to your new projects explicitly

Add folders to your library path once

This might be the most convenient because you have to do it only once.



Add a search path to your new projects in the project options

This you have to do for each new project.


Add the SVG package units to your new projects explicitly

This you might want to do if you want full control over which units you want to compile into your project, you have to do this also for each new project.

First add all the .pas files from the SVG package in folder Common.

Choose from the Delphi menu "Project ->Add to project". Navigate to the Common folder of the SVG package and select the files.



VCL application

In case you are creating a VCL application, choose "Project ->Add to project" again and go to the folder Common\Vcl and select all the files from there.

Next go to folder Common\Platform. The files you select here depend on the render context you want to use in your application.

The default render context for VCL is Direct2D with a fall back to GDI+.

For this, you have to select all the Direct2D header files (starting with BVE.DX...), next add BVE.SVG2ContextD2D.pas and add BVE.SVG2ContextGP.pas

For rendering text using system fonts, you need to include thetext layoutsalso, so add BVE.SVG2FontDirectWrite.pas and add BVE.SVG2FontGDI.pas.

The render context to use for VCL are defined asglobal definesin the include file Common\Vcl\ContextSettingsVCL.inc. If you want to use another render context than the default, you have to modify this file and include the appropriate files from Common\Platform into your project.


FMX application

In case you are creating a Firemonkey (FMX) application, choose "Project ->Add to project" again and go to the folder Common\Fmx and select all the files from there.

Next go to folder Common\Platform. Again the files you select here depend on the render context you want to use in your application.

The default render context for FMX is "FMX canvas".

For this, you have to select BVE.SVG2ContextFMX.pas.

The render context to use for FMX are defined in the include file Common\Fmx\ContextSettingsFMX.inc. If you want to use another render context than the default, you have to modify this file and include the appropriate files from Common\Platform into your project. See paragraph "Global defines" for details.


A more flexible approach would be to add all the units to your project as described above,exceptthe ones from Common\Platform. And add a search path to this folder in the project options or in the library path. This way you simply set the render context you want to use in ContextSettingsVCL.inc or ContextSettingsFMX.inc and let the system decide which units need to be included.


Test compilation Delphi

Next you can test if your project compiles.

  1. Create a new project.
  2. Select the TSVG2Image from the tool palette and put in on the form.
  3. Select the SVG2Image1 and put a path to an SVG file into the "FileName" property.
  4. Run the application.



FPC Lazarus installation


FPC and Lazarus requirements for the SVG library

The SVG library is developed with FPC version 3.0.4 and Lazarus version 1.8.4, so it is recommended to use these versions or later.

Also because the SVG library uses generics for lists, stacks and so on, the "rtl-generics" package is needed. This package is provided as standard in FPC 3.1.1.+ but must be downloaded separately for version 3.0.4.


Installation packages in FPC Lazarus

Load the package FPC\Package\svg2package.lpk into Lazarus IDE:

The package has the following search paths defined:

Common, shared units for all platforms
Common\Fpc, the FPC Lazarus specific units
Common\Platform, the render context interface implementations

If your FPC version doesn't include the rtl-generics version by default (version FPC 3.1.1.+ and above) you need to download the package and add a search path to it, or use the version included in the package.

Common\Fpc\generics, the "rtl-generics" package. Only include this path if your FPC Lazarus version doesn't include the package by default!

To include the SVG components into the Lazarus IDE, follow these steps:

  1. Compile the package
  2. Select Use->Install

Lazarus has to be rebuild to complete the installation.

After installation you will find the SVG controls on tab "SVG2"

Lazarus IDE, tab SVG2

Test compilation FPC Lazarus

Test the installation:

  1. Create a new application project.
  2. Select the TSVG2Image from the tool palette and put in on the form.
  3. Select the SVG2Image1 and put a path to an SVG file into the "FileName" property.
  4. Run the application
Test project FPC

Folders and units

Path Unit Description
Common\CompilerSettings.inc CompilerSettings.inc Include file where compiler versions and defines are located
Common\BVE.SVG2Types.pas BVE.SVG2Types Basic types and constants
Common\BVE.SVG2Intf.pas BVE.SVG2Intf Interface definitions
Common\BVE.SVG2Attributes.pas BVE.SVG2Attributes Implementations for SVG attributes
Common\BVE.SVG2Elements.pas BVE.SVG2Elements.pas Implementations for the SVG elements
Common\BVE.SVG2Context.pas BVE.SVG2Context.pas Implementations for base graphic classes
Common\BVE.SVG2PathData.pas BVE.SVG2PathData TSVGPathdata and path data parser.
Common\BVE.SVG2Bidi.pas BVE.SVG2Bidi Implementation of "UAX #9: Unicode Bidirectional Algorithm"
Common\BVE.SVG2CSSUtility.pas BVE.SVG2CSSUtility.pas Implementation of CSS, parsing and evaluation
Common\BVE.SVG2FilterUtility.pas BVE.SVG2FilterUtility Filter effects functions
Common\BVE.SVG2GeomUtility.pas BVE.SVG2GeomUtility Geometrical functions
Common\BVE.SVG2ParseUtility.pas BVE.SVG2ParseUtility XML and SVG attribute parser
Common\BVE.SVG2SaxParser.pas BVE.SVG2SaxParser TSVGSaxParser, Builds the DOM tree using the XML reader
Common\BVE.SVG2XMLReader.pas BVE.SVG2XMLReader Parses XML files and is used by TSVGSaxParser
Common\BVE.SVG2Doc.pas BVE.SVG2Doc TSVG2Doc, TSVG2AnimationTimer and TSVG2BaseView
Common\BVE.SVG2Dom.pas BVE.SVG2Dom Implementation of "SVG control DOM"
Common\BVE.SVG2SMIL.pas BVE.SVG2SMIL SMIL implementation for animation
Vcl
Common\Vcl\ContextSettingsVCL.inc ContextSettingsVCL.inc Include file for VCL, here is defined which render context and which text layout implementation to use.
Common\Vcl\BVE.SVG2Elements.VCL.pas BVE.SVG2Elements.VCL TSVGRootVLC, TSVGRenderContextManager, render context implementations for VCL, utility functions
Common\Vcl\BVE.SVG2Control.VCL.pas BVE.SVG2Control.VCL TSVG2BaseControl, TSVG2Control, TSVG2WinControl
Common\Vcl\BVE.SVG2Graphic.Vcl.pas BVE.SVG2Graphic.Vcl TSVGGraphic
Common\Vcl\BVE.SVG2Image.VCL.pas BVE.SVG2Image.VCL TSVG2Image
Common\Vcl\BVE.SVG2ImageList.VCL.pas BVE.SVG2ImageList.VCL TSVG2ImageList, TSVG2LinkedImageList, TSVG2LinkedImage
Freepascal
Common\Fpc\ContextSettingsFPC.inc ContextSettingsFPC.inc Include file for FPC, here is defined which render context and which text layout implementation to use.
Common\Fpc\BVE.SVG2Elements.FPC.pas BVE.SVG2Elements.FPC TSVGRootFPC, TSVGRenderContextManager, render context implementations for FPC, utility functions
Common\Fpc\BVE.SVG2Control.FPC.pas BVE.SVG2Control.FPC TSVG2BaseControl, TSVG2Control, TSVG2WinControl
Common\Fpc\BVE.SVG2Graphic.FPC.pas BVE.SVG2Graphic.FPC TSVGGraphic
Common\Fpc\BVE.SVG2Image.FPC.pas BVE.SVG2Image.FPC TSVG2Image
Common\Fpc\BVE.SVG2ImageList.FPC.pas BVE.SVG2ImageList.FPC TSVG2ImageList, TSVG2LinkedImageList, TSVG2LinkedImage
Firemonkey
Common\Fmx\ContextSettingsFMX.inc ContextSettingsFMX.inc Include file for FMX, here is defined which render context and which text layout implementation to use.
Common\Fmx\BVE.SVG2Elements.FMX.pas BVE.SVG2Elements.FMX TSVGRootFMX, TSVGRenderContextManager, render context implementations for FMX, utility functions
Common\Fmx\BVE.SVG2Control.FMX.pas BVE.SVG2Control.FMX TSVG2BaseControl, TSVG2Control
Common\Fmx\BVE.SVG2Image.FMX.pas BVE.SVG2Image.FMX TSVG2Image
Common\Fmx\BVE.SVG2ImageList.FMX.pas BVE.SVG2ImageList.FMX TSVG2ImageList, TSVG2LinkedImageList, TSVG2LinkedImage
OS specific units
Common\Platform\BVE.DX.....pas Windows Direct X header files
Common\Platform\BVE.FT...pas Freetype header files
Common\Platform\BVE.Usp10.pas BVE.Usp10 Windows Unicode Complex Script processor header file
Common\Platform\BVE.Brotli.pas BVE.Brotli Header for Brotli compression and decompression dll's
Common\Platform\BVE.Win.PrntVpt.pas BVE.Win.PrntVpt Header for Windows Print Ticket Services Module
Common\Platform\BVE.SFNT...pas Units for parsing SFNT type font files.
Common\Platform\BVE.SVG2ContextAgg.pas BVE.SVG2ContextAgg Implementation of render context based on "Aggpas"
Common\Platform\BVE.SVG2ContextBGRA.pas BVE.SVG2ContextBGRA Implementation of render context based on "BGRA bitmap"
Common\Platform\BVE.SVG2ContextD2D.pas BVE.SVG2ContextD2D Implementation of render context based on "DirectX"
Common\Platform\BVE.SVG2ContextFMX.pas BVE.SVG2ContextFMX Implementation of render context based on FMX canvas
Common\Platform\BVE.SVG2ContextGP.pas BVE.SVG2ContextGP Implementation of render context based on "GDI+"
Common\Platform\BVE.SVG2ContextGR32.pas BVE.SVG2ContextGR32 Implementation of render context based on "Graphics32"
Common\Platform\BVE.SVG2ContextQuartz.pas BVE.SVG2ContextQuartz Implementation of render context based on "Quartz"
Common\Platform\BVE.SVG2FontCoreText.pas BVE.SVG2FontCoreText Implementation of text layout based on "Core text"
Common\Platform\BVE.SVG2FontDirectWrite.pas BVE.SVG2FontDirectWrite Implementation of text layout based on "DirectWrite"
Common\Platform\BVE.SVG2FontFreetype.pas BVE.SVG2FontFreetype Implementation of text layout based on "Freetype"
Common\Platform\BVE.SVG2FontGDI.pas BVE.SVG2FontGDI Implementation of text layout based on "GDI"

SVG components and controls

The SVG package contains the following components and controls.

Components

  1. TSVG2Doc
  2. TSVG2AnimationTimer
  3. TSVG2ImageList
  4. TSVG2LinkedImageList
  5. TSVGGraphic (not available in FMX)

Controls

  1. TSVG2Control
  2. TSVG2WinControl (not available in FMX)
  3. TSVG2Image
  4. TSVG2LinkedImage

TSVG2Doc



Source Unit Parent
Vcl BVE.SVG2Doc.pas BVE.SVG2Doc Xml.XMLDoc.TXMLDocument
Fpc BVE.SVG2Doc.pas BVE.SVG2Doc Xml.XMLDoc.TXMLDocument
Fmx BVE.SVG2Doc.pas BVE.SVG2Doc Xml.XMLDoc.TXMLDocument

Description

The TSVG2Doc is derived from TXMLDocument. TSVG2Doc represents an SVG document. This component is useful if you need to manipulate the document after loading, for example add or delete nodes or if you need to use a specific DOM Vendor implementation. The package provides a default DOM Vendor "SVG Control DOM", but you could also select another one that is available.

The TSVG2Control, TSVG2Image and TSVG2DorectXControl have a property "SVGDoc" to which you can connect a TSVG2Doc, any changes you make in TSVG2Doc will be visible in the connected controls.


TSVG2AnimationTimer



Source Unit Parent
Vcl BVE.SVG2Doc.pas BVE.SVG2Doc TComponent
Fpc BVE.SVG2Doc.pas BVE.SVG2Doc TComponent
Fmx BVE.SVG2Doc.pas BVE.SVG2Doc TComponent

Description

The TSVG2AnimationTimer is used for controlling animated SVG's. The TSVG2AnimationTimer contains a thread, that by default is synced with the main application thread. The "FrameRate" property can be set with the desired frames per second, the default is 30.

The TSVG2AnimationTimer contains a second "sample" thread, that can be used for updating progress indicators or displays, the interval can be set independently from the frame rate. For example, if you want to show the actual frames per second (property "FPS"), every 250ms, you could set property "SampleInterval" to 250 and in the "OnSample" event write the FPS in a label caption.

Controls that have a "AnimationTimer" property can link to a TSVG2AnimationTimer, any animated SVG that is loaded into these controls will be controlled by the animation timer.

To start the timer set property "IsStarted" to TRUE, to stop the timer, set "IsStarted" to FALSE.

To pause the timer set property "IsPaused" to TRUE, to un-pause the timer, set "IsPaused" to FALSE.

The TSVG2AnimationTimer uses threads. With FPC/Lazarus on Unix systems, you must explicitly enable thread functionality otherwise the application crashes:

Add "-dUseCThreads" to the custom compiler options on Unix systems, see here

Properties

Methods

Events

Example

See Animation control in section Programming exampled.


TSVG2ImageList and TSVG2LinkedImageList


TSVG2ImageList is derived from TImageList and therefore contains a list of bitmap images. However, it also contains a list of SVG's and has the capability to automatically create the bitmap images out of the SVG.

The rendering of the bitmap images out of the SVG content occurs when the SVG data is modified or if the TSVGImageList properties are changed, otherwise it just behaves as a static TImageList.

Controls that have a "ImageList" property can also link to a TSVG2ImageList, the control will display the static image from the TSVG2ImageList. If SVG data or some other property of TSVG2ImageList is modified, than the associated static image will be updated and all linked controls will update there image as well.

The image bitmap data along with all the SVG content and settings are stored in the Delphi or FPC/Lazarus form file.

The software for parsing and rendering SVG is complex and adds to the complexity and size of the application you are developing, so it only makes sense to use TSVG2ImageList if you actually need to parse and render images in run-time. An alternative use case could be, to just use a normal TImageList in your main application and develop a small utility application with TSVG2ImageList, from which you export the images that you need in your main application.

Differences between the VCL, FMX and FPC image list

The standard TImageList for VCL, FMX an FPC each differ in design. The main differences are in how scaling is implemented:

These differences are also present in TSVG2ImageList, because it is derived from TImageList for VCL, FMX and FPC/Lazarus:


SVG Image lists functionality and settings

The TSVG2ImageList for VCL/FPC Lazarus extend the functionalities of the normal TImageList by the following items:

  1. Synthesizing images from SVG definitions
  2. Creating variations on images using "Styles"
  3. Creating multiple sizes of images using "Resolutions"
  4. Exporting images to stream or file

In stead of a list, the TSVG2ImageList can be seen as an image grid, where the rows represent SVG images and the columns represent "Styles" and the "Resolutions" represent different layers of the grid.

The total number of mages you can create with a TSVGImagelist:

SVG count * Style count * Resolution count

The TSVG2LinkedImageList can be used as an intermediate between the controls on the form and a centralized TSVG2ImageList.

The TSVG2LinkedImageList uses all SVG images and all, or one of the "Styles" from a linked parent "TSVG2ImageList" but for the rest has the same properties as a TSVG2ImageList.

Both TSVG2ImageList and TSVG2LinkedImageList are derived from TSVG2BaseImageList.

Component editor

The component editor is activated by double clicking on the TSVG2ImageList component on the form you are designing, or by the pop-up menu that is shown when you right click TSVG2ImageList and then select "Edit...".

VCL TSVG2ImageList component editor

FPC/Lazarus TSVG2ImageList component editor

FMX TSVG2ImageList component editor

TSVG2LinkedImageList component editor

There is also a component editor for the TSVG2LinkedImageList. It has the same "Bitmap" and "Export" tab as the TSVG2ImageList, but it lacks the "SVG" and "Styles". In stead it has a "Parent list" tab, where you can set the link to the parent TSVG2ImageList.

With the "Parent style index" you can select one of the styles that are defined in the parent TSVG2ImageList. If you set this to -1, all styles of the parent image list are used.

Example creating styles

An example for this is using a "Saturation" filter to create a "Disabled" version of an image. The outer SVG would look like this:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <filter id="fltr_saturate">
      <feColorMatrix in="SourceGraphic" type="saturate" values="0.20"/>
    </filter>
  </defs>
    <g filter="url(#fltr_saturate)">&cur_svg;</g>
</svg>

Here &cur_svg; is a special entity that the parser will replace with the SVG from the SVG list.

There are a couple of default outer SVG's defined that you can select and modify if you wish, or you can create your own outer SVG, as long as it contains the &cur_svg; entity and it is a valid SVG after substitution.

Entities added to the SVG

Another means of altering the SVG in order to create a different style is by using Entities in the SVG itself. Entities are often used to replace special characters in XML, for example the symbol "<" is replaced in XML by the entity "&lt;". See for details on Entities over here.

When the parser encounters an Entity in the SVG, it will add it to the Entity list of all the Styles that are defined in the TSVG2ImageList. For each style a different value for the Entity can be entered.

For example, we have the following SVG (start.svg):

<svg version="1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" enable-background="new 0 0 48 48">
  <path fill="#F44336" d="M38,42H10c-2.2,0-4-1.8-4-4V10c0-2.2,1.8-4,4-4h28c2.2,0,4,1.8,4,4v28C42,40.2,40.2,42,38,42z"/>
  <polygon fill="#fff" points="31,24 20,16 20,32"/>
</svg>

We want to create different styles by changing the colour, so we add a DTD and within that an Entity for the fill attribute:

<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd" [
  <!ENTITY fill_color "#F44336">
]>
<svg version="1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" enable-background="new 0 0 48 48">
  <path fill="&fill_color;" d="M38,42H10c-2.2,0-4-1.8-4-4V10c0-2.2,1.8-4,4-4h28c2.2,0,4,1.8,4,4v28C42,40.2,40.2,42,38,42z"/>
  <polygon fill="#fff" points="31,24 20,16 20,32"/>
</svg>

On parsing the entity "fill-color" will be added to the Entity list of all styles. On each style you can now set a different value.

The image below shows some other things that can be done, for example replacing a transformation value or a complete element with an entity.

The SVG used for the rotation example:

<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd" [
  <!ENTITY rotation "0">
]>
<svg version="1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" enable-background="new 0 0 48 48">
  <path fill="#F44336" d="M38,42H10c-2.2,0-4-1.8-4-4V10c0-2.2,1.8-4,4-4h28c2.2,0,4,1.8,4,4v28C42,40.2,40.2,42,38,42z"/>
  <polygon transform="rotate(&rotation;, 24, 24)" fill="#fff" points="31,24 20,16 20,32"/>
</svg>

The SVG used for the replaced element example:

<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd" [
  <!ENTITY shape "<polygon fill='#fff' points='31,24 20,16 20,32'/>">
]>
<svg version="1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" enable-background="new 0 0 48 48">
  <path fill="#F44336" d="M38,42H10c-2.2,0-4-1.8-4-4V10c0-2.2,1.8-4,4-4h28c2.2,0,4,1.8,4,4v28C42,40.2,40.2,42,38,42z"/>
  &shape;
</svg>


Example transparency on VCL and work-around in case of non-themed user interface

In VCL, the "Color depth" controls if the bitmaps in the list will support transparency. To enable transparency this must be set to "cd32bit" and the "Clear color" must be set to "clNone". However:

Windows only supports transparency on controls if runtime themes are enabled.

For example, in the following project the "Color depth" is set to "cd32bit" and the "Clear color" is set to "clNone".

If runtime themes are enabled, this will result in transparent images on the toolbar.

If runtime themes are disabled, in the application manifest or in the Windows OS settings, the background of the controls will become black.

A work around for this problem is to detect if runtime themes are enabled when the application starts and if not, to set the "Color depth" of the image list to "cd24" bit and the "ClearColor" equal to the controls background color, for example:

uses
  Vcl.Themes;

procedure TForm1.FormCreate(Sender: TObject);
begin
  if not StyleServices.Enabled then
  begin
    SVG2ImageList1.BeginUpdate;
    try
      SVG2ImageList1.ClearColor := clBtnFace;
      SVG2ImageList1.ColorDepth := cd24Bit;
    finally
      SVG2ImageList1.EndUpdate;
    end;
  end else begin
    SVG2ImageList1.BeginUpdate;
    try
      SVG2ImageList1.ClearColor := clNone;
      SVG2ImageList1.ColorDepth := cd32Bit;
    finally
      SVG2ImageList1.EndUpdate;
    end;
  end;
end;

After this, the images will have the following appearance in a non-themed application.



Example scaling

An example how to set up the image list for scaling can be found in the VCL demo viewer for Delphi 10.3.

There is one SVGImageList1 component, that has two styles, style 0 with plain images and style 1 with disabled images using a saturation filter. There are two linked image lists, "ilNormal" is linked two style 0 of the SVGImageList1 and "ilDisabled" is linked to style 1 of SVGImageList1.

The "DisabledImages" property of the ActionManager1 component is set to "ilDisabled" and the "Images" property is set to "ilNormal".


The properties of the image lists are set up as folows:

SVG2ImageList1 ilNormal ilDisabled
ParentImageList SVG2ImageList1 SVG2ImageList1
ParentStyleIndex 0 1
Scaled False True True
ScalingKind skReRender skReRender

Monitor scale is set to 100%


Monitor scale is set to 125%


The scaling kind is set to "skReRender", so when scaling occurs, all images in the image lists that have the "Scaled" property set to "True" will be re-rendered. It is also possible to create the images beforehand. It that case the image list properties must be set up as follows:

SVG2ImageList1 ilNormal ilDisabled
ParentImageList SVG2ImageList1 SVG2ImageList1
ParentStyleIndex 0 1
Scaled False True True
ScalingKind skBestFitFromResolution skBestFitFromResolution
Resolutions 24
30
36
48
skBestFitFromResolution skBestFitFromResolution

In the VCL version the resolutions are kept within the main SVG2ImageList1 and are defined by the width in pixels of the images. In this example they are chosen as to be 100% 125% 150% and 200% of the base image width. Maybe you can do with less resolutions, this is something you have to try out.

The FPC/Lazarus viewer has a SVG2ImageList1 component on which three styles are defined. Linked image list "ilNormal" links to style 0, linked image list "liDisabled" is linked to style 1 using a saturation filter, and linked image list "ilHot" links to style 2 using a drop shadow filter.

The "Images" property of the toolbar is set to "ilNormal", the "DisabledImages" is set to "ilDisabled" and "HotImages" is set to "ilHot".


The properties for scaling on the image lists are set up as follows:

SVG2ImageList1 ilNormal ilDisabled ilHot
ParentImageList SVG2ImageList1 SVG2ImageList1 SVG2ImageList1
ParentStyleIndex 0 1 2
Scaled False True True True
Resolutions 24
30
36
48
24
30
36
48
24
30
36
48

There is no "ScalingKind" property for the FMX/Lazarus SVG image list, an image will be selected from the resolutions that best fit the image width needed for the linked control. If not an exact match can be found, than the system will try to find a larger resolution and than make it smaller by resampling it to the right size.

For the FMX version there is also no "ScalingKind" property, there is also no "Scaled" property because Delphi FMX has scaling build into the core of all controls by default. In the FMX TImageList is a multi resolution bitmap. Again all resolutions you are likely to need in your application, must be defined beforehand. The SVG image list will render the images on all layers of the multi resolution bitmap.

The FMX viewer demo application has a SVG2ImageList1 component for the application icons. Delphi FMX has no separate "DisabledImages" property on toolbars or menu's, so the styling functionality of the TSVG2Imagelist is not used and also no linked image lists are needed.


The scaling for the SVG2ImageList1 component is set up as follows:

SVG2ImageList1
Resolutions 32
48

TSVG2BaseImageList


Source Unit Parent
Vcl BVE.SVG2ImageList.VCL.pas BVE.SVG2ImageList.VCL Vcl.Controls.TImageList
Fpc BVE.SVG2ImageList.FPC.pas BVE.SVG2ImageList.FPC TCustomImageList
Fmx BVE.SVG2ImageList.FMX.pas BVE.SVG2ImageList.FMX TCustomImageList

Description

TSVG2BaseImageList is the parent class for TSVG2ImageList and TSVG2LinkedImageList.

Properties

Methods

Events


TSVGListItem


Vcl BVE.SVG2ImageList.VCL.pas BVE.SVG2ImageList.VCL TCollectionItem TSVGBaseListItem
Fpc BVE.SVG2ImageList.FPC.pas BVE.SVG2ImageList.FPC TCollectionItem TSVGBaseListItem
Fmx BVE.SVG2ImageList.FMX.pas BVE.SVG2ImageList.FMX TCollectionItem

Description

TSVGListItem represents one SVG in the collection of SVG's in TSVG2ImageList.

Properties

Methods


TSVGImageStyle


Vcl BVE.SVG2ImageList.VCL.pas BVE.SVG2ImageList.VCL TCollectionItem TSVGBaseListItem
Fpc BVE.SVG2ImageList.FPC.pas BVE.SVG2ImageList.FPC TCollectionItem TSVGBaseListItem
Fmx BVE.SVG2ImageList.FMX.pas BVE.SVG2ImageList.FMX TCollectionItem

Description

TSVGImageStyle represents one style item in the collection of style items in a TSVG2ImageList. The style item has properties to modify SVG's to create a extra set of images, for example to create a "Disabled" version of the images.

Different styles are created using the XML functionality of "Entities". XML entities are a way of representing an item of data within an XML document, instead of using the data itself.

An entity can be referred to in XML by the Entity name preceded with a "&" and followed by a ";".

Properties

Methods


TSVGImageListResolution


Vcl BVE.SVG2ImageList.VCL.pas BVE.SVG2ImageList.VCL System.Classes.TPersistent
Fpc For FPC/Lazarus version 1.9 and up TCustomImageListResolution is used.
Fmx Delphi XE8 and higher uses a multi resolution bitmap.

Description

In VCL the TSVGImageListResolution is basically an extra image list within an image list but with larger images. The size of the images is defined by the "Width", the height of the images is calculated so that it is scaled proportionally.

Each resolution is one big data block of image count * image width * image height * 4 bytes. The extra resolutions can be used for DPI scaling, another use is for creating multi-layered images, for example icons.

In the VCL SVG image list, the type of scaling is controlled by the "ScalingKind" property, if it is set to "Re-render", than all images are re-rendered if scaling occurs and the extra resolutions are not needed, if the SVG's are very complex and it rendering takes a long time, you can set "ScalingKind" to "From resolutions". The system will then select a resolution that has images of the correct size or slightly larger and if so, resample the image.

The FPC/Lazarus image list supports scaling from version 1.9 onwards. It is basically a collection of image lists where each TCustomImageListResolution represents an image list with a different size of images. It's implementation does not allow for re-rendering of images, so if you need scaling, the extra resolutions must be defined always.

Properties

Methods


TSVG2ImageList


VCL, FMX, FPC/Lazarus


Source Unit Parent
Vcl BVE.SVG2ImageList.VCL.pas BVE.SVG2ImageList.VCL Vcl.Controls.TImageList TSVG2BaseImageList TSVG2CustomImageList
Fpc BVE.SVG2ImageList.FPC.pas BVE.SVG2ImageList.FPC TCustomImageList TSVG2BaseImageList TSVG2CustomImageList
Fmx BVE.SVG2ImageList.FMX.pas BVE.SVG2ImageList.FMX FMX.ImgList.TCustomImageList TSVG2BaseImageList TSVG2CustomImageList

Description

The TSVG2ImageList keeps the collection of SVG's and Styles.

Properties

Methods


TSVG2LinkedImageList


VCL, FMX, FPC/Lazarus


Source Unit Parent
Vcl BVE.SVG2ImageList.VCL.pas BVE.SVG2ImageList.VCL Vcl.Controls.TImageList TSVG2BaseImageList TSVG2CustomLinkedImageList
Fpc BVE.SVG2ImageList.FPC.pas BVE.SVG2ImageList.FPC TCustomImageList TSVG2BaseImageList TSVG2CustomLinkedImageList
Fmx BVE.SVG2ImageList.FMX.pas BVE.SVG2ImageList.FMX FMX.ImgList.TCustomImageList TSVG2BaseImageList TSVG2CustomLinkedImageList

Description

The TSVG2LinkedImageList is almost the same as as a TSVG2ImageList. It has its own list of images and most of TSVG2ImageList properties are present in TSVG2LinkedImageList as well, the main difference is that it does not contain any SVG or Style data, but uses these from the TSVG2ImageList it is linked to.

This makes it possible to centralize the SVG and Style data in one place and use TSVG2LinkedImageList as "agents" on forms, that are set up for the needs of that particular form. Something like the new TVirtualImageList and TImageCollection in Delphi 10.3

Properties

The TSVG2LinkedImageList has two properties for linking with a TSVG2ImageList:


TSVG2Graphic


Source Unit Parent
Vcl BVE.SVG2Graphic.VCL.pas BVE.SVG2Graphic.VCL Vcl.Graphics.TGraphic
Fpc BVE.SVG2Graphic.FPC.pas BVE.SVG2Graphic.FPC TGraphic
Fmx Not available

Description

The TSVG2Graphic is a non-visible component. It is derived from TGraphic and so can be used as any other TGraphic derived classes (like TJPEGImage and TPngImage).

If you look at the initialization section of the BVE.SVG2Graphic.VCL.pas unit, you will see that the TSVG2Graphic is used to register the SVG file format.

initialization
  // Register the file format for load/save dialog
  TPicture.RegisterFileFormat('svg', 'Scalable Vector Graphics', TSVG2Graphic);

Because it is registered, the TOpenPictureDialog will recognize SVG files as a graphic format, and also you can load SVG files into the "Picture" property of Delphi's standard TImage control.

TSVG2Graphic does not support animation.

Properties

Methods

Events

Examples

See the "OpenPicture" project in the Examples folder.

For an example how to use TSVGGraphic see Assign an SVG to a TSVG2Graphic object and assign to a standard TImage.


SVG Controls

The SVG controls TSVG2Control, TSVG2Image and TSVG2LinkedImage and TSVG2WinControl can be used to display and interact with a single SVG image.


Control name Icon SVG source Default AutoViewbox Default Clipping Blended with background
TSVG2Control From file
From strings
from a TSVG2Doc
False True Yes
TSVG2Image From file
From strings
from a TSVG2Doc
True False Yes
TSVG2LinkedImage From an imagelist True False Yes
TSVG2WinControl From file
From strings
from a TSVG2Doc
False True No

The VCL version of the controls, TSVG2Control, TSVG2Image and TSVG2LinkedImage are derived from TSVG2BaseControl which in turn is derived from TGraphicControl. A TGraphicControl is rendered in the paint cycle of its parent TWinControl. What happens is that the SVG image is first rendered to a bitmap and then this bitmap is blended into the background.

As all VCL controls there will be some flicker if the control repaints itself, to avoid flicker you should set the "DoubleBuffered" property of the parent TWinControl to "True".

In case of nested controls, for example you have placed a TSVG2Control on a TPanel, and the TPanel on a form, in that case you should set the "ParentBackground" of TPanel to False and "DoubleBuffered" of TPanel should be set to True.

The FPC/Lazarus and FMX versions do not suffer from this flicker problem.

The TSVG2WinControl is a special control for VCL and FPC. This control is derived from TCustomControl and so is a Windowed control and only available on MS Windows. The SVG image is not blended with the background, so there is no need to render it to an intermediate bitmap or double buffering, this will make this control faster and more suitable for animated SVG images.


TSVG2BaseControl


Source Unit Parent
Vcl BVE.SVG2Control.VCL.pas BVE.SVG2Control.VCL Vcl.Controls.TGraphicControl
Fpc BVE.SVG2Control.FPC.pas BVE.SVG2Control.FPC TGraphicControl
Fmx BVE.SVG2Control.FMX.pas BVE.SVG2Control.FMX FMX.Controls.TControl

Description

TSVG2BaseControl is the base class for the TSVG2Control, TSVG2Image and TSVG2LinkedImage.

All measuring, rendering and interaction is defined in TSVG2BaseControl. The derived SVG controls only add properties for selecting the source for the SVG.

Properties

Methods

Events


TSVG2Control



Source Unit Parent
Vcl BVE.SVG2Control.VCL.pas BVE.SVG2Control.VCL TSVGBaseControl TSVG2CustomControl
Fpc BVE.SVG2Control.FPC.pas BVE.SVG2Control.FPC TSVGBaseControl TSVG2CustomControl
Fmx BVE.SVG2Control.FMX.pas BVE.SVG2Control.FMX TSVGBaseControl TSVG2CustomControl

Description

TSVG2Control is used for displaying and interacting with a single SVG. It inherits most of its properties, methods and events from TSVGBaseControl.

By default the control is clipped by the parent canvas and property "AutoViewBox" is False. The SVG can be read from file, stored in an internal TStringList object or read from a linked TSVG2Doc component.

Properties


TSVG2Image



Source Unit Parent
Vcl BVE.SVG2Image.VCL.pas BVE.SVG2Image.VCL TSVGBaseControl TSVG2CustomImage
Fpc BVE.SVG2Image.FPC.pas BVE.SVG2Image.FPC TSVGBaseControl TSVG2CustomImage
Fmx BVE.SVG2Image.FMX.pas BVE.SVG2Image.FMX TSVGBaseControl TSVG2CustomImage

Description

The TSVG2Image control is also used for displaying and interacting with a single SVG. It inherits most of its properties, methods and events from TSVGBaseControl.

By default the control is not clipped to the parent canvas and "AutoViewBox" is True. The SVG can be read from file or stored in an internal TStringList object.

Properties


TSVG2WinControl



Source Unit Parent
Vcl BVE.SVG2Control.VCL.pas BVE.SVG2Control.VCL TCustomControl TSVG2CustoWinControl
Fpc BVE.SVG2Control.FPC.pas BVE.SVG2Control.FPC TCustomControl TSVG2CustomWinControl
Fmx Not available

Description

The TSVGWinControl is derived from TCustomControl, making it a windowed control. It does not blend with the background, but replaces it, making it faster and so more suited for animated SVG images.

Depending on the render context used, it either blits the rendered SVG image to the control canvas DC, or it uses a DirectX swap chain.

The D3D11 swap chain is by far the most efficient, because there is less need to copy data between CPU and GPU.

There is no TSVGWinControl for FMX available. On Windows the FMX canvas uses DirectX with a swap chain already, this is selected if you use the "SVG2ContextFMX" render context.

Properties

Apart from these properties, TSVGWinControl has the same properties as TSVGBaseControl, except for the following:


TSVG2LinkedImage



Source Unit Parent
Vcl BVE.SVG2ImageList.VCL.pas BVE.SVG2ImageList.VCL TSVGBaseControl TSVG2CustomLinkedImage
Fpc BVE.SVG2ImageList.FPC.pas BVE.SVG2ImageList.FPC TSVGBaseControl TSVG2CustomLinkedImage
Fmx BVE.SVG2ImageList.FMX.pas BVE.SVG2ImageList.FMX TSVGBaseControl TSVG2CustomLinkedImage

Description

The is the same as an TSVGImage only it reads its SVG from a TSVG2ImageList.

Properties


Common methods of the SVG components


Common properties of the SVG components


Common events of the SVG components


Technical design

The following section explains the design and architecture of the package.

Process flow

The purpose of the package is to transform raw SVG data into a something that can be used in an application. This could be a raster image, for example a bitmap in one or more different sizes and/or provide a structure for changing, animating or interacting with the SVG data.

Basically there are two processing stages:

  1. Parsing the input SVG data and producing a document object model (DOM) which is represented in the package by the ISVGRoot interface.
  2. Rendering the DOM to the output.

The input SVG data is basically text structured as XML in which the elements and attributes provide the instructions of how the graphic should be rendered.

The output will almost always be a raster image in the form of a in memory 32bit bitmap with alpha (transparency channel). The alpha can be premultiplied or not depending on the platform. The bitmap can be converted to PNG or JPG of something else and written to file.

The renderer can also output to Windows "Enhanced Metafile" (EMF) files. EMF files consist of GDI+ instructions and can only be produces by the GDI+ render context. See section GDI+ render context for more info about rendering to EMF.

SVG v230 architecture diagram

Zooming in on the rendering process, this can be divided into two sub processes:

  1. Issuing drawing instructions to the render context
  2. Rasterization

Drawing instructions to the render context are similar to the drawing instructions you would give to the canvas: DrawRect, FillRect, DrawEllipse etc.

The ISVGRoot implementation has the capability to render itself to the render context, by issuing the render instructions in the correct order and if necessary apply transformations, rendering to intermediate buffers or loading external resources like referenced svg graphics or bitmaps. This is the core functionality provided by the package.

The rasterization is delegated to the render context implementation. An important part of this is drawing polygons often filled by a scan line algorithm, for which different render context implementations provide different solutions. The decision to delegate the rasterization to an external software package was made as to benefit from existing solutions and not to reinvent the wheel. These external software solutions are either already provided by the operating system (Direct2D, GDI+ for windows, Quartz for Mac) or are graphic packages developed by the Delphi FPC Lazarus community (Graphics32, Aggpas, BGRABitmap). Also this makes it easier to provide a multi platform SVG solution.

See section "Render Contexts" for a complete overview of available render contexts.

A similar approach is taken for fonts and the rendering of text. Rendering text for different languages can be very very complex, so the package provides interfaces to existing solutions from which you can choose: DirectWrite or GDI + Uniscribe for Windows, CoreText for Mac or Freetype if these are not available. An exception is SVG fonts. Officially SVG fonts are no longer supported by the SVG standard, but because they provide a platform independent solution for rendering text, the functionality is kept in the package.

The following diagram shows how text is rendered. The implementation of the ISVGTextLayout interface handles the conversion of text with style information to path data. The style information includes font family, font weight, font style and so on.

With this information a best fitting font is selected from the font source. This is a database of fonts implemented by the ISVGFontSource interface.

You can select the ISVGTextLayout implementation that you want to use with a "global define". For windows you can select DirectWrite or GDI fonts, for Mac you can select CoreText or you can select Freetype which is available for almost every operating system.

If you don't select a specific text layout implementation in the global defines, than only SVG fonts will be rendered.


SVG v230 text rendering diagram

The RenderContext and TextLayout are independent of each other. So on Windows for example, you could use Graphics32 as the render context implementation and DirectWrite as the text layout implementation.

See section "Text layouts and fonts" for a complete overview of available options for rendering text.


Global defines

The package uses include files for setting global parameters using defines. These defines determine which render context is used, which text layout and a number of other settings.

There is one common include file, CompilerSettings.inc on folder Common and there is one include file for each platform: ContextSettingsVCL.inc for VCL in folder Common\Vcl, ContextSettingsFMX.inc for Firemonkey in folder Common\Fmx and ContextSettingsFPC.inc" for FPC Lazarus in folder Common\Fpc.


CompilerSettings.inc

Most defines in this file are switches for handling platform and Delphi version differences and you should not change these. There are a couple of defines that can be changed, these are listed in following table:

Define Meaning Default setting
SVGInlining This will cause some code to be inlined, which can speed up the software at the exspense of a larger executable. On
SVG_2 This will enable some features of the SVG 2 spec. This is needed for CSS animation. On
SvgCtrlExceptionsOff This will disable the exception if you try to load a non existing SVG file in one of the SVG controls. The error messages will be shown in the control window in stead. On
SVGDEBUG This will draw boxes around elements, and is used to debug the code. Off
SVGD2Debug This will set Direct2D in debug mode so Direct2D errors and messages are visible in the Delphi message panel. Off
SVGProfiler This will enable a build in profiler, sometimes used for development of the package. Off
SVGInternetAccess This will allow resources to be loaded from internet. For this Indy components will be compiled in the software and you might have to install libeay32.dll and ssleay32.dll for support for https. Off

ContextSettingsVCL.inc

The defines in this include file determine which render context implementation and which text layout implementation is used in Delphi VCL applications.

Define Meaning Default setting
SVGDirect2d3D11 This will include the Direct2D render context implementation, which renders to a DirectX device context. Off
SVGDirect2dDWIC This will include the Direct2D render context implementation, which renders to a WIC bitmap. On
SVGGDIP This will include the GDI+ render context implementation, which uses GDI+ for rendering. Off
SVGAGGRenderer This will include a render context based on the "Modernised Anti Grain" graphics library. You need to download this library to be able to compile with this setting. Off
SVGGraphics32 This will include a render context based on the "Graphics32" library. You need to download this library to be able to compile with this setting. Off
SVGFontDirectWrite This will include a text layout implementation based on DirectWrite, for using system fonts and text formatting. On
SVGFontGDI This will include a text layout implementation based on GDI and Uniscribe, for accessing system fonts and formatting text. Off
SVGFreetype This Includes the Freetype text layout implementation for accessing system fonts. Text formatting is handled by the SVG control package. The freetype.dll is has to be available on the system. See "Freetype.org" for more info. Off

You are allowed to enable SVGDirect2d3D11, SVGDirect2dDWIC, SVGGDIP and SVGFontDirectWrite and SVGFontGDI all at the same time. In that case the system will select the render context and text layout at run time, out of the options available op target system.

For example you want to use Direct2D using WIC and if Direct2D is not available on the target system you want to use GDI+, you would enable both SVGDirect2dDWIC and SVGGDIP.

The system will try SVGDirect2d3D11 first if enabled, then SVGDirect2dDWIC and last SVGGDIP. The same for text layouts, it will first try SVGFontDirectWrite if enabled then SVGFontGDI.


ContextSettingsFMX.inc

The defines in this include file determine which render context implementation and which text layout implementation is used in Delphi Firemonkey (FMX) applications. Because Firemonkey can build to different operating system targets, there is a set of defines for each possible operating system.

Define Meaning Default setting
Microsoft Windows
SVGDirect2dDWIC This will include the Direct2D render context implementation, which renders to a WIC bitmap. Off
SVGGDIP This will include the GDI+ render context implementation, which uses GDI+ for rendering. Off
SVGAGGRenderer This will include a render context based on the "Modernised Anti Grain" graphics library. You need to download this library to be able to compile with this setting. Off
SVGFMXCanvas This will include a render context based on the default Firemonkey canvas. System fonts will also be accessed via the Firemonkey canvas. On
SVGFontDirectWrite This will include a text layout implementation based on DirectWrite, for using system fonts and text formatting. Off
SVGFontGDI This will include a text layout implementation based on GDI and Uniscribe, for accessing system fonts and formatting text. Off
SVGFreetype This Includes the Freetype text layout implementation for accessing system fonts. Text formatting is handeled by the SVG control package. The freetype.dll is has to be available on the system. Off
Apple OsX or iOS
SVGQUARTZ This will include a context implementation based on Quartz. Off
SVGAGGRenderer This will include a render context based on the "Modernised Anti Grain" graphics library. You need to download this library to be able to compile with this setting. This is not tested on iOS. Off
SVGFMXCanvas This will include a render context based on the default Firemonkey canvas. System fonts will also be accessed via the Firemonkey canvas. On
SVGFontCoreText This will include a text layout implementation based on CoreText, for using system fonts and text formatting. Off
SVGFreetype This Includes the Freetype text layout implementation for accessing system fonts. Text formatting is handeled by the SVG control package. The freetype.dll is has to be available on the system. Off
All other operating systems
SVGFMXCanvas This will include a render context based on the default Firemonkey canvas. System fonts will also be accessed via the Firemonkey canvas. On
SVGFreetype This Includes the Freetype text layout implementation for accessing system fonts. Text formatting is handeled by the SVG control package. The freetype.dll is has to be available on the system. Off

ContextSettingsFPC.inc

The defines in this include file determine which render context implementation and which text layout implementation is used in FPC Lazarus applications. Again because FPC Lazarus can build to different operating system targets, there is a set of defines for each possible operating system.

Define Meaning Default setting
Microsoft Windows
SVGDirect2d3D11 This will include the Direct2D render context implementation, which renders to a DirectX device context. Off
SVGDirect2dDWIC This will include the Direct2D render context implementation, which renders to a WIC bitmap. On
SVGBGRABitmap This will include a render context based on the "BGRABitmap" graphics library. You need to download this library to be able to compile with this setting. Off
SVGGraphics32 This will include a render context based on the "Graphics32" library. You need to download this library to be able to compile with this setting. Off
SVGAGGRenderer This will include a render context based on the "Modernised Anti Grain" graphics library. You need to download this library to be able to compile with this setting. Off
SVGFontDirectWrite This will include a text layout implementation based on DirectWrite, for using system fonts and text formatting. On
SVGFreetype This Includes the "Freetype" text layout implementation for accessing system fonts. Text formatting is handled by the SVG control package. The freetype.dll is has to be available on the system. Off
Darwin
SVGQuartz This will include a context implementation based on Quartz. On
SVGBGRABitmap This will include a render context based on the "BGRABitmap" graphics library. You need to download this library to be able to compile with this setting. Off
SVGGraphics32 This will include a render context based on the "Graphics32" library. You need to download this library to be able to compile with this setting. Off
SVGAGGRenderer This will include a render context based on the "Modernised Anti Grain" graphics library. You need to download this library to be able to compile with this setting. Off
SVGFontCoreText This will include a text layout implementation based on CoreText, for using system fonts and text formatting. On
SVGFreetype This Includes the "Freetype" text layout implementation for accessing system fonts. Text formatting is handeled by the SVG control package. The freetype.dll is has to be available on the system. Off
Linux
SVGBGRABitmap This will include a render context based on the "BGRABitmap" graphics library. You need to download this library to be able to compile with this setting. On
SVGGraphics32 This will include a render context based on the "Graphics32" library. You need to download this library to be able to compile with this setting. Off
SVGAGGRenderer This will include a render context based on the "Modernised Anti Grain" graphics library. You need to download this library to be able to compile with this setting. Off
SVGFreetype This Includes the "Freetype" text layout implementation for accessing system fonts. Text formatting is handeled by the SVG control package. The freetype.dll is has to be available on the system. On
Other operating systems have not been tested...

Render contexts

There are a number of different render context implementations available in the package. Sometimes there is more than one render context available for an operating system, you can select the render context you want to use as explained in the previous section. The quality of the rendered image depends for a large part on the underlying graphics library of the selected render context.


Render contexts for Delphi

Render context Graphics library Supported OS* Platform Target Quality Rendering speed
SVG2ContextD2D Direct2D WIC Windows 7 and higher VCL Bitmap Very good, some minor issues with the stroke-cap. Very good
FMX Bitmap
Direct2D D3D11 Windows 7 and higher VCL Bitmap, Window, Commandlist, Printer
FMX Commandlist, Printer
SVG2ContextGP GDI+ Windows XP and higher VCL Bitmap, DC, Metafile, Printer Good, except the radial gradients Very good
FMX Bitmap, DC, Metafile, Printer
SVG2ContextQuartz Quartz OsX, iOS FMX Bitmap Good, radial gradient spread method not supported Very good
SVG2ContextFMX Implemented by Firemonkey All OS supported by Firemonkey: Windows, OsX, Android, iOS,.. FMX Bitmap, Printer, Canvas This is a rendercontext based on Delphi's FMX canvas implementations:
  • Windows good, gradients and stroking not fully supported
  • OSX good, gradients and stroking not fully supported
  • Android not very good
  • iOS also not good
  • Windows reasonable
  • OsX good
  • Android and iOS slow
SVG2ContextAgg Aggpas Windows, OsX VCL Bitmap Good Good
FMX Bitmap
SVG2ContextGR32 Graphics32 Windows, OsX VCL Bitmap, DC Good Good

Render contexts for FPC Lazarus

Render context Graphics library Supported OS* Target Quality Rendering speed
SVG2ContextD2D Direct2D WIC Windows 7 and higher Bitmap Very good, some minor issues with the stroke-cap in combination with stroke-width. Very good
Direct2D D3D11 Windows 7 and higher Bitmap, Window, Commandlist, Printer
SVG2ContextQuartz Quartz OsX Bitmap Good, radial gradient spread method not supported Very good
SVG2ContextGR32 Graphics32 Windows, Osx,Linux Bitmap Good Good
SVG2ContextAgg Aggpas Windows, OsX, Linux Bitmap Good Good
SVG2BGRABitmap BGRABitmap Windows, Osx, Linux Bitmap Reasonable: some SVG features are not fully supported. Good

*Only the operating system on which the package is tested is shown.


Direct2D render context

Supported operating systems:

Windows Vista, Win7, Win8, Win10

Supported platforms:

Delphi VCL, Delphi FMX (Wic only), FPC Lazarus

Library:

The required header files are included in the package, in folder Common\Platform.

Limitations:

Small issue with stroking in case of zero length path segments.

Unit:

Common\Platform\BVE.SVG2ContextD2D.pas

Remarks:

The Direct2D render context is the main render context for Windows. Quality and speed is very good. It has a great amount of functionality.

There are two types of Direct2D render contexts available, one is based on the "Windows Imaging Component" (WIC), this is available from Direct10 and upwards, the other is based in de "Direct11 Device context", this is the modern graphics platform for windows.

TextFormattingOptions are supported. If "tfoStrings" or "tfoStringsWithPlacedCharacters" is specified then Direct2D function "DrawText" will be used where possible to render text. If rendering to PDF printer, then this text will be selectable (if the font allows embedding in the PDF).


Direct2D WIC render context

The render context based on WIC is only able to render to a bitmap.

The Direct2D WIC render context is selected by enabling the "SVGDirect2dDWIC" define in the context settings include file.

Direct11 Device context render context

The render context based on Direct11 Device context can render to a bitmap, to a Window swap chain, and to a command list. The command list in turn can be send to a printer. It also supports hardware accelerated effects, which are used in the package to implement SVG filter functionality.

The Direct11 Device context render context is selected by enabling the "SVGDirect2d3D11" define in the context settings include file.


GDI+ render context

Supported operating systems:

Windows XP, Vista, Win7, Win8, Win10

Supported platforms:

Delphi VCL, Delphi FMX

Library:

The required header files are included with Delphi

Limitations:

Unit:

Common\Platform\BVE.SVG2ContextGP.pas

Remarks:

GDI+ is older technology but it is still used a lot. Speed is good, quality is reasonable, the main problem is that svg-style radial gradients are not supported.

With GDI+ you can render to a bitmap, to a DC (which can be a printer DC), or to a GDI+ Metafile.

TextFormattingOptions are supported. If "tfoStrings" or "tfoStringsWithPlacedCharacters" is specified then GDI+ function "DrawDriverString" will be used where possible to render text. If rendering to EMF or PDF printer, then this text will be selectable, but this only works for TTF fonts that are installed on Windows.


Rendering to Metafile (EMF)

The  "Graphic Device Interface+"  is mainly used as a fall back in cases DirectX is not available, for example on older XP versions, but the GDI+ render context, in combination with the GDI fonts and Uniscribe text layout, can also be used to convert SVG graphics to Windows "Enhanced Metafile" (EMF) files.

In order to render an SVG graphic to an EMF file, first the GDI+ render context and the GDI text layout must be enabled in the ContextSettingsVCL.inc file. All other render contexts and layouts must be disabled. Then the TSVGContextGP render context must be created with one of the following constructors:

This constructor will write create an EMF file with the supplied filename:

constructor Create(const aFilename: string; const aWidth, aHeight: integer; const aQuality: TSVGBufferQuality = bqHighQuality; const aTextFormattingOptions: TSVGTextFormattingOptions = []); overload;

This constructor will use the supplied TGPMetafile object to write to:

constructor Create(const aMetaFile: TGPMetaFile; const aWidth, aHeight: integer; const aQuality: TSVGBufferQuality = bqHighQuality; const aTextFormattingOptions: TSVGTextFormattingOptions = []); overload;

For an example of rendering to EMF, see "RenderToEMF".


Quartz render context

Supported operating systems:

OsX and iOS

Supported platforms:

Delphi FMX, FPC Lazarus

Library:

The required header files are included with Delphi and FPC Lazarus

Limitations:

The radial gradient does not support the spread-method.

Unit:

Common\Platform\BVE.SVG2ContextQuartz.pas

Remarks:

Quarz is a graphics library for Apple's OsX and iOS.

Speed is good, Quality is overall good but the radial gradient does not support the spread-method.

The Quartz render context can only render to a bitmap.

The IOS support is tested with the IOS simulator only.


FMX canvas render context

Supported operating systems:

All operating systems that Delphi Firemonkey supports for your particular Delphi version.

Supported platforms:

Delphi FMX

Library:

The required files are included with Delphi

Limitations:

The radial gradient does not support the spread-method.

Unit:

Common\Platform\BVE.SVG2ContextFMX.pas

Remarks:

The FMX render context is just a wrapper around Delphi's FMX canvas, On the positive side, this canvas supports a lot of operating systems, including Android (XE5 and higher). On the negative side, quality is not consistent. On Windows, FMX wil use Direct2D and quality will be reasonable but not as good as the dedicated Direct2D render context, because FMX doesn't use all available functionality of Direct2D. On Android for example overall quality of the rendered output is not very good. Also rendering text will not be as precise as with the dedicated render contexts.

The FMX render context can render to a bitmap, a canvas and a printer.


Aggpas render context

Supported operating systems:

Windows, OsX, Linux

Supported platforms:

Delphi VCL, Delphi FMX, FPC/Lazarus

Library

https://github.com/BVerhue/AggPasMod

Limitations:

Small issue with stroking in case of zero length path segments.

Unit:

Common\Platform\BVE.SVG2ContextAgg.pas

Remarks:

Aggpas is a port of the "Anti grain graphics library". Quality is good, speed is good.

The Aggpas render context can only render to a bitmap.


Graphics32 render context

Supported operating systems:

Windows (VCL, FPC) and Osx (FPC)

Supported platforms:

Delphi VCL, FPC/Lazarus

Library

https://github.com/graphics32/graphics32

Limitations:

Because SVG style gradients are not supported in this library, some code is borrowed from Aggpas to implement these using a custom polygon filler.

Unit:

Common\Platform\BVE.SVG2ContextGR32.pas

Remarks:

Graphics32 is a graphics library for Delphi and Lazarus. It is in principle a library for raster image processing, but it also has vector graphics capabilities implemented by the VPR vector graphics engine, the status of this extension is not really clear. Quality is good, speed is good.

The Graphics32 render context can render to a bitmap or a DC.


BGRABitmap render context

Supported operating systems:

Windows, Osx, Linux

Supported platforms:

FPC Lazarus

Library

https://github.com/bgrabitmap/bgrabitmap

Limitations:

Unit:

Common\Platform\BVE.SVG2ContextBGRA.pas

Remarks:

BGRABitmap is a graphics library for FPC Lazarus. Quality is good, speed is good but it has some limitations for rendering SVG graphics.

The BGRABitmap render context can only render to a bitmap.


Text rendering

This paragraph explains how text is rendered by the SVG library and what options are available for text rendering, apart from the SVG text rendering specifications.

The rendering of text is a complex process that involves:


Creating glyph runs and laying out text

The first of these processes, up and until bidi, are done by the text layout object.

Given a piece of text, and a list of regions with certain characteristics (font family name, font weight and so on...) the text layout creates "Glyph runs". The glyph run contains a list of glyphs, advances and a mapping to the original text characters for a certain font face and glyph progress direction.

The glyph run is then used by the SVG text layout algorithm to apply extra positioning if needed.

Because of the complexity for generating glyph runs, we try to use existing libraries when available, the following can be used with the SVG library:

If these are not available, the library uses the build in functionality for selecting fonts and for creating glyph runs.


Font selection

For text rendering, a font is needed. At the moment the library supports three font file types:

TTF and OTF fonts can also be compressed, in that case they have the extension .woff or .woff2.

WOFF files use zlib compression, WOFF2 files use the more complex Brotli compression. The SVG package can decompress these files automatically, but for WOFF2 you will need the Brotli dll binaries.


Organisation of fonts

In the package, fonts are organized in the following manner:

The steps that are taken to select a certain font are as follows.


Registring font collections to an SVG document

In the SVG library the "ISVGFontSource" is implemented by the SVG document (TSVGXMLDocument). Font collections that are valid for the SVG document must be registered with the SVG document.

Initially two font collections are registered with the SVG document:

If during parsing, external or embedded fonts are encountered and if the text layout supports custom font collections, then these fonts are added to a "custom font collection" and registered with the SVG document.

It is also possible to create a custom font collection yourself, load it with fonts and register it with the SVG document. See Custom fonts for details.


Outputting text to the render context

The default is that the glyphs that make up the text are rendered by the render context as "path" graphic primitives (contours). With this kind of rendering all SVG functionality on text is supported, such as rotation of glyphs, text on a path, animation of glyphs, outlining of glyphs and so on.

If this text is rendered to a PDF printer, the text will not be selectable, because only "path" graphic primitives are outputted, so the resulting PDF will not contain any actual text.

For certain render contexts, it is possible to output real text to the PDF printer. In that case we basically delegate the rendering of text to the graphics library underlaying the render context. The downside is that not all SVG functionality on text will be supported, so this will only work for "simple text" (see below), also outlining glyphs is not supported with this.

The render contexts that support this type of text rendering, have an extra parameter in the constructor, namely "TextFormattingOptions".

The TextFormattingOptions can have the following settings:


As mentioned before, the delegation of text rendering to the render context will only take place if a "TextFormattingOption" is specified an the text is "simple". This means:

If this check results in false, then the renderer reverts back to outputting "path" graphic primitives for the glyphs.


Creating PDF files with selectable text in a custom font

In the previous section is explained how to create a PDF with selectable text using the Text formatting option parameter. If this is rendered to a PDF printer, and with the restrictions mentioned in the previous section, than text in the PDF can be selectable.

What if we want to have selectable text in the PDF in a custom font?

On rendering to a printer, the graphical commands are first stored in a command file, for GDI+ this is a EMF Metafile, for Direct2D this is a ID2D1CommandList. These command files must support embedding fonts. In the EMF Metafile this is not the case, so it is only possible to create a PDF with selectable text in a custom font using render context "Direct2D D3D11"in combination with text layout "DirectWrite".

There are further restrictions:


Text layouts

Here follows a list of the text layout implementations that are available in the SVG library. As mentioned in the previous paragraph, the text layout objects manage fonts and create glyph runs. The actual rendering of text is done by the render context.

You select the text layout implementation that you want the library to use in the platform specific context settings include file.

In most cases you would choose the text layout implementation that belongs to the same underlying graphic library as the render context, so "Direct write" for render context "Direct2D Wic" or "Direct2d D3d11" and "GDI fonts and Uniscribe" for render context GDI+ and "CoreText" for render context "Quartz".


Direct write

Supported operating systems:

Windows Vista, Win7, Win8, Win10

Supported platforms:

Delphi FMX, FPC Lazarus

Library:

The required header files are included in the package, in folder Common\Platform

Supports custom font collections:

Yes.

Units

Common\Platform\BVE.SVG2FontDirectWrite.pas


GDI fonts and Uniscribe

Supported operating systems:

Windows XP, Vista, Win7, Win8, Win10

Supported platforms:

Delphi VCL, Delphi FMX

Library:

The required header files are included with Delphi, the header for Uniscribe is included in the package, in folder Common\Platform

Supports custom font collections:

Yes.

Units

Common\Platform\BVE.SVG2FontGDI.pas


Core text

Supported operating systems:

OsX and iOS

Supported platforms:

Delphi FMX, FPC Lazarus

Library:

The required header files are included with Delphi and FPC Lazarus

Supports custom font collections:

Not (yet) implemented

Units

Common\Platform\BVE.SVG2FontCoreText.pas


Freetype

Supported operating systems:

See website

Supported platforms:

Delphi VCL, FMX, FPC Lazarus

Library:

The required header files are included in the package, in folder Common\Platform

Supports custom font collections:

No.

Limitations:

Units

Common\Platform\BVE.SVG2FontFreetype.pas


FMX canvas text layout

Supported operating systems:

All operating systems that Delphi Firemonkey supports for your particular Delphi version.

Supported platforms:

Delphi FMX

Library:

The required files are included with Delphi

Limitations:

Supports custom font collections:

No.

Units

Common\Platform\BVE.SVG2ContextFMX.pas


Core functionality

In this paragraph follows a description of the lower level functions of the package.

  1. Parsing
  2. Creating a rendering context
  3. Rendering
  4. Rendering to a command list
  5. Rendering to a printer
  6. Custom fonts
  7. Element bounds calculations
  8. Mouse pointer events
  9. SVG and CSS Animation
  10. Text to path
  11. Miscellaneous functions

Parsing

Through the process of parsing an XML file representing an SVG image is converted to an in memory tree data structure that is accessible through a DOM interface.

ISVGXMLDocument

In Delphi, the DOM interface is wrapped in TXMLDocument, that has a universal interface to handle DOM implementations from different vendors, for example "MSXML" or "ADOM XML".

The SVG control package also has a DOM implementation, that is used by default, with vendor name "SVG Control DOM".

In the package the TSVGXMLDocument is derived from TXMLDocument to represent an SVG document and classes implementing the different SVG elements are derived from TXMLNode. The package does not use TXMLNode to represent the attributes, because this creates unnecessary overhead and makes parsing very slow.

Specifying the SVG data can be done by property "Filename" or by assigning strings to property "XML" or by loading a stream, this is the same as with TXMLDocument.

Also, as with TXMLDocument, to parse the SVG document we must set property "Active" of TSVGXMLDocument to "True".

In the SVG control package, TSVGXMLDocument and all the classes representing SVG elements that are derived from TXMLNode are used in rendering. They contain temporary variables, cached data, bitmap buffers and so on, to be able to render efficiently, while the DOM document that is wrapped by TSVGXMLDocument and the DOM node wrapped by TXMLNode, contains the actual XML data. To summarize:

ISVGRoot

SVG Documents can reference other SVG documents through external references, so in order to render the SVG image, the system has to have access to all SVG documents that depend on each other, for this all SVG documents necessary for rendering the image are collected in an "SVGRoot object", so if we want to render a SVG document, it must be added to a SVGRoot object first.

Create an SVG Root object

The TSVGRoot class has a derived class for every platform, this is because through the SVG root the renderer creates platform depended objects such as "path data". To create an SVG root in VCL, use the following:

uses
 BVE.SVG2Intf,
 BVE.SVG2Elements.VCL;
...
var
  Root: ISVGRoot;
...
  Root := TSVGRootVCL.Create;

And for FMX:

uses
 BVE.SVG2Intf,
 BVE.SVG2Elements.FMX;
...
var
  Root: ISVGRoot;
...
  Root := TSVGRootFMX.Create;

And for FPC/Lazarus:

uses
 BVE.SVG2Intf,
 BVE.SVG2Elements.FPC;
...
var
  Root: ISVGRoot;
...
  Root := TSVGRootFPC.Create;

Create an SVG XML document and add it to the root

There are basically three ways to create an SVG XML document, from the simplest method to more complex:

  1. Implicitly create an SVG XML document and add it to the root
  2. Explicitly create an SVG XML document and add it to the root
  3. Use the TSVG2Doc component

Implicitly create an SVG XML document and add it to the root:

This method uses the "DocFindOrLoad" function of ISVGRoot. This function first checks if the document specified by the resource identifier is already present in the SVG Root object, if so, it returns it otherwise it creates a XML SVG document adds it to the root, load and parses the data specified by the resource identifier and returns it.

function DocFindOrLoad(const aIri: TSVGIri): ISVGXMLDocument;

Example using this method.

uses
 BVE.SVG2Types,
 BVE.SVG2Intf,
 BVE.SVG2Elements.VCL;
...
var
  Root: ISVGRoot;
...
  Root := TSVGRootVCL.Create;

  // Use DocFindOrLoad function passing the resource id to the file
  if Root.DocFindOrLoad(TSVGIri.Create(isFile, Filename, '')) = nil then
    raise Exception.CreateFmt('Loading %s failed', [Filename]);

Explicitly create an SVG XML document and add it to the root:

uses
 BVE.SVG2Intf,
 BVE.SVG2Elements.VCL;
...
var
  Root: ISVGRoot;
  Doc: ISVGXMLDocument;
...
  Root := TSVGRootVCL.Create;

  // Create document and add to root
  Doc := TSVGXMLDocument.Create(nil);
  Root.DocAdd(Doc);

  // Specify the filename and set doc to active
  Doc.FileName := Filename;
  Doc.Active := True;

Use the TSVG2Doc component, this method is used by the SVG controls if you assign the "SVGDoc" property to a TSVG2Doc component.

The problem is that a XML SVG document object, in this case represented by TSVG2Doc, is part of the rendering tree and cannot be shared between different SVG Root objects, however, since TXMLDocument is only a wrapper around the actual DOM implementation containing the actual data, the DOM document can be shared.

So in this method a XML SVG document is created specially for the SVG Root object, that shares the DOM Document with SVG2Doc.

In this example we assume there is already a SVG2Doc1 component present on the form.

uses
 BVE.SVG2Intf,
 BVE.SVG2Doc,
 BVE.SVG2Elements.VCL;
...
var
  Root: ISVGRoot;
...
  Root := TSVGRootVCL.Create;

  // Set the filename if not already specified and make active
  SVG2Doc1.FileName := Filename;
  SVG2Doc1.Active := True;

  // Create a new XML SVG document
  Doc := Root.CreateSVGXMLDocument(False);

  // Copy the id from the source document
  Doc.Iri := FSVGDoc.Iri;

  // Assign the DOM document from the source document
  Doc.DOMDocument := FSVGDoc.DOMDocument;

  // Add the document to the root
  Root.DocAdd(Doc);

Creating a rendering context

The next thing that is needed for rendering an SVG image is a rendering context. The render context represents a graphics library that is used for the actual drawing of pixels. As explained in the paragraph about render contexts, the SVG control package has a number of different render context implementations that can be used, these are all platform specific.

You select which graphics library to use in the platform specific include file, by enabling one, or in some cases more than one defines, this is explained in paragraph Global defines.

TSVGRenderContextManager

After enabling a render context implementation in the include file, the "TSVGRenderContextManager" class is used to create render context objects. This class is platform specific, so is located in "BVE.SVG2Elements.VCL.pas", and "BVE.SVG2Elements.FMX.pas" and "BVE.SVG2Elements.FPC.pas."

Platform VCL

For VCL, the following class functions are present in TSVGRenderContextManager, to create render contexts:

CreateRenderContextBitmap creates a render context that renders to the supplied bitmap "aBitmap". Some render context implementations support different kinds of render quality, if so you can specify that in the "aQuality" parameter, otherwise this is ignored. If "aPreserveContentBitmap" is True, the SVG image will be rendered over the existing bitmap content, otherwise it will be erased. Preserving the content of the bitmap will involve an extra copy operation, so is slower.

class function CreateRenderContextBitmap(
  const aBitmap: TBitmap;
  const aQuality: TSVGBufferQuality = bqHighQuality;
  const aPreserveContentBitmap: boolean = FALSE): ISVGRenderContext;

The supplied bitmap must be of the correct pixel format, 32 bit, and support alpha channel. To create such a bitmap, the following function of "TSVGRenderContextManager" can be used:

class function CreateCompatibleBitmap(const aWidth, aHeight: integer; const aClear: boolean): TBitmap;

CreateRenderContextDC renders to a device context. A reference to a function that can supply this DC must be supplied in parameter "aProcGetDestHDC". The render context implementation must support rendering to a DC, otherwise this function will throw an exception.

class function CreateRenderContextDC(
  aProcGetDestHDC: TSVGGetDestHDCEvent;
  const aWidth, aHeight: Integer;
  const aQuality: TSVGBufferQuality = bqHighQuality): ISVGRenderContext;

CreateRenderContextWindow renders to a back buffer and through a swap chain process, this back buffer will be presented in the window. For this window, a handle must be supplied in parameter "aWindowHandle". Only render context "SVGDirect2d3D11" supports this kind of rendering.

class function CreateRenderContextWindow(
  aWindowHandle: HWND;
  const aWidth, aHeight: Integer;
  const aQuality: TSVGBufferQuality = bqHighQuality): ISVGRenderContext;

CreateRenderContextCmdList renders to a supplied command list. This is only supported by "SVGGDIP" (GDI+) in which case the command list is a "Metafile" and "SVGDirect2d3D11" in which case the command list is a ID2D1CommandList.

class function CreateRenderContextCmdList(
  aCmdList: ISVGRenderContextCmdList;
  const aWidth, aHeight: Integer;
  const aQuality: TSVGBufferQuality = bqHighQuality;
  const aTextFormattingOptions: TSVGTextFormattingOptions = []): ISVGRenderContext;

The supplied command list must be of the compatible with the render context, To create a command list, the following function of "TSVGRenderContextManager" can be used:

class function CreateCmdList: ISVGRenderContextCmdList;

Command lists in Windows are used to send content to a printer. They can also be used as an "Image". The render contexts that support command lists have the following method to draw a command list:

procedure DrawCmdList(aCmdList: ISVGRenderContextCmdList; const aDestRect: TSVGRect);

Platform FPC

For FPC, the functions to create render contexts are more or less the same as for VCL.

class function CreateRenderContextBitmap(
  aIntfBitmap: ISVGIntfBitmap;
  const aQuality: TSVGBufferQuality = bqHighQuality;
  const aPreserveContentBitmap: boolean = FALSE): ISVGRenderContext;

The supplied bitmap is represented by an interface because the bitmap must be compatible with the render context. To create this bitmap interface, the following global function can be used:

function SVGCreateIntfBitmap(const aWidth, aHeight: integer): ISVGIntfBitmap;

CreateRenderContextDC renders to a device context, this is the same as in VCL.

class function CreateRenderContextDC(
  aProcGetDestHDC: TSVGGetDestHDCEvent;
  const aWidth, aHeight: Integer;
  const aQuality: TSVGBufferQuality = bqHighQuality): ISVGRenderContext;

CreateRenderContextWindow is the same as in VCL.

class function CreateRenderContextWindow(
  aWindowHandle: HWND;
  const aWidth, aHeight: Integer;
  const aQuality: TSVGBufferQuality = bqHighQuality): ISVGRenderContext;

CreateRenderContextCmdList is the same as in VCL, GDI+ is not available in FPC, so it can only be used with "SVGDirect2d3D11".

class function CreateRenderContextCmdList(
  aCmdList: ISVGRenderContextCmdList;
  const aWidth, aHeight: Integer;
  const aQuality: TSVGBufferQuality = bqHighQuality;
  const aTextFormattingOptions: TSVGTextFormattingOptions = []): ISVGRenderContext;

To create a command list, you can use following function:

class function CreateCmdList: ISVGRenderContextCmdList;

Platform FMX

For FMX, the following class function is used to create a render context that targets an FMX bitmap:

class function CreateRenderContextBitmap(const aBitmap: TBitmap): ISVGRenderContext;

If "SVGGDIP" (GDI+) is selected or "SVGDirect2d3D11" then we can also render to a command list:

class function CreateRenderContextCmdList(
  aCmdList: ISVGRenderContextCmdList;
  const aWidth, aHeight: Integer;
  const aQuality: TSVGBufferQuality = bqHighQuality;
  const aTextFormattingOptions: TSVGTextFormattingOptions = []): ISVGRenderContext;

Again, for creating a command list, the following function can be used:

class function CreateCmdList: ISVGRenderContextCmdList;

Example creating a render context

Continuing the example for loading SVG documents in an SVG root, following example creates a render context bases on a bitmap.

uses
 BVE.SVG2Types,
 BVE.SVG2Intf,
 BVE.SVG2Elements.VCL;
...
var
  Root: ISVGRoot;
  RC: ISVGRenderContext;
  Bitmap: TBitmap;
...
  Root := TSVGRootVCL.Create;

  // Use DocFindOrLoad function passing the resource id to the file
  if Root.DocFindOrLoad(TSVGIri.Create(isFile, Filename, '')) = nil then
    raise Exception.CreateFmt('Loading %s failed', [Filename]);

  // Create a bitmap to render on
  Bitmap := TSVGRenderContextManager.CreateCompatibleBitmap(480, 360, True);

  // Create a render context
  RC := TSVGRenderContextManager.CreateRenderContextBitmap(Bitmap);

Rendering

The next step is rendering the actual SVG image.

The following lower level procedure is used for rendering the SVG image on a render context. Parameter "aSVGRoot" is an interface to a SVG Root object with one or more active SVG XML documents. "aContext" is the render context to use. "aWidth" and "aHeight" specify the size of the rendered SVG image. "aRenderOptions", "aAutoViewBox", "aAspectRatioAlign", "aAspectRatioMeetOrSlice" are described in paragraph Common properties of SVG components.

procedure SVGRenderToRenderContext(
  aSVGRoot: ISVGRoot;
  aContext: ISVGRenderContext;
  const aWidth, aHeight: TSVGFloat;
  const aRenderOptions: TSVGRenderOptions = [sroFilters, sroClippath];
  const aAutoViewBox: Boolean = True;
  const aAspectRatioAlign: TSVGAspectRatioAlign = arXMidYMid;
  const aAspectRatioMeetOrSlice: TSVGAspectRatioMeetOrSlice = arMeet);

If we want to render to a bitmap and don't need to do anything special, the following platform specific procedure can be used. This procedure will create the render context implicitly, so we only have to supply the target bitmap.

procedure SVGRenderToBitmap(aSVGRoot: ISVGRoot; aBitmap: TBitmap;
 const aRenderOptions: TSVGRenderOptions = [sroFilters, sroClippath];
 const aAutoViewBox: boolean = True;
 const aAspectRatioAlign: TSVGAspectRatioAlign = arXMidYMid;
 const aAspectRatioMeetOrSlice: TSVGAspectRatioMeetOrSlice = arMeet;
 const aQuality: TSVGBufferQuality = bqHighQuality;
 const aPreserveContentBitmap: boolean = FALSE); overload;

Continuing the example for creating a render context, following adds the code for rendering the SVG image.

uses
 BVE.SVG2Types,
 BVE.SVG2Intf,
 BVE.SVG2Elements.VCL;
...
var
  Root: ISVGRoot;
  RC: ISVGRenderContext;
  Bitmap: TBitmap;
...
  Root := TSVGRootVCL.Create;

  // Use DocFindOrLoad function passing the resource id to the file
  if Root.DocFindOrLoad(TSVGIri.Create(isFile, Filename, '')) = nil then
    raise Exception.CreateFmt('Loading %s failed', [Filename]);

  // Create a bitmap to render on
  Bitmap := TSVGRenderContextManager.CreateCompatibleBitmap(480, 360, True);

  // Create a render context
  RC := TSVGRenderContextManager.CreateRenderContextBitmap(Bitmap);

  // Render the SVG image
  RC.BeginScene;
  try
    RC.Clear(SVGColorNone);

    SVGRenderToRenderContext(
      Root,
      RC,
      Bitmap.Width,
      Bitmap.Height,
      [sroClippath, sroFilters],
      True,
      arXMidYMid,
      arMeet);

  finally
    RC.EndScene;
  end;

If we want to render the SVG image on a different position on the render context than (0, 0), or for example rotate the SVG image, we can set the transformation matrix on the render context before rendering. The following example renders the image 90 degrees rotated. Note that the renderer is not aware of any transformation on the render context, so we supply the "SVGRenderToRenderContext" procedure with "aWdith" and "aHeight" as if the image was not rotated.

uses
 BVE.SVG2Types,
 BVE.SVG2Intf,
 BVE.SVG2Elements.VCL;
...
var
  Root: ISVGRoot;
  RC: ISVGRenderContext;
  Bitmap: TBitmap;
  SaveMatrix: TSVGMatrix;
...
  Root := TSVGRootVCL.Create;

  // Use DocFindOrLoad function passing the resource id to the file
  if Root.DocFindOrLoad(TSVGIri.Create(isFile, Filename, '')) = nil then
    raise Exception.CreateFmt('Loading %s failed', [Filename]);

  // Create a bitmap to render on rotated 90 degrees
  Bitmap := TSVGRenderContextManager.CreateCompatibleBitmap(360, 480, True);

  // Create a render context
  RC := TSVGRenderContextManager.CreateRenderContextBitmap(Bitmap);

  // Render the SVG image
  RC.BeginScene;
  try
    RC.Clear(SVGColorNone);

    SaveMatrix := RC.Matrix;
    try
      RC.MultiplyMatrix(
        TSVGMatrix.CreateTranslation(Bitmap.Width, 0));

      RC.MultiplyMatrix(
        TSVGMatrix.CreateRotation(PI / 2));

      SVGRenderToRenderContext(
        Root,
        RC,
        Bitmap.Height,
        Bitmap.Width,
        [sroClippath, sroFilters],
        True,
        arXMidYMid,
        arMeet);

    finally
      RC.Matrix := SaveMatrix;
    end;
  finally
    RC.EndScene;
  end;

If we just want to render to a bitmap and don't have to do anything special, the example reduces to the following:

uses
 BVE.SVG2Types,
 BVE.SVG2Intf,
 BVE.SVG2Elements.VCL;
...
var
  Root: ISVGRoot;
  Bitmap: TBitmap;
...
  Root := TSVGRootVCL.Create;

  // Use DocFindOrLoad function passing the resource id to the file
  if Root.DocFindOrLoad(TSVGIri.Create(isFile, Filename, '')) = nil then
    raise Exception.CreateFmt('Loading %s failed', [Filename]);

  // Create a bitmap to render on
  Bitmap := TSVGRenderContextManager.CreateCompatibleBitmap(480, 360, True);

  // Render the SVG image
  SVGRenderToBitmap(Root, Bitmap);

Rendering to a command list

A command list represents a sequence of commands that can be recorded and played back in a specific graphical framework. The SVG library supports the "Metafile" (EMF) for GDI+ and "ID2D1CommandList" for Direct2D.

It can be used as an "Image", much in the same way that an SVG can be used as an image, in that it is often "scalable".

In some cases (parts of) the command list are transformed to a raster image, of course, when that happens, the command list or parts of it are no longer scalable. This happens for example if SVG filters, clippaths or masks are rendered to the command list.

In following example an SVG is rendered to a command list and then it is rendered on a bitmap as a pattern.

uses
 BVE.SVG2Types,
 BVE.SVG2Intf,
 BVE.SVG2SaxParser,
 BVE.SVG2Elements,
 BVE.SVG2Elements.VCL;
...
var
  Root: ISVGRoot;
  CmdList: ISVGRenderContextCmdList;
  Bitmap: TBitmap;
  Context: ISVGRenderContext;
  i, j: Integer;
...
  // Render an svg to a command list

  // Create a root object
  Root := TSVGRootVCL.Create;

  if Root.DocFindOrLoad(TSVGIri.Create(isFile, aFilename, '')) = nil then
    raise Exception.CreateFmt('Loading %s failed', [aFilename]);

  // Create a command list to render on
  CmdList := TSVGRenderContextManager.CreateCmdList;

  // Create a rendercontext for the command list
  Context := TSVGRenderContextManager.CreateRenderContextCmdList(CmdList, 50, 50);

  Context.BeginScene;
  try
    SVGRenderToRenderContext(Root, Context, 50, 50);
  finally
    Context.EndScene;
  end;

  // Create a bitmap
  FBitmap := TSVGRenderContextManager.CreateCompatibleBitmap(350, 200, True);

  // Create a rendercontext for the bitmap
  Context := TSVGRenderContextManager.CreateRenderContextBitmap(FBitmap);

  // Draw the command list as a pattern on a bitmap
  Context.BeginScene;
  try
    for i := 0 to 3 do
      for j := 0 to 6 do
      begin
        Context.DrawCmdList(CmdList, SVGRect(i*50, j*50, (i+1)*50, (j+1)* 50));
      end;
  finally
    Context.EndScene;
  end;

Rendering to a printer

Apart from just printing to paper, you can also create PDF files by printing to a PDF printer.

If the SVG does not contain parts that need to be converted to raster images, then the resulting image in the PDF file is also scalable.

The "Target" column in the render context table contains "Printer" if a render context supports printing.

For printing, first a print job needs to be created, this can be done with class function "CreatePrintJob" of the "TSVGRenderContextManager".

class function CreatePrintJob(
  const aJobName: TSVGUnicodeString;
  const aQuality: TSVGBufferQuality = bqHighQuality;
  const aTextFormattingOptions: TSVGTextFormattingOptions = [])): ISVGPrintJob;

This print job will print to the currently selected printer. You can select a printer using the standard print dialog.

By calling "BeginPage" of ISVGPrintJob, we start a new page. The "width" and "height" of the page needs to be supplied. The function returns a render context.

function BeginPage(const aWidth, aHeight: TSVGFloat): ISVGRenderContext;

Next we can fill this page by rendering to this render context. When we are ready with the page, we call procedure "EndPage" of the ISVGPrintJob.

function procedure EndPage;

Finally, when the PrintJob object is released, then it is send to the printer.

In following example a list of SVG's is printed, each on it's own page.

uses
 Vcl.Printers,
 BVE.SVG2Types,
 BVE.SVG2Intf,
 BVE.SVG2SaxParser,
 BVE.SVG2Elements,
 BVE.SVG2Elements.VCL;
...
procedure TForm1.Print(sl: TStringList);
var
  i: Integer;
  Root: ISVGRoot;
  PrintJob: ISVGPrintJob;
  Context: ISVGRenderContext;
begin
  // Select a printer
  if not PrintDialog1.Execute then
    Exit;

  // Create a root object
  Root := TSVGRootVCL.Create;

  // Create a print job
  PrintJob := TSVGRenderContextManager.CreatePrintJob('PrintSVG');

  for i := 0 to sl.Count - 1 do
  begin

    // Load the SVG in the root
    if Root.DocFindOrLoad(TSVGIri.Create(isFile, sl[i], '')) = nil then
      raise Exception.CreateFmt('Loading %s failed', [sl[i]]);

    // Render to page
    Context := PrintJob.BeginPage(Printer.PageWidth, Printer.PageHeight);
    try
      Context.BeginScene;
      try
        SVGRenderToRenderContext(
          Root,
          Context,
          Printer.PageWidth,
          Printer.PageHeight,
          [sroClippath, sroFilters],
          True // scale to page
          );

      finally
        Context.EndScene;
      end;

    finally
      PrintJob.EndPage;
    end;
  end;
end;

Custom fonts

With "custom fonts" is meant, ttf or otf fonts (or compressed versions of these types) that are not installed in de operating system.

SVG fonts are also custom fonts, but these are natively supported by the SVG library.


Specifying a custom font

There are basically three ways to render SVG text with a custom font:

  1. Create a custom font collection programmatically, load it with fonts and register it with the SVG document after parsing.
  2. Specify where to find a font with the "font-face-uri" element.
  3. Specify where to find a font with a css @font-face rule.

Create a custom font collection programmatically

This would be a good approach if we know what custom fonts are needed before the SVG's are parsed, and also if a number of SVG's use the same custom font, because we have to load the font only once.

A new font collection object can be created through the "TSVGRenderContextManager":

class function CreateCustomFontCollection: ISVGCustomFontCollection;

This will only work if a Text layout implementation is specified in the platform specific context settings include file that supports custom font collections, otherwise an exception will be thrown.

These two functions of "ISVGCustomFontCollection" can then be used to add a font to the collection:

procedure LoadFromFile(const aFilename: string);
procedure LoadFromStream(aStream: TStream);

The following example creates a custom font collection, loads it with a custom font. Then, an SVG that references this font by a "font-family" attribute is loaded in a TSVG2Image control. In the "OnAfterParse" handler of TSVG2Image, the custom font collection is registered with the SVG document.

We can only register the font collection with the SVG document after parsing, because the SVG document is created during parsing.

unit Unit1;

interface

uses
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Variants,
  System.Classes,
  Vcl.Graphics,
  Vcl.Controls,
  Vcl.Forms,
  Vcl.Dialogs,
  BVE.SVG2Intf,
  BVE.SVG2Control.VCL,
  BVE.SVG2Image.VCL;

type
  TForm1 = class(TForm)
    SVG2Image1: TSVG2Image;
    procedure SVG2Image1AfterParse(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    FCustomFontCollection: ISVGCustomFontCollection;
  end;

var
  Form1: TForm1;

implementation
uses
  BVE.SVG2Elements.VCL;

const
  Svg =
    '<svg width="10cm" height="3cm" viewBox="0 0 1000 300" xmlns="http://www.w3.org/2000/svg" version="1.1">'
      + '<g font-family="Acme" font-size="45" >'
        + '<text x="80" y="150">This font is called Acme-v11-latin-regular.</text>'
        + '<rect x="1" y="1" width="998" height="298" fill="none" stroke="blue" stroke-width="2" />'
      + '</g>'
    + '</svg>';

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FCustomFontCollection := TSVGRenderContextManager.CreateCustomFontCollection;
  FCustomFontCollection.LoadFromFile('acme-v11-latin-regular.woff');

  SVG2Image1.SVG.Text := Svg;
end;

procedure TForm1.SVG2Image1AfterParse(Sender: TObject);
var
  FontSource: ISVGFontSource;
begin
  if assigned(FCustomFontCollection)
  and Supports(SVG2Image1.SVGRoot.Doc, ISVGFontSource, FontSource) then
    FontSource.FontCollectionRegister(FCustomFontCollection);
end;

end.

Specify a custom font with a "font-face-uri" element

The following SVG specifies a custom font that is loaded on parsing the SVG and automatically added to a custom font collection that in turn is automatically registered with the SVG document.

The "font-face" element, defines the font family name. This must be the same as the family name in the font file.

The "font-face_uri" element specifies by url where the font can be found.

<?xml version="1.0" standalone="no"?>
<svg width="10cm" height="3cm" viewBox="0 0 1000 300" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">

  <defs>
    <font-face font-family="Acme">
      <font-face-src>
        <font-face-uri xlink:href="acme-v11-latin-regular.woff"/>
      </font-face-src>
    </font-face>
  </defs>

  <g font-family="Acme" font-size="45" >
    <text x="80" y="150">This font is called Acme-v11-latin-regular.</text>
    <rect x="1" y="1" width="998" height="298" fill="none" stroke="blue" stroke-width="2" />
  </g>

</svg>

In this example the url does not specify a scheme (file:, http:, https:, data:) so a local file is assumed. The url also does not specify an (absolute) path, so the system will either look for this file in the same folder as the SVG (if the SVG is loaded from file), or in the same folder of the application executable (if the SVG is loaded as text or from a stringlist or from a stream).

It is possible to load fonts directly from the internet, for this to work, "SVGInternetAccess" must be enabled in the CompilerSettings.inc file. Also you probably need the libeay32.dll and ssleay32.dll for https and Brotli dll binaries in case of woff2 compressed fonts. However, loading fonts from internet will almost never be practical, so it would be better to download the font and reference the local file.

Another possibility is to embed the entire font file base64 encoded in the url, so it that case you would not need font files separate from the SVG:

        <font-face-uri xlink:href="data:font/woff2;base64,d09GMgABAAAAAFWEABAAAAAA8jAAAFUmAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG+FCHIFcBmAA ..."/>

Specify a custom font with a css @font-face rule

We can also specify a custom font using css, this works basically the same as with the font-face-uri element.

<?xml version="1.0" standalone="no"?>
<svg width="10cm" height="3cm" viewBox="0 0 1000 300" xmlns="http://www.w3.org/2000/svg" version="1.1">

  <style type="text/css">

    @font-face {
      font-family: "Acme";
      src: url("acme-v11-latin-regular.woff") format("woff");
      }

  </style>

  <g font-family="Acme" font-size="45" >
    <text x="80" y="150">This font is called Acme-v11-latin-regular.</text>
    <rect x="1" y="1" width="998" height="298" fill="none" stroke="blue" stroke-width="2" />
  </g>

</svg>

Element bounds calculations

It is often necessary to know the bounds of elements in an SVG graphic, for example the size of the overall SVG graphic, or the size and location of individual path, rect, text or other elements for implementing mouse pointer interaction.

Calculating the size of the SVG graphic

The size of an SVG graphic can be defined on the outer SVG through the "width" and "height" attributes, the value of these attributes can be absolute, for example "10cm" or relative, for example "100%". In the later case, the size depends on the container. If no "width" or "height" is specified, "100%" is assumed, so the width would be 100% of the container width and the height would be 100% of the container height. In a web browser the container would be some html element, in the SVG control library this would be TSVG2Control or TSVG2Image.

To calculate the size of an SVG graphic, the following method of ISVGRoot can be used. This function needs the bounds of the SVG graphics container as input in case "width" and/or "height" of the SVG is expressed in percentages:

function CalcIntrinsicSize(const aParentBounds: TSVGRect): TSVGRect;

Continuing the example for rendering to a bitmap, following adds the code calculating the size needed for the bitmap.

uses
 BVE.SVG2Types,
 BVE.SVG2Intf,
 BVE.SVG2Elements.VCL;
...
var
  Root: ISVGRoot;
  Bitmap: TBitmap;
  R: TSVGRect;
...
  Root := TSVGRootVCL.Create;

  // Use DocFindOrLoad function passing the resource id to the file
  if Root.DocFindOrLoad(TSVGIri.Create(isFile, Filename, '')) = nil then
    raise Exception.CreateFmt('Loading %s failed', [Filename]);

  // Calculate the size of the SVG image
  R := Root.CalcIntrinsicSize(SVGRect(0, 0, 480, 360));

  // Create a bitmap to render on
  Bitmap := TSVGRenderContextManager.CreateCompatibleBitmap(Round(R.Width), Round(R.Height), True);

  // Render the SVG image
  SVGRenderToBitmap(Root, Bitmap);

Calculating the bounds of the SVG graphic elements

The bounds of the elements making up the SVG graphic are known after a rendering pass.

There is a difference how this is done between version 2.3 and version 2.4 of the package:

In version 2.3 of the package, the "sroEvents" must be specified in the RenderOptions. On rendering, a "map" of all visible elements is created, that can be accessed through property "ObjectStateRoot" of ISVGRoot. For details see documentation of version 2.3

In version 2.4 of the package, the bounds are always calculated and can be accessed through the "CacheList" property of ISVGObject. ISVGObject is the interface for elements that have dimensions.

The "CacheList" is used to store rendering information for an element, that can be reused. To explain why a "Cache" is needed, consider the following SVG graphic:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink">
  <defs>
    <rect id="rect" x="0" y="0" width="80" height="50" fill="green" stroke="none"/>
  </defs>
 
  <use x="20" y="20" xlink:href="#rect" />
  <use x="140" y="20" transform="scale(2)" xlink:href="#rect" />
</svg>

The "rect" element will be rendered twice, through the two "use" elements. From this example it is clear that there are two data structures involved: a document tree, in which the parent of "rect" is the "defs" element and a rendering tree, in which "rect" has two instances, each instance has one of the "use" elements as parent.

So in this case the "rect" element will have two items in its "CacheList", one will contain the rendering info if rendered through the first "use" element, the other if rendered through the second "use".

Below is an example how to get the bounding box of elements in an SVG graphic. In this example, we travel through the document tree and for every element that supports the ISVGObject interface, we draw all instances of its bounding boxes:

procedure PaintObjectBounds(aElement: IXMLNode);
var
  i: Integer;
  SVGObject: ISVGObject;
  Element: ISVGElement;
  Cache: ISVGObjectCache;
  R: TSVGRect;
begin
  if Supports(aElement, ISVGObject, SVGObject) then
  begin
    for Cache in SVGObject.CacheList do
    begin
      R := Cache.ScreenBBox;

      if R.IsUndefined then
        Continue;

      Canvas.Stroke.Kind := TBrushKind.bkSolid;
      Canvas.Stroke.Color := TAlphaColorRec.Green;
      Canvas.Fill.Color := TAlphaColorRec.Green;
      Canvas.DrawRect(RectF(R.Left, R.Top, R.Right, R.Bottom), 0, 0, [], 1.0);
      Canvas.FillText(RectF(R.Left, R.Top, R.Right, R.Bottom),
        SVGObject.LocalName, True, 1.0, [], TTextAlign.taLeading, TTextAlign.taLeading);
    end;
  end;

  for i := 0 to aElement.ChildNodes.Count - 1 do
    if Supports(aElement.ChildNodes[i], ISVGElement, Element) then
      PaintObjectBounds(Element);
 end;
 
 ...
 PaintObjectBounds(FRoot.SVG);
 ...

Mouse pointer events

Mouse pointer events can be generated for elements that have mouse event attributes defined on them. If [sroEvents] is not specified in the RenderOptions of ISVGRoot, than these events are ignored.

The SVG controls have the mouse pointer event functionality built in, on a lower level, the following functions, defined in unit "BVE.SVG2Elements" are used:

function SVGMouseDown(
  const aButton: TMouseButton;
  const aShift: TShiftState;
  const aClientX, aClientY, aScreenX, aScreenY: TSVGFloat;
  aSVGRoot: ISVGRoot): Boolean;
function SVGMouseMove(
  const aShift: TShiftState;
  const aClientX, aClientY, aScreenX, aScreenY: TSVGFloat;
  aSVGRoot: ISVGRoot): Boolean;
function SVGMouseUp(
  const aButton: TMouseButton;
  const aShift: TShiftState;
  const aClientX, aClientY, aScreenX, aScreenY: TSVGFloat;
  aSVGRoot: ISVGRoot): Boolean;

If a call to one of these functions results in an event being generated, an interface of this event, ISVGEvent, is passed to the event handler that is attached to property "OnEvent" of ISVGRoot and if the root object is hosted in one of the SVG controls, it is passed to the "OnSVGEvent" handler of the control. See example Using mouse events

Internally the bounding boxes that are calculated for the rendering tree, as described in the previous paragraph, are used to roughly determine which elements may be under the mouse pointer. Then, depending on the "pointer-events" attribute, the content of these elements are taken in consideration. For a path element for example, the path is first converted to one or more polygons and then it is determined if the point is inside or outside of the polygons.

A more simple method for finding which element is at a specific point, the function "ObjectAt" can be used. This function is also defined in unit "BVE.SVGElements".

function SVGObjectAt(
  aSVGRoot: ISVGRoot;
  const aPt: TSVGPoint;
  const aIfHitTest: Boolean = True): ISVGObject;

If only certain elements in an SVG graphic need to be found through this method, than you can set the "HitTest" property of these elements (ISVGObject) to "True and pass "True" also to the "aIfHitTest" parameter of the function. If no object is found at the specified point, the function will return "nil".

Following example displays the element type and id that is under the mouse pointer in the form caption, on a mouse-up event.

const
  Filename = 'C:\Repository\delphi-svg-control\SVGv240\Img\animated-clock.svg';

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  SVGObject: ISVGObject;
begin
  // Find object at mousepoint
  SVGObject := SVGObjectAt(FRoot, SVGPoint(X, Y), False);

  if assigned(SVGObject) then
    Caption := Format('Localname: %s  ID: %s', [SVGObject.LocalName, SVGObject.ID]);
end;

procedure TForm1.FormPaint(Sender: TObject);
var
  Bitmap: TBitmap;
begin
  // Create a bitmap to render on
  Bitmap := TSVGRenderContextManager.CreateCompatibleBitmap(ClientWidth, ClientHeight, True);
  try
    // Render the SVG image
    SVGRenderToBitmap(FRoot, Bitmap);

    // Draw the Bitmap on the canvas
    Canvas.Draw(0, 0, Bitmap);
  finally
    Bitmap.Free;
  end;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  FRoot := TSVGRootVCL.Create;

  if FRoot.DocFindOrLoad(TSVGIri.Create(isFile, Filename, '')) = nil then
    raise Exception.CreateFmt('Loading %s failed', [Filename]);
end;

SVG and CSS Animation

Version 2.4 of the package supports SVG animation and CSS animation and these are synchronized by a system based on SMIL. Animation using javascript is not supported. Another way for animation that was already available in version 2.3, is simply by modifying attributes programmatically and repainting the SVG graphic within a timer event handler. The execution of SVG and CSS animation is basically the same, except that the animation rules can be defined within the SVG graphic itself using the SVG animation elements and/or a CSS style sheet.

The SMIL implementation of the package is mostly based on the publication "Modeling with Time and Events in Computer Animation", Devendra Kalra and Alan H. Barr.

Timed elements and Timed containers

The structure of SMIL, consists of "Timed elements" and "Timed containers", the "Timed container" is the container for "Timed elements" and other child "Timed containers", so this again is a tree structure.

For SVG animation, the timed elements are represented by the "SVG animation elements", for CSS animation the timed elements are represented by the "Key frames".

In the SVG control package, the SVG XML Document contains the "Master time container". If the SVG document contains SVG animation as well as CSS animation, then the master time container has two child time containers, one for the SVG animation elements and one for the CSS key frames.

Calculating frames

Compared with CSS animation, SVG animation is much more complex, because it's behaviour is not only controlled by the progress of time but also by events. These events can be triggered by user interaction, or by state changes of SVG animation elements.

The publication mentioned above offers a the basic algorithm for processing this behaviour. Basically we want to calculate the state of the system at time + delta time, or interval [time] to [next time]. If no events occur in this interval, the new attribute values can be calculated by interpolation and we have a new frame. However, if events occur, the first event that occurs is selected, the new attribute values are calculated by interpolation to [time + first event time], the new state of all timed elements is determined by the behaviour rule of the event and the whole process is repeated for interval [time + first event time] to [next time]. Eventually, if we reach next time, we have a new frame.

Procedures that control animation

The animation in the package is controlled by methods defined by interface "ISVGAnimationTimerTarget" and these are implemented by TSVGRoot. The user of these methods is the TSVG2AnimationTimer component.

procedure DoAnimationTimerStart;
This method initializes the timed containers and prepares the rendering tree for animation.

procedure DoAnimationTimerStop;
This method re-initializes the timed containers, so we are back to the start of the animation.

procedure DoAnimationTimerPause;
This methods halts the calculation of the frames, so the animation is basically frozen. The animation can be paused before the animation is started. This will have the effect that the moment the animation is started, it is immediately frozen.

procedure DoAnimationTimerUnpause;
This will continue the calculation of frames if the animation was paused.

function DoAnimationTimerSync: TSMILSchedulingState;
This will calculate a new frame based on the current system time, measured from the system time of the previous frame. This function returns "ssFrame" if a new frame was calculated or "ssIdle" if nothing has changed. The amount of times this function is called within a second determines the animation frame rate.

function DoAnimationAdvanceFrame(const aDeltaTime: Cardinal): TSMILSchedulingState;
This function can be used to "step through" the animation, the step size must be specified with "aDeltaTime", all timings are in ms. The function returns "ssFrame" if a new frame was calculated or "ssIdle" if nothing has changed.

Notification of new frames

Objects that have implemented interface "ISVGRootObserver" can be notified of the event of a new frame: "reFrameUpdate", and can then for example repaint themselves. For this they must register themselves with SVG root with the following method:

procedure ObserverRegister(aObserver: ISVGRootObserver);

Setting the animation to an arbitrary time

The following property of ISVGRoot, gets or sets the the elapsed time for the master time container in ms.

property AnimationTime: Cardinal

This can be used to "scroll" through the animation. Normally the animation would be paused first. If we set the animation time in the future, the next frame is calculated based on the interval [current time] to [new time], if the animation is set in the past, the new frame is calculated based on the interval [start time] to [new time], which of course involves a lot more calculations.

Check if an SVG graphic has animations defined

To check if animations are present in an SVG Root object, the following functions of ISVGRoot can be used.

To check if there are SMIL animations, use

function HasSMILAnimation: Boolean;

To check if there are CSS animations, use

function HasCSSAnimation: Boolean;

Animation performance

Overall, animation of SVG graphics can consume a lot of system resources, therefore a number of optimizations are added to the rendering procedures in version 2.4. Another big influence on the performance is the render context that is being used.

Optimizations in rendering procedures

In animation the SVG graphic needs to be rendered often, typically the goal is about 30 times per second. So it makes sense to try to reuse values that are calculated in a previous frame.

One method is using the SVG object cache, that is mentioned in paragraph Calculating the bounds of the SVG graphic elements to store values that can be reused.

The other method is drawing branches of the rendering tree that don't contain animations to a bitmap buffer, and simply drawing this buffer in subsequent frames in stead of rendering the branch. This functionality can be turned on by adding [sroPersistentBuffers] to the RenderOptions. This can have a significant impact on the performance, specially if the "dead branch" has elements that require operations on pixel level, for example "masks", "clippaths" or "filters".

Suitability of render contexts for animation

The render context does the low level rendering of graphics. Some render contexts can use the GPU others are only CPU, this can make a big difference. Sometimes some additional post processing of the bitmap is needed, for example premultiply the pixels, this will bring the performance down. If bitmaps need to transfered from GPU to CPU, this is slow. In some cases particular operations are slow, for example "dashed strokes" are often used in animations but slow on Direct2D.

To compare the different render contexts, a simple 32bit application is used, run without debugging with animation Toy_train_SMIL.svg running with [sroPersistentBuffers] enabled. The requested frames per second is set to 100, then we measure the actual FPC and the processor load.


The measurements only indicate the relative performance between the render contexts and the type of controls. On another computer the results might be totally different. Also in normal situations, we wouldn't set the frame rate to 100, but possibly 30 or so, which would drastically reduce the load on the system. So following table only give a general idea.


Render contexts for Delphi VCL
Render context TSVG2Control TSVG2WinControl
SVG2ContextD2D using D3D11 FPC 65 CPU 7% FPC 64 CPU 4%
SVG2ContextD2D using WIC FPC 64 CPU 9% FPC 64 CPU 9%
SVG2ContextGP FPC 64 CPU 14% FPC 59 CPU 12%
SVG2ContextGR32 FPC 57 CPU 17% FPC 64 CPU 14%
SVG2ContextAgg FPC 16 CPU 23% N.A.

So it seams that in this setup we never get more than 64 FPS, that may be because of the amount of resources windows allocates to the application process. Also, since the animation thread is synchronized with the main thread, we can go no faster than windows messages are being processed.


Render contexts for FPC/Lazarus
Render context TSVG2Control TSVG2WinControl
SVG2ContextD2D using D3D11 FPC 65 CPU 7% FPC 64 CPU 6%
SVG2ContextD2D using WIC FPC 64 CPU 11% FPC 65 CPU 11%
SVG2ContextGR32 FPC 64 CPU 22% FPC 64 CPU 18%
SVG2BGRABitmap FPC 38 CPU 14% N.A.
SVG2ContextAgg FPC 16 CPU 25% N.A.

Render contexts for Delphi FMX
Render context TSVG2Control TSVG2WinControl
SVG2ContextD2D using WIC FPC 64 CPU 8% N.A.
SVG2ContextFMX FPC 64 CPU 11% N.A.
SVG2ContextGP FPC 64 CPU 12% N.A.
SVG2ContextAgg FPC 17 CPU 24% N.A.
SVG2ContextQuartz N.A.

Performance on Android and iOs are not tested, but good performance is not likely there.


Text to path

Rendering text is the most complex part of SVG rendering. For rendering text, fonts are needed, these have to be available on the system where the SVG image is rendered. If the font is not available on the host system, a different, default font is selected, which sometimes does not result in the image that was intended.

To ensure that an image looks the same on every system, independent of the installed fonts, it is possible to convert text element to path elements. The steps to do this are as follows.

  1. Parse and then render the SVG image with render options [sroTextToPath] enabled.

  2. Make a copy of the SVG root object.

  3. Save the XML strings of SVG XML document of the copied SVG Root object.

Below is a very simple example how this looks in code, another example can be found in the programming examples section.

uses
  ...
  BVE.SVG2Types,
  BVE.SVG2Intf,
  BVE.SVG2Elements.VCL;

const
  svg_text =
      '<svg>'
      + '<text font-family="Times new roman" font-size="24">Text to path example</text>'
    + '</svg>';

procedure TForm1.Button1Click(Sender: TObject);
var
  Bitmap: TBitmap;
  Root, DestRoot: ISVGRoot;
  Doc: ISVGXMLDocument;
begin
  Bitmap := TSVGRenderContextManager.CreateCompatibleBitmap(1, 1, TRUE);

  Root := TSVGRootVCL.Create;

  Doc := Root.CreateSVGXMLDocument(False);
  Root.DocAdd(Doc);

  Doc.LoadFromXML(svg_text);
  Doc.Active := True;

  SVGRenderToBitmap(Root, Bitmap, [sroTextToPath]);

  DestRoot := Root.CreateCopy;

  Memo1.Lines.Assign(DestRoot.Doc.XML);
end;

Miscellaneous functions


Miscellaneous functions for VCL

Unit BVE.SVG2Elements.VCL

Save a bitmap as in Png format to stream:

procedure SaveBitmapAsPng(aBitmap: TBitmap; aStream: TStream); overload;

Save a bitmap as in Png format to file:

procedure SaveBitmapAsPng(aBitmap: TBitmap; const aFilename: string); overload;

Save a bitmap as in Jpeg format to stream:

procedure SaveBitmapAsJpg(aBitmap: TBitmap; aStream: TStream); overload;

Save a bitmap as in Jpeg format to file:

procedure SaveBitmapAsJpg(aBitmap: TBitmap; const aFilename: string); overload;

Convert a list of bitmaps to a Windows Icon:

function SVGCreateIcon(aBitmapList, aMaskList: TList): TIcon;

Fill a bitmap with a color:

procedure FillBitmap(aBitmap: TBitmap; const aColor: TSVGColor);

Convert a TAlphaColor (TSVGColor) to a TColor:

function SvgColorToColor(Value: TAlphaColor): TColor;

Convert a TColor to a CSS color:

function ColorToCSSColor(Value: TColor): string;

Convert a TColor to TSVGColor (TAlphaColor):

function ColorToSvgColor(Value: TColor): TSVGColor;


Miscellaneous functions for FPC

Unit BVE.SVG2Elements.FPC

Save a bitmap as in Png format to stream:

procedure SaveBitmapAsPng(aBitmap: TBitmap; aStream: TStream); overload;

Save a bitmap as in Png format to file:

procedure SaveBitmapAsPng(aBitmap: TBitmap; const aFilename: string); overload;

Save a bitmap as in Jpeg format to stream:

procedure SaveBitmapAsJpg(aBitmap: TBitmap; aStream: TStream); overload;

Save a bitmap as in Jpeg format to file:

procedure SaveBitmapAsJpg(aBitmap: TBitmap; const aFilename: string); overload;

Convert a list of bitmaps to a Windows Icon:

function SVGCreateIcon(aBitmapList, aMaskList: TList): TIcon;

Convert a TSVGColor to a TColor:

function SvgColorToColor(const aValue: TSVGColor): TColor;

Convert a TColor to a TSVGColor:

function ColorToSvgColor(const aValue: TColor): TSVGColor;


Supported SVG features

The following table lists the supported SVG functionality for version 2.4 of the SVG package. The functionality marked with "Yes" is supported in basis but that doesn't always mean for 100%. If there is a known significant part of functionality missing, it is mentioned in the remark.


Limitations


Elements

The following list indicates the implementation status of "svg elements" in the SVG library.


SVG Element Implemented Type Remarks
<a> No The a element is converted to a group element
<altGlyph> No
<altGlyphDef> No

<altGlyphItem> No

<animate> Yes ISVGAnimate Version 2.4
<animateColor> Yes TSVGAnimateColor Version 2.4
<animateMotion> Yes ISVGAnimateMotion Version 2.4
<animateTransform> Yes TSVGAnimateTransform Version 2.4
<circle> Yes ISVGCircle
<color-profile> No

<cursor> No

<clipPath> Yes ISVGClipPath
<defs> Yes ISVGDefs
<desc> Yes ISVGDesc
<ellipse> Yes ISVGEllipse
<feBlend> Yes ISVGFilterBlend
<feColorMatrix> Yes ISVGFilterColorMatrix
<feComponentTransfer> Yes ISVGFilterComponentTransfer
<feComposite> Yes ISVGFilterComposite
<feConvolveMatrix> Yes ISVGFilterConvolveMatrix
<feDiffuseLighting> Yes ISVGFilterDiffuseLighting
<feDisplacementMap> Yes ISVGFilterDisplacementMap
<feDistantLight> Yes ISVGFilterDistantLight
<feFlood> Yes ISVGFilterFlood
<feFuncA> Yes ISVGFilterFunc
<feFuncB> Yes ISVGFilterFunc
<feFuncG> Yes ISVGFilterFunc
<feFuncR> Yes ISVGFilterFunc
<feGaussianBlur> Yes ISVGFilterGuassianBlur
<feImage> Yes ISVGFilterImage
<feMerge> Yes ISVGFilterMerge
<feMergeNode> Yes ISVGFilterMergeNode
<feMorphology> Yes ISVGFilterMorphology
<feOffset> Yes ISVGFilterOffset
<fePointLight> Yes ISVGFilterPointLight
<feSpecularLighting> Yes ISVGFilterSpecularLighting
<feSpotLight> Yes ISVGFilterSpotlight
<feTile> Yes ISVGFilterTile
<feTurbulence> Yes ISVGFilterTurbulence
<filter> Yes ISVGFilter
<font> Yes ISVGFont
<font-face> Yes ISVGFontFace
<font-face-format> Yes ISVGFontFaceFormat
<font-face-name> Yes ISVGFontFaceName
<font-face-src> Yes ISVGFontFaceSrc
<font-face-uri> Yes ISVGFontFaceUri
<foreignObject> No
<g> Yes ISVGGroup
<glyph> Yes ISVGGlyph
<glyphRef> No

<hkern> Yes ISVGHKern
<image> Yes ISVGImage
<line> Yes ISVGLine
<linearGradient> Yes TSVGLinearGradient
<marker> Yes ISVGMarker
<mask> Yes ISVGMask
<metadata> No

<missing-glyph> No ISVGGlyph is used
<mpath> Yes ISVGMPath Version 2.4
<path> Yes ISVGPath
<pattern> Yes ISVGPattern
<polygon> Yes ISVGPolygon
<polyline> Yes ISVGPolyline
<radialGradient> Yes ISVGRadialGradient Some render contexts do not fully support this, see "Render contexts".
<rect> Yes ISVGRect
<script> No

<set> Yes ISVGSet Version 2.4
<stop> Yes ISVGGradientPoint
<style> Yes
<svg> Yes ISVG
<switch> Yes ISVGSwitch
<symbol> Yes ISVGSymbol
<text> Yes ISVGText
<textPath> Yes ISVGTextPath
<title> Yes ISVGTitle
<tref> Yes ISVGTextRef
<tspan> Yes ISVGSpan
<use> Yes ISVGUse
<view> No
<vkern> Yes ISVGVKern

Presentation attributes

This is a list indicates the implementation status of the "presentation attributes" in the SVG control library.


Attribute Implemented Type Remarks
alignment-baseline No

baseline-shift Yes TSVGBaselineShift
clip-path Yes string
clip-rule Yes TSVGFillRule
clip Yes TSVGDimRect
color-interpolation-filters Yes TSVGColorSpace
color-interpolation Yes TSVGColorSpace
color-profile No

color-rendering No

color Yes TSVGColor
cursor No

direction Yes TSVGDirection
display Yes TSVGDisplay
dominant-baseline No

enable-background No
This attribute is to be removed from the SVG specs. and is replaced by the "isolation" attribute
fill-opacity Yes TSVGFloat
fill-rule Yes TSVGFillRule
fill Yes TSVGPaint
filter Yes string
flood-color Yes TSVGPaint
flood-opacity Yes TSVGFloat
font-family Yes TFontName
font-size-adjust No

font-size Yes TSVGFontSize
font-stretch Yes TSVGFontStretch
font-style Yes TSVGFontStyle
font-variant Yes TSVGFontVariant
font-weight
Yes TSVGFontWeight
glyph-orientation-horizontal
No


glyph-orientation-vertical
No


image-rendering
No


isolation Yes TSVGIsolationMode
Kerning
No


letter-spacing Yes TSVGSpacing
lighting-color Yes TSVGPaint
marker Yes string
marker-start Yes string
marker-mid Yes string
marker-end Yes string
mask Yes
string

opacity Yes TSVGFloat
overflow Yes TSVGOverflow
pointer-events Yes TSVGPointerEventsType
shape-rendering
No


stop-color Yes TSVGPaint

stop-opacity Yes TSVGFloat

stroke Yes TSVGPaint
stroke-opacity Yes TSVGFloat
stroke-width Yes TSVGDimension
stroke-linecap Yes TSVGStrokeCap
stroke-linejoin Yes TSVGStrokeJoin
stroke-miterlimit Yes TSVGFloat

stroke-dasharray Yes TSVGDimArray
stroke-dashoffset Yes TSVGDimension
text-anchor Yes TSVGTextAnchor
text-decoration Yes
TSVGTextDecoration

text-rendering No

unicode-bidi Yes TSVGUnicodeBidi

visibility Yes TSVGVisibility
word-spacing
No


writing-mode Yes TSVGWritingMode

Events

This is a list indicates the implementation status of the "SVG events" in the SVG control library.


Event name Implemented Type Remarks
onfocusin No UIEvent
onfocusout No UIEvent
onactivate No UIEvent
onclick Yes MouseEvent
onmousedown Yes MouseEvent
onmouseup Yes MouseEvent
onmouseover Yes MouseEvent
onmousemove Yes MouseEvent
onmouseout Yes MouseEvent
DOMSubtreeModified No MutationEvent
DOMNodeInserted No MutationEvent
DOMNodeRemoved No MutationEvent
DOMNodeRemovedFromDocument No MutationEvent
DOMNodeInsertedIntoDocument No MutationEvent
DOMAttrModified No MutationEvent
DOMCharacterDataModified No MutationEvent
onload No none
onunload No none
onabort No none
onerror No none
onresize No none
onscroll No none
onzoom No none
onbegin Yes none
onend Yes none
onrepeat Yes none


CSS

CSS is implemented as case-sensitive at the moment. Anything that is not on the list is not supported.

Selector types Implemented
* (All) Yes
<element> Yes
Selectors
. (Class) Yes
# (ID) Yes
Attribute Yes
PseudoClass Yes
PseudoClassTypes
FirstChild Yes
LastChild (not possible with the sax-parser)
No
NthChild Yes
Language Yes
others... No
Attribute evaluators
Name only Yes
= (Equal) Yes
~= (Contains word) Yes
|= (Begins with word) Yes
^= (Begins with part of word) Yes
$= (Ends with word) Yes
*= (Contains part of word) Yes
others... No
Combinators
 (Descendant) Yes
> (Direct descendant) Yes
+ (Adjacent) Yes
~ (Preceding) Yes
others... No
Properties
See list of attributes

Programming examples

Here are some examples on how to use the package for rendering and modifying SVG graphics programmatically.

  1. Assign an SVG to a TSVG2Image
  2. Create a simple digital clock
  3. Create a filter graph
  4. Assign an SVG to a TSVG2Graphic object and assign to a standard TImage
  5. Render SVG programmatically
  6. Find and element the easy way
  7. Find and element the hard way: traverse the DOM tree
  8. Change an attribute
  9. Setting style attributes
  10. Add and remove elements
  11. Add an SVG fragment
  12. Find element under the mouse pointer
  13. Using mouse events
  14. Loading files from internet
  15. Animation control
  16. Text to path

The source code for these examples can be found in Examples\ExamplesFromDoc.


Assign an SVG to a TSVG2Image

Put a TSVG2Image on a form.

In the "OnCreate" event handler of the form put the following (replace.. with a valid path to the img folder)

Code

procedure TForm1.FormCreate(Sender: TObject);
begin
  SVG2Image1.Filename := '..\Img\Butterfly.svg';
end;

Create a simple digital clock

Put a TSVG2Image on a form and set property "Align" of the SVG2Image1 to "alClient".

For Delphi VCL set the "DoubleBuffered" property of the form to "True", to surpress flicker.

Put a TTimer on the form.

Add the following code to the unit:

Code

const
  svg_clock =
     '<svg xmlns="http://www.w3.org/2000/svg" version="1.1"'
    + ' viewBox="0 0 200 80">'
     + '<g font-family="courier new" font-size="24" text-anchor="middle">'
       + '<text x="100" y="30" fill="blue" >%s</text>'
       + '<text x="100" y="60" fill="blue" >%s</text>'
     + '</g>'
     + '<rect x="1" y="1" width="198" height="78" fill="none" stroke="blue" stroke-width="2" />'
   + '</svg>';

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  SVG2Image1.SVG.Text := Format(svg_clock, [DateToStr(Now), TimeToStr(Now)]);
  SVG2Image1.Repaint;
end;

Create a filter graph

Put a TSVG2Image on a form and set property "Align" of the SVG2Image1 to "alClient".

For Delphi VCL set the "DoubleBuffered" property of the form to "True", to surpress flicker.

Create an "OnCreate" handler on the form.

Add the following code to the unit.

Code

uses
  BVE.SVG2Types;

const
  // Filter borowed from here: http://luxor-xul.sourceforge.net/talk/jug-nov-2002/slides.html
  svg_filter = '<filter id="%s" width="%s" height="%s">%s</filter>';
  svg_filter_gaussian =
    '<feGaussianBlur in="%s" result="%s" stdDeviation="%d"  />';
  svg_filter_offset =
    '<feOffset in="%s" result="%s" dx="%d" dy="%d" />';
  svg_filter_composite =
    '<feComposite in="%s" in2="%s" result="%s" operator="%s" />';
  svg_filter_spec_light =
    '<feSpecularLighting in="%s" result="%s"'
    + ' surfaceScale="%d" specularConstant="%d" specularExponent="%d"'
    + ' kernelUnitLength="1" lighting-color = "%s">%s</feSpecularLighting>';
  svg_filter_light_distant =
    '<feDistantLight azimuth="%d" elevation="%d" />';

  svg_filter_text =
      '<svg>'
      + '%s'
      + '<g font-size="100" font-weight="bold">'
        + '<text x="20" y="320" fill="%s" filter="url(#myFilter)">%s</text>'
      + '</g>'
    + '</svg>';

procedure TForm1.FormCreate(Sender: TObject);
var
  Filter: string;
begin
  SVG2Image1.RenderOptions := [sroFilters, sroClippath];

  Filter :=
    Format(svg_filter, ['myFilter', '120%', '150%',
        Format(svg_filter_gaussian, ['SourceAlpha', 'gauss1', 6])
      + Format(svg_filter_offset, ['gauss1', 'offset', 5, 5])
      + Format(svg_filter_composite, ['SourceGraphic', 'offset', 'comp1', 'over'])
      + Format(svg_filter_gaussian, ['SourceAlpha', 'gauss2', 2])
      + Format(svg_filter_spec_light, ['gauss2', 'spec', -3, 1, 16, 'white',
            Format(svg_filter_light_distant, [45, 45])
            ])
      + Format(svg_filter_composite, ['spec', 'SourceGraphic', 'comp2', 'in'])
      + Format(svg_filter_composite, ['comp2', 'comp1', 'comp3', 'over'])
    ]);

  SVG2Image1.SVG.Text := Format(svg_filter_text, [filter, 'red', 'SVG rocks!']);
end;

Assign an SVG to a TSVG2Graphic object and assign to a standard TImage

The TSVG2Graphic class is derived from TGraphic, so you can use it in a standard (Delphi or FPC) TImage object.

Put a TImage object on a form.

Set the "Align" property of the Image1 object to "alClient".

Set the "Stretch" property of the Image1 object to "True"

Put the following code in the "OnCreate" handler of the form (replace .. with a valid path to the img folder):

Code

uses
{$IFnDEF FPC}
  BVE.SVG2Graphic.VCL;
{$ELSE}
  BVE.SVG2Graphic.FPC;
{$ENDIF}

procedure TForm1.FormCreate(Sender: TObject);
var
  Graphic: TSVG2Graphic;
begin
  Graphic := TSVG2Graphic.Create;
  try
    Graphic.LoadFromFile('..\Img\Butterfly.svg');
    Image1.Picture.Assign(Graphic);
  finally
    Graphic.Free;
  end;
end;

Render SVG programmatically

Here is an example how to load an SVG graphic from file, parse it and render it to a bitmap.

For FPC Lazarus, there is a small difference, we render to an interface to a bitmap in stead of to a bitmap directly. This has to do with differences in widget set implementations.

Put a TButton on a form and put a TOpenFIle dialog object on the form.

On the Button1 "OnClick" event handler put the following.

Add the following code to the unit.

Code

uses
{$IFnDEF FPC}
  BVE.SVG2Elements.VCL,
{$ELSE}
  BVE.SVG2Elements.FPC,
{$ENDIF}
  BVE.SVG2Intf,
  BVE.SVG2Types,
  BVE.SVG2SaxParser;

procedure TfrmRenderProgrammatically.Button1Click(Sender: TObject);
{$IFnDEF FPC}
var
  FileName: string;
  SVGParser: TSVGSaxParser;
  SVGRoot: ISVGRoot;
  Bitmap: TBitmap;
  R: TSVGRect;
begin
  // Code for Delphi VCL

  if OpenDialog1.Execute then
  begin
    FileName := OpenDialog1.FileName;

    // Create a root to store the SVG rendering tree

    SVGRoot := TSVGRootVcl.Create;

    // Create a SAX parser instance

    SVGParser := TSVGSaxParser.Create(nil);
    try

      // Parse SVG document and build the rendering tree

      SVGParser.Parse(FileName, SVGRoot);

      // Create a bitmap

      Bitmap := TBitmap.Create;
      try
        Bitmap.PixelFormat := TPixelFormat.pf32bit;   // 32bit bitmap
        Bitmap.AlphaFormat := TAlphaFormat.afDefined; // Enable alpha channel

        R := SVGRoot.CalcIntrinsicSize(SVGRect(0, 0, Width, Height)); // Calc size of SVG Graphic, passing the height of the container (Form)
        Bitmap.SetSize(Round(R.Width), Round(R.Height)); // Set the bitmap size

        Bitmap.Canvas.Brush.Color := clNone; // Fill background with transparent
        Bitmap.Canvas.FillRect(Rect(0, 0, Bitmap.Width, Bitmap.Height));

        // Render the SVG onto the bitmap

        SVGRenderToBitmap(
          SVGRoot, // The root containing the rendering tree
          Bitmap   // The destination bitmap
          );

        // Do something with the bitmap...
        Canvas.Draw(0, 0, Bitmap);

      finally
        Bitmap.Free;
      end;

    finally
      SVGParser.Free;
    end;
  end;
end;

{$ELSE}

var
  FileName: string;
  SVGParser: TSVGSaxParser;
  SVGRoot: ISVGRoot;
  IntfBitmap: ISVGIntfBitmap;
  Bitmap: TBitmap;
begin
  // Code for FPC Lazarus

  if OpenDialog1.Execute then
  begin
    FileName := OpenDialog1.FileName;

    // Create a root to store the SVG rendering tree

    SVGRoot := TSVGRootFpc.Create;

    // Create a SAX parser instance

    SVGParser := TSVGSaxParser.Create(nil);
    try

      // Parse SVG document and build the rendering tree

      SVGParser.Parse(FileName, SVGRoot);

      // Create an interface bitmap.
      // Because of differences in widget set implementations we cannot
      // render to a bitmap directly, but must use an interface with the
      // appropriate implementation.

      IntfBitmap := SVGCreateIntfBitmap(480, 320);

      // Render the SVG onto the interface bitmap

      SVGRenderToBitmap(
        SVGRoot,    // The root containing the rendering tree
        IntfBitmap  // The destination bitmap
        );

      // Create a bitmap from the interface bitmap
      Bitmap := IntfBitmap.CreateBitmap;
      try

        // Do something with the bitmap...
        Canvas.Draw(30, 30, Bitmap);

      finally
        Bitmap.Free;
      end;

    finally
      SVGParser.Free;
    end;
  end;
end;
{$ENDIF}

Find and element the easy way

Put a TSVG2Image on a form and set property "Align" of the SVG2Image1 to "alClient".

For Delphi VCL set the "DoubleBuffered" property of the form to "True", to surpress flicker.

Create an "OnCreate" and an "OnKeyDown" handler on the form.

Add the following code to the unit.

Code

uses
  BVE.SVG2Intf;

const
  svg_text =
     '<svg viewBox="0 0 200 80">'
     + '<g font-family="courier new" text-anchor="middle">'
       + '<text id="text1" x="100" y="30" font-size="16" fill="blue" ></text>'
       + '<text id="text2" x="100" y="60" font-size="24" fill="red" ></text>'
     + '</g>'
     + '<rect x="1" y="1" width="198" height="78" fill="none" stroke="blue" stroke-width="2" />'
   + '</svg>';

procedure TForm1.FormCreate(Sender: TObject);
begin
  SVG2Image1.SVG.Text := svg_text;
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  Element: ISVGElement;
  ShiftValue: string;
begin
  // Find element with id = 'text1'
  Element := SVG2Image1.SVGRoot.Element['text1'];
  if assigned(Element) then
  begin
    ShiftValue := '';
    if ssShift in Shift then
      ShiftValue := ShiftValue + '[Shift]';
    if ssAlt in Shift then
      ShiftValue := ShiftValue + '[Alt]';
    if ssCtrl in Shift then
      ShiftValue := ShiftValue + '[Ctrl]';

    Element.Text := ShiftValue;
  end;

  // Find element with id = 'text2'
  Element := SVG2Image1.SVGRoot.Element['text2'];
  if assigned(Element) then
  begin
    Element.Text := IntToStr(Key);
  end;

  SVG2Image1.Repaint;
end;

Find and element the hard way: traverse the DOM tree

Sometimes you need to evaluate all nodes in an SVG graphic.

The following code traverses the DOM tree and outputs the structure to a memo.

Put a TSVG2Image on a form.

Put a TMemo on the form.

Add an "OnCreate" event handler to the form.

Add an "OnAfterParse" event handler to the SVG2Image1 object.

Add the following code to the unit:

Code

uses
  ...
{$IFnDEF FPC}
  XML.XMLIntf;
{$ELSE}
  BVE.SVG2Types;
{$ENDIF}

type
  TForm1 = class(TForm)
    SVG2Image1: TSVG2Image;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure SVG2Image1AfterParse(Sender: TObject);
  private
    procedure DoNode(aLevel: integer; aNode: IXMLNode);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  BVE.SVG2Intf;

const
  svg_text =
     '<svg viewBox="0 0 200 80">'
     + '<g font-family="courier new" text-anchor="middle">'
       + '<text id="text1" x="100" y="30" font-size="16" fill="blue" >Blue</text>'
       + '<text id="text2" x="100" y="60" font-size="24" fill="red" >Red</text>'
     + '</g>'
     + '<rect x="1" y="1" width="198" height="78" fill="none" stroke="blue" stroke-width="2" />'
   + '</svg>';

type
  TOnNodeEvent = procedure(aLevel: integer; aNode: IXMLNode) of object;

procedure Traverse(aNode: IXMLNode; aLevel: integer; aProcNode: TOnNodeEvent);
var
  i: integer;
begin
  aProcNode(aLevel, aNode);
  for i := 0 to aNode.ChildNodes.Count - 1 do
    Traverse(aNode.ChildNodes[i], aLevel + 1, aProcNode);
end;

procedure TForm1.DoNode(aLevel: integer; aNode: IXMLNode);
var
  Info: string;
begin
  Info := '<' +  aNode.LocalName + '>';
  if aNode.HasAttribute('id') then
    Info := Info + ' id: ' + aNode.Attributes['id'];

  Memo1.Lines.Add(StringOfChar(' ', aLevel*4) + Info);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SVG2Image1.SVG.Text := svg_text;
end;

procedure TForm1.SVG2Image1AfterParse(Sender: TObject);
begin
  Memo1.Clear;
{$IFnDEF FPC}
  Traverse(SVG2Image1.SVGRoot.SVG, 0, DoNode);
{$ELSE}
  Traverse(SVG2Image1.SVGRoot.SVG, 0, @DoNode);
{$ENDIF}
end;


Change an attribute

The following example changes the "transform" attribute of a group element.

For Delphi VCL set the "DoubleBuffered" property of the form to "True", to surpress flicker.

Put a TSVG2Image on a form and set property "Align" of the SVG2Image1 to "alClient".

Put a TTimer on the form.

Add an "OnCreate" event handler to the form.

Add an "OnAfterParse" event handler to the SVG2Image1 object.

Add an "OnTimer" event handler on the Timer1 object.

Set the folowing properties of Timer1:

"Enabled = False"

"Interval = 25"

Add the following code to the unit:

Code

type
  TForm1 = class(TForm)
    SVG2Image1: TSVG2Image;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure SVG2Image1AfterParse(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    t: integer;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  BVE.SVG2Intf;

const
  svg_transform =
    'translate(%3.2f,%3.2f) rotate(%3.2f)';

  svg_orbit =
     '<svg width="200" height="200" viewBox="0 0 200 200">'
     + '<ellipse cx="100" cy="100" rx="100" ry="100" fill="none" stroke="blue" stroke-width="2" />'
     + '<g id="g1">'
       + '<circle cx="0" cy="0" r="20" fill="none" stroke="blue" stroke-width="2" />'
       + '<text x="0" y="12" text-anchor="middle" font-size="36" fill="blue" >A</text>'
     + '</g>'
   + '</svg>';

procedure TForm1.FormCreate(Sender: TObject);
begin
  SVG2Image1.SVG.Text := svg_orbit;
end;

procedure TForm1.SVG2Image1AfterParse(Sender: TObject);
begin
  t := 0;
  Timer1.Enabled := True;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  Element: ISVGElement;
  A1, A2, Ar: Double;
begin
  Element := SVG2Image1.SVGRoot.Element['g1'];
  if assigned(Element) then
  begin
    A1 := 360*(t mod 100)/100;
    Ar := A1*PI/180;

    A2 := -A1*100/20;

    Element.Attributes['transform'] :=
      Format(svg_transform, [100+80*Cos(Ar), 100+80*Sin(Ar), A2]);
  end;

  Inc(t);

  SVG2Image1.Repaint;
end;

Setting style attributes

In SVG there are three ways to define style:

  1. With an element attribute, for example:
    <rect width="50" height="50" fill="red"/>
  2. With a css style sheet, for example:

    <style type="text/css">
      rect {fill: red; stroke: none;}
    </style>
    <rect  width="50" height="50" />
  3. With an inline css style attribute, for example:
    <rect width="50" height="50" style="fill: red; stroke: none;"/>

The priority in which style is evaluated is 3, then 2, then 1, so inline style overrules style sheet style which in turn overrules the attribute style.

The following example demonstrates setting style.

Put a TSVG2Image on a form and set property "Align" of the SVG2Image1 to "alClient".

Add an "OnCreate" event handler to the form.

Add an "OnClick" event handler to the SVG2Image1 object.

Add the following code to the unit:

Code

uses
  BVE.SVG2Intf;

const
  svg_style1 =
      ' text {'
     + ' font-family: "courier new";'
     + ' font-size: 24;'
     + ' }';

  svg_style2 =
      ' text {'
     + ' font-family: "arial";'
     + ' font-size: 16;'
     + ' }';

  svg_text =
     '<svg viewBox="0 0 200 80">'
     + '<style id="stylesheet" type="text/css">'
     + svg_style1
     + '</style>'
     + '<g text-anchor="middle">'
       + '<text id="text1" x="100" y="30" fill="blue" >Blue</text>'
       + '<text id="text2" x="100" y="60" fill="red" >Red</text>'
     + '</g>'
     + '<rect x="1" y="1" width="198" height="78" fill="none" stroke="blue" stroke-width="2" />'
   + '</svg>';

procedure TForm1.FormCreate(Sender: TObject);
begin
  SVG2Image1.SVG.Text := svg_text;
end;

procedure TForm1.SVG2Image1Click(Sender: TObject);
var
  Element: ISVGElement;
begin
  // Change inline style (highes priority)

  Element := SVG2Image1.SVGRoot.Element['text2'];
  if assigned(Element) then
    Element.Attributes['style'] := 'font-family: "Times New Roman"; font-size: 24';

  // Change style sheet (medium priority)

  Element := SVG2Image1.SVGRoot.Element['stylesheet'];
  if assigned(Element) then
    Element.Text := svg_style2;

  // Change attribute (lowest priority, this will have no effect)

  Element := SVG2Image1.SVGRoot.Element['text1'];
  if assigned(Element) then
    Element.Attributes['font-family'] := 'Georgia';

  SVG2Image1.Repaint;
end;

Add and remove elements

The following example shows how to add or remove elements.

Put a TSVG2Image on a form and set property "Align" of the SVG2Image1 to "alClient".

Add an "OnCreate" event handler to the form.

Add an "OnClick" event handler to the SVG2Image1 object.

Add the following code to the unit:

Code

type
  TForm1 = class(TForm)
    SVG2Image1: TSVG2Image;
    procedure SVG2Image1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    FStep: integer;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
{$IFnDEF FPC}
  XML.XMLIntf,
{$ENDIF}
  BVE.SVG2Types,
  BVE.SVG2Elements,
  BVE.SVG2Intf;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FStep := 0;
end;

procedure TForm1.SVG2Image1Click(Sender: TObject);
var
  Node: IXMLNode;
  Doc: ISVGXMLDocument;
begin
  case FSTep of
  0: begin
       SVG2Image1.SVGRoot.Clear;

       Doc := SVG2Image1.SVGRoot.CreateSVGXMLDocument;
       SVG2Image1.SVGRoot.DocAdd(Doc);

       Node := Doc.DocumentElement;

       Node.Attributes['width'] := '10cm';
       Node.Attributes['height'] := '8cm';
       Node.Attributes['viewBox'] := '0 0 100 80';

       Caption := 'SVG element created';
       Inc(FStep);
     end;

  1: begin
       Node := SVG2Image1.SVGRoot.SVG.AddChild(el_rect, ns_uri_svg);

       Node.Attributes['id'] := 'rect';
       Node.Attributes['x'] := 0;
       Node.Attributes['y'] := 0;
       Node.Attributes['width'] := 100;
       Node.Attributes['height'] := 80;
       Node.Attributes['style'] := 'fill: yellow; stroke: blue;';

       Caption := 'Rect element created';
       Inc(FStep);
     end;

  2: begin
       Node := SVG2Image1.SVGRoot.SVG.AddChild(el_ellipse, ns_uri_svg);

       Node.Attributes['cx'] := 50;
       Node.Attributes['cy'] := 40;
       Node.Attributes['rx'] := 50;
       Node.Attributes['ry'] := 40;
       Node.Attributes['style'] := 'fill: none; stroke: red;';

       Caption := 'Ellipse element created';
       Inc(FStep);
     end;

  3: begin
       Node := SVG2Image1.SVGRoot.SVG.AddChild(el_text, ns_uri_svg);

       Node.Attributes['x'] := 50;
       Node.Attributes['y'] := 45;
       Node.Attributes['text-anchor'] := 'middle';
       Node.Attributes['style'] := 'font-family: "times"; font-size: 24; fill: red;';

       Node.Text := 'Ready...';

       Caption := 'Text element created';
       Inc(FStep);
     end;

  4: begin
       Node := SVG2Image1.SVGRoot.Element['rect'];
       if assigned(Node) then
         SVG2Image1.SVGRoot.SVG.ChildNodes.Remove(Node);

       Caption := 'Rect element removed';
       Inc(FStep);
     end;

  5: begin
       SVG2Image1.SVGRoot.SVG.ChildNodes.Clear;

       Caption := 'SVG child list cleared';
       FStep := 1;
     end;
  end;

  SVG2Image1.Repaint;
end;

Add an SVG fragment

Apart from adding elements one by one your can also add multiple elements at once to the ChildList of a node.

Method "AddFragment" of ISVGElement allows you to parse an xml fragment and add the result to the Childlist of the element.

Put a TSVG2Image on a form.

Add an "OnClick" event handler to the SVG2Image1 object.

Add the following code to the unit:

Code

uses
  BVE.SVG2Intf;

const
  svg_text =
     '<svg viewBox="0 0 200 80">'
     + '<g text-anchor="middle">'
       + '<rect x="20" y="20" width="160" height="40" fill="#101010" stroke="#CCCCCC" stroke-width="2" />'
       + '<text id="text"  x="100" y="45" fill="white" >Click me...</text>'
     + '</g>'
     + '<rect x="1" y="1" width="198" height="78" fill="none" stroke="blue" stroke-width="1" />'
   + '</svg>';

procedure TForm1.FormCreate(Sender: TObject);
begin
  SVG2Image1.SVG.Text := svg_text;
end;

procedure TForm1.SVG2Image1Click(Sender: TObject);
var
  Element: ISVGElement;
begin
  Element := SVG2Image1.SVGRoot.Element['text'];
  if assigned(Element) then
  begin
    Element.ChildNodes.Clear;
    Element.AddFragment('<tspan fill="yellow">Button <tspan fill="red" font-weight="bold" text-decoration="underline">clicked!</tspan></tspan>');
    SVG2Image1.Repaint;
  end;
end;

Find element under the mousepointer

The following example shows how to get an element that is under the mousepointer.
Only elements with physical dimensions can receive mousepointer events, these are elements that support the ISVGObject interface.

Put a TSVG2Image on a form.

Set property "Align" of the SVG2Image1 to "alClient".
Set the "Filename" property of SVG2Image1 to "animated-clock.svg" in the "Img" folder.
Set property "RenderOptions" to include "sroEvents" (for example [sroClippath,sroEvents]), this will enable catching mousepointer events.

Add an "OnMouseDown" event handler to the SVG2Image1 object.

Add the following code to the unit. The clicked element will be shown in the form's caption.
uses
  BVE.SVG2Types,
  BVE.SVG2Intf;

procedure TForm1.SVG2Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  SVGObject: ISVGObject;
begin
  SVGObject := SVG2Image1.ObjectAtPt(SVGPoint(X, Y), FALSE);
  if assigned(SVGObject) then
    Caption := SVGObject.LocalName + ' id:' + SVGObject.ID
  else
    Caption := 'No object';
end;

Using mouse events

The following example shows how to use SVGpointer events.

The mouse event that you want to catch must be defined in the SVG graphic.

Put a TSVG2Image on a form.

Set property "Align" of the SVG2Image1 to "alClient".

Set property "RenderOoptions" to "[sroClippath,sroEvents]"

Add an "OnCreate" event handler to the form,.

Add an "OnSVGEvent" event handler to the SVG2Image1 object.

Add the following code to the unit.

All info linked to the event will be accessible through the ISVGEvent interface, which is passed to the event handler.

Code

uses
  ...
  BVE.SVG2Intf;

type
  TfrmMouseEvents = class(TForm)
    SVG2Image1: TSVG2Image;
    procedure SVG2Image1SVGEvent(Sender: TObject; aSVGRoot: ISVGRoot;
      aEvent: ISVGEvent; const aValue: string);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmMouseEvents: TfrmMouseEvents;

implementation

{$R *.dfm}

uses
  BVE.SVG2Types;

const
  svg_events =
      '<svg width="6cm" height="5cm" viewBox="0 0 600 500"'
     + ' xmlns="http://www.w3.org/2000/svg" version="1.1"'
     + ' xmlns:xlink="http://www.w3.org/1999/xlink">'
     + '<defs>'
       + '<ellipse id="ellipse_large" cx="300" cy="250" rx="300" ry="200" />'
     + '</defs>'
     + '<use id="use_back" onmouseover="MouseOver!" onmouseout="MouseOut!" onmousemove="MouseMove!" fill="red" stroke="black" stroke-width="1" xlink:href="#ellipse_large" />'
     + '<use id="use_left" onmouseover="MouseOver!" onmouseout="MouseOut!" onmousemove="MouseMove!" fill="green" stroke="black" stroke-width="4" transform="translate(-50,250) scale(0.25)" xlink:href="#ellipse_large" />'
     + '<use id="use_right" onmouseover="MouseOver!" onmouseout="MouseOut!" onmousemove="MouseMove!" fill="blue" stroke="black" stroke-width="4" transform="translate(500,250) scale(0.25)" xlink:href="#ellipse_large" />'
   + '</svg>';

procedure TfrmMouseEvents.FormCreate(Sender: TObject);
begin
  SVG2Image1.SVG.Text := svg_events;
end;

procedure TfrmMouseEvents.SVG2Image1SVGEvent(Sender: TObject;
  aSVGRoot: ISVGRoot; aEvent: ISVGEvent; const aValue: string);
var
  Info: string;
  MouseEvent: ISVGMouseEvent;
begin
  if aEvent.EventType <> etNone then
  begin

    // Only the eventtypes defined in the SVG will be catched!

    Info := aEvent.CurrentTarget.LocalName + ' id: ' + aEvent.CurrentTarget.ID + ' event: ';

    case aEvent.EventType of
      etClick:
        Info := Info + 'click';
      etMouseDown:
        Info := Info + 'mouseDown';
      etMouseUp:
        Info := Info + 'mouseUp';
      etMouseOut:
        Info := Info + 'mouseOut';
      etMouseMove:
        Info := Info + 'mouseMove';
      etMouseOver:
        Info := Info + 'mouseOver';
    end;

    if Supports(aEvent, ISVGMouseEvent, MouseEvent) then
      Info := Info + Format(' x = %3.1f y = %3.1f', [MouseEvent.ScreenX, MouseEvent.ScreenY]);
  end else
    Info := '';

  Caption := Info;
end;

Loading files from internet

The following example shows how to load files from internet.

Requirements for downloading files directly from the internet:

  1. Define InternetAccess must be enabled in CompilerSettings.inc.
  2. For SSL (https) libeay32.dll and ssleay32.dll must  be available on the system. Download from https://indy.fulgan.com/SSL/

The example loads SVG files from the SVG1.1 test suite and loads the corresponding Png file and then displays them side by side.

To enable loading files from internet  the define "SVGInternetAccess" must be enabled in include file "Common\CompilerSettings.inc".

Put a TSVG2Image on a form.

Next to it, put a TImage control on the form.

Set width = 480 and height = 360 on both controls.

Put a TButton on the form.

Add an "OnCreate" event handler to the form.

Add an "OnDestroy" event handler to the form.

Add an "OnClick" event handler to the Button1 object.

Add the following code to the unit:

Code

type
  TForm1 = class(TForm)
    SVG2Image1: TSVG2Image;
    Image1: TImage;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    FStep: integer;
    Fsl: TStringList;
    FPath: string;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  BVE.SVG2SaxParser,
  BVE.SVG2Types,
  PngImage;

procedure TForm1.Button1Click(Sender: TObject);
var
  p: integer;
  Filename: string;
  MemStream: TMemoryStream;
  Png: TPngImage;
begin
  if FStep >= Fsl.Count then
    FStep := 0;

  if FStep < Fsl.Count then
  begin
    FileName := Fsl[FStep];
    Caption := FileName;

    try
      // Load the SVG file

      SVG2Image1.FileName := FPath + 'svg/' + FileName;

      // Load the corresponding Png file

      MemStream := TMemoryStream.Create;
      try
        p := Pos('.', FileName);
        Png := TPngImage.Create;
        try
          TSVGSaxParser.LoadFromInternet(FPath + 'png/' + copy(FileName, 1, p - 1) + '.png', MemStream);
          MemStream.Position := 0;
          Png.LoadFromStream(MemStream);
          Image1.Picture.Graphic := Png;
        finally
          Png.Free;
        end;

      finally
        MemStream.Free;
      end;

      except on E:Exception do
      begin
        Caption := Caption + ' Error: ' + E.Message;
      end;
    end;
  end;

  FStep := FStep + 1;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  MemStream: TMemoryStream;
  i: integer;
begin
  SVG2Image1.RenderOptions := [sroClippath, sroFilters];

  FPath := 'https://dev.w3.org/SVG/profiles/1.1F2/test/';

  Fsl := TStringList.Create;
  MemStream := TMemoryStream.Create;
  try
    // Load the list of files in the SVG1.1 test suite

    TSVGSaxParser.LoadFromInternet(FPath + '/svg/basic-files.txt', MemStream);
    TSVGSaxParser.LoadFromInternet(FPath + '/svg/tiny-files.txt', MemStream);
    TSVGSaxParser.LoadFromInternet(FPath + '/svg/full-files.txt', MemStream);

    MemStream.Position := 0;
    Fsl.LoadFromStream(MemStream);

    // Delete the testfiles that are not supported
    i := 0;
    while i < Fsl.Count do
    begin
      // Note: not all test svg's will render correctly, filter out the
      // svg groups that are not supported in any case.

      if (Pos('animate-', Fsl[i])<>0)
      or (Pos('interact-', Fsl[i])<>0)
      or (Pos('script-', Fsl[i])<>0)
      or (Pos('struct-dom-', Fsl[i])<>0)
      or (Pos('text-dom-', Fsl[i])<>0)
      or (Pos('text-tselect-', Fsl[i])<>0)
      then
        Fsl.Delete(i)
      else
        Inc(i);
    end;


    Fsl.Sort;
  finally
    MemStream.Free;
  end;

  FStep := 0;
  Button1Click(Self);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Fsl.Free;
end;

Animation control

The following example shows how add animation control to an application.

The application can Start/Stop and Pause/Unpause using checkboxes the animation.

If the animation is paused, you can scroll through the animation using the trackbar.

The animation time and frames per second are displayed in a label.

Code

unit UnitAnimationControl;

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

interface
uses
{$IFnDEF FPC}
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Variants,
  System.Classes,
  Vcl.Graphics,
  Vcl.Controls,
  Vcl.Forms,
  Vcl.Dialogs,
  Vcl.StdCtrls,
  Vcl.ComCtrls,
  Vcl.ExtCtrls,
  BVE.SVG2Control.VCL,
{$ELSE}
  Windows,
  Messages,
  SysUtils,
  Variants,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  ExtCtrls,
  StdCtrls,
  ComCtrls,
  BVE.SVG2Control.FPC,
{$ENDIF}
  BVE.SVG2Doc;

type
  TfrmAnimationControl = class(TForm)
    Panel1: TPanel;
    cbStart: TCheckBox;
    cbPause: TCheckBox;
    TrackBar1: TTrackBar;
    Label1: TLabel;
    SVG2AnimationTimer1: TSVG2AnimationTimer;
    SVG2WinControl1: TSVG2WinControl;
    procedure SVG2AnimationTimer1Sample(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
    procedure cbPauseClick(Sender: TObject);
    procedure cbStartClick(Sender: TObject);
  private
    { Private declarations }
  public
    procedure UpdateControls;
  end;

var
  frmAnimationControl: TfrmAnimationControl;

implementation

{$IFnDEF FPC}
  {$R *.dfm}
{$ELSE}
  {$R *.lfm}
{$ENDIF}

procedure TfrmAnimationControl.cbPauseClick(Sender: TObject);
begin
  SVG2AnimationTimer1.IsPaused := cbPause.Checked;
end;

procedure TfrmAnimationControl.cbStartClick(Sender: TObject);
begin
  SVG2AnimationTimer1.IsStarted := cbStart.Checked;
end;

procedure TfrmAnimationControl.SVG2AnimationTimer1Sample(Sender: TObject);
begin
  UpdateControls;
end;

procedure TfrmAnimationControl.TrackBar1Change(Sender: TObject);
begin
  if SVG2AnimationTimer1.IsPaused then
  begin
    SVG2WinControl1.AnimationTime := TrackBar1.Position;
    UpdateControls;
  end;
end;

procedure TfrmAnimationControl.UpdateControls;
begin
  Label1.Caption := Format('Time: %4.1f  FPS: %3.1f',
    [SVG2WinControl1.AnimationTime / 1000, SVG2AnimationTimer1.FPS]);

  if not SVG2AnimationTimer1.IsPaused then
    TrackBar1.Position := SVG2WinControl1.AnimationTime;
end;

end.

Text to path

This example application first creates an SVG image based on the text that is entered in an edit box and the font that is selected with the font selection dialog. By pressing a button, this SVG image is copied to a new SVG image in which the text element is converted to path elements.

Code

unit UnitTextToPath;

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

interface
uses
{$IFnDEF FPC}
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Variants,
  System.Classes,
  System.UITypes,
  Vcl.Graphics,
  Vcl.Controls,
  Vcl.Forms,
  Vcl.Dialogs,
  Vcl.StdCtrls,
  Vcl.ExtCtrls,
  BVE.SVG2Control.VCL;
{$ELSE}
  SysUtils,
  Variants,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  ExtCtrls,
  StdCtrls,
  BVE.SVG2Control.FPC;
{$ENDIF}

type
  TfrmTextToPath = class(TForm)
    Panel1: TPanel;
    Label1: TLabel;
    Edit1: TEdit;
    Button1: TButton;
    Button2: TButton;
    Panel2: TPanel;
    SVG2Control1: TSVG2Control;
    Panel3: TPanel;
    Splitter1: TSplitter;
    Panel4: TPanel;
    SVG2Control2: TSVG2Control;
    Splitter2: TSplitter;
    Splitter3: TSplitter;
    Memo1: TMemo;
    Memo2: TMemo;
    FontDialog1: TFontDialog;
    lFontAttributes: TLabel;
    procedure Button2Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Edit1Change(Sender: TObject);
  private
  public
    procedure CreateInput;
    procedure CreateOutput;
  end;

var
  frmTextToPath: TfrmTextToPath;

implementation
uses
  BVE.SVG2Types;

{$IFnDEF FPC}
  {$R *.dfm}
{$ELSE}
  {$R *.lfm}
{$ENDIF}

const
  svg_text =
      '<svg version="1" xmlns="http://www.w3.org/2000/svg" width="305" height="105">'
        + '<text x="20" y="50" %s>%s</text>'
    + '</svg>';

{ TfrmTextToPath }

procedure TfrmTextToPath.Button1Click(Sender: TObject);
var
  FontStyle, FontWeight: string;
begin
  if FontDialog1.Execute then
  begin
    // Convert Font settings to SVG Font Settings

    if fsBold in FontDialog1.Font.Style then
      FontWeight := 'bold'
    else
      FontWeight := 'normal';

    if fsItalic in FontDialog1.Font.Style then
      FontStyle := 'italic'
    else
      FontStyle := 'normal';

    lFontAttributes.Caption :=
      Format('font-family="%s" font-size="%d" font-style="%s" font-weight="%s"',
      [FontDialog1.Font.Name, FontDialog1.Font.Size, FontStyle, FontWeight]);

    CreateInput;
  end;
end;

procedure TfrmTextToPath.Button2Click(Sender: TObject);
begin
  CreateOutput;
end;

procedure TfrmTextToPath.CreateInput;
begin
  Memo1.Text := Format(svg_text, [lFontAttributes.Caption, Edit1.Text]);

  // By setting [sroTextToPath] in the render options, a group element is
  // created for every text element. For every glyph in the text, a
  // corresponding path element is added to the group.

  // The grouped path elments are not active, they only become active if
  // we copy the root to another root (see CreateOutput).

  SVG2Control1.RenderOptions := [sroTextToPath];

  // Here we assign the the SVG image text to the control, this will trigger
  // a parse and a render.

  SVG2Control1.SVG.Assign(Memo1.Lines);
end;

procedure TfrmTextToPath.CreateOutput;
begin
  // The moment we copy a root that is created with option [sroTextToPath]
  // to another root, the text elements are replaced by the grouped path elements.

  SVG2Control2.SVGRoot := SVG2Control1.SVGRoot.CreateCopy;

  SVG2Control2.Repaint;
  Memo2.Lines.Assign(SVG2Control2.SVGRoot.Doc.XML);
end;

procedure TfrmTextToPath.Edit1Change(Sender: TObject);
begin
  CreateInput;
end;

procedure TfrmTextToPath.FormCreate(Sender: TObject);
begin
  lFontAttributes.Caption := 'font-family="Times New Roman" font-size="20"';
  CreateInput;
end;

end.

SVG Viewer applications

The purpose of these viewer applications is to demonstrate the functionality of the SVG package but also to provide some coding examples for working with the SVG in application development.


SVG viewer VCL


SVG Viewer for VCL screenshot

The demo viewer is a viewer for SVG graphics and build with the VCL framework, so Windows only.


SVG viewer FMX


Demo SVG viewer for FMX screenshot

The demo viewer is a viewer for SVG graphics and build with the FMX framework, it compiles on all platforms.

It has more or less the same functionality as the VCL viewer, but additionally it also supports global scalling, with the slider on the right.


Bug reporting

It is not unlikely that at some point you run into a bug. I spend a lot of time testing, but considering that the package supports 10 Delphi versions, the VCL, FMX and FPC Lazarus platform and possibly 4 operating systems, the number of test scenarios add up and some bugs may slip through.

If you find a bug you can send me an email at BVerhue@gmail.com and I will do my best to solve it.

In your bug report please name:

  1. the Delphi or FPC and Lazarus version you are using
  2. the platform you are using (VCL or FMX or FPC Lazarus)
  3. the operating system
  4. the version of the SVG control package
  5. and if possible send the SVG graphic that triggers the bug.

It is not guaranteed that I can solve everything, some render contexts are limited or do not support all functionality needed for rendering the full SVG specification. See sections "Render contexts" and "Text layouts" for known limitations.


Future development

With this new version I hope I made another step to a more complete and more useful SVG implementation for Delphi and FPC Lazarus. Of course adding SVG functionality is something I will keep on doing.

If you have any suggestions for the control, you can send it to me. I can't guaranty I will be able to implemented it, but I'll have a go. I am developing this control in my free time so I don't have an unlimited time for development.

See the website for updates and more examples

https://www.bverhue.nl/delphisvg

Bruno Verhue, Delft 2021

BVerhue@gmail.com


License

See the order page on the Delphi SVG site or see the License.txt that is included in the package.