Skip to content

Commit 87f4e01

Browse files
committed
feat(qr-generator): add PNG output, Bun adapter, and file/stream docs 🌸
- Add tests for PNG.createDataURL - Add Bun file storage adapter and use it first in getDefaultStorage - Add examples png.ts, to-file.ts, to-file-stream.ts and list in examples README - Add PNGOptions and PNG encoder (grayscale/RGB) with toPNG API - Add runtime checks and clear errors in Deno and Node adapters - Document toPNG, methods overview, and "Pick a Method" table in USAGE - Reorder types (FinderOptions, QRErrorLevel) and add core PNG types - Rename Gif helper to GIF and use Stream namespace in GIF encoder - Update README badges (Node/Deno/Bun/Browser) and feature list for PNG
1 parent 2e35a9e commit 87f4e01

19 files changed

Lines changed: 732 additions & 95 deletions

File tree

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,35 @@
1+
<div align="center">
2+
13
# QR Generator
24

3-
[![Module type: Deno/ESM](https://img.shields.io/badge/module%20type-deno%2Fesm-brightgreen)](https://github.com/NeaByteLab/QR-Generator) [![npm version](https://img.shields.io/npm/v/@neabyte/qr-generator.svg)](https://www.npmjs.org/package/@neabyte/qr-generator) [![JSR](https://jsr.io/badges/@neabyte/qr-generator)](https://jsr.io/@neabyte/qr-generator) [![CI](https://github.com/NeaByteLab/QR-Generator/actions/workflows/ci.yaml/badge.svg)](https://github.com/NeaByteLab/QR-Generator/actions/workflows/ci.yaml) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5+
Generate QR codes as SVG, path, GIF, PNG, ASCII, or canvas with custom shapes, gradients, and logo.
46

5-
Generate QR as SVG, path, GIF, ASCII, or canvas. Seven shapes (rounded, circle, diamond, square, shuriken, star, triangle), finder styling, solid or linear/radial gradients, logo (text/image). Deno (JSR) and npm.
7+
[![Node](https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white)](https://nodejs.org) [![Deno](https://img.shields.io/badge/deno-compatible-ffcb00?logo=deno&logoColor=000000)](https://deno.com) [![Bun](https://img.shields.io/badge/bun-compatible-f9f1e1?logo=bun&logoColor=000000)](https://bun.sh) [![Browser](https://img.shields.io/badge/browser-compatible-4285F4?logo=googlechrome&logoColor=white)](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
68

7-
**Prerequisites:** For **Deno** use [Deno](https://deno.com/) (install from [deno.com](https://deno.com/)). For **npm** use Node.js (e.g. [nodejs.org](https://nodejs.org/)).
9+
[![Module type: Deno/ESM](https://img.shields.io/badge/module%20type-deno%2Fesm-brightgreen)](https://github.com/NeaByteLab/QR-Generator) [![npm version](https://img.shields.io/npm/v/@neabyte/qr-generator.svg)](https://www.npmjs.org/package/@neabyte/qr-generator) [![JSR](https://jsr.io/badges/@neabyte/qr-generator)](https://jsr.io/@neabyte/qr-generator) [![CI](https://github.com/NeaByteLab/QR-Generator/actions/workflows/ci.yaml/badge.svg)](https://github.com/NeaByteLab/QR-Generator/actions/workflows/ci.yaml) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
810

9-
| No logo | Text logo | Variant |
11+
| No Logo | Text Logo | Variant |
1012
| :--------------------------------------: | :------------------------------------------: | :--------------------------------------: |
11-
| ![no logo](./preview/qrcode-no-logo.gif) | ![text logo](./preview/qrcode-text-logo.gif) | ![variant](./preview/qrcode-variant.gif) |
13+
| ![No Logo](./preview/qrcode-no-logo.gif) | ![Text Logo](./preview/qrcode-text-logo.gif) | ![Variant](./preview/qrcode-variant.gif) |
14+
15+
</div>
1216

1317
> [!IMPORTANT]
1418
> Generate preview assets (SVGs; GIFs if `rsvg-convert` is installed): `deno run -A preview/generator.ts`
1519
1620
## Features
1721

18-
- **Multiple output formats** — SVG string (`toSVG`), raw path data (`toPath`), GIF data URL (`toDataURL`), ASCII art (`toASCII`), table/img HTML (`toTableTag`, `toImgTag`), and canvas rendering (`toCanvas`).
22+
- **Multiple output formats** — SVG string (`toSVG`), raw path data (`toPath`), GIF data URL (`toDataURL`), PNG data URL (`toPNG`), ASCII art (`toASCII`), table/img HTML (`toTableTag`, `toImgTag`), and canvas rendering (`toCanvas`).
1923
- **Finder pattern styling** — Separate shape and gap for the three corner finder patterns.
2024
- **Custom module shapes** — Rounded, circle, diamond, square, shuriken, star, triangle; configurable gap.
2125
- **Color options** — Solid color or linear/radial gradients with full control over stops and geometry.
2226
- **Center logo** — Text or image overlay with size and corner radius; SVG output escapes attributes.
2327

2428
## Installation
2529

30+
> [!NOTE]
31+
> **Prerequisites:** For **Deno** (install from [deno.com](https://deno.com/)). For **npm** use Node.js (e.g. [nodejs.org](https://nodejs.org/)).
32+
2633
**Deno (JSR):**
2734

2835
```bash

USAGE.md

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,37 @@ const svg = QRCode.toSVG({
2222
})
2323
```
2424

25+
## Methods Overview
26+
27+
| Method | Options | Returns | Description |
28+
| :-------------------- | :------------------- | :---------------- | :------------------------------------------------------ |
29+
| `QRCode.toASCII` | FormatOptions | string | Terminal-style block or half-block art. |
30+
| `QRCode.toCanvas` | (ctx, FormatOptions) | void | Draws QR on given 2D context. |
31+
| `QRCode.toDataURL` | FormatOptions | string | `data:image/gif;base64,...`. |
32+
| `QRCode.toFile` | (path, SVGOptions) | Promise\<void\> | Writes SVG to file (or triggers download in browser). |
33+
| `QRCode.toFileStream` | (stream, SVGOptions) | Promise\<void\> | Writes SVG to writable stream. |
34+
| `QRCode.toPath` | QRCodeOptions | PathResult | `{ cellSize, path }` for custom rendering. |
35+
| `QRCode.toPNG` | PNGOptions | Promise\<string\> | `data:image/png;base64,...`; optional color/background. |
36+
| `QRCode.toSVG` | SVGOptions | string | Full SVG with path, fill, defs, optional logo. |
37+
| `QRCode.toTableTag` | FormatOptions | string | HTML `<table>...</table>`. |
38+
39+
### Pick a Method by What You Need
40+
41+
| I want to… | Use this | Minimal call |
42+
| :------------------------------ | :-------------------- | :---------------------------------------------------------------------- |
43+
| Draw on my own canvas | `QRCode.toCanvas` | `toCanvas(ctx, { value: '…' })` |
44+
| Get an SVG to show or save | `QRCode.toSVG` | `toSVG({ value: '…', size: 400 })` |
45+
| Get GIF for `<img>` or download | `QRCode.toDataURL` | `toDataURL({ value: '…' })` |
46+
| Get HTML table | `QRCode.toTableTag` | `toTableTag({ value: '…' })` |
47+
| Get path string for custom draw | `QRCode.toPath` | `toPath({ value: '…', size: 400 })` |
48+
| Get PNG for `<img>` or download | `QRCode.toPNG` | `await QRCode.toPNG({ value: '…', color: '#000', background: '#fff' })` |
49+
| Print QR in terminal | `QRCode.toASCII` | `toASCII({ value: '…' })` |
50+
| Write SVG to file or download | `QRCode.toFile` | `toFile('out.svg', { value: '…', size: 400 })` |
51+
| Write SVG to a stream | `QRCode.toFileStream` | `toFileStream(stream, { value: '…', size: 400 })` |
52+
53+
> [!NOTE]
54+
> Every method needs at least `value` (the text or URL to encode). `toSVG`, `toPath`, `toFile`, and `toFileStream` also need `size` (width/height in pixels). `toASCII`, `toDataURL`, `toPNG`, and `toTableTag` use optional `cellSize` and `margin` for dimensions.
55+
2556
## Options
2657

2758
### toSVG (Full Options)
@@ -168,6 +199,21 @@ const dataUrl = QRCode.toDataURL({ value: 'hello-world', cellSize: 2, margin: 8
168199
const tableHtml = QRCode.toTableTag({ value: 'hello-world', cellSize: 2, margin: 8 })
169200
```
170201

202+
### toPNG
203+
204+
Returns a PNG data URL with optional hex foreground and background. Uses **PNGOptions**: required `value`; optional `error`, `cellSize`, `margin`, `color`, `background`. When both `color` and `background` are set the output is RGB; otherwise grayscale. Defaults: cellSize 2, margin cellSize×4.
205+
206+
```typescript
207+
const pngUrl = await QRCode.toPNG({
208+
value: 'hello-world',
209+
cellSize: 10,
210+
margin: 25,
211+
color: '#0f172a',
212+
background: '#f1f5f9'
213+
})
214+
// pngUrl: data:image/png;base64,...
215+
```
216+
171217
### toCanvas
172218

173219
Draws the QR on a 2D canvas context. Uses **FormatOptions** (`value`, optional `error`, `cellSize`); `margin` is not used. Default cellSize is 2.
@@ -180,35 +226,6 @@ if (ctx) {
180226
}
181227
```
182228

183-
## Methods Overview
184-
185-
| Method | Options | Returns | Description |
186-
| :-------------------- | :------------------- | :-------------- | :---------------------------------------------------- |
187-
| `QRCode.toASCII` | FormatOptions | string | Terminal-style block or half-block art. |
188-
| `QRCode.toCanvas` | (ctx, FormatOptions) | void | Draws QR on given 2D context. |
189-
| `QRCode.toDataURL` | FormatOptions | string | `data:image/gif;base64,...`. |
190-
| `QRCode.toFile` | (path, SVGOptions) | Promise\<void\> | Writes SVG to file (or triggers download in browser). |
191-
| `QRCode.toFileStream` | (stream, SVGOptions) | Promise\<void\> | Writes SVG to writable stream. |
192-
| `QRCode.toPath` | QRCodeOptions | PathResult | `{ cellSize, path }` for custom rendering. |
193-
| `QRCode.toSVG` | SVGOptions | string | Full SVG with path, fill, defs, optional logo. |
194-
| `QRCode.toTableTag` | FormatOptions | string | HTML `<table>...</table>`. |
195-
196-
### Pick a Method by What You Need
197-
198-
| I want to… | Use this | Minimal call |
199-
| :-------------------------------- | :-------------------- | :------------------------------------------------ |
200-
| Print QR in terminal | `QRCode.toASCII` | `toASCII({ value: '…' })` |
201-
| Draw on my own canvas | `QRCode.toCanvas` | `toCanvas(ctx, { value: '…' })` |
202-
| Get image for `<img>` or download | `QRCode.toDataURL` | `toDataURL({ value: '…' })` |
203-
| Write SVG to file or download | `QRCode.toFile` | `toFile('out.svg', { value: '…', size: 400 })` |
204-
| Write SVG to a stream | `QRCode.toFileStream` | `toFileStream(stream, { value: '…', size: 400 })` |
205-
| Get path string for custom draw | `QRCode.toPath` | `toPath({ value: '…', size: 400 })` |
206-
| Get an SVG to show or save | `QRCode.toSVG` | `toSVG({ value: '…', size: 400 })` |
207-
| Get HTML table | `QRCode.toTableTag` | `toTableTag({ value: '…' })` |
208-
209-
> [!NOTE]
210-
> Every method needs at least `value` (the text or URL to encode). `toSVG`, `toPath`, `toFile`, and `toFileStream` also need `size` (width/height in pixels).
211-
212229
## API Reference
213230

214231
### QRCode.toASCII(options)
@@ -230,7 +247,7 @@ Use when you already have a `<canvas>` and its 2D context and want to draw the Q
230247

231248
### QRCode.toDataURL(options)
232249

233-
Use when you need a data URL for `<img src="…">` or for download (e.g. "Save as image").
250+
Use when you need a data URL for `<img src="…">` or for download (e.g. "Save as image"). Output is GIF.
234251

235252
- `options` `<FormatOptions>`: `value` (required), `error?`, `cellSize?`, `margin?`.
236253
- Returns: `<string>` `data:image/gif;base64,...`.
@@ -261,6 +278,14 @@ Use when you need the raw path and cell size (e.g. custom SVG, canvas, or Skia).
261278
- Returns: `<PathResult>` `{ cellSize: number, path: string }`.
262279
- Encodes value, builds matrix, applies shape options and logo cutout, returns path `d` and cell size.
263280

281+
### QRCode.toPNG(options)
282+
283+
Use when you need a PNG data URL for `<img src="…">` or for download, with optional foreground/background hex colors.
284+
285+
- `options` `<PNGOptions>`: `value` (required), `error?`, `cellSize?`, `margin?`, `color?`, `background?`. Same as FormatOptions plus optional hex `color` and `background`; when both are set the PNG is RGB, otherwise grayscale.
286+
- Returns: `<Promise<string>>` `data:image/png;base64,...`.
287+
- Defaults: cellSize 2, margin cellSize×4.
288+
264289
### QRCode.toSVG(options)
265290

266291
Use when you need a full SVG string (e.g. inject in HTML, save as `.svg`, or send from API).
@@ -278,17 +303,18 @@ Use when you need an HTML `<table>` snippet (e.g. email or legacy layout).
278303

279304
## Option Types
280305

281-
Types are exported for TypeScript: `import type { SVGOptions, FormatOptions, PathResult } from '@neabyte/qr-generator'`.
306+
Types are exported for TypeScript: `import type { ColorOption, FormatOptions, PathResult, PNGOptions, QRCodeOptions, SVGOptions, WritableStreamLike } from '@neabyte/qr-generator'`.
282307

283-
- **FormatOptions:** `value` `<string>`, `error?` `<ErrorOptions>`, `cellSize?` `<number>`, `margin?` `<number>`.
284-
- **ErrorOptions:** `level?` `<ErrorLevel>`. **ErrorLevel:** `'L' | 'M' | 'Q' | 'H'`.
285-
- **QRCodeOptions:** `value` `<string>`, `size` `<number>`, `error?`, `finder?` `<FinderOptions>`, `module?` `<ModuleOptions>`, `logo?` `<LogoOptions>`.
286-
- **SVGOptions:** QRCodeOptions & `color?` `<ColorOption>`, `background?` `<string>`.
287308
- **ColorOption:** `<string>` | **LinearGradient** | **RadialGradient**. **GradientStop:** `offset` (0–1), `color` `<string>`.
309+
- **ErrorOptions:** `level?` `<ErrorLevel>`. **ErrorLevel:** `'L' | 'M' | 'Q' | 'H'`.
288310
- **FinderOptions / ModuleOptions:** `shape?` `<ModuleShape>`, `gap?` `<number>`.
289-
- **ModuleShape:** `'circle' | 'diamond' | 'rounded' | 'square' | 'shuriken' | 'star' | 'triangle'`.
311+
- **FormatOptions:** `value` `<string>`, `error?` `<ErrorOptions>`, `cellSize?` `<number>`, `margin?` `<number>`.
290312
- **LogoOptions:** `size?` `<number>`, `radius?` `<number>`, `text?` `<string>`, `image?` `<string>`.
313+
- **ModuleShape:** `'circle' | 'diamond' | 'rounded' | 'square' | 'shuriken' | 'star' | 'triangle'`.
291314
- **PathResult:** `cellSize` `<number>`, `path` `<string>`.
315+
- **PNGOptions:** FormatOptions & `color?` `<string>`, `background?` `<string>` (hex e.g. `#000`, `#fff`). Used by `toPNG`.
316+
- **QRCodeOptions:** `value` `<string>`, `size` `<number>`, `error?`, `finder?` `<FinderOptions>`, `module?` `<ModuleOptions>`, `logo?` `<LogoOptions>`.
317+
- **SVGOptions:** QRCodeOptions & `color?` `<ColorOption>`, `background?` `<string>`.
292318
- **WritableStreamLike:** `write(data: string | Uint8Array): void | Promise<void>`, `end?(): void | Promise<void>` (for `toFileStream`).
293319

294320
## Reference

examples/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ Replace `<name>` with any script below (e.g. `basic-svg`, `formats`).
1919
| [formats.ts](formats.ts) | `toDataURL`, `toASCII`, `toTableTag` → log + `qr-table.html` |
2020
| [logo-full.ts](logo-full.ts) | Logo text+size+radius, logo image (data URI), radius cutout → 3 SVGs |
2121
| [path-and-canvas.ts](path-and-canvas.ts) | `toPath``path-only.html`; `toCanvas``canvas-qr.html` (open in browser) |
22+
| [png.ts](png.ts) | `toPNG()` data URL → decode and write `qr-default.png`, `qr-colored.png` |
2223
| [shapes-all.ts](shapes-all.ts) | All 7 module shapes → `shapes-rounded.svg``shapes-triangle.svg` |
2324
| [shapes-and-colors.ts](shapes-and-colors.ts) | Rounded/circle, linear gradient, logo text → 4 SVGs |
25+
| [to-file-stream.ts](to-file-stream.ts) | `toFileStream(stream, SVGOptions)``to-file-stream.svg` |
26+
| [to-file.ts](to-file.ts) | `toFile(path, SVGOptions)``to-file.svg` |
2427

2528
All scripts use `import QRCode from '@neabyte/qr-generator'` (resolved via `deno.json` when run locally).
2629

examples/png.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* PNG output via toPNG (data URL); decode and save to file.
3+
* @description PNGOptions (value, cellSize, margin; optional color/background); writes out/qr-default.png, qr-colored.png.
4+
* Run from repo root: deno run -A examples/png.ts
5+
*/
6+
import QRCode from '@neabyte/qr-generator'
7+
8+
/** Step 1: Output directory. */
9+
const outDir = new URL('./out', import.meta.url).pathname
10+
await Deno.mkdir(outDir, { recursive: true })
11+
12+
/** Step 2: toPNG returns data:image/png;base64,... — decode and write default (black on white). */
13+
const dataUrlDefault = await QRCode.toPNG({
14+
value: 'https://github.com/neabyte/qr-generator',
15+
cellSize: 4,
16+
margin: 8
17+
})
18+
const base64Default = dataUrlDefault.replace(/^data:image\/png;base64,/, '')
19+
await Deno.writeFile(
20+
`${outDir}/qr-default.png`,
21+
Uint8Array.from(atob(base64Default), c => c.charCodeAt(0))
22+
)
23+
24+
/** Step 3: toPNG with hex color/background for RGB PNG; decode and write. */
25+
const dataUrlColored = await QRCode.toPNG({
26+
value: 'PNG with colors',
27+
cellSize: 4,
28+
margin: 8,
29+
color: '#1a1a2e',
30+
background: '#eaeaea'
31+
})
32+
33+
/** Step 4: Decode and write the colored PNG. */
34+
const base64Colored = dataUrlColored.replace(/^data:image\/png;base64,/, '')
35+
await Deno.writeFile(
36+
`${outDir}/qr-colored.png`,
37+
Uint8Array.from(atob(base64Colored), c => c.charCodeAt(0))
38+
)
39+
console.log(`Saved: ${outDir}/qr-default.png, ${outDir}/qr-colored.png`)

examples/to-file-stream.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Write SVG to a stream via toFileStream(stream, options).
3+
* @description Stream with write(data) and end(); here stream writes to out/to-file-stream.svg.
4+
* Run from repo root: deno run -A examples/to-file-stream.ts
5+
*/
6+
import QRCode from '@neabyte/qr-generator'
7+
8+
/** Step 1: Output directory and path. */
9+
const outDir = new URL('./out', import.meta.url).pathname
10+
await Deno.mkdir(outDir, { recursive: true })
11+
const path = `${outDir}/to-file-stream.svg`
12+
13+
/** Step 2: WritableStreamLike: write(data) and end(); buffer then write file in end(). */
14+
let buffer = ''
15+
const stream = {
16+
write(data: string | Uint8Array) {
17+
buffer += typeof data === 'string' ? data : new TextDecoder().decode(data)
18+
},
19+
async end() {
20+
await Deno.writeTextFile(path, buffer)
21+
}
22+
}
23+
24+
/** Step 3: toFileStream calls stream.write(svg) then stream.end(). */
25+
await QRCode.toFileStream(stream, { value: 'toFileStream demo', size: 200 })
26+
console.log(`Saved: ${path}`)

examples/to-file.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Write SVG to file via toFile(path, options).
3+
* @description toFile uses adapter storage (Deno/Node writes to path); writes out/to-file.svg.
4+
* Run from repo root: deno run -A examples/to-file.ts
5+
*/
6+
import QRCode from '@neabyte/qr-generator'
7+
8+
/** Step 1: Output directory and SVG options. */
9+
const outDir = new URL('./out', import.meta.url).pathname
10+
await Deno.mkdir(outDir, { recursive: true })
11+
const opts = { value: 'toFile demo', size: 200, color: '#2563eb', background: '#eff6ff' }
12+
13+
/** Step 2: toFile(path, SVGOptions) writes SVG to path (adapter: Deno/Node file, browser download). */
14+
await QRCode.toFile(`${outDir}/to-file.svg`, opts)
15+
console.log(`Saved: ${outDir}/to-file.svg`)

src/Types.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ export type ErrorOptions = {
3838
level?: ErrorLevel
3939
}
4040

41+
/**
42+
* Finder pattern shape and gap options.
43+
* @description Shape and spacing for finder patterns.
44+
*/
45+
export type FinderOptions = {
46+
/** Finder module shape */
47+
shape?: ModuleShape
48+
/** Gap between finder modules */
49+
gap?: number
50+
}
51+
4152
/**
4253
* Options for data URL, ASCII, table, and canvas output.
4354
* @description Value, optional error level and cell layout.
@@ -53,17 +64,6 @@ export type FormatOptions = {
5364
margin?: number
5465
}
5566

56-
/**
57-
* Finder pattern shape and gap options.
58-
* @description Shape and spacing for finder patterns.
59-
*/
60-
export type FinderOptions = {
61-
/** Finder module shape */
62-
shape?: ModuleShape
63-
/** Gap between finder modules */
64-
gap?: number
65-
}
66-
6767
/**
6868
* Single gradient stop (offset and color).
6969
* @description One stop in a gradient definition.
@@ -172,6 +172,17 @@ export type PathResult = {
172172
path: string
173173
}
174174

175+
/**
176+
* PNG output options: value, layout, colors.
177+
* @description Same as FormatOptions plus optional hex colors.
178+
*/
179+
export type PNGOptions = FormatOptions & {
180+
/** Foreground hex color e.g. #000 */
181+
color?: string
182+
/** Background hex color e.g. #fff */
183+
background?: string
184+
}
185+
175186
/** 2D point with x and y. */
176187
export type Point = {
177188
/** X coordinate */

0 commit comments

Comments
 (0)