Writing an SVG Library

In this post I will develop a simple C library to create and save an SVG (Scalable Vector Graphics) file, complete with a few examples of its use.

Perhaps the biggest obstacle encountered when learning programming is the disillusionment that kicks in when you start to realise you have worked long and hard getting your head round numerous arcane ideas, but still have no idea how to actually do anything constructive and worthwhile. There are many, many books with a thousand or more pages which teach you everything you need to know about a particular language - except how to actually write software in it!

A core principle which I will apply to this blog is to only present code in the context of actually doing something useful. Perhaps the task with the most immediate impact, but which can easily be accomplished in a small project is the creation of graphics.

To this end I'll dive straight in to writing the library and some functions to use it.

What is SVG?

Before actually starting to cut code I'll give a brief overview of SVG, as it perhaps isn't as well know as, say, JPEG or PNG. Obviously, if you already understand SVG skip the rest of this section.

Image formats such as JPEG and PNG are bitmap or raster images: they consist of just rows and columns of pixels and are best suited to photographs or complex computer-generated artworks. SVG is completely different as it is a vector graphics format (SVG stands for Scalable Vector Graphics). Instead of pixels, the image is made up of individual elements such as shapes (rectangles, circles etc.), lines, text and so on. This makes it more suitable for simpler images such as graphs, diagrams or logos. SVG is XML based and therefore contains a hierarchy of human-readable tags. Just to give a flavour the following will draw a blue circle with a 2-pixel black border. The circle's radius is 32 pixels, and the coordinates of the centre are 128, 128.

SVG example

<circle stroke='black' stroke-width='2px' fill='blue' r='32' cy='128' cx='128' />

It's probably worth mentioning that SVG can be used in two ways. The first way is to create a stand-alone file as I am doing here. The second way is to embed it in an HTML page, where the individual elements form part of the DOM and can therefore be manipulated by JavaScript (eg for animations), and any text in the image can be read by search engines. You can of course put an SVG file in an image tag on a page, but it cannot then be scripted or picked up by search engines.

What are we actually going to create?

