Skip to content

Commit

Permalink
feat: when the logo reports an error, it normally displays the QR code
Browse files Browse the repository at this point in the history
  • Loading branch information
OriginRing committed Feb 16, 2023
1 parent 61331b3 commit e3ba144
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 85 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ A angular component to generate QR codes for rendering to the DOM.
## Introduction

Support canvas and svg two formats

Support adding QR code logo

Angular version >= 14
Expand Down Expand Up @@ -45,14 +46,14 @@ import { QrcodeModule } from 'qrcode-angular';
## API

| prop | type | default value | note |
| ------------ | --------------------------------- | --------------------------------- |---------------------------|
| ------------ | --------------------------------- |-----------------------------------|---------------------------|
| `mode` | `'canvas'|'svg'` | `'canvas'` | output mode |
| `value` | `string` | - | scanned link |
| `color` | `{ dark: string, light: string }` | `{ dark: '#000', light: '#fff' }` | QR code Color |
| `size` | `number` | `160` | QR code Size |
| `icon` | `string` | - | QR code include logo url |
| `iconSize` | `number` | `40` | QR code include logo size |
| `errorLevel` | `'L'|'M'|'Q'|'H'` | `H` | Error Code Level |
| `errorLevel` | `'L'|'M'|'Q'|'H'` | `M` | Error Code Level |

## Notice

Expand Down
5 changes: 3 additions & 2 deletions projects/qrcode-angular/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ A angular component to generate QR codes for rendering to the DOM.
## Introduction

Support canvas and svg two formats

Support adding QR code logo

Angular version >= 14
Expand Down Expand Up @@ -45,14 +46,14 @@ import { QrcodeModule } from 'qrcode-angular';
## API

| prop | type | default value | note |
| ------------ | --------------------------------- | --------------------------------- |---------------------------|
| ------------ | --------------------------------- |-----------------------------------|---------------------------|
| `mode` | `'canvas'|'svg'` | `'canvas'` | output mode |
| `value` | `string` | - | scanned link |
| `color` | `{ dark: string, light: string }` | `{ dark: '#000', light: '#fff' }` | QR code Color |
| `size` | `number` | `160` | QR code Size |
| `icon` | `string` | - | QR code include logo url |
| `iconSize` | `number` | `40` | QR code include logo size |
| `errorLevel` | `'L'|'M'|'Q'|'H'` | `H` | Error Code Level |
| `errorLevel` | `'L'|'M'|'Q'|'H'` | `M` | Error Code Level |

## Notice

Expand Down
39 changes: 28 additions & 11 deletions projects/qrcode-angular/src/lib/qrcode.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
ViewChild
} from '@angular/core';

