PSVG - Programmable SVG
Doc | Playground | Examples | NPM
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 embedded 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}">
<return/>
</if>
<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}"/>
</def-sierptri>
<fill opacity="0.1"/>
<sierptri/>
</psvg>
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 stop
s are determined by a function of the index.
<var n="12"/>
<defs>
<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)"/>
</linearGradient>
</for>
</defs>
Which will generate gradients with id
s 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}">
<push>
<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"/>
</push>
<if true="{d==0}">
<return/>
</if>
<push>
<translate x="{-w/4}" y="{-w-w/4}"/>
<rotate deg="-45"/>
<pythtree w="{w/SQRT(2)}" d="{d-1}"/>
</push>
<push>
<translate x="{w/4}" y="{-w-w/4}"/>
<rotate deg="45"/>
<pythtree w="{w/SQRT(2)}" d="{d-1}"/>
</push>
</def-pythtree>
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 command-line tool
Install it globally via npm
npm i -g @lingdong/psvg
and use it with:
psvg input.svg > output.svg
For example, to compile the hilbert curve example in this repo:
psvg examples/hilbert.psvg > examples/hibert.svg
or try it without installing via npx
(comes together with npm)
npx -s @lingdong/psvg input.svg > output.svg
For the browser
PSVG is also available for browser via CDN, or directly download
<script src="http://unpkg.com/@lingdong/psvg"></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
Install locally in your project via npm
npm i @lingdong/psvg
import { compilePSVG } from "@lingdong/psvg"
console.log(compilePSVG("<psvg>...</psvg>"))
or
const { compilePSVG } = require("@lingdong/psvg")
console.log(compilePSVG("<psvg>...</psvg>"))
Additionally, parsePSVG()
transpilePSVG()
and evalPSVG()
which are individual steps of compilation are also exported.
In browsers, functions are exported under the global variable PSVG
.
Check out QUICKSTART.md for a quick introduction to the PSVG language.
Editor Support
Syntax highlighting and auto-completion can be configured for editors by:
VS Code
Add the following lines to your settting.json
. details
"files.associations": {
"*.psvg": "xml"
}
GitHub
To get highlighting for PSVG files in your repositories on GitHub, create .gitattributes
file at the root of your repo with the following content. details
*.psvg linguist-language=SVG
Other editors
Since PSVG is compliant with XML and HTML specs, you can always alias your language id to XML or SVG via the corresponding config on your editor.