From c6ad86c83b8e88ed19ad7c891b313bdb9877be3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Balet?= Date: Tue, 28 May 2024 09:16:43 +0200 Subject: [PATCH] perf(kjua): use library locally & with ts (#95) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Raphaƫl Balet --- package-lock.json | 20 +- package.json | 2 +- projects/ngx-kjua/src/lib/kjua/index.d.ts | 91 +++++ projects/ngx-kjua/src/lib/kjua/index.ts | 25 ++ .../src/lib/kjua/lib/create_canvas_qrcode.ts | 85 +++++ .../src/lib/kjua/lib/create_svg_qrcode.ts | 347 ++++++++++++++++++ .../ngx-kjua/src/lib/kjua/lib/defaults.ts | 56 +++ projects/ngx-kjua/src/lib/kjua/lib/dom.ts | 72 ++++ .../ngx-kjua/src/lib/kjua/lib/draw_mode.ts | 69 ++++ .../ngx-kjua/src/lib/kjua/lib/draw_rounded.ts | 163 ++++++++ projects/ngx-kjua/src/lib/kjua/lib/qrcode.ts | 47 +++ .../ngx-kjua/src/lib/ngx-kjua.component.ts | 5 +- 12 files changed, 968 insertions(+), 14 deletions(-) create mode 100644 projects/ngx-kjua/src/lib/kjua/index.d.ts create mode 100644 projects/ngx-kjua/src/lib/kjua/index.ts create mode 100644 projects/ngx-kjua/src/lib/kjua/lib/create_canvas_qrcode.ts create mode 100644 projects/ngx-kjua/src/lib/kjua/lib/create_svg_qrcode.ts create mode 100644 projects/ngx-kjua/src/lib/kjua/lib/defaults.ts create mode 100644 projects/ngx-kjua/src/lib/kjua/lib/dom.ts create mode 100644 projects/ngx-kjua/src/lib/kjua/lib/draw_mode.ts create mode 100644 projects/ngx-kjua/src/lib/kjua/lib/draw_rounded.ts create mode 100644 projects/ngx-kjua/src/lib/kjua/lib/qrcode.ts diff --git a/package-lock.json b/package-lock.json index 46a3093..e36d6ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ngx-kjua-library", - "version": "17.0.0", + "version": "18.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ngx-kjua-library", - "version": "17.0.0", + "version": "18.0.0", "license": "MIT", "dependencies": { "@angular/common": "^18.0.0", @@ -17,7 +17,6 @@ "@angular/language-service": "^18.0.0", "@angular/platform-browser": "^18.0.0", "@angular/platform-browser-dynamic": "^18.0.0", - "kjua-svg": "^1.13.1", "tslib": "^2.6.2", "zone.js": "~0.14.6" }, @@ -30,6 +29,7 @@ "jest": "^29.7.0", "jest-preset-angular": "^14.1.0", "ng-packagr": "^18.0.0", + "qrcode-generator": "^1.4.4", "rxjs": "~7.8.1", "ts-node": "~10.9.2", "tslint": "~6.1.3", @@ -11050,14 +11050,6 @@ "node": ">=0.10.0" } }, - "node_modules/kjua-svg": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/kjua-svg/-/kjua-svg-1.13.1.tgz", - "integrity": "sha512-qGcT0L3oymlJtKvVV0TIR24oXEJ76Mpc6jSCyV1Piqog664JChG5Ho5dJ44MFEwf650rJSiop1yFKW36f7J70g==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -13291,6 +13283,12 @@ } ] }, + "node_modules/qrcode-generator": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz", + "integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==", + "dev": true + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", diff --git a/package.json b/package.json index c5e0816..7a2df18 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "@angular/language-service": "^18.0.0", "@angular/platform-browser": "^18.0.0", "@angular/platform-browser-dynamic": "^18.0.0", - "kjua-svg": "^1.13.1", "tslib": "^2.6.2", "zone.js": "~0.14.6" }, @@ -55,6 +54,7 @@ "jest": "^29.7.0", "jest-preset-angular": "^14.1.0", "ng-packagr": "^18.0.0", + "qrcode-generator": "^1.4.4", "rxjs": "~7.8.1", "ts-node": "~10.9.2", "tslint": "~6.1.3", diff --git a/projects/ngx-kjua/src/lib/kjua/index.d.ts b/projects/ngx-kjua/src/lib/kjua/index.d.ts new file mode 100644 index 0000000..09a0b92 --- /dev/null +++ b/projects/ngx-kjua/src/lib/kjua/index.d.ts @@ -0,0 +1,91 @@ +export interface KjuaOptions { + /** + * Render the image as either SVG, canvas or PNG-image + */ + render?: "image" | "svg" | "canvas"; + + /** + * render pixel-perfect lines + */ + crisp?: boolean; + + /** + * minimum version: 1..40 + */ + minVersion?: number; + + /** + * error correction level: 'L', 'M', 'Q' or 'H' + */ + ecLevel?: "L" | "M" | "Q" | "H"; + + /** + * size in pixel + */ + size?: number; + + /** + * pixel-ratio, null for devicePixelRatio + */ + ratio?: number | null; + + /** + * code color + */ + fill?: string; + + /** + * background color + */ + back?: string; + + /** + * content + */ + text?: string; + + /** + * roundend corners in pc: 0..100 + */ + rounded?: number; + + /** + * quiet zone in modules + */ + quiet?: number; + + /** + * modes: 'plain', 'label', 'image', 'imagelabel' or 'labelimage' + */ + mode?: "plain" | "label" | "image" | "imagelabel" | "labelimage"; + + /** + * label/image size and pos in pc: 0..100 + */ + mSize?: number | number[]; + mPosX?: number | number[]; + mPosY?: number | number[]; + + /** + * label + */ + label?: string; + fontname?: string; + fontcolor?: string; + fontoutline?: boolean; + + /** + * image element + */ + image?: null | HTMLImageElement | string; + + /** + * draw the image as part of the code + */ + imageAsCode?: boolean; + + /** + * an optional HTML-ID-attribute for the element (works only with render-mode SVG and image) + */ + elementId?: string; +} diff --git a/projects/ngx-kjua/src/lib/kjua/index.ts b/projects/ngx-kjua/src/lib/kjua/index.ts new file mode 100644 index 0000000..d251d6e --- /dev/null +++ b/projects/ngx-kjua/src/lib/kjua/index.ts @@ -0,0 +1,25 @@ +import { create_canvas_qrcode } from "./lib/create_canvas_qrcode"; +import { create_svg_qrcode } from "./lib/create_svg_qrcode"; +import { defaults } from "./lib/defaults"; +import { quiet_qrcode } from "./lib/qrcode"; + +export const kjua = (options: any): any => { + const settings = Object.assign({}, defaults, options); + const qr = quiet_qrcode( + settings.text, + settings.ecLevel, + settings.minVersion, + settings.quiet + ); + + if (typeof settings.image === "string") { + const image = new Image(); + image.src = "data:image/png;base64," + settings.image; + image.crossOrigin = "anonymous"; + settings.image = image; + } + if (settings.render === "svg") { + return create_svg_qrcode(qr, settings); + } + return create_canvas_qrcode(qr, settings, settings.render === "image"); +}; diff --git a/projects/ngx-kjua/src/lib/kjua/lib/create_canvas_qrcode.ts b/projects/ngx-kjua/src/lib/kjua/lib/create_canvas_qrcode.ts new file mode 100644 index 0000000..dca2e15 --- /dev/null +++ b/projects/ngx-kjua/src/lib/kjua/lib/create_canvas_qrcode.ts @@ -0,0 +1,85 @@ +import * as dom from "./dom"; +import { draw_mode } from "./draw_mode"; +import { draw_module_rounded } from "./draw_rounded"; + +const draw_background = (ctx: any, settings: any) => { + if (settings.back) { + ctx.fillStyle = settings.back; + ctx.fillRect(0, 0, settings.size, settings.size); + } +}; + +const draw_module_default = ( + qr: any, + ctx: any, + settings: any, + width: any, + row: any, + col: any +) => { + if (qr.is_dark(row, col)) { + ctx.rect(col * width, row * width, width, width); + } +}; + +export const draw_modules = (qr: any, ctx: any, settings: any) => { + if (!qr) { + return; + } + + const draw_module = + settings.rounded > 0 && settings.rounded <= 100 + ? draw_module_rounded + : draw_module_default; + const mod_count = qr.module_count; + + let mod_size = settings.size / mod_count; + let offset = 0; + if (settings.crisp) { + mod_size = Math.floor(mod_size); + offset = Math.floor((settings.size - mod_size * mod_count) / 2); + } + + ctx.translate(offset, offset); + ctx.beginPath(); + for (let row = 0; row < mod_count; row += 1) { + for (let col = 0; col < mod_count; col += 1) { + draw_module(qr, ctx, settings, mod_size, row, col); + } + } + ctx.fillStyle = settings.fill; + ctx.fill(); + ctx.translate(-offset, -offset); +}; + +const draw = (qr: any, ctx: any, settings: any) => { + draw_background(ctx, settings); + draw_modules(qr, ctx, settings); + draw_mode(ctx, settings); +}; + +export const create_canvas_qrcode = (qr: any, settings: any, as_image: any) => { + const ratio = settings.ratio || dom.dpr; + const canvas = dom.create_canvas(settings.size, ratio); + const context = canvas.getContext("2d"); + + if (settings.imageAsCode) { + const canvas = dom.create_canvas(settings.size, ratio); + const ctx2 = canvas.getContext("2d"); + draw_modules(qr, ctx2, settings); + const imagePos = dom.calc_image_pos(settings); + ctx2.globalCompositeOperation = "source-in"; + ctx2.drawImage( + settings.image, + imagePos.x, + imagePos.y, + imagePos.iw, + imagePos.ih + ); + settings = Object.assign({}, settings, { image: ctx2.canvas }); + } + + context.scale(ratio, ratio); + draw(qr, context, settings); + return as_image ? dom.canvas_to_img(canvas, settings.elementId) : canvas; +}; diff --git a/projects/ngx-kjua/src/lib/kjua/lib/create_svg_qrcode.ts b/projects/ngx-kjua/src/lib/kjua/lib/create_svg_qrcode.ts new file mode 100644 index 0000000..b3c3846 --- /dev/null +++ b/projects/ngx-kjua/src/lib/kjua/lib/create_svg_qrcode.ts @@ -0,0 +1,347 @@ +import { draw_modules } from "./create_canvas_qrcode"; +import { + SVG_NS, + calc_image_pos, + create_canvas, + create_svg_el, + dpr, + get_attr, +} from "./dom"; + +const create_draw_ctx = (ctx: any) => { + const rnd = (x: any) => Math.round(x * 10) / 10; + const rndo = (x: any) => Math.round(x * 10) / 10 + ctx.o; + return { + m(x: any, y: any) { + ctx.p += `M ${rndo(x)} ${rndo(y)} `; + return this; + }, + l(x: any, y: any) { + ctx.p += `L ${rndo(x)} ${rndo(y)} `; + return this; + }, + a(x: any, y: any, rad: any) { + ctx.p += `A ${rnd(rad)} ${rnd(rad)} 0 0 1 ${rndo(x)} ${rndo(y)} `; + return this; + }, + }; +}; + +const draw_dark = ( + ctx: any, + l: any, + t: any, + r: any, + b: any, + rad: any, + nw: any, + ne: any, + se: any, + sw: any +) => { + if (nw) { + ctx.m(l + rad, t); + } else { + ctx.m(l, t); + } + + if (ne) { + ctx.l(r - rad, t).a(r, t + rad, rad); + } else { + ctx.l(r, t); + } + + if (se) { + ctx.l(r, b - rad).a(r - rad, b, rad); + } else { + ctx.l(r, b); + } + + if (sw) { + ctx.l(l + rad, b).a(l, b - rad, rad); + } else { + ctx.l(l, b); + } + + if (nw) { + ctx.l(l, t + rad).a(l + rad, t, rad); + } else { + ctx.l(l, t); + } +}; + +const draw_light = ( + ctx: any, + l: any, + t: any, + r: any, + b: any, + rad: any, + nw: any, + ne: any, + se: any, + sw: any +) => { + if (nw) { + ctx + .m(l + rad, t) + .l(l, t) + .l(l, t + rad) + .a(l + rad, t, rad); + } + + if (ne) { + ctx + .m(r, t + rad) + .l(r, t) + .l(r - rad, t) + .a(r, t + rad, rad); + } + + if (se) { + ctx + .m(r - rad, b) + .l(r, b) + .l(r, b - rad) + .a(r - rad, b, rad); + } + + if (sw) { + ctx + .m(l, b - rad) + .l(l, b) + .l(l + rad, b) + .a(l, b - rad, rad); + } +}; + +const draw_mod = ( + qr: any, + ctx: any, + settings: any, + width: any, + row: any, + col: any +) => { + const left = col * width; + const top = row * width; + const right = left + width; + const bottom = top + width; + const radius = settings.rounded * 0.005 * width; + + const is_dark = qr.is_dark; + const row_n = row - 1; + const row_s = row + 1; + const col_w = col - 1; + const col_e = col + 1; + const dark_center = is_dark(row, col); + const dark_nw = is_dark(row_n, col_w); + const dark_n = is_dark(row_n, col); + const dark_ne = is_dark(row_n, col_e); + const dark_e = is_dark(row, col_e); + const dark_se = is_dark(row_s, col_e); + const dark_s = is_dark(row_s, col); + const dark_sw = is_dark(row_s, col_w); + const dark_w = is_dark(row, col_w); + + if (dark_center) { + draw_dark( + ctx, + left, + top, + right, + bottom, + radius, + !dark_n && !dark_w, + !dark_n && !dark_e, + !dark_s && !dark_e, + !dark_s && !dark_w + ); + } else { + draw_light( + ctx, + left, + top, + right, + bottom, + radius, + dark_n && dark_w && dark_nw, + dark_n && dark_e && dark_ne, + dark_s && dark_e && dark_se, + dark_s && dark_w && dark_sw + ); + } +}; + +const create_path = (qr: any, settings: any) => { + if (!qr) { + return ""; + } + + const ctx = { p: "", o: 0 }; + const mod_count = qr.module_count; + let mod_size = settings.size / mod_count; + if (settings.crisp) { + mod_size = Math.floor(mod_size); + ctx.o = Math.floor((settings.size - mod_size * mod_count) / 2); + } + + const draw_ctx = create_draw_ctx(ctx); + for (let row = 0; row < mod_count; row += 1) { + for (let col = 0; col < mod_count; col += 1) { + draw_mod(qr, draw_ctx, settings, mod_size, row, col); + } + } + + return ctx.p; +}; + +const add_label = (el: any, settings: any) => { + let mSize = settings.mSize; + let mPosX = settings.mPosX; + let mPosY = settings.mPosY; + let arrayPos = 0; + if (settings.mode === "imagelabel") { + arrayPos = 1; + } + if (Array.isArray(settings.mSize)) { + mSize = settings.mSize[arrayPos]; + } + if (Array.isArray(settings.mPosX)) { + mPosX = settings.mPosX[arrayPos]; + } + if (Array.isArray(settings.mPosY)) { + mPosY = settings.mPosY[arrayPos]; + } + + const size = settings.size; + const font = "bold " + mSize * 0.01 * size + "px " + settings.fontname; + + const ratio = settings.ratio || dpr; + const ctx = create_canvas(size, ratio).getContext("2d"); + ctx.strokeStyle = settings.back; + ctx.lineWidth = mSize * 0.01 * size * 0.1; + ctx.fillStyle = settings.fontcolor; + ctx.font = font; + const w = ctx.measureText(settings.label).width; + + const sh = mSize * 0.01; + const sw = w / size; + const sl = (1 - sw) * mPosX * 0.01; + const st = (1 - sh) * mPosY * 0.01; + const x = sl * size; + const y = st * size + 0.75 * mSize * 0.01 * size; + + const text_el = create_svg_el("text", { + x, + y, + }); + Object.assign(text_el.style, { + font, + fill: settings.fontcolor, + "paint-order": "stroke", + stroke: settings.back, + "stroke-width": ctx.lineWidth, + }); + + text_el.textContent = settings.label; + el.appendChild(text_el); +}; + +const add_image = (el: any, settings: any) => { + let img_el; + if (settings.imageAsCode) { + img_el = create_svg_el("image", { + href: settings.image, + x: 0, + y: 0, + width: settings.size, + height: settings.size, + }); + } else { + const imagePos = calc_image_pos(settings); + img_el = create_svg_el("image", { + href: settings.image, + x: imagePos.x, + y: imagePos.y, + width: imagePos.iw, + height: imagePos.ih, + }); + } + el.appendChild(img_el); +}; + +export const create_svg_qrcode = (qr: any, settings: any) => { + const size = settings.size; + const mode = settings.mode; + + const svg_el = create_svg_el("svg", { + xmlns: SVG_NS, + width: size, + height: size, + viewBox: `0 0 ${size} ${size}`, + }); + svg_el.style.width = `${size}px`; + svg_el.style.height = `${size}px`; + svg_el.setAttribute("title", settings.text); + if (!!settings.elementId) { + svg_el.setAttribute("id", settings.elementId); + } + + if (settings.back) { + svg_el.appendChild( + create_svg_el("rect", { + x: 0, + y: 0, + width: size, + height: size, + fill: settings.back, + }) + ); + } + + svg_el.appendChild( + create_svg_el("path", { + d: create_path(qr, settings), + fill: settings.fill, + }) + ); + + if (settings.image) { + if (settings.imageAsCode) { + const ratio = settings.ratio || dpr; + const canvas = create_canvas(settings.size, ratio); + const ctx2 = canvas.getContext("2d"); + draw_modules(qr, ctx2, settings); + const imagePos = calc_image_pos(settings); + ctx2.globalCompositeOperation = "source-in"; + ctx2.drawImage( + settings.image, + imagePos.x, + imagePos.y, + imagePos.iw, + imagePos.ih + ); + settings = Object.assign({}, settings, { + image: ctx2.canvas.toDataURL(), + }); + } else { + settings = Object.assign({}, settings, { + image: get_attr(settings.image, "src"), + }); + } + } + if (mode === "label") { + add_label(svg_el, settings); + } else if (mode === "image") { + add_image(svg_el, settings); + } else if (mode === "labelimage") { + add_label(svg_el, settings); + add_image(svg_el, settings); + } else if (mode === "imagelabel") { + add_image(svg_el, settings); + add_label(svg_el, settings); + } + + return svg_el; +}; diff --git a/projects/ngx-kjua/src/lib/kjua/lib/defaults.ts b/projects/ngx-kjua/src/lib/kjua/lib/defaults.ts new file mode 100644 index 0000000..2f04320 --- /dev/null +++ b/projects/ngx-kjua/src/lib/kjua/lib/defaults.ts @@ -0,0 +1,56 @@ +export const defaults = { + // render method: 'canvas' or 'image' or 'svg' + render: "svg", + + // render pixel-perfect lines + crisp: true, + + // minimum version: 1..40 + minVersion: 1, + + // error correction level: 'L', 'M', 'Q' or 'H' + ecLevel: "L", + + // size in pixel + size: 200, + + // pixel-ratio, null for devicePixelRatio + ratio: null, + + // code color + fill: "#333", + + // background color + back: "#fff", + + // content + text: "", + + // rounded corners in pc: 0..100 + rounded: 0, + + // quiet zone in modules + quiet: 0, + + // modes: 'plain', 'label', 'image', 'imagelabel' or 'labelimage' + mode: "plain", + + // label/image size and pos in pc: 0..100 + mSize: 30, + mPosX: 50, + mPosY: 50, + + // label + label: "", + fontname: "sans", + fontcolor: "#333", + fontoutline: true, + + // image element + image: null, + + // draw the image as part of the code + imageAsCode: false, + + elementId: null, +}; diff --git a/projects/ngx-kjua/src/lib/kjua/lib/dom.ts b/projects/ngx-kjua/src/lib/kjua/lib/dom.ts new file mode 100644 index 0000000..ac0fe60 --- /dev/null +++ b/projects/ngx-kjua/src/lib/kjua/lib/dom.ts @@ -0,0 +1,72 @@ +const win = window; // eslint-disable-line no-undef +const doc = win.document; +export const dpr = win.devicePixelRatio || 1; + +export const SVG_NS = "http://www.w3.org/2000/svg"; + +export const get_attr = (el: any, key: any) => el.getAttribute(key); +export const set_attrs = (el: any, obj: any) => { + Object.keys(obj || {}).forEach((key) => { + el.setAttribute(key, obj[key]); + }); + return el; +}; + +export const create_el = (name: any, obj: any) => + set_attrs(doc.createElement(name), obj); +export const create_svg_el = (name: any, obj: any) => + set_attrs(doc.createElementNS(SVG_NS, name), obj); + +export const create_canvas = (size: any, ratio: any) => { + const canvas = create_el("canvas", { + width: size * ratio, + height: size * ratio, + }); + canvas.style.width = `${size}px`; + canvas.style.height = `${size}px`; + return canvas; +}; + +export const canvas_to_img = (canvas: any, elementId: any) => { + const img = create_el("img", { + crossOrigin: "anonymous", + src: canvas.toDataURL("image/png"), + width: get_attr(canvas, "width"), + height: get_attr(canvas, "height"), + id: elementId, + }); + img.style.width = canvas.style.width; + img.style.height = canvas.style.height; + return img; +}; + +export const calc_image_pos = (settings: any) => { + let mSize = settings.mSize; + let mPosX = settings.mPosX; + let mPosY = settings.mPosY; + let arrayPos = 0; + if (settings.mode === "labelimage") { + arrayPos = 1; + } + if (Array.isArray(settings.mSize)) { + mSize = settings.mSize[arrayPos]; + } + if (Array.isArray(settings.mPosX)) { + mPosX = settings.mPosX[arrayPos]; + } + if (Array.isArray(settings.mPosY)) { + mPosY = settings.mPosY[arrayPos]; + } + const size = settings.size; + const w = settings.image.naturalWidth || 1; + const h = settings.image.naturalHeight || 1; + const sh = mSize * 0.01; + const sw = (sh * w) / h; + const sl = (1 - sw) * mPosX * 0.01; + const st = (1 - sh) * mPosY * 0.01; + const x = sl * size; + const y = st * size; + const iw = sw * size; + const ih = sh * size; + return { x, y, iw, ih }; +}; diff --git a/projects/ngx-kjua/src/lib/kjua/lib/draw_mode.ts b/projects/ngx-kjua/src/lib/kjua/lib/draw_mode.ts new file mode 100644 index 0000000..f0f7c98 --- /dev/null +++ b/projects/ngx-kjua/src/lib/kjua/lib/draw_mode.ts @@ -0,0 +1,69 @@ +import { calc_image_pos } from "./dom"; + +const draw_label = (ctx: any, settings: any) => { + let mSize = settings.mSize; + let mPosX = settings.mPosX; + let mPosY = settings.mPosY; + let arrayPos = 0; + if (settings.mode === "imagelabel") { + arrayPos = 1; + } + if (Array.isArray(settings.mSize)) { + mSize = settings.mSize[arrayPos]; + } + if (Array.isArray(settings.mPosX)) { + mPosX = settings.mPosX[arrayPos]; + } + if (Array.isArray(settings.mPosY)) { + mPosY = settings.mPosY[arrayPos]; + } + + const size = settings.size; + const font = "bold " + mSize * 0.01 * size + "px " + settings.fontname; + + ctx.strokeStyle = settings.back; + ctx.lineWidth = mSize * 0.01 * size * 0.1; + ctx.fillStyle = settings.fontcolor; + ctx.font = font; + + const w = ctx.measureText(settings.label).width; + const sh = mSize * 0.01; + const sw = w / size; + const sl = (1 - sw) * mPosX * 0.01; + const st = (1 - sh) * mPosY * 0.01; + const x = sl * size; + const y = st * size + 0.75 * mSize * 0.01 * size; + + ctx.strokeText(settings.label, x, y); + ctx.fillText(settings.label, x, y); +}; + +const draw_image = (ctx: any, settings: any) => { + const imagePos = calc_image_pos(settings); + if (settings.imageAsCode) { + ctx.drawImage(settings.image, 0, 0, settings.size, settings.size); + } else { + ctx.drawImage( + settings.image, + imagePos.x, + imagePos.y, + imagePos.iw, + imagePos.ih + ); + } +}; + +export const draw_mode = (ctx: any, settings: any) => { + const mode = settings.mode; + if (mode === "label") { + draw_label(ctx, settings); + } else if (mode === "image") { + draw_image(ctx, settings); + } else if (mode === "labelimage") { + draw_label(ctx, settings); + draw_image(ctx, settings); + } else if (mode === "imagelabel") { + draw_image(ctx, settings); + draw_label(ctx, settings); + } +}; diff --git a/projects/ngx-kjua/src/lib/kjua/lib/draw_rounded.ts b/projects/ngx-kjua/src/lib/kjua/lib/draw_rounded.ts new file mode 100644 index 0000000..219acdd --- /dev/null +++ b/projects/ngx-kjua/src/lib/kjua/lib/draw_rounded.ts @@ -0,0 +1,163 @@ +const create_draw_ctx = (ctx: any) => { + return { + m(x: any, y: any) { + ctx.moveTo(x, y); + return this; + }, + l(x: any, y: any) { + ctx.lineTo(x, y); + return this; + }, + a(...args: any) { + ctx.arcTo(...args); + return this; + }, + }; +}; + +const draw_dark = ( + ctx: any, + l: any, + t: any, + r: any, + b: any, + rad: any, + nw: any, + ne: any, + se: any, + sw: any +) => { + if (nw) { + ctx.m(l + rad, t); + } else { + ctx.m(l, t); + } + + if (ne) { + ctx.l(r - rad, t).a(r, t, r, b, rad); + } else { + ctx.l(r, t); + } + + if (se) { + ctx.l(r, b - rad).a(r, b, l, b, rad); + } else { + ctx.l(r, b); + } + + if (sw) { + ctx.l(l + rad, b).a(l, b, l, t, rad); + } else { + ctx.l(l, b); + } + + if (nw) { + ctx.l(l, t + rad).a(l, t, r, t, rad); + } else { + ctx.l(l, t); + } +}; + +export const draw_light = ( + ctx: any, + l: any, + t: any, + r: any, + b: any, + rad: any, + nw: any, + ne: any, + se: any, + sw: any +) => { + if (nw) { + ctx + .m(l + rad, t) + .l(l, t) + .l(l, t + rad) + .a(l, t, l + rad, t, rad); + } + + if (ne) { + ctx + .m(r - rad, t) + .l(r, t) + .l(r, t + rad) + .a(r, t, r - rad, t, rad); + } + + if (se) { + ctx + .m(r - rad, b) + .l(r, b) + .l(r, b - rad) + .a(r, b, r - rad, b, rad); + } + + if (sw) { + ctx + .m(l + rad, b) + .l(l, b) + .l(l, b - rad) + .a(l, b, l + rad, b, rad); + } +}; + +export const draw_module_rounded = ( + qr: any, + ctx: any, + settings: any, + width: any, + row: any, + col: any +) => { + const left = col * width; + const top = row * width; + const right = left + width; + const bottom = top + width; + const radius = settings.rounded * 0.005 * width; + + const is_dark = qr.is_dark; + const row_n = row - 1; + const row_s = row + 1; + const col_w = col - 1; + const col_e = col + 1; + const dark_center = is_dark(row, col); + const dark_nw = is_dark(row_n, col_w); + const dark_n = is_dark(row_n, col); + const dark_ne = is_dark(row_n, col_e); + const dark_e = is_dark(row, col_e); + const dark_se = is_dark(row_s, col_e); + const dark_s = is_dark(row_s, col); + const dark_sw = is_dark(row_s, col_w); + const dark_w = is_dark(row, col_w); + + const draw_ctx = create_draw_ctx(ctx); + if (dark_center) { + draw_dark( + draw_ctx, + left, + top, + right, + bottom, + radius, + !dark_n && !dark_w, + !dark_n && !dark_e, + !dark_s && !dark_e, + !dark_s && !dark_w + ); + } else { + draw_light( + draw_ctx, + left, + top, + right, + bottom, + radius, + dark_n && dark_w && dark_nw, + dark_n && dark_e && dark_ne, + dark_s && dark_e && dark_se, + dark_s && dark_w && dark_sw + ); + } +}; diff --git a/projects/ngx-kjua/src/lib/kjua/lib/qrcode.ts b/projects/ngx-kjua/src/lib/kjua/lib/qrcode.ts new file mode 100644 index 0000000..9a4ea59 --- /dev/null +++ b/projects/ngx-kjua/src/lib/kjua/lib/qrcode.ts @@ -0,0 +1,47 @@ +const RE_CODE_LENGTH_OVERFLOW = /code length overflow/i; + +import * as qr_gen from "qrcode-generator"; +(qr_gen as any).stringToBytes = qr_gen.stringToBytesFuncs["UTF-8"]; + +const min_qrcode = (text: any, level: any, min_ver: number = 1) => { + min_ver = Math.max(1, min_ver); + + for (let version = min_ver; version <= 40; version += 1) { + try { + const qr = (qr_gen as any)(version, level); + qr.addData(text); + qr.make(); + const module_count = qr.getModuleCount(); + const is_dark = (row: any, col: any) => { + return ( + row >= 0 && + row < module_count && + col >= 0 && + col < module_count && + qr.isDark(row, col) + ); + }; + return { text, level, version, module_count, is_dark }; + } catch (err) { + if (!(version < 40 && RE_CODE_LENGTH_OVERFLOW.test(err as string))) { + throw new Error(err as string); + } + } + } + return null; +}; + +export const quiet_qrcode = ( + text = "", + level = "L", + min_ver = 1, + quiet = 0 +) => { + const qr = min_qrcode(text, level, min_ver); + if (qr) { + const prev_is_dark = qr.is_dark; + qr.module_count += 2 * quiet; + qr.is_dark = (row, col) => prev_is_dark(row - quiet, col - quiet); + } + return qr; +}; diff --git a/projects/ngx-kjua/src/lib/ngx-kjua.component.ts b/projects/ngx-kjua/src/lib/ngx-kjua.component.ts index 8d3870b..e7cfda3 100644 --- a/projects/ngx-kjua/src/lib/ngx-kjua.component.ts +++ b/projects/ngx-kjua/src/lib/ngx-kjua.component.ts @@ -10,13 +10,14 @@ import { SimpleChanges, ViewChild, } from "@angular/core"; -import kjua, { KjuaOptions } from "kjua-svg"; +import { kjua } from "./kjua/index"; +import { KjuaOptions } from "./kjua/index.d"; import { KjuaEcLevel, KjuaMode, KjuaRender } from "./ngx-kjua.interface"; @Component({ standalone: true, selector: "ngx-kjua", - template: `
`, + template: `
`, styles: [ ` :host {