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.