No description
  • TypeScript 93.2%
  • HTML 5.5%
  • JavaScript 1.3%
Find a file
2026-08-25 12:37:36 +02:00
example lets go! 2026-08-25 12:37:36 +02:00
src lets go! 2026-08-25 12:37:36 +02:00
test Initial commit: canvark markdown-to-canvas parser 2026-08-20 21:04:27 +02:00
.gitignore lets go! 2026-08-25 12:37:36 +02:00
package-lock.json lets go! 2026-08-25 12:37:36 +02:00
package.json lets go! 2026-08-25 12:37:36 +02:00
README.md Initial commit: canvark markdown-to-canvas parser 2026-08-20 21:04:27 +02:00
tsconfig.json Initial commit: canvark markdown-to-canvas parser 2026-08-20 21:04:27 +02:00
vite.config.js lets go! 2026-08-25 12:37:36 +02:00

canvark

Parse markdown text into a flat list of canvas-drawable primitives.

parseMarkdown(md) → Drawable { parts, width, height }, then draw the parts in order with draw(ctx, drawable). No DOM required to parse (text measurement falls back to an estimate in Node); images are drawn only if you supply preloaded elements.

Inspired by the architecture of canvas-markdown (lexer → layout parts → render), rewritten as a clean, dependency-light TypeScript library.

Install / build

npm install
npm run build   # emits dist/ (ESM + .d.ts)
npm test        # node --test suite (builds first)

Usage

import { parseMarkdown, draw, renderToCanvas } from 'canvark';

const md = `# Hello
Some **bold** text with a \`code span\` and a [link](https://example.com).

\`\`\`js
console.log("hi");
\`\`\`
`;

const drawable = parseMarkdown(md, { width: 600 });
// drawable = { parts: DrawPart[], width: 600, height: 172 }

// Manual draw:
const ctx = canvas.getContext('2d')!;
draw(ctx, drawable);

// Or one-shot (sizes the canvas for you, handles devicePixelRatio):
renderToCanvas(canvas, md, { width: 600, padding: 16 });

Drawable parts

Each part is plain data — you can inspect, transform (offset/scale), animate, or draw them yourself:

type fields
text x, y, text, font, fillStyle, textBaseline, width, height
rect x, y, width, height, radius, fillStyle, strokeStyle, lineWidth
line x, y, x2, y2, strokeStyle, lineWidth
ellipse x, y, radius, fillStyle
image x, y, src, width, height (drawn only if draw gets it preloaded)

Options

parseMarkdown(md, {
  width: 800,                 // content width, text wraps beyond it
  theme: {                    // any partial theme override
    fontSize: 18,
    fontFamily: 'Inter, sans-serif',
    colors: { link: '#0969da' },
  },
  measureText: (text, font) => number, // custom measurement
});

draw(ctx, drawable, {
  images: new Map([['https://…/i.png', htmlImageElement]]),
});

Supported markdown

  • Headings (#######)
  • Paragraphs with word wrap
  • Inline: bold, italic, strikethrough, code span, [links], images, <br>
  • Fenced code blocks (background + border, monospace)
  • Blockquotes (left bar, muted text, nested blocks)
  • Ordered & unordered lists (nested)
  • Horizontal rules
  • Tables (simple, monospace, bordered)

How it works

  1. marked.lexer(md) tokenizes the markdown.
  2. A recursive layout pass walks block → inline tokens, maintaining a text cursor and emitting positioned parts (widths come from measureText).
  3. draw() replays the parts onto a 2D context.
src/
  index.ts     public API + renderToCanvas
  parse.ts     lexer → Drawable (the layout engine)
  draw.ts      Drawable → canvas ctx
  measure.ts   text measurement (canvas or headless estimate)
  theme.ts     default visual theme
  types.ts     DrawPart / Drawable / Theme types