Lean QR
Minimal library for generating QR Codes in the browser and server-side.
Optimised for code size while maintaining decent performance.
Less than 8kB uncompressed (~4kB compressed).
You can see it in action at https://qr.davidje13.com/
Or try it from the commandline: npx lean-qr 'MY MESSAGE HERE'
Install dependency
npm install --save lean-qr
Usage
NodeJS
import { generate } from 'lean-qr';
const code = generate('LEAN-QR LIBRARY');
process.stdout.write(code.toString({
on: '\u001B[7m \u001B[0m',
}));
Browser
import { generate } from 'lean-qr';
const code = generate('LEAN-QR LIBRARY');
code.toCanvas(document.getElementById('my-canvas'));
<canvas id="my-canvas" />
<style>
#my-canvas {
width: 100%;
image-rendering: crisp-edges;
image-rendering: pixelated;
}
</style>
Shell
There is also a small commandline tool included for testing:
npx lean-qr 'MY MESSAGE HERE'
The commandline tool includes all extras by default (SVG output and Shift-JIS mode):
npx lean-qr '漢字'
npx lean-qr --format svg 'hello'
For full documentation, run npx lean-qr --help
.
Modes
By default, the optimal encoding mode is chosen to minimise the resulting
image size (this includes switching modes part way through a message if
it reduces the size). If you want to specify an explicit mode, you can:
import { mode, generate } from 'lean-qr';
const code = generate(mode.alphaNumeric('LEAN-QR LIBRARY'));
mode | bits / char | charset |
---|
mode.numeric | 10 / 3 | 0-9 |
mode.alphaNumeric | 11 / 2 | 0-9A-Z $%*+-./: |
mode.iso8859_1 | 8 / 1 | ISO-8859-1 |
shift_jis | 13 / 1 | See notes below |
mode.utf8 | varies | Unicode |
Note that if you specify a mode explicitly, it is your responsibility to
ensure the content you are encoding conforms to the accepted character
set. If you provide mismatched content, the resulting QR Code will likely
be malformed.
multi
mode.multi
enables switching modes during a message, for example:
const code = generate(mode.multi(
mode.iso8859_1('https://example.com/'),
mode.numeric('123456789012345678901234567890'),
mode.alphaNumeric('/LOOKUP'),
));
Note that you should not mix utf8
, iso8859_1
, or eci
, as they all
involve changing the global interpretation of the message and will
conflict with each other.
eci
/ bytes
mode.eci
lets you switch the Extended Channel Interpretation of the
message. After setting this, subsequent mode.bytes
will be interpreted
in the specified character set.
Wikipedia includes a list of possible values.
Note that iso8859_1
and utf8
both use bytes
for their data, so
you cannot combine a custom eci
with iso8859_1
or utf8
.
const code = generate(mode.multi(
mode.eci(24),
mode.bytes([0xD3]),
));
auto
mode.auto
will pick the optimal combination of modes for the message.
This is used by default if you provide a plain string to generate
, but
you can also use it explicitly to get more control:
const code = generate(mode.auto('FOOBAR', {
modes: [mode.numeric, mode.iso8859_1],
}));
You can omit the modes
argument to default to the standard modes.
You can also provide your own custom modes, and auto
will consider
them alongside the built-in modes (see below for details).
shift_jis
This is not included in the main library to keep it small, but if you need
Shift-JIS encoding, you can access it from a separate import (adds ~20kB):
import { shift_jis } from 'lean-qr/extras/jis';
const code = generate(shift_jis('漢字'));
It can also be registered with auto
mode:
const code = generate(mode.auto('漢字', {
modes: [
mode.numeric,
mode.alphaNumeric,
mode.iso8859_1,
shift_jis,
mode.utf8,
],
}));
The supported character set is all double-byte Shift-JIS characters in the
ranges: [0x8140 – 0x9FFC], [0xE040 – 0xEBBF].
Custom modes
Other modes are not currently supported, but it is possible to write
custom modes:
const myMode = (value) => (data, version) => {
data.push(0b101010, 6);
};
const code = generate(myMode('foobar'));
If you want your custom mode to be compatible with auto
, you need to
provide a pair of properties:
myMode.reg = /[0-9a-zA-Z]/;
myMode.est = (value, version) => (12 + value.length * 8);
For example the implementation of iso8859_1
:
const iso8859_1 = (value) => (data, version) => {
data.push(0b0100, 4);
data.push(value.length, version < 10 ? 8 : 16);
for (let i = 0; i < value.length; ++i) {
data.push(value.codePointAt(i), 8);
}
};
iso8859_1.reg = /[\u0000-\u00FF]/;
iso8859_1.est = (value, version) => (
4 + (version < 10 ? 8 : 16) +
value.length * 8
);
The .reg
property does not have to be a regular expression, as long as
it is an object which conforms to the RegExp.test
API (i.e. it is an
object with a test
method that accepts a character to check, returning
true
if the character is supported and false
if not).
Correction Levels
You can specify minimum and maximum correction levels:
const code = generate(mode.alphaNumeric('LEAN-QR LIBRARY'), {
minCorrectionLevel: correction.M,
maxCorrectionLevel: correction.Q,
});
generate
will pick the smallest code size which supports the
minCorrectionLevel
, then within this version will use the highest
possible correction level up to maxCorrectionLevel
.
correction level | error tolerance | data overhead |
---|
correction.L | ~7.5% | ~25% |
correction.M | ~15.0% | ~60% |
correction.Q | ~22.5% | ~120% |
correction.H | ~30.0% | ~190% |
Versions
By default, all versions (sizes) can be used. To restrict this, you can
specify a minimum and/or maximum version:
const code = generate(mode.alphaNumeric('LEAN-QR LIBRARY'), {
minVersion: 10,
maxVersion: 20,
});
Versions must be integers in the range 1 – 40 (inclusive).
The resulting size will be 17 + version * 4
.
If there is too much data for the maxVersion
size, an exception will be
thrown.
Masks
ISO 18004 requires masks be chosen according to a specific algorithm which
is designed to maximize readability by QR Code readers. This is done by
default, however if you would like to specify a particular mask, you can:
const code = generate(mode.alphaNumeric('LEAN-QR LIBRARY'), {
mask: 5,
});
Valid masks are integers in the range 0 – 7 (inclusive).
Output
The output can be displayed in several ways.
toString([options])
toString
takes several options. The defaults are shown here:
code.toString({
on: '##',
off: ' ',
lf: '\n',
padX: 4,
padY: 4,
});
Note that 4-cell padding is required by the standard to guarantee a
successful read, but you can change it to any value if you want.
Ensure the on
and off
strings have the same length or the resulting
code will be misaligned.
Note that if your terminal's line height is greater than the character
height (usually the case in terminal emulators running inside a graphical
interface), you should use ANSI escape sequences as shown in the top
example to ensure the code will be readable. But it is also possible to
display the result in other ways:
process.stdout.write(code.toString({
on: '\u001B[40m ',
off: '\u001B[107m ',
lf: '\u001B[0m\n',
}));
process.stdout.write(code.toString({
on: '\u2588\u2588',
off: ' ',
}));
toCanvas(canvas[, options])
toCanvas
takes several options. The defaults are shown here:
code.toCanvas(myTargetCanvas, {
on: [0x00, 0x00, 0x00, 0xFF],
off: [0x00, 0x00, 0x00, 0x00],
padX: 4,
padY: 4,
});
This will replace the image in myTargetCanvas
(which must be a canvas
element) with a copy of the current code. The result is always at a scale
of 1 pixel per module (the canvas will be resized to the correct size
automatically). To display this image at a reasonable size, it is
recommended that you use the following CSS:
.myTargetCanvas {
width: 100%;
image-rendering: crisp-edges;
image-rendering: pixelated;
}
The values of on
and off
should be arrays in [red, green, blue, alpha]
format. If alpha
is omitted, 255 is assumed.
toImageData(context[, options])
If you do not want to replace the entire content of a canvas, you can can
use toImageData
instead. This returns an ImageData
representation of
the code (created using context.createImageData
).
const imageData = code.toImageData(myContext, {
on: [0x00, 0x00, 0x00, 0xFF],
off: [0x00, 0x00, 0x00, 0x00],
padX: 4,
padY: 4,
});
myContext.putImageData(imageData, 200, 100);
myContext
must be a 2D context (i.e. the result of calling
myCanvas.getContext('2d')
) on a canvas element.
Note that until version 1.4.0, toImageData
did not include padding. To get
the same behaviour in 1.4.0+, set padX
and padY
to 0.
toDataURL([options])
Returns a string which can be used as a href
, e.g. for downloading;
const url = code.toDataURL({
type: 'image/png',
on: [0x00, 0x00, 0x00, 0xFF],
off: [0x00, 0x00, 0x00, 0x00],
padX: 4,
padY: 4,
scale: 1,
});
This URL can also be used as an img
source, but this is not recommended
(for best results use toCanvas
as shown above — this will avoid blurry
edges on high resolution displays and if the user zooms in).
Note that this is only available in-browser; it will fail if called in NodeJS.
toSvg(code, target[, options])
This is not included in the main library to keep it small, but if you need
SVG output, you can access it from a separate import (adds ~2kB):
import { toSvg } from 'lean-qr/extras/svg';
const mySvg = document.getElementById('my-svg');
const svg = toSvg(code, mySvg, {
on: 'black',
off: 'transparent',
padX: 4,
padY: 4,
width: null,
height: null,
scale: 1,
});
This will replace the image in mySvg
(which must be an svg
element)
with a copy of the current code. The result is always at a scale of 1 SVG
unit per module (the viewBox will be resized to the correct size
automatically). You can define a different size for the SVG element to scale
the image.
If mySvg
is the document
object, this will create and return a new SVG
entity associated with the document (but not attached to it).
If width
/ height
is given, the root SVG element will have the explicit
size applied. If these are not specified, they will be auto-calculated by
multiplying the code size + padding by scale
. You can override this for
display by setting CSS properties
(e.g. mySvg.style.width = '100%'; mySvg.style.height = 'auto';
).
toSvgSource(code[, options])
Like toSvg
but returns the source code for an SVG, rather than manipulating
DOM nodes (can be called inside NodeJS).
import { toSvgSource } from 'lean-qr/extras/svg';
const svgSource = toSvgSource(code, {
on: 'black',
off: 'transparent',
padX: 4,
padY: 4,
width: null,
height: null,
xmlDeclaration: false,
scale: 1,
});
Returns a complete SVG document which can be written to a standalone file or
included inside a HTML document.
If writing to a file, you should set xmlDeclaration
to true
(this prefixes
the source with <?xml version="1.0" encoding="UTF-8" ?>
).
toSvgDataURL(code[, options])
Like toSvg
but returns a data:image/svg
URL containing the image data,
suitable for displaying in an img
tag or downloading from an a
tag.
Can be called inside NodeJS.
import { toSvgDataURL } from 'lean-qr/extras/svg';
const dataURL = toSvgDataURL(code, {
on: 'black',
off: 'transparent',
padX: 4,
padY: 4,
width: null,
height: null,
scale: 1,
});
toSvgPath(code)
A raw SVG path definition for the QR code. Used by toSvg
and toSvgSource
.
import { toSvgPath } from 'lean-qr/extras/svg';
const svgPath = toSvgPath(code);
The returned path is always at a scale of 1 SVG unit to 1 module, with no
padding. The path will define the whole QR code in a single string suitable for
use inside <path d="[path here]">
, and can be used with fill-rule
of either
evenodd
or nonzero
. The path is optimised to ensure only true edges are
defined; it will not include overlapping edges (and will not have "cracks"
between pixels). No other guarantees are made about the structure of this
string, and the details could change in later versions.
get(x, y)
For other types of output, you can inspect the data directly:
for (let y = 0; y < code.size; ++y) {
for (let x = 0; x < code.size; ++x) {
process.stdout.write(code.get(x, y) ? '##' : ' ');
}
process.stdout.write('\n');
}
Requests outside the range 0 <= x < size, 0 <= y < size
will return false
.
Resources