export const createCustomWatermark: CreateCustomWatermark = ({ text, fontSize = 14, color = "rgba(128, 128, 128, 0.3)", // 더 연한 회색, 더 투명하게 opacity = 30, // 더 낮은 opacity rotation = -45, fontFamily = "Arial", }) => { return (ctx, pageNumber, pageWidth, pageHeight) => { if (!text) return; const lines = text.split("\n"); ctx.save(); // 전체 opacity 설정 ctx.globalAlpha = opacity / 100; // 텍스트 스타일 설정 ctx.fillStyle = color; ctx.font = `${fontSize}px ${fontFamily}`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; // 텍스트 크기 계산 const maxLineWidth = Math.max(...lines.map(line => ctx.measureText(line).width )); const lineHeight = fontSize * 1.2; const textBlockHeight = lines.length * lineHeight; // 격자 간격 계산 (텍스트 블록 크기 기반) const horizontalSpacing = maxLineWidth * 0.8; // 가로 간격 const verticalSpacing = textBlockHeight * 1.5; // 세로 간격 // 회전을 고려한 대각선 길이 계산 const diagonal = Math.sqrt(pageWidth * pageWidth + pageHeight * pageHeight); const rotationRad = (rotation * Math.PI) / 180; // 시작 위치 계산 (페이지 밖에서 시작하여 전체 커버) const startX = -diagonal / 2; const startY = -diagonal / 2; const endX = diagonal / 2; const endY = diagonal / 2; // 격자 패턴으로 워터마크 그리기 for (let x = startX; x <= endX; x += horizontalSpacing) { for (let y = startY; y <= endY; y += verticalSpacing) { ctx.save(); // 중심점으로 이동 ctx.translate(pageWidth / 2, pageHeight / 2); // 회전 적용 ctx.rotate(rotationRad); // 현재 위치로 이동 ctx.translate(x, y); // 각 줄 그리기 let yOffset = -(lines.length - 1) * lineHeight / 2; lines.forEach((line) => { ctx.fillText(line, 0, yOffset); yOffset += lineHeight; }); ctx.restore(); } } ctx.restore(); }; }; // 대안 1: 더 촘촘한 격자 패턴 (보안 강화) export const createDenseGridWatermark: CreateCustomWatermark = ({ text, fontSize = 12, // 더 작은 폰트 color = "rgba(150, 150, 150, 0.08)", // 매우 연한 색상 opacity = 8, rotation = -45, fontFamily = "Arial", }) => { return (ctx, pageNumber, pageWidth, pageHeight) => { if (!text) return; const lines = text.split("\n"); ctx.save(); ctx.globalAlpha = opacity / 100; ctx.fillStyle = color; ctx.font = `${fontSize}px ${fontFamily}`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; // 더 촘촘한 간격 const horizontalSpacing = 120; // 고정 간격 const verticalSpacing = 80; const diagonal = Math.sqrt(pageWidth * pageWidth + pageHeight * pageHeight); const rotationRad = (rotation * Math.PI) / 180; for (let x = -diagonal; x <= diagonal; x += horizontalSpacing) { for (let y = -diagonal; y <= diagonal; y += verticalSpacing) { ctx.save(); ctx.translate(pageWidth / 2 + x, pageHeight / 2 + y); ctx.rotate(rotationRad); let yOffset = 0; lines.forEach((line) => { ctx.fillText(line, 0, yOffset); yOffset += fontSize * 1.2; }); ctx.restore(); } } ctx.restore(); }; }; // 대안 2: 랜덤 위치 워터마크 (패턴 예측 방지) export const createRandomizedWatermark: CreateCustomWatermark = ({ text, fontSize = 14, color = "rgba(140, 140, 140, 0.1)", opacity = 10, rotation = -45, fontFamily = "Arial", }) => { return (ctx, pageNumber, pageWidth, pageHeight) => { if (!text) return; const lines = text.split("\n"); ctx.save(); ctx.globalAlpha = opacity / 100; ctx.fillStyle = color; ctx.font = `${fontSize}px ${fontFamily}`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; // 페이지 번호를 시드로 사용하여 일관된 랜덤 생성 const seed = pageNumber * 1000; const random = (min: number, max: number, index: number) => { const x = Math.sin(seed + index) * 10000; return min + (x - Math.floor(x)) * (max - min); }; // 워터마크 개수 (페이지 크기에 비례) const count = Math.floor((pageWidth * pageHeight) / 40000); // 조정 가능 for (let i = 0; i < count; i++) { const x = random(0, pageWidth, i * 2); const y = random(0, pageHeight, i * 2 + 1); const rotationVariation = random(-5, 5, i * 3); // ±5도 변화 ctx.save(); ctx.translate(x, y); ctx.rotate((rotation + rotationVariation) * Math.PI / 180); let yOffset = 0; lines.forEach((line) => { ctx.fillText(line, 0, yOffset); yOffset += fontSize * 1.2; }); ctx.restore(); } ctx.restore(); }; };