SSVG: Simplified Scalable Vector GraphicsClick on a title to open contents!
(Or: open allclose again)
The idea for SSVG came spontaneous and originated in the need for creating a large number of simple icons for an Android app. We found it inconvenient to do all the drawing by hand (and invarious sizes for different screen resolutions), especially graphics with simple, symmetric symbols that are best calculated by a program.
After having established a primitive Android version for SSVG support, we liked this idea so much that we started implementing an advanced SSVG graphic editor in Android and just recently we also began working on a web version for that editor.
Now we are giving SSVG to the public as we believe it may be useful for others as well!
Computer graphics formats come in two flavors: raster graphics (like PNG images) and vector graphics. The former essentially stores information about the color of each pixel, the latter consists of a sequence of drawing instructions.
An advantage of vector graphics is that they can be scaled without any loss of quality to any size, as they can simply be redrawn when we require another size. They can also be modified in a natural way by just modifying some of the drawing instructions.
The World Wide Web Consortium has adopted a standard for vector graphics called Scalable Vector Graphics (SVG), which is quite a fantastic thing. For example, all major web browsers now support inclusion of SVG graphics in web sites.
So why creating a new technology and not using SVG instead? Simplified SVG (SSVG) are what the name says: simplified. SVG as part of the XML family is rather verbose and has many very special features that are rarely used. In fact, writing an SVG file by hand is rarely practised as it can be a lot of work to do. It is more common to use editors that produce SVG files, like for example Inkscape. What we had in mind was a super simple mini language so one could write a line of code to produce something as simple as an icon with an arrow on it.
If you know SVG, you will feel that SSVG code looks a bit like the simple SVG path language, but with many more instructions to choose from.
SSVG has only the most essential features to produce simple graphics. It can be converted into SVG (we are working on such a converter right now), but in general not vice versa. The benefit is that one can quickly write up an SSVG on the fly.
Some of the feature we included in SSVG are:
- Reusability/modularity: an SSVG can be included in another one
- Loops for repeating sequences of drawing instructions
- Turtle-style instructions: having a current drawing position and direction allows for instructions like "draw a line forward"
- Polar coordinate support, allowing for convenient drawing of circular shapes, like stars for example
There are eight types of instructions: color definition, clipping, settings, transforms, path elements, movements, drawing and blocks. All instructions consist of either one or two characters. For example "+l" means "add a line", or "#n" means "define a new color".
The most essential instructions are path instructions. A path is a sequence of elements, like lines, curves, and others. Such a path can then be drawn by filling it with a color or by drawing its contour.
While composing a path, the current drawing position and direction changes. For example, the instruction "+l 100,50" adds a line from the current position to the position 100 units to the right and 50 down and then sets the new current position as the endpoint of that line. It also turns the current direction in the direction of the line drawing. If you continue with "+g 40", an additional line will be drawn 40 units futher in the same direction. Or you could first add "t 90" which turns 90 degrees to the right, then do "+g 40" and you will get a line segment with a right angle with respect to the first line. Building on this, the sequence "+l 100,50 t 90 +g40 +z f" will draw a closed right triangle. The "+z" means "close the path" and "f" means "fill the path with the current color". By the way, all the spaces are unnecessary, we could just write "+l100,50t90+g40+zf".
4. List of instructions
Note that instructions are case sensitive: for example the instruction #s is different from the instruction #S
All color instructions start with "#", followed by a letter. All red, green and blue values, as well as alpha values (for transparency) must be integers in the range between 0 and 255.
Whenever a new color or gradient is defined, it immediately becomes the current color (or gradient) for subsequent drawings. It can also be referenced in some instructions by its index (the n-th defined color/gradient has index n).
There is a fixed list of "global colors" that can be referenced by their number (see Appendix A).
Defining a gradient works as follows: first use #g to start the definition. Then add gradient stops to define colors along the gradient line/radius/angle. Gradient stops provide a percentage (where along the gradient it is attached; 0% ist the start, 100% the end, 50% the middle, etc.) and a color.
|#n||r,g,b||Define a new color by providing red, green and blue values|
|#n||a,r,g,b||Define a new color by providing alpha, red, green and blue values|
|#c||n||Define a new color by copying the alpha, red, green and blue values of the n-th global color|
|#g||x1,y1,x2,y2||Start the definition of a new linear gradient along the line given by (absolute) coordinates (x1,y1), (x2,y2)/td>|
|#g||x,y||Start the definition of a new linear gradient along the line from the current position to (x,y)|
|#g||r||Start the definition of a new radial gradient with center the current position and of radius r)|
|#g||<none>||Start the definition of a new sweep (angular) gradient with center the current position|
|#S||p,n||Add a gradient stop at percentage p using the n-th global color|
|#s||p,n||Add a gradient stop at percentage p using the n-th color defined so far|
|#s||p,r,g,b||Add a gradient stop at percentage p using the opaque color (r,g,b). (Note: the given color will not be another defined color that can be referenced later on!)|
|#s||p,a,r,g,b||Add a gradient stop at percentage p using the a-r-g-b color (a,r,g,b). (Note: the given color will not be another defined color that can be referenced later on!)|
Clipping instructions consist of a single letter. Defining a clipping overwrites a preceding clipping. It is valid until it is canceled or overwritten. All coordinates given in clipping instructions will be interpreted as absolute, regardless of the settings !x, !X, !p, !P.
|c||x1,y1,x2,y2||Clip with the rectangle defined by the (absolute) coordinates (x1,y1), (x2,y2)|
|c||<none>||Clip with the currently defined path|
|C||x1,y1,x2,y2||Clip with the area outside of the rectangle defined by the (absolute) coordinates (x1,y1), (x2,y2)|
|C||<none>||Clip with the area outside of the currently defined path|
|x||<none>||Cancel the currently defined clipping|
Settings instructions always start with "!", followed by a letter. If no color is specified before drawing takes place, the default color is black.
|!c||n||Use the n-th color (or gradient) defined so far, for subsequent drawing|
|!C||n||Use the n-th global color for subsequent drawing|
|!a||n||Set the transparency of the current color to n. n should vary between 0 (completely transparent) and 255 (completely opaque)|
|!w||n||Set the current line width to n. Default is 2% of the SSVG height.|
|!l||on,off||Set the line dash style. A line drawing will alternate between drawing 'on' many units and leaving out 'off' many units. Default is on=1, off=0|
|!L||n||Set the line end style. Values for n are: 0=straight, 1=round, 2=square. Default is n=1|
|!j||n||Set the line join style: how to draw the corner of two connected lines. Values for n are: 0=cut angle (bevel), 1=sharp angle (miter), 2=round. Default is n=1|
|!s||n||Set text size in coordinate units. Default is 10% of the SSVG height.|
|!t||f,a||Set text style. f is the font (0=default, 1=default bold, 2=monospace, 3=sans serif, 4=serif) and a is the attribute (0=normal, 1=bold, 2=italic, 3=bold italic). Default is f=0, a=0|
|!T||n||Text alignment: 0=left, 1=center, 2=right. Default is n=2|
|!b||n||Set blur effect (the larger the number, the more blurred the drawing)|
|!x||<none>||Use relative Cartesian coordinates: positions are of the form (horizontal,vertical), relative to the current position. This is the default setting|
|!X||<none>||Use absolute Cartesian coordinates: positions are of the form (horizontal,vertical), relative to the point (0,0) of the coordinate system.|
|!p||<none>||Use relative polar coordinates: positions are of the form (angle,distance), relative to the current position and relative to the current direction.|
|!P||<none>||Use absolute polar coordinates: positions are of the form (angle,distance), relative to the point (0,0) of the coordinate system and the zero angle (pointing horizontally to the right).|
Applying a transform changes the coordinate system. All subsequent coordinates will be interpreted with respect to the new coordinate system. Transform instructions start with "*", followed by a letter. All coordinates given in transform instructions will be interpreted as absolute, regardless of the settings !x, !X, !p, !P.
|*I||<none>||Reset all transforms. Establishes the original coordinate system|
|*T||x,y||Translation. Moves the coordinate system by x units horizontally and by y units vertically|
|*t||x,y||Relative translation (moving the coordinate system). x and y are interpreted as offsets to the current position. For example, "*t 0,0" moves the coordinate system so that the point (0,0) is now where the current position was.|
|*R||angle,x,y||Rotation about the point x,y|
|*r||angle||Rotation about the current position|
|*o||<none>||Orient the coordinate system so that the x-axis now points to where the current direction was pointing to|
|*S||sx,sy,x,y||Scale (stretch, squeeze) in x-direction by sx and in y-direction by sy, considering (x,y) as the center of the scaling|
|*s||sx,sy||Scale in x-direction by sx and in y-direction by sy, considering the current position as the center of the scaling|
|*X||sx,sy,x,y||Skew in x-direction by sx and in y-direction by sy, considering (x,y) as the center of the skewing|
|*x||sx,sy||Skew in x-direction by sx and in y-direction by sy, considering the current position as the center of the skewing|
|*c||a,b,c,d,e,f||Apply a custom transform by a matrix whose rows are (a b c), (d e f) and (0 0 1). For further details, see the SVG specification|
Path instructions consist of "+" followed by a letter. They add elements to what we call the "current path". Nothing is drawn until a "p" or "f" instruction follows. After drawing takes place, the "current path" is emptied as soon as the first new path instruction follows, so a new path can be constructed. The same happens when the "current path" is used as a clipping path.
The coordinates used in path instructions are interpreted according to the settings given by the instructions !x, !X, !p, !P.
Adding a path element moves the current position to the endpoint of that element and changes the current direction to that it is tangent to that element at the endpoint. This rule applies to all curves, lines, arcs. It does not apply to (rounded) rectangles nor to circles, ellipses, elliptic arcs where the current position is used as the center point.
|+l||x,y||Add a line from the current point to (x,y)|
|+h||x||Add a horizontal line. If absolute coordinates are active (!X or !P), x is interpreted as an absolute position, else relative to the current x-position|
|+v||y||Add a vertical line. If absolute coordinates are active (!X or !P), y is interpreted as an absolute position, else relative to the current y-position|
|+g||d||Go forward: add a line of length d from the current point in the current direction|
|+q||cx,cy,x,y||Add a quadratic curve. (cx,cy) is the control point, (x,y) the endpoint of the curve.|
|+Q||cx,cy,d||Go quadratic forward: add a quadratic curve in the current direction with distance d. The control coordinates (cx,cy) are interpreted relative to the line connecting the current position and the endpoint: cx is the distance from the current position along that line and cy the left-right offset from that line.|
|+t||x,y||Add a quadratic curve with endoint (x,y) continuing smoothly the path (may produce undefined results when used as the first path element)|
|+T||d||Like +t but using the current direction and d as distance|
|+c||cx1,cy1,cx2,cy2,x,y||Add a cubic curve. (cx1,cy1) and (cx2,cy2) are the two control points, (x,y) the endpoint of the curve.|
|+C||cx1,cy1,cx2,cy2,d||Like +c but using the current direction and d as distance. The two control points (cx1,cy1) and (cx2,cy2) are interpreted like in +Q|
|+s||cx,cy,x,y||Add a cubic curve with endoint (x,y) and second control point (cx,cy) continuing smoothly the path (may produce undefined results when used as the first path element)|
|+S||cx,cy,d||Like +s but using the current direction and d as distance. The control point (cx,cy) is interpreted like in +Q|
|+b||w,h||Add a rectangle at the current point with width and height given by w, h. The rectangle is drawn counter-clockwise.|
|+B||w,h||Like +b but drawn clockwise.|
|+r||w,h,r||Add a rounded rectangle at the current point with width and height given by w, h and corner radius given by r. The rectangle is drawn counter-clockwise.|
|+R||w,h,r||Like +r but drawn clockwise.|
|+o||r||Add a circle around the current point with radius given by r. The circle is drawn counter-clockwise.|
|+O||r||Like +o but drawn clockwise.|
|+a||rx,ry,x,y||Add a counter-clockwise small elliptic arc with radii rx and ry, starting at the current position and ending at (x,y)|
|+A||rx,ry,x,y||Like +a but drawn clockwise.|
|+u||rx,ry,x,y||U-turn: add a counter-clockwise large elliptic arc with radii rx and ry, starting at the current position and ending at (x,y)|
|+U||rx,ry,x,y||Like +u but drawn clockwise.|
|+a||ratio,x,y||Add an elliptic arc from the current position to (x,y) continuing smoothly the path (may produce undefined results when used as the first path element). ratio is the value of ry/rx where rx and ry are the two radii of the underlying ellipse|
|+a||ratio,d||Like +a ratio,x,y but using the current direction and d as the distance|
|+a||d||Like +a ratio,d with ratio set to 1 (circular arc)|
|+e||rx,ry,angle,sweep||Adds an elliptic arc with radii rx, ry using the current position as the center. The arc starts at angle and continues from there according to the given sweep angle|
|+e||rx,ry||Add an ellipse around the current point with radii rx, ry. The ellipse is drawn counter-clockwise.|
|+E||rx,ry||Like +e rx,ry but drawn clockwise.|
|+z||<none>||Close the current path|
|+Z||<none>||Close the current path smoothly|
Single letter instructions manipulating the current position and direction (angle). When we start executing an SSVG, the current position and direction wil be set to (0,0) and 0 respectively.
|M||x,y||Move to the absolute position (x,y). Coordinate modes given by the instructions !x, !p, !P are ignored|
|m||x,y||Move to (x,y) (respects the current coordinate mode set by !x, !X, !p, !P)|
|h||x||Horizontal move: like +h but only moving, without adding a line|
|v||y||Vertical move: like +v but only moving, without adding a line|
|g||d||Move forward: like +g but only moving, without adding a line|
|T||angle||Turn to the absolute position given by angle (in degrees). Angle zero points horizontally to the right. Angles are counted clockwise|
|t||angle||Turn by an angle: add angle to the current angle|
|S||<none>||Save the current position and angle (may be used several times in a row before restoring)|
|R||<none>||Restore saved position and angle|
|A||<none>||Restore only the angle from a saved position and angle|
|r||<none>||Restore only the position from a saved position and angle|
Single character instructions for drawing paths or text.
|b||<none>||Fill the background with the current color or gradient|
|"||text||Draw a text at the current position. This instruction starts with double quotes and draws the text given between double quotes. Double and single quotes that should be included in the text can be escaped as \" and \'. The backslash itself can be escaped as \\|
|p||<none>||Draw the contour of the current path with the current color or gradient and the current line styles|
|P||a,r,g,b||Like p but using the given a-r-g-b color. This color will not be considered as a color that can be referenced by subsequent instructions|
|f||<none>||Draw the current path by filling it with the current color or gradient|
|F||a,r,g,b||Like f but using the given a-r-g-b color. This color will not be considered as a color that can be referenced by subsequent instructions|
A block is a collection of instructions. There are two kinds: repeat blocks and closed blocks.
A repeat block starts with "[", directly followed by an integer: the number n of repetitions; it ends with "]". Everything inside the block is executed n times, in a loop. The effect is exactly the same a writing the content of the block n times in a row. Repeat blocks are really ony shortcuts. If the number n is not provided, it defaults to 0 and the block will not be executed (i.e. zero times).
Closed blocks start with "(" and end with ")". Their content is executed exactly once, but in a closed context. All paramaters that can be manipulated by settings as well as current position and direction inside the block are completely independent from those valid outside of the block. The block starts executing with settings like a brand new SSVG and after its execution, the parent continues executing from where it left off. There is one exception: without setting a color for drawing, the default color in the block will not be black but the last color used outside the block.
Think of closed blocks as drawing a complex shape at the current position. The drawing will be rotated according to the current direction as well. And we can even define the size of that drawing: precede the block by <w,h> where w and h are numbers. Then the block will be drawn with a width of w and a height of h. If not provided, the width and height will default to the width and height of the SSVG containing the block, the parent.SSVG has the notion of a collection. A collection is a set of SSVGs with names attached to them. When we are working with a collection, we can include a graphic as a closed block in another one using its name. As an example, we would write <50,80>'mygraphic' to draw an SSVG named "mygraphic" with width 50 and height 80 (note no parentheses may be used with this notation).
We also have a dedicated instruction to execute a block (repeat or closed block) given in the current SSVG: the letter "e" followed by an integer n. It will execute (exactly once, and without a closed context; even if the block is a closed block!) the n-th block defined in the current SSVG. You can think of this as "calling a subroutine". If no block corresponds to n, the instruction does nothing. If n is negative and the instruction is inside a block, we search for a corresponding block backwards through the parent structure. For example, if n=-2, we execute the second-last block defined in the current execution, before we started executing the block containing the "e" instruction.
As a final remark, it is possible to apply modifications ("modding") to a block before executing it. See Appendix B for details.
Appendix A. Global colors
There exists exactly 741 global colors that can be referenced by their number. All of those except the first one are completely opaque.
The first 12 colors are: completely transparent, black, white, light gray, medium gray, dark gray, red, green, blue, yellow, cyan, magenta. The precise a,r,g,b values are: (0,0,0,0), (255,0,0,0), (255,255,255,255), (255,191,191,191), (255,127,127,127), (255,63,63,63), (255,255,0,0), (255,0,255,0), (255,0,0,255), (255,255,255,0), (255,0,255,255), (255,255,0,255).
The colors number 12 to 741 are defined such that alpha is 255 (completely opaque) and r, g, b range from 0 to 255 in steps of 32 (so we have 9 to the power of 3 equals 729 combinations; note that colors 1 to 11 will be repeated in that list).
The order of colors 12 to 741 is such that dark colors come first and light colors last. Here is the precise algorithm for generating the colors in order:
- Let n increase starting from 0 up to 24
- For any given n, let m increase starting from max(0,n-8) up to min(16,n)
- For any given m and n, let k increase starting from max(0,m-8) up to min(8,m)
- Now given m, n and k, define an opaque color (r,g,b) by: r=max(0,k*32-1), g=max(0,(m-k)*32-1) and b=max(0,(n-m)*32-1)
Appendix B. Modding
What if we want to reuse an existing SSVG by drawing it as a closed block, but some detail like a color should be different in that block?
It is possible to precede a (repeat- or closed-) block by a sequence of modding instructions that change parameters of instructions in the block.
A simple example of a modding instruction is of the form n:m. It means: take the n-th instruction if the block and change its first parameter to be m.
If the n-th instruction has more than one parameter, we can write n:m1,m2,... to change them to m1, m2, etc. If one parameter should not be changed, we can use the wildcard character "*". For example: 5:*,3,*,10 changes the fifth instruction so that its second parameter equals 3 and its fourth parameter equals 10.
Should the n-th instruction itself be a block instruction, we can tap into that block as follows: n:k:m1,m2,.... This will change the k-th instruction of the block at the n-th position. This can be done in any depth by using as many "n:" as necessary.
Modding instructions are to be given in the "block header" which precedes the block, starts with "<" and ends with ">". Inbetween, we may have as many modding instructions as we want, separated by semicolons (but no semicolon after the last one).
In the case of a closed block, we may also have external (i.e. drawing-) dimensions in the header. They have to be at the very beginning of the header. Only after them follow possible modding instructions. Therefore, in the case of a closed block, the first modding instruction has to be preceded by a semicolon as well, to separate it from the dimensions, even if no dimensions are given (in case we want the default dimensions). In the case of repeat blocks, no dimensions may be given in the header, on no semicolon may precede the first modding instruction.
Here are a few examples:
Appendix C. Dimension
By default, the area of an SSVG is 720 units wide and 720 units high. The point (0,0) is located in the upper left corner and is the default current location.
We call this the "internal dimensions". It is possible to change those by starting the SSVG code by two numbers, separated by a comma. The first one will be the internal width, the second the internal height (replacing the value 720 each).
This only decides how coordinates are interpreted: for instance, if our SSVG starts with "24,36 ...", the lower right corner point will have coordinates (24,36), the center point will be (12,18), etc. It has no effect on the size of the drawing on the screen, which is set externally to the SSVG (for example by passing parameters to a drawing routine of some SSVG implementation).
The same rule applies to closed blocks: they have internal dimensions 720, 720 by default, but their code may start with a couple of numbers indicating custom internal dimensions as well. In the case of a closed block, external (drawing-) dimensions can be set by preceding the block by <w,h>, where w indicates the drawing width and h the drawing height.
if at the begin of the SSVG or of a closed block, only one number is given, it will be interpreted as the internal height and the internal width will still default to 720.