import type { QrCodeEcc } from './types'; import { HIGH, MAX_VERSION, MEDIUM, MIN_VERSION, QUARTILE } from './constants'; import { QrCode } from './qr-code'; import { makeBytes, makeSegments } from './segment'; import type { QrSegment } from './segment'; import { appendBits, assert, getNumDataCodewords, getTotalBits, numCharCountBits } from './utils'; /** * Returns a QR Code representing the given Unicode text string at the given error correction level. * As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer * Unicode code points (not UTF-16 code units) if the low error correction level is used. * The smallest possible QR Code version is automatically chosen for the output. */ export function encodeText(text: string, ecl: QrCodeEcc): QrCode { const segs = makeSegments(text); return encodeSegments(segs, ecl); } /** * Returns a QR Code representing the given binary data at the given error correction level. * This function always encodes using the binary segment mode, not any text mode. * The maximum number of bytes allowed is 2953. */ export function encodeBinary(data: readonly number[], ecl: QrCodeEcc): QrCode { const seg = makeBytes(data); return encodeSegments([seg], ecl); } /** * Returns a QR Code representing the given segments with the given encoding parameters. * The smallest possible QR Code version within the given range is automatically chosen for the output. * This is a mid-level API; the high-level API is encodeText() and encodeBinary(). */ export function encodeSegments( segs: readonly QrSegment[], ecl: QrCodeEcc, minVersion = 1, maxVersion = 40, mask = -1, boostEcl = true, ): QrCode { if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) throw new RangeError('Invalid value'); // Find the minimal version number to use let version: number; let dataUsedBits: number; for (version = minVersion; ; version++) { const dataCapacityBits = getNumDataCodewords(version, ecl) * 8; const usedBits = getTotalBits(segs, version); if (usedBits <= dataCapacityBits) { dataUsedBits = usedBits; break; } if (version >= maxVersion) throw new RangeError('Data too long'); } // Increase the error correction level while the data still fits in the current version number for (const newEcl of [MEDIUM, QUARTILE, HIGH]) { if (boostEcl && dataUsedBits! <= getNumDataCodewords(version, newEcl) * 8) ecl = newEcl; } // Concatenate all segments to create the data bit string const bb: number[] = []; for (const seg of segs) { appendBits(seg.mode[0], 4, bb); appendBits(seg.numChars, numCharCountBits(seg.mode, version), bb); for (const b of seg.bitData) bb.push(b); } assert(bb.length === dataUsedBits!); // Add terminator and pad up to a byte if applicable const dataCapacityBits = getNumDataCodewords(version, ecl) * 8; assert(bb.length <= dataCapacityBits); appendBits(0, Math.min(4, dataCapacityBits - bb.length), bb); appendBits(0, (8 - bb.length % 8) % 8, bb); assert(bb.length % 8 === 0); // Pad with alternating bytes until data capacity is reached for (let padByte = 0xEC; bb.length < dataCapacityBits; padByte ^= 0xEC ^ 0x11) appendBits(padByte, 8, bb); // Pack bits into bytes in big endian const dataCodewords = Array.from({ length: Math.ceil(bb.length / 8) }, () => 0); for (let i = 0; i < bb.length; i++) dataCodewords[i >>> 3]! |= bb[i]! << (7 - (i & 7)); // Create the QR Code object return new QrCode(version, ecl, dataCodewords, mask); }