diff --git a/README.md b/README.md index dfe108ba..d4ed7f39 100644 --- a/README.md +++ b/README.md @@ -135,11 +135,12 @@ When QR type is svg, the image may not load in certain applications as it is sav `options.dotsOptions` structure -Property|Type |Default Value|Description ---------|------------------------------------------------------------------------------|-------------|------------------- -color |string |`'#000'` |Color of QR dots -gradient|object | |Gradient of QR dots -type |string (`'rounded' 'dots' 'classy' 'classy-rounded' 'square' 'extra-rounded'`)|`'square'` |Style of QR dots +Property | Type | Default Value |Description +-------- |--------------------------------------------------------------------------------|---------------|------------------- +color | string | `'#000'` |Color of QR dots +gradient | object | |Gradient of QR dots +type | string (`'rounded' 'dots' 'classy' 'classy-rounded' 'square' 'extra-rounded'`) | `'square'` |Style of QR dots +roundSize| boolean | true |Whether to round dots size to integer. `true` value might create extra margin around qr code. If `false`, [shape-rendering="crispEdges"](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering#crispedges) will be applied to SVG element. `options.backgroundOptions` structure diff --git a/src/core/QROptions.ts b/src/core/QROptions.ts index 22b88fb4..902caa26 100644 --- a/src/core/QROptions.ts +++ b/src/core/QROptions.ts @@ -27,6 +27,7 @@ export interface RequiredOptions extends Options { type: DotType; color: string; gradient?: Gradient; + roundSize?: boolean; }; backgroundOptions: { round: number; @@ -56,7 +57,8 @@ const defaultOptions: RequiredOptions = { }, dotsOptions: { type: "square", - color: "#000" + color: "#000", + roundSize: true, }, backgroundOptions: { round: 0, diff --git a/src/core/QRSVG.ts b/src/core/QRSVG.ts index 66ee54e9..77208c2b 100644 --- a/src/core/QRSVG.ts +++ b/src/core/QRSVG.ts @@ -53,6 +53,9 @@ export default class QRSVG { this._element.setAttribute("width", String(options.width)); this._element.setAttribute("height", String(options.height)); this._element.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); + if (!options.dotsOptions.roundSize) { + this._element.setAttribute("shape-rendering", "crispEdges"); + } this._element.setAttribute("viewBox", `0 0 ${options.width} ${options.height}`); this._defs = this._window.document.createElementNS("http://www.w3.org/2000/svg", "defs"); this._element.appendChild(this._defs); @@ -87,7 +90,7 @@ export default class QRSVG { const count = qr.getModuleCount(); const minSize = Math.min(this._options.width, this._options.height) - this._options.margin * 2; const realQRSize = this._options.shape === shapeTypes.circle ? minSize / Math.sqrt(2) : minSize; - const dotSize = Math.floor(realQRSize / count); + const dotSize = this._roundSize(realQRSize / count); let drawImageSize = { hideXDots: 0, hideYDots: 0, @@ -115,6 +118,7 @@ export default class QRSVG { }); } + console.log(this._image?.width, this._image?.height) this.drawBackground(); this.drawDots((row: number, col: number): boolean => { if (this._options.imageOptions.hideBackgroundDots) { @@ -166,8 +170,8 @@ export default class QRSVG { element.setAttribute("rx", String((height / 2) * options.backgroundOptions.round)); } - element.setAttribute("x", String((options.width - width) / 2)); - element.setAttribute("y", String((options.height - height) / 2)); + element.setAttribute("x", String(this._roundSize((options.width - width) / 2))); + element.setAttribute("y", String(this._roundSize((options.height - height) / 2))); element.setAttribute("width", String(width)); element.setAttribute("height", String(height)); @@ -201,9 +205,9 @@ export default class QRSVG { const minSize = Math.min(options.width, options.height) - options.margin * 2; const realQRSize = options.shape === shapeTypes.circle ? minSize / Math.sqrt(2) : minSize; - const dotSize = Math.floor(realQRSize / count); - const xBeginning = Math.floor((options.width - count * dotSize) / 2); - const yBeginning = Math.floor((options.height - count * dotSize) / 2); + const dotSize = this._roundSize(realQRSize / count); + const xBeginning = this._roundSize((options.width - count * dotSize) / 2); + const yBeginning = this._roundSize((options.height - count * dotSize) / 2); const dot = new QRDot({ svg: this._element, type: options.dotsOptions.type, @@ -252,12 +256,12 @@ export default class QRSVG { } if (options.shape === shapeTypes.circle) { - const additionalDots = Math.floor((minSize / dotSize - count) / 2); + const additionalDots = this._roundSize((minSize / dotSize - count) / 2); const fakeCount = count + additionalDots * 2; const xFakeBeginning = xBeginning - additionalDots * dotSize; const yFakeBeginning = yBeginning - additionalDots * dotSize; const fakeMatrix: number[][] = []; - const center = Math.floor(fakeCount / 2); + const center = this._roundSize(fakeCount / 2); for (let row = 0; row < fakeCount; row++) { fakeMatrix[row] = []; @@ -322,11 +326,11 @@ export default class QRSVG { const count = this._qr.getModuleCount(); const minSize = Math.min(options.width, options.height) - options.margin * 2; const realQRSize = options.shape === shapeTypes.circle ? minSize / Math.sqrt(2) : minSize; - const dotSize = Math.floor(realQRSize / count); + const dotSize = this._roundSize(realQRSize / count); const cornersSquareSize = dotSize * 7; const cornersDotSize = dotSize * 3; - const xBeginning = Math.floor((options.width - count * dotSize) / 2); - const yBeginning = Math.floor((options.height - count * dotSize) / 2); + const xBeginning = this._roundSize((options.width - count * dotSize) / 2); + const yBeginning = this._roundSize((options.height - count * dotSize) / 2); [ [0, 0, 0], @@ -456,11 +460,6 @@ export default class QRSVG { imageToBlob(): void { if (!this._image) return; - // fix blurry svg - if (/(\.svg$)|(^data:image\/svg)/.test(this._options.image ?? "")) { - this._image.width = this._options.width; - this._image.height = this._options.height; - } if (this._options.imageOptions.saveAsBlob && this._canvas) { const ctx = this._canvas.getContext("2d"); if (ctx) { @@ -519,10 +518,10 @@ export default class QRSVG { dotSize: number; }): Promise { const options = this._options; - const xBeginning = Math.floor((options.width - count * dotSize) / 2); - const yBeginning = Math.floor((options.height - count * dotSize) / 2); - const dx = xBeginning + options.imageOptions.margin + (count * dotSize - width) / 2; - const dy = yBeginning + options.imageOptions.margin + (count * dotSize - height) / 2; + const xBeginning = this._roundSize((options.width - count * dotSize) / 2); + const yBeginning = this._roundSize((options.height - count * dotSize) / 2); + const dx = xBeginning + this._roundSize(options.imageOptions.margin + (count * dotSize - width) / 2); + const dy = yBeginning + this._roundSize(options.imageOptions.margin + (count * dotSize - height) / 2); const dw = width - options.imageOptions.margin * 2; const dh = height - options.imageOptions.margin * 2; @@ -635,4 +634,11 @@ export default class QRSVG { this._element.appendChild(rect); } + + _roundSize = (value: number) => { + if (this._options.dotsOptions.roundSize) { + return Math.floor(value); + } + return value; + } }