qjs-nanovg
QuickJS · OpenGL · NanoVG

qjs-nanovg: draw antialiased vector graphics from a script

NanoVG's Canvas-style API (paths, gradients, text, images) as a loadable QuickJS module. Point it at a GLFW window and draw.

import * as glfw from 'glfw';
import { CreateGL3, ANTIALIAS, RGBA } from 'nanovg';

const win = new glfw.Window(800, 600, 'NanoVG');
glfw.context.current = win;
const nvg = CreateGL3(ANTIALIAS);

while(!win.shouldClose) {
  nvg.BeginFrame(800, 600, 1);
  nvg.BeginPath();
  nvg.RoundedRect(100, 100, 200, 150, 12);
  nvg.FillColor(RGBA(255, 128, 0, 255));
  nvg.Fill();
  nvg.EndFrame();
  win.swapBuffers();
  glfw.poll();
}

Why qjs-nanovg

Canvas-like drawing without a browser: the whole binding is one C file on top of NanoVG.

01

Canvas-style API

Paths, bezier and arc segments, fill and stroke, linear, box and radial gradients, scissor clipping and transform stacks, all as methods on one context.

02

Text, images, framebuffers

Load fonts and images by id, lay out and align text, render into offscreen framebuffers and read pixels back with ReadPixels.

03

Small and loadable

A single nvgjs-module.c (about 2,300 lines) plus NanoVG itself, built to nanovg.so. It needs QuickJS, GLEW and OpenGL; the window comes from the glfw module.

Or write it as browser Canvas

qjsm has no DOM, but canvas2d.js gives you a canvas and a requestAnimationFrame that draw through NanoVG in a GLFW window, so Canvas 2D code runs under qjsm.

import { canvas, requestAnimationFrame } from 'canvas2d.js';

const ctx = canvas.getContext('2d');

requestAnimationFrame(function loop() {
  ctx.fillRect(10, 10, 100, 100);
  requestAnimationFrame(loop);
});

Canvas2D usage and known gaps →

And in the browser

The same API runs on a web page: NanoVG compiled to WebAssembly with Emscripten, drawing on WebGL2. web/nanovg.js mirrors the native module name for name, so the demo scripts run there unchanged.

01

Same scripts

The planets, curve editor and polyline editor examples load as they are. A small loader makes their blocking glfw.poll() loops yield to the browser.

02

Checked against native

A compatibility script diffs every export, function length, property flag and error message of the browser module against nanovg.so; the pure-math results agree.

03

Built with the module

A normal CMake build also produces the wasm when emcc is found (BUILD_WEB).

Live demo →   Browser version documentation →

Install

git clone --recursive https://github.com/rsenn/qjs-nanovg.git
cd qjs-nanovg
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
sudo cmake --install build      # installs nanovg.so for qjsm
qjsm examples/planets.js