Skip to content

LingDong-/psvg

Repository files navigation

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 stops 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 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}">
  <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.