import { drawCanvas, plotQrCodeData, toSvgString } from './qrcode';
import { drawCanvas, ERROR_LEVEL_MAP, plotQrCodeData, toSvgString } from './qrcode';
@Component({
selector: 'qrcode',
template: `
Expand All @@ -30,7 +30,7 @@ import { drawCanvas, plotQrCodeData, toSvgString } from './qrcode';
<path [attr.fill]="color.light" [attr.d]="d" shape-rendering="crispEdges" />
<path [attr.d]="path" [attr.fill]="color.dark" shape-rendering="crispEdges" />
<svg
*ngIf="!!icon"
*ngIf="!!icon && svgImg"
[attr.viewBox]="iconViewBox"
[attr.x]="iconX"
[attr.y]="iconX"
Expand All @@ -54,21 +54,26 @@ import { drawCanvas, plotQrCodeData, toSvgString } from './qrcode';
:host {
display: inline-block;
}
canvas {
display: block;
}
img {
display: none;
}
`
]
})
export class QrcodeComponent implements OnInit, OnChanges, AfterViewInit {
@ViewChild('canvas', { static: false }) canvas!: ElementRef;
@ViewChild('canvas', { static: false }) canvas!: ElementRef<HTMLCanvasElement>;
@Input() mode: 'canvas' | 'svg' = 'canvas';
@Input() value: string = '';
@Input() color: { dark: string; light: string } = { dark: '#000', light: '#fff' };
@Input() size: number = 160;
@Input() icon: string = '';
@Input() iconSize: number = 40;
@Input() errorLevel: 'L' | 'M' | 'Q' | 'H' = 'M';
@Input() errorLevel: keyof typeof ERROR_LEVEL_MAP = 'M';

svgImg: boolean = true;

path: string | null = null;
viewBox: string | null = null;
Expand All @@ -82,8 +87,8 @@ export class QrcodeComponent implements OnInit, OnChanges, AfterViewInit {
ngOnInit(): void {}

ngOnChanges(changes: SimpleChanges): void {
const { mode, value, icon, errorLevel } = changes;
if (mode || value || icon || errorLevel) {
const { mode, value, color, icon, errorLevel } = changes;
if (mode || value || color || icon || errorLevel) {
switch (this.mode) {
case 'canvas':
if (this.canvas) this.drawCanvasQRCode();
Expand All @@ -107,6 +112,22 @@ export class QrcodeComponent implements OnInit, OnChanges, AfterViewInit {
}

drawSvgQrCode(): void {
if (!this.icon) {
this.drawSvgQrCodeFilling();
} else {
const iconImg = new Image();
iconImg.src = this.icon;
iconImg.crossOrigin = 'anonymous';
iconImg.onload = () => this.drawSvgQrCodeFilling();
iconImg.onerror = () => {
this.svgImg = false;
this.drawSvgQrCodeFilling();
this.cdr.markForCheck();
};
}
}

drawSvgQrCodeFilling(): void {
const svg = toSvgString(plotQrCodeData(this.value, this.errorLevel));
this.path = svg.parts.join(' ');
this.viewBox = `0 0 ${svg.size} ${svg.size}`;
Expand All @@ -118,12 +139,8 @@ export class QrcodeComponent implements OnInit, OnChanges, AfterViewInit {
}

drawCanvasQRCode(): void {
if (!this.value) {
return;
}

drawCanvas(
this.canvas,
this.canvas.nativeElement,
plotQrCodeData(this.value, this.errorLevel),
this.size,
20,
Expand Down
87 changes: 50 additions & 37 deletions projects/qrcode-angular/src/lib/qrcode.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { ElementRef } from '@angular/core';

import qrcodegen from './qrcodegen';

const ERROR_LEVEL_MAP: { [index: string]: qrcodegen.QrCode.Ecc } = {
export const ERROR_LEVEL_MAP: { [index in 'L' | 'M' | 'Q' | 'H']: qrcodegen.QrCode.Ecc } = {
L: qrcodegen.QrCode.Ecc.LOW,
M: qrcodegen.QrCode.Ecc.MEDIUM,
Q: qrcodegen.QrCode.Ecc.QUARTILE,
H: qrcodegen.QrCode.Ecc.HIGH
};
} as const;

const DEFAULT_SIZE = 160;
const DEFAULT_SCALE = 10;
const DEFAULT_BGCOLOR = '#FFFFFF';
const DEFAULT_FGCOLOR = '#000000';
const DEFAULT_ICONSIZE = 40;
const DEFAULT_LEVEL = 'L';
const DEFAULT_LEVEL: keyof typeof ERROR_LEVEL_MAP = 'M';

export const toSvgString = (value: qrcodegen.QrCode): { size: number; parts: string[] } => {
export const toSvgString = (value: qrcodegen.QrCode | null): { size: number; parts: string[] } => {
if (!value) {
return { size: 0, parts: [] };
}
let parts: string[] = [];
for (let y = 0; y < value.size; y++) {
for (let x = 0; x < value.size; x++) {
Expand All @@ -27,61 +28,73 @@ export const toSvgString = (value: qrcodegen.QrCode): { size: number; parts: str
return { size: value.size, parts };
};

export const plotQrCodeData = (value: string, level = DEFAULT_LEVEL): qrcodegen.QrCode => {
export const plotQrCodeData = (
value: string,
level: keyof typeof ERROR_LEVEL_MAP = DEFAULT_LEVEL
): qrcodegen.QrCode | null => {
if (!value) {
return null;
}
return qrcodegen.QrCode.encodeText(value, ERROR_LEVEL_MAP[level]);
};

export function drawCanvas(
canvas: ElementRef,
value: qrcodegen.QrCode,
canvas: HTMLCanvasElement,
value: qrcodegen.QrCode | null,
size = DEFAULT_SIZE,
scale = DEFAULT_SCALE,
lightColor = DEFAULT_BGCOLOR,
darkColor = DEFAULT_FGCOLOR,
iconSize = DEFAULT_ICONSIZE,
icon?: string
): void {
const ctx = canvas.nativeElement.getContext('2d') as CanvasRenderingContext2D;
canvas.nativeElement.width = value.size * scale;
canvas.nativeElement.height = value.size * scale;
canvas.nativeElement.style.width = `${size}px`;
canvas.nativeElement.style.height = `${size}px`;
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
canvas.style.width = `${size}px`;
canvas.style.height = `${size}px`;
if (!value) {
ctx.fillStyle = lightColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return;
}
canvas.width = value.size * scale;
canvas.height = value.size * scale;
if (!icon) {
for (let y = 0; y < value.size; y++) {
for (let x = 0; x < value.size; x++) {
ctx.fillStyle = value.getModule(x, y) ? darkColor : lightColor;
ctx.fillRect(x * scale, y * scale, scale, scale);
}
}
drawCanvasColor(ctx, value, scale, darkColor, lightColor);
} else {
const iconImg = new Image();
iconImg.src = icon;
iconImg.crossOrigin = 'anonymous';
iconImg.width = iconSize * (canvas.nativeElement.width / size);
iconImg.height = iconSize * (canvas.nativeElement.width / size);
iconImg.width = iconSize * (canvas.width / size);
iconImg.height = iconSize * (canvas.width / size);
iconImg.onload = () => {
for (let y = 0; y < value.size; y++) {
for (let x = 0; x < value.size; x++) {
ctx.fillStyle = value.getModule(x, y) ? darkColor : lightColor;
ctx.fillRect(x * scale, y * scale, scale, scale);
}
}
const iconCoordinate = canvas.nativeElement.width / 2 - (iconSize * (canvas.nativeElement.width / size)) / 2;
drawCanvasColor(ctx, value, scale, darkColor, lightColor);
const iconCoordinate = canvas.width / 2 - (iconSize * (canvas.width / size)) / 2;

ctx.fillStyle = lightColor;
ctx.fillRect(
iconCoordinate,
iconCoordinate,
iconSize * (canvas.nativeElement.width / size),
iconSize * (canvas.nativeElement.width / size)
);
ctx.fillRect(iconCoordinate, iconCoordinate, iconSize * (canvas.width / size), iconSize * (canvas.width / size));
ctx.drawImage(
iconImg,
iconCoordinate,
iconCoordinate,
iconSize * (canvas.nativeElement.width / size),
iconSize * (canvas.nativeElement.width / size)
iconSize * (canvas.width / size),
iconSize * (canvas.width / size)
);
};
iconImg.onerror = () => drawCanvasColor(ctx, value, scale, darkColor, lightColor);
}
}

export function drawCanvasColor(
ctx: CanvasRenderingContext2D,
value: qrcodegen.QrCode,
scale: number,
darkColor: string,
lightColor: string
): void {
for (let y = 0; y < value.size; y++) {
for (let x = 0; x < value.size; x++) {
ctx.fillStyle = value.getModule(x, y) ? darkColor : lightColor;
ctx.fillRect(x * scale, y * scale, scale, scale);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,31 @@
* SPDX-License-Identifier: MIT
*/

/**
* QR Code generator library (TypeScript)
*
* Copyright (c) Project Nayuki.
* https://www.nayuki.io/page/qr-code-generator-library
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

'use strict';

namespace qrcodegen {
Expand Down
22 changes: 0 additions & 22 deletions projects/qrcode-angular/src/lib/qrcodegen/LICENSE

This file was deleted.

11 changes: 0 additions & 11 deletions projects/qrcode-angular/src/lib/qrcodegen/README.md

This file was deleted.

0 comments on commit e3ba144

Please sign in to comment.