Skip to content

Commit

Permalink
chore: code optimization (#123)
Browse files Browse the repository at this point in the history
  • Loading branch information
li-jia-nan authored Jan 8, 2025
1 parent b7a77b5 commit fd675ed
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 50 deletions.
60 changes: 28 additions & 32 deletions src/hooks/useQRCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,43 @@ import type { ErrorCorrectionLevel, ImageSettings } from '../interface';
import { ERROR_LEVEL_MAP, getImageSettings, getMarginSize } from '../utils';
import React from 'react';

export const useQRCode = ({
value,
level,
minVersion,
includeMargin,
marginSize,
imageSettings,
size,
}: {
interface Options {
value: string;
level: ErrorCorrectionLevel;
minVersion: number;
includeMargin: boolean;
marginSize?: number;
imageSettings?: ImageSettings;
size: number;
}) => {
const qrcode = React.useMemo<QrCode>(() => {
}

export const useQRCode = (opt: Options) => {
const {
value,
level,
minVersion,
includeMargin,
marginSize,
imageSettings,
size,
} = opt;

const memoizedQrcode = React.useMemo(() => {
const segments = QrSegment.makeSegments(value);
return QrCode.encodeSegments(segments, ERROR_LEVEL_MAP[level], minVersion);
}, [value, level, minVersion]);

const { cells, margin, numCells, calculatedImageSettings } =
React.useMemo(() => {
const cs = qrcode.getModules();
const mg = getMarginSize(includeMargin, marginSize);
const ncs = cs.length + mg * 2;
const cis = getImageSettings(cs, size, mg, imageSettings);
return {
cells: cs,
margin: mg,
numCells: ncs,
calculatedImageSettings: cis,
};
}, [qrcode, size, imageSettings, includeMargin, marginSize]);

return {
qrcode,
margin,
cells,
numCells,
calculatedImageSettings,
};
return React.useMemo(() => {
const cs = memoizedQrcode.getModules();
const mg = getMarginSize(includeMargin, marginSize);
const ncs = cs.length + mg * 2;
const cis = getImageSettings(cs, size, mg, imageSettings);
return {
cells: cs,
margin: mg,
numCells: ncs,
calculatedImageSettings: cis,
qrcode: memoizedQrcode,
};
}, [memoizedQrcode, size, imageSettings, includeMargin, marginSize]);
};
89 changes: 87 additions & 2 deletions src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CSSProperties } from 'react';
import type React from 'react';
import type { Ecc, QrCode } from './libs/qrcodegen';

export type Modules = ReturnType<QrCode['getModules']>;
Expand All @@ -11,27 +11,112 @@ export type ERROR_LEVEL_MAPPED_TYPE = {
};

export type ImageSettings = {
/**
* The URI of the embedded image.
*/
src: string;
/**
* The height, in pixels, of the image.
*/
height: number;
/**
* The width, in pixels, of the image.
*/
width: number;
/**
* Whether or not to "excavate" the modules around the embedded image. This
* means that any modules the embedded image overlaps will use the background
* color.
*/
excavate: boolean;
/**
* The horiztonal offset of the embedded image, starting from the top left corner.
* Will center if not specified.
*/
x?: number;
/**
* The vertical offset of the embedded image, starting from the top left corner.
* Will center if not specified.
*/
y?: number;
/**
* The opacity of the embedded image in the range of 0-1.
* @defaultValue 1
*/
opacity?: number;
/**
* The cross-origin value to use when loading the image. This is used to
* ensure compatibility with CORS, particularly when extracting image data
* from QRCodeCanvas.
* Note: `undefined` is treated differently than the seemingly equivalent
* empty string. This is intended to align with HTML behavior where omitting
* the attribute behaves differently than the empty string.
*/
crossOrigin?: CrossOrigin;
};

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;
/**
* The size, in pixels, to render the QR Code.
* @defaultValue 128
*/
size?: number;
/**
* The Error Correction Level to use.
* @see https://www.qrcode.com/en/about/error_correction.html
* @defaultValue L
*/
level?: ErrorCorrectionLevel;
/**
* The background color used to render the QR Code.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
* @defaultValue #FFFFFF
*/
bgColor?: string;
/**
* The foregtound color used to render the QR Code.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
* @defaultValue #000000
*/
fgColor?: string;
style?: CSSProperties;
/**
* The style to apply to the QR Code.
*/
style?: React.CSSProperties;
/**
* Whether or not a margin of 4 modules should be rendered as a part of the
* QR Code.
* @deprecated Use `marginSize` instead.
* @defaultValue false
*/
includeMargin?: boolean;
/**
* The number of _modules_ to use for margin. The QR Code specification
* requires `4`, however you can specify any number. Values will be turned to
* integers with `Math.floor`. Overrides `includeMargin` when both are specified.
* @defaultValue 0
*/
marginSize?: number;
/**
* The settings for the embedded image.
*/
imageSettings?: ImageSettings;
/**
* The title to assign to the QR Code. Used for accessibility reasons.
*/
title?: string;
/**
* The minimum version used when encoding the QR Code. Valid values are 1-40
* with higher values resulting in more complex QR Codes. The optimal
* (lowest) version is determined for the `value` provided, using `minVersion`
* as the lower bound.
* @defaultValue 1
*/
minVersion?: number;
};

Expand Down
43 changes: 27 additions & 16 deletions src/libs/qrcodegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,22 +371,24 @@ export class QrCode {
if (
boostEcl &&
dataUsedBits <= QrCode.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.modeBits, 4, bb);
appendBits(seg.numChars, seg.mode.numCharCountBits(version), bb);
for (const b of seg.getData()) bb.push(b);
for (const b of seg.getData()) {
bb.push(b);
}
}
assert(bb.length == dataUsedBits);

// Add terminator and pad up to a byte if applicable
const dataCapacityBits: number =
QrCode.getNumDataCodewords(version, ecl) * 8;
const dataCapacityBits = QrCode.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);
Expand All @@ -397,15 +399,16 @@ export class QrCode {
let padByte = 0xec;
bb.length < dataCapacityBits;
padByte ^= 0xec ^ 0x11
)
) {
appendBits(padByte, 8, bb);
}

