npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

canvasium

v1.2.1

Published

Markup-based canvas rendering engine for Node.js

Readme


✨ What is Canvasium?

Canvasium turns HTML-like markup into Node.js canvas drawings. Write declarative markup; get a rendered PNG/stream with cards, text, images, and bars—all without hand-writing canvas code.

<body width="360" height="200" background="#0f172a" radius="16">
  <card x="16" y="16" width="328" height="168" color="#111827" radius="12">
    <image src="https://i.pravatar.cc/100?img=12" x="18" y="18" width="64" height="64" rounded="true" />
    <text x="96" y="26" font="20px 'Poppins', sans-serif" color="#e5e7eb">Alex Morgan</text>
    <text x="96" y="54" font="14px 'Poppins', sans-serif" color="#9ca3af">Product Designer</text>
    <bar x="18" y="104" width="292" height="12" value="62" max="100" color="#22c55e" background="#1f2937" outerRadius="6" innerRadius="6" />
    <text x="18" y="124" font="14px 'Poppins', sans-serif" color="#9ca3af">Portfolio completeness</text>
    <text x="275" y="124" font="14px 'Poppins', sans-serif" color="#e5e7eb">62%</text>
  </card>
</body>

📦 Installation

npm install canvasium

Requires Node.js 16+ and the canvas native dependency (installed automatically via npm).


🚀 Quick Start

import { render } from 'canvasium';
import fs from 'fs';

const markup = `
<body width="320" height="140" background="#0b1221" radius="12">
  <card x="12" y="12" width="296" height="116" color="#111827" radius="10">
    <text x="16" y="18" font="18px 'Poppins', sans-serif" color="#e5e7eb">Hello, Canvasium!</text>
    <text x="16" y="46" font="14px 'Poppins', sans-serif" color="#94a3b8">Render canvas from markup.</text>
  </card>
</body>
`;

const ctx = await render(markup);
ctx.canvas.createPNGStream().pipe(fs.createWriteStream('output.png'));

🧩 Components

  • <body>: required root element. Supports width, height, background, radius (or per-corner radius props).
  • <card>: rectangle with fill and optional border/radius. Supports positioning (x, y, alignment), width, height, color, borderWidth, borderColor, radius props (uniform radius or per-corner: radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft), and zIndex.
  • <text>: draws text. Props: x, y, font, color, opacity, shadowColor, shadowBlur, horizontalAlign, verticalAlign, maxWidth, zIndex.
  • <image>: draws an image (URL or local). Props: src, x, y, width, height, rounded (circle), per-corner radius (radius, radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft), alignment props, zIndex.
  • <bar>: progress bar. Props: x, y, width, height, value, max, color, background, outerRadius, innerRadius, per-corner radii (outerRadiusTopLeft, outerRadiusTopRight, outerRadiusBottomRight, outerRadiusBottomLeft, innerRadiusTopLeft, innerRadiusTopRight, innerRadiusBottomRight, innerRadiusBottomLeft), safeWidth, minDrawWidth, zIndex.

📐 Z-index & render order

  • Lower zIndex draws first; higher draws last (on top).
  • If zIndex is equal or absent, earlier markup wins and renders on top (top-to-bottom priority).

🎨 Advanced Examples

1) Profile hero with layered avatar and badge

<body width="420" height="220" background="#0b1221" radius="18">
  <card x="16" y="16" width="388" height="188" color="#111827" radius="14">
    <image src="https://i.pravatar.cc/120?img=47" x="22" y="24" width="88" height="88" rounded="true" zIndex="2" />
    <card x="16" y="20" width="356" height="100" color="#0f172a" radius="12" zIndex="0">
      <text x="110" y="14" font="20px 'Poppins', sans-serif" color="#e5e7eb">Alice Lee</text>
      <text x="110" y="42" font="14px 'Poppins', sans-serif" color="#9ca3af">Lead Frontend Engineer</text>
      <bar x="110" y="64" width="220" height="10" value="78" max="100" color="#22c55e" background="#1f2937" outerRadius="6" innerRadius="6" />
    </card>
    <card x="12" y="12" width="72" height="28" color="#22c55e" radius="8" zIndex="3">
      <text x="12" y="6" font="14px 'Poppins', sans-serif" color="#0b1221">Online</text>
    </card>
  </card>
</body>

Preview:

2) Metric tiles with alignment and safe-width bar

<body width="520" height="200" background="#0f172a" radius="16">
  <card x="16" y="16" width="240" height="168" color="#151e30ff" radius="12">
    <text x="16" y="16" font="16px 'Poppins', sans-serif" color="#cbd5e1">Downloads</text>
    <text x="16" y="44" font="28px 'Poppins', sans-serif" color="#e5e7eb">18,240</text>
    <bar x="16" y="88" width="208" height="12" value="84" max="100" color="#3b82f6" background="#1f2937" outerRadius="6" innerRadius="6" safeWidth="true" minDrawWidth="6" />
    <text x="16" y="108" font="13px 'Poppins', sans-serif" color="#94a3b8">Goal 22,000</text>
  </card>
  <card x="264" y="16" width="240" height="168" color="#151e30ff" radius="12">
    <image src="https://images.unsplash.com/photo-1502764613149-7f1d229e230f?w=400" width="240" height="120" radiusTopLeft="12" radiusTopRight="12" />
    <text x="16" y="136" font="16px 'Poppins', sans-serif" color="#e5e7eb">New collection</text>
  </card>
</body>

Preview:

3) Asymmetric corners on bars and cards

<body width="380" height="220" background="#0b1221" radiusTopLeft="16" radiusBottomRight="16">
  <card x="16" y="16" width="348" height="188" color="#111827" radiusTopLeft="14" radiusBottomRight="14">
    <image
      src="https://i.pravatar.cc/140?img=32"
      x="18"
      y="18"
      width="120"
      height="120"
      radiusTopLeft="18"
      radiusBottomRight="18"
    />
    <bar
      x="154"
      y="28"
      width="180"
      height="14"
      value="45"
      max="100"
      color="#22c55e"
      background="#1f2937"
      outerRadiusTopLeft="10"
      outerRadiusBottomRight="10"
      innerRadiusTopLeft="10"
      innerRadiusBottomRight="10"
    />
  </card>
</body>

Preview:


🌀 Dynamic Data

Canvasium supports {} interpolation:

<text x="20" y="20" color="#fff">Welcome, {username}!</text>
await render(markup, { username: 'Alex' });

🛠 API

  • render(markup: string, data?): parse and render markup. Returns a 2D context whose canvas can stream/write.
  • renderFile(path: string, data?): load .canvasium file and render.
  • parse(markup: string): returns the AST.
  • interpolateAST(node, data): perform variable replacement on a parsed AST.
  • Parser errors include line/column details for missing or mismatched tags.

📝 Changelog

1.2.1

  • Fixed self-closing parsing so siblings render correctly.
  • Ensured async child rendering inside <card> waits for images.
  • Added zIndex support to all elements; default tie-break favors earlier markup on top.
  • Adjusted default stacking to respect document order (top-to-bottom priority).
  • Added per-corner radius for bars (outer/inner) and documented corner-specific radius on body/cards/images.
  • Parser now reports detailed line/column info for missing or mismatched tags.

1.1.x

  • Initial stable renderer with <body>, <card>, <text>, <image>, <bar> and AST utilities.

📄 License

MIT