import { BaseLoggger, Html5QrcodeResultFactory, Html5QrcodeErrorFactory, Html5QrcodeSupportedFormats, isValidHtml5QrcodeSupportedFormats, Html5QrcodeConstants, isNullOrUndefined } from "./core"; import { Html5QrcodeStrings } from "./strings"; import { VideoConstraintsUtil } from "./utils"; import { Html5QrcodeShim } from "./code-decoder"; import { CameraFactory } from "./camera/factories"; import { CameraRetriever } from "./camera/retriever"; import { StateManagerFactory, Html5QrcodeScannerState } from "./state-manager"; class Constants extends Html5QrcodeConstants { } Constants.DEFAULT_WIDTH = 300; Constants.DEFAULT_WIDTH_OFFSET = 2; Constants.FILE_SCAN_MIN_HEIGHT = 300; Constants.FILE_SCAN_HIDDEN_CANVAS_PADDING = 100; Constants.MIN_QR_BOX_SIZE = 50; Constants.SHADED_LEFT = 1; Constants.SHADED_RIGHT = 2; Constants.SHADED_TOP = 3; Constants.SHADED_BOTTOM = 4; Constants.SHADED_REGION_ELEMENT_ID = "qr-shaded-region"; Constants.VERBOSE = false; Constants.BORDER_SHADER_DEFAULT_COLOR = "#ffffff"; Constants.BORDER_SHADER_MATCH_COLOR = "rgb(90, 193, 56)"; class InternalHtml5QrcodeConfig { constructor(config, logger) { this.logger = logger; this.fps = Constants.SCAN_DEFAULT_FPS; if (!config) { this.disableFlip = Constants.DEFAULT_DISABLE_FLIP; } else { if (config.fps) { this.fps = config.fps; } this.disableFlip = config.disableFlip === true; this.qrbox = config.qrbox; this.aspectRatio = config.aspectRatio; this.videoConstraints = config.videoConstraints; } } isMediaStreamConstraintsValid() { if (!this.videoConstraints) { this.logger.logError("Empty videoConstraints", true); return false; } return VideoConstraintsUtil.isMediaStreamConstraintsValid(this.videoConstraints, this.logger); } isShadedBoxEnabled() { return !isNullOrUndefined(this.qrbox); } static create(config, logger) { return new InternalHtml5QrcodeConfig(config, logger); } } export class Html5Qrcode { constructor(elementId, configOrVerbosityFlag) { this.element = null; this.canvasElement = null; this.scannerPausedUiElement = null; this.hasBorderShaders = null; this.borderShaders = null; this.qrMatch = null; this.renderedCamera = null; this.qrRegion = null; this.context = null; this.lastScanImageFile = null; this.isScanning = false; if (!document.getElementById(elementId)) { throw `HTML Element with id=${elementId} not found`; } this.elementId = elementId; this.verbose = false; let experimentalFeatureConfig; let configObject; if (typeof configOrVerbosityFlag == "boolean") { this.verbose = configOrVerbosityFlag === true; } else if (configOrVerbosityFlag) { configObject = configOrVerbosityFlag; this.verbose = configObject.verbose === true; experimentalFeatureConfig = configObject.experimentalFeatures; } this.logger = new BaseLoggger(this.verbose); this.qrcode = new Html5QrcodeShim(this.getSupportedFormats(configOrVerbosityFlag), this.getUseBarCodeDetectorIfSupported(configObject), this.verbose, this.logger); this.foreverScanTimeout; this.shouldScan = true; this.stateManagerProxy = StateManagerFactory.create(); } start(cameraIdOrConfig, configuration, qrCodeSuccessCallback, qrCodeErrorCallback) { if (!cameraIdOrConfig) { throw "cameraIdOrConfig is required"; } if (!qrCodeSuccessCallback || typeof qrCodeSuccessCallback != "function") { throw "qrCodeSuccessCallback is required and should be a function."; } let qrCodeErrorCallbackInternal; if (qrCodeErrorCallback) { qrCodeErrorCallbackInternal = qrCodeErrorCallback; } else { qrCodeErrorCallbackInternal = this.verbose ? this.logger.log : () => { }; } const internalConfig = InternalHtml5QrcodeConfig.create(configuration, this.logger); this.clearElement(); let videoConstraintsAvailableAndValid = false; if (internalConfig.videoConstraints) { if (!internalConfig.isMediaStreamConstraintsValid()) { this.logger.logError("'videoConstraints' is not valid 'MediaStreamConstraints, " + "it will be ignored.'", true); } else { videoConstraintsAvailableAndValid = true; } } const areVideoConstraintsEnabled = videoConstraintsAvailableAndValid; const element = document.getElementById(this.elementId); const rootElementWidth = element.clientWidth ? element.clientWidth : Constants.DEFAULT_WIDTH; element.style.position = "relative"; this.shouldScan = true; this.element = element; const $this = this; const toScanningStateChangeTransaction = this.stateManagerProxy.startTransition(Html5QrcodeScannerState.SCANNING); return new Promise((resolve, reject) => { const videoConstraints = areVideoConstraintsEnabled ? internalConfig.videoConstraints : $this.createVideoConstraints(cameraIdOrConfig); if (!videoConstraints) { toScanningStateChangeTransaction.cancel(); reject("videoConstraints should be defined"); return; } let cameraRenderingOptions = {}; if (!areVideoConstraintsEnabled || internalConfig.aspectRatio) { cameraRenderingOptions.aspectRatio = internalConfig.aspectRatio; } let renderingCallbacks = { onRenderSurfaceReady: (viewfinderWidth, viewfinderHeight) => { $this.setupUi(viewfinderWidth, viewfinderHeight, internalConfig); $this.isScanning = true; $this.foreverScan(internalConfig, qrCodeSuccessCallback, qrCodeErrorCallbackInternal); } }; CameraFactory.failIfNotSupported().then((factory) => { factory.create(videoConstraints).then((camera) => { return camera.render(this.element, cameraRenderingOptions, renderingCallbacks) .then((renderedCamera) => { $this.renderedCamera = renderedCamera; toScanningStateChangeTransaction.execute(); resolve(null); }) .catch((error) => { toScanningStateChangeTransaction.cancel(); reject(error); }); }).catch((error) => { toScanningStateChangeTransaction.cancel(); reject(Html5QrcodeStrings.errorGettingUserMedia(error)); }); }).catch((_) => { toScanningStateChangeTransaction.cancel(); reject(Html5QrcodeStrings.cameraStreamingNotSupported()); }); }); } pause(shouldPauseVideo) { if (!this.stateManagerProxy.isStrictlyScanning()) { throw "Cannot pause, scanner is not scanning."; } this.stateManagerProxy.directTransition(Html5QrcodeScannerState.PAUSED); this.showPausedState(); if (isNullOrUndefined(shouldPauseVideo) || shouldPauseVideo !== true) { shouldPauseVideo = false; } if (shouldPauseVideo && this.renderedCamera) { this.renderedCamera.pause(); } } resume() { if (!this.stateManagerProxy.isPaused()) { throw "Cannot result, scanner is not paused."; } if (!this.renderedCamera) { throw "renderedCamera doesn't exist while trying resume()"; } const $this = this; const transitionToScanning = () => { $this.stateManagerProxy.directTransition(Html5QrcodeScannerState.SCANNING); $this.hidePausedState(); }; if (!this.renderedCamera.isPaused()) { transitionToScanning(); return; } this.renderedCamera.resume(() => { transitionToScanning(); }); } getState() { return this.stateManagerProxy.getState(); } stop() { if (!this.stateManagerProxy.isScanning()) { throw "Cannot stop, scanner is not running or paused."; } const toStoppedStateTransaction = this.stateManagerProxy.startTransition(Html5QrcodeScannerState.NOT_STARTED); this.shouldScan = false; if (this.foreverScanTimeout) { clearTimeout(this.foreverScanTimeout); } const removeQrRegion = () => { if (!this.element) { return; } let childElement = document.getElementById(Constants.SHADED_REGION_ELEMENT_ID); if (childElement) { this.element.removeChild(childElement); } }; let $this = this; return this.renderedCamera.close().then(() => { $this.renderedCamera = null; if ($this.element) { $this.element.removeChild($this.canvasElement); $this.canvasElement = null; } removeQrRegion(); if ($this.qrRegion) { $this.qrRegion = null; } if ($this.context) { $this.context = null; } toStoppedStateTransaction.execute(); $this.hidePausedState(); $this.isScanning = false; return Promise.resolve(); }); } scanFile(imageFile, showImage) { return this.scanFileV2(imageFile, showImage) .then((html5qrcodeResult) => html5qrcodeResult.decodedText); } scanFileV2(imageFile, showImage) { if (!imageFile || !(imageFile instanceof File)) { throw "imageFile argument is mandatory and should be instance " + "of File. Use 'event.target.files[0]'."; } if (isNullOrUndefined(showImage)) { showImage = true; } if (!this.stateManagerProxy.canScanFile()) { throw "Cannot start file scan - ongoing camera scan"; } return new Promise((resolve, reject) => { this.possiblyCloseLastScanImageFile(); this.clearElement(); this.lastScanImageFile = URL.createObjectURL(imageFile); const inputImage = new Image; inputImage.onload = () => { const imageWidth = inputImage.width; const imageHeight = inputImage.height; const element = document.getElementById(this.elementId); const containerWidth = element.clientWidth ? element.clientWidth : Constants.DEFAULT_WIDTH; const containerHeight = Math.max(element.clientHeight ? element.clientHeight : imageHeight, Constants.FILE_SCAN_MIN_HEIGHT); const config = this.computeCanvasDrawConfig(imageWidth, imageHeight, containerWidth, containerHeight); if (showImage) { const visibleCanvas = this.createCanvasElement(containerWidth, containerHeight, "qr-canvas-visible"); visibleCanvas.style.display = "inline-block"; element.appendChild(visibleCanvas); const context = visibleCanvas.getContext("2d"); if (!context) { throw "Unable to get 2d context from canvas"; } context.canvas.width = containerWidth; context.canvas.height = containerHeight; context.drawImage(inputImage, 0, 0, imageWidth, imageHeight, config.x, config.y, config.width, config.height); } let padding = Constants.FILE_SCAN_HIDDEN_CANVAS_PADDING; let hiddenImageWidth = Math.max(inputImage.width, config.width); let hiddenImageHeight = Math.max(inputImage.height, config.height); let hiddenCanvasWidth = hiddenImageWidth + 2 * padding; let hiddenCanvasHeight = hiddenImageHeight + 2 * padding; const hiddenCanvas = this.createCanvasElement(hiddenCanvasWidth, hiddenCanvasHeight); element.appendChild(hiddenCanvas); const context = hiddenCanvas.getContext("2d"); if (!context) { throw "Unable to get 2d context from canvas"; } context.canvas.width = hiddenCanvasWidth; context.canvas.height = hiddenCanvasHeight; context.drawImage(inputImage, 0, 0, imageWidth, imageHeight, padding, padding, hiddenImageWidth, hiddenImageHeight); try { this.qrcode.decodeRobustlyAsync(hiddenCanvas) .then((result) => { resolve(Html5QrcodeResultFactory.createFromQrcodeResult(result)); }) .catch(reject); } catch (exception) { reject(`QR code parse error, error = ${exception}`); } }; inputImage.onerror = reject; inputImage.onabort = reject; inputImage.onstalled = reject; inputImage.onsuspend = reject; inputImage.src = URL.createObjectURL(imageFile); }); } clear() { this.clearElement(); } static getCameras() { return CameraRetriever.retrieve(); } getRunningTrackCapabilities() { return this.getRenderedCameraOrFail().getRunningTrackCapabilities(); } getRunningTrackSettings() { return this.getRenderedCameraOrFail().getRunningTrackSettings(); } getRunningTrackCameraCapabilities() { return this.getRenderedCameraOrFail().getCapabilities(); } applyVideoConstraints(videoConstaints) { if (!videoConstaints) { throw "videoConstaints is required argument."; } else if (!VideoConstraintsUtil.isMediaStreamConstraintsValid(videoConstaints, this.logger)) { throw "invalid videoConstaints passed, check logs for more details"; } return this.getRenderedCameraOrFail().applyVideoConstraints(videoConstaints); } getRenderedCameraOrFail() { if (this.renderedCamera == null) { throw "Scanning is not in running state, call this API only when" + " QR code scanning using camera is in running state."; } return this.renderedCamera; } getSupportedFormats(configOrVerbosityFlag) { const allFormats = [ Html5QrcodeSupportedFormats.QR_CODE, Html5QrcodeSupportedFormats.AZTEC, Html5QrcodeSupportedFormats.CODABAR, Html5QrcodeSupportedFormats.CODE_39, Html5QrcodeSupportedFormats.CODE_93, Html5QrcodeSupportedFormats.CODE_128, Html5QrcodeSupportedFormats.DATA_MATRIX, Html5QrcodeSupportedFormats.MAXICODE, Html5QrcodeSupportedFormats.ITF, Html5QrcodeSupportedFormats.EAN_13, Html5QrcodeSupportedFormats.EAN_8, Html5QrcodeSupportedFormats.PDF_417, Html5QrcodeSupportedFormats.RSS_14, Html5QrcodeSupportedFormats.RSS_EXPANDED, Html5QrcodeSupportedFormats.UPC_A, Html5QrcodeSupportedFormats.UPC_E, Html5QrcodeSupportedFormats.UPC_EAN_EXTENSION, ]; if (!configOrVerbosityFlag || typeof configOrVerbosityFlag == "boolean") { return allFormats; } if (!configOrVerbosityFlag.formatsToSupport) { return allFormats; } if (!Array.isArray(configOrVerbosityFlag.formatsToSupport)) { throw "configOrVerbosityFlag.formatsToSupport should be undefined " + "or an array."; } if (configOrVerbosityFlag.formatsToSupport.length === 0) { throw "Atleast 1 formatsToSupport is needed."; } const supportedFormats = []; for (const format of configOrVerbosityFlag.formatsToSupport) { if (isValidHtml5QrcodeSupportedFormats(format)) { supportedFormats.push(format); } else { this.logger.warn(`Invalid format: ${format} passed in config, ignoring.`); } } if (supportedFormats.length === 0) { throw "None of formatsToSupport match supported values."; } return supportedFormats; } getUseBarCodeDetectorIfSupported(config) { if (isNullOrUndefined(config)) { return true; } if (!isNullOrUndefined(config.useBarCodeDetectorIfSupported)) { return config.useBarCodeDetectorIfSupported !== false; } if (isNullOrUndefined(config.experimentalFeatures)) { return true; } let experimentalFeatures = config.experimentalFeatures; if (isNullOrUndefined(experimentalFeatures.useBarCodeDetectorIfSupported)) { return true; } return experimentalFeatures.useBarCodeDetectorIfSupported !== false; } validateQrboxSize(viewfinderWidth, viewfinderHeight, internalConfig) { const qrboxSize = internalConfig.qrbox; this.validateQrboxConfig(qrboxSize); let qrDimensions = this.toQrdimensions(viewfinderWidth, viewfinderHeight, qrboxSize); const validateMinSize = (size) => { if (size < Constants.MIN_QR_BOX_SIZE) { throw "minimum size of 'config.qrbox' dimension value is" + ` ${Constants.MIN_QR_BOX_SIZE}px.`; } }; const correctWidthBasedOnRootElementSize = (configWidth) => { if (configWidth > viewfinderWidth) { this.logger.warn("`qrbox.width` or `qrbox` is larger than the" + " width of the root element. The width will be truncated" + " to the width of root element."); configWidth = viewfinderWidth; } return configWidth; }; validateMinSize(qrDimensions.width); validateMinSize(qrDimensions.height); qrDimensions.width = correctWidthBasedOnRootElementSize(qrDimensions.width); } validateQrboxConfig(qrboxSize) { if (typeof qrboxSize === "number") { return; } if (typeof qrboxSize === "function") { return; } if (qrboxSize.width === undefined || qrboxSize.height === undefined) { throw "Invalid instance of QrDimensions passed for " + "'config.qrbox'. Both 'width' and 'height' should be set."; } } toQrdimensions(viewfinderWidth, viewfinderHeight, qrboxSize) { if (typeof qrboxSize === "number") { return { width: qrboxSize, height: qrboxSize }; } else if (typeof qrboxSize === "function") { try { return qrboxSize(viewfinderWidth, viewfinderHeight); } catch (error) { throw new Error("qrbox config was passed as a function but it failed with " + "unknown error" + error); } } return qrboxSize; } setupUi(viewfinderWidth, viewfinderHeight, internalConfig) { if (internalConfig.isShadedBoxEnabled()) { this.validateQrboxSize(viewfinderWidth, viewfinderHeight, internalConfig); } const qrboxSize = isNullOrUndefined(internalConfig.qrbox) ? { width: viewfinderWidth, height: viewfinderHeight } : internalConfig.qrbox; this.validateQrboxConfig(qrboxSize); let qrDimensions = this.toQrdimensions(viewfinderWidth, viewfinderHeight, qrboxSize); if (qrDimensions.height > viewfinderHeight) { this.logger.warn("[Html5Qrcode] config.qrbox has height that is" + "greater than the height of the video stream. Shading will be" + " ignored"); } const shouldShadingBeApplied = internalConfig.isShadedBoxEnabled() && qrDimensions.height <= viewfinderHeight; const defaultQrRegion = { x: 0, y: 0, width: viewfinderWidth, height: viewfinderHeight }; const qrRegion = shouldShadingBeApplied ? this.getShadedRegionBounds(viewfinderWidth, viewfinderHeight, qrDimensions) : defaultQrRegion; const canvasElement = this.createCanvasElement(qrRegion.width, qrRegion.height); const contextAttributes = { willReadFrequently: true }; const context = canvasElement.getContext("2d", contextAttributes); context.canvas.width = qrRegion.width; context.canvas.height = qrRegion.height; this.element.append(canvasElement); if (shouldShadingBeApplied) { this.possiblyInsertShadingElement(this.element, viewfinderWidth, viewfinderHeight, qrDimensions); } this.createScannerPausedUiElement(this.element); this.qrRegion = qrRegion; this.context = context; this.canvasElement = canvasElement; } createScannerPausedUiElement(rootElement) { const scannerPausedUiElement = document.createElement("div"); scannerPausedUiElement.innerText = Html5QrcodeStrings.scannerPaused(); scannerPausedUiElement.style.display = "none"; scannerPausedUiElement.style.position = "absolute"; scannerPausedUiElement.style.top = "0px"; scannerPausedUiElement.style.zIndex = "1"; scannerPausedUiElement.style.background = "rgba(9, 9, 9, 0.46)"; scannerPausedUiElement.style.color = "#FFECEC"; scannerPausedUiElement.style.textAlign = "center"; scannerPausedUiElement.style.width = "100%"; rootElement.appendChild(scannerPausedUiElement); this.scannerPausedUiElement = scannerPausedUiElement; } scanContext(qrCodeSuccessCallback, qrCodeErrorCallback) { if (this.stateManagerProxy.isPaused()) { return Promise.resolve(false); } return this.qrcode.decodeAsync(this.canvasElement) .then((result) => { qrCodeSuccessCallback(result.text, Html5QrcodeResultFactory.createFromQrcodeResult(result)); this.possiblyUpdateShaders(true); return true; }).catch((error) => { this.possiblyUpdateShaders(false); let errorMessage = Html5QrcodeStrings.codeParseError(error); qrCodeErrorCallback(errorMessage, Html5QrcodeErrorFactory.createFrom(errorMessage)); return false; }); } foreverScan(internalConfig, qrCodeSuccessCallback, qrCodeErrorCallback) { if (!this.shouldScan) { return; } if (!this.renderedCamera) { return; } const videoElement = this.renderedCamera.getSurface(); const widthRatio = videoElement.videoWidth / videoElement.clientWidth; const heightRatio = videoElement.videoHeight / videoElement.clientHeight; if (!this.qrRegion) { throw "qrRegion undefined when localMediaStream is ready."; } const sWidthOffset = this.qrRegion.width * widthRatio; const sHeightOffset = this.qrRegion.height * heightRatio; const sxOffset = this.qrRegion.x * widthRatio; const syOffset = this.qrRegion.y * heightRatio; this.context.drawImage(videoElement, sxOffset, syOffset, sWidthOffset, sHeightOffset, 0, 0, this.qrRegion.width, this.qrRegion.height); const triggerNextScan = () => { this.foreverScanTimeout = setTimeout(() => { this.foreverScan(internalConfig, qrCodeSuccessCallback, qrCodeErrorCallback); }, this.getTimeoutFps(internalConfig.fps)); }; this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback) .then((isSuccessfull) => { if (!isSuccessfull && internalConfig.disableFlip !== true) { this.context.translate(this.context.canvas.width, 0); this.context.scale(-1, 1); this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback) .finally(() => { triggerNextScan(); }); } else { triggerNextScan(); } }).catch((error) => { this.logger.logError("Error happend while scanning context", error); triggerNextScan(); }); } createVideoConstraints(cameraIdOrConfig) { if (typeof cameraIdOrConfig == "string") { return { deviceId: { exact: cameraIdOrConfig } }; } else if (typeof cameraIdOrConfig == "object") { const facingModeKey = "facingMode"; const deviceIdKey = "deviceId"; const allowedFacingModeValues = { "user": true, "environment": true }; const exactKey = "exact"; const isValidFacingModeValue = (value) => { if (value in allowedFacingModeValues) { return true; } else { throw "config has invalid 'facingMode' value = " + `'${value}'`; } }; const keys = Object.keys(cameraIdOrConfig); if (keys.length !== 1) { throw "'cameraIdOrConfig' object should have exactly 1 key," + ` if passed as an object, found ${keys.length} keys`; } const key = Object.keys(cameraIdOrConfig)[0]; if (key !== facingModeKey && key !== deviceIdKey) { throw `Only '${facingModeKey}' and '${deviceIdKey}' ` + " are supported for 'cameraIdOrConfig'"; } if (key === facingModeKey) { const facingMode = cameraIdOrConfig.facingMode; if (typeof facingMode == "string") { if (isValidFacingModeValue(facingMode)) { return { facingMode: facingMode }; } } else if (typeof facingMode == "object") { if (exactKey in facingMode) { if (isValidFacingModeValue(facingMode[`${exactKey}`])) { return { facingMode: { exact: facingMode[`${exactKey}`] } }; } } else { throw "'facingMode' should be string or object with" + ` ${exactKey} as key.`; } } else { const type = (typeof facingMode); throw `Invalid type of 'facingMode' = ${type}`; } } else { const deviceId = cameraIdOrConfig.deviceId; if (typeof deviceId == "string") { return { deviceId: deviceId }; } else if (typeof deviceId == "object") { if (exactKey in deviceId) { return { deviceId: { exact: deviceId[`${exactKey}`] } }; } else { throw "'deviceId' should be string or object with" + ` ${exactKey} as key.`; } } else { const type = (typeof deviceId); throw `Invalid type of 'deviceId' = ${type}`; } } } const type = (typeof cameraIdOrConfig); throw `Invalid type of 'cameraIdOrConfig' = ${type}`; } computeCanvasDrawConfig(imageWidth, imageHeight, containerWidth, containerHeight) { if (imageWidth <= containerWidth && imageHeight <= containerHeight) { const xoffset = (containerWidth - imageWidth) / 2; const yoffset = (containerHeight - imageHeight) / 2; return { x: xoffset, y: yoffset, width: imageWidth, height: imageHeight }; } else { const formerImageWidth = imageWidth; const formerImageHeight = imageHeight; if (imageWidth > containerWidth) { imageHeight = (containerWidth / imageWidth) * imageHeight; imageWidth = containerWidth; } if (imageHeight > containerHeight) { imageWidth = (containerHeight / imageHeight) * imageWidth; imageHeight = containerHeight; } this.logger.log("Image downsampled from " + `${formerImageWidth}X${formerImageHeight}` + ` to ${imageWidth}X${imageHeight}.`); return this.computeCanvasDrawConfig(imageWidth, imageHeight, containerWidth, containerHeight); } } clearElement() { if (this.stateManagerProxy.isScanning()) { throw "Cannot clear while scan is ongoing, close it first."; } const element = document.getElementById(this.elementId); if (element) { element.innerHTML = ""; } } possiblyUpdateShaders(qrMatch) { if (this.qrMatch === qrMatch) { return; } if (this.hasBorderShaders && this.borderShaders && this.borderShaders.length) { this.borderShaders.forEach((shader) => { shader.style.backgroundColor = qrMatch ? Constants.BORDER_SHADER_MATCH_COLOR : Constants.BORDER_SHADER_DEFAULT_COLOR; }); } this.qrMatch = qrMatch; } possiblyCloseLastScanImageFile() { if (this.lastScanImageFile) { URL.revokeObjectURL(this.lastScanImageFile); this.lastScanImageFile = null; } } createCanvasElement(width, height, customId) { const canvasWidth = width; const canvasHeight = height; const canvasElement = document.createElement("canvas"); canvasElement.style.width = `${canvasWidth}px`; canvasElement.style.height = `${canvasHeight}px`; canvasElement.style.display = "none"; canvasElement.id = isNullOrUndefined(customId) ? "qr-canvas" : customId; return canvasElement; } getShadedRegionBounds(width, height, qrboxSize) { if (qrboxSize.width > width || qrboxSize.height > height) { throw "'config.qrbox' dimensions should not be greater than the " + "dimensions of the root HTML element."; } return { x: (width - qrboxSize.width) / 2, y: (height - qrboxSize.height) / 2, width: qrboxSize.width, height: qrboxSize.height }; } possiblyInsertShadingElement(element, width, height, qrboxSize) { if ((width - qrboxSize.width) < 1 || (height - qrboxSize.height) < 1) { return; } const shadingElement = document.createElement("div"); shadingElement.style.position = "absolute"; const rightLeftBorderSize = (width - qrboxSize.width) / 2; const topBottomBorderSize = (height - qrboxSize.height) / 2; shadingElement.style.borderLeft = `${rightLeftBorderSize}px solid rgba(0, 0, 0, 0.48)`; shadingElement.style.borderRight = `${rightLeftBorderSize}px solid rgba(0, 0, 0, 0.48)`; shadingElement.style.borderTop = `${topBottomBorderSize}px solid rgba(0, 0, 0, 0.48)`; shadingElement.style.borderBottom = `${topBottomBorderSize}px solid rgba(0, 0, 0, 0.48)`; shadingElement.style.boxSizing = "border-box"; shadingElement.style.top = "0px"; shadingElement.style.bottom = "0px"; shadingElement.style.left = "0px"; shadingElement.style.right = "0px"; shadingElement.id = `${Constants.SHADED_REGION_ELEMENT_ID}`; if ((width - qrboxSize.width) < 11 || (height - qrboxSize.height) < 11) { this.hasBorderShaders = false; } else { const smallSize = 5; const largeSize = 40; this.insertShaderBorders(shadingElement, largeSize, smallSize, -smallSize, null, 0, true); this.insertShaderBorders(shadingElement, largeSize, smallSize, -smallSize, null, 0, false); this.insertShaderBorders(shadingElement, largeSize, smallSize, null, -smallSize, 0, true); this.insertShaderBorders(shadingElement, largeSize, smallSize, null, -smallSize, 0, false); this.insertShaderBorders(shadingElement, smallSize, largeSize + smallSize, -smallSize, null, -smallSize, true); this.insertShaderBorders(shadingElement, smallSize, largeSize + smallSize, null, -smallSize, -smallSize, true); this.insertShaderBorders(shadingElement, smallSize, largeSize + smallSize, -smallSize, null, -smallSize, false); this.insertShaderBorders(shadingElement, smallSize, largeSize + smallSize, null, -smallSize, -smallSize, false); this.hasBorderShaders = true; } element.append(shadingElement); } insertShaderBorders(shaderElem, width, height, top, bottom, side, isLeft) { const elem = document.createElement("div"); elem.style.position = "absolute"; elem.style.backgroundColor = Constants.BORDER_SHADER_DEFAULT_COLOR; elem.style.width = `${width}px`; elem.style.height = `${height}px`; if (top !== null) { elem.style.top = `${top}px`; } if (bottom !== null) { elem.style.bottom = `${bottom}px`; } if (isLeft) { elem.style.left = `${side}px`; } else { elem.style.right = `${side}px`; } if (!this.borderShaders) { this.borderShaders = []; } this.borderShaders.push(elem); shaderElem.appendChild(elem); } showPausedState() { if (!this.scannerPausedUiElement) { throw "[internal error] scanner paused UI element not found"; } this.scannerPausedUiElement.style.display = "block"; } hidePausedState() { if (!this.scannerPausedUiElement) { throw "[internal error] scanner paused UI element not found"; } this.scannerPausedUiElement.style.display = "none"; } getTimeoutFps(fps) { return 1000 / fps; } } //# sourceMappingURL=html5-qrcode.js.map