A compact parser for SVG path shape definitions. It allows to generate the same path in absolute coordinates for easy manipulations by hand or in relative coordinates, allowing easy manual placement. The parser is based on Mikko Mononen's NanoSVG parser. The new parser uses a simpler and more accurate path format, using SVG commands as its internal representation.
The source code containing the new parser is complemented with a main
function that receives the string to be parsed, which is the "d" attribute of a path. The program can also receive other arguments as detailed below. Once the executable is run, a path with equivalent "d" attribute is displayed, but converted entirely to absolute coordinates or to relative coordinates. The absolute coordinates allow to easily debug it or to reuse parts of the path to contruct new paths with, while the relative coordinates allow to easily experiment with the path in diferent positions.
The resulting application can be helpful to designers or programmers willing to resuse the path in different contexts or to experienced web programmers to redesign the path by hand. It is possible, for example, to redesign the entire path only using integers. The path can be repositioned and/or have its scale changed and/or rotated (passing a matrix with "-m" argument) to mix with other paths defined in different scales. This kind of activity was only possible using vector graphics editors like Illustrator or Inkscape, but even in these applications the automatization of this kind of task is hard. Here it only requires using a batch or a script file.
The source code in itself shows how to use the parser programatically and allows to easily extract the parser to be used in other contexts. The main motivation that came into mind when rewriting this parser was the possibility to reuse it in transpilers, where a GUI defined in SVG can be translated to a specific language or library.
While NanoSVG parser generates only cubic Bezier curves, the new parser stores the path with SVG's original commands and points as much as possible. The only difficulty was with HLINETO and VLINETO single coordinate commands that had to be substituted by LINETO commands in order to allow rotations. Since the matrix is systematically applied to all shapes, two coordinates are necessary to allow the calculation. Another difficulty was with ARCTO commands which also requires storing the angle of the rotation besides the matrix.
The code (source code and compiled executable a.exe) implements an application that needs inline input arguments to be executed correctly. According to the parameters supplied, it will print on the console a complete SVG path command, either in absolute coordinates or in relative coordinates (using "-r" - see below). The SVG generated is optimized substituting "l" or "L" commands by "h", "H", "V" or "v" commands when appropriate, and further optimized when either of these commands would not change the current point (as for example, "h0" or "v0"). In this case they are ignored, since they are equivalent to no operations. The program also eliminates spaces between commands, since this allows to save space.
Notice that in the relative coordinates mode the first path command is always an "M" which supplies the initial coordinates of the path in absolute coordinates. This allows the path to be placed at the coordinates declared there, whereas all the other elements of the path will follow flawlessly, since they are all in relative coordinates. Even if the first command in a path is "m" it will always be interpreted as an "M" command according to W3C SVG standard.
The source code generates traces when DEBUG is defined. It also generates numbers in full float precision when VERBOSE is defined, otherwise all results are truncated to a maximum of 3 digits after the decimal point.
The only compulsory parameter for the application is the "d" attribute of a path that is to be parsed. This attribute completely describes the shape of the path without the need of other atributes. It must appear between quotes and separated by spaces from the program executable name or from the other program parameters. If no other parameters are given, the path to be parsed is converted to absolute coordinates and shown in the shell window. For example, supposing the executable "a.exe" called from a shell window, the program can be called like this:
./a "M100,0 a100,50 0 1 1 0,-1 "
The program will display in the next line the same path in absolute coordinates:
<path d="M100,0A100,50 0 1 1 100,-1"/>
Since only the "d" attribute of the path is passed as a parameter here, the program only adds the path tag, besides converting the path to absolute coordinates as shown. To add more path attributes besides "d" one can use the "-p" command explained below. Notice that one can drop the "./" prefix when calling from a batch file. Also notice that "a.exe" ("a.out" on Linux) is the standard output of C/C++ compilers, when the executable is not explicitly named. For the sake of simplicity and since the source and the executable files are both unique in the directory containing them, "a.exe" is implictly refering to the source file "SVGparser.c".
A summary of the commands that can be in the same line of the program call, all separated by spaces from one another, are shown in the table below:
Command | Parameter | Description | Example |
---|---|---|---|
-r |
none | generates a path with relative coordinates | -r |
-m |
matrix | transformation matrix with 6 elements separated by spaces inside a string | -m"1 0 0 1 100 100" |
-p |
string1 | attributes to be included in the path such as color, stroke-width, etc. | -p"stroke=\"#DB362D\" fill=\"none\"" |
-e |
string2 | commands to be included at the end of the path | -ez |
-a |
number | the rotation angle in degrees (can be supplied instead of a matrix) | -a12.5 |
- a string with no white spaces (thus, with only one attribute) can appear without the external quotes
- a string with only one word can appear without quotes, but explict quotes are necessary if a command with arguments or several commands are used in this context
Since it is cumbersome to type commands each time one calls a program in a shell window, it is recommended to call the program using batch files. It is possible to generate entire SVG files only using batch files and calling the application from it, as illustrated in the following examples.
One should read the batch (or the bash scripts) files to check how the examples are generated. By running any of the batch files one will see the resulting paths one after the other being displayed in the shell window. These paths can then be copied and pasted in a file containing already a standard SVG file header (<svg>
tag at the start and </svg>
tag at the end) and use it as an empty frame like in the examples. See how to create an SVG file header and how to set a viewport below.
One possibility for contructing batch files is using w64devkit bash scripts. These scripts are supplied in this repository. They have the suffix .sh
after their names and have #!/bin/bash
in their first line. These scripts are prefered instead of Windows batch files because they are easier to understand and because they can also be used on Linux platforms. The only inconvenient in running them on Windows is that they cannot run in standard Windows command terminals without some previous preparation. To be able to run bash scripts with w64devkit, the most innocuous way (w64devkit.exe is not advised) is following these steps:
- Open a Windows command terminal, not a Powershell: right-click at
Start
, then clickRun
, typecmd.exe
and clickOK
or pressEnter
. - Transform the Windows command terminal into a Linux command window: type
sh -l
and pressEnter
- Go to the directory where your bash script is: copy the location from the explorer window, type
cd
followed by a blank space, type"
, right-click the command window header, chooseEdit > Paste
(Control-V doesn't work), type"
again and pressEnter
. - Type the name of the bash and press
Enter
.
Steps 1 to 3 initialize any Windows command terminal to use any Linux commands with w64devkit. Many commands work on Powershell as well, as for example calling the gcc
compiler, but not bash.
To compile on Windows with w64devkit one merely calls gcc:
gcc SVGparser.c
To run the bash scripts to run the compiled program, one needs to go to the NASA
and ellipses
directories first. From those directories one simply calls the bash scripts from there. For example, in NASA
diectory:
./NASA.sh
Once the bash scripts are run, the content can be copied by just selecting and pressing Enter
.
To compile SVGparser.c on Linux use the script build_linux.sh
. Linux requires to compile with C99 standard and to explicitly link math library because of some float functions that are needed by the parser (with w64devkit that's not necessary). Also, on Linux, the executable is called "a.out." To use the same bash script on Windows and on Linux, one needs to change the name of the executable file from "a.out" to "a.exe". All this is done by the build script build_linux.sh
. Also, on Linux one needs to explicitly type the ".sh" suffix in order to run the script.
Therefore, on Linux one needs to call the build script in this way:
./build_linux.sh
To run the bash scripts to run the compiled program, one needs to proceed in the same way as described in the previous section.
Once the bash scripts are run, the content can be copied by just selecting and right-cliking.
The Windows batch files use some conventions that are specific to them. A table of common expressions used in Windows batch files is shown below
Batch expression | Description |
---|---|
@echo off | command that says the commands executed by the batch file are not going to be shown, including this command (because it's preceded by a "@") |
%~dp0..\a | "%~dp0" indicates the current directory in which the batch file is located. "..\a" following "%~dp0" means to call executable "a.exe" in the parent of the current directory |
The viewport is an essencial part of SVG graphics because it defines the bounding box of all the shapes between <svg>
and <\svg>
. Some SVG viewers require the viewport in order to display the SVG contents and the contents can be cut or be invisible if the viewport is incorrect.
This is how an SVG with a viewport is defined:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 230 130">
<!-- the paths are to be inserted here -->
</svg>
This viewport indicates that the SVG is to be viewed into a rectangle between points (0,0) and (230,130). Supposing the following graphics, this viewport is defined by the rectangle:
The viewport attribute of the <svg>
tag is entirely the user's resposibility, mainly because one is supposed to add elements by hand into the SVG at will. Therefore, only the user will know what will be the bounding box enveloping all the elements in an svg manipulated in this way. This is also simpler and more compatible with other vector formats and languages where the bounding box is rarely calculated automatically.
Example 1: NASA Logo
The NASA logo above is a good example on how to parse the original SVG that mixes absolute and relative coordinates, to generate a file that contains either only absolute coordinates or only relative coordinates (except for the initial "M" command which must be in absolute coordinates).
The NASA directory within this repository shows several files (described in the table below) which work assuming the hierarchy in the repository, that is, the executable is located in the parent directory and the files using the program are located in NASA directory.
NASA directory, thus contain the following files
Filename | Description |
---|---|
NASA_logo.svg | the original NASA logo in SVG |
NASA-absolute.svg | the NASA logo converted to absolute coordinates using NASAa.bat or NASAa.sh |
NASA-relative.svg | the NASA logo converted to relative coordinates using NASA.bat or NASA.sh |
NASAa.bat | Windows batch file that reuses each path of the original NASA SVG logo and that generates them in absolute coordinates |
NASA.bat | Windows batch file that reuses each path of the original NASA SVG logo and that generates them in relative coordinates |
NASAa.sh | bash script producing the same result as NASAa.bat |
NASA.sh | bash script producing the same result as NASA.bat |
The batch files contain the three paths in the original SVG passed as parameters to the parser, except for small changes. The most notable is that the third path in the original file becomes the second path in the batch files. This is because this path corresponds to second and third letters of the logo, and they should appear right after the first path, not as the last one as in the original file. Another change was made in few coordinates to avoid the generation of useless commands. This corrections were made by observing the initial coordinates generated by the parser in relative coordinates.
This is a another use of the parser: to use relative coordinates to simplify paths. The careful reader will be able to easily spot these changes by examining the data used in the batch files and comparing them with the original file. Comments about these changes are not going to be addressed further in this text.
Example 2: Rotated ellipses
The ellipse is probably one of the most particular path commands in SVG and this example was required to test "A" or "a" commands in the parser to be used to draw rotated ellipses. On the other hand, rotated SVG ellipses are easier to be generated on the fly by using ad hoc functions in JavaScript. An experienced JavaScript programmer could produce the same paths on the fly and then use inspect to copy the SVG paths generated. The purpose of this example is to demonstrate an alternative way to generate rotated static SVG ellipses without the need of programming using an initial trivial ellipse centered at the origin defined in absolute coordinates aligned with both axes, and to use transformation matrices to rotate it and to translate it to the desired point.
The ellipses directory within this repository include two simpler examples and a more complex one containing both examples in the same svg file (the image above is a rendering of the file mentioned here that shows both examples side by side).
The first simple example generates 4 ellipses, each one rotated 45 degrees from the previous one. One uses ellipse4.bat:
@echo off
%~dp0..\a -r -p"fill=\"none\" stroke=\"red\" stroke-width=\"2\"" -ez -m"1 0 0 1 100 100" "M 100 0 A 100 50 0 1 1 100 -1"
%~dp0..\a -r -p"fill=\"none\" stroke=\"green\" stroke-width=\"2\"" -ez -m".70710678 .70710678 -0.70710678 .70710678 100 100" "M 100 0 A 100 50 0 1 1 100 -1"
%~dp0..\a -r -p"fill=\"none\" stroke=\"blue\" stroke-width=\"2\"" -ez -m"0 1 -1 0 100 100" "M 100 0 A 100 50 0 1 1 100 -1"
%~dp0..\a -r -p"fill=\"none\" stroke=\"magenta\" stroke-width=\"2\"" -ez -m"-.70710678 .70710678 -0.70710678 -.70710678 100 100" "M 100 0 A 100 50 0 1 1 100 -1"
Or the ellipse4.sh script:
#!/bin/bash
../a.exe -r -p"fill=\"none\" stroke=\"red\" stroke-width=\"2\"" -ez -m"1 0 0 1 100 100" "M 100 0 A 100 50 0 1 1 100 -1"
../a.exe -r -p"fill=\"none\" stroke=\"green\" stroke-width=\"2\"" -ez -m".70710678 .70710678 -0.70710678 .70710678 100 100" "M 100 0 A 100 50 0 1 1 100 -1"
../a.exe -r -p"fill=\"none\" stroke=\"blue\" stroke-width=\"2\"" -ez -m"0 1 -1 0 100 100" "M 100 0 A 100 50 0 1 1 100 -1"
../a.exe -r -p"fill=\"none\" stroke=\"magenta\" stroke-width=\"2\"" -ez -m"-.70710678 .70710678 -0.70710678 -.70710678 100 100" "M 100 0 A 100 50 0 1 1 100 -1"
The batch and script files force all four ellipses to be generated in relative coordinates ("-r"), with no fill, stroke width of 2, and each one with a different color ("-p"). Notice that the initial ellipse path is always the same and given in absolute coordinates. The ellipse is centered at the origin with initial point at (100, 0), end point at (100, -1), with x axis radius of 100, y axis radius of 50, and a zero degrees angle:
"M 100 0 A 100 50 0 1 1 100 -1"
Notice that this path is not closed. This is done because ellipses and circles inside SVG paths cannot be closed, since the initial and end point must be different. One could have used the "z" command in the original ellipse to close it, but here the "-ez" command is used to close each ellipse. By removing this command the gap between the start point and the end point shows how angles grow in SVG. They do it in a clockwise manner because the y axis is inverted since it always points down. The produced paths give the following pattern:
This example allows to easily understand the use of matrices to produce rotations of 0°, 45°, 90° and 135°. The generated paths contain exactly these angles with start and end point rotated by these angles about the ellipses center point, the point (100, 100) as seen in the translation part of the matrices.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 204 204">
<path fill="none" stroke="red" stroke-width="2" d="M200,100a100,50 0 1 1 0,-1z"/>
<path fill="none" stroke="green" stroke-width="2" d="M170.711,170.711a100,50 45 1 1 0.707,-0.707z"/>
<path fill="none" stroke="blue" stroke-width="2" d="M100,200a100,50 90 1 1 1,0z"/>
<path fill="none" stroke="magenta" stroke-width="2" d="M29.289,170.711a100,50 135 1 1 0.707,0.707z"/>
</svg>
The second simple example generates 6 ellipses, each one rotated 30 degrees from the previous one. One uses ellipse6.bat or ellipse6.sh to generate the paths. Here the matrices produce rotations of 0°, 30°, 60°, 90°, 120°, and 150°, resulting in the following pattern:
The more complex example combine both simpler examples above together in a single svg file. One uses ellipse46.bat or ellipse46.sh to generate the paths. The result was shown above.