Files
PDFWiz-App/src/PDFViewer/PDFViewer.tsx
jeong-wonjin e9198bb7db 최신커밋
2024-02-21 10:16:30 +09:00

1255 lines
41 KiB
TypeScript

import React, {useEffect, useRef, useState} from "react";
import {
getDocument,
GlobalWorkerOptions,
PageViewport,
PDFDocumentProxy,
PDFPageProxy,
renderTextLayer
} from "pdfjs-dist";
import "./PDFViewer.css"
import {Button, Dropdown, Input, MenuProps, Modal, Progress, Space, Tooltip} from "antd";
import {
CaretLeftOutlined,
CaretRightOutlined, DownloadOutlined, FullscreenOutlined,
PercentageOutlined, PrinterOutlined,
ZoomInOutlined,
ZoomOutOutlined
} from "@ant-design/icons";
import html2canvas from "html2canvas";
import {PDFDocument, PDFPage} from "pdf-lib";
const currentUrl = getCurrentUrl();
console.log(currentUrl);
GlobalWorkerOptions.workerSrc = `${currentUrl}/pdf.worker.js`;
function getCurrentUrl(): string {
// window.location 객체를 통해 현재 URL 정보에 접근
const { protocol, hostname, port } = window.location;
// 포트가 80이 아닌 경우에만 포트를 포함한 URL 반환
const portSuffix = (port === '80' || port === '443') ? '' : `:${port}`;
// 조합된 URL 반환
return `${protocol}//${hostname}${portSuffix}/pdfwiz/pdfjs`;
}
interface PdfViewerProps {
fileUrl: string;
fileName?:string;
downloadFileUrl?:string;
scale?:number;
renderText?:boolean;
waterMarkText?:string;
}
async function loadPage(pdf:PDFDocumentProxy, pageNum:number, scale:number, rotate:number, container:HTMLDivElement, isRenderText:boolean, waterMarkText:string|undefined)
{
const pageLayer = document.createElement("div");
const canvas = document.createElement("canvas");
const cavasLayer = document.createElement("div");
const loadingLayer = document.createElement("div");
const drawLayer = document.createElement("div");
const loadingImage = document.createElement("img");
loadingImage.setAttribute("src", "pdfwiz/image/loading.svg");
const textLayer = isRenderText ? document.createElement("div") : null;
if (textLayer)
{
pageLayer.appendChild(textLayer);
textLayer.className = "textLayer";
}
pageLayer.className = "pageLayer";
cavasLayer.className = "loading_canvasWrapper";
pageLayer.setAttribute("data-pageIdx", `${pageNum}`);
pageLayer.setAttribute("id", `page-${pageNum}`);
pageLayer.setAttribute("data-pageLoad", `false`);
drawLayer.className = "drawLayer";
drawLayer.setAttribute("data-pageIdx", `${pageNum}`);
drawLayer.setAttribute("id", `draw-${pageNum}`);
if (waterMarkText)
{
const waterMarkLayer = document.createElement("div");
waterMarkLayer.className = "waterMarkLayer";
drawLayer.appendChild(waterMarkLayer);
}
pageLayer.appendChild(drawLayer);
container.appendChild(pageLayer);
cavasLayer.appendChild(canvas);
pageLayer.appendChild(cavasLayer);
loadingLayer.appendChild(loadingImage);
loadingLayer.className = "loading";
cavasLayer.appendChild(loadingLayer);
const page = await pdf.getPage(pageNum);
const viewport = page.getViewport({
scale: scale
,rotation:rotate|0
})
canvas.width = viewport.width;
canvas.height = viewport.height;
const context = canvas.getContext("2d");
if (!context) return;
if(pageNum <= 3)
{
const isLoad = pageLayer.getAttribute("data-pageLoad") === "true" ? true : false;
if(!isLoad)
{
pageLayer.setAttribute("data-pageLoad", `true`);
await renderPage(page, context, viewport, textLayer).finally(() => {
cavasLayer.className = "canvasWrapper";
if (loadingImage && cavasLayer.contains(loadingLayer)) {
cavasLayer.removeChild(loadingLayer);
}
});
}
}
}
async function renderPage(page:PDFPageProxy, context:CanvasRenderingContext2D, viewport:PageViewport, textLayer:HTMLDivElement|null)
{
const renderContext = {
canvasContext: context,
viewport: viewport,
};
await page.render(renderContext).promise;
if (textLayer)
{
page.getTextContent().then(textContent => {
const txtLayer = renderTextLayer(
{
textContentSource: textContent,
container: textLayer,
viewport: viewport,
isOffscreenCanvasSupported:true,
}
).promise;
}).finally(()=>{
const parent = textLayer.parentElement;
if (parent != null)
{
const drawLayer = parent.querySelector(".drawLayer");
const pageStyle = textLayer.getAttribute("style");
if (drawLayer && pageStyle)
{
drawLayer.setAttribute("style", pageStyle);
}
}
});
}
}
export const PDFViewer = (props: PdfViewerProps) => {
const { fileUrl , scale, renderText, downloadFileUrl
, waterMarkText, fileName} = props;
const [modalVisible, setModalVisible] = useState<boolean>(false);
//const downloadUrl = downloadFileUrl || fileUrl;
//const fileTitle= fileName||fileUrl;
const [threshold, setThreshold] = useState<number>(0.7);
const [scaleValue, setScaleValue] = useState<number>(scale || 1.5);
const [currentPage, setCurrentPage] = useState<number>(1);
const [totalPage, setTotalPage] = useState<number>(0);
const rightClickCountRef = useRef(0);
const leftClickCountRef = useRef(0);
const [observer, setObserver] = useState<IntersectionObserver>();
const [calcScaleValue, setCalcScaleValue] = useState(scaleValue);
const timerRef = useRef<NodeJS.Timeout | null>(null);
const timerClickRef = useRef<NodeJS.Timeout | null>(null);
const [pdfDocument, setPdfDocument] = useState<PDFDocumentProxy>();
const [modalTitle, setModalTitle] = useState<string>();
const [progressVisible, setProgressVisible] = useState<boolean>(false);
const [procPercent, setProcPercent] = useState<number>(0);
const [passwordVisible, setPasswordVisible] = useState<boolean>(false);
const [pdfPassword, setPdfPassword] = useState<string>("");
const [pdfErrorMsg, setPdfErrorMsg] = useState<string>("");
const waterMarkCanvasRef = useRef<HTMLCanvasElement>(null);
const isRenderText = renderText||false;
const containerRef = useRef<HTMLDivElement>(null);
const items:MenuProps['items'] = [
{
key: 'ACTUAL_SIZE',
label: '실제크기',
},
{
key: 'WIDTH_FIT',
label: '가로에 맞추기',
},
{
key: 'HEIGHT_FIT',
label: '세로에 맞추기',
},
{
key: '0.5',
label: '50%',
},
{
key: '0.75',
label: '70%',
},
{
key: '1.0',
label: '100%',
},
{
key: '1.25',
label: '125%',
},
{
key: '1.5',
label: '150%',
},
{
key: '2.0',
label: '200%',
},
{
key: '3.0',
label: '300%',
},
{
key: '4.0',
label: '400%',
},
];
const handleRatioMenuClick: MenuProps['onClick'] = (e) => {
const key:string = e.key;
switch (key)
{
case 'ACTUAL_SIZE' :
ZoomCtrl(1.0);
break;
case 'WIDTH_FIT' :
ZoomCtrl(getWidthFitScale());
break;
case 'HEIGHT_FIT' :
ZoomCtrl(getHeightFitScale());
break;
case '0.5' :
ZoomCtrl(0.5);
break;
case '0.75' :
ZoomCtrl(0.75);
break;
case '1.0' :
ZoomCtrl(1.0);
break;
case '1.25' :
ZoomCtrl(1.25);
break;
case '1.5' :
ZoomCtrl(1.5);
break;
case '2.0' :
ZoomCtrl(2.0);
break;
case '3.0' :
ZoomCtrl(3.0);
break;
case '4.0' :
ZoomCtrl(4.0);
break;
default:
break;
}
};
function calcThreshold(stdScaleValue:number) :number
{
if (0.5 >= stdScaleValue )
{
return 1.0;
}
else if (0.75 >= stdScaleValue && 1.0 < stdScaleValue)
{
return 0.9;
}
else if (1.0 >= stdScaleValue && 1.25 < stdScaleValue)
{
return 0.8;
}
else if (1.25 >= stdScaleValue && 1.5 < stdScaleValue)
{
return 0.7;
}
else if (1.5 >= stdScaleValue && 1.75 < stdScaleValue)
{
return 0.6;
}
else if (1.75 >= stdScaleValue && 2.0 < stdScaleValue)
{
return 0.5;
}
else if (2.0 >= stdScaleValue && 3.0 < stdScaleValue)
{
return 0.4;
}
else if (4.0 >= stdScaleValue )
{
return 0.3;
}
else
{
return 0.65
}
}
const raioMenuProps = {
items,
onClick: handleRatioMenuClick,
};
function enterFullScreen(elementId: string): void {
const element = document.getElementById(elementId) as HTMLElement & {
requestFullscreen?: () => Promise<void>;
webkitRequestFullscreen?: () => Promise<void>;
mozRequestFullScreen?: () => Promise<void>;
msRequestFullscreen?: () => Promise<void>;
};
if (!element) {
console.error(`Element with id ${elementId} not found.`);
return;
}
const onFullScreenEntered = () => {
// 여기에 원하는 로직을 추가하세요.
console.log("전체 화면 모드에 진입하였습니다.");
ZoomCtrl(getWindowHeightFitScale());
// 이벤트 리스너 제거
document.removeEventListener('fullscreenchange', onFullScreenEntered);
};
// 이벤트 리스너 추가
document.addEventListener('fullscreenchange', onFullScreenEntered);
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
} else {
console.error("Fullscreen API is not supported.");
}
}
function scrollToDiv(pageNum:number) {
const divId = `page-${pageNum}`;
const element = document.getElementById(divId);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
} else {
console.error(`Element with id ${divId} not found.`);
}
}
function movePage(pageNum:number)
{
if(pageNum < 1)
{
pageNum = 1;
}
else if(pageNum > totalPage)
{
pageNum = totalPage;
}
scrollToDiv(pageNum);
return pageNum;
}
function getWidthFitScale()
{
const parentObj = document.getElementById("pdf_content");
if(parentObj)
{
const parentWidth = parentObj.offsetWidth;
console.log("부모 : " + parentWidth);
const pageLayer = parentObj.querySelector(`.pageLayer[data-pageidx="${currentPage}"]`);
if (pageLayer) {
const canvas = pageLayer.querySelector('canvas');
if (canvas)
{
console.log("자식 : " + (canvas.width / calcScaleValue) );
return parentWidth / ((canvas.width / calcScaleValue) + 15) ;
}
}
}
return 0;
}
function getHeightFitScale()
{
const parentObj = document.getElementById("pdf_content");
if(parentObj)
{
const parentHeight = parentObj.offsetHeight;
const pageLayer = parentObj.querySelector(`.pageLayer[data-pageidx="${currentPage}"]`);
if (pageLayer) {
const canvas = pageLayer.querySelector('canvas');
if (canvas)
{
return parentHeight / ((canvas.height / calcScaleValue) + 30);
}
}
}
return 0;
}
function getWindowHeightFitScale()
{
const parentObj = document.getElementById("pdf_content");
if(parentObj)
{
const pageLayer = parentObj.querySelector(`.pageLayer[data-pageidx="${currentPage}"]`);
if (pageLayer) {
const canvas = pageLayer.querySelector('canvas');
if (canvas)
{
return window.screen.height / ((canvas.height / calcScaleValue) + 15);
}
}
}
return 0;
}
function zoomIn(stdValue:number)
{
const maxZoom:number = 4.0;
let calsZoom:number = stdValue + 0.25;
if (calsZoom > maxZoom)
{
calsZoom = maxZoom;
}
return calsZoom;
}
function zoomOut(stdValue:number)
{
const minZoom:number = 0.5;
let calsZoom:number = stdValue - 0.25;
if (calsZoom < minZoom)
{
calsZoom = minZoom;
}
return calsZoom;
}
function drawLoadingPage(pageLayer:Element, isLoadingIcon:boolean)
{
pageLayer.setAttribute("data-pageLoad", "false");
const cavasLayer = pageLayer.querySelector('.canvasWrapper') as HTMLDivElement;
if (cavasLayer)
{
cavasLayer.className = "loading_canvasWrapper";
if (isLoadingIcon)
{
const loadingLayer = document.createElement("div");
const loadingImage = document.createElement("img");
loadingImage.setAttribute("src", "pdf-loading.svg");
loadingLayer.appendChild(loadingImage);
loadingLayer.className = "loading";
cavasLayer.appendChild(loadingLayer);
}
}
}
function drawLayerShowNHide(isShow:boolean)
{
if (containerRef.current)
{
const drawLayers = containerRef.current.querySelectorAll(".drawLayer");
drawLayers.forEach((layer) => {
if (!isShow)
{
(layer as HTMLElement).style.display = 'none';
}
else
{
(layer as HTMLElement).style.display = 'block';
}
});
}
}
const ZoomCtrl = (calcZoom:number) => {
if(calcZoom === calcScaleValue)
{
return;
}
if (observer)
{
observer.disconnect();
}
console.log("cal : " + calcZoom);
console.log("calsc : " + calcScaleValue);
console.log("cur : " + currentPage);
const targetPageIdx = currentPage;
const startIdx = Math.max(1, targetPageIdx - 3);
const endIdx = targetPageIdx + 3;
if (containerRef.current)
{
drawLayerShowNHide(false);
setCalcScaleValue(calcZoom);
const pdfViewer = containerRef.current;
for (let i = 1; i <= totalPage; i++) {
const pageLayer = pdfViewer.querySelector(`.pageLayer[data-pageidx="${i}"]`);
if (pageLayer) {
const canvas = pageLayer.querySelector('canvas');
if (canvas) {
if ( i >= startIdx && i <= endIdx)
{
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
if (!tempCtx) {
console.error("Failed to get 2d context from tempCanvas.");
return;
}
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
tempCtx.drawImage(canvas, 0, 0);
canvas.width = (canvas.width / calcScaleValue) * calcZoom;
canvas.height = (canvas.height / calcScaleValue) * calcZoom;
const ctx = canvas.getContext('2d')!;
ctx.drawImage(tempCanvas, 0, 0, tempCanvas.width, tempCanvas.height, 0, 0, canvas.width, canvas.height);
}
else
{
drawLoadingPage(pageLayer, true);
canvas.width = (canvas.width / calcScaleValue) * calcZoom;
canvas.height = (canvas.height / calcScaleValue) * calcZoom;
}
console.log("can : " + canvas.width);
}
}
}
}
if (timerRef.current) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
if (containerRef.current) {
setCalcScaleValue(calcZoom);
const pdfViewer = containerRef.current;
for (let i = startIdx; i <= endIdx; i++) {
const pageLayer = pdfViewer.querySelector(`.pageLayer[data-pageidx="${i}"]`);
if (pageLayer) {
drawLoadingPage(pageLayer, false);
}
}
}
const container = containerRef.current;
container!.style.setProperty('--scale-factor', `${calcZoom}`); // 원하는 값으로 설정
setScaleValue(calcZoom);
setThreshold(calcThreshold(calcZoom));
console.log("타임 : " + calcZoom);
}, 500);
}
const observerOptions = {
root: null,
rootMargin: '0px 0px 30px 0px',
threshold: threshold,
}
function createObserver(pdfDocument:PDFDocumentProxy)
{
if (observer)
{
observer.disconnect();
}
const newObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(async entry => {
// 관찰 대상이 viewport 안에 들어온 경우 image 로드
if (entry.isIntersecting) {
const pageLayer = entry.target;
const pageIdx: number = Number(pageLayer.getAttribute("data-pageidx"));
setCurrentPage(pageIdx);
await drawPdfPage(pdfDocument, pageLayer);
}
})
}, observerOptions);
setObserver(newObserver);
const pdfPages = document.querySelectorAll('.pageLayer');
pdfPages.forEach((pdfPage) => {
newObserver.observe(pdfPage);
})
return () => {
newObserver.disconnect();
};
}
async function drawPdfPage(pdfDocument:PDFDocumentProxy, pageLayer: Element) {
console.log(scaleValue);
if (pdfDocument) {
const pageIdx: number = Number(pageLayer.getAttribute("data-pageidx"));
const cavasLayer = pageLayer.querySelector('.loading_canvasWrapper') as HTMLDivElement;
const isLoad = pageLayer.getAttribute("data-pageLoad") === "true" ? true : false;
if (cavasLayer && !isLoad) {
pageLayer.setAttribute("data-pageLoad", `true`);
const page = await pdfDocument.getPage(pageIdx);
const textLayer = pageLayer.querySelector('.textLayer') as HTMLDivElement;
const canvas = pageLayer.querySelector('canvas');
const loadingLayer = pageLayer.querySelector('.loading');
const context = canvas!.getContext("2d");
if (!context) return;
const viewport = page.getViewport({
scale: scaleValue
, rotation: 0
})
await renderPage(page, context, viewport, textLayer).finally(() => {
cavasLayer.className = "canvasWrapper";
if (loadingLayer && cavasLayer.contains(loadingLayer)) {
cavasLayer.removeChild(loadingLayer);
}
});
}
}
}
async function runZoom() {
if (containerRef.current && pdfDocument) {
const pdfViewer = containerRef.current;
const targetPageIdx = currentPage;
const startIdx = Math.max(1, targetPageIdx - 3);
const endIdx = targetPageIdx + 3;
for (let i = startIdx; i <= endIdx; i++) {
const pageLayer = pdfViewer.querySelector(`.pageLayer[data-pageidx="${i}"]`);
if (pageLayer)
{
await drawPdfPage(pdfDocument, pageLayer);
}
}
createObserver(pdfDocument);
}
}
async function loadPdfFromUrl() {
const response = await fetch(fileUrl);
if (!response.ok) {
throw new Error(`Failed to fetch PDF from ${fileUrl}. Status: ${response.statusText}`);
}
const pdfBytes = new Uint8Array(await response.arrayBuffer());
return pdfBytes;
}
async function getPdfBlob() {
const warterMark = document.querySelector("#draw-1");
if (warterMark != null && pdfDocument) {
const waterMarkCanvas = await html2canvas(warterMark as HTMLElement, {
backgroundColor: null,
});
//패스워드가 있으면 이미지를 만들고 없으면 기존거에 워터마크만 추가한다.
const isSecurity = pdfPassword.length > 0;
let pdfDoc:PDFDocument = isSecurity ? await PDFDocument.create() : await PDFDocument.load(await pdfDocument.getData());
const waterMarkData = waterMarkCanvas.toDataURL("image/png");
const waterMarkImage = pdfDoc.embedPng(waterMarkData);
for (let i=1; i<=totalPage; i++)
{
setProcPercent(calculateProgress(i, totalPage, 100));
let pdfPage:PDFPage|null = null;
if (isSecurity)
{
const page = await pdfDocument.getPage(i);
const viewport = page.getViewport({ scale: 2.0 });
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
if (context && viewport) {
const renderContext = {
canvasContext: context,
viewport: viewport
};
await page.render(renderContext).promise;
pdfPage = pdfDoc.addPage([viewport.width, viewport.height]);
const pdfPageIamge = pdfDoc.embedJpg(canvas.toDataURL("image/jpeg"));
pdfPage.drawImage(await pdfPageIamge, {
x: 0,
y: 0,
width: viewport.width,
height: viewport.height
});
}
}
else
{
pdfPage = pdfDoc.getPages()[i-1];
}
//워터마크 삽입
if (pdfPage && waterMarkImage)
{
pdfPage.drawImage(await waterMarkImage, {
x: 0,
y: 0,
width: pdfPage.getWidth(),
height: pdfPage.getHeight(),
});
}
}
const modifiedPdfBytes = await pdfDoc.save();
setProcPercent(100);
const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
return blob;
}
return null;
}
const downloadPdfDocument = async () => {
const blob = await getPdfBlob();
if (blob)
{
const pdfUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = pdfUrl;
link.download = 'overlayed.pdf';
link.click();
URL.revokeObjectURL(pdfUrl);
hideModal();
}
};
const printPdfDocument = async () => {
const blob = await getPdfBlob();
if (blob)
{
const pdfUrl = URL.createObjectURL(blob);
const printIframe = document.getElementById('printFrame') as HTMLIFrameElement;
printIframe.src = pdfUrl;
const printWindow = printIframe.contentWindow;
if (printWindow) {
printIframe.onload = function () {
setTimeout(function () {
printWindow.print();
hideModal();
URL.revokeObjectURL(pdfUrl);
}, 500);
};
}
}
};
function calculateProgress(currentPage:number, totalPage:number, totalPercent:number) {
const progress = (currentPage / totalPage) * totalPercent;
console.log(progress);
return Math.min(Math.round(progress), totalPercent); // 진행률이 100%를 초과하지 않도록 합니다.
}
function showModal(revModalTitle:string, isProgress:boolean)
{
if (isProgress)
{
setProgressVisible(true);
}
setProcPercent(0);
setModalTitle(revModalTitle);
setModalVisible(true);
}
function hideModal()
{
setPdfErrorMsg("");
setModalVisible(false);
setModalTitle("");
setProcPercent(0);
setProgressVisible(false);
setPasswordVisible(false);
}
function PrevPage()
{
const movePageNum = currentPage-1;
setCurrentPage(movePage(movePageNum < 1 ? 1: movePageNum ));
}
function NextPage()
{
const movePageNum = currentPage+1;
setCurrentPage(movePage(movePageNum > totalPage ? totalPage : movePageNum ));
}
const handleDoubleClick = (event:React.MouseEvent<HTMLDivElement, MouseEvent>) => {
event.preventDefault();
leftClickCountRef.current += 1;
if (leftClickCountRef.current === 1) {
timerClickRef.current = setTimeout(() => {
leftClickCountRef.current = 0; // 클릭 횟수 초기화
}, 250);
} else if (leftClickCountRef.current === 2) {
if (timerClickRef.current) {
clearTimeout(timerClickRef.current);
}
ZoomCtrl( zoomIn( calcScaleValue || scaleValue ) );
leftClickCountRef.current = 0; // 클릭 횟수 초기화
}
};
const handleRightClick = (event:React.MouseEvent<HTMLDivElement, MouseEvent>) => {
event.preventDefault();
rightClickCountRef.current += 1;
if (rightClickCountRef.current === 1) {
timerClickRef.current = setTimeout(() => {
rightClickCountRef.current = 0; // 클릭 횟수 초기화
}, 250);
} else if (rightClickCountRef.current === 2) {
if (timerClickRef.current) {
clearTimeout(timerClickRef.current);
}
ZoomCtrl( zoomOut( calcScaleValue || scaleValue ) );
rightClickCountRef.current = 0; // 클릭 횟수 초기화
}
};
const handlePasswordSubmit = () => {
loadPdf(pdfPassword).then(r => { });
};
useEffect(() => {
if (waterMarkText && waterMarkCanvasRef.current) {
const canvas = waterMarkCanvasRef.current;
const ctx = canvas.getContext('2d');
const fontSize = 70 * scaleValue;
const padding = fontSize;
if (ctx) {
ctx.font = `${fontSize}px Arial`;
const textWidth = ctx.measureText(waterMarkText).width;
const textHeight = fontSize; // 이 값은 폰트 크기에 따라 조절하시길 바랍니다.
canvas.width = textWidth + padding;
canvas.height = textHeight + padding * 2;
ctx.font = `${fontSize}px Arial`; // 캔버스 크기가 변경되면 폰트 설정도 초기화되므로 다시 설정해야 합니다.
ctx.fillStyle = 'rgba(0, 0, 0, 0.10)';
ctx.fillText(waterMarkText, 0, textHeight - 5); // y 좌표를 조절하여 텍스트가 캔버스 내에 잘 들어가도록 합니다.
//setWaterMarkImgURl(canvas.toDataURL('image/png'));
const waterMarkImgURl = canvas.toDataURL('image/png');
if(waterMarkImgURl)
{
const style = document.createElement('style');
style.innerHTML = `
.pdfViewer .waterMarkLayer::before {
content: "";
position: absolute;
top: 0px;
left: 0px;
width: 200%;
height: 200%;
background-image: url(${waterMarkImgURl});
background-repeat: repeat;
transform: rotate(-45deg);
background-color: transparent;
}`;
document.head.appendChild(style);
}
}
}
}, [waterMarkText, scaleValue]);
const loadPdf = async (password: string | null | undefined) => {
console.log("PDFViewer component rendered");
const container = containerRef.current;
if (!container) return;
// Clear the container's contents whenever fileUrl changes
container.innerHTML = "";
const loadOption: any = {
url: fileUrl,
cMapUrl: `${currentUrl}/cmaps/`,
cMapPacked: true,
enableXfa: true,
};
if (password) {
loadOption.password = password;
}
let isError = false;
try {
const pdfDocumentProxy: PDFDocumentProxy = await getDocument(loadOption).promise;
setPdfDocument(pdfDocumentProxy);
setTotalPage(pdfDocumentProxy.numPages);
container.style.setProperty('--scale-factor', `${scaleValue}`); // 원하는 값으로 설정
for (let pageNum = 1; pageNum <= pdfDocumentProxy.numPages; pageNum++) {
await loadPage(pdfDocumentProxy, pageNum, scaleValue, 0, container, isRenderText, waterMarkText);
}
createObserver(pdfDocumentProxy);
} catch (error:any) {
isError = true
if (error.name === 'PasswordException' && !password) {
showModal("보안 PDF", false);
setPasswordVisible(true);
}
else if (error.name === 'PasswordException' && error.message === 'Incorrect Password' && password) {
setPdfErrorMsg("PDF 암호가 잘못되었습니다.");
showModal("보안 PDF", false);
setPasswordVisible(true);
}
else {
console.error(error);
}
}
finally {
if (!isError && password)
hideModal();
}
};
useEffect(() => {
setTotalPage(0);
setCurrentPage(1);
setPdfErrorMsg("");
setPdfPassword("");
loadPdf(null);
}, [fileUrl]);
useEffect(() => {
runZoom().then().finally( () =>{
drawLayerShowNHide(true);
});
}, [ scaleValue ]);
useEffect(() => {
const handleKeyDown = (event:KeyboardEvent) => {
switch (event.key) {
case 'PageUp':
PrevPage();
console.log("up");
event.preventDefault(); // 기본 동작 방지
break;
case 'PageDown':
NextPage();
console.log("down");
event.preventDefault(); // 기본 동작 방지
break;
case '+':
ZoomCtrl( zoomIn( calcScaleValue || scaleValue ) );
break;
case '-':
ZoomCtrl( zoomOut( calcScaleValue || scaleValue ) );
break;
default:
break;
}
};
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [currentPage, scaleValue, calcScaleValue, totalPage]); // currentPage가 변경될 때마다 이벤트 리스너 갱신
return (
<div
style={{
width: '100%',
}}
>
<nav style={{
margin:'0px 0px 4px 0px',
padding:'9px 8px 9px 0px',
height: '50px',
display:'flex',
justifyContent: "space-between",
alignItems:'center',
borderRadius: "8px", // 라운드 처리 추가
border: '1px solid #dadada',
background: "white",
}}>
<div style={{ width:'34%', marginLeft: '8px' }}>
<Space.Compact style={{
width:'100%',
justifyContent:'left',
alignItems:'center',
display:'flex'
}} block>
<Tooltip title="이전페이지" arrow={false}>
<Button type="primary" icon={<CaretLeftOutlined />} onClick={() => {
PrevPage();
}}/>
</Tooltip>
<Input
type="number"
style={{
marginLeft : "10px",
marginRight : "5px",
width : "65px"
}} size="small" value={currentPage}
onChange={e => setCurrentPage(Number(e.target.value))}
onKeyDown={(e) => {
if (e.key === 'Enter') {
setCurrentPage(movePage(currentPage));
}
}}
/>
/
<Input style={{
marginLeft : "5px",
marginRight : "10px",
width : "40px"
}} size="small" value={totalPage} readOnly/>
<Tooltip title="다음페이지" arrow={false}>
<Button type="primary" icon={<CaretRightOutlined />} onClick={() => {
NextPage();
}}/>
</Tooltip>
</Space.Compact>
</div>
<div style={{ width:'33%', textAlign: 'center', flexGrow: 1 }}>
<Space.Compact style={{
width:'100%',
justifyContent:'center',
}} block>
<Tooltip title="확대" arrow={false}>
<Button type="primary" icon={<ZoomInOutlined />} onClick={() => {
ZoomCtrl( zoomIn( calcScaleValue || scaleValue ) );
//zoonIn();
}}/>
</Tooltip>
<Tooltip title="비율로보기">
<Dropdown
placement="bottomCenter"
menu={raioMenuProps}
>
<Button type="primary" icon={<PercentageOutlined />}/>
</Dropdown >
</Tooltip>
<Tooltip title="축소" arrow={false}>
<Button type="primary" icon={<ZoomOutOutlined />} onClick={() => {
//zoonOut();
ZoomCtrl( zoomOut( calcScaleValue || scaleValue ) );
}}/>
</Tooltip>
</Space.Compact>
</div>
<div style={{ width:'33%', marginRight: '8px',textAlign: 'right' }}>
<Space.Compact style={{
width:'100%',
justifyContent:'flex-end',
}} block>
<Tooltip title="전체화면" arrow={false}>
<Button type="primary" icon={<FullscreenOutlined />} onClick={() => {
enterFullScreen("pdf_content");
}}/>
</Tooltip>
<Tooltip title="다운로드" arrow={false}>
<Button type="primary" icon={<DownloadOutlined />} onClick={() => {
showModal("다운로드 준비중..", true);
downloadPdfDocument().finally(() => {
});
}
}/>
</Tooltip>
<Tooltip title="인쇄" arrow={false}>
<Button type="primary" icon={<PrinterOutlined />} onClick={() => {
showModal("인쇄 준비중..", true);
printPdfDocument().finally(() => {
});
}}/>
</Tooltip>
</Space.Compact>
</div>
</nav>
<div
id="pdf_content"
style={{
border: '1px solid rgba(0, 0, 0, 0.1)',
display: 'flex',
height: 'calc( 100vh - 120px )',
}}
>
<div
style={{
width:'100%',
overflow:"auto"
}}
>
<div onClick={handleDoubleClick}
onContextMenu={handleRightClick}
className="pdfViewer" ref={containerRef} />
</div>
<canvas ref={waterMarkCanvasRef} style={{ display: 'none' }} />
<iframe id="printFrame"/>
<Modal
centered
open={modalVisible}
title={modalTitle}
footer={null}
closable={false}
>
{progressVisible && <Progress percent={procPercent} status="active" />}
{
passwordVisible &&
<>
<div style={{
fontWeight:"bold",
marginLeft:"10px",
marginBottom : "10px",
color:"red"}}>{pdfErrorMsg}</div>
<Input
placeholder="문서가 비밀번호로 보호되고 있습니다. 비밀번호를 입력하세요."
type="password"
style={{
margin : "0px",
width : "100%",
height : "35px"
}} size="small"
value={pdfPassword}
onChange={(e) => {setPdfPassword(e.target.value)}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handlePasswordSubmit();
}
}}
/>
</>
}
</Modal>
</div>
</div>
);
};