diff --git a/src/components/interactives/KasutiEmbroidery.tsx b/src/components/interactives/KasutiEmbroidery.tsx index f1b3437..f5d912e 100644 --- a/src/components/interactives/KasutiEmbroidery.tsx +++ b/src/components/interactives/KasutiEmbroidery.tsx @@ -2146,6 +2146,48 @@ const PatternEditor: React.FC = ({ open, onOpenChange, initi URL.revokeObjectURL(url); }, [edgeList, title]); + const handleDownloadPng = useCallback(() => { + const pattern = normalizePattern(edgeList); + if (!pattern) return; + const trimmed = title.trim() || 'Custom'; + const safeName = + trimmed.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'motif'; + const [rows, cols] = pattern.bbox; + const span = Math.max(rows, cols, 1); + const cellPx = 800 / span; + const pad = 40; + const W = Math.round(cols * cellPx + pad * 2); + const H = Math.round(rows * cellPx + pad * 2); + const canvas = document.createElement('canvas'); + canvas.width = W; + canvas.height = H; + const ctx = canvas.getContext('2d'); + if (!ctx) return; + ctx.fillStyle = '#ffffff'; + ctx.fillRect(0, 0, W, H); + ctx.strokeStyle = '#171717'; + ctx.lineWidth = Math.max(4, cellPx * 0.16); + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + for (const [a, b] of pattern.edges) { + ctx.beginPath(); + ctx.moveTo(pad + a[1] * cellPx, pad + a[0] * cellPx); + ctx.lineTo(pad + b[1] * cellPx, pad + b[0] * cellPx); + ctx.stroke(); + } + canvas.toBlob((blob) => { + if (!blob) return; + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `kasuti-${safeName}-${pattern.edges.length}.png`; + document.body.appendChild(link); + link.click(); + link.remove(); + URL.revokeObjectURL(url); + }, 'image/png'); + }, [edgeList, title]); + const handleUpload = useCallback(() => { fileInputRef.current?.click(); }, []); @@ -2312,7 +2354,7 @@ const PatternEditor: React.FC = ({ open, onOpenChange, initi /> -
+
+