From 8ecd58b176644fbd3d693a788fc0537289a9308f Mon Sep 17 00:00:00 2001 From: Timo Knuth Date: Tue, 11 Nov 2025 21:41:01 +0100 Subject: [PATCH] Rounded QR code --- src/app/(app)/create/page.tsx | 45 +++++++++++++++++- src/app/(app)/settings/page.tsx | 5 +- src/components/dashboard/QRCodeCard.tsx | 61 +++++++++++++++++++++---- 3 files changed, 96 insertions(+), 15 deletions(-) diff --git a/src/app/(app)/create/page.tsx b/src/app/(app)/create/page.tsx index e0a0f73..e4bec4d 100644 --- a/src/app/(app)/create/page.tsx +++ b/src/app/(app)/create/page.tsx @@ -523,7 +523,27 @@ export default function CreatePage() { onClick={() => { const svg = document.querySelector('#create-qr-preview svg'); if (!svg) return; - const svgData = new XMLSerializer().serializeToString(svg); + + let svgData = new XMLSerializer().serializeToString(svg); + + // If rounded corners, wrap in a clipped SVG + if (cornerStyle === 'rounded') { + const width = svg.getAttribute('width') || '200'; + const height = svg.getAttribute('height') || '200'; + const borderRadius = 20; + + svgData = ` + + + + + + + ${svgData} + + `; + } + const blob = new Blob([svgData], { type: 'image/svg+xml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); @@ -545,6 +565,8 @@ export default function CreatePage() { if (!svg) return; const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); + if (!ctx) return; + const img = new Image(); const svgData = new XMLSerializer().serializeToString(svg); const blob = new Blob([svgData], { type: 'image/svg+xml' }); @@ -553,7 +575,25 @@ export default function CreatePage() { img.onload = () => { canvas.width = 200; canvas.height = 200; - ctx?.drawImage(img, 0, 0); + + // Apply rounded corners if needed + if (cornerStyle === 'rounded') { + const borderRadius = 20; + ctx.beginPath(); + ctx.moveTo(borderRadius, 0); + ctx.lineTo(200 - borderRadius, 0); + ctx.quadraticCurveTo(200, 0, 200, borderRadius); + ctx.lineTo(200, 200 - borderRadius); + ctx.quadraticCurveTo(200, 200, 200 - borderRadius, 200); + ctx.lineTo(borderRadius, 200); + ctx.quadraticCurveTo(0, 200, 0, 200 - borderRadius); + ctx.lineTo(0, borderRadius); + ctx.quadraticCurveTo(0, 0, borderRadius, 0); + ctx.closePath(); + ctx.clip(); + } + + ctx.drawImage(img, 0, 0); canvas.toBlob((blob) => { if (blob) { const url = URL.createObjectURL(blob); @@ -564,6 +604,7 @@ export default function CreatePage() { URL.revokeObjectURL(url); } }); + URL.revokeObjectURL(url); }; img.src = url; }} diff --git a/src/app/(app)/settings/page.tsx b/src/app/(app)/settings/page.tsx index 542168b..cf7e9b4 100644 --- a/src/app/(app)/settings/page.tsx +++ b/src/app/(app)/settings/page.tsx @@ -354,10 +354,9 @@ export default function SettingsPage() { )} diff --git a/src/components/dashboard/QRCodeCard.tsx b/src/components/dashboard/QRCodeCard.tsx index 7c19a46..16a3782 100644 --- a/src/components/dashboard/QRCodeCard.tsx +++ b/src/components/dashboard/QRCodeCard.tsx @@ -82,7 +82,26 @@ END:VCARD`; if (!svg) return; if (format === 'svg') { - const svgData = new XMLSerializer().serializeToString(svg); + let svgData = new XMLSerializer().serializeToString(svg); + + // If rounded corners, wrap in a clipped SVG + if (qr.style?.cornerStyle === 'rounded') { + const width = svg.getAttribute('width') || '96'; + const height = svg.getAttribute('height') || '96'; + const borderRadius = 10; // Smaller radius for dashboard + + svgData = ` + + + + + + + ${svgData} + + `; + } + const blob = new Blob([svgData], { type: 'image/svg+xml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); @@ -96,15 +115,35 @@ END:VCARD`; // Convert SVG to PNG const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); + if (!ctx) return; + const img = new Image(); const svgData = new XMLSerializer().serializeToString(svg); const blob = new Blob([svgData], { type: 'image/svg+xml' }); const url = URL.createObjectURL(blob); - + img.onload = () => { canvas.width = 300; canvas.height = 300; - ctx?.drawImage(img, 0, 0, 300, 300); + + // Apply rounded corners if needed + if (qr.style?.cornerStyle === 'rounded') { + const borderRadius = 30; // Scale up for 300px canvas + ctx.beginPath(); + ctx.moveTo(borderRadius, 0); + ctx.lineTo(300 - borderRadius, 0); + ctx.quadraticCurveTo(300, 0, 300, borderRadius); + ctx.lineTo(300, 300 - borderRadius); + ctx.quadraticCurveTo(300, 300, 300 - borderRadius, 300); + ctx.lineTo(borderRadius, 300); + ctx.quadraticCurveTo(0, 300, 0, 300 - borderRadius); + ctx.lineTo(0, borderRadius); + ctx.quadraticCurveTo(0, 0, borderRadius, 0); + ctx.closePath(); + ctx.clip(); + } + + ctx.drawImage(img, 0, 0, 300, 300); canvas.toBlob((blob) => { if (blob) { const url = URL.createObjectURL(blob); @@ -166,13 +205,15 @@ END:VCARD`;
- +
+ +