So let's think about what we are going to create. As I mentioned above, we will develop a library which can be compiled and reused in different projects, as well as a client application to test it out and demonstrate its usage. The library will consist of the following:

  • A struct to hold the text of the SVG file we are creating (plus a few utility members I'll discuss as we get to them).
  • A function to create a new, empty SVG document
  • Functions to add circles, lines, rectangles, text and ellipses, and to fill the background
  • A function to finalize the SVG document (basically just close the SVG tag)
  • A function to save it as a file
  • A function to print out the content of the document
  • An all-important "free" function to release all the memory we have used

This does not cover all that SVG is capable of by any means, but it covers the basics enough to be useful. There is plenty of scope for adding more functionality which I hope to do in the future.

Despite its simplicity it demonstrates many C features, as well as the core principles involved in creating a reusable code library, including

  • using a struct to hold the document and supporting data
  • creating a set of functions to manipulate the document
  • memory management: the dreaded malloc, realloc and free functions!
  • creating a reusable library

Start Coding!

Firstly, create a new folder somewhere convenient and within it create the following empty files. If you do not want to type or copy/paste the code you can download a zip file with the source code from the Downloads page.

  • main.c - contains the code to use the library
  • svg.h - the library's header file
  • svg.c - the library's implementation

In svg.h type or copy/paste the #includes we will need.

svg.h

#include<stdlib.h>
#include<stdbool.h>
#include<stdio.h>
#include<math.h>

Followed by the struct.

svg.h

// --------------------------------------------------------
// STRUCT svg
// --------------------------------------------------------
typedef struct svg
{
    char* svg;
    int height;
    int width;
    bool finalized;
} svg;

And finally the function prototypes.

svg.h

// --------------------------------------------------------
// FUNCTION PROTOTYPES
// --------------------------------------------------------
svg* svg_create(int width, int height);
void svg_finalize(svg* psvg);
void svg_print(svg* psvg);
void svg_save(svg* psvg, char* filepath);
void svg_free(svg* psvg);
void svg_circle(svg* psvg, char* stroke, int strokewidth, char* fill, int r, int cx, int cy);
void svg_line(svg* psvg, char* stroke, int strokewidth, int x1, int y1, int x2, int y2);
void svg_rectangle(svg* psvg, int width, int height, int x, int y, char* fill, char* stroke, int strokewidth, int radiusx, int radiusy);
void svg_fill(svg* psvg, char* fill);
void svg_text(svg* psvg, int x, int y, char* fontfamily, int fontsize, char* fill, char* stroke, char* text);
void svg_ellipse(svg* psvg, int cx, int cy, int rx, int ry, char* fill, char* stroke, int strokewidth);

That's the header file finished, so save it but leave it open in your editor. Header files aren't just for the compiler, they are a primary reference for the developer as well!

Getting to work on the functions

Open svg.c and enter the #includes.

svg.c

#include<stdlib.h>
#include<stdbool.h>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include"svg.h"

Most of what comes next consists of appending text or numbers to the SVG document. This involves calculating how much extra memory we need, calling realloc to get that memory, and then sticking the new text or number on the end. Having typed the code out three times I realised they needed to be in separate functions, so here they are. Just type or copy/paste the following after the #includes.

svg.c

// --------------------------------------------------------
// STATIC FUNCTION appendstringtosvg
// --------------------------------------------------------
static void appendstringtosvg(svg* psvg, char* text)
{
    int l = strlen(psvg->svg) + strlen(text) + 1;

    char* p = realloc(psvg->svg, l);

    if(p)
    {
        psvg->svg = p;
    }

    strcat(psvg->svg, text);
}

// --------------------------------------------------------
// STATIC FUNCTION appendnumbertosvg
// --------------------------------------------------------
static void appendnumbertosvg(svg* psvg, int n)
{
    char sn[16];

    sprintf(sn, "%d", n);

    appendstringtosvg(psvg, sn);
}

In appendstringtosvg we firstly calculate the new amount of memory needed by adding the length of the new piece of text to the length of the existing document. We then add 1 for the null ('/0') string terminator. This is essential: if we don't we'll end up writing the terminating character to a byte which does not belong to us which could cause erratic errors. Next we call realloc to expand the memory to the required size, and finally add the new text to the end.

We also need to be able to add numbers to the document, so the second function writes the number to a string and then calls appendstringtosvg. Note that both functions are declared as static, as they are to be called only by code within the current file.

Next we will implement the svg_create function. This creates a new instance of the svg struct, and writes the opening tags to it.

svg.c

// --------------------------------------------------------
// FUNCTION svg_create
// --------------------------------------------------------
svg* svg_create(int width, int height)
{
    svg* psvg = malloc(sizeof(svg));

    if(psvg != NULL)
    {
        *psvg = (svg){.svg = NULL, .width = width, .height = height, .finalized = false};

        psvg->svg = malloc(1);

        sprintf(psvg->svg, "%s", "\0");

        appendstringtosvg(psvg, "<svg width='");
        appendnumbertosvg(psvg, width);
        appendstringtosvg(psvg, "px' height='");
        appendnumbertosvg(psvg, height);
        appendstringtosvg(psvg, "px' xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink'>\n");

        return psvg;
    }
    else
    {
        return NULL;
    }
}

The first malloc allocates enough memory for the struct itself, and the second allocates 1 byte for the string terminator which, when set, gets us an empty string. (If we do not do this, the block of memory may contain data left behind by any previous user of that memory, which could cause chaos.) The calls to appendstringtosvg and appendnumbertosvg then create the opening tag.

The next function is svg_finalise. This will be called after we have finished adding elements to the document, and just adds a closing tag.

svg.c

// --------------------------------------------------------
// FUNCTION svg_finalize
// --------------------------------------------------------
void svg_finalize(svg* psvg)
{
    appendstringtosvg(psvg, "</svg>");

    psvg->finalized = true;
}

Now we can start on the functions which actually add elements to the SVG document - I'll start with the svg_rectangle function

svg.c

//----------------------------------------------------------------
// FUNCTION svg_rectangle
//----------------------------------------------------------------
void svg_rectangle(svg* psvg, int width, int height, int x, int y, char* fill, char* stroke, int strokewidth, int radiusx, int radiusy)
{
    appendstringtosvg(psvg, "    <rect fill='");
    appendstringtosvg(psvg, fill);
    appendstringtosvg(psvg, "' stroke='");
    appendstringtosvg(psvg, stroke);
    appendstringtosvg(psvg, "' stroke-width='");
    appendnumbertosvg(psvg, strokewidth);
    appendstringtosvg(psvg, "px' width='");
    appendnumbertosvg(psvg, width);
    appendstringtosvg(psvg, "' height='");
    appendnumbertosvg(psvg, height);
    appendstringtosvg(psvg, "' y='");
    appendnumbertosvg(psvg, y);
    appendstringtosvg(psvg, "' x='");
    appendnumbertosvg(psvg, x);
    appendstringtosvg(psvg, "' ry='");
    appendnumbertosvg(psvg, radiusy);
    appendstringtosvg(psvg, "' rx='");
    appendnumbertosvg(psvg, radiusx);
    appendstringtosvg(psvg, "' />\n");
}

I can't pretend that isn't rather tedious, but that's what life is going to be like for a while: just assembling function arguments into valid SVG. But first let's write the svg_save and the all-important svg_free functions.

svg.c

// --------------------------------------------------------
// FUNCTION svg_save
// --------------------------------------------------------
void svg_save(svg* psvg, char* filepath)
{
    if(!psvg->finalized)
    {
        svg_finalize(psvg);
    }

    FILE* fp;

    fp = fopen(filepath, "w");

    if(fp != NULL)
    {
        fwrite(psvg->svg, 1, strlen(psvg->svg), fp);

        fclose(fp);
    }
}

//----------------------------------------------------------------
// FUNCTION svg_free
//----------------------------------------------------------------
void svg_free(svg* psvg)
{
    free(psvg->svg);

    free(psvg);
}

We now have enough code to create a new SVG document, add rectangles to it in any size, shape and colour, and then save the file. So let's do so...

Open main.c and enter the following. Four functions are prototyped here but for now we'll just implement and use drawrectangles.

main.c

#include<stdio.h>
#include<math.h>
#include<time.h>

#include"svg.h"

//--------------------------------------------------------
// FUNCTION PROTOTYPES
//--------------------------------------------------------
void drawrectangles(void);
void drawallshapes(void);
void iwanttobelieve(void);
void mondrian(void);

//--------------------------------------------------------
// FUNCTION main
//--------------------------------------------------------
int main()
{
    puts("Code in C - SVG\n---------------\n");

    drawrectangles();

    //drawallshapes();

    //iwanttobelieve();

    //mondrian();

    return EXIT_SUCCESS;
}

// --------------------------------------------------------
// FUNCTION drawrectangles
// --------------------------------------------------------
void drawrectangles(void)
{
    svg* psvg;
    psvg = svg_create(512, 512);

    if(psvg == NULL)
    {
        puts("psvg is NULL");
    }
    else
    {
        svg_rectangle(psvg, 512, 512, 0, 0, "white", "white", 0, 0, 0);

        svg_rectangle(psvg, 384, 384, 64, 64, "#00FF00", "#000000", 4, 0, 0);
        svg_rectangle(psvg, 320, 320, 96, 96, "#FFFF00", "#000000", 2, 8, 8);
        svg_rectangle(psvg, 256, 256, 128, 128, "#00FFFF", "#000000", 2, 8, 8);
        svg_rectangle(psvg, 192, 192, 160, 160, "#FF80FF", "#000000", 2, 8, 8);

        svg_finalize(psvg);
        svg_save(psvg, "rectangles.svg");
        svg_free(psvg);
    }
}

We can now compile and run. Using gcc run the following in a command line/terminal. (In Windows, change the last part to main.exe.) If you get any errors check the file and line number in your source code corresponds exactly to that above.

Compiling

gcc main.c svg.c -std=c11 -o main

Still at the command line/terminal, type ./main (in Linux) or main.exe (Windows) to run the program. Then go to the folder where you saved your files and open rectangles.svg.

(Note that the images here are converted to PNG as the WordPress platform this site uses won't allow SVG for "security reasons"!)

rectangles.svg

Note that in drawrectangles we started off with a bluish rectangle the size of the image at 0, 0. Without this the background would be transparent. A function to simplify this would be useful, so go back to svg.c and add the following function, which we'll use from now on.

svg.c

// --------------------------------------------------------
// FUNCTION svg_fill
// --------------------------------------------------------
void svg_fill(svg* psvg, char* Fill)
{
    svg_rectangle(psvg, psvg->width, psvg->height, 0, 0, Fill, Fill, 0, 0, 0);
}

For testing and debugging it is also handy to output the text of the SVG, so add the svg_print function to svg.c.

svg.c

// --------------------------------------------------------
// FUNCTION svg_print
// --------------------------------------------------------
void svg_print(svg* psvg)
{
    printf("%s\n", psvg->svg);
}

We now need to write the functions in svg.c to draw lines, text, circles and ellipses. (If you have done any graphics in different environments you might be surprised that in SVG circles and ellipses are two different kinds of shape. Typically you would only have the option to draw an ellipse, a circle merely being an ellipse with the height and width the same. Why the clever people at the W3C chose to do it this way I do not know. If you have any idea please let me know!)

As I mentioned above these functions are rather tedious and just output the various function's arguments as valid SVG, so lets get them done in one go by pasting the following into svg.c. When you have done so that file will be finished.

svg.c

//----------------------------------------------------------------
// FUNCTION svg_line
//----------------------------------------------------------------
void svg_line(svg* psvg, char* stroke, int strokewidth, int x1, int y1, int x2, int y2)
{
    appendstringtosvg(psvg, "    <line stroke='");
    appendstringtosvg(psvg, stroke);
    appendstringtosvg(psvg, "' stroke-width='");
    appendnumbertosvg(psvg, strokewidth);
    appendstringtosvg(psvg, "px' y2='");
    appendnumbertosvg(psvg, y2);
    appendstringtosvg(psvg, "' x2='");
    appendnumbertosvg(psvg, x2);
    appendstringtosvg(psvg, "' y1='");
    appendnumbertosvg(psvg, y1);
    appendstringtosvg(psvg, "' x1='");
    appendnumbertosvg(psvg, x1);
    appendstringtosvg(psvg, "' />\n");
}

//----------------------------------------------------------------
// FUNCTION svg_text
//----------------------------------------------------------------
void svg_text(svg* psvg, int x, int y, char* fontfamily, int fontsize, char* fill, char* stroke, char* text)
{
    appendstringtosvg(psvg, "    <text x='");
    appendnumbertosvg(psvg, x);
    appendstringtosvg(psvg, "' y = '");
    appendnumbertosvg(psvg, y);
    appendstringtosvg(psvg, "' font-family='");
    appendstringtosvg(psvg, fontfamily);
    appendstringtosvg(psvg, "' stroke='");
    appendstringtosvg(psvg, stroke);
    appendstringtosvg(psvg, "' fill='");
    appendstringtosvg(psvg, fill);
    appendstringtosvg(psvg, "' font-size='");
    appendnumbertosvg(psvg, fontsize);
    appendstringtosvg(psvg, "px'>");
    appendstringtosvg(psvg, text);
    appendstringtosvg(psvg, "</text>\n");
}

//----------------------------------------------------------------
// FUNCTION svg_circle
//----------------------------------------------------------------
void svg_circle(svg* psvg, char* stroke, int strokewidth, char* fill, int r, int cx, int cy)
{
    appendstringtosvg(psvg, "    <circle stroke='");
    appendstringtosvg(psvg, stroke);
    appendstringtosvg(psvg, "' stroke-width='");
    appendnumbertosvg(psvg, strokewidth);
    appendstringtosvg(psvg, "px' fill='");
    appendstringtosvg(psvg, fill);
    appendstringtosvg(psvg, "' r='");
    appendnumbertosvg(psvg, r);
    appendstringtosvg(psvg, "' cy='");
    appendnumbertosvg(psvg, cy);
    appendstringtosvg(psvg, "' cx='");
    appendnumbertosvg(psvg, cx);
    appendstringtosvg(psvg, "' />\n");
}

//----------------------------------------------------------------
// FUNCTION svg_ellipse
//----------------------------------------------------------------
void svg_ellipse(svg* psvg, int cx, int cy, int rx, int ry, char* fill, char* stroke, int strokewidth)
{
    appendstringtosvg(psvg, "    <ellipse cx='");
    appendnumbertosvg(psvg, cx);
    appendstringtosvg(psvg, "' cy='");
    appendnumbertosvg(psvg, cy);
    appendstringtosvg(psvg, "' rx='");
    appendnumbertosvg(psvg, rx);
    appendstringtosvg(psvg, "' ry='");
    appendnumbertosvg(psvg, ry);
    appendstringtosvg(psvg, "' fill='");
    appendstringtosvg(psvg, fill);
    appendstringtosvg(psvg, "' stroke='");
    appendstringtosvg(psvg, stroke);
    appendstringtosvg(psvg, "' stroke-width='");
    appendnumbertosvg(psvg, strokewidth);
    appendstringtosvg(psvg, "' />\n");
}

We are now on the home straight. The library is finished so now we can just write a few more functions to try it out. The first one, drawallshapes, will just draw one simple shape of each type to make sure everything is working properly. Go to main.c and enter the following.

main.c

// --------------------------------------------------------
// FUNCTION drawallshapes
// --------------------------------------------------------
void drawallshapes(void)
{
    svg* psvg;
    psvg = svg_create(192, 320);

    if(psvg == NULL)
    {
        puts("psvg is NULL");
    }
    else
    {
        svg_fill(psvg, "#DADAFF");

        svg_text(psvg, 32, 32, "sans-serif", 16, "#000000", "#000000", "drawallshapes");
        svg_circle(psvg, "#000080", 4, "#0000FF", 32, 64, 96);
        svg_ellipse(psvg, 64, 160, 32, 16, "#FF0000", "#800000", 4);

        svg_line(psvg, "#000000", 2, 32, 192, 160, 192);

        svg_rectangle(psvg, 64, 64, 32, 224, "#00FF00", "#008000", 4, 4, 4);

        svg_finalize(psvg);
        svg_print(psvg);
        svg_save(psvg, "allshapes.svg");
        svg_free(psvg);
    }
}

Scroll up to the main function, comment out drawrectangles and uncomment drawallshapes. Compile and run the program again, and you should get the following SVG file, just to confirm all the functions are working correctly.

allshapes.svg

We could end it there but even software engineers have a bit of artistic talent so let's finish off by drawing a couple of proper pictures. You might have noticed a prototype up the top of main.c for a function called iwanttobelieve. This is it. Paste it into the file, comment out drawallshapes in main and uncomment iwanttobelieve.

Note that colours can be in hexadecimal format, named colours, rgb or rgba; the "a" in rgba stands for alpha, ie opacity. In one of the functions I have set this to 0 meaning completely transparent. You'll see why in a moment.

main.c

// --------------------------------------------------------
// FUNCTION iwanttobelieve
// --------------------------------------------------------
void iwanttobelieve(void)
{
    svg* psvg;
    psvg = svg_create(512, 768);

    if(psvg == NULL)
    {
        puts("psvg is NULL");
    }
    else
    {
        svg_fill(psvg, "#000010");

        srand(time(NULL));
        int x, y;
        for(int s = 0; s <= 512; s++)
        {
            x = (rand() % 512);
            y = (rand() % 768);

            svg_rectangle(psvg, 1, 1, x, y, "white", "white", 0, 0, 0);
        }

        svg_text(psvg, 96, 712, "sans-serif", 32, "#FFFFFF", "#FFFFFF", "I WANT TO BELIEVE");

        svg_circle(psvg, "silver", 2, "rgba(0,0,0,0)", 28, 256, 384);

        svg_ellipse(psvg, 256, 374, 8, 14, "#808080", "#808080", 0);
        svg_ellipse(psvg, 252, 372, 3, 2, "#000000", "#000000", 0);
        svg_ellipse(psvg, 260, 372, 3, 2, "#000000", "#000000", 0);
        svg_rectangle(psvg, 1, 1, 251, 371, "white", "white", 0, 0, 0);
        svg_rectangle(psvg, 1, 1, 259, 371, "white", "white", 0, 0, 0);
        svg_line(psvg, "black", 1, 254, 378, 258, 378);

        svg_line(psvg, "silver", 2, 234, 416, 226, 432);
        svg_line(psvg, "silver", 2, 278, 416, 286, 432);
        svg_ellipse(psvg, 256, 400, 64, 16, "silver", "silver", 4);

        svg_finalize(psvg);
        svg_save(psvg, "iwanttobelieve.svg");
        svg_free(psvg);
    }
}

After you do the compile-and-run thing you will have created the following image.

iwanttobelieve.svg

The stars are randomly generated so the chances of yours being identical are

Number of possible star patterns!

1.0 / pow(768, 393216);

so not much chance at all really! If there happen to be any stars behind the spaceship's dome you will see them - that's why I used an alpha of 0 for its fill.

Finally let's do some real art. Paste the following into main.c, in the main function comment out iwanttobelieve, uncomment mondrian, compile and run.

main.c

// --------------------------------------------------------
// FUNCTION mondrian
// --------------------------------------------------------
void mondrian(void)
{
    svg* psvg;
    psvg = svg_create(512, 512);

    if(psvg == NULL)
    {
        puts("psvg is NULL");
    }
    else
    {
        svg_fill(psvg, "white");

        svg_rectangle(psvg, 512, 512, 0, 0, "white", "black", 1, 0, 0);

        svg_rectangle(psvg, 256, 256, 64, 64, "red", "red", 0, 0, 0);
        svg_rectangle(psvg, 128, 128, 64, 320, "black", "black", 0, 0, 0);
        svg_rectangle(psvg, 64, 128, 0, 384, "orange", "orange", 0, 0, 0);
        svg_rectangle(psvg, 128, 192, 320, 0, "orange", "orange", 0, 0, 0);
        svg_rectangle(psvg, 128, 64, 320, 384, "navy", "navy", 0, 0, 0);
        svg_rectangle(psvg, 64, 128, 448, 384, "red", "red", 0, 0, 0);

        svg_line(psvg, "black", 8, 0, 64, 448, 64);
        svg_line(psvg, "black", 8, 64, 64, 64, 512);
        svg_line(psvg, "black", 8, 0, 192, 64, 192);
        svg_line(psvg, "black", 8, 0, 384, 512, 384);
        svg_line(psvg, "black", 8, 128, 0, 128, 64);
        svg_line(psvg, "black", 8, 320, 0, 320, 448);
        svg_line(psvg, "black", 8, 64, 320, 448, 320);
        svg_line(psvg, "black", 8, 320, 192, 448, 192);
        svg_line(psvg, "black", 8, 64, 448, 448, 448);
        svg_line(psvg, "black", 8, 448, 0, 448, 512);
        svg_line(psvg, "black", 8, 192, 320, 192, 512);
        svg_line(psvg, "black", 8, 384, 192, 384, 320);

        svg_finalize(psvg);
        svg_save(psvg, "mondrian.svg");
        svg_free(psvg);
    }
}

You should end up with this. I told you we were doing real art!

mondrian.svg

We now have a complete and useful SVG library but of course there is huge scope for both improving the existing functionality and adding more. I'll save those for the future but in the meantime if you want to make any additions or improvements yourself please go ahead and post them in the comments.

Please follow Code in C on Twitter to keep up with future posts.

This entry was posted in Uncategorized. Bookmark the permalink.

One Response to Writing an SVG Library

  1. boomslang says:

    (1) In svg_create, return value of malloc not checked.

    psvg->svg = malloc(1);
    sprintf(psvg->svg, “%s”, “\0”);

    Will cause a NULL pointer dereference if psvg->svg is NULL.

    (2) In appendstringtosvg

    If realloc fails and returns NULL, call to strcat may write outside the bounds of what’s been allocated for psvg->svg. In the end, who knows what sort of strings will have successfully been appended to or not.

    (3) might be useful if save_svg returned a value to indicate whether the save succeeded or not.

Leave a Reply

Your email address will not be published. Required fields are marked *