@mesgflow/ui-kit 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/mesgflow-ui-kit-accordion.mjs +6 -6
- package/fesm2022/mesgflow-ui-kit-agenda.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-alert.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-aspect-ratio.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-avatar-group.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-avatar.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-badge.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-banner.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-blocks.mjs +16 -16
- package/fesm2022/mesgflow-ui-kit-blocks.mjs.map +1 -1
- package/fesm2022/mesgflow-ui-kit-box-input.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-brand.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-breadcrumb.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-button-group.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-button.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-card.mjs +12 -12
- package/fesm2022/mesgflow-ui-kit-carousel.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-chart.mjs +12 -12
- package/fesm2022/mesgflow-ui-kit-checkbox.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-chip.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-code-block.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-collapsible.mjs +9 -9
- package/fesm2022/mesgflow-ui-kit-color-picker.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-combobox.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-command.mjs +15 -16
- package/fesm2022/mesgflow-ui-kit-command.mjs.map +1 -1
- package/fesm2022/mesgflow-ui-kit-context-menu.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-copy-button.mjs +6 -6
- package/fesm2022/mesgflow-ui-kit-core.mjs +9 -4
- package/fesm2022/mesgflow-ui-kit-core.mjs.map +1 -1
- package/fesm2022/mesgflow-ui-kit-countdown.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-date-picker.mjs +12 -12
- package/fesm2022/mesgflow-ui-kit-description-list.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-dialog.mjs +197 -34
- package/fesm2022/mesgflow-ui-kit-dialog.mjs.map +1 -1
- package/fesm2022/mesgflow-ui-kit-divider.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-drawer.mjs +4 -4
- package/fesm2022/mesgflow-ui-kit-drawer.mjs.map +1 -1
- package/fesm2022/mesgflow-ui-kit-dropzone.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-empty-state.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-field.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-file-upload.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-hover-card.mjs +6 -6
- package/fesm2022/mesgflow-ui-kit-i18n-tr.mjs +4 -0
- package/fesm2022/mesgflow-ui-kit-i18n-tr.mjs.map +1 -1
- package/fesm2022/mesgflow-ui-kit-icon.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-inline-edit.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-input.mjs +18 -18
- package/fesm2022/mesgflow-ui-kit-kbd.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-lightbox.mjs +9 -9
- package/fesm2022/mesgflow-ui-kit-link.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-listbox.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-mention.mjs +6 -6
- package/fesm2022/mesgflow-ui-kit-menu.mjs +12 -12
- package/fesm2022/mesgflow-ui-kit-nav-menu.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-notification-center.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-number-input.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-otp-input.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-page-header.mjs +6 -6
- package/fesm2022/mesgflow-ui-kit-pagination.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-password-input.mjs +35 -70
- package/fesm2022/mesgflow-ui-kit-password-input.mjs.map +1 -1
- package/fesm2022/mesgflow-ui-kit-popover.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-progress.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-qr-code.mjs +766 -0
- package/fesm2022/mesgflow-ui-kit-qr-code.mjs.map +1 -0
- package/fesm2022/mesgflow-ui-kit-radio.mjs +6 -6
- package/fesm2022/mesgflow-ui-kit-rating.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-scroll-area.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-search-input.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-segmented-control.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-select.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-sidebar.mjs +12 -12
- package/fesm2022/mesgflow-ui-kit-skeleton.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-slider.mjs +6 -6
- package/fesm2022/mesgflow-ui-kit-sortable.mjs +6 -6
- package/fesm2022/mesgflow-ui-kit-spinner.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-split-button.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-splitter.mjs +12 -12
- package/fesm2022/mesgflow-ui-kit-stat-tile.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-stepper.mjs +6 -6
- package/fesm2022/mesgflow-ui-kit-switch.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-table.mjs +21 -21
- package/fesm2022/mesgflow-ui-kit-tabs.mjs +27 -27
- package/fesm2022/mesgflow-ui-kit-tag-input.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-theme-switcher.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-theming.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-time-picker.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-timeline.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-toast.mjs +6 -6
- package/fesm2022/mesgflow-ui-kit-toggle.mjs +9 -9
- package/fesm2022/mesgflow-ui-kit-toolbar.mjs +9 -9
- package/fesm2022/mesgflow-ui-kit-tooltip.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-tour.mjs +9 -9
- package/fesm2022/mesgflow-ui-kit-transfer-list.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-tree-select.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-tree.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit-virtual-list.mjs +3 -3
- package/fesm2022/mesgflow-ui-kit.mjs +1 -0
- package/fesm2022/mesgflow-ui-kit.mjs.map +1 -1
- package/package.json +5 -1
- package/tokens.json +1 -1
- package/types/mesgflow-ui-kit-command.d.ts +2 -2
- package/types/mesgflow-ui-kit-core.d.ts +10 -2
- package/types/mesgflow-ui-kit-dialog.d.ts +97 -12
- package/types/mesgflow-ui-kit-password-input.d.ts +33 -5
- package/types/mesgflow-ui-kit-qr-code.d.ts +160 -0
- package/types/mesgflow-ui-kit.d.ts +1 -0
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Directive, input, numberAttribute, output, inject, contentChild, computed, effect, Component } from '@angular/core';
|
|
3
|
+
import { MF_QR_CODE_LABELS, cn } from '@mesgflow/ui-kit/core';
|
|
4
|
+
|
|
5
|
+
const FINDER = 7;
|
|
6
|
+
/** SVG yolu sayıları — gereksiz ondalıkları at, çıktıyı kısa tut. */
|
|
7
|
+
function n(value) {
|
|
8
|
+
return String(Math.round(value * 1000) / 1000);
|
|
9
|
+
}
|
|
10
|
+
function isFinder(x, y, size) {
|
|
11
|
+
return ((x < FINDER && y < FINDER) ||
|
|
12
|
+
(x >= size - FINDER && y < FINDER) ||
|
|
13
|
+
(x < FINDER && y >= size - FINDER));
|
|
14
|
+
}
|
|
15
|
+
/** Saat yönünde yuvarlatılmış dikdörtgen alt yolu. */
|
|
16
|
+
function roundedRect(x, y, w, h, r) {
|
|
17
|
+
if (r <= 0)
|
|
18
|
+
return `M${n(x)} ${n(y)}h${n(w)}v${n(h)}h${n(-w)}z`;
|
|
19
|
+
return (`M${n(x + r)} ${n(y)}h${n(w - 2 * r)}a${n(r)} ${n(r)} 0 0 1 ${n(r)} ${n(r)}` +
|
|
20
|
+
`v${n(h - 2 * r)}a${n(r)} ${n(r)} 0 0 1 ${n(-r)} ${n(r)}` +
|
|
21
|
+
`h${n(-(w - 2 * r))}a${n(r)} ${n(r)} 0 0 1 ${n(-r)} ${n(-r)}` +
|
|
22
|
+
`v${n(-(h - 2 * r))}a${n(r)} ${n(r)} 0 0 1 ${n(r)} ${n(-r)}z`);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Köşe başına yarıçaplı tek modül — bir köşe yalnızca o köşeye bakan İKİ komşu
|
|
26
|
+
* de açıksa yuvarlanır. Sonuç: yan yana modüller kesintisiz "akışkan" bloklar,
|
|
27
|
+
* yalnız modüller daire olur.
|
|
28
|
+
*/
|
|
29
|
+
function blobModule(x, y, tl, tr, br, bl) {
|
|
30
|
+
let d = `M${n(x + tl)} ${n(y)}H${n(x + 1 - tr)}`;
|
|
31
|
+
if (tr)
|
|
32
|
+
d += `a${n(tr)} ${n(tr)} 0 0 1 ${n(tr)} ${n(tr)}`;
|
|
33
|
+
d += `V${n(y + 1 - br)}`;
|
|
34
|
+
if (br)
|
|
35
|
+
d += `a${n(br)} ${n(br)} 0 0 1 ${n(-br)} ${n(br)}`;
|
|
36
|
+
d += `H${n(x + bl)}`;
|
|
37
|
+
if (bl)
|
|
38
|
+
d += `a${n(bl)} ${n(bl)} 0 0 1 ${n(-bl)} ${n(-bl)}`;
|
|
39
|
+
d += `V${n(y + tl)}`;
|
|
40
|
+
if (tl)
|
|
41
|
+
d += `a${n(tl)} ${n(tl)} 0 0 1 ${n(tl)} ${n(-tl)}`;
|
|
42
|
+
return `${d}z`;
|
|
43
|
+
}
|
|
44
|
+
function dot(x, y, r) {
|
|
45
|
+
return `M${n(x + 0.5 - r)} ${n(y + 0.5)}a${n(r)} ${n(r)} 0 1 0 ${n(2 * r)} 0a${n(r)} ${n(r)} 0 1 0 ${n(-2 * r)} 0z`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Logo için merkezde boşaltılacak kare kenarı (modül). QR kenarıyla aynı
|
|
49
|
+
* paritede (tek) tutulur ki kare tam ortalansın; oran 0–0.3 arası kırpılır
|
|
50
|
+
* (H seviyesi ~%30 hasarı kurtarır — alan olarak 0.3² ≈ %9, güvenli pay).
|
|
51
|
+
*/
|
|
52
|
+
function excavationSize(size, ratio) {
|
|
53
|
+
const clamped = Math.min(0.3, Math.max(0, ratio));
|
|
54
|
+
if (clamped === 0)
|
|
55
|
+
return 0;
|
|
56
|
+
let side = Math.round(size * clamped);
|
|
57
|
+
if (side % 2 !== size % 2)
|
|
58
|
+
side += 1;
|
|
59
|
+
return Math.min(side, size - 2 * (FINDER + 1));
|
|
60
|
+
}
|
|
61
|
+
/** Kodlanmış matrisi saf SVG yol dizelerine çevirir — DOM'suz, `computed()` içinde çağrılır. */
|
|
62
|
+
function qrGeometry(matrix, shape, finder, logoRatio) {
|
|
63
|
+
const size = matrix.size;
|
|
64
|
+
const excavated = excavationSize(size, logoRatio);
|
|
65
|
+
const start = (size - excavated) / 2;
|
|
66
|
+
const inLogo = (x, y) => excavated > 0 && x >= start && x < start + excavated && y >= start && y < start + excavated;
|
|
67
|
+
const drawn = (x, y) => x >= 0 &&
|
|
68
|
+
y >= 0 &&
|
|
69
|
+
x < size &&
|
|
70
|
+
y < size &&
|
|
71
|
+
matrix.modules[y][x] &&
|
|
72
|
+
!isFinder(x, y, size) &&
|
|
73
|
+
!inLogo(x, y);
|
|
74
|
+
let dataPath = '';
|
|
75
|
+
for (let y = 0; y < size; y++) {
|
|
76
|
+
if (shape === 'square') {
|
|
77
|
+
// Yatay koşuları birleştir — path ~%60 kısalır.
|
|
78
|
+
for (let x = 0; x < size;) {
|
|
79
|
+
if (!drawn(x, y)) {
|
|
80
|
+
x++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
let run = 1;
|
|
84
|
+
while (drawn(x + run, y))
|
|
85
|
+
run++;
|
|
86
|
+
dataPath += `M${x} ${y}h${run}v1h${-run}z`;
|
|
87
|
+
x += run;
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
for (let x = 0; x < size; x++) {
|
|
92
|
+
if (!drawn(x, y))
|
|
93
|
+
continue;
|
|
94
|
+
if (shape === 'dots') {
|
|
95
|
+
dataPath += dot(x, y, 0.42);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const up = drawn(x, y - 1);
|
|
99
|
+
const down = drawn(x, y + 1);
|
|
100
|
+
const left = drawn(x - 1, y);
|
|
101
|
+
const right = drawn(x + 1, y);
|
|
102
|
+
const r = 0.5;
|
|
103
|
+
dataPath += blobModule(x, y, !up && !left ? r : 0, !up && !right ? r : 0, !down && !right ? r : 0, !down && !left ? r : 0);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const rounded = finder === 'rounded';
|
|
107
|
+
const origins = [
|
|
108
|
+
[0, 0],
|
|
109
|
+
[size - FINDER, 0],
|
|
110
|
+
[0, size - FINDER],
|
|
111
|
+
];
|
|
112
|
+
let finderOuterPath = '';
|
|
113
|
+
let finderInnerPath = '';
|
|
114
|
+
for (const [x, y] of origins) {
|
|
115
|
+
finderOuterPath +=
|
|
116
|
+
roundedRect(x, y, 7, 7, rounded ? 2 : 0) + roundedRect(x + 1, y + 1, 5, 5, rounded ? 1.4 : 0);
|
|
117
|
+
finderInnerPath += roundedRect(x + 2, y + 2, 3, 3, rounded ? 0.9 : 0);
|
|
118
|
+
}
|
|
119
|
+
return { size, dataPath, finderOuterPath, finderInnerPath, excavated };
|
|
120
|
+
}
|
|
121
|
+
/** Bağımsız bir `<svg>` belgesi (indirme/paylaşım için) — logo katmanı HTML olduğu için dahil DEĞİL. */
|
|
122
|
+
function qrSvgString(geometry, options = {}) {
|
|
123
|
+
const margin = options.margin ?? 4;
|
|
124
|
+
const px = options.pixelSize ?? 256;
|
|
125
|
+
const fg = escapeAttr(options.foreground ?? '#000000');
|
|
126
|
+
const bg = escapeAttr(options.background ?? '#ffffff');
|
|
127
|
+
const total = geometry.size + margin * 2;
|
|
128
|
+
return (`<svg xmlns="http://www.w3.org/2000/svg" width="${px}" height="${px}" viewBox="${-margin} ${-margin} ${total} ${total}">` +
|
|
129
|
+
`<rect x="${-margin}" y="${-margin}" width="${total}" height="${total}" fill="${bg}"/>` +
|
|
130
|
+
`<path d="${geometry.finderOuterPath}" fill="${fg}" fill-rule="evenodd"/>` +
|
|
131
|
+
`<path d="${geometry.finderInnerPath}" fill="${fg}"/>` +
|
|
132
|
+
`<path d="${geometry.dataPath}" fill="${fg}"/>` +
|
|
133
|
+
`</svg>`);
|
|
134
|
+
}
|
|
135
|
+
function escapeAttr(value) {
|
|
136
|
+
return value.replace(/[&"<>]/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/*
|
|
140
|
+
* QR Code encoder — Project Nayuki "QR Code generator library" (TypeScript)
|
|
141
|
+
* algoritmasının kite ait, tipli ve sadeleştirilmiş portu.
|
|
142
|
+
*
|
|
143
|
+
* Copyright (c) Project Nayuki. (MIT License)
|
|
144
|
+
* https://www.nayuki.io/page/qr-code-generator-library
|
|
145
|
+
*
|
|
146
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
147
|
+
* this software and associated documentation files (the "Software"), to deal in
|
|
148
|
+
* the Software without restriction, including without limitation the rights to
|
|
149
|
+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
150
|
+
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
151
|
+
* subject to the following conditions:
|
|
152
|
+
* - The above copyright notice and this permission notice shall be included in
|
|
153
|
+
* all copies or substantial portions of the Software.
|
|
154
|
+
* - The Software is provided "as is", without warranty of any kind, express or
|
|
155
|
+
* implied, including but not limited to the warranties of merchantability,
|
|
156
|
+
* fitness for a particular purpose and noninfringement. In no event shall the
|
|
157
|
+
* authors or copyright holders be liable for any claim, damages or other
|
|
158
|
+
* liability, whether in an action of contract, tort or otherwise, arising from,
|
|
159
|
+
* out of or in connection with the Software or the use or other dealings in the
|
|
160
|
+
* Software.
|
|
161
|
+
*
|
|
162
|
+
* Neden vendor (bağımlılık değil): kitin her runtime kütüphanesi bir peerDependency
|
|
163
|
+
* olmak zorunda ve `ng add` bunu HER tüketiciye kurar — ~5 KB'lık saf bir algoritma
|
|
164
|
+
* için yeni bir peer eklemek yerine kodlayıcı burada yaşıyor. DOM'a dokunmaz (SSR güvenli).
|
|
165
|
+
*/
|
|
166
|
+
/** İçerik, `maxVersion` ve seçilen `ecc` ile sığmıyor. */
|
|
167
|
+
class QrCapacityError extends Error {
|
|
168
|
+
constructor() {
|
|
169
|
+
super('QR code data too long for the requested version / error correction level');
|
|
170
|
+
this.name = 'QrCapacityError';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const ECC_ORDINAL = { L: 0, M: 1, Q: 2, H: 3 };
|
|
174
|
+
const ECC_FORMAT_BITS = { L: 1, M: 0, Q: 3, H: 2 };
|
|
175
|
+
// prettier-ignore
|
|
176
|
+
const ECC_CODEWORDS_PER_BLOCK = [
|
|
177
|
+
// Sürüm: 0 (dolgu), 1 … 40
|
|
178
|
+
[-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // L
|
|
179
|
+
[-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28], // M
|
|
180
|
+
[-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // Q
|
|
181
|
+
[-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // H
|
|
182
|
+
];
|
|
183
|
+
// prettier-ignore
|
|
184
|
+
const NUM_ERROR_CORRECTION_BLOCKS = [
|
|
185
|
+
[-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25], // L
|
|
186
|
+
[-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49], // M
|
|
187
|
+
[-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68], // Q
|
|
188
|
+
[-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81], // H
|
|
189
|
+
];
|
|
190
|
+
const PENALTY_N1 = 3;
|
|
191
|
+
const PENALTY_N2 = 3;
|
|
192
|
+
const PENALTY_N3 = 40;
|
|
193
|
+
const PENALTY_N4 = 10;
|
|
194
|
+
const ALPHANUMERIC_CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
|
195
|
+
const NUMERIC = /^[0-9]*$/;
|
|
196
|
+
const ALPHANUMERIC = /^[A-Z0-9 $%*+./:-]*$/;
|
|
197
|
+
const MODE_NUMERIC = { bits: 0x1, countBits: [10, 12, 14] };
|
|
198
|
+
const MODE_ALPHANUMERIC = { bits: 0x2, countBits: [9, 11, 13] };
|
|
199
|
+
const MODE_BYTE = { bits: 0x4, countBits: [8, 16, 16] };
|
|
200
|
+
function appendBits(value, length, out) {
|
|
201
|
+
for (let i = length - 1; i >= 0; i--)
|
|
202
|
+
out.push((value >>> i) & 1);
|
|
203
|
+
}
|
|
204
|
+
function getBit(value, i) {
|
|
205
|
+
return ((value >>> i) & 1) !== 0;
|
|
206
|
+
}
|
|
207
|
+
function charCountBits(mode, version) {
|
|
208
|
+
return mode.countBits[Math.floor((version + 7) / 17)];
|
|
209
|
+
}
|
|
210
|
+
function makeSegment(data) {
|
|
211
|
+
if (typeof data !== 'string')
|
|
212
|
+
return byteSegment(data);
|
|
213
|
+
if (data === '')
|
|
214
|
+
return null;
|
|
215
|
+
if (NUMERIC.test(data)) {
|
|
216
|
+
const bits = [];
|
|
217
|
+
for (let i = 0; i < data.length; i += 3) {
|
|
218
|
+
const chunk = data.substring(i, i + 3);
|
|
219
|
+
appendBits(Number.parseInt(chunk, 10), chunk.length * 3 + 1, bits);
|
|
220
|
+
}
|
|
221
|
+
return { mode: MODE_NUMERIC, numChars: data.length, data: bits };
|
|
222
|
+
}
|
|
223
|
+
if (ALPHANUMERIC.test(data)) {
|
|
224
|
+
const bits = [];
|
|
225
|
+
let i = 0;
|
|
226
|
+
for (; i + 2 <= data.length; i += 2) {
|
|
227
|
+
const pair = ALPHANUMERIC_CHARSET.indexOf(data.charAt(i)) * 45 +
|
|
228
|
+
ALPHANUMERIC_CHARSET.indexOf(data.charAt(i + 1));
|
|
229
|
+
appendBits(pair, 11, bits);
|
|
230
|
+
}
|
|
231
|
+
if (i < data.length)
|
|
232
|
+
appendBits(ALPHANUMERIC_CHARSET.indexOf(data.charAt(i)), 6, bits);
|
|
233
|
+
return { mode: MODE_ALPHANUMERIC, numChars: data.length, data: bits };
|
|
234
|
+
}
|
|
235
|
+
return byteSegment(new TextEncoder().encode(data));
|
|
236
|
+
}
|
|
237
|
+
function byteSegment(bytes) {
|
|
238
|
+
const bits = [];
|
|
239
|
+
for (const b of bytes)
|
|
240
|
+
appendBits(b, 8, bits);
|
|
241
|
+
return { mode: MODE_BYTE, numChars: bytes.length, data: bits };
|
|
242
|
+
}
|
|
243
|
+
function totalBits(segments, version) {
|
|
244
|
+
let result = 0;
|
|
245
|
+
for (const seg of segments) {
|
|
246
|
+
const ccBits = charCountBits(seg.mode, version);
|
|
247
|
+
if (seg.numChars >= 1 << ccBits)
|
|
248
|
+
return Number.POSITIVE_INFINITY;
|
|
249
|
+
result += 4 + ccBits + seg.data.length;
|
|
250
|
+
}
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
function numRawDataModules(version) {
|
|
254
|
+
let result = (16 * version + 128) * version + 64;
|
|
255
|
+
if (version >= 2) {
|
|
256
|
+
const numAlign = Math.floor(version / 7) + 2;
|
|
257
|
+
result -= (25 * numAlign - 10) * numAlign - 55;
|
|
258
|
+
if (version >= 7)
|
|
259
|
+
result -= 36;
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
function numDataCodewords(version, ecc) {
|
|
264
|
+
const o = ECC_ORDINAL[ecc];
|
|
265
|
+
return (Math.floor(numRawDataModules(version) / 8) -
|
|
266
|
+
ECC_CODEWORDS_PER_BLOCK[o][version] * NUM_ERROR_CORRECTION_BLOCKS[o][version]);
|
|
267
|
+
}
|
|
268
|
+
// --- Reed–Solomon (GF(2^8), indirgeme polinomu 0x11D) ---
|
|
269
|
+
function rsMultiply(x, y) {
|
|
270
|
+
let z = 0;
|
|
271
|
+
for (let i = 7; i >= 0; i--) {
|
|
272
|
+
z = (z << 1) ^ ((z >>> 7) * 0x11d);
|
|
273
|
+
z ^= ((y >>> i) & 1) * x;
|
|
274
|
+
}
|
|
275
|
+
return z;
|
|
276
|
+
}
|
|
277
|
+
function rsDivisor(degree) {
|
|
278
|
+
const result = new Array(degree).fill(0);
|
|
279
|
+
result[degree - 1] = 1;
|
|
280
|
+
let root = 1;
|
|
281
|
+
for (let i = 0; i < degree; i++) {
|
|
282
|
+
for (let j = 0; j < result.length; j++) {
|
|
283
|
+
result[j] = rsMultiply(result[j], root);
|
|
284
|
+
if (j + 1 < result.length)
|
|
285
|
+
result[j] ^= result[j + 1];
|
|
286
|
+
}
|
|
287
|
+
root = rsMultiply(root, 0x02);
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
function rsRemainder(data, divisor) {
|
|
292
|
+
const result = divisor.map(() => 0);
|
|
293
|
+
for (const b of data) {
|
|
294
|
+
const factor = b ^ result.shift();
|
|
295
|
+
result.push(0);
|
|
296
|
+
divisor.forEach((coef, i) => (result[i] ^= rsMultiply(coef, factor)));
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
// --- Matris ---
|
|
301
|
+
class Grid {
|
|
302
|
+
version;
|
|
303
|
+
size;
|
|
304
|
+
modules;
|
|
305
|
+
isFunction;
|
|
306
|
+
constructor(version) {
|
|
307
|
+
this.version = version;
|
|
308
|
+
this.size = version * 4 + 17;
|
|
309
|
+
this.modules = Array.from({ length: this.size }, () => new Array(this.size).fill(false));
|
|
310
|
+
this.isFunction = Array.from({ length: this.size }, () => new Array(this.size).fill(false));
|
|
311
|
+
}
|
|
312
|
+
setFunction(x, y, dark) {
|
|
313
|
+
this.modules[y][x] = dark;
|
|
314
|
+
this.isFunction[y][x] = true;
|
|
315
|
+
}
|
|
316
|
+
drawFunctionPatterns(ecc) {
|
|
317
|
+
for (let i = 0; i < this.size; i++) {
|
|
318
|
+
this.setFunction(6, i, i % 2 === 0);
|
|
319
|
+
this.setFunction(i, 6, i % 2 === 0);
|
|
320
|
+
}
|
|
321
|
+
this.drawFinder(3, 3);
|
|
322
|
+
this.drawFinder(this.size - 4, 3);
|
|
323
|
+
this.drawFinder(3, this.size - 4);
|
|
324
|
+
const align = this.alignmentPositions();
|
|
325
|
+
const n = align.length;
|
|
326
|
+
for (let i = 0; i < n; i++) {
|
|
327
|
+
for (let j = 0; j < n; j++) {
|
|
328
|
+
const corner = (i === 0 && j === 0) || (i === 0 && j === n - 1) || (i === n - 1 && j === 0);
|
|
329
|
+
if (!corner)
|
|
330
|
+
this.drawAlignment(align[i], align[j]);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
this.drawFormatBits(ecc, 0); // yer tutucu — maske seçildikten sonra üzerine yazılır
|
|
334
|
+
this.drawVersion();
|
|
335
|
+
}
|
|
336
|
+
drawFinder(cx, cy) {
|
|
337
|
+
for (let dy = -4; dy <= 4; dy++) {
|
|
338
|
+
for (let dx = -4; dx <= 4; dx++) {
|
|
339
|
+
const dist = Math.max(Math.abs(dx), Math.abs(dy));
|
|
340
|
+
const x = cx + dx;
|
|
341
|
+
const y = cy + dy;
|
|
342
|
+
if (x >= 0 && x < this.size && y >= 0 && y < this.size) {
|
|
343
|
+
this.setFunction(x, y, dist !== 2 && dist !== 4);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
drawAlignment(cx, cy) {
|
|
349
|
+
for (let dy = -2; dy <= 2; dy++) {
|
|
350
|
+
for (let dx = -2; dx <= 2; dx++) {
|
|
351
|
+
this.setFunction(cx + dx, cy + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
alignmentPositions() {
|
|
356
|
+
if (this.version === 1)
|
|
357
|
+
return [];
|
|
358
|
+
const numAlign = Math.floor(this.version / 7) + 2;
|
|
359
|
+
const step = this.version === 32 ? 26 : Math.ceil((this.version * 4 + 4) / (numAlign * 2 - 2)) * 2;
|
|
360
|
+
const result = [6];
|
|
361
|
+
for (let pos = this.size - 7; result.length < numAlign; pos -= step)
|
|
362
|
+
result.splice(1, 0, pos);
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
drawFormatBits(ecc, mask) {
|
|
366
|
+
const data = (ECC_FORMAT_BITS[ecc] << 3) | mask;
|
|
367
|
+
let rem = data;
|
|
368
|
+
for (let i = 0; i < 10; i++)
|
|
369
|
+
rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
|
|
370
|
+
const bits = ((data << 10) | rem) ^ 0x5412;
|
|
371
|
+
for (let i = 0; i <= 5; i++)
|
|
372
|
+
this.setFunction(8, i, getBit(bits, i));
|
|
373
|
+
this.setFunction(8, 7, getBit(bits, 6));
|
|
374
|
+
this.setFunction(8, 8, getBit(bits, 7));
|
|
375
|
+
this.setFunction(7, 8, getBit(bits, 8));
|
|
376
|
+
for (let i = 9; i < 15; i++)
|
|
377
|
+
this.setFunction(14 - i, 8, getBit(bits, i));
|
|
378
|
+
for (let i = 0; i < 8; i++)
|
|
379
|
+
this.setFunction(this.size - 1 - i, 8, getBit(bits, i));
|
|
380
|
+
for (let i = 8; i < 15; i++)
|
|
381
|
+
this.setFunction(8, this.size - 15 + i, getBit(bits, i));
|
|
382
|
+
this.setFunction(8, this.size - 8, true); // her zaman koyu modül
|
|
383
|
+
}
|
|
384
|
+
drawVersion() {
|
|
385
|
+
if (this.version < 7)
|
|
386
|
+
return;
|
|
387
|
+
let rem = this.version;
|
|
388
|
+
for (let i = 0; i < 12; i++)
|
|
389
|
+
rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25);
|
|
390
|
+
const bits = (this.version << 12) | rem;
|
|
391
|
+
for (let i = 0; i < 18; i++) {
|
|
392
|
+
const dark = getBit(bits, i);
|
|
393
|
+
const a = this.size - 11 + (i % 3);
|
|
394
|
+
const b = Math.floor(i / 3);
|
|
395
|
+
this.setFunction(a, b, dark);
|
|
396
|
+
this.setFunction(b, a, dark);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
drawCodewords(codewords) {
|
|
400
|
+
let i = 0;
|
|
401
|
+
for (let right = this.size - 1; right >= 1; right -= 2) {
|
|
402
|
+
if (right === 6)
|
|
403
|
+
right = 5;
|
|
404
|
+
for (let vert = 0; vert < this.size; vert++) {
|
|
405
|
+
for (let j = 0; j < 2; j++) {
|
|
406
|
+
const x = right - j;
|
|
407
|
+
const upward = ((right + 1) & 2) === 0;
|
|
408
|
+
const y = upward ? this.size - 1 - vert : vert;
|
|
409
|
+
if (!this.isFunction[y][x] && i < codewords.length * 8) {
|
|
410
|
+
this.modules[y][x] = getBit(codewords[i >>> 3], 7 - (i & 7));
|
|
411
|
+
i++;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
applyMask(mask) {
|
|
418
|
+
for (let y = 0; y < this.size; y++) {
|
|
419
|
+
for (let x = 0; x < this.size; x++) {
|
|
420
|
+
if (!this.isFunction[y][x] && maskBit(mask, x, y)) {
|
|
421
|
+
this.modules[y][x] = !this.modules[y][x];
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
penalty() {
|
|
427
|
+
let result = 0;
|
|
428
|
+
const size = this.size;
|
|
429
|
+
const m = this.modules;
|
|
430
|
+
const line = (get) => {
|
|
431
|
+
let runColor = false;
|
|
432
|
+
let run = 0;
|
|
433
|
+
const history = [0, 0, 0, 0, 0, 0, 0];
|
|
434
|
+
for (let i = 0; i < size; i++) {
|
|
435
|
+
if (get(i) === runColor) {
|
|
436
|
+
run++;
|
|
437
|
+
if (run === 5)
|
|
438
|
+
result += PENALTY_N1;
|
|
439
|
+
else if (run > 5)
|
|
440
|
+
result++;
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
this.addHistory(run, history);
|
|
444
|
+
if (!runColor)
|
|
445
|
+
result += this.countFinderLike(history) * PENALTY_N3;
|
|
446
|
+
runColor = get(i);
|
|
447
|
+
run = 1;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (runColor) {
|
|
451
|
+
this.addHistory(run, history);
|
|
452
|
+
run = 0;
|
|
453
|
+
}
|
|
454
|
+
run += size;
|
|
455
|
+
this.addHistory(run, history);
|
|
456
|
+
result += this.countFinderLike(history) * PENALTY_N3;
|
|
457
|
+
};
|
|
458
|
+
for (let y = 0; y < size; y++)
|
|
459
|
+
line((x) => m[y][x]);
|
|
460
|
+
for (let x = 0; x < size; x++)
|
|
461
|
+
line((y) => m[y][x]);
|
|
462
|
+
for (let y = 0; y < size - 1; y++) {
|
|
463
|
+
for (let x = 0; x < size - 1; x++) {
|
|
464
|
+
const c = m[y][x];
|
|
465
|
+
if (c === m[y][x + 1] && c === m[y + 1][x] && c === m[y + 1][x + 1])
|
|
466
|
+
result += PENALTY_N2;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
let dark = 0;
|
|
470
|
+
for (const row of m)
|
|
471
|
+
for (const c of row)
|
|
472
|
+
if (c)
|
|
473
|
+
dark++;
|
|
474
|
+
const total = size * size;
|
|
475
|
+
const k = Math.ceil(Math.abs(dark * 20 - total * 10) / total) - 1;
|
|
476
|
+
result += k * PENALTY_N4;
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
countFinderLike(h) {
|
|
480
|
+
const n = h[1];
|
|
481
|
+
const core = n > 0 && h[2] === n && h[3] === n * 3 && h[4] === n && h[5] === n;
|
|
482
|
+
return ((core && h[0] >= n * 4 && h[6] >= n ? 1 : 0) + (core && h[6] >= n * 4 && h[0] >= n ? 1 : 0));
|
|
483
|
+
}
|
|
484
|
+
addHistory(run, history) {
|
|
485
|
+
if (history[0] === 0)
|
|
486
|
+
run += this.size; // ilk koşuya sanal açık sessiz bölge
|
|
487
|
+
history.pop();
|
|
488
|
+
history.unshift(run);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function maskBit(mask, x, y) {
|
|
492
|
+
switch (mask) {
|
|
493
|
+
case 0:
|
|
494
|
+
return (x + y) % 2 === 0;
|
|
495
|
+
case 1:
|
|
496
|
+
return y % 2 === 0;
|
|
497
|
+
case 2:
|
|
498
|
+
return x % 3 === 0;
|
|
499
|
+
case 3:
|
|
500
|
+
return (x + y) % 3 === 0;
|
|
501
|
+
case 4:
|
|
502
|
+
return (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0;
|
|
503
|
+
case 5:
|
|
504
|
+
return ((x * y) % 2) + ((x * y) % 3) === 0;
|
|
505
|
+
case 6:
|
|
506
|
+
return (((x * y) % 2) + ((x * y) % 3)) % 2 === 0;
|
|
507
|
+
default:
|
|
508
|
+
return (((x + y) % 2) + ((x * y) % 3)) % 2 === 0;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function addEccAndInterleave(data, version, ecc) {
|
|
512
|
+
const o = ECC_ORDINAL[ecc];
|
|
513
|
+
const numBlocks = NUM_ERROR_CORRECTION_BLOCKS[o][version];
|
|
514
|
+
const blockEccLen = ECC_CODEWORDS_PER_BLOCK[o][version];
|
|
515
|
+
const rawCodewords = Math.floor(numRawDataModules(version) / 8);
|
|
516
|
+
const numShortBlocks = numBlocks - (rawCodewords % numBlocks);
|
|
517
|
+
const shortBlockLen = Math.floor(rawCodewords / numBlocks);
|
|
518
|
+
const divisor = rsDivisor(blockEccLen);
|
|
519
|
+
const blocks = [];
|
|
520
|
+
for (let i = 0, k = 0; i < numBlocks; i++) {
|
|
521
|
+
const dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
|
|
522
|
+
k += dat.length;
|
|
523
|
+
const eccBytes = rsRemainder(dat, divisor);
|
|
524
|
+
if (i < numShortBlocks)
|
|
525
|
+
dat.push(0);
|
|
526
|
+
blocks.push(dat.concat(eccBytes));
|
|
527
|
+
}
|
|
528
|
+
const result = [];
|
|
529
|
+
for (let i = 0; i < blocks[0].length; i++) {
|
|
530
|
+
blocks.forEach((block, j) => {
|
|
531
|
+
if (i !== shortBlockLen - blockEccLen || j >= numShortBlocks)
|
|
532
|
+
result.push(block[i]);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Metni (UTF-8; tamamen rakamsa numeric, büyük harf/rakam/` $%*+-./:` ise
|
|
539
|
+
* alphanumeric kip) ya da ham baytları bir QR matrisine kodlar. En küçük uygun
|
|
540
|
+
* sürümü ve en düşük cezalı maskeyi seçer.
|
|
541
|
+
*
|
|
542
|
+
* @throws {QrCapacityError} içerik sığmıyorsa.
|
|
543
|
+
*/
|
|
544
|
+
function encodeQr(data, options = {}) {
|
|
545
|
+
const minVersion = Math.max(1, Math.min(40, Math.floor(options.minVersion ?? 1)));
|
|
546
|
+
const maxVersion = Math.max(minVersion, Math.min(40, Math.floor(options.maxVersion ?? 40)));
|
|
547
|
+
let ecc = options.ecc ?? 'M';
|
|
548
|
+
const boost = options.boostEcc ?? true;
|
|
549
|
+
const segment = makeSegment(data);
|
|
550
|
+
const segments = segment ? [segment] : [];
|
|
551
|
+
let version = minVersion;
|
|
552
|
+
let usedBits;
|
|
553
|
+
for (;; version++) {
|
|
554
|
+
usedBits = totalBits(segments, version);
|
|
555
|
+
if (usedBits <= numDataCodewords(version, ecc) * 8)
|
|
556
|
+
break;
|
|
557
|
+
if (version >= maxVersion)
|
|
558
|
+
throw new QrCapacityError();
|
|
559
|
+
}
|
|
560
|
+
if (boost) {
|
|
561
|
+
for (const higher of ['M', 'Q', 'H']) {
|
|
562
|
+
if (usedBits <= numDataCodewords(version, higher) * 8)
|
|
563
|
+
ecc = higher;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const bits = [];
|
|
567
|
+
for (const seg of segments) {
|
|
568
|
+
appendBits(seg.mode.bits, 4, bits);
|
|
569
|
+
appendBits(seg.numChars, charCountBits(seg.mode, version), bits);
|
|
570
|
+
for (const b of seg.data)
|
|
571
|
+
bits.push(b);
|
|
572
|
+
}
|
|
573
|
+
const capacity = numDataCodewords(version, ecc) * 8;
|
|
574
|
+
appendBits(0, Math.min(4, capacity - bits.length), bits); // sonlandırıcı
|
|
575
|
+
appendBits(0, (8 - (bits.length % 8)) % 8, bits);
|
|
576
|
+
for (let pad = 0xec; bits.length < capacity; pad ^= 0xec ^ 0x11)
|
|
577
|
+
appendBits(pad, 8, bits);
|
|
578
|
+
const codewords = new Array(bits.length / 8).fill(0);
|
|
579
|
+
bits.forEach((b, i) => (codewords[i >>> 3] |= b << (7 - (i & 7))));
|
|
580
|
+
const grid = new Grid(version);
|
|
581
|
+
grid.drawFunctionPatterns(ecc);
|
|
582
|
+
grid.drawCodewords(addEccAndInterleave(codewords, version, ecc));
|
|
583
|
+
let bestMask = 0;
|
|
584
|
+
let minPenalty = Number.POSITIVE_INFINITY;
|
|
585
|
+
for (let mask = 0; mask < 8; mask++) {
|
|
586
|
+
grid.applyMask(mask);
|
|
587
|
+
grid.drawFormatBits(ecc, mask);
|
|
588
|
+
const penalty = grid.penalty();
|
|
589
|
+
if (penalty < minPenalty) {
|
|
590
|
+
bestMask = mask;
|
|
591
|
+
minPenalty = penalty;
|
|
592
|
+
}
|
|
593
|
+
grid.applyMask(mask); // XOR — geri al
|
|
594
|
+
}
|
|
595
|
+
grid.applyMask(bestMask);
|
|
596
|
+
grid.drawFormatBits(ecc, bestMask);
|
|
597
|
+
return {
|
|
598
|
+
size: grid.size,
|
|
599
|
+
version,
|
|
600
|
+
ecc,
|
|
601
|
+
mask: bestMask,
|
|
602
|
+
modules: grid.modules,
|
|
603
|
+
isFunction: grid.isFunction,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* `<mf-qr-code data="…"><img mfQrCodeLogo src="logo.svg" alt="" /></mf-qr-code>` —
|
|
609
|
+
* QR'ın ortasına yerleştirilen logo. Varlığı hata düzeltmeyi otomatik `H`'ye
|
|
610
|
+
* yükseltir ve altındaki modülleri boşaltır. Dekoratif olduğu için `alt=""`
|
|
611
|
+
* önerilir — erişilebilir ad `mf-qr-code`'un kendi `aria-label`'ındadır.
|
|
612
|
+
*/
|
|
613
|
+
class QrCodeLogo {
|
|
614
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: QrCodeLogo, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
615
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: QrCodeLogo, isStandalone: true, selector: "[mfQrCodeLogo]", host: { classAttribute: "block max-h-full max-w-full object-contain" }, ngImport: i0 });
|
|
616
|
+
}
|
|
617
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: QrCodeLogo, decorators: [{
|
|
618
|
+
type: Directive,
|
|
619
|
+
args: [{
|
|
620
|
+
selector: '[mfQrCodeLogo]',
|
|
621
|
+
host: { class: 'block max-h-full max-w-full object-contain' },
|
|
622
|
+
}]
|
|
623
|
+
}] });
|
|
624
|
+
/**
|
|
625
|
+
* `<mf-qr-code data="https://mesgflow.dev" />`
|
|
626
|
+
*
|
|
627
|
+
* Saf SVG QR kod — kodlayıcı kitin içinde (`qr-encoder.ts`, Nayuki portu), ek
|
|
628
|
+
* bağımlılık yok; tüm geometri `computed()` ile üretilir, DOM API'si
|
|
629
|
+
* kullanılmaz (SSR güvenli).
|
|
630
|
+
*
|
|
631
|
+
* - `shape`: `square` (klasik) · `rounded` (akışkan, komşu modüllerle birleşen) · `dots`.
|
|
632
|
+
* - `finder`: köşe desenleri `square` / `rounded`.
|
|
633
|
+
* - `ecc`: L/M/Q/H — logo varsa otomatik `H`. Aynı boyutta daha yüksek seviye
|
|
634
|
+
* sığıyorsa zaten yükseltilir (`boostEcc`).
|
|
635
|
+
* - `margin`: sessiz bölge (modül). Okuyucular ≥ 2 ister; baskı için 4.
|
|
636
|
+
* - Logo: `[mfQrCodeLogo]` içerik (bkz. `QrCodeLogo`).
|
|
637
|
+
* - `toSvgString()`: indirme/paylaşım için bağımsız SVG.
|
|
638
|
+
*
|
|
639
|
+
* Renk: varsayılan beyaz kart üstünde siyah modül — BİLİNÇLİ ham renk istisnası:
|
|
640
|
+
* kameralar koyu-üstünde-açık (ters) QR'ı güvenilir okuyamaz, bu yüzden koyu temada
|
|
641
|
+
* da açık kart kalır. `foreground`/`background` (herhangi bir CSS rengi, `var(--…)`
|
|
642
|
+
* dahil) ile değiştirilir — satır içi stil olduğu için sınıf sırası çakışması yok;
|
|
643
|
+
* modüller `currentColor`, logo kutusu zemini miras alır. Koyu modül / açık zemin ve
|
|
644
|
+
* yeterli kontrast (≥ ~4:1) korunmalı.
|
|
645
|
+
*
|
|
646
|
+
* İçerik sığmazsa hata fırlatmaz: yerinde erişilebilir bir hata durumu çizer ve
|
|
647
|
+
* `encoded` çıkışı `null` yayar.
|
|
648
|
+
*/
|
|
649
|
+
class QrCode {
|
|
650
|
+
/** Kodlanacak içerik (URL, metin, `WIFI:…`, `mailto:` vb.). */
|
|
651
|
+
data = input.required(/* @ts-ignore */
|
|
652
|
+
...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
653
|
+
/** Piksel kenar (sessiz bölge dahil). */
|
|
654
|
+
size = input(160, { ...(ngDevMode ? { debugName: "size" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
655
|
+
ecc = input('M', /* @ts-ignore */
|
|
656
|
+
...(ngDevMode ? [{ debugName: "ecc" }] : /* istanbul ignore next */ []));
|
|
657
|
+
/** Sessiz bölge, modül cinsinden. */
|
|
658
|
+
margin = input(2, { ...(ngDevMode ? { debugName: "margin" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
659
|
+
shape = input('square', /* @ts-ignore */
|
|
660
|
+
...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
|
|
661
|
+
finder = input('square', /* @ts-ignore */
|
|
662
|
+
...(ngDevMode ? [{ debugName: "finder" }] : /* istanbul ignore next */ []));
|
|
663
|
+
/** Logo karesinin QR kenarına oranı (0–0.3); yalnızca `[mfQrCodeLogo]` varken etkili. */
|
|
664
|
+
logoSize = input(0.22, { ...(ngDevMode ? { debugName: "logoSize" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
665
|
+
/** Modül rengi (CSS rengi). Verilmezse siyah. */
|
|
666
|
+
foreground = input(/* @ts-ignore */
|
|
667
|
+
...(ngDevMode ? [undefined, { debugName: "foreground" }] : /* istanbul ignore next */ []));
|
|
668
|
+
/** Zemin rengi (CSS rengi). Verilmezse beyaz. */
|
|
669
|
+
background = input(/* @ts-ignore */
|
|
670
|
+
...(ngDevMode ? [undefined, { debugName: "background" }] : /* istanbul ignore next */ []));
|
|
671
|
+
/** `role="img"` erişilebilir adı — verilmezse `MF_QR_CODE_LABELS.ariaLabel`. İçeriği anlatan bir ad önerilir. */
|
|
672
|
+
ariaLabel = input(/* @ts-ignore */
|
|
673
|
+
...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
674
|
+
/** Her yeniden kodlamada matris (hata durumunda `null`). */
|
|
675
|
+
encoded = output();
|
|
676
|
+
labels = inject(MF_QR_CODE_LABELS);
|
|
677
|
+
logo = contentChild(QrCodeLogo, /* @ts-ignore */
|
|
678
|
+
...(ngDevMode ? [{ debugName: "logo" }] : /* istanbul ignore next */ []));
|
|
679
|
+
hasLogo = computed(() => !!this.logo(), /* @ts-ignore */
|
|
680
|
+
...(ngDevMode ? [{ debugName: "hasLogo" }] : /* istanbul ignore next */ []));
|
|
681
|
+
effectiveEcc = computed(() => (this.hasLogo() ? 'H' : this.ecc()), /* @ts-ignore */
|
|
682
|
+
...(ngDevMode ? [{ debugName: "effectiveEcc" }] : /* istanbul ignore next */ []));
|
|
683
|
+
matrix = computed(() => {
|
|
684
|
+
try {
|
|
685
|
+
return encodeQr(this.data(), { ecc: this.effectiveEcc() });
|
|
686
|
+
}
|
|
687
|
+
catch (error) {
|
|
688
|
+
if (error instanceof QrCapacityError)
|
|
689
|
+
return null;
|
|
690
|
+
throw error;
|
|
691
|
+
}
|
|
692
|
+
}, /* @ts-ignore */
|
|
693
|
+
...(ngDevMode ? [{ debugName: "matrix" }] : /* istanbul ignore next */ []));
|
|
694
|
+
geometry = computed(() => {
|
|
695
|
+
const matrix = this.matrix();
|
|
696
|
+
if (!matrix)
|
|
697
|
+
return null;
|
|
698
|
+
return qrGeometry(matrix, this.shape(), this.finder(), this.hasLogo() ? this.logoSize() : 0);
|
|
699
|
+
}, /* @ts-ignore */
|
|
700
|
+
...(ngDevMode ? [{ debugName: "geometry" }] : /* istanbul ignore next */ []));
|
|
701
|
+
quietZone = computed(() => Math.max(0, this.margin()), /* @ts-ignore */
|
|
702
|
+
...(ngDevMode ? [{ debugName: "quietZone" }] : /* istanbul ignore next */ []));
|
|
703
|
+
viewBox = computed(() => {
|
|
704
|
+
const g = this.geometry();
|
|
705
|
+
if (!g)
|
|
706
|
+
return '0 0 1 1';
|
|
707
|
+
const m = this.quietZone();
|
|
708
|
+
const total = g.size + m * 2;
|
|
709
|
+
return `${-m} ${-m} ${total} ${total}`;
|
|
710
|
+
}, /* @ts-ignore */
|
|
711
|
+
...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
|
|
712
|
+
/** Logo kutusunun piksel kenarı — boşaltılan karenin ekrandaki karşılığı. */
|
|
713
|
+
logoBoxPx = computed(() => {
|
|
714
|
+
const g = this.geometry();
|
|
715
|
+
if (!g || !g.excavated)
|
|
716
|
+
return 0;
|
|
717
|
+
return (g.excavated / (g.size + this.quietZone() * 2)) * this.size();
|
|
718
|
+
}, /* @ts-ignore */
|
|
719
|
+
...(ngDevMode ? [{ debugName: "logoBoxPx" }] : /* istanbul ignore next */ []));
|
|
720
|
+
crisp = computed(() => this.shape() === 'square', /* @ts-ignore */
|
|
721
|
+
...(ngDevMode ? [{ debugName: "crisp" }] : /* istanbul ignore next */ []));
|
|
722
|
+
effectiveAriaLabel = computed(() => this.matrix() ? (this.ariaLabel() ?? this.labels().ariaLabel) : this.labels().error, /* @ts-ignore */
|
|
723
|
+
...(ngDevMode ? [{ debugName: "effectiveAriaLabel" }] : /* istanbul ignore next */ []));
|
|
724
|
+
hostClasses = computed(() => cn('relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-lg', this.matrix() ? '' : 'bg-danger-tint text-danger-tint-fg'), /* @ts-ignore */
|
|
725
|
+
...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
|
|
726
|
+
constructor() {
|
|
727
|
+
effect(() => this.encoded.emit(this.matrix()));
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Bağımsız SVG belgesi — logo katmanı dahil DEĞİL. İçerik sığmıyorsa `null`.
|
|
731
|
+
* Renkler verilmezse `foreground`/`background` girdileri (onlar da yoksa siyah/beyaz);
|
|
732
|
+
* `var(--…)` gibi bağlama bağlı değerler dışa aktarımda çözülmez — somut renk geçin.
|
|
733
|
+
*/
|
|
734
|
+
toSvgString(options) {
|
|
735
|
+
const g = this.geometry();
|
|
736
|
+
if (!g)
|
|
737
|
+
return null;
|
|
738
|
+
return qrSvgString(g, {
|
|
739
|
+
margin: this.quietZone(),
|
|
740
|
+
foreground: this.foreground(),
|
|
741
|
+
background: this.background(),
|
|
742
|
+
...options,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: QrCode, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
746
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: QrCode, isStandalone: true, selector: "mf-qr-code", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, ecc: { classPropertyName: "ecc", publicName: "ecc", isSignal: true, isRequired: false, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, finder: { classPropertyName: "finder", publicName: "finder", isSignal: true, isRequired: false, transformFunction: null }, logoSize: { classPropertyName: "logoSize", publicName: "logoSize", isSignal: true, isRequired: false, transformFunction: null }, foreground: { classPropertyName: "foreground", publicName: "foreground", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { encoded: "encoded" }, host: { attributes: { "role": "img" }, properties: { "attr.aria-label": "effectiveAriaLabel()", "class": "hostClasses()", "style.width.px": "size()", "style.height.px": "size()", "style.color": "matrix() ? (foreground() ?? '#000') : null", "style.background-color": "matrix() ? (background() ?? '#fff') : null" } }, queries: [{ propertyName: "logo", first: true, predicate: QrCodeLogo, descendants: true, isSignal: true }], exportAs: ["mfQrCode"], ngImport: i0, template: "@if (geometry(); as g) {\r\n <svg\r\n class=\"block h-full w-full\"\r\n [attr.viewBox]=\"viewBox()\"\r\n [attr.shape-rendering]=\"crisp() ? 'crispEdges' : 'geometricPrecision'\"\r\n aria-hidden=\"true\"\r\n focusable=\"false\"\r\n >\r\n <path data-part=\"finder\" fill=\"currentColor\" fill-rule=\"evenodd\" [attr.d]=\"g.finderOuterPath\" />\r\n <path data-part=\"finder\" fill=\"currentColor\" [attr.d]=\"g.finderInnerPath\" />\r\n <path data-part=\"modules\" fill=\"currentColor\" [attr.d]=\"g.dataPath\" />\r\n </svg>\r\n} @else {\r\n <span class=\"p-3 text-center text-xs\" aria-hidden=\"true\">{{ effectiveAriaLabel() }}</span>\r\n}\r\n\r\n<!-- `contentChild` logo'yu projeksiyon yerinden ba\u011F\u0131ms\u0131z g\u00F6r\u00FCr; kutu yaln\u0131zca bo\u015Faltma varken \u00E7izilir. -->\r\n@if (logoBoxPx()) {\r\n <span\r\n class=\"absolute inset-0 m-auto flex items-center justify-center overflow-hidden rounded-md bg-inherit p-[6%]\"\r\n [style.width.px]=\"logoBoxPx()\"\r\n [style.height.px]=\"logoBoxPx()\"\r\n >\r\n <ng-content select=\"[mfQrCodeLogo]\" />\r\n </span>\r\n}\r\n" });
|
|
747
|
+
}
|
|
748
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: QrCode, decorators: [{
|
|
749
|
+
type: Component,
|
|
750
|
+
args: [{ selector: 'mf-qr-code', exportAs: 'mfQrCode', host: {
|
|
751
|
+
role: 'img',
|
|
752
|
+
'[attr.aria-label]': 'effectiveAriaLabel()',
|
|
753
|
+
'[class]': 'hostClasses()',
|
|
754
|
+
'[style.width.px]': 'size()',
|
|
755
|
+
'[style.height.px]': 'size()',
|
|
756
|
+
'[style.color]': "matrix() ? (foreground() ?? '#000') : null",
|
|
757
|
+
'[style.background-color]': "matrix() ? (background() ?? '#fff') : null",
|
|
758
|
+
}, template: "@if (geometry(); as g) {\r\n <svg\r\n class=\"block h-full w-full\"\r\n [attr.viewBox]=\"viewBox()\"\r\n [attr.shape-rendering]=\"crisp() ? 'crispEdges' : 'geometricPrecision'\"\r\n aria-hidden=\"true\"\r\n focusable=\"false\"\r\n >\r\n <path data-part=\"finder\" fill=\"currentColor\" fill-rule=\"evenodd\" [attr.d]=\"g.finderOuterPath\" />\r\n <path data-part=\"finder\" fill=\"currentColor\" [attr.d]=\"g.finderInnerPath\" />\r\n <path data-part=\"modules\" fill=\"currentColor\" [attr.d]=\"g.dataPath\" />\r\n </svg>\r\n} @else {\r\n <span class=\"p-3 text-center text-xs\" aria-hidden=\"true\">{{ effectiveAriaLabel() }}</span>\r\n}\r\n\r\n<!-- `contentChild` logo'yu projeksiyon yerinden ba\u011F\u0131ms\u0131z g\u00F6r\u00FCr; kutu yaln\u0131zca bo\u015Faltma varken \u00E7izilir. -->\r\n@if (logoBoxPx()) {\r\n <span\r\n class=\"absolute inset-0 m-auto flex items-center justify-center overflow-hidden rounded-md bg-inherit p-[6%]\"\r\n [style.width.px]=\"logoBoxPx()\"\r\n [style.height.px]=\"logoBoxPx()\"\r\n >\r\n <ng-content select=\"[mfQrCodeLogo]\" />\r\n </span>\r\n}\r\n" }]
|
|
759
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], ecc: [{ type: i0.Input, args: [{ isSignal: true, alias: "ecc", required: false }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], finder: [{ type: i0.Input, args: [{ isSignal: true, alias: "finder", required: false }] }], logoSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "logoSize", required: false }] }], foreground: [{ type: i0.Input, args: [{ isSignal: true, alias: "foreground", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], encoded: [{ type: i0.Output, args: ["encoded"] }], logo: [{ type: i0.ContentChild, args: [i0.forwardRef(() => QrCodeLogo), { isSignal: true }] }] } });
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Generated bundle index. Do not edit.
|
|
763
|
+
*/
|
|
764
|
+
|
|
765
|
+
export { QrCapacityError, QrCode, QrCodeLogo, encodeQr, excavationSize, qrGeometry, qrSvgString };
|
|
766
|
+
//# sourceMappingURL=mesgflow-ui-kit-qr-code.mjs.map
|