@qr-styled/browser 1.0.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.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Handles gradient creation for QR codes
3
+ */
4
+ export class GradientRenderer {
5
+ constructor(ctx, options) {
6
+ this.ctx = ctx;
7
+ this.options = options;
8
+ }
9
+ /**
10
+ * Creates a gradient fill style
11
+ */
12
+ createGradient() {
13
+ const { size, gradientColors, gradientAngle } = this.options;
14
+ const ctx = this.ctx;
15
+ // Parse gradient colors
16
+ const colors = gradientColors.split(',').map(c => c.trim());
17
+ // Calculate angle in radians
18
+ const angleRad = (gradientAngle * Math.PI) / 180;
19
+ // Calculate gradient start and end points
20
+ const centerX = size / 2;
21
+ const centerY = size / 2;
22
+ const length = Math.sqrt(size * size + size * size) / 2;
23
+ const x0 = centerX - Math.cos(angleRad) * length;
24
+ const y0 = centerY - Math.sin(angleRad) * length;
25
+ const x1 = centerX + Math.cos(angleRad) * length;
26
+ const y1 = centerY + Math.sin(angleRad) * length;
27
+ // Create linear gradient
28
+ const gradient = ctx.createLinearGradient(x0, y0, x1, y1);
29
+ // Add color stops uniformly distributed
30
+ colors.forEach((color, index) => {
31
+ const stop = index / (colors.length - 1);
32
+ gradient.addColorStop(stop, color);
33
+ });
34
+ return gradient;
35
+ }
36
+ /**
37
+ * Gets the appropriate fill style (gradient or solid color)
38
+ */
39
+ getFillStyle() {
40
+ const { gradient, foregroundColor } = this.options;
41
+ return gradient ? this.createGradient() : foregroundColor;
42
+ }
43
+ }
@@ -0,0 +1,25 @@
1
+ import { QROptions } from '../utils/types.js';
2
+ /**
3
+ * Handles logo rendering on QR codes
4
+ */
5
+ export declare class LogoRenderer {
6
+ private ctx;
7
+ private options;
8
+ constructor(ctx: CanvasRenderingContext2D, options: Required<QROptions>);
9
+ /**
10
+ * Renders logo with background
11
+ */
12
+ render(logo: string | HTMLImageElement): Promise<void>;
13
+ /**
14
+ * Loads an image from URL
15
+ */
16
+ private loadImage;
17
+ /**
18
+ * Draws logo background (circle or rounded square)
19
+ */
20
+ private drawLogoBackground;
21
+ /**
22
+ * Draws logo with shadow effect
23
+ */
24
+ private drawLogoWithShadow;
25
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Handles logo rendering on QR codes
3
+ */
4
+ export class LogoRenderer {
5
+ constructor(ctx, options) {
6
+ this.ctx = ctx;
7
+ this.options = options;
8
+ }
9
+ /**
10
+ * Renders logo with background
11
+ */
12
+ async render(logo) {
13
+ if (!logo)
14
+ return;
15
+ let img;
16
+ if (typeof logo === 'string') {
17
+ // Load image from URL
18
+ img = await this.loadImage(logo);
19
+ }
20
+ else {
21
+ img = logo;
22
+ }
23
+ const { size, logoPadding } = this.options;
24
+ const logoSize = size * 0.25;
25
+ const bgSize = logoSize + logoPadding;
26
+ const centerX = size / 2;
27
+ const centerY = size / 2;
28
+ // Draw logo background
29
+ this.drawLogoBackground(centerX, centerY, bgSize);
30
+ // Draw logo with shadow
31
+ this.drawLogoWithShadow(img, centerX, centerY, logoSize);
32
+ }
33
+ /**
34
+ * Loads an image from URL
35
+ */
36
+ loadImage(url) {
37
+ return new Promise((resolve, reject) => {
38
+ const img = new Image();
39
+ img.crossOrigin = 'anonymous';
40
+ img.onload = () => resolve(img);
41
+ img.onerror = reject;
42
+ img.src = url;
43
+ });
44
+ }
45
+ /**
46
+ * Draws logo background (circle or rounded square)
47
+ */
48
+ drawLogoBackground(centerX, centerY, bgSize) {
49
+ const { logoShape, logoRadius, backgroundColor } = this.options;
50
+ const ctx = this.ctx;
51
+ ctx.fillStyle = backgroundColor;
52
+ if (logoShape === 'circle') {
53
+ ctx.beginPath();
54
+ ctx.arc(centerX, centerY, bgSize / 2, 0, Math.PI * 2);
55
+ ctx.fill();
56
+ }
57
+ else {
58
+ // Rounded square
59
+ const squareX = centerX - bgSize / 2;
60
+ const squareY = centerY - bgSize / 2;
61
+ ctx.beginPath();
62
+ ctx.moveTo(squareX + logoRadius, squareY);
63
+ ctx.arcTo(squareX + bgSize, squareY, squareX + bgSize, squareY + bgSize, logoRadius);
64
+ ctx.arcTo(squareX + bgSize, squareY + bgSize, squareX, squareY + bgSize, logoRadius);
65
+ ctx.arcTo(squareX, squareY + bgSize, squareX, squareY, logoRadius);
66
+ ctx.arcTo(squareX, squareY, squareX + bgSize, squareY, logoRadius);
67
+ ctx.closePath();
68
+ ctx.fill();
69
+ }
70
+ }
71
+ /**
72
+ * Draws logo with shadow effect
73
+ */
74
+ drawLogoWithShadow(img, centerX, centerY, logoSize) {
75
+ const ctx = this.ctx;
76
+ // Apply shadow
77
+ ctx.shadowColor = 'rgba(0, 0, 0, 0.15)';
78
+ ctx.shadowBlur = 12;
79
+ ctx.shadowOffsetX = 0;
80
+ ctx.shadowOffsetY = 2;
81
+ // Draw logo
82
+ const logoX = centerX - logoSize / 2;
83
+ const logoY = centerY - logoSize / 2;
84
+ ctx.drawImage(img, logoX, logoY, logoSize, logoSize);
85
+ // Reset shadow
86
+ ctx.shadowColor = 'transparent';
87
+ ctx.shadowBlur = 0;
88
+ }
89
+ }
@@ -0,0 +1,43 @@
1
+ import { QROptions } from '../utils/types.js';
2
+ interface QRModules {
3
+ size: number;
4
+ get(row: number, col: number): number;
5
+ }
6
+ interface RenderOptions extends Required<QROptions> {
7
+ moduleCount: number;
8
+ moduleSize: number;
9
+ }
10
+ /**
11
+ * Handles QR module rendering with rounded corners
12
+ */
13
+ export declare class ModuleRenderer {
14
+ private ctx;
15
+ private modules;
16
+ private options;
17
+ constructor(ctx: CanvasRenderingContext2D, modules: QRModules, options: RenderOptions);
18
+ /**
19
+ * Checks if a module exists at given position
20
+ */
21
+ private hasModule;
22
+ /**
23
+ * Checks if position is part of finder pattern (eye)
24
+ */
25
+ private isPartOfEye;
26
+ /**
27
+ * Renders all QR modules
28
+ */
29
+ render(fillStyle: string | CanvasGradient): void;
30
+ /**
31
+ * Renders modules with rounded corners
32
+ */
33
+ private renderRoundedModules;
34
+ /**
35
+ * Draws a single module with selectively rounded corners
36
+ */
37
+ private drawRoundedModule;
38
+ /**
39
+ * Renders simple square modules
40
+ */
41
+ private renderSquareModules;
42
+ }
43
+ export {};
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Handles QR module rendering with rounded corners
3
+ */
4
+ export class ModuleRenderer {
5
+ constructor(ctx, modules, options) {
6
+ this.ctx = ctx;
7
+ this.modules = modules;
8
+ this.options = options;
9
+ }
10
+ /**
11
+ * Checks if a module exists at given position
12
+ */
13
+ hasModule(row, col) {
14
+ const { moduleCount } = this.options;
15
+ if (row < 0 || row >= moduleCount || col < 0 || col >= moduleCount) {
16
+ return false;
17
+ }
18
+ return this.modules.get(row, col) === 1;
19
+ }
20
+ /**
21
+ * Checks if position is part of finder pattern (eye)
22
+ */
23
+ isPartOfEye(row, col) {
24
+ const { moduleCount } = this.options;
25
+ // Top-left eye
26
+ if (row < 7 && col < 7)
27
+ return true;
28
+ // Top-right eye
29
+ if (row < 7 && col >= moduleCount - 7)
30
+ return true;
31
+ // Bottom-left eye
32
+ if (row >= moduleCount - 7 && col < 7)
33
+ return true;
34
+ return false;
35
+ }
36
+ /**
37
+ * Renders all QR modules
38
+ */
39
+ render(fillStyle) {
40
+ const { rounded } = this.options;
41
+ this.ctx.fillStyle = fillStyle;
42
+ if (rounded) {
43
+ this.renderRoundedModules();
44
+ }
45
+ else {
46
+ this.renderSquareModules();
47
+ }
48
+ }
49
+ /**
50
+ * Renders modules with rounded corners
51
+ */
52
+ renderRoundedModules() {
53
+ const { moduleCount, moduleSize, padding, moduleRadius, eyeColor, eyeRadius } = this.options;
54
+ const ctx = this.ctx;
55
+ for (let row = 0; row < moduleCount; row++) {
56
+ for (let col = 0; col < moduleCount; col++) {
57
+ if (this.modules.get(row, col)) {
58
+ const x = padding + col * moduleSize;
59
+ const y = padding + row * moduleSize;
60
+ // Determine if this is part of an eye (finder pattern)
61
+ const isEye = this.isPartOfEye(row, col);
62
+ // Use eye-specific color if available
63
+ if (isEye && eyeColor) {
64
+ ctx.fillStyle = eyeColor;
65
+ }
66
+ // Use eye-specific radius if available, otherwise use moduleRadius
67
+ const cornerRadius = isEye && eyeRadius !== undefined && eyeRadius > 0
68
+ ? moduleSize * eyeRadius
69
+ : moduleSize * moduleRadius;
70
+ // Check adjacent modules
71
+ const top = this.hasModule(row - 1, col);
72
+ const right = this.hasModule(row, col + 1);
73
+ const bottom = this.hasModule(row + 1, col);
74
+ const left = this.hasModule(row, col - 1);
75
+ // Determine which corners to round
76
+ const roundTL = !top && !left;
77
+ const roundTR = !top && !right;
78
+ const roundBR = !bottom && !right;
79
+ const roundBL = !bottom && !left;
80
+ // Small extension to eliminate gaps
81
+ const gap = 0.5;
82
+ const extendTop = top ? gap : 0;
83
+ const extendRight = right ? gap : 0;
84
+ const extendBottom = bottom ? gap : 0;
85
+ const extendLeft = left ? gap : 0;
86
+ this.drawRoundedModule(x, y, moduleSize, cornerRadius, { roundTL, roundTR, roundBR, roundBL }, { extendTop, extendRight, extendBottom, extendLeft });
87
+ }
88
+ }
89
+ }
90
+ }
91
+ /**
92
+ * Draws a single module with selectively rounded corners
93
+ */
94
+ drawRoundedModule(x, y, size, radius, corners, extensions) {
95
+ const ctx = this.ctx;
96
+ const { roundTL, roundTR, roundBR, roundBL } = corners;
97
+ const { extendTop, extendRight, extendBottom, extendLeft } = extensions;
98
+ ctx.beginPath();
99
+ // Top-left corner
100
+ if (roundTL) {
101
+ ctx.moveTo(x + radius - extendLeft, y - extendTop);
102
+ }
103
+ else {
104
+ ctx.moveTo(x - extendLeft, y - extendTop);
105
+ }
106
+ // Top side and top-right corner
107
+ if (roundTR) {
108
+ ctx.lineTo(x + size - radius + extendRight, y - extendTop);
109
+ ctx.quadraticCurveTo(x + size + extendRight, y - extendTop, x + size + extendRight, y + radius - extendTop);
110
+ }
111
+ else {
112
+ ctx.lineTo(x + size + extendRight, y - extendTop);
113
+ }
114
+ // Right side and bottom-right corner
115
+ if (roundBR) {
116
+ ctx.lineTo(x + size + extendRight, y + size - radius + extendBottom);
117
+ ctx.quadraticCurveTo(x + size + extendRight, y + size + extendBottom, x + size - radius + extendRight, y + size + extendBottom);
118
+ }
119
+ else {
120
+ ctx.lineTo(x + size + extendRight, y + size + extendBottom);
121
+ }
122
+ // Bottom side and bottom-left corner
123
+ if (roundBL) {
124
+ ctx.lineTo(x + radius - extendLeft, y + size + extendBottom);
125
+ ctx.quadraticCurveTo(x - extendLeft, y + size + extendBottom, x - extendLeft, y + size - radius + extendBottom);
126
+ }
127
+ else {
128
+ ctx.lineTo(x - extendLeft, y + size + extendBottom);
129
+ }
130
+ // Left side and close path
131
+ if (roundTL) {
132
+ ctx.lineTo(x - extendLeft, y + radius - extendTop);
133
+ ctx.quadraticCurveTo(x - extendLeft, y - extendTop, x + radius - extendLeft, y - extendTop);
134
+ }
135
+ else {
136
+ ctx.lineTo(x - extendLeft, y - extendTop);
137
+ }
138
+ ctx.closePath();
139
+ ctx.fill();
140
+ }
141
+ /**
142
+ * Renders simple square modules
143
+ */
144
+ renderSquareModules() {
145
+ const { moduleCount, moduleSize, padding, eyeColor } = this.options;
146
+ const ctx = this.ctx;
147
+ for (let row = 0; row < moduleCount; row++) {
148
+ for (let col = 0; col < moduleCount; col++) {
149
+ if (this.modules.get(row, col)) {
150
+ const x = padding + col * moduleSize;
151
+ const y = padding + row * moduleSize;
152
+ // Use eye-specific color if this is part of an eye
153
+ if (this.isPartOfEye(row, col) && eyeColor) {
154
+ ctx.fillStyle = eyeColor;
155
+ }
156
+ ctx.fillRect(x, y, moduleSize, moduleSize);
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
@@ -0,0 +1,22 @@
1
+ import type { QROptions } from '../utils/types.js';
2
+ /**
3
+ * Renderer for SVG output
4
+ */
5
+ export declare class SVGRenderer {
6
+ private options;
7
+ private qrData;
8
+ private size;
9
+ constructor(qrData: any, options: Required<QROptions>, size: number);
10
+ /**
11
+ * Generates SVG string
12
+ */
13
+ generate(): string;
14
+ /**
15
+ * Checks if module exists at position
16
+ */
17
+ private hasModule;
18
+ /**
19
+ * Checks if position is part of finder pattern (eye)
20
+ */
21
+ private isPartOfEye;
22
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Renderer for SVG output
3
+ */
4
+ export class SVGRenderer {
5
+ constructor(qrData, options, size) {
6
+ this.qrData = qrData;
7
+ this.options = options;
8
+ this.size = size;
9
+ }
10
+ /**
11
+ * Generates SVG string
12
+ */
13
+ generate() {
14
+ const { size, backgroundColor, foregroundColor, moduleRadius, eyeColor, eyeRadius } = this.options;
15
+ const modules = this.qrData.modules;
16
+ const moduleCount = modules.size;
17
+ const moduleSize = size / moduleCount;
18
+ let svg = `<?xml version="1.0" encoding="UTF-8"?>`;
19
+ svg += `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">`;
20
+ // Background
21
+ svg += `<rect width="${size}" height="${size}" fill="${backgroundColor}"/>`;
22
+ // QR modules
23
+ svg += `<g fill="${foregroundColor}">`;
24
+ for (let row = 0; row < moduleCount; row++) {
25
+ for (let col = 0; col < moduleCount; col++) {
26
+ if (this.hasModule(col, row)) {
27
+ const x = col * moduleSize;
28
+ const y = row * moduleSize;
29
+ // Check if this is part of an eye (finder pattern)
30
+ const isEye = this.isPartOfEye(col, row, moduleCount);
31
+ const color = isEye && eyeColor ? eyeColor : foregroundColor;
32
+ const radius = isEye && eyeRadius
33
+ ? eyeRadius * moduleSize
34
+ : moduleRadius * moduleSize;
35
+ if (radius > 0) {
36
+ svg += `<rect x="${x}" y="${y}" width="${moduleSize}" height="${moduleSize}" rx="${radius}" fill="${color}"/>`;
37
+ }
38
+ else {
39
+ svg += `<rect x="${x}" y="${y}" width="${moduleSize}" height="${moduleSize}" fill="${color}"/>`;
40
+ }
41
+ }
42
+ }
43
+ }
44
+ svg += `</g>`;
45
+ svg += `</svg>`;
46
+ return svg;
47
+ }
48
+ /**
49
+ * Checks if module exists at position
50
+ */
51
+ hasModule(x, y) {
52
+ const modules = this.qrData.modules;
53
+ if (x < 0 || x >= modules.size || y < 0 || y >= modules.size) {
54
+ return false;
55
+ }
56
+ return modules.data[y * modules.size + x] === 1;
57
+ }
58
+ /**
59
+ * Checks if position is part of finder pattern (eye)
60
+ */
61
+ isPartOfEye(x, y, size) {
62
+ // Top-left eye
63
+ if (x < 7 && y < 7)
64
+ return true;
65
+ // Top-right eye
66
+ if (x >= size - 7 && y < 7)
67
+ return true;
68
+ // Bottom-left eye
69
+ if (x < 7 && y >= size - 7)
70
+ return true;
71
+ return false;
72
+ }
73
+ }
@@ -0,0 +1,25 @@
1
+ import type { VCardData, WiFiData, EmailData, SMSData, GeoData, QRDataType } from './types.js';
2
+ /**
3
+ * Formats vCard contact data to vCard 3.0 format
4
+ */
5
+ export declare function formatVCard(data: VCardData): string;
6
+ /**
7
+ * Formats WiFi configuration to WiFi QR format
8
+ */
9
+ export declare function formatWiFi(data: WiFiData): string;
10
+ /**
11
+ * Formats email data to mailto URI
12
+ */
13
+ export declare function formatEmail(data: EmailData): string;
14
+ /**
15
+ * Formats SMS data to SMS URI
16
+ */
17
+ export declare function formatSMS(data: SMSData): string;
18
+ /**
19
+ * Formats geolocation data to geo URI
20
+ */
21
+ export declare function formatGeo(data: GeoData): string;
22
+ /**
23
+ * Formats QR data based on type
24
+ */
25
+ export declare function formatQRData(type: QRDataType, url?: string, data?: VCardData | WiFiData | EmailData | SMSData | GeoData): string;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Formats vCard contact data to vCard 3.0 format
3
+ */
4
+ export function formatVCard(data) {
5
+ const lines = ['BEGIN:VCARD', 'VERSION:3.0'];
6
+ if (data.firstName || data.lastName) {
7
+ const name = `${data.lastName || ''};${data.firstName || ''};;;`;
8
+ lines.push(`N:${name}`);
9
+ lines.push(`FN:${[data.firstName, data.lastName].filter(Boolean).join(' ')}`);
10
+ }
11
+ if (data.organization) {
12
+ lines.push(`ORG:${data.organization}`);
13
+ }
14
+ if (data.title) {
15
+ lines.push(`TITLE:${data.title}`);
16
+ }
17
+ if (data.phone) {
18
+ lines.push(`TEL:${data.phone}`);
19
+ }
20
+ if (data.email) {
21
+ lines.push(`EMAIL:${data.email}`);
22
+ }
23
+ if (data.url) {
24
+ lines.push(`URL:${data.url}`);
25
+ }
26
+ if (data.address || data.city || data.state || data.zip || data.country) {
27
+ const adr = `;;${data.address || ''};${data.city || ''};${data.state || ''};${data.zip || ''};${data.country || ''}`;
28
+ lines.push(`ADR:${adr}`);
29
+ }
30
+ lines.push('END:VCARD');
31
+ return lines.join('\n');
32
+ }
33
+ /**
34
+ * Formats WiFi configuration to WiFi QR format
35
+ */
36
+ export function formatWiFi(data) {
37
+ const { ssid, password = '', encryption = 'WPA', hidden = false } = data;
38
+ const escape = (str) => str.replace(/([\\;,:])/g, '\\$1');
39
+ let result = 'WIFI:';
40
+ result += `T:${encryption};`;
41
+ result += `S:${escape(ssid)};`;
42
+ if (password) {
43
+ result += `P:${escape(password)};`;
44
+ }
45
+ if (hidden) {
46
+ result += 'H:true;';
47
+ }
48
+ result += ';';
49
+ return result;
50
+ }
51
+ /**
52
+ * Formats email data to mailto URI
53
+ */
54
+ export function formatEmail(data) {
55
+ let result = `mailto:${data.email}`;
56
+ const params = [];
57
+ if (data.subject) {
58
+ params.push(`subject=${encodeURIComponent(data.subject)}`);
59
+ }
60
+ if (data.body) {
61
+ params.push(`body=${encodeURIComponent(data.body)}`);
62
+ }
63
+ if (params.length > 0) {
64
+ result += '?' + params.join('&');
65
+ }
66
+ return result;
67
+ }
68
+ /**
69
+ * Formats SMS data to SMS URI
70
+ */
71
+ export function formatSMS(data) {
72
+ let result = `sms:${data.phone}`;
73
+ if (data.message) {
74
+ result += `?body=${encodeURIComponent(data.message)}`;
75
+ }
76
+ return result;
77
+ }
78
+ /**
79
+ * Formats geolocation data to geo URI
80
+ */
81
+ export function formatGeo(data) {
82
+ return `geo:${data.latitude},${data.longitude}`;
83
+ }
84
+ /**
85
+ * Formats QR data based on type
86
+ */
87
+ export function formatQRData(type, url, data) {
88
+ switch (type) {
89
+ case 'url':
90
+ return url || '';
91
+ case 'text':
92
+ return url || '';
93
+ case 'vcard':
94
+ return data ? formatVCard(data) : '';
95
+ case 'wifi':
96
+ return data ? formatWiFi(data) : '';
97
+ case 'email':
98
+ return data ? formatEmail(data) : '';
99
+ case 'sms':
100
+ return data ? formatSMS(data) : '';
101
+ case 'geo':
102
+ return data ? formatGeo(data) : '';
103
+ default:
104
+ return url || '';
105
+ }
106
+ }