
"순수하게 웹 기술(HTML, CSS, JavaScript)만으로 PDF 뷰어를 만들 수 있을까?"라는 의문에서 시작해, 서버 연동까지 확장된 개발 여정을 정리해 보았습니다. 결론부터 말씀드리면, 순수 웹 기술만으로도 가능하지만, Spring 같은 백엔드 프레임워크와 결합하면 훨씬 강력하고 안정적인 뷰어를 만들 수 있습니다.
처음에는 서버 없이 오직 클라이언트(브라우저)에서 모든 것을 처리하는 방식을 고려할 수 있습니다. 이 방식은 PDF 파일의 복잡한 구조를 해석하고, 이를 웹 페이지에 렌더링하는 자바스크립트 기반의 렌더링 라이브러리와 HTML Canvas API를 활용합니다.
<canvas> 요소를 '도화지' 삼아 추출한 정보를 그려냅니다.하지만 이 방법은 파일 관리, 보안, 대용량 파일 처리 등에서 한계가 명확합니다.
안정적이고 확장 가능한 PDF 뷰어 서비스를 구축하려면, 스프링(Spring) 같은 백엔드 프레임워크와 결합하는 것이 효과적입니다. 백엔드는 파일을 안전하게 관리하고, 클라이언트는 그 파일을 받아와 렌더링에만 집중하는 역할 분담이 이루어집니다.
스프링 서버는 PDF 파일을 안전하게 저장하고, 요청이 들어올 때 인증된 사용자에게만 파일을 제공하는 REST API를 구현합니다.
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
public class PdfFileController {
// PDF 파일이 저장된 서버 경로
private static final String FILE_STORAGE_PATH = "/path/to/your/pdf/files/";
// PDF 파일을 제공하는 API 엔드포인트
@GetMapping("/api/pdf/{filename}")
public ResponseEntity<byte[]> getPdfFile(@PathVariable String filename) throws IOException {
Path filePath = Paths.get(FILE_STORAGE_PATH + filename);
// 파일이 존재하지 않으면 404 Not Found 반환
if (!Files.exists(filePath)) {
return ResponseEntity.notFound().build();
}
// 파일의 모든 내용을 읽어옵니다.
byte[] pdfContent = Files.readAllBytes(filePath);
HttpHeaders headers = new HttpHeaders();
// 응답 타입을 PDF로 설정
headers.setContentType(MediaType.APPLICATION_PDF);
// 다운로드 시 파일명 설정
headers.setContentDispositionFormData(filename, filename);
// 파일을 응답 본문에 담아 반환
return ResponseEntity.ok()
.headers(headers)
.body(pdfContent);
}
}
클라이언트는 스프링 서버가 제공하는 API를 호출하여 PDF 파일 데이터를 가져온 후, 이 데이터를 캔버스에 렌더링합니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spring PDF 뷰어</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; }
#pdf-viewer-container { display: flex; justify-content: center; margin-bottom: 20px; }
#pdf-viewer-canvas { border: 1px solid #ccc; max-width: 100%; height: auto; }
.controls { text-align: center; }
.controls button { padding: 8px 16px; margin: 0 5px; cursor: pointer; }
</style>
</head>
<body>
<div id="pdf-viewer-container">
<canvas id="pdf-viewer-canvas"></canvas>
</div>
<div class="controls">
<button id="prev-page">이전</button>
<button id="next-page">다음</button>
<span>페이지: <span id="page-num"></span> / <span id="page-count"></span></span>
</div>
<script type="module">
const viewerCanvas = document.getElementById('pdf-viewer-canvas');
const context = viewerCanvas.getContext('2d');
// 스프링 서버의 API URL
const fileUrl = 'http://localhost:8080/api/pdf/my_document.pdf';
let pdfDoc = null;
let pageNum = 1;
// 스프링 서버로부터 PDF 파일을 가져와 렌더링
fetch(fileUrl)
.then(response => response.arrayBuffer()) // 파일을 ArrayBuffer 형태로 가져옴
.then(arrayBuffer => {
return getDocument({ data: arrayBuffer }).promise;
})
.then(pdf => {
pdfDoc = pdf;
document.getElementById('page-count').textContent = pdf.numPages;
renderPage(pageNum);
})
.catch(error => {
console.error('PDF 로드 중 오류 발생:', error);
});
function renderPage(num) {
pdfDoc.getPage(num).then(page => {
const viewport = page.getViewport({ scale: 1.5 });
viewerCanvas.height = viewport.height;
viewerCanvas.width = viewport.width;
page.render({
canvasContext: context,
viewport: viewport
});
document.getElementById('page-num').textContent = num;
});
}
// 페이지 이동 버튼 이벤트 리스너
document.getElementById('prev-page').addEventListener('click', () => {
if (pageNum <= 1) return;
pageNum--;
renderPage(pageNum);
});
document.getElementById('next-page').addEventListener('click', () => {
if (pageNum >= pdfDoc.numPages) return;
pageNum++;
renderPage(pageNum);
});
</script>
</body>
</html>
결론적으로, 순수 웹 기술만으로도 PDF 뷰어의 기본 기능을 구현하는 것은 충분히 가능합니다. 하지만 상용 수준의 안정성, 보안, 확장성을 고려한다면 스프링 백엔드와 연동하는 하이브리드 접근법이 훨씬 더 현명한 선택입니다.