@@ -157,13 +157,38 @@ export interface Coder<F, T> {
157157 decode ( to : T ) : F ;
158158}
159159
160+ export function anumber ( n : number , title : string ) : number {
161+ if ( typeof n !== 'number' )
162+ throw new TypeError ( `"${ title } " expected number, got type=${ typeof n } ` ) ;
163+ return n ;
164+ }
165+
166+ export function asafenumber ( n : number , title : string ) : number {
167+ anumber ( n , title ) ;
168+ if ( ! Number . isSafeInteger ( n ) )
169+ throw new RangeError ( `"${ title } " expected safe integer, got ${ n } ` ) ;
170+ return n ;
171+ }
172+
173+ export function astring ( s : string , title : string ) : string {
174+ if ( typeof s !== 'string' )
175+ throw new TypeError ( `"${ title } " expected string, got type=${ typeof s } ` ) ;
176+ return s ;
177+ }
178+
179+ export function validateObject < T > ( object : TArg < T > , title : string ) : T {
180+ if ( object === null || typeof object !== 'object' || Array . isArray ( object ) )
181+ throw new TypeError ( `"${ title } " expected object, got type=${ typeof object } ` ) ;
182+ return object as T ;
183+ }
184+
160185function assertNumber ( n : number ) {
161186 if ( ! Number . isSafeInteger ( n ) ) throw new Error ( `integer expected: ${ n } ` ) ;
162187}
163188
164189function validateVersion ( ver : Version ) : void {
165- if ( ! Number . isSafeInteger ( ver ) || ver < 1 || ver > 40 )
166- throw new Error ( `Invalid version=${ ver } . Expected number [1..40]` ) ;
190+ asafenumber ( ver , ' ver' ) ;
191+ if ( ver < 1 || ver > 40 ) throw new RangeError ( `Invalid version=${ ver } . Expected number [1..40]` ) ;
167192}
168193
169194function bin ( dec : number , pad : number ) : string {
@@ -348,10 +373,13 @@ type ReadFn = (c: Point, curr: DrawValue) => void;
348373export class Bitmap {
349374 private static size ( size : Size | number , limit ?: Size ) {
350375 if ( typeof size === 'number' ) size = { height : size , width : size } ;
376+ size = validateObject ( size , 'size' ) ;
377+ anumber ( size . height , 'size.height' ) ;
378+ anumber ( size . width , 'size.width' ) ;
351379 if ( ! Number . isSafeInteger ( size . height ) && size . height !== Infinity )
352- throw new Error ( `Bitmap: invalid height=${ size . height } (${ typeof size . height } )` ) ;
380+ throw new RangeError ( `Bitmap: invalid height=${ size . height } (${ typeof size . height } )` ) ;
353381 if ( ! Number . isSafeInteger ( size . width ) && size . width !== Infinity )
354- throw new Error ( `Bitmap: invalid width=${ size . width } (${ typeof size . width } )` ) ;
382+ throw new RangeError ( `Bitmap: invalid width=${ size . width } (${ typeof size . width } )` ) ;
355383 if ( limit !== undefined ) {
356384 // Clamp length, so it won't overflow, also allows to use Infinity, so we draw until end
357385 size = {
@@ -361,7 +389,15 @@ export class Bitmap {
361389 }
362390 return size ;
363391 }
392+ private static point ( p : Point , title : string ) : Point {
393+ p = validateObject ( p , title ) ;
394+ if ( ! ( 'x' in p ) || ! ( 'y' in p ) ) throw new TypeError ( `Bitmap: "${ title } " expected { x, y }` ) ;
395+ asafenumber ( p . x , title + '.x' ) ;
396+ asafenumber ( p . y , title + '.y' ) ;
397+ return p ;
398+ }
364399 static fromString ( s : string ) : Bitmap {
400+ astring ( s , 's' ) ;
365401 // Remove linebreaks on start and end, so we draw in `` section
366402 // Fixture strings use LF-delimited rows of X / space / ? characters; callers
367403 // must normalize CRLF input before handing it to this debug parser.
@@ -413,7 +449,9 @@ export class Bitmap {
413449 this . fullWords = Math . floor ( width / 32 ) | 0 ;
414450 this . value = new Uint32Array ( this . words * height ) ;
415451 this . defined = new Uint32Array ( this . value . length ) ;
416- if ( data ) {
452+ if ( data !== undefined ) {
453+ if ( ! Array . isArray ( data ) )
454+ throw new TypeError ( `"data" expected array, got type=${ typeof data } ` ) ;
417455 // accept same semantics as old version
418456 if ( data . length !== height )
419457 throw new Error ( `Bitmap: data height mismatch: exp=${ height } got=${ data . length } ` ) ;
@@ -426,6 +464,7 @@ export class Bitmap {
426464 }
427465 }
428466 point ( p : Point ) : DrawValue {
467+ p = Bitmap . point ( p , 'p' ) ;
429468 // The storage docs above say "undefined is used as a marker whether cell
430469 // was written or not"; `point()` is the detector's dark-module read and
431470 // intentionally treats both undefined and false as not-dark. Use
@@ -434,17 +473,17 @@ export class Bitmap {
434473 }
435474 // Raw bounds check for scan loops; unlike `xy()`, this does not wrap or normalize coordinates.
436475 isInside ( p : Point ) : boolean {
476+ p = Bitmap . point ( p , 'p' ) ;
437477 return 0 <= p . x && p . x < this . width && 0 <= p . y && p . y < this . height ;
438478 }
439479 size ( offset ?: Point | number ) : { height : number ; width : number } {
440480 if ( ! offset ) return { height : this . height , width : this . width } ;
441- const { x, y } = this . xy ( offset ) ;
481+ const { x, y } = this . xy ( offset , 'offset' ) ;
442482 return { height : this . height - y , width : this . width - x } ;
443483 }
444- private xy ( c : Point | number ) {
484+ private xy ( c : Point | number , title = 'c' ) {
445485 if ( typeof c === 'number' ) c = { x : c , y : c } ;
446- if ( ! Number . isSafeInteger ( c . x ) ) throw new Error ( `Bitmap: invalid x=${ c . x } ` ) ;
447- if ( ! Number . isSafeInteger ( c . y ) ) throw new Error ( `Bitmap: invalid y=${ c . y } ` ) ;
486+ c = Bitmap . point ( c , title ) ;
448487 // Bitmap's class docs say "For most `draw` calls, structure is mutable";
449488 // coordinate objects follow that hot-path policy too and are normalized in place.
450489 c . x = mod ( c . x , this . width ) ;
@@ -586,8 +625,8 @@ export class Bitmap {
586625 border ( border = 2 , value : DrawValue ) : Bitmap {
587626 // `border` is used both as output-size delta and as embed coordinate; keep
588627 // it a positive safe integer before those paths allocate or normalize.
589- if ( ! Number . isSafeInteger ( border ) || border <= 0 )
590- throw new Error ( `Bitmap.border: invalid size=${ border } ` ) ;
628+ asafenumber ( border , ' border' ) ;
629+ if ( border <= 0 ) throw new RangeError ( `Bitmap.border: invalid size=${ border } ` ) ;
591630 const height = this . height + 2 * border ;
592631 const width = this . width + 2 * border ;
593632 const out = new Bitmap ( { height, width } ) ;
@@ -687,8 +726,8 @@ export class Bitmap {
687726 }
688727 // Each pixel size is multiplied by factor
689728 scale ( factor : number ) : Bitmap {
690- if ( ! Number . isSafeInteger ( factor ) || factor > 1024 )
691- throw new Error ( `invalid scale factor: ${ factor } ` ) ;
729+ asafenumber ( factor , ' factor' ) ;
730+ if ( factor <= 0 || factor > 1024 ) throw new RangeError ( `invalid scale factor: ${ factor } ` ) ;
692731 const { height, width } = this ;
693732 // Bitmap storage docs say "undefined is used as a marker whether cell was
694733 // written or not"; `scale()` is an output materialization path and samples
@@ -722,11 +761,12 @@ export class Bitmap {
722761 countPatternInRow ( y : number , patternLen : number , ...patterns : number [ ] ) : number {
723762 // Penalty scanning only passes Table 11 windows over bounded symbol rows;
724763 // validate this public helper before JS shifts / typed-array reads coerce bad inputs.
725- if ( ! Number . isSafeInteger ( patternLen ) || patternLen <= 0 || patternLen >= 32 )
726- throw new Error ( 'wrong patternLen' ) ;
764+ asafenumber ( y , 'y' ) ;
765+ asafenumber ( patternLen , 'patternLen' ) ;
766+ if ( patternLen <= 0 || patternLen >= 32 ) throw new RangeError ( 'wrong patternLen' ) ;
727767 const mask = ( 1 << patternLen ) - 1 ;
728768 const { height, width, value, words } = this ;
729- if ( ! Number . isSafeInteger ( y ) || y < 0 || y >= height ) return 0 ;
769+ if ( y < 0 || y >= height ) return 0 ;
730770 let count = 0 ;
731771 const rowBase = this . wordIndex ( 0 , y ) ;
732772 for ( let i = 0 , window = 0 ; i < words ; i ++ ) {
@@ -750,7 +790,8 @@ export class Bitmap {
750790 // ISO/IEC 18004:2024 §7.8.3.1 N1 scans adjacent modules in bounded rows
751791 // and columns; validate this public helper before missing typed-array rows
752792 // are coerced into all-light runs by bitwise operators.
753- if ( ! Number . isSafeInteger ( y ) || y < 0 || y >= height ) return ;
793+ asafenumber ( y , 'y' ) ;
794+ if ( y < 0 || y >= height ) return ;
754795 let runLen = 0 ;
755796 let runValue : boolean | undefined ;
756797 const rowBase = this . wordIndex ( 0 , y ) ;
@@ -785,7 +826,8 @@ export class Bitmap {
785826 const { height, width, words } = this ;
786827 // ISO/IEC 18004:2024 §7.8.3.1 N2 counts 2 x 2 module blocks in bounded
787828 // rows; reject non-integer scan rows before bitwise coercions truncate them.
788- if ( width < 2 || ! Number . isSafeInteger ( y ) || y < 0 || y + 1 >= height ) return 0 ;
829+ asafenumber ( y , 'y' ) ;
830+ if ( width < 2 || y < 0 || y + 1 >= height ) return 0 ;
789831 const base0 = this . wordIndex ( 0 , y ) ;
790832 const base1 = this . wordIndex ( 0 , y + 1 ) ;
791833 // valid "left-edge" positions x in [0 .. W-2]
@@ -1005,7 +1047,10 @@ const ECC_BLOCKS = {
10051047// ISO/IEC 18004:2024 sections 5.3/7.4/7.5/7.9/7.10: QR layout, segment, format/version, and capacity helpers.
10061048const info = /* @__PURE__ */ Object . freeze ( {
10071049 size : /* @__PURE__ */ Object . freeze ( {
1008- encode : ( ver : Version ) => 21 + 4 * ( ver - 1 ) , // ver1 = 21, ver40 = 177 modules per side
1050+ encode : ( ver : Version ) => {
1051+ validateVersion ( ver ) ;
1052+ return 21 + 4 * ( ver - 1 ) ; // ver1 = 21, ver40 = 177 modules per side
1053+ } ,
10091054 decode : ( size : number ) => ( size - 17 ) / 4 ,
10101055 } as Coder < Version , number > ) ,
10111056 // ISO/IEC 18004:2024 Table 3: map version ranges 1-9, 10-26, and 27-40 to count-width indexes.
@@ -1519,7 +1564,7 @@ declare const TextEncoder: any;
15191564// non-default initial ECI data starts with an ECI header. Keep UTF-8 bytes
15201565// without that header for compatibility with existing emoji/qrcode fixtures.
15211566export function utf8ToBytes ( str : string ) : TRet < Uint8Array > {
1522- if ( typeof str !== 'string' ) throw new Error ( `utf8ToBytes expected string, got ${ typeof str } ` ) ;
1567+ astring ( str , ' str' ) ;
15231568 return new Uint8Array ( new TextEncoder ( ) . encode ( str ) ) as TRet < Uint8Array > ; // https://bugzil.la/1681809
15241569}
15251570
@@ -1756,7 +1801,10 @@ export function encodeQR(
17561801 output : Output = 'raw' ,
17571802 opts : TArg < QrOpts & SvgQrOpts > = { }
17581803) {
1759- const _opts = opts as QrOpts & SvgQrOpts ;
1804+ // Public guards run before QR mode detection and option reads so bad inputs keep argument names.
1805+ astring ( text , 'text' ) ;
1806+ astring ( output , 'output' ) ;
1807+ const _opts = validateObject ( opts , 'opts' ) as QrOpts & SvgQrOpts ;
17601808 const ecc = _opts . ecc !== undefined ? _opts . ecc : 'medium' ;
17611809 validateECC ( ecc ) ;
17621810 const encoding = _opts . encoding !== undefined ? _opts . encoding : detectType ( text ) ;
@@ -1789,7 +1837,8 @@ export function encodeQR(
17891837 // 2-module default to avoid changing encoder output; callers that need a
17901838 // standards-conformant quiet zone must pass `border: 4` explicitly.
17911839 const border = _opts . border === undefined ? 2 : _opts . border ;
1792- if ( ! Number . isSafeInteger ( border ) || border <= 0 ) throw new Error ( `invalid border=${ border } ` ) ;
1840+ asafenumber ( border , 'opts.border' ) ;
1841+ if ( border <= 0 ) throw new RangeError ( `invalid border=${ border } ` ) ;
17931842 res = res . border ( border , false ) ; // Add border
17941843 if ( _opts . scale !== undefined ) res = res . scale ( _opts . scale ) ; // Scale image
17951844 if ( output === 'raw' ) return res . toRaw ( ) ;
0 commit comments