diff --git a/src/hooks/useQRCode.tsx b/src/hooks/useQRCode.tsx index a5c5fa6..3af1a32 100644 --- a/src/hooks/useQRCode.tsx +++ b/src/hooks/useQRCode.tsx @@ -3,15 +3,7 @@ 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; @@ -19,31 +11,35 @@ export const useQRCode = ({ marginSize?: number; imageSettings?: ImageSettings; size: number; -}) => { - const qrcode = React.useMemo(() => { +} + +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]); }; diff --git a/src/interface.ts b/src/interface.ts index b08ba92..e531041 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -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; @@ -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; }; diff --git a/src/libs/qrcodegen.ts b/src/libs/qrcodegen.ts index 78c0cf6..864f0f0 100644 --- a/src/libs/qrcodegen.ts +++ b/src/libs/qrcodegen.ts @@ -371,8 +371,9 @@ export class QrCode { if ( boostEcl && dataUsedBits <= QrCode.getNumDataCodewords(version, newEcl) * 8 - ) + ) { ecl = newEcl; + } } // Concatenate all segments to create the data bit string @@ -380,13 +381,14 @@ export class QrCode { 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); @@ -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); @@ -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 @@ -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); @@ -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 } @@ -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);