Programmable Scalable Vector Graphics -- drawings that draw themselves

image-format, programming-language, svg, vector-graphics
npm install @antfu/psvg@0.0.1


PSVG - Programmable SVG

Doc | Playground | Examples

PSVG is an extension of the SVG (Scalable Vector Graphics) format that introduces programming language features like functions, control flows, and variables -- Instead of writing a program that draws a picture, write a picture that draws itself!

PSVG is compliant with XML and HTML specs, so it can be easily embeded in a webpage or edited with an XML editor.

This repo contains a PSVG→SVG complier that transforms PSVG files to just regular SVG's. It can also automatically render all PSVG's on an HTML page when included as a <script>.

Note: Experimental and under development, currently the compiler is not very friendly and might misbehave at times; Contributions/Issues welcome.

For example, define a recursive function that draws the Sierpiński's triangle:

<psvg width="300" height="260">

  <def-sierptri x1="{WIDTH/2}" y1="0" x2="{WIDTH}" y2="{HEIGHT}" x3="0" y3="{HEIGHT}" d="7">
    <path d="M{x1} {y1} L{x2} {y2} L{x3} {y3} z"/>
    <if false="{d}">
    <sierptri x1="{x1}" y1="{y1}" x2="{(x1+x2)/2}" y2="{(y1+y2)/2}" x3="{(x3+x1)/2}" y3="{(y3+y1)/2}" d="{d-1}"/>
    <sierptri x1="{x2}" y1="{y2}" x2="{(x2+x3)/2}" y2="{(y2+y3)/2}" x3="{(x1+x2)/2}" y3="{(y1+y2)/2}" d="{d-1}"/>
    <sierptri x1="{x3}" y1="{y3}" x2="{(x3+x1)/2}" y2="{(y3+y1)/2}" x3="{(x2+x3)/2}" y3="{(y2+y3)/2}" d="{d-1}"/>

  <fill opacity="0.1"/>


Which looks like this (after running it through the PSVG to SVG complier):

Since PSVG is a superset of SVG, all the elements in SVG are also in PSVG, and all of them are programmable. For example, you can use a for loop to generate a bunch of gradients whose stops are determined by a function of the index.

<var n="12"/>

  <for i="0" true="{i<n}" step="1">
    <var t="{i/(n-1)}"/>
    <linearGradient id="grad{i}">
      <stop offset="0%"   stop-color="black"/>
      <stop offset="100%" stop-color="rgb(200,{FLOOR(LERP(0,255,t))},0)"/>

Which will generate gradients with ids grad0, grad1, grad2, ... To use, simply write:

<rect fill="url(#grad7)"/>

The above is a simplified excerpt from examples/pythagoras.psvg, which utilizes this "gradient of gradient" to colorize a tree:

To transform shapes in vanilla SVG, the "group" metaphor (<g transform="...">) is often used. In addition to groups, PSVG also introduces Processing/p5.js-like pushMatrix() popMatrix() metaphors. For example, from the same examples/pythagoras.psvg as above, the <push></push> tag combined with <translate/> <roatate/> are used to draw a fractal tree:

<def-pythtree w="" d="{depth}">
    <fill color="url(#grad{depth-d})"/>
    <path d="M0 {w/2} L{w/2} 0 L{w/2} {-w} L{-w/2} {-w} L{-w/2} 0 z"/>

  <if true="{d==0}">
    <translate x="{-w/4}" y="{-w-w/4}"/>
    <rotate deg="-45"/>
    <pythtree w="{w/SQRT(2)}" d="{d-1}"/>
    <translate x="{w/4}" y="{-w-w/4}"/>
    <rotate deg="45"/>
    <pythtree w="{w/SQRT(2)}" d="{d-1}"/>

You can have your own pick of degree or radians: <rotate deg="45"> or <rotate rad="{PI/4}"/> are the same. You can also use <scale x="2" y="2"/> to scale subsequent drawings.

Similarly, styling can also be written as commands to effect subsequent draw calls:

<stroke color="red" cap="round"/>
<fill color="green"/>

<path d="...">
<polyline points="...">

In addition to simple fractals shown above, PSVG is also capable of implementing complex algorithms, as it's a full programming language. For example, an implementation of Poisson disk sampling described in this paper, examples/poisson.psvg:

The PSVG to SVG Compiler

A baseline PSVG to SVG complier is included in this repo. It is a very "quick-and-dirty" implementation that eval()s transpiled JavaScript. So for now, don't compile files you don't trust!

As commandline tool

Grab dist/psvg.js and run

node psvg.js path/to/input.psvg > path/to/out.svg

For example, to compile the hilbert curve example in this repo:

node dist/psvg.js examples/hilbert.psvg > examples/hibert.svg

Alternatively you can use things like the shebang trick, pkg tool, bash alias etc. to make it appear as an executable, and:

psvg input.svg > output.svg

The original source code is all in one file, psvg.ts, so you can also use ts-node to run that directly instead (or recompile with tsc to make sure you get the absolutely newest version).

For the browser

<script src="psvg.js"></script>

By including the script, all the <psvg> elements on the webpage will be compiled to <svg> when the page loads. Again, don't include PSVG files that you don't trust.

As a library

In node:

const {compilePSVG} = require("./psvg");

Additionally, parsePSVG() transpilePSVG() and evalPSVG() which are individual steps of compilation are also exported.

In browser, compilePSVG and others are available globally.

Check out QUICKSTART.md for a quick introduction to the PSVG language.