UNPKG

ngx-qrcode-styling

Version:

This library is built for the purpose for generating QR codes with a logo and styling.

1,254 lines (1,246 loc) 39.6 kB
import * as i0 from '@angular/core'; import { Injectable, Input, ViewEncapsulation, Component } from '@angular/core'; import { AsyncSubject } from 'rxjs'; import QRCodeStyling from 'qr-code-styling'; const Templates = (name) => { switch (name) { case 'default': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#202020', }, backgroundOptions: { color: '#ffffff', }, }; case 'ocean': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { type: 'dots', gradient: { type: 'radial', colorStops: [ { offset: 0, color: '#36CDA5', }, { offset: 1, color: '#1582AE', }, { offset: 2, color: '#0277BD', }, ], }, }, backgroundOptions: { color: '#ffffff', }, cornersSquareOptions: { type: 'square', }, }; case 'sunflower': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { type: 'dots', gradient: { type: 'radial', colorStops: [ { offset: 0, color: '#BB8412', }, { offset: 1, color: '#4D762E', }, { offset: 2, color: '#2F702F', }, ], }, }, backgroundOptions: { color: '#ffffff', }, cornersDotOptions: { color: '#006B3B', type: 'dot', }, cornersSquareOptions: { color: '#006B3B', type: 'extra-rounded', }, }; case 'luxury': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { type: 'classy-rounded', gradient: { type: 'radial', colorStops: [ { offset: 0, color: '#E29C0F', }, { offset: 1, color: '#913C0C', }, { offset: 2, color: '#732E0C', }, ], }, }, backgroundOptions: { color: '#ffffff', }, cornersDotOptions: { color: '#732E0C', type: 'dot', }, cornersSquareOptions: { color: '#732E0C', type: 'extra-rounded', }, }; case 'bitcoin': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#3a3a3a', type: 'dots', }, backgroundOptions: { color: '#ffffff', }, cornersDotOptions: { color: '#ff9900', }, cornersSquareOptions: { type: 'extra-rounded', }, }; case 'starbucks': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#3a3a3a', type: 'dots', }, backgroundOptions: { color: '#ffffff', }, cornersDotOptions: { color: '#00643C', type: 'dot', }, cornersSquareOptions: { color: '#00643C', type: 'extra-rounded', }, }; case 'angular': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#c3002f', type: 'dots', }, backgroundOptions: { color: '#ffffff', }, }; case 'facebook': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#4D72A9', type: 'dots', }, backgroundOptions: { color: '#ffffff', }, }; case 'beans': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#416B2C', type: 'dots', }, backgroundOptions: { color: '#ffffff', }, cornersDotOptions: { color: '#60A541', type: 'dot', }, cornersSquareOptions: { type: 'extra-rounded', }, }; case 'green': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#006B3B', type: 'dots', }, backgroundOptions: { color: '#ffffff', }, cornersDotOptions: { color: '#006B3B', type: 'dot', }, cornersSquareOptions: { color: '#006B3B', type: 'extra-rounded', }, }; case 'sky': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#2c7597', type: 'rounded', }, backgroundOptions: { color: '#ffffff', }, }; case 'mosaic': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { type: 'rounded', gradient: { rotation: 190, type: 'linear', colorStops: [ { offset: 0, color: '#0277BD', }, { offset: 1, color: '#943A40', }, { offset: 2, color: '#A13535', }, ], }, }, backgroundOptions: { color: '#ffffff', }, }; case 'coffee': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#5E4F39', type: 'dots', }, backgroundOptions: { color: '#FAEDE7', }, }; case 'vintage': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#2D7363', type: 'extra-rounded', }, backgroundOptions: { color: '#E7E5C5', }, cornersDotOptions: { color: '#BE8C49', }, cornersSquareOptions: { color: '#2D7363', }, }; case 'stamp': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#2a3c16', type: 'square', }, backgroundOptions: { color: '#9cc66e', }, cornersDotOptions: { color: '#4c692c', }, cornersSquareOptions: { color: '#2a3c16', }, }; case 'chess': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#F4EAA5', type: 'dots', }, backgroundOptions: { color: '#494B49', }, }; case 'jungle': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { type: 'classy-rounded', gradient: { type: 'radial', colorStops: [ { offset: 0, color: '#84b931', }, { offset: 1, color: '#537234', }, { offset: 2, color: '#537234', }, ], }, }, cornersSquareOptions: { color: '#434343', gradient: { type: 'radial', colorStops: [ { offset: 0, color: '#84b931', }, { offset: 1, color: '#537234', }, { offset: 2, color: '#537234', }, ], }, }, }; case 'arabic': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { type: 'classy-rounded', gradient: { type: 'radial', colorStops: [ { offset: 0, color: '#393939', }, { offset: 1, color: '#393939', }, { offset: 2, color: '#393939', }, ], }, }, cornersSquareOptions: { color: '#434343', gradient: { type: 'radial', colorStops: [ { offset: 0, color: '#393939', }, { offset: 1, color: '#72a28b', }, { offset: 2, color: '#72a28b', }, ], }, }, }; case 'tea': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { type: 'extra-rounded', gradient: { type: 'radial', colorStops: [ { offset: 0, color: '#72b0b4', }, { offset: 1, color: '#035558', }, { offset: 2, color: '#035558', }, ], }, }, cornersSquareOptions: { color: '#434343', gradient: { type: 'radial', colorStops: [ { offset: 0, color: '#72b0b4', }, { offset: 1, color: '#035558', }, { offset: 2, color: '#035558', }, ], }, }, }; case 'grape': return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { type: 'rounded', gradient: { type: 'radial', colorStops: [ { offset: 0, color: '#7076ae', }, { offset: 1, color: '#2a3240', }, { offset: 2, color: '#2a3240', }, ], }, }, cornersSquareOptions: { color: '#434343', gradient: { type: 'radial', colorStops: [ { offset: 0, color: '#7b7cb5', }, { offset: 1, color: '#2a3240', }, { offset: 2, color: '#2a3240', }, ], }, }, }; default: return { data: '', qrOptions: { errorCorrectionLevel: 'Q', }, dotsOptions: { color: '#202020', }, backgroundOptions: { color: '#ffffff', }, }; } }; /** * drawQrcode * @param config * @param container * @returns */ function drawQrcode(config, container) { const subject = new AsyncSubject(); // Reject if (!config || !container) { subject.error('Container or Config not available!'); subject.complete(); return subject; } const element = document.createElement('div'); /** * QRCODE_NONE_FRAME * @returns */ const QRCODE_NONE_FRAME = () => { if (config?.frameOptions) { return false; } else { const encodeConfig = () => { let deep = config && JSON.parse(JSON.stringify(config)); // deep return Object.assign({ data: window.unescape(encodeURIComponent(deep?.data ?? '')), }, deep); }; // removeChild while (container.firstChild) { container.removeChild(container.lastChild); } const CR = new QRCodeStyling(encodeConfig()); // append to container CR.append(container); return true; } }; const styleName = config?.frameOptions?.style ?? 'F_020'; const height = config?.frameOptions?.height ?? 300; const width = config?.frameOptions?.width ?? 300; const x = config?.frameOptions?.x ?? 50; const y = config?.frameOptions?.y ?? 50; /** * ADD_FRAME_SVG_TO_ELEMENT * @returns */ const ADD_FRAME_SVG_TO_ELEMENT = () => { const http = fetch(`https://cdn.jsdelivr.net/gh/id1945/ngx-qrcode-styling/svg/1.2.9/${styleName}.svg`, { method: 'GET' }); return new Promise((resolve, reject) => { http .then((response) => response.text()) .then((result) => { if (result !== '404: Not Found') { upgradeSvg(result); } resolve(result); }) .catch((error) => { console.error(error); reject(error); }); }); }; const upgradeSvg = (result) => { const parser = new DOMParser(); const doc = parser.parseFromString(result, 'image/svg+xml'); if (doc) { const svgEl = doc.documentElement.children[styleName + '_svg']; if (!svgEl) return; const textEls = svgEl.getElementsByClassName('frame-text'); const contentEls = svgEl.getElementsByClassName('frame-content'); const containerEls = svgEl.getElementsByClassName('frame-container'); config?.frameOptions?.background && svgEl.setAttribute('style', `background: ${config?.frameOptions?.background};${svgEl?.getAttribute('style')}`); const updateStyle = (el, config) => { if (el) { for (const key in config) { if (['x', 'y', 'transform'].includes(key)) { el.setAttribute(key, config && config[key]); } else if (['textContent'].includes(key)) { el[key] = config && config[key]; } else { el.style[key] = config && config[key]; } } } }; const createElementNS = (config) => { const svgNS = 'http://www.w3.org/2000/svg'; const newText = document.createElementNS(svgNS, 'text'); updateStyle(newText, config); svgEl.appendChild(newText); }; if (config?.frameOptions?.texts) { [...config.frameOptions.texts].forEach((text, i) => { const el = [...textEls]?.[i]; el ? updateStyle(el, text) : createElementNS(text); }); } if (config?.frameOptions?.containers) { [...containerEls].forEach((el, i) => { updateStyle(el, config?.frameOptions?.containers?.[i]); }); } if (config?.frameOptions?.contents) { [...contentEls].forEach((el, i) => { updateStyle(el, config?.frameOptions?.contents?.[i]); }); } element.appendChild(doc.documentElement); } }; /** * UPDATE_POSITION_QRCODE_ON_FRAME * @returns HTMLElement */ const UPDATE_POSITION_QRCODE_ON_FRAME = () => { const after = element.querySelector('.ngx_qrcode_styling_after'); if (after && config?.zIndex === 1) { after?.setAttribute('transform', `translate(${x},${y})`); return after; } const before = element.querySelector('.ngx_qrcode_styling_before'); before?.setAttribute('transform', `translate(${x},${y})`); return before; }; /** * UPDATE_ROTATE_SCALE_QRCODE_ON_FRAME * @param svg * @returns void */ const UPDATE_ROTATE_SCALE_QRCODE_ON_FRAME = (svg) => { if (svg && config?.rotate) { svg?.childNodes?.[0]?.childNodes?.forEach((node) => { if (node.nodeName === 'rect') { node.style.transformOrigin = `50% 50%`; node.style.transform = `rotate(${config?.rotate ?? 0}deg)`; } }); } if (svg && config?.scale) { svg?.childNodes?.[0]?.childNodes?.forEach((node) => { if (node.nodeName === 'rect') { node.style.scale = config?.scale ?? 0; } }); } }; /** * CREATE_QRCODE_INTO_FRAME * @param addsvg * @returns */ const CREATE_QRCODE_INTO_FRAME = (addsvg) => { const defaultConfig = () => { let deep = config && JSON.parse(JSON.stringify(config)); // deep deep = { ...deep, ...{ type: 'svg', data: window.unescape(encodeURIComponent(deep?.data ?? '')), }, }; delete deep.frameOptions; delete deep.template; return deep; }; // removeChild while (container?.firstChild) { container.removeChild(container.lastChild); } const CR = new QRCodeStyling(defaultConfig()); return CR?._svgDrawingPromise ?.then(() => { CR.append(addsvg); }) .catch((error) => console.error(error)); }; /** * QRCODE_TYPE_SVG * @returns */ const QRCODE_TYPE_SVG = () => { if (config?.type === 'svg') { UPDATE_SIZE_SVG(); container.appendChild(element); return true; } return false; }; /** * CREATE_CANVAS_WITH_SIZE * @returns */ const CREATE_CANVAS_WITH_SIZE = () => { const canvas = document.createElement('canvas'); canvas.height = height; canvas.width = width; container.appendChild(canvas); return canvas; }; /** * ELEMENT_CONVERT_TO_BASE64 * @param s1 * @returns */ const ELEMENT_CONVERT_TO_BASE64 = (s1) => { let b64 = 'data:image/svg+xml;base64,'; const xml = s1 && new XMLSerializer().serializeToString(s1); return (b64 += xml && btoa(unescape(encodeURIComponent(xml)))); }; /** * UPDATE_SIZE_SVG * @returns */ const UPDATE_SIZE_SVG = () => { const s1 = element.querySelector(`#${styleName}_svg`); s1 && s1.setAttribute('height', `${height}px`); s1 && s1.setAttribute('width', `${width}px`); return s1; }; /** * BASE64_TO_BLOB * @param base64Image * @returns */ const BASE64_TO_BLOB = (base64Image) => { // Split into two parts const parts = base64Image.split(';base64,'); // Hold the content type const imageType = parts[0].split(':')[1]; // Decode Base64 string const decodedData = window.atob(parts[1]); // Create UNIT8ARRAY of size same as row data length const uInt8Array = new Uint8Array(decodedData.length); // Insert all character code into uInt8Array for (let i = 0; i < decodedData.length; ++i) { uInt8Array[i] = decodedData.charCodeAt(i); } // Return BLOB image after conversion return new Blob([uInt8Array], { type: imageType }); }; /** * CREATE_IMAGE */ const CREATE_IMAGE = () => { const img = new Image(); const ctx = CREATE_CANVAS_WITH_SIZE().getContext('2d'); img.onload = function () { ctx && ctx.drawImage(img, 0, 0); }; const blob = BASE64_TO_BLOB(ELEMENT_CONVERT_TO_BASE64(UPDATE_SIZE_SVG())); const blobUrl = URL.createObjectURL(blob); img.src = blobUrl; }; /** * MAIN */ (async function () { if (QRCODE_NONE_FRAME()) { subject.next({ config, container }); subject.complete(); return; // Mode qrcode basic } else { await ADD_FRAME_SVG_TO_ELEMENT(); const ADDSVG = UPDATE_POSITION_QRCODE_ON_FRAME(); await CREATE_QRCODE_INTO_FRAME(ADDSVG); UPDATE_ROTATE_SCALE_QRCODE_ON_FRAME(ADDSVG); if (QRCODE_TYPE_SVG()) { // Mode qrcode + frame type svg subject.next({ config, container }); subject.complete(); } else { // Mode qrcode + frame type canvas CREATE_IMAGE(); subject.next({ config, container }); subject.complete(); } } })(); return subject; } /** * defaultTemplate * @param config * @returns */ const defaultTemplate = (config) => { let deep = config && JSON.parse(JSON.stringify(config)); return config?.template ? { ...Templates(config.template.toLocaleLowerCase()), ...deep } : deep; }; /** * deepUpdate * @param config * @param configUpdate * @returns */ const deepUpdate = async (config, configUpdate) => { const origin = config && JSON.parse(JSON.stringify(config)); let clone = { ...origin, ...configUpdate }; const keys = [ 'frameOptions', 'qrOptions', 'imageOptions', 'dotsOptions', 'cornersSquareOptions', 'cornersDotOptions', 'backgroundOptions', ]; for await (const key of keys) { if (key in configUpdate) { const update = { [key]: { ...origin[key], ...configUpdate[key] }, }; clone = { ...clone, ...update }; } } return clone; }; class NgxQrcodeStylingService { /** * create * @param config * @param container * @returns */ create(config, container) { return drawQrcode(defaultTemplate(config), container); } /** * update * @param config * @param configUpdate * @param container * @returns */ update(config, configUpdate, container) { const subject = new AsyncSubject(); (async function () { const conf = await deepUpdate(defaultTemplate(config), defaultTemplate(configUpdate)); drawQrcode(conf, container).subscribe((s) => { subject.next(s); subject.complete(); }); })(); return subject; } /** * download image * @param fileName eg: demo.png * @param container * @param timeout * @returns */ download(container, fileName = '', timeout = 0) { const subject = new AsyncSubject(); const _fileName = (type) => { if (!fileName) { fileName = `ngx_qrcode_styling_${Date.now()}.${type}`; } }; const _download = (dataURL) => { const link = document.createElement('a'); link.download = fileName; link.href = dataURL; link.click(); link.remove(); }; const _complete = (url) => { if (url) { _download(url); subject.next({ fileName, container, url: url }); subject.complete(); } else { subject.error('Container not found!'); subject.complete(); } }; setTimeout(() => { const canvas = container?.querySelector('canvas'); if (canvas) { _fileName('png'); const typeImg = fileName ? fileName?.split('.')?.slice(-1)?.toString() : 'png'; canvas.toBlob((blob) => _complete(URL.createObjectURL(blob)), 'image/' + typeImg); } else { const svg = container?.querySelector('svg'); if (svg) { _fileName('svg'); const svgData = svg.outerHTML; const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8', }); const svgUrl = URL.createObjectURL(svgBlob); _complete(svgUrl); } else { _complete(''); } } }, timeout); return subject; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: NgxQrcodeStylingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: NgxQrcodeStylingService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: NgxQrcodeStylingService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); class NgxQrcodeStylingComponent { canvas; service; /** * Options */ config; /** * TemplateType */ template = 'default'; /** * DrawType */ type; /** * ShapeType */ shape; /** * number */ width; /** * number */ height; /** * number */ margin; /** * string */ data; /** * string */ image; /** * number */ scale = 0; /** * number */ rotate = 0; /** * number */ zIndex = 2; /** * object */ frameOptions; /** * object */ qrOptions; /** * object */ imageOptions; /** * object */ dotsOptions; /** * object */ cornersSquareOptions; /** * object */ cornersDotOptions; /** * object */ backgroundOptions; constructor(canvas, service) { this.canvas = canvas; this.service = service; } ngOnInit() { if (this.canvas) { /** * @Input override */ if (this.template) this.config = { ...this.config, template: this.template, }; if (this.type) this.config = { ...this.config, type: this.type, }; if (this.shape) this.config = { ...this.config, shape: this.shape, }; if (this.width) this.config = { ...this.config, width: this.width, }; if (this.height) this.config = { ...this.config, height: this.height, }; if (this.margin) this.config = { ...this.config, margin: this.margin, }; if (this.data) this.config = { ...this.config, data: this.data, }; if (this.image) this.config = { ...this.config, image: this.image, }; if (this.scale) this.config = { ...this.config, scale: this.scale, }; if (this.rotate) this.config = { ...this.config, rotate: this.rotate, }; if (this.zIndex) this.config = { ...this.config, zIndex: this.zIndex, }; if (this.frameOptions) this.config = { ...this.config, frameOptions: this.frameOptions, }; if (this.qrOptions) this.config = { ...this.config, qrOptions: this.qrOptions, }; if (this.imageOptions) this.config = { ...this.config, imageOptions: this.imageOptions, }; if (this.dotsOptions) this.config = { ...this.config, dotsOptions: this.dotsOptions, }; if (this.cornersSquareOptions) this.config = { ...this.config, cornersSquareOptions: this.cornersSquareOptions, }; if (this.cornersDotOptions) this.config = { ...this.config, cornersDotOptions: this.cornersDotOptions, }; if (this.backgroundOptions) this.config = { ...this.config, backgroundOptions: this.backgroundOptions, }; this.create(this.config); } } /** * create * @param config * @returns */ create(config) { return this.service.create(config, this.canvas.nativeElement); } /** * update * @param config * @param configUpdate * @returns */ update(config, configUpdate) { return this.service.update(config, configUpdate, this.canvas.nativeElement); } /** * download image * @param fileName eg: demo.png * @param timeout * @returns */ download(fileName, timeout = 0) { return this.service.download(this.canvas.nativeElement, fileName, timeout); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: NgxQrcodeStylingComponent, deps: [{ token: i0.ElementRef }, { token: NgxQrcodeStylingService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.0", type: NgxQrcodeStylingComponent, isStandalone: true, selector: "ngx-qrcode-styling", inputs: { config: "config", template: "template", type: "type", shape: "shape", width: "width", height: "height", margin: "margin", data: "data", image: "image", scale: "scale", rotate: "rotate", zIndex: "zIndex", frameOptions: "frameOptions", qrOptions: "qrOptions", imageOptions: "imageOptions", dotsOptions: "dotsOptions", cornersSquareOptions: "cornersSquareOptions", cornersDotOptions: "cornersDotOptions", backgroundOptions: "backgroundOptions" }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: NgxQrcodeStylingComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-qrcode-styling', template: '<ng-content></ng-content>', encapsulation: ViewEncapsulation.None, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: NgxQrcodeStylingService }], propDecorators: { config: [{ type: Input }], template: [{ type: Input }], type: [{ type: Input }], shape: [{ type: Input }], width: [{ type: Input }], height: [{ type: Input }], margin: [{ type: Input }], data: [{ type: Input }], image: [{ type: Input }], scale: [{ type: Input }], rotate: [{ type: Input }], zIndex: [{ type: Input }], frameOptions: [{ type: Input }], qrOptions: [{ type: Input }], imageOptions: [{ type: Input }], dotsOptions: [{ type: Input }], cornersSquareOptions: [{ type: Input }], cornersDotOptions: [{ type: Input }], backgroundOptions: [{ type: Input }] } }); /* * Public API Surface of ngx-qrcode-styling */ /** * Generated bundle index. Do not edit. */ export { NgxQrcodeStylingComponent, NgxQrcodeStylingService, Templates }; //# sourceMappingURL=ngx-qrcode-styling.mjs.map