Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: code optimization #123

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading