ERP-node/frontend/components/report/designer/WatermarkLayer.tsx

179 lines
4.9 KiB
TypeScript

"use client";
import React from "react";
import { getFullImageUrl } from "@/lib/api/client";
import { WatermarkConfig } from "@/types/report";
interface Props {
watermark: WatermarkConfig;
/** 캔버스/페이지 너비 (px) */
width: number;
/** 캔버스/페이지 높이 (px) */
height: number;
}
/**
* 워터마크 레이어 공용 컴포넌트
*
* ReportDesignerCanvas 와 ReportPreviewModal 양쪽에서 사용.
* imageUrl 이 "data:" 로 시작하면 그대로 사용하고,
* 서버 경로인 경우 getFullImageUrl 로 변환한다.
*/
export function WatermarkLayer({ watermark, width, height }: Props) {
const baseStyle: React.CSSProperties = {
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
overflow: "hidden",
zIndex: 1,
};
const rotation = watermark.rotation ?? -45;
const resolveImageSrc = (url: string): string => (url.startsWith("data:") ? url : getFullImageUrl(url));
const renderContent = (tileFontSize?: number) => {
if (watermark.type === "text") {
return (
<span
style={{
fontSize: `${tileFontSize ?? watermark.fontSize ?? 48}px`,
color: watermark.fontColor || "#cccccc",
fontWeight: "bold",
userSelect: "none",
whiteSpace: "nowrap",
}}
>
{watermark.text || "WATERMARK"}
</span>
);
}
if (watermark.imageUrl) {
return null; // 이미지는 각 스타일 블록에서 직접 렌더링
}
return null;
};
// 대각선 스타일
if (watermark.style === "diagonal") {
return (
<div style={baseStyle}>
<div
className="absolute flex items-center justify-center"
style={{
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) rotate(${rotation}deg)`,
opacity: watermark.opacity,
whiteSpace: "nowrap",
}}
>
{watermark.type === "text"
? renderContent()
: watermark.imageUrl && (
<img
src={resolveImageSrc(watermark.imageUrl)}
alt="watermark"
style={{
maxWidth: "50%",
maxHeight: "50%",
objectFit: "contain",
}}
/>
)}
</div>
</div>
);
}
// 중앙 스타일
if (watermark.style === "center") {
return (
<div style={baseStyle}>
<div
className="absolute flex items-center justify-center"
style={{
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
opacity: watermark.opacity,
}}
>
{watermark.type === "text"
? renderContent()
: watermark.imageUrl && (
<img
src={resolveImageSrc(watermark.imageUrl)}
alt="watermark"
style={{
maxWidth: "50%",
maxHeight: "50%",
objectFit: "contain",
}}
/>
)}
</div>
</div>
);
}
// 타일 스타일
if (watermark.style === "tile") {
const tileRotation = watermark.rotation ?? -30;
const tileSize = watermark.type === "text" ? (watermark.fontSize || 48) * 4 : 150;
const cols = Math.ceil(width / tileSize) + 2;
const rows = Math.ceil(height / tileSize) + 2;
return (
<div style={baseStyle}>
<div
style={{
position: "absolute",
top: "-50%",
left: "-50%",
width: "200%",
height: "200%",
display: "flex",
flexWrap: "wrap",
alignContent: "flex-start",
transform: `rotate(${tileRotation}deg)`,
opacity: watermark.opacity,
}}
>
{Array.from({ length: rows * cols }).map((_, index) => (
<div
key={index}
style={{
width: `${tileSize}px`,
height: `${tileSize}px`,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
{watermark.type === "text"
? renderContent(watermark.fontSize || 24)
: watermark.imageUrl && (
<img
src={resolveImageSrc(watermark.imageUrl)}
alt="watermark"
style={{
width: `${tileSize * 0.6}px`,
height: `${tileSize * 0.6}px`,
objectFit: "contain",
}}
/>
)}
</div>
))}
</div>
</div>
);
}
return null;
}