From 04fe4492f56f8367a6a8c875a786495080bd171d Mon Sep 17 00:00:00 2001 From: dkozak Date: Tue, 3 Sep 2019 17:09:39 +0300 Subject: [PATCH] Update code structure, fix issue #5 --- app/index.js | 2 +- package.json | 2 +- src/core/QRCanvas.js | 137 +++++++++++++++++++++++++++ src/core/QRCode.js | 7 ++ src/core/QRCodeStyling.js | 72 +++++++++++++++ src/{dot.js => core/QRDot.js} | 2 +- src/index.js | 169 +--------------------------------- src/tools/domTools.js | 25 ----- src/tools/merge.js | 2 +- 9 files changed, 222 insertions(+), 196 deletions(-) create mode 100644 src/core/QRCanvas.js create mode 100644 src/core/QRCode.js create mode 100644 src/core/QRCodeStyling.js rename src/{dot.js => core/QRDot.js} (98%) delete mode 100644 src/tools/domTools.js diff --git a/app/index.js b/app/index.js index f2a588fd..24df68de 100644 --- a/app/index.js +++ b/app/index.js @@ -23,4 +23,4 @@ const qrCode = new QrCodeStyling({ } }); -qrCode.append("#canvas"); \ No newline at end of file +qrCode.append(document.getElementById("canvas")); \ No newline at end of file diff --git a/package.json b/package.json index a178e15e..62edda82 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.2", "description": "Add a style and an image to your QR code", "main": "lib/qr-code-styling.js", - "module": "src/index.js", + "module": "src/core/index.js", "files": [ "lib" ], diff --git a/src/core/QRCanvas.js b/src/core/QRCanvas.js new file mode 100644 index 00000000..552ca10e --- /dev/null +++ b/src/core/QRCanvas.js @@ -0,0 +1,137 @@ +import calculateImageSize from "../tools/calculateImageSize"; +import errorCorrectionPercents from "../constants/errorCorrectionPercents"; +import QRDot from "./QRDot"; + +export default class QRCanvas { + constructor(options) { + this.canvas = document.createElement("canvas"); + this.canvas.width = options.width; + this.canvas.height = options.height; + this.options = options; + } + + get context () { + return this.canvas.getContext("2d"); + } + + get width () { + return this.canvas.width; + } + + get height () { + return this.canvas.height; + } + + getCanvas () { + return this.canvas; + } + + clear () { + const canvasContext = this.context; + + canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height); + } + + drawQR(qr) { + this.clear(); + this.drawBackground(); + this.qr = qr; + + if (this.options.image) { + this.drawImageAndDots(); + } else { + this.drawDots(); + } + } + + drawBackground() { + const canvasContext = this.context; + const options = this.options; + + canvasContext.fillStyle = options.backgroundOptions.colour; + canvasContext.fillRect(0, 0, this.canvas.width, this.canvas.height); + } + + + drawDots(filter) { + const canvasContext = this.context; + const options = this.options; + const count = this.qr.getModuleCount(); + const minSize = Math.min(options.width, options.height); + const dotSize = Math.floor(minSize / count); + const xBeginning = Math.floor((options.width - count * dotSize) / 2); + const yBeginning = Math.floor((options.height - count * dotSize) / 2); + + if (count > options.width || count > options.height) { + throw "The canvas is too small."; + } + + const dot = new QRDot({ context: canvasContext, type: options.dotsOptions.type }); + + for(let i = 0; i < count; i++) { + for(let j = 0; j < count; j++) { + if (filter && !filter(i, j)) { + continue; + } + + if (this.qr.isDark(i, j)) { + canvasContext.fillStyle = options.dotsOptions.colour; + dot.draw(xBeginning + i * dotSize, yBeginning + j * dotSize, dotSize, (xOffset, yOffset) => { + if (i + xOffset >= 0 && j + yOffset >= 0 && i + xOffset < count && j + yOffset < count) { + if (filter && !filter(i + xOffset, j + yOffset)) return false; + return this.qr.isDark(i + xOffset, j + yOffset); + } + }); + } + } + } + } + + drawImageAndDots() { + const canvasContext = this.context; + const options = this.options; + const count = this.qr.getModuleCount(); + const minSize = Math.min(options.width, options.height); + const dotSize = Math.floor(minSize / count); + const xBeginning = Math.floor((options.width - count * dotSize) / 2); + const yBeginning = Math.floor((options.height - count * dotSize) / 2); + + const image = new Image(); + const coverLevel = options.imageOptions.imageSize * errorCorrectionPercents[options.qrOptions.errorCorrectionLevel]; + + image.src = options.image; + image.onload = () => { + const maxHiddenDots = Math.floor(coverLevel * count * count); + const { + resizedImageWidth, + resizedImageHeight, + hiddenDotsWidth, + hiddenDotsHeight + } = calculateImageSize({ + originalWidth: image.width, + originalHeight: image.height, + maxHiddenDots, + dotSize + }); + + this.drawDots((i, j) => { + if (!options.imageOptions.hideBackgroundDots) { + return true; + } + return ( + i < (count - hiddenDotsWidth) / 2 + || i >= (count + hiddenDotsWidth) / 2 + || j < (count - hiddenDotsHeight) / 2 + || j >= (count + hiddenDotsHeight) / 2 + ) + }); + + canvasContext.drawImage( + image, + xBeginning + (count * dotSize - resizedImageWidth) / 2, + yBeginning + (count * dotSize - resizedImageHeight) / 2, + resizedImageWidth, + resizedImageHeight); + }; + } +}; \ No newline at end of file diff --git a/src/core/QRCode.js b/src/core/QRCode.js new file mode 100644 index 00000000..0b1985fa --- /dev/null +++ b/src/core/QRCode.js @@ -0,0 +1,7 @@ +import qrcode from "qrcode-generator"; + +export default class QRCode extends qrcode { + constructor(options) { + super(options.qrOptions.typeNumber, options.qrOptions.errorCorrectionLevel); + } +}; \ No newline at end of file diff --git a/src/core/QRCodeStyling.js b/src/core/QRCodeStyling.js new file mode 100644 index 00000000..182c05ed --- /dev/null +++ b/src/core/QRCodeStyling.js @@ -0,0 +1,72 @@ +import getMode from "../tools/getMode"; +import mergeDeep from "../tools/merge"; +import errorCorrectLevels from "../constants/errorCorrectLevels"; +import types from "../constants/types"; +import QRCode from "./QRCode"; +import QRCanvas from "./QRCanvas"; + +const defaultOptions = { + width: 300, + height: 300, + data: undefined, + image: undefined, + qrOptions: { + typeNumber: types[0], + mode: undefined, + errorCorrectionLevel: errorCorrectLevels.Q, + }, + imageOptions: { + hideBackgroundDots: true, + imageSize: 0.4 + }, + dotsOptions: { + type: "square", + colour: "#000", + }, + backgroundOptions: { + colour: "#fff", + } +}; + +export default class QRCodeStyling { + constructor(options) { + this.options = mergeDeep(defaultOptions, options); + this.update(); + } + + update(options) { + this.clearContainer(this.container); + this.options = mergeDeep(this.options, options); + + if (!this.options.data) { + return; + } + + this.qr = new QRCode(this.options); + this.qr.addData(this.options.data, this.options.qrOptions.mode || getMode(this.options.data)); + this.qr.make(); + this.canvas = new QRCanvas(this.options); + this.canvas.drawQR(this.qr); + this.append(this.container); + } + + clearContainer(container) { + if (container) { + container.innerHTML = ""; + } + } + + append(container) { + if (!container) { + return; + } + + if (typeof container.appendChild !== "function") { + throw "Container should be a single DOM node"; + } + + this.container = container; + + container.appendChild(this.canvas.getCanvas()); + } +}; \ No newline at end of file diff --git a/src/dot.js b/src/core/QRDot.js similarity index 98% rename from src/dot.js rename to src/core/QRDot.js index 15c47d3d..40cbb8f1 100644 --- a/src/dot.js +++ b/src/core/QRDot.js @@ -1,4 +1,4 @@ -export default class Dot { +export default class QRDot { constructor({ context, type }) { this.context = context; this.type = type; diff --git a/src/index.js b/src/index.js index 43b36960..b9a22404 100644 --- a/src/index.js +++ b/src/index.js @@ -1,168 +1,3 @@ -import qrcode from "qrcode-generator"; -import calculateImageSize from "./tools/calculateImageSize"; -import { getDOMElement } from "./tools/domTools"; -import getMode from "./tools/getMode"; -import mergeDeep from "./tools/merge"; -import errorCorrectLevels from "./constants/errorCorrectLevels"; -import types from "./constants/types"; -import errorCorrectionPercents from "./constants/errorCorrectionPercents"; -import Dot from "./dot"; +import QRCodeStyling from "./core/QRCodeStyling"; -const defaultProps = { - width: 300, - height: 300, - qrOptions: { - typeNumber: types[0], - errorCorrectionLevel: errorCorrectLevels.L, - }, - dotsOptions: { - colour: "#000", - }, - backgroundOptions: { - colour: "#fff", - } -}; - -const defaultImageProps = { - qrOptions: { - errorCorrectionLevel: errorCorrectLevels.Q, - }, - imageOptions: { - hideBackgroundDots: true, - imageSize: 0.4 - } -}; - -export default class QrCodeStyling { - constructor(options) { - let mergedOptions; - - if (options.image) { - mergedOptions = mergeDeep(defaultProps, defaultImageProps, options); - } else { - mergedOptions = mergeDeep(defaultProps, options); - } - this.initialOptions = mergedOptions; - this.qr = qrcode(mergedOptions.qrOptions.typeNumber, mergedOptions.qrOptions.errorCorrectionLevel); - this.canvas = document.createElement("canvas"); - this.canvas.width = mergedOptions.width; - this.canvas.height = mergedOptions.height; - - if (mergedOptions.data) { - this.addData(mergedOptions.data, mergedOptions.qrOptions.mode); - } - } - - addData(data, mode) { - this.qr.addData(data, mode || getMode(data)); - this.qr.make(); - this.drawQR(); - } - - drawQR() { - const options = this.initialOptions; - - this.drawBackground(); - - if (options.image) { - this.drawImageAndDots(); - } else { - this.drawDots(); - } - } - - drawBackground() { - const canvasContext = this.canvas.getContext("2d"); - const options = this.initialOptions; - - canvasContext.fillStyle = options.backgroundOptions.colour; - canvasContext.fillRect(0, 0, this.canvas.width, this.canvas.height); - } - - - drawDots(filter) { - const canvasContext = this.canvas.getContext("2d"); - const options = this.initialOptions; - const count = this.qr.getModuleCount(); - const minSize = Math.min(options.width, options.height); - const dotSize = Math.floor(minSize / count); - const xBeginning = Math.floor((options.width - count * dotSize) / 2); - const yBeginning = Math.floor((options.height - count * dotSize) / 2); - - if (count > options.width || count > options.height) { - throw "The canvas is too small."; - } - - const dot = new Dot({ context: canvasContext, type: options.dotsOptions.type }); - - for(let i = 0; i < count; i++) { - for(let j = 0; j < count; j++) { - if (filter && !filter(i, j)) { - continue; - } - - if (this.qr.isDark(i, j)) { - canvasContext.fillStyle = options.dotsOptions.colour; - dot.draw(xBeginning + i * dotSize, yBeginning + j * dotSize, dotSize, (xOffset, yOffset) => { - if (i + xOffset >= 0 && j + yOffset >= 0 && i + xOffset < count && j + yOffset < count) { - if (filter && !filter(i + xOffset, j + yOffset)) return false; - return this.qr.isDark(i + xOffset, j + yOffset); - } - }); - } - } - } - } - - drawImageAndDots() { - const canvasContext = this.canvas.getContext("2d"); - const options = this.initialOptions; - const count = this.qr.getModuleCount(); - const minSize = Math.min(options.width, options.height); - const dotSize = Math.floor(minSize / count); - const xBeginning = Math.floor((options.width - count * dotSize) / 2); - const yBeginning = Math.floor((options.height - count * dotSize) / 2); - - const image = new Image(); - const coverLevel = options.imageOptions.imageSize * errorCorrectionPercents[options.qrOptions.errorCorrectionLevel]; - - image.src = options.image; - image.onload = () => { - const maxHiddenDots = Math.floor(coverLevel * count * count); - const { - resizedImageWidth, - resizedImageHeight, - hiddenDotsWidth, - hiddenDotsHeight - } = calculateImageSize({ - originalWidth: image.width, - originalHeight: image.height, - maxHiddenDots, - dotSize - }); - - this.drawDots((i, j) => { - if (!options.imageOptions.hideBackgroundDots) { - return true; - } - return ( - i < (count - hiddenDotsWidth) / 2 - || i >= (count + hiddenDotsWidth) / 2 - || j < (count - hiddenDotsHeight) / 2 - || j >= (count + hiddenDotsHeight) / 2 - ) - }); - - canvasContext.drawImage( - image, - xBeginning + (count * dotSize - resizedImageWidth) / 2, - yBeginning + (count * dotSize - resizedImageHeight) / 2, - resizedImageWidth, - resizedImageHeight); - }; - } - - append(selector) { - getDOMElement(selector).appendChild(this.canvas); - } -}; \ No newline at end of file +export default QRCodeStyling; \ No newline at end of file diff --git a/src/tools/domTools.js b/src/tools/domTools.js deleted file mode 100644 index 3a3d332a..00000000 --- a/src/tools/domTools.js +++ /dev/null @@ -1,25 +0,0 @@ -export function getDOMElement(selector) { - if (!selector) { - throw "'selector' is required"; - } - - if (selector[0] === ".") { - const elements = document.getElementsByClassName(selector.substr(1)); - - if (elements && elements.length) { - return elements[0] - } else { - throw "Element is not found"; - } - } else if (selector[0] === "#") { - const element = document.getElementById(selector.substr(1)); - - if (element) { - return element; - } else { - throw "Element is not found"; - } - } else { - throw "Unknown selector"; - } -} \ No newline at end of file diff --git a/src/tools/merge.js b/src/tools/merge.js index 16d162fe..6369a1ec 100644 --- a/src/tools/merge.js +++ b/src/tools/merge.js @@ -10,7 +10,7 @@ export default function mergeDeep(target, ...sources) { } if (!isObject(target) || !isObject(source)) { - return source; + return target; } Object.keys(source).forEach(key => {