libmsvg v0.71

Programmer's guide

Last update: July 11, 2022


Abstract

libmsvg is a minimal and generic C library for reading and writing static SVG files.

SVG stand for Scalable Vector Graphics. It is a standard defined by the World Wide Web Consortium (see http://www.w3.org/Graphics/SVG/).

libmsvg concentrates on a subset of SVG to be useful. More specifically on a subset of the SVG Tiny 1.2 specification. The subset is described in Appendix A.

This document assumes a minimum knowledge of the SVG standard, if you do not know anything about the SVG format, read the above mentioned specification first. There are also a large number of introductory texts on the Internet.

Contents

  • Starting with examples
  • The MsvgElement tree
  • The MsvgElement structure
  • Reading SVG files
  • Building a RAW MsvgElement tree by program
  • Building a COOKED MsvgElement tree by program
  • Manipulating a MsvgElement tree
  • Finding elements in a MsvgElement tree
  • Working with cooked transformation matrix
  • Serialize a COOKED MsvgElement tree
  • Converting text elements to path elements
  • Converting path elements to poly elements
  • Writing SVG files
  • Appendix A, the libmsvg SVG subset
  • Appendix B, raw to cooked parameter conversion
  • Appendix C, quirks

  • Starting with examples

    I think the best way to start with a programmer's guide is to show examples, so here there are.

    Example 1

    This example read a SVG file in a MsvgElement tree

    #include <stdio.h>
    #include <msvg.h>
    
    int main(int argc, char **argv)
    {
        MsvgElement *root;
        int error;
    
        if (argc <2) {
            printf("Usage: example svgfile\n");
            return 0;
        }
    
        root = MsvgReadSvgFile(argv[1], &error);
        
        if (root == NULL) {
            printf("Error %d reading %s\n", error, argv[1]);
            return 0;
        }
    
        // Now you can process the structure. By example:
        MsvgPrintRawElementTree(stdout, root, 0);
    
        return 1;
    }
    

    Example 2

    This example builds a MsvgElement tree and writes it to a file

    #include <stdio.h>
    #include <msvg.h>
    
    #define TESTFILE "msvgt2.svg"
    
    int main(int argc, char **argv)
    {
        MsvgElement *root, *son;
    
        root = MsvgNewElement(EID_SVG, NULL);
        MsvgAddRawAttribute(root, "version", "1.2");
        MsvgAddRawAttribute(root, "baseProfile", "tiny");
        MsvgAddRawAttribute(root, "xmlns", "http://www.w3.org/2000/svg");
        MsvgAddRawAttribute(root, "viewBox", "0 0 400 400");
    
        son = MsvgNewElement(EID_RECT, root);
        MsvgAddRawAttribute(son, "x", "1");
        MsvgAddRawAttribute(son, "y", "1");
        MsvgAddRawAttribute(son, "width", "398");
        MsvgAddRawAttribute(son, "height", "398");
        MsvgAddRawAttribute(son, "stroke", "#F00");
        MsvgAddRawAttribute(son, "fill", "#FFF");
    
        son = MsvgNewElement(EID_RECT, root);
        MsvgAddRawAttribute(son, "x", "11");
        MsvgAddRawAttribute(son, "y", "11");
        MsvgAddRawAttribute(son, "width", "380");
        MsvgAddRawAttribute(son, "height", "380");
        MsvgAddRawAttribute(son, "stroke", "#0F0");
        MsvgAddRawAttribute(son, "fill", "none");
    
        MsvgPrintRawElementTree(stdout, root, 0);
    
        if (!MsvgWriteSvgFile(root, TESTFILE)) {
            printf("Error writing %s\n", TESTFILE);
            return 0;
        }
    
        return 1;
    }
    

    How to compile the example programs

    We assume here that you have the libmsvg library previously installed. If not, please read the "readme" file for installation instructions.

    These are the command lines to compile the examples for the three supported platforms:


    The MsvgElement tree

    The central structure of libmsvg is the MsvgElement tree. Every MsvgElement has an element type id (eid), a pointer to his father, pointers to his previous and next siblings and a pointer to his first son. The root element must be a EID_SVG, but you can have subtrees starting with any element ID (even subtrees with only one element) for your program's needs.

    The other supported elements are EID_DEFS, EID_G, EID_RECT, EID_CIRCLE, EID_ELLIPSE, EID_LINE, EID_POLYLINE, EID_POLYGON, EID_PATH, EID_TEXT, EID_USE, EID_LINEARGRADIENT, EID_RADIALGRADIENT, EID_STOP, EID_FONT, EID_FONTFACE, EID_MISSINGGLYPH and EID_GLYPH.

    This graph represents an example of a MsvgElement tree:

    MsvgElement tree example

    Not every element can have any child. For each element type id there is a list of elements that can be its children:

    Element attributes

    Every MsvgElement can have attributes, they can be of two types: raw or cooked. The type of MsvgElement attributes in a tree is determined by a variable in the root element (it must be a EID_SVG!!): root->psvgattr->tree_type. If this variable is RAW_SVGTREE all attributes are raw. Raw attributes are simple key,value strings pairs. If the variable is COOKED_SVGTREE the tree has cooked attributes too. Cooked attributes are typed variables and can be generic or specific, most element type ids (but not all) have the generic ones, most element type ids have his specific ones.

    The RAW_SVGTREE tree type

    After a SVG file is loaded to a MsvgElement tree by the MsvgReadSvgFile function it is marked as a RAW_SVGTREE. Only the supported elements are inserted in the tree, but all the read attributes are stored like raw attributes. This stage of the tree can suffice for some programs. Elements and attributes can be added, deleted or reordered and finally be written to a file using the MsvgWriteSvgFile function.

    The COOKED_SVGTREE tree type

    Using the MsvgRaw2CookedTree funtion you can convert a MsvgElement tree to the COOKED_SVGTREE type. Only the supported attributes are processed and converted to cooked attributes. In this state the tree is easier to be manipulated and/or rasterized by a program.

    Note that if we have a subtree whose parent is not an EID_SVG element, it is up to the programmer to know if it is a raw or cooked subtree.

    Content

    In addition to attributes an element can have content, content is stored in a MsvgContent variable, and it is shared by RAW and COOKED trees. In this version of the library only EID_TEXT element contents are stored in the tree.


    The MsvgElement structure

    After reading the previous section it must be easy to undestand the MsvgElement structure from the msvg.h include file:

    typedef struct _MsvgElement *MsvgElementPtr;
    
    typedef struct _MsvgElement {
        enum EID eid;               /* element type id */
        MsvgElementPtr father;      /* pointer to father element */
        MsvgElementPtr psibling;    /* pointer to previous sibling element */
        MsvgElementPtr nsibling;    /* pointer to next sibling element */
        MsvgElementPtr fson;        /* pointer to first son element */
    
        MsvgRawAttributePtr frattr; /* pointer to first raw attribute */
        MsvgContentPtr fcontent;    /* pointer to content */
    
        /* cooked generic attributes */
        char *id;                   /* id attribute */
        MsvgPaintCtxPtr pctx;       /* pointer to painting context */
    
        /* cooked specific attributes */
        union {
            MsvgSvgAttributes *psvgattr;
            MsvgDefsAttributes *pdefsattr;
            MsvgGAttributes *pgattr;
            MsvgUseAttributes *puseattr;
            MsvgRectAttributes *prectattr;
            MsvgCircleAttributes *pcircleattr;
            MsvgEllipseAttributes *pellipseattr;
            MsvgLineAttributes *plineattr;
            MsvgPolylineAttributes *ppolylineattr;
            MsvgPolygonAttributes *ppolygonattr;
            MsvgPathAttributes *ppathattr;
            MsvgTextAttributes *ptextattr;
            MsvgLinearGradientAttributes *plgradattr;
            MsvgRadialGradientAttributes *prgradattr;
            MsvgStopAttributes *pstopattr;
            MsvgFontAttributes *pfontattr;
            MsvgFontFaceAttributes *pfontfaceattr;
            MsvgGlyphAttributes *pglyphattr;
        };
    } MsvgElement;
    

    Raw attributes are stored in a simple linked list of MsvgRawAttribute variables:

    typedef struct _MsvgRawAttribute *MsvgRawAttributePtr;
    
    typedef struct _MsvgRawAttribute {
        char *key;                  /* key attribute */
        char *value;                /* value attribute */
        MsvgRawAttributePtr nrattr; /* pointer to next raw attribute */
    } MsvgRawAttribute;
    

    Content is stored in a MsvgContent variable:

    typedef struct _MsvgConten {
        int len;                 /* len content */
        char s[1];               /* content (not actual size) */
    } MsvgContent;
    

    Cooked generic attributes are the "id" attribute (that can be NULL) and the MsvgPaintCtx structure (that can be NULL too), all element type ids can have the id attribute, only EID_SVG, EID_G, EID_RECT, EID_CIRCLE, EID_ELLIPSE, EID_LINE, EID_POLYLINE, EID_POLYGON, EID_PATH, EID_TEXT and EID_USE have a MsvgPaintCtx structure:

    typedef struct {
        double a, b, c, d, e, f;
    } TMatrix;
    
    ttypedef struct _MsvgPaintCtx {
        rgbcolor fill;         /* fill color attribute */
        char *fill_iri;        /* paint server if fill == IRI_COLOR */
        double fill_opacity;   /* fill-opacity attribute */
        rgbcolor stroke;       /* stroke color attribute */
        char *stroke_iri;      /* paint server if stroke == IRI_COLOR */
        double stroke_width;   /* stroke-width attribute */
        double stroke_opacity; /* stroke-opacity attribute */
        TMatrix tmatrix;       /* transformation matrix */
        int text_anchor;       /* text-anchor attribute */
        char *sfont_family;    /* font-family string attribute */
        int ifont_family;      /* font-family type attribute */
        int font_style;        /* font-style attribute */
        int font_weight;       /* font-weight attribute */
        double font_size;      /* font-size attribute */
    } MsvgPaintCtx;
    

    it is important to note that all attributes in the MsvgPaintCtx can be inherited from his father element. This can be doing explicitly with the value INHERIT_COLOR (for colors) INHERIT_VALUE (for doubles) or INHERIT_IVALUE (for ints), that correspond to the raw keyword value "inherit", or implicitly when the value is NODEFINED_COLOR (for colors) NODEFINED_VALUE (for doubles) or NODEFINED_IVALUE (for ints), that occurs when the raw value is not defined and the father value is defined.

    fill and stroke colors can have the value NO_COLOR, that correspond to the raw keyword value "none" that means fill or stroke must not be done.

    fill and stroke colors can have the value IRI_COLOR, that correspond to the raw keyword value "url(#iri)" that means fill or stroke with the paint server referenced, the iri value is stored in fill_iri or stroke_iri.

    tmatrix is special, if undefined it defaults to the identity matrix. Before rasterizing any element it is neceary to calculate the Current Transformation Matrix, multiplying all the ancestors transformation matrix in order and finally with the own element transformation matrix.

    The font-family raw attribute will be stored literaly (if fefined) in sfont_family, a processed integer based in the raw attribute will be stored in ifont_family, aside from INHERIT_IVALUE and NODEFINED_IVALUE it can be one of the values defined in msvg.h

    Values that can be converted from raw to cooked are listend in Appendix B.

    Cooked specific attributes are different for each element type id (with some exceptions) and are stored in a union, you can inspect each one in the msvg.h include file.

    Exceptions: EID_DEFS and EID_G don't have specific cooked attributes really, in this version of libmsvg they have a dummy one but probably it will be deleted in future versions. EID_MISSINGGLYPH share sppecific cooked attributes with EID_GLYPH.


    Reading SVG files

    Using the MsvgReadSvgFile function you load a SVG file in a MsvgElement tree.

        MsvgElement *root;
        int error;
        root = MsvgReadSvgFile("filein.svg", &error);
    

    If the file doesn't exists or if it isn't a valid SVG file, root will be NULL and error will be filled with the error number, so you must check it. Only the supported elements are stored in the tree, the not supported ones are silently ignored. The tree will be a RAW tree, with all the element attributes stored as raw attributes. If you want a COOKED tree you can use the MsvgRaw2CookedTree funtion:

        int result;
        result = MsvgRaw2CookedTree(root);
    

    result will be true if all was right, false otherwise. Note that after calling this function you have really a mixed RAW/COOKED tree, because all raw parameters are preserved. If you are sure you don't need the raw parameters anymore you can call the MsvgDelAllTreeRawAttributes function:

        int result;
        result = MsvgDelAllTreeRawAttributes(root);
    

    result will be the number of raw parameters deleted.


    Building a RAW MsvgElement tree by program

    Using only two function we can construct a MsvgElement tree by program. The MsvgNewElement function takes two parameters: the element type id and the father element, that can be NULL, it returns the pointer to the constructed element. The MsvgAddRawAttribute takes three parameters: an element pointer, a key and a value.

    Let's see an example. We begin constructing the SVG element passing NULL in the father parameter, because it is the root element. By default the tree will be RAW_SVGTREE.

        MsvgElement *root;
        root = MsvgNewElement(EID_SVG, NULL);
    

    Now, we add attributes to identify the svg element properly and set the drawing limits using the MsvgAddRawAttribute function.

        MsvgAddRawAttribute(root, "version", "1.2");
        MsvgAddRawAttribute(root, "baseProfile", "tiny");
        MsvgAddRawAttribute(root, "xmlns", "http://www.w3.org/2000/svg");
        MsvgAddRawAttribute(root, "xmlns:xlink", "http://www.w3.org/1999/xlink");
        MsvgAddRawAttribute(root, "viewBox", "0 0 400 400");
    

    We continue adding two child elements, a RECT element and a translated G element.

        MsvgElement *son;
        son = MsvgNewElement(EID_RECT, root);
        MsvgAddRawAttribute(son, "x", "1");
        MsvgAddRawAttribute(son, "y", "1");
        MsvgAddRawAttribute(son, "width", "398");
        MsvgAddRawAttribute(son, "height", "398");
        MsvgAddRawAttribute(son, "stroke", "#F00");
        MsvgAddRawAttribute(son, "fill", "#FFF");
        son = MsvgNewElement(EID_G, root);
        MsvgAddRawAttribute(son, "stroke", "#0F0");
        MsvgAddRawAttribute(son, "fill", "none");
        MsvgAddRawAttribute(son, "transform", "translate(50)");
    

    Finally we add two child CIRCLE elements to the G element, one directly and the other indirectly by an USE element. Note that they will inherit the stroke and fill attributes and the transformation matrix.

        MsvgElement *son2;
        son2 = MsvgNewElement(EID_CIRCLE, son);
        MsvgAddRawAttribute(son2, "id", "mycircle");
        MsvgAddRawAttribute(son2, "cx", "100");
        MsvgAddRawAttribute(son2, "cy", "200");
        MsvgAddRawAttribute(son2, "r", "80");
        son2 = MsvgNewElement(EID_USE, son);
        MsvgAddRawAttribute(son2, "xlink:href", "#mycircle");
        MsvgAddRawAttribute(son2, "x", "100");
    

    We have now our MsvgElement tree and we can manipulate it or write it to a file.


    Building a COOKED MsvgElement tree by program

    Here you have two options, you can construct a RAW MsvgElement tree and them call MsvgRaw2CookedTree or you can construct a COOKED tree directly.

    Constructing a COOKED tree is the same like constructing a RAW one, except we don't use the MsvgAddRawAttribute function. Instead we set directly the element cooked attributes, there are no functions to hide the variables, because we are programmers and we know what are we doing, doesn't it. The only precaution to take into account is to reserve memory when the parameter to be assigned is a pointer. This space will be freed if we delete the element.

        MsvgElement *root;
        root = MsvgNewElement(EID_SVG, NULL);
        root->psvgattr->vb_min_x = 0;
        root->psvgattr->vb_min_y = 0;
        root->psvgattr->vb_width = 400;
        root->psvgattr->vb_height = 400;
        root->psvgattr->tree_type = COOKED_SVGTREE;
    
        MsvgElement *son;
        son = MsvgNewElement(EID_RECT, root);
        son->prectattr->x = 1;
        son->prectattr->y = 1;
        son->prectattr->width = 398;
        son->prectattr->height = 398;
        son->pctx->fill = 0XFFFFFF;
        son->pctx->stroke = 0XFF0000;
        son = MsvgNewElement(EID_G, root);
        son->pctx->fill = NO_COLOR;
        son->pctx->stroke = 0X00FF00;
        TMSetTranslation(&(son->pctx->tmatrix), 50, 50);
    
        MsvgElement *son2;
        son2 = MsvgNewElement(EID_CIRCLE, son);
        son2->id = strdup("mycircle");
        son2->pcircleattr->cx = 100;
        son2->pcircleattr->cy = 200;
        son2->pcircleattr->r = 80;
        son2 = MsvgNewElement(EID_USE, son);
        son2->puseattr->refel = strdup("mycircle");
        son2->puseattr->x = 100;
    

    Manipulating a MsvgElement tree

    There are some functions to manipulate a MsvgElement tree.

    void MsvgPruneElement(MsvgElement *el);
    

    The MsvgPruneElement function prune an element from his tree and them we can insert it in another tree or in another point of the same tree. Note that if the pruned element has children, it retains them after pruned.

    void MsvgDeleteElement(MsvgElement *el);
    

    The MsvgDeleteElement does two things, prunes the element from his tree and deletes it, freeing the allocated memory used. Note that if the deleted element has children, they are deleted too.

    int MsvgInsertSonElement(MsvgElement *el, MsvgElement *father);
    int MsvgInsertPSiblingElement(MsvgElement *el, MsvgElement *sibling);
    int MsvgInsertNSiblingElement(MsvgElement *el, MsvgElement *sibling);
    

    This three functios insert an element (that can be a subtree if it has child) in the desired point of a tree. The MsvgInsertSonElement fucntion inserts the element like the last son of the indicated father. The MsvgInsertPSiblingElement function inserts the element like a previous sibling to the indicated sibling. And the MsvgInsertNSiblingElement function inserts the element like a next sibling. The three functions return 1 if all was ok, 0 otherwise.

    MsvgElement *MsvgDupElement(MsvgElement *el, int copytree);
    

    The MsvgDupElement function duplicates the element passed as the first parameter, if that element have children and the copytree parameter equals to 1 it duplicates all the child elements too.

    int MsvgReplaceElement(MsvgElement *old, MsvgElement *newe);
    

    MsvgReplaceElement replaces an old element (can be a subtree) by a new element (can be a subtree too) in a tree. Note that the old element is pruned from the tree, so if you don't need it remeber to call MsvgDeleteElement to delete it.


    Finding elements in a MsvgElement tree

    Walking a tree

    Walking a tree, visiting each element is easy using the element pointers, but if you prefer you can use a msvg funtion

    typedef void (*MsvgWalkUserFn)(MsvgElement *el, void *udata);
    
    void MsvgWalkTree(MsvgElement *root, MsvgWalkUserFn wufn, void *udata);
    

    MsvgWalkTree will call the suplied wufn user function for each element in the tree, in udata you can pass a user data struct or NULL

    Counting

    typedef struct {
        int nelem[EID_LAST+1];  // num elements per type
        int totelem;            // total num elements
        int totelwid;           // num elements with id != NULL
    } MsvgTreeCounts;
    
    void MsvgCalcCountsCookedTree(const MsvgElement *el, MsvgTreeCounts *tc);
    void MsvgCalcCountsRawTree(const MsvgElement *el, MsvgTreeCounts *tc);
    

    both functions fill a MsvgTreeCounts structure counting elements for the tree pointed by el, note that there are a version for raw trees and another for cooked trees, this is necsary to populate the totelwid variable because in raw trees is necesary to check the raw attribute "id" and for cooked trees we can use the id cooked attribute. Note that the root EID_SVG element is not counted

    Finding

    There are some functions to find elements in a MsvgElement tree.

    MsvgElement *MsvgFindFirstFather(MsvgElement *el);
    

    returns the first father of an element, normally a EID_SVG element

    MsvgElement *MsvgFindIdCookedTree(MsvgElement *el, char *id);
    MsvgElement *MsvgFindIdRawTree(MsvgElement *el, char *id);
    

    find for the element with the provided id in the tree pointed by el, again there is a version for raw trees and another for cooked trees. It returns NULL if no element is found

    The above functions walk all the tree comparing ids to find the element every time they are called, if you have to find lot of elements it is better to construct first a MsvgTableId structure calling one of these functions ( one for raw trees and one for cooked trees):

    MsvgTableId *MsvgBuildTableIdCookedTree(MsvgElement *el);
    MsvgTableId *MsvgBuildTableIdRawTree(MsvgElement *el);
    

    After that you can use next function to find elements (it does an efficient binary search):

    MsvgElement *MsvgFindIdTableId(const MsvgTableId *tid, char *id);
    

    Note that if you add or delete elements in the tree you have to build a new MsvgTableId structure. when it is no longer necessary use next function to free memory:

    void MsvgDestroyTableId(MsvgTableId *tid);
    

    Working with cooked transformation matrix

    libmsvg has a number of functions to work with the transformation matrix cooked parameter TMatrix. Note that the transformation matrix is like that:

        a c e
        b d f
        0 0 1
    

    so only the a, b, c, d, e, f values are stored int the TMatrix structure. Here are the functions:

    void TMSetIdentity(TMatrix *des);
    int TMIsIdentity(const TMatrix *t);
    int TMHaveRotation(const TMatrix *t);
    void TMSetFromArray(TMatrix *des, const double *p);
    void TMMpy(TMatrix *des, const TMatrix *op1, const TMatrix *op2);
    void TMSetTranslation(TMatrix *des, double tx, double ty);
    void TMSetScaling(TMatrix *des, double sx, double sy);
    void TMSetRotationOrigin(TMatrix *des, double ang);
    void TMSetRotation(TMatrix *des, double ang, double cx, double cy);
    void TMTransformCoord(double *x, double *y, const TMatrix *ctm);
    

    TMSetIdentity stores in des the identity matrix (1 0 0 1 0 0)
    TMIsIdentity returns 1 if it is the identity matrix
    TMHaveRotation returns 1 if it includes a rotation (b or c != 0)
    TMSetFromArray set des from the double array p of dimension 6
    TMMpy multiply op1 by op2 and stores the result in des
    TMSetTranslation sets des with a traslation
    TMSetScaling sets des with a scaling
    TMSetRotationOrigin sets des with a rotation about the origin
    TMSetRotation sets des with a rotation about cx, cy
    TMTransformCoord changes x, y coordinates using ctm


    Serialize a COOKED MsvgElement tree

    libmsvg provides a function to serialize a COOKED MsvgElement tree. Using this function is the easier way to rasterize a MsvgElement tree.

    int MsvgSerCookedTree(MsvgElement *root, MsvgSerUserFn sufn, void *udata);
    

    this function will process the tree and call the supply user function for every drawable element (not for container elements). The user function must be defined as:

    typedef void (*MsvgSerUserFn)(MsvgElement *el, MsvgPaintCtx *pctx, void *udata);
    

    where "el" is the pointer to the drawable element and pctx a pointer to a constructed MsvgPaintCtx whit all the inherit and initial values processed and the current transformation matrix calculated, so the user program must use this painting context instead the element one to do the drawing.

    The serialization process has into account the EID_USE elements, replacing them with the referenced element (that can be a subtree).

    When calling MsvgSerCookedTree a pointer to a user-data variable can be provided, that will be passed to the supply user function.

    Another function that libmsvg provides can be called by the user function:

    MsvgElement *MsvgTransformCookedElement(MsvgElement *el, MsvgPaintCtx *pctx,);
    

    that process the cooked element parameters using the current transformation matrix (pctx->tmatrix) and returns a new drawable element that can be of different type that the original one, by example if an EID_RECT has to be rotated an EID_POLYGON or a EID_PATH (if it has rounded corners) is returned. . The function also adjust the element paint context parameters and sets the transformation matrix to the identity matrix. Note that you have to delete the new element returned after using it.

    If MsvgTransformCookedElement cannot returns a properly transformed element it returns NULL and it is the user function responsability to manage correctly the element.

    So using all the pieces the code to rasterize a SVG image will be like that:

    static void userfn(MsvgElement *el, MsvgPaintCtx *pctx, void *udata)
    {
        MsvgElement *newel;
    
        newel = MsvgTransformCookedElement(el, pctx);
        if (newel == NULL) return; // or manage "el" + "pctx" by coding
    
        switch (newel->eid) {
            case EID_RECT :
                YourDrawRectElement(newel);
                break;
            case EID_CIRCLE :
                YourDrawCircleElement(newel);
                break;
            case EID_ELLIPSE :
                YourDrawEllipseElement(newel);
                break;
            case EID_LINE :
                YourDrawLineElement(newel);
                break;
            case EID_POLYLINE :
                YourDrawPolylineElement(newel);
                break;
            case EID_POLYGON :
                YourDrawPolygonElement(newel);
                break;
            case EID_PATH :
                YourDrawPathElement(newel);
                break;
            case EID_TEXT :
                YourDrawTextElement(newel);
                break;
           default :
                break;
        }
    
        MsvgDeleteElement(newel);
    }
    
    ...
    // in main
        MsvgElement *root
    ...
    // read or build the Msvg tree and convert it to a COOKED tree
    ...
    // set the graphics coordinates using the information in the EID_SVG element
    ...
        MsvgSerCookedTree(root, userfn, NULL);
    ...
    

    Converting text elements to path elements

    Despite the SVG standard defines a font element they don't recommend using it, instead they recommend to use external fonts. The result is that drawing text elements in a svg file is not predictable, it depends of the render program and the fonts instaled in the rendering machine. Even if you provides a svg font in your svg file and point text elements to that font most svg renderer programs use external fonts. Anyway one thing you can do is to convert text elements to path elements before render. Msvg provides some functions to help do that.

    Create a MsvgBFont structure

    Msvg provides the MsvgBFont structure to hold a binary representation for a font element (and his children)

    MsvgBFont *MsvgNewBFont(MsvgElement *el);
    void MsvgDestroyBFont(MsvgBFont *bfont);
    

    MsvgNewBFont cretes a new MsvgBFont from an EID_FONT element. It the element is not a EID_FONT element or there are problems creating the struct it returns NULL. Use MsvgDestroyBFont to destroy the MsvgBFont when you don't need it anymore

    Converting a unicode char to a path element

    MsvgElement *MsvgCharToPath(long unicode, double font_size, double *advx, MsvgBFont *bfont);
    

    This function return a EID_PATH element for the unicode char and the bfont provided, it needs the required font_size too and returns in advx the advance distance necesary to draw the next character.

    Converting a text element to a path element

    MsvgElement *MsvgTextToPathGroup(MsvgElement *el, MsvgBFont *bfont);
    

    MsvgTextToPathGroup returns an EID_G subtree with an EID_PATH element for each character the EID_TEXT element provided contents. To do that it calls MsvgCharToPath for each character in the EID_TEXT contents using the element font_size and advancing the x position. After that (and checking for NULL returns first) you can replace the text element with the group element in the tree, by example:

        MsvgElement *el, *font, *group;
        MsvgBFon *bfont;
    
        //... find EID_FONT element in a tree
        font = ...
        bfont = MsvgNewBFont(font);
        // remember to check for NULL in a real program
    
        //... find the EID_TEXT you want to convert
        el = ...
    
        if (el->eid == EID_TEXT) {
            group = MsvgTextToPathGroup(el, bfont);
            if (group) {
                if (MsvgReplaceElement(el, group))
                MsvgDeleteElement(el);
            }
        }
    
        // delete bfont if we don't need it anymore
        MsvgDestroyBFont(bfont);
     

    SVG fonts

    Obviusly to make use of the above functions you need SVG fonts, linmsvg came with some SVG fonts in the "gfont" subdirectory, they are subsets (covering Latin, Greek, Cyrilic and some other chars) of the Free GNU Fonts, but they are not part of libmsvg really, like the original ones they are covered by the GPLv3 but with an exception that let you to embed them in a document.

    SVG font library

    To make the conversion from text to path easier, libmsvg includes some support to build an internal SVG font library:

    int MsvgBFontLibLoad(MsvgElement *el);
    int MsvgBFontLibLoadFromFile(char *fname);
    MsvgBFont *MsvgBFontLibFind(char *sfont_family, int ifont_family);
    void MsvgBFontLibFree(void);
    MsvgElement *MsvgTextToPathGroupUsingBFontLib(MsvgElement *el);
    int MsvgBFontLibGetNumOfFonts(void);
    MsvgBFont *MsvgBFontLibGetBFontByNumber(int nfont);
    void MsvgBFontLibSetDefaultBfont(int nfont);
    

    the first function load the SVG fonts embeded in the cooked tree element provided if any. The second one load the SVG fonts in the fname file. MsvgBFontLibFind return a MsvgBFont pointer to a font in the library wich his sfont_family member match the value provided (or a part of it), if no one is found it returns the one that match the ifont_family parameter, them if no one is found it returns the first MsvgBFont with his ifont_family member values FONTFAMILY_SANS, finally if no one is found it returns the one selected as default with MsvgBFontLibSetDefaultBfont or NULL. The function MsvgBFontLibFree free all the fonts in the library. MsvgBFontLibGetNumOfFonts and MsvgBFontLibGetBFontByNumber can be used to report the fonts in the library.

    Finally MsvgTextToPathGroupUsingBFontLib replace text by path using the best font found in the library as commented with the MsvgBFontLibFind function. This is an excerpt of a possible code to manage the conversion:

        int nf = 0;
        ...
        // load external fonts
        nf = MsvgBFontLibLoadFromFile("../gfonts/rsans.svg");
        if (nf > 0) MsvgBFontLibSetDefaultBfont(0);
        nf += MsvgBFontLibLoadFromFile("../gfonts/rserif.svg");
        nf += MsvgBFontLibLoadFromFile("../gfonts/rmono.svg");
        // load internal fonts if any
        nf += MsvgBFontLibLoad(root);
        ...
        //... find the EID_TEXT you want to replace
        el = ...
    
        if (el->eid == EID_TEXT) {
            group = MsvgTextToPathGroupUsingBFontLib(el);
            if (group) {
                if (MsvgReplaceElement(el, group))
                MsvgDeleteElement(el);
            }
        }
        ...
        // Free the font library when we don't need it
        MsvgBFontLibFree();
    

    Converting path elements to poly elements

    Another interesting funtion included in libmsvg is:

    MsvgElement *MsvgSubPathToPoly(MsvgElement *el, int nsp, double px_x_unit);
    

    that transform a subpath from an EID_PATH element either in an EID_POLYLINE or an EID_POLYGON element (note that an EID_PATH element could have one or more subpaths). This way you can use a graphics library that not know how to handle Bezier curves to render the svg-tree. An example of coding that you can use while serialize a tree:

            case EID_PATH :
                nsp = MsvgCountSubPaths(newel->ppathattr->sp);
                for (i=0; i<nsp; i++) {
                    newel2 = MsvgSubPathToPoly(newel, i, 1);
                    if (newel2) {
                        if (newel2->eid == EID_POLYGON)
                            YourDrawPolygonElement(newel2);
                        else if (newel2->eid == EID_POLYLINE)
                            YourDrawPolylineElement(newel2);
                        MsvgDeleteElement(newel2);
                    }
                }
                break;
    

    the px_x_unit parameter indicates the number of pixels per unit of svg coordinates, it is important so that the function can calculate the number of points to interpolate for each bezier curve. Here we pass 1 for simplicity but in a real program it must be calculated to have smooth curves.

    The function:

    MsvgElement *MsvgPathToPolyGroup(MsvgElement *el, double px_x_unit);
    

    returns an EID_G subtree with all the subpaths converted to EID_POLYLINE or EID_POLYGON elements, so you can, by example, substitute the path element with the group:

        MsvgElement *el, *group;
    
        //... find the EID_PATH you want to convert
        el = ...
    
        if (el->eid == EID_PATH) {
            group = MsvgPathToPolyGroup(el, 1);
            if (group) {
                if (MsvgReplaceElement(el, group))
                MsvgDeleteElement(el);
            }
        }
     

    Note that there is a caveat in this procedure, when filling a path the holes must not be filled, but converting paths to polys you lost this information, so you need to do some special code to handle it. We try to add a solution to this in future libmsvg releases.


    Writing SVG files

    Using the MsvgWriteSvgFile function you can write a MsvgElement tree to a file.

        MsvgElement *root;
        int result;
        // construct the root tree
        result = MsvgWriteSvgFile(root, "fileout.svg")
    

    MsvgWriteSvgFile only know how to write RAW trees. If you want to write a COOKED only tree you need to use the MsvgCooked2RawTree funtion first:

        int result;
        MsvgDelAllTreeRawAttributes(root);
        result = MsvgCooked2RawTree(root);
    

    Note that after call MsvgCooked2RawTree you continue to have a COOKED tree, but with all the raw parameters populated. Because the function doesn't delete any previous raw parameters, it is good idea to delete them in advance.


    Appendix A, the libmsvg SVG subset

    This table lists the SVG elements and attributes that are intended to be supported by libmsvg.

    Each row lists an element, the supported attributes and the possible child elements.

    Note that attributes listed only affects to COOKED trees, RAW trees can have any attribute.

    A status mark (TO DO) means... you know: to do.

    Element (EID)

    Specific supported attributes (status)

    Notes

    <svg> (EID_SVG)

    version="1.2" (fixed value)

    baseprofile="tiny" (fixed value)

    xmlns="http://www.w3.org/2000/svg" (fixed value)

    xmlns:xlink="http://www.w3.org/1999/xlink" (fixed value)

    --- ---

    viewbox="min-x min-y width height"

    width="length"

    height="length"

    preserveaspectratio="value" (TO DO)

    viewport-fill="color"

    viewport-fill-opacity="n"

    id="value"

    --- for inheritance ---

    fill="color"

    fill-opacity="n"

    stroke="color"

    stroke-width="n"

    stroke-opacity="n"

    transform="transformation"

    text-anchor="value"

    font-family="value"

    font-style="value"

    font-weight="value"

    font-size="value"

    -- possible child elements --

    <defs>, <g>, <use>, <rect>, <circle>, <ellipse>, <line>, <polyline>, <polygon>, <path>, <text>

    <defs> (EID_DEFS)

    id="value"

    -- possible child elements --

    <g>, <rect>, <circle>, <ellipse>, <line>, <polyline>, <polygon>, <path>, <text>, <linearGradient>, <radialGradient>, <font>

    <g> (EID_G)

    id="value"

    --- for inheritance ---

    fill="color"

    fill-opacity="n"

    stroke="color"

    stroke-width="n"

    stroke-opacity="n"

    transform="transformation"

    text-anchor="value"

    font-family="value"

    font-style="value"

    font-weight="value"

    font-size="value"

    -- possible child elements --

    <g>, <use>, <rect>, <circle>, <ellipse>, <line>, <polyline>, <polygon>, <path>, <text>

    <use> (EID_USE)

    xlink:href ="#id"

    x="n"

    y="n"

    --- for inheritance ---

    fill="color"

    fill-opacity="n"

    stroke="color"

    stroke-width="n"

    stroke-opacity="n"

    transform="transformation"

    text-anchor="value"

    font-family="value"

    font-style="value"

    font-weight="value"

    font-size="value"

    Only local references are supported, stored in the "refel" cooked parameter.

    <rect> (EID_RECT)

    id="value"

    x="n"

    y="n"

    width="n"

    height="n"

    rx="n"

    ry="n"

    fill="color"

    fill-opacity="n"

    stroke="color"

    stroke-width="n"

    stroke-opacity="n"

    transform="transformation"


    <circle> (EID_CIRCLE)

    id="value"

    cx="n"

    cy="n"

    r="n"

    fill="color"

    fill-opacity="n"

    stroke="color"

    stroke-width="n"

    stroke-opacity="n"

    transform="transformation"


    <ellipse> (EID_ELLIPSE)

    id="value"

    cx="n"

    cy="n"

    rx="n"

    ry="n"

    fill="color"

    fill-opacity="n"

    stroke="color"

    stroke-width="n"

    stroke-opacity="n"

    transform="transformation"

    Using "rx", "ry" and "cx", "cy" raw parameters there will be calculated the "rx_x", "rx_y", "ry_x", "ry_y" cooked parameters, storing the real semi-axis points, so it will be easier transforming and rasterizing an ellipse.

    <line> (EID_LINE)

    id="value"

    x1="n"

    y1="n"

    x2="n"

    y2="n"

    stroke="color"

    stroke-width="n"

    stroke-opacity="n"

    transform="transformation"


    <polyline> (EID_POLYLINE)

    id="value"

    points="data"

    fill="color"

    fill-opacity="n"

    stroke="color"

    stroke-width="n"

    stroke-opacity="n"

    transform="transformation"


    <polygon> (EID_POLYGON)

    id="value"

    points="data"

    fill="color"

    fill-opacity="n"

    stroke="color"

    stroke-width="n"

    stroke-opacity="n"

    transform="transformation"


    <path> (EID_PATH)

    id="value"

    d="path-data"

    fill="color"

    fill-opacity="n"

    stroke="color"

    stroke-width="n"

    stroke-opacity="n"

    transform="transformation"


    <text> (EID_TEXT)

    id="value"

    x="n"

    y="n"

    fill="color"

    fill-opacity="n"

    stroke="color"

    stroke-width="n"

    stroke-opacity="n"

    transform="transformation"

    text-anchor="value"

    font-family="value"

    font-style="value"

    font-weight="value"

    font-size="value"

    The TEXT content will be stored in a MsvgContent variable.

    <linearGradient> (EID_LINEARGRADIENT)

    id="value"

    gradientUnits="value"

    x1="n"

    y1="n"

    x2="n"

    y2="n"

    -- possible child elements --

    <stop>

    <radialGradient> (EID_RADIALGRADIENT)

    id="value"

    gradientUnits="value"

    cx="n"

    cy="n"

    r="n"

    -- possible child elements --

    <stop>

    <stop> (EID_STOP)

    id="value"

    stop-color="color"

    stop-opacity="n"


    <font> (EID_FONT)

    id="value"

    horiz-adv-x="n"

    -- possible child elements --

    <font-face>, <missing-glyph>, <glyph>

    <font-face> (EID_FONTFACE)

    id="value"

    font-family="value"

    font-style="value"

    font-weight="value"

    units-per-em="n"

    ascent="n"

    descent="n"


    <missing-glyph> (EID_MISSINGGLYPH)

    id="value"

    horiz-adv-x="n"

    d="path-data"


    <glyph> (EID_GLYPH)

    id="value"

    unicode="value"

    horiz-adv-x="n"

    d="path-data"



    Appendix B, raw to cooked parameter conversion

    This table list the possible values that some parameters can have (obvious parameters are not listed).

    Note that these are the values that are expected when converting to a COOKED tree, RAW parameters can have any value.

    A status mark (TO DO) means... you know: to do.

    Parameters

    Raw value => Cooked value (status)

    Notes

    svg.width

    svg.height

    number => same value

    numberpt => number * 1.25

    numberpc => number * 15

    numbermm => number * 3.54

    numbercm => number * 35.4

    numberin => number * 90

    We assume 90 px per inch

    If width not defined or <=0 width = viewbox.width
    If height not defined or <=0 height = viewbox.height
    If viewbox.width not defined or <=0 viewbox.width = width
    If viewbox.height not defined or <=0 viewbox.height = height
    If width continues undefined width = viewbox.width = 600;
    If height continues undefined height = viewbox.height = 300;

    viewport-fill

    fill

    stroke

    stop-color

    NO DEFINED => NODEFINED_COLOR

    none => NO_COLOR

    currentColor => INHERIT_COLOR

    inherit => INHERIT_COLOR

    black => 0x000000

    silver => 0xc0c0c0

    gray => 0x808080

    white => 0xffffff

    maroon => 0x800000

    red => 0xff0000

    purple => 0x800080

    fuchsia => 0xff00ff

    green => 0x008000

    lime => 0x00ff00

    olive => 0x808000

    yellow => 0xffff00

    navy => 0x000080

    blue => 0x0000ff

    teal => 0x008080

    aqua => 0x00ffff

    #rgb => 0xrrggbb

    #rrggbb => 0xrrggbb

    rgb(rrr, ggg, bbb) => 0xrrggbb (TO DO)

    url(#iri) ==> IRI_COLOR (See notes)

    If viewport-fill is not defined it will be NO_COLOR.

    If after the inheritance process fill is not defined it will be 0x000000.

    If after the inheritance process stroke is not defined it will be NO_COLOR.

    If stop-color is not defined it will be 0x000000.

    "url(#iri)" is a local reference to a paint server, normally a gradient, only valid for fill and stroke, the iri itself is stored in fill_iri or stroke_iri variables.

    viewport-fill-opacity

    fill-opacity

    stroke-opacity

    stop-opacity

    NO DEFINED => NODEFINED_VALUE

    inherit => INHERIT_VALUE

    number between 0.0 and 1.0 => same value

    If viewport-fill-opacity is not defined it will be 1.

    If after the inheritance process fill-opacity is not defined it will be 1.

    If after the inheritance process stroke-opacity is not defined it will be 1.

    If stop-opacity is not defined it will be 1.

    stroke-width

    NO DEFINED => NODEFINED_VALUE

    inherit => INHERIT_VALUE

    number > 0.0 => same value

    If after the inheritance process stroke-width is not defined it will be 1.

    transform

    NO DEFINED => (1 0 0 1 0 0)

    none => (1 0 0 1 0 0)

    matrix(a b c d e f) => (a b c d e f)

    translate(x) => (1 0 0 1 x 0)

    translate(x y) => (1 0 0 1 x y)

    rotate(ang) => (cos(ang) sin(ang) -sin(ang) cos(ang) 0 0)

    rotate(ang cx cy) => traslate(cx cy) * rotate(ang) * translate(-cx -cy)

    scale(sx) => (sx 0 0 sx 0 0)

    scale(sx sy) => (sx 0 0 sy 0 0)

    skewX(ang) => (1 0 tan(ang) 1 0 0) (TO DO)

    skewY(ang) => (1 tan(ang) 0 1 0 0) (TO DO)

    ref(svg) => no transform for this element (TO DO)

    Note that before a drawable element can be rasterized it is necesary to calculate the current transformation matrix multiplying all transformation matrix from his parents elements.

    text-anchor

    NO DEFINED => NODEFINED_IVALUE

    inherit => INHERIT_IVALUE

    start => TEXTANCHOR_START

    middel => TEXTANCHOR_MIDDLE

    end => TEXTANCHOR_END

    If after the inheritance process text-anchor is not defined it will be TEXTANCHOR_START.

    font-family

    NO DEFINED => sfont_family=NULL and ifont_family=NODEFINED_IVALUE

    if defined sfont_family=value and ifont_family will have the integer constant defined bellow:

    inherit => INHERIT_IVALUE

    value contents "sans" => FONTFAMILY_SANS

    value contents "serif" => FONTFAMILY_SERIF

    value contents "cursive" => FONTFAMILY_CURSIVE

    value contents "fantasy" => FONTFAMILY_FANTASY

    value contents "monospace" => FONTFAMILY_MONOSPACE

    other values => FONTFAMILY_OTHER

    The literal value of font-family will be stored in sfont_family, a processed integer will be stored in ifont_family

    If after the inheritance process font-family is not defined ifont_family will be FONTFAMILY_SANS and sfont_family will be NULL.

    font-style

    NO DEFINED => NODEFINED_IVALUE

    inherit => INHERIT_IVALUE

    normal => FONTSTYLE_NORMAL

    italic => FONTSTYLE_ITALIC

    oblique => FONTSTYLE_OBLIQUE

    If after the inheritance process font-style is not defined it will be FONTSTYLE_NORMAL.

    font-weight

    NO DEFINED => NODEFINED_IVALUE

    inherit => INHERIT_IVALUE

    100 => FONTWEIGHT_100

    200 => FONTWEIGHT_200

    300 => FONTWEIGHT_300

    400 => FONTWEIGHT_400

    500 => FONTWEIGHT_500

    600 => FONTWEIGHT_600

    700 => FONTWEIGHT_700

    800 => FONTWEIGHT_800

    900 => FONTWEIGHT_900

    normal => FONTWEIGHT_NORMAL (=FONTWEIGHT_400)

    bold => FONTWEIGHT_BOLD (=FONTWEIGHT_700)

    bolder => (TODO)

    lighter => (TODO)

    If after the inheritance process font-weight is not defined it will be FONTWEIGHT_NORMAL.

    font-size

    NO DEFINED => NODEFINED_VALUE

    inherit => INHERIT_VALUE

    number => same value

    numberpt => number * 1.25

    numberpc => number * 15

    numbermm => number * 3.54

    numbercm => number * 35.4

    numberin => number * 90

    [ xx-small | x-small | small | medium | large | x-large | xx-large ] => (TODO)

    [ larger | smaller ] => (TODO)

    If after the inheritance process font-size is not defined it will be 12.

    points

    x1,y1 x2,y2 ... => double values

    Stored in:
    double *points; // points values
    int npoints; // number of points

    d

    path string => linked list of normalized MsvgSubPath structs

    Stored in:
    MsvgSubPath *sp;
    Normalized subpaths only have M (at the begining), L, C or Q commands, the variable sp->closed indicated if it is a closed subpath or not.

    gradientUnits

    NO DEFINED => GRADUNIT_BBOX

    objectBoundingBox => GRADUNIT_BBOX

    userSpaceOnUse => GRADUNIT_USER

    Stored in the gradunits variable.

    offset

    number between 0.0 and 1.0 => same value

    If offset is not defined it will be 0.

    unicode

    string => read first UTF-8 char

    note that expat change html entities first:

    &#xn => hexadecimal code point n

    &amp; => &

    &quot; => "

    &apos; => '

    &lt; => <

    &gt; => >

    If there are more than one UTF-8 characters in the value string or unicode is not defined the stored value is 0


    Appendix C, quirks

    Style property

    Not in the Tiny 1.2 specs, but there are lot of svg images that instead of using individual style attributes group them in a unique style property using this format:

    style="attribute:value;attribute:value;..."

    If libmsvg finds such a construct it replaces it with individual raw attributes.

    Elliptical arcs

    The SVG 1.1 specs defines 'a', 'A' commands in paths as elliptical arcs, it isn't in the Tiny 1.2 specs. If libmsvg find some they will be replaced by lines.

    Gradient normalization

    The SVG 1.1 specs let gradients to refer other gradients and inherit the attributes not defined and even the stop elements. Not in the Tiny 1.2 specs but because it is heavilly used libmsvg has a function to normalize gradients to the Tiny 1.2 specs:

    int MsvgNormalizeRawGradients(MsvgElement *el);
    

    Call it after reading a svg file and before cooking the tree.