언어·프레임워크/React.js
[React.js] jsPDF를 이용한 웹 화면 PDF 내보내기 중 이슈: 페이지 오버플로우 이미지 잘림 문제
DandyNow
2024. 7. 24. 11:06
728x90
반응형
1. 화면에 렌더링 된 테이블 페이지 오버플로우 문제
jsPDF를 이용해 웹 화면을 PDF로 내보내기 기능을 구현했다. 에러 없이 작동하였으나 [그림 1]과 같이 페이지 오버플로우로 그림이 잘린 경우 앞 페이지 하단, 뒷 페이지 상단 여백이 전혀 없는 pdf 파일이 생성되었다.
2. canvas 이용하여 해결
페이지 오버플로우가 발생할 경우 이미지를 페이지 크기로 잘라 새로운 캔버스에 그리고 새로운 캔버스를 PDF에 추가하는 방식으로 해결하였다. [그림 2]는 최종 결과물이다.
작성한 전체 코드는 아래와 같다.
import jsPDF from 'jspdf';
import font from './font/NanumGothic-normal';
import { format } from 'date-fns';
const PdfDownloader = async (images, period, orientation = 'p') => {
const [openDate, closeDate] = period;
const startDate = format(openDate, 'yyyy.MM.dd').toString();
const endDate = format(closeDate, 'yyyy.MM.dd').toString();
const periodStr = `${startDate} ~ ${endDate}`;
try {
const marginLeft = 10; // 왼쪽 마진 값 (mm)
const marginRight = 10; // 오른쪽 마진 값 (mm)
const marginTop = 15; // 상단 마진 값 (mm)
const marginBottom = 10; // 하단 마진 값 (mm)
const imageMargin = 5; // 이미지 사이의 여백 (mm)
// PDF 문서 준비
const doc = new jsPDF(orientation, 'mm', 'a4', true);
// PDF 페이지의 가로 세로 사이즈
const pageWidth =
doc.internal.pageSize.getWidth() - (marginLeft + marginRight);
const pageHeight =
doc.internal.pageSize.getHeight() -
(marginTop + marginBottom + imageMargin);
// 한글 폰트 추가
doc.addFileToVFS('NanumGothic.ttf', font);
doc.addFont('NanumGothic.ttf', 'NanumGothic', 'normal');
doc.setFont('NanumGothic');
// "보고서" 문구 추가 (중앙 정렬)
doc.setFontSize(24);
const reportTextWidth =
(doc.getStringUnitWidth('보 고 서') * doc.internal.getFontSize()) /
doc.internal.scaleFactor;
const reportTextX = (pageWidth - reportTextWidth) / 2 + marginLeft;
doc.text('보 고 서', reportTextX, marginTop + 10);
// 기간 정보 추가 (왼쪽 정렬)
doc.setFontSize(10);
const periodText = `◯ 기간: ${periodStr}`;
doc.text(periodText, marginLeft, marginTop + 25);
await images.reduce(async (promise, pdfObj, index, array) => {
await promise; // Wait for the previous iteration to complete
const { canvas, image } = pdfObj;
console.log('canvas : ', canvas);
console.log('image : ', image);
// 이미지의 길이와 PDF 페이지의 가로 길이를 기준으로 비율을 구함
const widthRatio = pageWidth / canvas.width;
// 비율에 따른 이미지 높이
const customHeight = canvas.height * widthRatio;
// 첫 페이지에만 marginTop + 15 적용, 나머지는 marginTop만 적용
const topMargin = index === 0 ? marginTop + 30 : marginTop;
// 캔버스를 사용하여 이미지를 페이지 크기로 자르기
let heightLeft = customHeight; // 남은 이미지 높이
let position = 0; // 이미지 자를 위치
while (heightLeft > 0) {
const sliceHeight = Math.min(pageHeight, heightLeft);
// 새로운 캔버스 생성
const newCanvas = document.createElement('canvas');
newCanvas.width = canvas.width;
newCanvas.height = sliceHeight / widthRatio;
// 잘라낸 이미지 부분을 새로운 캔버스에 그림
const newCtx = newCanvas.getContext('2d');
newCtx.drawImage(
canvas,
0,
position / widthRatio,
canvas.width,
sliceHeight / widthRatio,
0,
0,
canvas.width,
sliceHeight / widthRatio,
);
// 새로운 캔버스의 이미지를 PDF에 추가
const newImage = newCanvas.toDataURL('image/jpeg');
doc.addImage(
newImage,
'JPEG',
marginLeft,
topMargin,
pageWidth,
sliceHeight,
);
// 남은 이미지 높이와 자를 위치 업데이트
heightLeft -= sliceHeight;
position += sliceHeight;
// 페이지가 남아있는 경우 새 페이지 추가
if (heightLeft > 0) {
doc.addPage();
}
}
// 리스트의 마지막 요소가 아닌 경우에만 페이지를 추가
if (index !== array.length - 1) {
doc.addPage();
}
}, Promise.resolve()); // Initial value for reduce()
// PDF 문서 저장
doc.save(
`report_${format(openDate, 'yyyyMMdd').toString()}_${format(
closeDate,
'yyyyMMdd',
).toString()}.pdf`,
);
} catch (error) {
console.log('error :: ', error);
throw error;
}
};
export default PdfDownloader;
2024-12-09 추가
3. 폰트(ttf) 파일 base64 인코딩
한글 폰트의 경우 base64로 인코딩 된 폰트 파일을 적용하지 않으면 한글 깨짐 현상이 발생한다. 위 예제의 경우 ttf 폰트 파일을 인코딩한 코드를 "NanumGothic-normal.js" 모듈로 만들어 import 하였다. ttf 폰트 파일의 인코딩은 아래 웹사이트를 이용하면 [그림 3]과 같이 쉽게 가능하다.
📌 Base64 Encoder: https://www.giftofspeed.com/base64-encoder/
728x90
반응형