SVG Rendering
For the past few weeks I've been working on writing and testing an object oriented encapsulation of the free open source SDL and NanoVG libraries. Below is a short video clip of one of my demo programs highlighting realtime symbolic vector graphics (SVG) parsing and rendering.
This demo is a Free Pascal program which dynamically reads in SVG document files and renders them on screen. The SVG images are rendered in realtime using vector parsed data and converted to OpenGL vertex data with the help of compute shaders. Note the listing at the end of this page uses only 156 lines of code to render an SVG file!
This program has been tested and runs well on Windows, Linux, Mac, and Raspberry Pi
Video
Here is a screen recording of the program viewing various SVG files.
Video: SVG Rendering
Source Code
The following is a listing of the entire unit defining this SVG rendering portion of my system. Comments are provided to explain how it is written and how it works.
unit Tiny.Graphics.Svg.Renderer; {$i tiny.svg} interface uses Tiny.System, Tiny.Types, Tiny.Graphics, Tiny.Graphics.Svg; { TSvgRender } type TSvgRender = class private FCanvas: ICanvas; FDoc: TSvgDocument; public constructor Create(Canvas: ICanvas); destructor Destroy; override; procedure Parse(const Xml: string); procedure ParseFile(const FileName: string); procedure Draw; end; implementation { TSvgRender } constructor TSvgRender.Create(Canvas: ICanvas); begin inherited Create; FCanvas := Canvas; FDoc := TSvgDocument.Create; end; destructor TSvgRender.Destroy; begin FDoc.Free; inherited Destroy; end; procedure TSvgRender.Parse(const Xml: string); begin FDoc.Parse(Xml); end; procedure TSvgRender.ParseFile(const FileName: string); begin Parse(FileLoadText(FileName)); end; procedure TSvgRender.Draw; procedure RenderPath(Path: TSvgPath); var C: TSvgCommand; I: Integer; begin for I := 0 to Path.Commands.Length - 1 do begin C := Path.Commands[I]; case C.Action of svgMove: FCanvas.MoveTo(C.X, C.Y); svgLine: FCanvas.LineTo(C.X, C.Y); svgCubic: FCanvas.BezierTo(C.X1, C.Y1, C.X2, C.Y2, C.X, C.Y); svgQuadratic: FCanvas.QuadTo(C.X1, C.Y1, C.X, C.Y); svgClose: FCanvas.ClosePath; end; end; end; procedure Polyline(N: TSvgPolyline); var P: TPointF; I: Integer; begin for I := 0 to N.Points.Length - 1 do begin P := N.Points[I]; if I = 0 then FCanvas.MoveTo(P.X, P.Y) else FCanvas.LineTo(P.X, P.Y); end; if N is TSvgPolygon then FCanvas.ClosePath; end; procedure Render(N: TSvgNode; Opacity: Float); var L: TSvgLine absolute N; C: TSvgCircle absolute N; E: TSvgEllipse absolute N; R: TSvgRect absolute N; P: TSvgPath absolute N; G: TSvgGroup absolute N; S: TSvgNode; T: TSvgTransform; M: IMatrix; begin M := nil; if N.Transform <> '' then begin N.BuildTransform(T); M := NewMatrix; M.Copy(T[0], T[1], T[2], T[3], T[4], T[5]); M.Transform(FCanvas.Matrix); FCanvas.Matrix.Push; FCanvas.Matrix := M; end; if N is TSvgLine then begin FCanvas.MoveTo(L.X1, L.Y1); FCanvas.LineTo(L.X2, L.Y2); end else if N is TSvgPolyLine then Polyline(TSvgPolyline(N)) else if N is TSvgCircle then FCanvas.Circle(C.X, C.Y, C.R) else if N is TSvgEllipse then FCanvas.Ellipse(E.X, E.Y, E.W, E.H) else if N is TSvgRect then if R.R > 0 then FCanvas.RoundRect(R.X, R.Y, R.W, R.H, R.R) else FCanvas.Rect(R.X, R.Y, R.W, R.H) else if N is TSvgPath then RenderPath(P) else if N is TSvgGroup then begin for S in G do Render(S, Opacity * G.Opacity); if M <> nil then FCanvas.Matrix.Pop; Exit; end; if not (N is TSvgLine) then if R.Style.Fill <> 0 then FCanvas.Fill(BuildBrush(R, Opacity * R.Opacity), True); if R.Style.Stroke <> 0 then FCanvas.Stroke(BuildPen(R, Opacity * R.Opacity), True); FCanvas.BeginPath; if M <> nil then FCanvas.Matrix.Pop; end; var N: TSvgNode; begin for N in FDoc do Render(N, 1); end; end.