Giter Site home page Giter Site logo

lingdong- / psvg Goto Github PK

View Code? Open in Web Editor NEW
295.0 6.0 15.0 768 KB

Programmable Scalable Vector Graphics -- drawings that draw themselves

Home Page: https://psvg.netlify.app/

TypeScript 69.59% JavaScript 30.41%
svg programming-language vector-graphics image-format

psvg's Introduction

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.

psvg's People

Contributors

0918nobita avatar antfu avatar lingdong- avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

psvg's Issues

Use acorn & astring in transpiler

The current transpiler manipulates JS source code in string form, which makes it quite difficult to understand the behavior of generated JS program and to make changes.
In order to simplify the logic of transpiler, I think we should manipulate ESTree-compliant objects and finally convert them to source code in string form.

As a library to generate JS source code from ESTree-compliant objects, I suggest astring package.
It works very fast, so there seems to be no need to worry about changes in performance.

Also, to parse the attribute values of PSVG elements to ESTree-compliant object, JavaScript parser such as acorn is needed.

TypeScript type definitions for ESTree-compliant objects is provided via @types/estree package.

Example of JS code generation:

import type {
  Program,
  ExpressionStatement,
  CallExpression,
  MemberExpression,
  Identifier,
  Literal,
} from 'estree';
import { generate } from 'astring';

const consoleIdent: Identifier = {
  type: 'Identifier',
  name: 'console',
};

const logIdent: Identifier = {
  type: 'Identifier',
  name: 'log',
};

const memberExpr: MemberExpression = {
  type: 'MemberExpression',
  object: consoleIdent,
  property: logIdent,
  computed: false,
  optional: false,
};

const strLit: Literal = {
  type: 'Literal',
  value: 'Hello, world!',
};

const callExpr: CallExpression = {
  type: 'CallExpression',
  callee: memberExpr,
  arguments: [strLit],
  optional: false,
};

const exprStmt: ExpressionStatement = {
  type: 'ExpressionStatement',
  expression: callExpr,
};

const program: Program = {
  type: 'Program',
  sourceType: 'module',
  body: [exprStmt],
};

console.log(generate(program)); // => console.log("Hello, world!");

A... Vite plugin!

I made a PSVG Vite plugin for myself to use PSVG on my projects.
Posting here in case someone would be interested in it: https://github.com/antfu/vite-plugin-psvg

As the official npm package is not released yet, I published a fork under @antfu/psvg with the exact same code as the current branch. Will move the dependency to the official one once it's ready. 😜 Updated

NPM Package

Hey!

I am so excited to see this project and would love to try it out to do some experiments. Publishing as an npm package would help people to start using it in their project. While we can also provide CLI interface and programmatic API at the same time. This also adds up the possibility to have ecosystem integrations like rollup-plugin-psvg or psvg-loader etc.

Since psvg on npm is already taken. I would suggest publishing under your username scope like @lingdong/psvg.

Expecting to see the usages like this:

npm i -g @lingdong/psvg

psvg input.svg > output.svg

or

npx @lingdong/psvg input.svg > output.svg

And programmatic API

npm i @lingdong/psvg
import { compilePSVG } from '@lingdong/psvg'

CDN

<script src='https://unpkg.com/@lingdong/psvg'></script>

What do you think? If you like this idea, I can take care of all the tooling and building processes and open a PR for it.

Cannot use VAR more than once on the same variable (and declaring empty array)

First let me say: great project! I was searching for a way to program macros in SVG editors for ever and PSVG solves this problem for me.

I have two things to point out: when I try to execute this example (found in your docs):

  <var data="M 0 0 L 10 10"/>
  <var data="CAT(data,'L',20,10)"/> <!-- adds a new point -->
  <path d="{data}">

Chrome (version 89.0.4389.90) raises an "Identifer data has already been declared" error.

I was able to circumvent the problem, but that end up being kinda ugly :) and is the source of the second point.

I declared the variable just once and then manipulate it using dummy attributes, using javascript inside them (what I believe can be against the philosophy of the language):

  <var incs="0 0 1 1 0 0" />
  <var ps="[ ]" />
  <g style="{ps.shift(),ps.shift()}"></g>
  <for i="0" true="{i<5}">
    <g style="{ps.push((px + w * incs[ i ]) * scale, (py + h * incs[ i + 1 ]) * scale )}"></g>
  </for>

My intention was to declare an empty array and the only way I could manage it was to declare ps="[ ]" (notice that this generates and array, with the 1st element == "[" and the second == "]" :) ) and then use "shift" twice in the next line.

So my second point is: Is there a way to declare empty arrays? I've tried ps="" and then use "push", ps="[]" (but that generates a string, I think), and

  <var ps="" /><g style="{ps=[]}"></g> 

and no one of them worked.

Also it appears to me (but I can be wrong, please point it out if it is the case) that the builtins CAT, NTH and etc cannot be used inside { } in PSVG, hence the use of "push".

Exclude dist from source control

It's not ideal to keep dist files into the source control as it creates too much verbose and maybe potentially misalignment with the source. But having dist files could benefit users to grab and run without run into the install and build process. Or maybe we can guide users to download from npm or CDN dist instead: https://unpkg.com/@lingdong/psvg

What do you think?

Implementing a Full Interpreter?

This issue is for discussing the proposal of implementing a full interpreter, to replace the current transpiler to JS. (see #18 )

Some things that I can think of:

Pros:

  • More control over everything, easier to add features (like @0918nobita said)
  • Thoroughly prevent executing arbitrary JavaScript code in XML attributes.
  • Potentially easier to catch user errors and produce friendly messages
  • Easier for third-party PSVG implementations that don't want to depend on JS to base their interpreters on our code instead of having to write their own from scratch.
  • In PSVG variables of the form "1 2 3 42 666 ..." has an Array/String "duality", and the current JS solution can be a bit slow in some situations. If we have our own interpreter, we might be able to optimize specifically for this language feature.
  • It's cooler. Transpiling to JS is not much of a challenge :)

Cons:

  • Speed. The JavaScript V8 JIT is like, crazy good. I think we're unlikely to beat it. (or there's still a chance? since PSVG is a much "smaller" language)
  • Potentially a lot more work and debugging. Instead of throwing all the heavy-lifting to JS, we need to take care of many things on our own.
  • This is not a big deal, but the the library size will sure become larger/heavyweight.
  • @LingDong- has never written an interpreter before. He'll be learning stuff as he goes. Or maybe that's a pros :)

Please feel free to add to this list!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.