@@ -268,6 +268,7 @@ export class Html5Qrcode {
268268 private readonly qrcode : RobustQrcodeDecoderAsync ;
269269
270270 private shouldScan : boolean ;
271+ private flippedScan : boolean ;
271272
272273 // Nullable elements
273274 // TODO(mebjas): Reduce the state-fulness of this mammoth class, by splitting
@@ -280,7 +281,7 @@ export class Html5Qrcode {
280281 private borderShaders : Array < HTMLElement > | null = null ;
281282 private qrMatch : boolean | null = null ;
282283 private renderedCamera : RenderedCamera | null = null ;
283-
284+
284285 private foreverScanTimeout : any ;
285286 private qrRegion : QrcodeRegionBounds | null = null ;
286287 private context : CanvasRenderingContext2D | null = null ;
@@ -308,15 +309,15 @@ export class Html5Qrcode {
308309 *
309310 * TODO(mebjas): Deprecate the verbosity boolean flag completely.
310311 */
311- public constructor ( elementId : string ,
312+ public constructor ( elementId : string ,
312313 configOrVerbosityFlag ?: boolean | Html5QrcodeFullConfig | undefined ) {
313314 if ( ! document . getElementById ( elementId ) ) {
314315 throw `HTML Element with id=${ elementId } not found` ;
315316 }
316317
317318 this . elementId = elementId ;
318319 this . verbose = false ;
319-
320+
320321 let experimentalFeatureConfig : ExperimentalFeaturesConfig | undefined ;
321322 let configObject : Html5QrcodeFullConfig | undefined ;
322323 if ( typeof configOrVerbosityFlag == "boolean" ) {
@@ -326,7 +327,7 @@ export class Html5Qrcode {
326327 this . verbose = configObject . verbose === true ;
327328 experimentalFeatureConfig = configObject . experimentalFeatures ;
328329 }
329-
330+
330331 this . logger = new BaseLoggger ( this . verbose ) ;
331332 this . qrcode = new Html5QrcodeShim (
332333 this . getSupportedFormats ( configOrVerbosityFlag ) ,
@@ -336,6 +337,7 @@ export class Html5Qrcode {
336337
337338 this . foreverScanTimeout ;
338339 this . shouldScan = true ;
340+ this . flippedScan = false ;
339341 this . stateManagerProxy = StateManagerFactory . create ( ) ;
340342 }
341343
@@ -390,7 +392,7 @@ export class Html5Qrcode {
390392 if ( ! internalConfig . isMediaStreamConstraintsValid ( ) ) {
391393 this . logger . logError (
392394 "'videoConstraints' is not valid 'MediaStreamConstraints, "
393- + "it will be ignored.'" ,
395+ + "it will be ignored.'" ,
394396 /* experimental= */ true ) ;
395397 } else {
396398 videoConstraintsAvailableAndValid = true ;
@@ -413,8 +415,8 @@ export class Html5Qrcode {
413415 Html5QrcodeScannerState . SCANNING ) ;
414416 return new Promise ( ( resolve , reject ) => {
415417 const videoConstraints = areVideoConstraintsEnabled
416- ? internalConfig . videoConstraints
417- : $this . createVideoConstraints ( cameraIdOrConfig ) ;
418+ ? internalConfig . videoConstraints
419+ : $this . createVideoConstraints ( cameraIdOrConfig ) ;
418420 if ( ! videoConstraints ) {
419421 toScanningStateChangeTransaction . cancel ( ) ;
420422 reject ( "videoConstraints should be defined" ) ;
@@ -566,7 +568,7 @@ export class Html5Qrcode {
566568 if ( childElement ) {
567569 this . element . removeChild ( childElement ) ;
568570 }
569- } ;
571+ } ;
570572
571573 let $this = this ;
572574 return this . renderedCamera ! . close ( ) . then ( ( ) => {
@@ -637,7 +639,7 @@ export class Html5Qrcode {
637639 : Promise < Html5QrcodeResult > {
638640 if ( ! imageFile || ! ( imageFile instanceof File ) ) {
639641 throw "imageFile argument is mandatory and should be instance "
640- + "of File. Use 'event.target.files[0]'." ;
642+ + "of File. Use 'event.target.files[0]'." ;
641643 }
642644
643645 if ( isNullOrUndefined ( showImage ) ) {
@@ -841,7 +843,7 @@ export class Html5Qrcode {
841843 private getRenderedCameraOrFail ( ) {
842844 if ( this . renderedCamera == null ) {
843845 throw "Scanning is not in running state, call this API only when"
844- + " QR code scanning using camera is in running state." ;
846+ + " QR code scanning using camera is in running state." ;
845847 }
846848 return this . renderedCamera ! ;
847849 }
@@ -882,7 +884,7 @@ export class Html5Qrcode {
882884 Html5QrcodeSupportedFormats . UPC_EAN_EXTENSION ,
883885 ] ;
884886
885- if ( ! configOrVerbosityFlag
887+ if ( ! configOrVerbosityFlag
886888 || typeof configOrVerbosityFlag == "boolean" ) {
887889 return allFormats ;
888890 }
@@ -893,7 +895,7 @@ export class Html5Qrcode {
893895
894896 if ( ! Array . isArray ( configOrVerbosityFlag . formatsToSupport ) ) {
895897 throw "configOrVerbosityFlag.formatsToSupport should be undefined "
896- + "or an array." ;
898+ + "or an array." ;
897899 }
898900
899901 if ( configOrVerbosityFlag . formatsToSupport . length === 0 ) {
@@ -962,7 +964,7 @@ export class Html5Qrcode {
962964 const validateMinSize = ( size : number ) => {
963965 if ( size < Constants . MIN_QR_BOX_SIZE ) {
964966 throw "minimum size of 'config.qrbox' dimension value is"
965- + ` ${ Constants . MIN_QR_BOX_SIZE } px.` ;
967+ + ` ${ Constants . MIN_QR_BOX_SIZE } px.` ;
966968 }
967969 } ;
968970
@@ -1013,7 +1015,7 @@ export class Html5Qrcode {
10131015 // Alternatively, the config is expected to be of type QrDimensions.
10141016 if ( qrboxSize . width === undefined || qrboxSize . height === undefined ) {
10151017 throw "Invalid instance of QrDimensions passed for "
1016- + "'config.qrbox'. Both 'width' and 'height' should be set." ;
1018+ + "'config.qrbox'. Both 'width' and 'height' should be set." ;
10171019 }
10181020 }
10191021
@@ -1058,7 +1060,7 @@ export class Html5Qrcode {
10581060
10591061 // If `qrbox` size is not set, it will default to the dimensions of the
10601062 // viewfinder.
1061- const qrboxSize = isNullOrUndefined ( internalConfig . qrbox ) ?
1063+ const qrboxSize = isNullOrUndefined ( internalConfig . qrbox ) ?
10621064 { width : viewfinderWidth , height : viewfinderHeight } : internalConfig . qrbox ! ;
10631065
10641066 this . validateQrboxConfig ( qrboxSize ) ;
@@ -1068,10 +1070,10 @@ export class Html5Qrcode {
10681070 + "greater than the height of the video stream. Shading will be"
10691071 + " ignored" ) ;
10701072 }
1071-
1073+
10721074 const shouldShadingBeApplied
10731075 = internalConfig . isShadedBoxEnabled ( )
1074- && qrDimensions . height <= viewfinderHeight ;
1076+ && qrDimensions . height <= viewfinderHeight ;
10751077 const defaultQrRegion : QrcodeRegionBounds = {
10761078 x : 0 ,
10771079 y : 0 ,
@@ -1082,7 +1084,7 @@ export class Html5Qrcode {
10821084 const qrRegion = shouldShadingBeApplied
10831085 ? this . getShadedRegionBounds ( viewfinderWidth , viewfinderHeight , qrDimensions )
10841086 : defaultQrRegion ;
1085-
1087+
10861088 const canvasElement = this . createCanvasElement (
10871089 qrRegion . width , qrRegion . height ) ;
10881090 // Tell user agent that this canvas will be read frequently.
@@ -1104,7 +1106,7 @@ export class Html5Qrcode {
11041106 }
11051107
11061108 this . createScannerPausedUiElement ( this . element ! ) ;
1107-
1109+
11081110 // Update local states
11091111 this . qrRegion = qrRegion ;
11101112 this . context = context ;
@@ -1126,38 +1128,38 @@ export class Html5Qrcode {
11261128 rootElement . appendChild ( scannerPausedUiElement ) ;
11271129 this . scannerPausedUiElement = scannerPausedUiElement ;
11281130 }
1129-
1130- /**
1131- * Scans current context using the qrcode library.
1132- *
1133- * <p>This method call would result in callback being triggered by the
1134- * qrcode library. This method also handles the border coloring.
1135- *
1136- * @returns true if scan match is found, false otherwise.
1137- */
1131+
1132+ /**
1133+ * Scans current context using the qrcode library.
1134+ *
1135+ * <p>This method call would result in callback being triggered by the
1136+ * qrcode library. This method also handles the border coloring.
1137+ *
1138+ * @returns true if scan match is found, false otherwise.
1139+ */
11381140 private scanContext (
1139- qrCodeSuccessCallback : QrcodeSuccessCallback ,
1140- qrCodeErrorCallback : QrcodeErrorCallback
1141- ) : Promise < boolean > {
1141+ qrCodeSuccessCallback : QrcodeSuccessCallback ,
1142+ qrCodeErrorCallback : QrcodeErrorCallback
1143+ ) : Promise < boolean > {
11421144 if ( this . stateManagerProxy . isPaused ( ) ) {
11431145 return Promise . resolve ( false ) ;
11441146 }
11451147
11461148 return this . qrcode . decodeAsync ( this . canvasElement ! )
1147- . then ( ( result ) => {
1148- qrCodeSuccessCallback (
1149- result . text ,
1150- Html5QrcodeResultFactory . createFromQrcodeResult (
1151- result ) ) ;
1152- this . possiblyUpdateShaders ( /* qrMatch= */ true ) ;
1153- return true ;
1154- } ) . catch ( ( error ) => {
1155- this . possiblyUpdateShaders ( /* qrMatch= */ false ) ;
1156- let errorMessage = Html5QrcodeStrings . codeParseError ( error ) ;
1157- qrCodeErrorCallback (
1158- errorMessage , Html5QrcodeErrorFactory . createFrom ( errorMessage ) ) ;
1159- return false ;
1160- } ) ;
1149+ . then ( ( result ) => {
1150+ qrCodeSuccessCallback (
1151+ result . text ,
1152+ Html5QrcodeResultFactory . createFromQrcodeResult (
1153+ result ) ) ;
1154+ this . possiblyUpdateShaders ( /* qrMatch= */ true ) ;
1155+ return true ;
1156+ } ) . catch ( ( error ) => {
1157+ this . possiblyUpdateShaders ( /* qrMatch= */ false ) ;
1158+ let errorMessage = Html5QrcodeStrings . codeParseError ( error ) ;
1159+ qrCodeErrorCallback (
1160+ errorMessage , Html5QrcodeErrorFactory . createFrom ( errorMessage ) ) ;
1161+ return false ;
1162+ } ) ;
11611163 }
11621164
11631165 /**
@@ -1217,14 +1219,31 @@ export class Html5Qrcode {
12171219 // TODO(mebjas): Move this logic to decoding library.
12181220 this . scanContext ( qrCodeSuccessCallback , qrCodeErrorCallback )
12191221 . then ( ( isSuccessfull ) => {
1220- // Previous scan failed and disableFlip is off.
1221- if ( ! isSuccessfull && internalConfig . disableFlip !== true ) {
1222- this . context ! . translate ( this . context ! . canvas . width , 0 ) ;
1223- this . context ! . scale ( - 1 , 1 ) ;
1224- this . scanContext ( qrCodeSuccessCallback , qrCodeErrorCallback )
1225- . finally ( ( ) => {
1226- triggerNextScan ( ) ;
1227- } ) ;
1222+ // Previous scan failed
1223+ if ( ! isSuccessfull ) {
1224+ // disableFlip is off and is not flipped
1225+ if ( internalConfig . disableFlip !== true && this . flippedScan === false ) {
1226+ this . context ! . translate ( this . context ! . canvas . width , 0 ) ;
1227+ this . context ! . scale ( - 1 , 1 ) ;
1228+ this . flippedScan = true ;
1229+ } else {
1230+ // disableFlip is off
1231+ if ( internalConfig . disableFlip !== true ) {
1232+ this . context ! . translate ( this . context ! . canvas . width , 0 ) ;
1233+ this . context ! . scale ( - 1 , 1 ) ;
1234+ }
1235+ this . flippedScan = false ;
1236+ }
1237+ // previous scan failed, flipped scan failed, try to invert colors
1238+ if ( this . context ! . filter === 'none' && this . flippedScan === false ) {
1239+ this . context ! . filter = 'invert(1)' ;
1240+ } else {
1241+ // restore initial filter value
1242+ if ( this . context ! . filter === 'invert(1)' && this . flippedScan === false ) {
1243+ this . context ! . filter = 'none' ;
1244+ }
1245+ }
1246+ triggerNextScan ( ) ;
12281247 } else {
12291248 triggerNextScan ( ) ;
12301249 }
@@ -1237,7 +1256,7 @@ export class Html5Qrcode {
12371256
12381257 private createVideoConstraints (
12391258 cameraIdOrConfig : string | MediaTrackConstraints )
1240- : MediaTrackConstraints | undefined {
1259+ : MediaTrackConstraints | undefined {
12411260 if ( typeof cameraIdOrConfig == "string" ) {
12421261 // If it's a string it should be camera device Id.
12431262 return { deviceId : { exact : cameraIdOrConfig } } ;
@@ -1254,20 +1273,20 @@ export class Html5Qrcode {
12541273 } else {
12551274 // Invalid config
12561275 throw "config has invalid 'facingMode' value = "
1257- + `'${ value } '` ;
1276+ + `'${ value } '` ;
12581277 }
12591278 } ;
12601279
12611280 const keys = Object . keys ( cameraIdOrConfig ) ;
12621281 if ( keys . length !== 1 ) {
12631282 throw "'cameraIdOrConfig' object should have exactly 1 key,"
1264- + ` if passed as an object, found ${ keys . length } keys` ;
1283+ + ` if passed as an object, found ${ keys . length } keys` ;
12651284 }
12661285
12671286 const key :string = Object . keys ( cameraIdOrConfig ) [ 0 ] ;
12681287 if ( key !== facingModeKey && key !== deviceIdKey ) {
12691288 throw `Only '${ facingModeKey } ' and '${ deviceIdKey } ' `
1270- + " are supported for 'cameraIdOrConfig'" ;
1289+ + " are supported for 'cameraIdOrConfig'" ;
12711290 }
12721291
12731292 if ( key === facingModeKey ) {
@@ -1286,15 +1305,15 @@ export class Html5Qrcode {
12861305 } else if ( typeof facingMode == "object" ) {
12871306 if ( exactKey in facingMode ) {
12881307 if ( isValidFacingModeValue ( facingMode [ `${ exactKey } ` ] ) ) {
1289- return {
1290- facingMode : {
1291- exact : facingMode [ `${ exactKey } ` ]
1292- }
1293- } ;
1308+ return {
1309+ facingMode : {
1310+ exact : facingMode [ `${ exactKey } ` ]
1311+ }
1312+ } ;
12941313 }
12951314 } else {
12961315 throw "'facingMode' should be string or object with"
1297- + ` ${ exactKey } as key.` ;
1316+ + ` ${ exactKey } as key.` ;
12981317 }
12991318 } else {
13001319 const type = ( typeof facingMode ) ;
@@ -1316,7 +1335,7 @@ export class Html5Qrcode {
13161335 } ;
13171336 } else {
13181337 throw "'deviceId' should be string or object with"
1319- + ` ${ exactKey } as key.` ;
1338+ + ` ${ exactKey } as key.` ;
13201339 }
13211340 } else {
13221341 const type = ( typeof deviceId ) ;
@@ -1443,7 +1462,7 @@ export class Html5Qrcode {
14431462 height : number ,
14441463 qrboxSize : QrDimensions ) {
14451464 if ( ( width - qrboxSize . width ) < 1 || ( height - qrboxSize . height ) < 1 ) {
1446- return ;
1465+ return ;
14471466 }
14481467 const shadingElement = document . createElement ( "div" ) ;
14491468 shadingElement . style . position = "absolute" ;
@@ -1465,18 +1484,18 @@ export class Html5Qrcode {
14651484 shadingElement . style . left = "0px" ;
14661485 shadingElement . style . right = "0px" ;
14671486 shadingElement . id = `${ Constants . SHADED_REGION_ELEMENT_ID } ` ;
1468-
1487+
14691488 // Check if div is too small for shadows. As there are two 5px width
14701489 // borders the needs to have a size above 10px.
1471- if ( ( width - qrboxSize . width ) < 11
1490+ if ( ( width - qrboxSize . width ) < 11
14721491 || ( height - qrboxSize . height ) < 11 ) {
1473- this . hasBorderShaders = false ;
1492+ this . hasBorderShaders = false ;
14741493 } else {
14751494 const smallSize = 5 ;
14761495 const largeSize = 40 ;
14771496 this . insertShaderBorders (
14781497 shadingElement ,
1479- /* width= */ largeSize ,
1498+ /* width= */ largeSize ,
14801499 /* height= */ smallSize ,
14811500 /* top= */ - smallSize ,
14821501 /* bottom= */ null ,
@@ -1563,12 +1582,12 @@ export class Html5Qrcode {
15631582 elem . style . bottom = `${ bottom } px` ;
15641583 }
15651584 if ( isLeft ) {
1566- elem . style . left = `${ side } px` ;
1585+ elem . style . left = `${ side } px` ;
15671586 } else {
1568- elem . style . right = `${ side } px` ;
1587+ elem . style . right = `${ side } px` ;
15691588 }
15701589 if ( ! this . borderShaders ) {
1571- this . borderShaders = [ ] ;
1590+ this . borderShaders = [ ] ;
15721591 }
15731592 this . borderShaders . push ( elem ) ;
15741593 shaderElem . appendChild ( elem ) ;
0 commit comments