diff --git a/frontend/components/report/designer/SignatureGenerator.tsx b/frontend/components/report/designer/SignatureGenerator.tsx
new file mode 100644
index 00000000..f54c9a7c
--- /dev/null
+++ b/frontend/components/report/designer/SignatureGenerator.tsx
@@ -0,0 +1,223 @@
+"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: "배달의민족 도현", style: "Dokdo, cursive", weight: 400 },
+ { name: "귀여운", style: "Gugi, cursive", weight: 400 },
+ { name: "싱글데이", style: "'Single Day', cursive", weight: 400 },
+ { name: "스타일리시", style: "Stylish, cursive", weight: 400 },
+ { name: "해바라기", style: "Sunflower, sans-serif", weight: 700 },
+ { name: "손글씨", style: "Gaegu, cursive", weight: 700 },
+ ],
+ 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 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";
+ ctx.font = `${font.weight} 124px ${font.style}`;
+ ctx.textAlign = "center";
+ ctx.textBaseline = "middle";
+
+ // 텍스트 그리기
+ 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 (
+
+ {/* 언어 선택 */}
+
+
+
+
+
+ {/* 이름 입력 */}
+
+
+
+ setName(e.target.value)}
+ placeholder={language === "korean" ? "홍길동" : "John Doe"}
+ className="h-8 flex-1"
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ generateSignatures();
+ }
+ }}
+ />
+
+
+
+
+ {/* 생성된 서명 목록 */}
+ {generatedSignatures.length > 0 && (
+
+
+
더블클릭하여 서명을 선택하세요
+
+
+ {generatedSignatures.map((signature, index) => (
+
handleSignatureDoubleClick(signature)}
+ >
+
+

+
+ {fonts[index].name}
+
+
+
+ ))}
+
+
+
+ )}
+
+ );
+}