"use client"; import { useState, useRef, useEffect } from "react"; import { Card } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Wand2 } from "lucide-react"; interface SignatureGeneratorProps { onSignatureSelect: (dataUrl: string) => void; } // 서명용 손글씨 폰트 목록 (완전한 한글 지원 폰트만 사용) const SIGNATURE_FONTS = { korean: [ { name: "나눔손글씨 붓", style: "'Nanum Brush Script', cursive", weight: 400 }, { name: "나눔손글씨 펜", style: "'Nanum Pen Script', cursive", weight: 400 }, { name: "손글씨 (Gaegu)", style: "Gaegu, cursive", weight: 700 }, { name: "하이멜로디", style: "'Hi Melody', cursive", weight: 400 }, { name: "감자꽃", style: "'Gamja Flower', cursive", weight: 400 }, { name: "푸어스토리", style: "'Poor Story', cursive", weight: 400 }, { name: "도현", style: "'Do Hyeon', sans-serif", weight: 400 }, { name: "주아", style: "Jua, sans-serif", weight: 400 }, ], english: [ { name: "Allura (우아한)", style: "Allura, cursive", weight: 400 }, { name: "Dancing Script (춤추는)", style: "'Dancing Script', cursive", weight: 700 }, { name: "Great Vibes (멋진)", style: "'Great Vibes', cursive", weight: 400 }, { name: "Pacifico (파도)", style: "Pacifico, cursive", weight: 400 }, { name: "Satisfy (만족)", style: "Satisfy, cursive", weight: 400 }, { name: "Caveat (거친)", style: "Caveat, cursive", weight: 700 }, { name: "Permanent Marker", style: "'Permanent Marker', cursive", weight: 400 }, { name: "Shadows Into Light", style: "'Shadows Into Light', cursive", weight: 400 }, { name: "Kalam (볼드)", style: "Kalam, cursive", weight: 700 }, { name: "Patrick Hand", style: "'Patrick Hand', cursive", weight: 400 }, { name: "Indie Flower", style: "'Indie Flower', cursive", weight: 400 }, { name: "Amatic SC", style: "'Amatic SC', cursive", weight: 700 }, { name: "Covered By Your Grace", style: "'Covered By Your Grace', cursive", weight: 400 }, ], }; export function SignatureGenerator({ onSignatureSelect }: SignatureGeneratorProps) { const [language, setLanguage] = useState<"korean" | "english">("korean"); const [name, setName] = useState(""); const [generatedSignatures, setGeneratedSignatures] = useState([]); const [isGenerating, setIsGenerating] = useState(false); const [fontsLoaded, setFontsLoaded] = useState(false); const canvasRefs = useRef<(HTMLCanvasElement | null)[]>([]); const fonts = SIGNATURE_FONTS[language]; // 컴포넌트 마운트 시 폰트 미리 로드 useEffect(() => { const loadAllFonts = async () => { try { await document.fonts.ready; // 모든 폰트를 명시적으로 로드 const allFonts = [...SIGNATURE_FONTS.korean, ...SIGNATURE_FONTS.english]; const fontLoadPromises = allFonts.map((font) => document.fonts.load(`${font.weight} 124px ${font.style}`)); await Promise.all(fontLoadPromises); // 임시 Canvas를 그려서 폰트를 강제로 렌더링 (브라우저가 폰트를 실제로 사용하도록) const tempCanvas = document.createElement("canvas"); tempCanvas.width = 100; tempCanvas.height = 100; const tempCtx = tempCanvas.getContext("2d"); if (tempCtx) { for (const font of allFonts) { tempCtx.font = `${font.weight} 124px ${font.style}`; tempCtx.fillText("테", 0, 50); tempCtx.fillText("A", 0, 50); } } // 폰트 렌더링 후 대기 await new Promise((resolve) => setTimeout(resolve, 500)); setFontsLoaded(true); } catch (error) { console.warn("Font preloading failed:", error); await new Promise((resolve) => setTimeout(resolve, 1500)); setFontsLoaded(true); } }; loadAllFonts(); }, []); // 서명 생성 const generateSignatures = async () => { if (!name.trim()) return; setIsGenerating(true); // 폰트가 미리 로드될 때까지 대기 if (!fontsLoaded) { await new Promise((resolve) => { const checkInterval = setInterval(() => { if (fontsLoaded) { clearInterval(checkInterval); resolve(true); } }, 100); }); } // 사용자가 입력한 텍스트로 각 폰트의 글리프를 미리 로드 const preloadCanvas = document.createElement("canvas"); preloadCanvas.width = 500; preloadCanvas.height = 200; const preloadCtx = preloadCanvas.getContext("2d"); if (preloadCtx) { for (const font of fonts) { preloadCtx.font = `${font.weight} 124px ${font.style}`; preloadCtx.fillText(name, 0, 100); } } // 글리프 로드 대기 (중요: 첫 렌더링 후 폰트가 완전히 로드되도록) await new Promise((resolve) => setTimeout(resolve, 300)); const newSignatures: string[] = []; // 동기적으로 하나씩 생성 for (const font of fonts) { const canvas = document.createElement("canvas"); canvas.width = 500; canvas.height = 200; const ctx = canvas.getContext("2d"); if (ctx) { // 배경 흰색 ctx.fillStyle = "#ffffff"; ctx.fillRect(0, 0, canvas.width, canvas.height); // 텍스트 스타일 ctx.fillStyle = "#000000"; let fontSize = 124; ctx.font = `${font.weight} ${fontSize}px ${font.style}`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; // 텍스트 너비 측정 및 크기 조정 (캔버스 너비의 90% 이내로) let textWidth = ctx.measureText(name).width; const maxWidth = canvas.width * 0.9; while (textWidth > maxWidth && fontSize > 30) { fontSize -= 2; ctx.font = `${font.weight} ${fontSize}px ${font.style}`; textWidth = ctx.measureText(name).width; } // 텍스트 그리기 ctx.fillText(name, canvas.width / 2, canvas.height / 2); // 데이터 URL로 변환 newSignatures.push(canvas.toDataURL("image/png")); } } setGeneratedSignatures(newSignatures); setIsGenerating(false); }; // 서명 선택 (더블클릭) const handleSignatureDoubleClick = (dataUrl: string) => { onSignatureSelect(dataUrl); }; return (
{/* 언어 선택 */}
{/* 이름 입력 */}
{ const input = e.target.value; // 국문일 때는 한글, 영문일 때는 영문+숫자+공백만 허용 if (language === "korean") { // 한글만 허용 (자음, 모음, 완성된 글자) const koreanOnly = input.replace(/[^\u3131-\u3163\uac00-\ud7a3\s]/g, ""); setName(koreanOnly); } else { // 영문, 숫자, 공백만 허용 const englishOnly = input.replace(/[^a-zA-Z\s]/g, ""); setName(englishOnly); } }} placeholder={language === "korean" ? "홍길동" : "John Doe"} maxLength={14} className="h-8 flex-1" onKeyDown={(e) => { if (e.key === "Enter") { generateSignatures(); } }} />
{/* 생성된 서명 목록 */} {generatedSignatures.length > 0 && (

더블클릭하여 서명을 선택하세요

{generatedSignatures.map((signature, index) => ( handleSignatureDoubleClick(signature)} >
{`서명

{fonts[index].name}

))}
)}
); }