서버 없이 브라우저에서 인쇄용 PDF 만들기: pocaprint를 만들며 정한 것들
pocaprint는 사용자가 올린 이미지로 포토카드 인쇄 시트를 PDF로 만들어 주는 도구입니다. 만들 때 가장 먼저 정한 원칙은 이미지를 서버로 보내지 않는다였습니다. 개인 사진을 받는 순간 저장, 삭제, 유출 책임이 생기고 서버 비용도 듭니다. 브라우저 안에서 모든 것을 처리하면 이런 문제가 사라집니다. 이 글은 그 구조를 정리합니다.
구성
- 빌드 도구 없는 정적 HTML·CSS·JS
- PDF 생성: jsPDF 2.5.2 (저장소에 직접 포함해서 CDN 없이도 동작)
- 이미지 읽기:
<input type="file">과 드래그 앤 드롭, 브라우저의 이미지 디코딩 - 재렌더:
<canvas>
단위는 mm로 통일
인쇄물은 픽셀이 아니라 길이로 생각해야 합니다. 그래서 모든 배치 계산을 mm로 합니다. 카드는 55×85mm, 블리드 3mm, 칸은 61×91mm, 종이 가장자리 최소 여백은 10mm입니다. jsPDF도 문서를 mm 단위로 만들 수 있어서, 계산한 숫자를 그대로 좌표로 씁니다. 용지별로 세로·가로 두 방향의 칸 수를 계산해 더 많이 들어가는 쪽을 고르고, 카드 묶음을 가운데에 놓습니다. 계산 과정은 A4와 Letter 비교 글에 자세히 있습니다.
미리보기와 PDF가 같은 코드로 그린다
미리보기는 화면 캔버스에, 최종 결과는 PDF에 그립니다. 두 곳의 그리기 코드가 따로 있으면 언젠가 결과가 어긋납니다. 그래서 페이지 하나를 그리는 함수는 "그리는 도구(painter)"만 받도록 만들었습니다. 같은 함수에 캔버스용 painter를 넘기면 미리보기가, PDF용 painter를 넘기면 PDF가 됩니다. painter는 배경, 이미지, 사각형, 둥근 사각형, 선, 글자 같은 몇 가지 동작만 구현하면 됩니다.
이미지는 300dpi로 다시 그려서 넣는다
원본 이미지를 PDF에 그대로 넣으면 두 가지 문제가 생깁니다. 4000px짜리 사진 9장이면 PDF가 지나치게 커지고, 크롭과 확대를 PDF 좌표로 표현하기 번거롭습니다. 그래서 칸마다 캔버스를 하나 만들어 61×91mm를 300dpi로 환산한 약 720×1075px 크기로 잘라 그린 다음, 그 결과를 JPEG로 PDF에 넣습니다. 크기는 일정해지고, 화면에서 본 크롭이 그대로 반영됩니다.
크롭: cover + 위치 + 확대
칸 비율과 원본 비율이 다르면 빈 공간이 생기지 않도록 더 긴 쪽을 잘라 냅니다(CSS의 object-fit: cover와 같은 개념). 여기에 0~1 범위의 가로·세로 위치 값과 1 이상의 확대 값을 더해, 사용자가 드래그와 슬라이더로 남길 부분을 고르게 했습니다. 확대할수록 원본에서 쓰는 영역이 줄어드는 점은 해상도 글에서 설명했습니다.
인쇄를 위한 장치
- 재단선은 카드 묶음 바깥 여백에만 그려서 카드에 남지 않게 했습니다.
- 페이지 아래 여백에 50mm 눈금선을 넣어, 인쇄 후 배율을 자로 확인할 수 있게 했습니다.
- 페이지 위에 "PRINT AT 100% / ACTUAL SIZE" 안내를 넣었습니다. 축소 인쇄가 가장 흔한 실패 원인이기 때문입니다.
검증
사람 눈으로만 확인하면 1mm 오차를 놓칩니다. 그래서 테스트 스크립트가 브라우저를 자동으로 띄워 PDF를 내려받고, 별도 스크립트가 PDF의 페이지 크기와 이미지 위치를 읽어 계산값과 비교하게 했습니다. 정적 도구라도 이런 자동 검증 하나가 있으면 고칠 때 마음이 편합니다.
남은 과제
브라우저 안 처리의 한계는 메모리입니다. 아주 큰 이미지를 수십 장 넣으면 휴대폰에서는 느려질 수 있습니다. 이미지를 읽을 때 미리 줄여 두는 방식으로 개선할 여지가 있습니다.