@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
580 lines (469 loc) • 18.2 kB
JavaScript
import {
toBitsString,
toDecimal
} from '../utils';
import { FreeCellVisitor } from './free-cell-visitor';
import { IsoEncoder } from './encoders/iso-encoder';
import { Utf8Encoder } from './encoders/utf8-encoder';
import { VersionsCodewordsInformation } from './version-codewords';
let terminator = "0000",
powersOfTwo = { "1": 0 },
powersOfTwoResult = { "0": 1 },
irregularAlignmentPatternsStartDistance = {
15: 20, 16: 20, 18: 24, 19: 24, 22: 20,
24: 22, 26: 24, 28: 20, 30: 20, 31: 24,
32: 28, 33: 24, 36: 18, 37: 22, 39: 20, 40: 24
},
finderPattern = [1, 0, 1, 1, 1],
alignmentPattern = [1, 0, 1],
errorCorrectionPatterns = { L: "01", M: "00", Q: "11", H: "10" },
formatMaskPattern = "101010000010010",
formatGeneratorPolynomial = "10100110111",
versionGeneratorPolynomial = "1111100100101",
paddingCodewords = ["11101100", "00010001"],
finderPatternValue = 93,
/* eslint-disable arrow-body-style */
/* eslint-disable no-unused-vars */
maskPatternConditions = [
(row, column) => { return (row + column) % 2 === 0; },
(row, column) => { return row % 2 === 0; },
(row, column) => { return column % 3 === 0; },
(row, column) => { return (row + column) % 3 === 0; },
(row, column) => { return (Math.floor(row / 2) + Math.floor(column / 3)) % 2 === 0; },
(row, column) => { return ((row * column) % 2) + ((row * column) % 3) === 0; },
(row, column) => { return (((row * column) % 2) + ((row * column) % 3)) % 2 === 0; },
(row, column) => { return (((row + column) % 2) + ((row * column) % 3)) % 2 === 0; }
];
/* eslint-enable no-unused-vars */
/* eslint-enable arrow-body-style */
export const generatorPolynomials = [[1, 0], [1, 25, 0]];
export function fillFunctionCell(matrices, bit, x, y) {
for (let i = 0; i < matrices.length; i++) {
matrices[i][x][y] = bit;
}
}
export function fillDataCell(matrices, bit, x, y) {
for (let i = 0; i < maskPatternConditions.length; i++) {
matrices[i][x][y] = maskPatternConditions[i](x, y) ? bit ^ 1 : parseInt(bit, 10);
}
}
export function fillData(matrices, blocks) {
let cellVisitor = new FreeCellVisitor(matrices[0]),
block,
codewordIdx,
cell;
for (let blockIdx = 0; blockIdx < blocks.length; blockIdx++) {
block = blocks[blockIdx];
codewordIdx = 0;
while (block.length > 0) {
for (let i = 0; i < block.length; i++) {
for (let j = 0; j < 8; j++) {
cell = cellVisitor.getNextCell();
fillDataCell(matrices, block[i][codewordIdx].charAt(j), cell.row, cell.column);
}
}
codewordIdx++;
while (block[0] && codewordIdx === block[0].length) {
block.splice(0, 1);
}
}
}
while ((cell = cellVisitor.getNextRemainderCell())) {
fillDataCell(matrices, 0, cell.row, cell.column);
}
}
export function padDataString(initialDataString, totalDataCodewords) {
let dataBitsCount = totalDataCodewords * 8,
terminatorIndex = 0,
paddingCodewordIndex = 0;
let dataString = initialDataString;
while (dataString.length < dataBitsCount && terminatorIndex < terminator.length) {
dataString += terminator.charAt(terminatorIndex++);
}
if (dataString.length % 8 !== 0) {
dataString += new Array(9 - dataString.length % 8).join("0");
}
while (dataString.length < dataBitsCount) {
dataString += paddingCodewords[paddingCodewordIndex];
paddingCodewordIndex ^= 1;
}
return dataString;
}
export function generatePowersOfTwo() {
let result;
let power;
for (power = 1; power < 255; power++) {
result = powersOfTwoResult[power - 1] * 2;
if (result > 255) {
result = result ^ 285;
}
powersOfTwoResult[power] = result;
powersOfTwo[result] = power;
}
result = (powersOfTwoResult[power - 1] * 2) ^ 285;
powersOfTwoResult[power] = result;
powersOfTwoResult[-1] = 0;
}
export function xorPolynomials(x, y) {
let result = [],
idx = x.length - 2;
for (let i = idx; i >= 0; i--) {
result[i] = x[i] ^ y[i];
}
return result;
}
export function multiplyPolynomials(x, y) {
let result = [];
for (let i = 0; i < x.length; i++) {
for (let j = 0; j < y.length; j++) {
if (result[i + j] === undefined) {
result[i + j] = (x[i] + (y[j] >= 0 ? y[j] : 0)) % 255;
} else {
result[i + j] = powersOfTwo[powersOfTwoResult[result[i + j]] ^ powersOfTwoResult[(x[i] + y[j]) % 255]];
}
}
}
return result;
}
export function generateGeneratorPolynomials() {
let maxErrorCorrectionCodeWordsCount = 68;
for (let idx = 2; idx <= maxErrorCorrectionCodeWordsCount; idx++) {
let firstPolynomial = generatorPolynomials[idx - 1],
secondPolynomial = [idx, 0];
generatorPolynomials[idx] = multiplyPolynomials(firstPolynomial, secondPolynomial);
}
}
//possibly generate on demand
generatePowersOfTwo();
generateGeneratorPolynomials();
export function multiplyByConstant(polynomial, power) {
let result = [],
idx = polynomial.length - 1;
do {
result[idx] = powersOfTwoResult[(polynomial[idx] + power) % 255];
idx--;
}
while (polynomial[idx] !== undefined);
return result;
}
export function generateErrorCodewords(data, errorCodewordsCount) {
let generator = generatorPolynomials[errorCodewordsCount - 1],
result = new Array(errorCodewordsCount).concat(data),
generatorPolynomial = new Array(result.length - generator.length).concat(generator),
steps = data.length,
errorCodewords = [],
divisor,
idx;
for (idx = 0; idx < steps; idx++) {
divisor = multiplyByConstant(generatorPolynomial, powersOfTwo[result[result.length - 1]]);
generatorPolynomial.splice(0, 1);
result = xorPolynomials(divisor, result);
}
for (idx = result.length - 1; idx >= 0; idx--) {
errorCodewords[errorCodewordsCount - 1 - idx] = toBitsString(result[idx], 8);
}
return errorCodewords;
}
export function getBlocks(dataStream, versionCodewordsInformation) {
let codewordStart = 0,
dataBlocks = [],
errorBlocks = [],
dataBlock,
versionGroups = versionCodewordsInformation.groups,
blockCodewordsCount,
groupBlocksCount,
messagePolynomial,
codeword;
for (let groupIdx = 0; groupIdx < versionGroups.length; groupIdx++) {
groupBlocksCount = versionGroups[groupIdx][0];
for (let blockIdx = 0; blockIdx < groupBlocksCount; blockIdx++) {
blockCodewordsCount = versionGroups[groupIdx][1];
dataBlock = [];
messagePolynomial = [];
for (let codewordIdx = 1; codewordIdx <= blockCodewordsCount; codewordIdx++) {
codeword = dataStream.substring(codewordStart, codewordStart + 8);
dataBlock.push(codeword);
messagePolynomial[blockCodewordsCount - codewordIdx] = toDecimal(codeword);
codewordStart += 8;
}
dataBlocks.push(dataBlock);
errorBlocks.push(generateErrorCodewords(messagePolynomial,
versionCodewordsInformation.errorCodewordsPerBlock));
}
}
return [dataBlocks, errorBlocks];
}
//fix case all zeros
export function encodeFormatInformation(format) {
let formatNumber = toDecimal(format),
encodedString,
result = "";
if (formatNumber === 0) {
return "101010000010010";
}
encodedString = encodeBCH(toDecimal(format), formatGeneratorPolynomial, 15);
for (let i = 0; i < encodedString.length; i++) {
result += encodedString.charAt(i) ^ formatMaskPattern.charAt(i);
}
return result;
}
export function encodeBCH(value, generatorPolynomial, codeLength) {
let generatorNumber = toDecimal(generatorPolynomial),
polynomialLength = generatorPolynomial.length - 1,
valueNumber = value << polynomialLength,
length = codeLength - polynomialLength,
valueString = toBitsString(value, length),
result = dividePolynomials(valueNumber, generatorNumber);
result = valueString + toBitsString(result, polynomialLength);
return result;
}
export function dividePolynomials(numberX, numberY) {
let yLength = numberY.toString(2).length,
xLength = numberX.toString(2).length;
let x = numberX;
do {
x ^= numberY << xLength - yLength;
xLength = x.toString(2).length;
}
while (xLength >= yLength);
return x;
}
export function getNumberAt(str, idx) {
return parseInt(str.charAt(idx), 10);
}
export function initMatrices(version) {
let matrices = [],
modules = 17 + 4 * version;
for (let i = 0; i < maskPatternConditions.length; i++) {
matrices[i] = new Array(modules);
for (let j = 0; j < modules; j++) {
matrices[i][j] = new Array(modules);
}
}
return matrices;
}
export function addFormatInformation(matrices, formatString) {
let matrix = matrices[0],
x,
y,
idx = 0,
length = formatString.length;
for (x = 0, y = 8; x <= 8; x++) {
if (x !== 6) {
fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
}
}
for (x = 8, y = 7; y >= 0; y--) {
if (y !== 6) {
fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
}
}
idx = 0;
for (y = matrix.length - 1, x = 8; y >= matrix.length - 8; y--) {
fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
}
fillFunctionCell(matrices, 1, matrix.length - 8, 8);
for (x = matrix.length - 7, y = 8; x < matrix.length; x++) {
fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
}
}
export function encodeVersionInformation(version) {
return encodeBCH(version, versionGeneratorPolynomial, 18);
}
export function addVersionInformation(matrices, dataString) {
let matrix = matrices[0],
modules = matrix.length,
x1 = 0,
y1 = modules - 11,
x2 = modules - 11,
y2 = 0,
quotient,
mod,
value;
for (let idx = 0; idx < dataString.length; idx++) {
quotient = Math.floor(idx / 3);
mod = idx % 3;
value = getNumberAt(dataString, dataString.length - idx - 1);
fillFunctionCell(matrices, value, x1 + quotient, y1 + mod);
fillFunctionCell(matrices, value, x2 + mod, y2 + quotient);
}
}
export function addCentricPattern(matrices, pattern, x, y) {
let size = pattern.length + 2,
length = pattern.length + 1,
value;
for (let i = 0; i < pattern.length; i++) {
for (let j = i; j < size - i; j++) {
value = pattern[i];
fillFunctionCell(matrices, value, x + j, y + i);
fillFunctionCell(matrices, value, x + i, y + j);
fillFunctionCell(matrices, value, x + length - j, y + length - i);
fillFunctionCell(matrices, value, x + length - i, y + length - j);
}
}
}
export function addFinderSeparator(matrices, direction, x, y) {
let nextX = x,
nextY = y,
matrix = matrices[0];
do {
fillFunctionCell(matrices, 0, nextX, y);
fillFunctionCell(matrices, 0, x, nextY);
nextX += direction[0];
nextY += direction[1];
}
while (nextX >= 0 && nextX < matrix.length);
}
export function addFinderPatterns(matrices) {
let modules = matrices[0].length;
addCentricPattern(matrices, finderPattern, 0, 0);
addFinderSeparator(matrices, [-1, -1], 7, 7);
addCentricPattern(matrices, finderPattern, modules - 7, 0);
addFinderSeparator(matrices, [1, -1], modules - 8, 7);
addCentricPattern(matrices, finderPattern, 0, modules - 7);
addFinderSeparator(matrices, [-1, 1], 7, modules - 8);
}
export function addAlignmentPatterns(matrices, version) {
if (version < 2) {
return;
}
let matrix = matrices[0],
modules = matrix.length,
pointsCount = Math.floor(version / 7),
points = [6],
startDistance,
distance,
idx = 0;
if ((startDistance = irregularAlignmentPatternsStartDistance[version])) {
distance = (modules - 13 - startDistance) / pointsCount;
} else {
startDistance = distance = (modules - 13) / (pointsCount + 1);
}
points.push(points[idx++] + startDistance);
while ((points[idx] + distance) < modules) {
points.push(points[idx++] + distance);
}
for (let i = 0; i < points.length; i++) {
for (let j = 0; j < points.length; j++) {
if (matrix[points[i]][points[j]] === undefined) {
addCentricPattern(matrices, alignmentPattern, points[i] - 2, points[j] - 2);
}
}
}
}
export function addTimingFunctions(matrices) {
let row = 6,
column = 6,
value = 1,
modules = matrices[0].length;
for (let i = 8; i < modules - 8; i++) {
fillFunctionCell(matrices, value, row, i);
fillFunctionCell(matrices, value, i, column);
value ^= 1;
}
}
export function scoreMaskMatrixes(matrices) {
let scores = [],
previousBits = [],
darkModules = [],
patterns = [],
adjacentSameBits = [],
matrix,
i,
row = 0,
column = 1,
modulesLength = matrices[0].length;
for (i = 0; i < matrices.length; i++) {
scores[i] = 0;
darkModules[i] = 0;
adjacentSameBits[i] = [0, 0];
patterns[i] = [0, 0];
previousBits[i] = [];
}
for (let rowIndex = 0; rowIndex < modulesLength; rowIndex++) {
for (let columnIndex = 0; columnIndex < modulesLength; columnIndex++) {
for (let matrixIndex = 0; matrixIndex < matrices.length; matrixIndex++) {
matrix = matrices[matrixIndex];
darkModules[matrixIndex] += parseInt(matrix[rowIndex][columnIndex], 10);
if (previousBits[matrixIndex][row] === matrix[rowIndex][columnIndex] &&
rowIndex + 1 < modulesLength &&
columnIndex - 1 >= 0 &&
matrix[rowIndex + 1][columnIndex] === previousBits[matrixIndex][row] &&
matrix[rowIndex + 1][columnIndex - 1] === previousBits[matrixIndex][row]) {
scores[matrixIndex] += 3;
}
scoreFinderPatternOccurance(matrixIndex, patterns, scores, row, matrix[rowIndex][columnIndex]);
scoreFinderPatternOccurance(matrixIndex, patterns, scores, column, matrix[columnIndex][rowIndex]);
scoreAdjacentSameBits(matrixIndex, scores, previousBits, matrix[rowIndex][columnIndex], adjacentSameBits, row);
scoreAdjacentSameBits(matrixIndex, scores, previousBits, matrix[columnIndex][rowIndex], adjacentSameBits, column);
}
}
}
let total = modulesLength * modulesLength,
minIdx,
min = Number.MAX_VALUE;
for (i = 0; i < scores.length; i++) {
scores[i] += calculateDarkModulesRatioScore(darkModules[i], total);
if (scores[i] < min) {
min = scores[i];
minIdx = i;
}
}
return minIdx;
}
export function scoreFinderPatternOccurance(idx, patterns, scores, rowColumn, bit) {
patterns[idx][rowColumn] = ((patterns[idx][rowColumn] << 1) ^ bit) % 128;
if (patterns[idx][rowColumn] === finderPatternValue) {
scores[idx] += 40;
}
}
export function scoreAdjacentSameBits(idx, scores, previousBits, bit, adjacentBits, rowColumn) {
if (previousBits[idx][rowColumn] === bit) {
adjacentBits[idx][rowColumn]++;
} else {
previousBits[idx][rowColumn] = bit;
if (adjacentBits[idx][rowColumn] >= 5) {
scores[idx] += 3 + adjacentBits[idx][rowColumn] - 5;
}
adjacentBits[idx][rowColumn] = 1;
}
}
export function calculateDarkModulesRatioScore(darkModules, total) {
let percent = Math.floor((darkModules / total) * 100),
mod5 = percent % 5,
previous = Math.abs(percent - mod5 - 50),
next = Math.abs(percent + 5 - mod5 - 50),
score = 10 * Math.min(previous / 5, next / 5);
return score;
}
export function createQRCodeDataEncoder(encoding) {
if (encoding && encoding.toLowerCase().indexOf("utf_8") >= 0) {
return new Utf8Encoder();
}
return new IsoEncoder();
}
export function encodeData(inputString, errorCorrectionLevel, encoding) {
let encoder = createQRCodeDataEncoder(encoding),
encodingResult = encoder.getEncodingResult(inputString, errorCorrectionLevel),
version = encodingResult.version,
versionInformation = VersionsCodewordsInformation[version - 1][errorCorrectionLevel],
dataString = padDataString(encodingResult.dataString, versionInformation.totalDataCodewords),
blocks = getBlocks(dataString, versionInformation),
matrices = initMatrices(version);
addFinderPatterns(matrices);
addAlignmentPatterns(matrices, version);
addTimingFunctions(matrices);
if (version >= 7) {
addVersionInformation(matrices, toBitsString(0, 18));
}
addFormatInformation(matrices, toBitsString(0, 15));
fillData(matrices, blocks);
let minIdx = scoreMaskMatrixes(matrices),
optimalMatrix = matrices[minIdx];
if (version >= 7) {
addVersionInformation([optimalMatrix], encodeVersionInformation(version));
}
let formatString = errorCorrectionPatterns[errorCorrectionLevel] + toBitsString(minIdx, 3);
addFormatInformation([optimalMatrix], encodeFormatInformation(formatString));
return optimalMatrix;
}