// Pack bits numbero bytes in big endian
const dataCodewords: number[] = [];
while (dataCodewords.length * 8 < bb.length) dataCodewords.push(0);
bb.forEach(
(b: number, i: number) => (dataCodewords[i >>> 3] |= b << (7 - (i & 7))),
);
while (dataCodewords.length * 8 < bb.length) {
dataCodewords.push(0);
}
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 @@ -459,7 +462,9 @@ export class QrCode {
// Check scalar arguments
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');
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
Expand Down Expand Up @@ -558,7 +563,9 @@ export class QrCode {
// Calculate error correction code and pack bits
const data: number = (this.errorCorrectionLevel.formatBits << 3) | mask; // errCorrLvl is unumber2, mask is unumber3
let rem: number = data;
for (let i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
for (let i = 0; i < 10; i++) {
rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
}
const bits = ((data << 10) | rem) ^ 0x5412; // unumber15
assert(bits >>> 15 == 0);

Expand All @@ -567,14 +574,16 @@ export class QrCode {
this.setFunctionModule(8, 7, getBit(bits, 6));
this.setFunctionModule(8, 8, getBit(bits, 7));
this.setFunctionModule(7, 8, getBit(bits, 8));
for (let i = 9; i < 15; i++)
for (let i = 9; i < 15; i++) {
this.setFunctionModule(14 - i, 8, getBit(bits, i));

}
// Draw second copy
for (let i = 0; i < 8; i++)
for (let i = 0; i < 8; i++) {
this.setFunctionModule(this.size - 1 - i, 8, getBit(bits, i));
for (let i = 8; i < 15; i++)
}
for (let i = 8; i < 15; i++) {
this.setFunctionModule(8, this.size - 15 + i, getBit(bits, i));
}
this.setFunctionModule(8, this.size - 8, true); // Always dark
}

Expand All @@ -587,7 +596,9 @@ export class QrCode {

// Calculate error correction code and pack bits
let rem: number = this.version; // version is unumber6, in the range [7, 40]
for (let i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25);
for (let i = 0; i < 12; i++) {
rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25);
}
const bits: number = (this.version << 12) | rem; // unumber18
assert(bits >>> 18 == 0);

Expand Down

0 comments on commit fd675ed

Please sign in to comment.