Download Now
Forums

    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.