Skip to content

Commit

Permalink
feat: value support string[] type
Browse files Browse the repository at this point in the history
  • Loading branch information
li-jia-nan committed Jan 8, 2025
1 parent fd675ed commit b1d7fc5
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 42 deletions.
6 changes: 3 additions & 3 deletions src/QRCodeCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const QRCodeCanvas = React.forwardRef<HTMLCanvasElement, QRPropsCanvas>(
marginSize,
style,
imageSettings,
boostLevel,
...otherProps
} = props;
const imgSrc = imageSettings?.src;
Expand Down Expand Up @@ -54,6 +55,7 @@ const QRCodeCanvas = React.forwardRef<HTMLCanvasElement, QRPropsCanvas>(
marginSize,
imageSettings,
size,
boostLevel,
});

React.useEffect(() => {
Expand Down Expand Up @@ -137,9 +139,7 @@ const QRCodeCanvas = React.forwardRef<HTMLCanvasElement, QRPropsCanvas>(
src={imgSrc}
key={imgSrc}
style={{ display: 'none' }}
onLoad={() => {
setIsImageLoaded(true);
}}
onLoad={() => setIsImageLoaded(true)}
ref={_image}
// when crossOrigin is not set, the image will be tainted
// and the canvas cannot be exported to an image
Expand Down
2 changes: 2 additions & 0 deletions src/QRCodeSVG.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const QRCodeSVG = React.forwardRef<SVGSVGElement, QRPropsSVG>((props, ref) => {
title,
marginSize,
imageSettings,
boostLevel,
...otherProps
} = props;

Expand All @@ -35,6 +36,7 @@ const QRCodeSVG = React.forwardRef<SVGSVGElement, QRPropsSVG>((props, ref) => {
marginSize,
imageSettings,
size,
boostLevel,
});

let cellsToDraw = cells;
Expand Down
21 changes: 17 additions & 4 deletions src/hooks/useQRCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { ERROR_LEVEL_MAP, getImageSettings, getMarginSize } from '../utils';
import React from 'react';

interface Options {
value: string;
value: string | string[];
level: ErrorCorrectionLevel;
minVersion: number;
includeMargin: boolean;
marginSize?: number;
imageSettings?: ImageSettings;
size: number;
boostLevel?: boolean;
}

export const useQRCode = (opt: Options) => {
Expand All @@ -22,12 +23,24 @@ export const useQRCode = (opt: Options) => {
marginSize,
imageSettings,
size,
boostLevel,
} = opt;

const memoizedQrcode = React.useMemo(() => {
const segments = QrSegment.makeSegments(value);
return QrCode.encodeSegments(segments, ERROR_LEVEL_MAP[level], minVersion);
}, [value, level, minVersion]);
const values = Array.isArray(value) ? value : [value];
const segments = values.reduce<QrSegment[]>((acc, v) => {
acc.push(...QrSegment.makeSegments(v));
return acc;
}, []);
return QrCode.encodeSegments(
segments,
ERROR_LEVEL_MAP[level],
minVersion,
undefined,
undefined,
boostLevel,
);
}, [value, level, minVersion, boostLevel]);

return React.useMemo(() => {
const cs = memoizedQrcode.getModules();
Expand Down
10 changes: 9 additions & 1 deletion src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export type QRProps = {
* The value to encode into the QR Code. An array of strings can be passed in
* to represent multiple segments to further optimize the QR Code.
*/
value: string;
value: string | string[];
/**
* The size, in pixels, to render the QR Code.
* @defaultValue 128
Expand Down Expand Up @@ -118,6 +118,14 @@ export type QRProps = {
* @defaultValue 1
*/
minVersion?: number;

/**
* If enabled, the Error Correction Level of the result may be higher than
* the specified Error Correction Level option if it can be done without
* increasing the version.
* @defaultValue true
*/
boostLevel?: boolean;
};

export type QRPropsCanvas = QRProps &
Expand Down
80 changes: 46 additions & 34 deletions src/libs/qrcodegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ function appendBits(val: number, len: number, bb: number[]): void {
let i = len - 1;
i >= 0;
i-- // Append bit by bit
)
) {
bb.push((val >>> i) & 1);
}
}

// Returns true iff the i'th bit of x is set to 1.
Expand Down Expand Up @@ -112,7 +113,9 @@ export class QrSegment {
// can be converted to UTF-8 bytes and encoded as a byte mode segment.
public static makeBytes(data: Readonly<number[]>): QrSegment {
const bb: number[] = [];
for (const b of data) appendBits(b, 8, bb);
for (const b of data) {
appendBits(b, 8, bb);
}
return new QrSegment(Mode.BYTE, data.length, bb);
}

Expand Down Expand Up @@ -258,8 +261,9 @@ export class QrSegment {
const str = encodeURI(input);
const result: number[] = [];
for (let i = 0; i < str.length; i++) {
if (str.charAt(i) != '%') result.push(str.charCodeAt(i));
else {
if (str.charAt(i) != '%') {
result.push(str.charCodeAt(i));
} else {
result.push(parseInt(str.substring(i + 1, i + 3), 16));
i += 2;
}
Expand Down Expand Up @@ -315,7 +319,7 @@ export class QrCode {
// bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.
// The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.
public static encodeBinary(data: Readonly<number[]>, ecl: Ecc): QrCode {
const seg: QrSegment = QrSegment.makeBytes(data);
const seg = QrSegment.makeBytes(data);
return QrCode.encodeSegments([seg], ecl);
}

Expand Down Expand Up @@ -346,23 +350,24 @@ export class QrCode {
) ||
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: number =
QrCode.getNumDataCodewords(version, oriEcl) * 8; // Number of data bits available
const dataCapacityBits = QrCode.getNumDataCodewords(version, oriEcl) * 8; // Number of data bits available
const usedBits: number = QrSegment.getTotalBits(segs, version);
if (usedBits <= dataCapacityBits) {
dataUsedBits = usedBits;
break; // This version number is found to be suitable
}
if (version >= maxVersion)
if (version >= maxVersion) {
// All versions in the range could not fit the given data
throw new RangeError('Data too long');
}
}
let ecl: Ecc = oriEcl;
// Increase the error correction level while the data still fits in the current version number
Expand Down Expand Up @@ -408,7 +413,9 @@ export class QrCode {
while (dataCodewords.length * 8 < bb.length) {
dataCodewords.push(0);
}
bb.forEach((b, i) => (dataCodewords[i >>> 3] |= b << (7 - (i & 7))));
bb.forEach((b, i) => {
dataCodewords[i >>> 3] |= b << (7 - (i & 7));
});

// Create the QR Code object
return new QrCode(version, ecl, dataCodewords, mask);
Expand Down Expand Up @@ -460,16 +467,19 @@ export class QrCode {
this.version = version;
this.errorCorrectionLevel = errorCorrectionLevel;
// Check scalar arguments
if (version < QrCode.MIN_VERSION || version > QrCode.MAX_VERSION)
if (version < QrCode.MIN_VERSION || version > QrCode.MAX_VERSION) {
throw new RangeError('Version value out of range');
}
if (msk < -1 || msk > 7) {
throw new RangeError('Mask value out of range');
}
this.size = version * 4 + 17;

// Initialize both grids to be size*size arrays of Boolean false
const row: boolean[] = [];
for (let i = 0; i < this.size; i++) row.push(false);
for (let i = 0; i < this.size; i++) {
row.push(false);
}
for (let i = 0; i < this.size; i++) {
this.modules.push(row.slice()); // Initially all light
this.isFunction.push(row.slice());
Expand Down Expand Up @@ -570,7 +580,9 @@ export class QrCode {
assert(bits >>> 15 == 0);

// Draw first copy
for (let i = 0; i <= 5; i++) this.setFunctionModule(8, i, getBit(bits, i));
for (let i = 0; i <= 5; i++) {
this.setFunctionModule(8, i, getBit(bits, i));
}
this.setFunctionModule(8, 7, getBit(bits, 6));
this.setFunctionModule(8, 8, getBit(bits, 7));
this.setFunctionModule(7, 8, getBit(bits, 8));
Expand Down Expand Up @@ -658,19 +670,15 @@ export class QrCode {
throw new RangeError('Invalid argument');
}
// Calculate parameter numbers
const numBlocks: number =
QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
const blockEccLen: number =
QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver];
const rawCodewords: number = Math.floor(
QrCode.getNumRawDataModules(ver) / 8,
);
const numShortBlocks: number = numBlocks - (rawCodewords % numBlocks);
const shortBlockLen: number = Math.floor(rawCodewords / numBlocks);
const numBlocks = QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
const blockEccLen = QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver];
const rawCodewords = Math.floor(QrCode.getNumRawDataModules(ver) / 8);
const numShortBlocks = numBlocks - (rawCodewords % numBlocks);
const shortBlockLen = Math.floor(rawCodewords / numBlocks);

// Split data numbero blocks and append ECC to each block
const blocks: number[][] = [];
const rsDiv: number[] = QrCode.reedSolomonComputeDivisor(blockEccLen);
const rsDiv = QrCode.reedSolomonComputeDivisor(blockEccLen);
for (let i = 0, k = 0; i < numBlocks; i++) {
const dat = data.slice(
k,
Expand Down Expand Up @@ -854,8 +862,9 @@ export class QrCode {

// Balance of dark and light modules
let dark: number = 0;
for (const row of this.modules)
for (const row of this.modules) {
dark = row.reduce((sum, color) => sum + (color ? 1 : 0), dark);
}
const total: number = this.size * this.size; // Note that size is odd, so dark/total != 1/2
// Compute the smallest numbereger k >= 0 such that (45-5k)% <= dark/total <= (55+5k)%
const k: number = Math.ceil(Math.abs(dark * 20 - total * 10) / total) - 1;
Expand All @@ -874,14 +883,15 @@ export class QrCode {
if (this.version == 1) {
return [];
} else {
const numAlign: number = Math.floor(this.version / 7) + 2;
const step: number =
const numAlign = Math.floor(this.version / 7) + 2;
const step =
this.version == 32
? 26
: Math.ceil((this.version * 4 + 4) / (numAlign * 2 - 2)) * 2;
const result: number[] = [6];
for (let pos = this.size - 7; result.length < numAlign; pos -= step)
for (let pos = this.size - 7; result.length < numAlign; pos -= step) {
result.splice(1, 0, pos);
}
return result;
}
}
Expand Down Expand Up @@ -925,7 +935,9 @@ export class QrCode {
// Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1.
// For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the unumber8 array [255, 8, 93].
const result: number[] = [];
for (let i = 0; i < degree - 1; i++) result.push(0);
for (let i = 0; i < degree - 1; i++) {
result.push(0);
}
result.push(1); // Start off with the monomial x^0

// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
Expand All @@ -949,15 +961,15 @@ export class QrCode {
private static reedSolomonComputeRemainder(
data: Readonly<number[]>,
divisor: Readonly<number[]>,
): number[] {
) {
const result = divisor.map<number>(() => 0);
for (const b of data) {
// Polynomial division
const factor: number = b ^ (result.shift() as number);
const factor = b ^ result.shift();
result.push(0);
divisor.forEach(
(coef, i) => (result[i] ^= QrCode.reedSolomonMultiply(coef, factor)),
);
divisor.forEach((coef, i) => {
result[i] ^= QrCode.reedSolomonMultiply(coef, factor);
});
}
return result;
}
Expand Down Expand Up @@ -1016,7 +1028,7 @@ export class QrCode {
private finderPenaltyAddHistory(
oriCurrentRunLength: number,
runHistory: number[],
): void {
) {
let currentRunLength = oriCurrentRunLength;
if (runHistory[0] == 0) {
currentRunLength += this.size; // Add light border to initial run
Expand Down
1 change: 1 addition & 0 deletions src/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const generatePath = (modules: Modules, margin: number = 0) => {
});
return ops.join('');
};

/**
* Excavate modules
* @param modules
Expand Down

0 comments on commit b1d7fc5

Please sign in to comment.