@nuintun/qrcode
Version:
A pure JavaScript QRCode encode and decode library.
116 lines (112 loc) • 3.48 kB
JavaScript
/**
* @module QRCode
* @package @nuintun/qrcode
* @license MIT
* @version 5.0.2
* @author nuintun <nuintun@qq.com>
* @description A pure JavaScript QRCode encode and decode library.
* @see https://github.com/nuintun/qrcode#readme
*/
import {
getSegmentLength,
appendECI,
appendFNC1Info,
appendModeInfo,
isHanziMode,
chooseRecommendVersion,
calculateBitsNeeded,
willFit,
appendLengthInfo,
appendTerminator,
injectECCodewords,
chooseBestMaskAndMatrix
} from './utils/encoder.js';
import { Encoded } from './Encoded.js';
import { Charset } from '../common/Charset.js';
import { ECLevel } from '../common/ECLevel.js';
import { BitArray } from '../common/BitArray.js';
import { VERSIONS } from '../common/Version.js';
import { encode } from '../common/encoding/index.js';
import { assertHints, assertLevel, assertVersion } from './utils/asserts.js';
/**
* @module Encoder
*/
class Encoder {
#hints;
#level;
#encode;
#version;
/**
* @constructor
* @param options The options of encoder.
*/
constructor({ hints = {}, level = 'L', version = 'Auto', encode: encode$1 = encode } = {}) {
assertHints(hints);
assertLevel(level);
assertVersion(version);
this.#hints = hints;
this.#encode = encode$1;
this.#version = version;
this.#level = ECLevel[level];
}
/**
* @method encode
* @description Encode the segments.
* @param segments The segments.
*/
encode(...segments) {
const ecLevel = this.#level;
const encode = this.#encode;
const { fnc1 } = this.#hints;
const versionNumber = this.#version;
const segmentBlocks = [];
// Only append FNC1 once.
let isFNC1Appended = false;
// Current ECI value.
let [currentECIValue] = Charset.ISO_8859_1.values;
// Init segments.
for (const segment of segments) {
const { mode } = segment;
const head = new BitArray();
const body = segment.encode(encode);
const length = getSegmentLength(segment, body);
// Append ECI segment if applicable.
currentECIValue = appendECI(head, segment, currentECIValue);
// Append FNC1 if applicable.
if (fnc1 != null && !isFNC1Appended) {
isFNC1Appended = true;
appendFNC1Info(head, fnc1);
}
// With ECI in place, Write the mode marker.
appendModeInfo(head, mode);
// If is Hanzi mode append GB2312 subset.
if (isHanziMode(segment)) {
head.append(1, 4);
}
// Push segment block.
segmentBlocks.push({ mode, head, body, length });
}
let version;
if (versionNumber === 'Auto') {
version = chooseRecommendVersion(segmentBlocks, ecLevel);
} else {
version = VERSIONS[versionNumber - 1];
const bitsNeeded = calculateBitsNeeded(segmentBlocks, version);
if (!willFit(bitsNeeded, version, ecLevel)) {
throw new Error('data too big for requested version');
}
}
const buffer = new BitArray();
for (const { mode, head, body, length } of segmentBlocks) {
buffer.append(head);
appendLengthInfo(buffer, mode, version, length);
buffer.append(body);
}
const ecBlocks = version.getECBlocks(ecLevel);
appendTerminator(buffer, ecBlocks.numTotalDataCodewords);
const codewords = injectECCodewords(buffer, ecBlocks);
const [mask, matrix] = chooseBestMaskAndMatrix(codewords, version, ecLevel);
return new Encoded(matrix, version, ecLevel, mask);
}
}
export { Encoder };