@nuintun/qrcode
Version:
A pure JavaScript QRCode encode and decode library.
169 lines (164 loc) • 6.45 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
*/
'use strict';
const Pattern = require('./Pattern.cjs');
const utils = require('../common/utils.cjs');
const Version = require('../common/Version.cjs');
const module$1 = require('./utils/module.cjs');
const Point = require('../common/Point.cjs');
/**
* @module FinderPatternGroup
*/
function calculateSizeRatio(size1, size2) {
return size1 > size2 ? size1 / size2 : size2 / size1;
}
function calculateDistanceRatio(pattern1, pattern2) {
const ratio = Math.max(
calculateSizeRatio(Pattern.Pattern.width(pattern1), Pattern.Pattern.width(pattern2)),
calculateSizeRatio(Pattern.Pattern.height(pattern1), Pattern.Pattern.height(pattern2))
);
return ratio * ratio;
}
function crossProductZ(pattern1, pattern2, pattern3) {
const { x, y } = pattern2;
return (pattern3.x - x) * (pattern1.y - y) - (pattern3.y - y) * (pattern1.x - x);
}
function orderFinderPatterns(patterns) {
let topLeft;
let topRight;
let bottomLeft;
// Find distances between pattern centers.
const [pattern1, pattern2, pattern3] = patterns;
// @see https://github.com/zxing-cpp/zxing-cpp/blob/master/core/src/qrcode/QRDetector.cpp
const oneTwoDistance = Point.squaredDistance(pattern1, pattern2) * calculateDistanceRatio(pattern1, pattern2);
const oneThreeDistance = Point.squaredDistance(pattern1, pattern3) * calculateDistanceRatio(pattern1, pattern3);
const twoThreeDistance = Point.squaredDistance(pattern2, pattern3) * calculateDistanceRatio(pattern2, pattern3);
// Assume one closest to other two is B; A and C will just be guesses at first.
if (twoThreeDistance >= oneTwoDistance && twoThreeDistance >= oneThreeDistance) {
[topLeft, bottomLeft, topRight] = patterns;
} else if (oneThreeDistance >= twoThreeDistance && oneThreeDistance >= oneTwoDistance) {
[bottomLeft, topLeft, topRight] = patterns;
} else {
[bottomLeft, topRight, topLeft] = patterns;
}
// Use cross product to figure out whether A and C are correct or flipped.
// This asks whether BC x BA has a positive z component, which is the arrangement
// we want for A, B, C. If it's negative, then we've got it flipped around and
// should swap A and C.
if (crossProductZ(bottomLeft, topLeft, topRight) < 0) {
[bottomLeft, topRight] = [topRight, bottomLeft];
}
return [topLeft, topRight, bottomLeft];
}
function calculateBottomRightPoint([topLeft, topRight, bottomLeft]) {
const { x, y } = topLeft;
const bottomRightX = topRight.x + bottomLeft.x - x;
const bottomRightY = topRight.y + bottomLeft.y - y;
return new Point.Point(bottomRightX, bottomRightY);
}
function calculateSymbolSize([topLeft, topRight, bottomLeft], moduleSize) {
const width = Point.distance(topLeft, topRight);
const height = Point.distance(topLeft, bottomLeft);
const size = utils.round((width + height) / moduleSize / 2) + 7;
switch (size & 0x03) {
case 0:
return size + 1;
case 2:
return size - 1;
case 3:
return Math.min(size + 2, Version.MAX_VERSION_SIZE);
}
return size;
}
class FinderPatternGroup {
#area;
#size;
#matrix;
#bottomRight;
#moduleSize;
#patterns;
#moduleSizes;
static moduleSizes(finderPatternGroup) {
if (finderPatternGroup.#moduleSizes == null) {
const matrix = finderPatternGroup.#matrix;
const [topLeft, topRight, bottomLeft] = finderPatternGroup.#patterns;
finderPatternGroup.#moduleSizes = [
module$1.calculateModuleSizeOneWay(matrix, topLeft, topRight),
module$1.calculateModuleSizeOneWay(matrix, topLeft, bottomLeft)
];
}
return finderPatternGroup.#moduleSizes;
}
static size(finderPatternGroup) {
if (finderPatternGroup.#size == null) {
const moduleSize = FinderPatternGroup.moduleSize(finderPatternGroup);
finderPatternGroup.#size = calculateSymbolSize(finderPatternGroup.#patterns, moduleSize);
}
return finderPatternGroup.#size;
}
static moduleSize(finderPatternGroup) {
if (finderPatternGroup.#moduleSize == null) {
finderPatternGroup.#moduleSize = utils.accumulate(FinderPatternGroup.moduleSizes(finderPatternGroup)) / 2;
}
return finderPatternGroup.#moduleSize;
}
static contains(finderPatternGroup, pattern) {
const area = finderPatternGroup.#calculateArea();
const [topLeft, topRight, bottomLeft] = finderPatternGroup.#patterns;
const bottomRight = FinderPatternGroup.bottomRight(finderPatternGroup);
const s1 = Point.calculateTriangleArea(topLeft, topRight, pattern);
const s2 = Point.calculateTriangleArea(topRight, bottomRight, pattern);
const s3 = Point.calculateTriangleArea(bottomRight, bottomLeft, pattern);
const s4 = Point.calculateTriangleArea(bottomLeft, topLeft, pattern);
// Pattern not a point, increase the detection margin appropriately.
return s1 + s2 + s3 + s4 - area < 1;
}
static bottomRight(finderPatternGroup) {
if (finderPatternGroup.#bottomRight == null) {
finderPatternGroup.#bottomRight = calculateBottomRightPoint(finderPatternGroup.#patterns);
}
return finderPatternGroup.#bottomRight;
}
constructor(matrix, patterns) {
this.#matrix = matrix;
this.#patterns = orderFinderPatterns(patterns);
}
get topLeft() {
return this.#patterns[0];
}
get topRight() {
return this.#patterns[1];
}
get bottomLeft() {
return this.#patterns[2];
}
#calculateArea() {
const [topLeft, topRight, bottomLeft] = this.#patterns;
const bottomRight = FinderPatternGroup.bottomRight(this);
if (this.#area == null) {
const s1 = Point.calculateTriangleArea(topLeft, topRight, bottomRight);
const s2 = Point.calculateTriangleArea(bottomRight, bottomLeft, topLeft);
this.#area = s1 + s2;
}
return this.#area;
}
}
function calculateTopLeftAngle({ topLeft, topRight, bottomLeft }) {
const { x, y } = topLeft;
const dx1 = topRight.x - x;
const dy1 = topRight.y - y;
const dx2 = bottomLeft.x - x;
const dy2 = bottomLeft.y - y;
const d = dx1 * dx2 + dy1 * dy2;
const l2 = (dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2);
return Math.acos(d / Math.sqrt(l2));
}
exports.FinderPatternGroup = FinderPatternGroup;
exports.calculateTopLeftAngle = calculateTopLeftAngle;