// ===== 환경 설정 ===== // PDF.js ES Module 파일 경로 (unpkg에서 받으신 파일을 서버에 저장한 경로로 맞추세요) import * as pdfjsLib from 'https://cslabs.co.kr/js/pdf.min.mjs'; // 워커 경로 (같은 폴더에 저장했으면 상대경로 유지) pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cslabs.co.kr/js/pdf.worker.min.mjs'; // 하드코딩할 PDF 경로 const urlDefault = 'https://cslabs.co.kr//pdfs/cslabs.pdf'; // ===== 요소 참조 ===== const canvas = document.getElementById('pdf-canvas'); const ctx = canvas.getContext('2d'); const pageCountEl = document.getElementById('page-count'); const pageNumEl = document.getElementById('page-num'); const zoomLevelEl = document.getElementById('zoom-level'); const btn = { prev: document.getElementById('prev-page'), next: document.getElementById('next-page'), zoomIn: document.getElementById('zoom-in'), zoomOut: document.getElementById('zoom-out'), rotL: document.getElementById('rotate-left'), rotR: document.getElementById('rotate-right'), fitW: document.getElementById('fit-width'), fitP: document.getElementById('fit-page'), dl: document.getElementById('download-btn'), }; // ===== 상태값 ===== let pdfDoc = null; let pageNum = 1; let scale = 1.0; let rotation = 0; // ===== 유틸 ===== function enableControls(on = true) { Object.values(btn).forEach(b => b.disabled = !on); } function renderPage(num) { return pdfDoc.getPage(num).then(page => { const viewport = page.getViewport({ scale, rotation }); canvas.width = viewport.width; canvas.height = viewport.height; const renderCtx = { canvasContext: ctx, viewport }; return page.render(renderCtx).promise; }).then(() => { pageCountEl.textContent = pdfDoc.numPages; pageNumEl.value = num; zoomLevelEl.textContent = Math.round(scale * 100) + '%'; }); } function queueRenderPage(num) { if (!pdfDoc) return; if (num < 1) num = 1; if (num > pdfDoc.numPages) num = pdfDoc.numPages; pageNum = num; renderPage(pageNum); } async function loadPdf(url) { try { enableControls(false); const pdf = await pdfjsLib.getDocument(url).promise; pdfDoc = pdf; pageNum = 1; rotation = 0; scale = 1.0; await renderPage(pageNum); enableControls(true); } catch (err) { console.error(err); alert('PDF 로드 실패: ' + err.message); } } // ===== 이벤트 ===== btn.prev.addEventListener('click', () => { if (pdfDoc && pageNum > 1) queueRenderPage(pageNum - 1); }); btn.next.addEventListener('click', () => { if (pdfDoc && pageNum < pdfDoc.numPages) queueRenderPage(pageNum + 1); }); pageNumEl.addEventListener('change', e => { if (pdfDoc) queueRenderPage(parseInt(e.target.value, 10) || 1); }); btn.zoomIn.addEventListener('click', () => { if (!pdfDoc) return; scale = Math.min(scale + 0.25, 4); queueRenderPage(pageNum); }); btn.zoomOut.addEventListener('click', () => { if (!pdfDoc) return; scale = Math.max(scale - 0.25, 0.25); queueRenderPage(pageNum); }); btn.rotL.addEventListener('click', () => { if (!pdfDoc) return; rotation = (rotation - 90 + 360) % 360; queueRenderPage(pageNum); }); btn.rotR.addEventListener('click', () => { if (!pdfDoc) return; rotation = (rotation + 90) % 360; queueRenderPage(pageNum); }); btn.fitW.addEventListener('click', async () => { if (!pdfDoc) return; const containerWidth = document.getElementById('viewer-container').clientWidth - 32; const page = await pdfDoc.getPage(pageNum); const vp = page.getViewport({ scale: 1, rotation }); scale = containerWidth / vp.width; queueRenderPage(pageNum); }); btn.fitP.addEventListener('click', async () => { if (!pdfDoc) return; const container = document.getElementById('viewer-container'); const cw = container.clientWidth - 32; const ch = container.clientHeight - 32; const page = await pdfDoc.getPage(pageNum); const vp = page.getViewport({ scale: 1, rotation }); const scaleX = cw / vp.width; const scaleY = ch / vp.height; scale = Math.min(scaleX, scaleY); queueRenderPage(pageNum); }); btn.dl.addEventListener('click', () => { if (!pdfDoc) { alert('PDF가 아직 로드되지 않았습니다.'); return; } const a = document.createElement('a'); a.href = urlDefault; a.download = ''; document.body.appendChild(a); a.click(); a.remove(); }); // ===== 초기 로드 ===== loadPdf(urlDefault);