Last update: November 1, 2023
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.
I think the best way to start with a programmer's guide is to show examples, so here there are.
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; }
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; }
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 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:
Not every element can have any child. For each element type id there is a list of elements that can be its children:
In addition to the elements mentioned previously, libmsvg supports several elements that are considered comments and do not intervene in the definition of the drawing, these are the EID_TITLE and EID_DESC elements defined in the SVG standard and also XML comments as the virtual element EID_V_COMMENT.
In this version of libsmvg comment elements are supported only if they are direct children of a container element: EID_SVG, EID_DEFS, EID_G or EID_FONT.
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.
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 attributes read are stored as raw attributes. This stage of the tree may be enough for some programs. Elements and attributes can be added, deleted or reordered and finally be written to a file using the MsvgWriteSvgFile function.
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 stage the tree is easier to manipulate and/or to rasterize 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.
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 content for the EID_TEXT element (the text that must be draw) and for the three comment elements (the comment itself) are stored in the tree.
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 */ MsvgBPServerPtr fill_bps; /* binary paint server for fill */ double fill_opacity; /* fill-opacity attribute */ rgbcolor stroke; /* stroke color attribute */ char *stroke_iri; /* paint server if stroke == IRI_COLOR */ MsvgBPServerPtr stroke_bps; /* binary paint server for stroke */ 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 done explicitly when the value is 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.
fill_bps and stroke_bps can point to binary paint server structs, these variables are not filled by default and will be discussed later.
tmatrix is special, if undefined it defaults to the identity matrix. Before rasterizing any element it is necesary 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
fill_bps and stroke_bps can hold a binary paint server struct, these variables are not populated normally and will be discussed later.
Cooked specific attributes are different for each element type id (with some exceptions) and are stored in an 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.
Values that can be converted from raw to cooked are listed in Appendix B.
Using the MsvgReadSvgFile function you can 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 need to use the MsvgRaw2CookedTree function after reading:
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 return the number of raw parameters deleted.
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 construct an element of the type id requested, adds that element to the father linked list of children (if it is not NULL) and returns the pointer to the constructed element. If there is any problem, like not enough memory or a type id not supported, it returns NULL, so in a real program the returned pointer must be checked. 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. Note that we can reuse the son variable after adding attributes, because the element itself is saved in the father linked list of children.
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.
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;
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.
Walking a tree, visiting each element is easy using the element pointers, but if you prefer you can use a libmsvg 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
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
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 the 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 the function bellow to free memory:
void MsvgDestroyTableId(MsvgTableId *tid);
Sometimes the image dimensions declared in the EID_SVG are incorrect, if necesary you can use the next function to have a rough estimation:
int MsvgGetCookedDims(MsvgElement *root, double *minx, double *maxx, double *miny, double *maxy);Internally this function serialize a cooked tree, do all necesary coordinate transformation and returns the max and min coordinates found. We will speak about serialization later.
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 t is the identity matrix
TMHaveRotation returns 1 if t 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
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, int genbps);
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.
The genbps parameter indicates to generate or not binary paint servers when possible and link there to the pctx struct passed to the user function, we will speak later about this.
Another function that libmsvg provides can be called by the user function:
MsvgElement *MsvgTransformCookedElement(MsvgElement *el, MsvgPaintCtx *pctx, int mode);
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.
The mode parameter can be set to 0 (or MSVGTCE_NORMAL) or to an ored combination of MSVGTCE_CIR2PATH and MSVGTCE_ELL2PATH. The first will returns path elements aproximating a circle for circle elements, the second returns path elements aproximating an ellipse for ellipse elements. It can be used for graphics libraries that doesn't have primitives to draw circles and/or ellipses. Other options can will be added in the future
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, 0); ...
To help a graphics library to rasterize gradients libmsvg includes a struct that can hold a binary representation of a gradient:
/* binary paint servers types */ #define BPSERVER_LINEARGRADIENT 1 #define BPSERVER_RADIALGRADIENT 2 /* max stops a binary gradient can have */ #define BGRADIENT_MAXSTOPS 10 /* binary paint server struct */ typedef struct _MsvgBGradientStops { int nstops; /* num stops */ double offset[BGRADIENT_MAXSTOPS]; /* offset [0..1] */ double sopacity[BGRADIENT_MAXSTOPS]; /* stop opacity attribute */ rgbcolor scolor[BGRADIENT_MAXSTOPS]; /* stop color attribute */ } MsvgBGradientStops; typedef struct _MsvgBLinearGradient { int gradunits; /* Gradient units */ double x1; /* grad vector x1 coordinate */ double y1; /* grad vector y1 coordinate */ double x2; /* grad vector x2 coordinate */ double y2; /* grad vector y2 coordinate */ MsvgBGradientStops stops; /* stops data */ } MsvgBLinearGradient; typedef struct _MsvgBRadialGradient { int gradunits; /* Gradient units */ double cx; /* x center coordinate */ double cy; /* y center coordinate */ double r; /* radius */ MsvgBGradientStops stops; /* stops data */ } MsvgBRadialGradient; typedef struct _MsvgBPServer { int type; union { MsvgBLinearGradient blg; MsvgBRadialGradient brg; }; } MsvgBPServer;
You can create a binary paint server from a cooked EID_LINEARGRADIENT or EID_RADIALGRADIENT element and their EID_STOP children:
MsvgBPServer *MsvgNewBPServer(MsvgElement *el);
And destroy it when you don't need it anymore:
void MsvgDestroyBPServer(MsvgBPServer *bps);
There is a special function that, given the bounding box of an element and its transformation matrix, calculates the real units of the gradient (if these are GRADUNIT_BBOX), and then transforms them according to the matrix. Both bbox or t can be NULL and the corresponding operation is not applied.
int MsvgCalcUnitsBPServer(MsvgBPServer *bps, MsvgBox *bbox, TMatrix *t);
Remember the MsvgSerCookedTree genbps parameter? If it is true (not 0) the function will generate binary paint servers and populates the fill_bps and stroke_bps variables of the MsvgPaintCtx struct passed to the user function when possible. Namelly if the fill variable value is IRI_COLOR and it is found a corresponding gradient element to the fill_iri variable the fill_bps variable will be filled with a point to the generate binary paint server. The same for the stroke variable. After that if we call MsvgTransformCookedElement in the user function it will recognize the variables and will call MsvgCalcUnitsBPServer with the element properties, so that the binary paint servers are ready for the graphics library to render.
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 render programs use external fonts. Anyway one thing you can do is to convert text elements to path elements before render. libsvg provides some functions to help do that.
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 an 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
MsvgElement *MsvgCharToPath(long unicode, double font_size, double *advx, MsvgBFont *bfont);
This function return an 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.
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);
Obviusly to make use of the above functions you need SVG fonts, libmsvg 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.
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();
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.
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.
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>, <title>, <desc>, <!-- --> |
<defs> (EID_DEFS) |
id="value" |
-- possible child elements -- <g>, <rect>, <circle>, <ellipse>, <line>, <polyline>, <polygon>, <path>, <text>, <linearGradient>, <radialGradient>, <font>, <title>, <desc>, <!-- --> |
<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>, <title>, <desc>, <!-- --> |
<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>, <title>, <desc>, <!-- --> |
<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" |
|
<title> (EID_TITLE) |
id="value" |
The TITLE content will be stored in a MsvgContent variable. |
<desc> (EID_DESC) |
id="value" |
The DESC content will be stored in a MsvgContent variable. |
<!-- --> (EID_V_COMMENT) |
The XML comment content will be stored in a MsvgContent variable. |
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 |
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: |
d |
path string => linked list of normalized MsvgSubPath structs |
Stored in: |
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 & => & " => " ' => ' < => < > => > |
If there are more than one UTF-8 characters in the value string or unicode is not defined the stored value is 0 |
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.
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.
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 on the root element after reading a svg file and before cooking the tree. The function returns the number of gradients normalized.