Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
Tags
- github
- literal
- TypeScript
- 1px border
- TS
- ES5
- 0.75px border
- angular
- 0.25px border
- 서버리스 #
- es6
- jwt
- 데이터베이스 #try #이중
- 당근마켓
- 전역변수
- npm
- entity
- 클론코딩
- 컴포넌튼
- 문서번호
- 으
- 타입스크립트
- 10px
- Props
- &연산
- font-size
- ZOOM
- 0.5px border
- Websocket
- Strict
Archives
- Today
- Total
복잡한뇌구조마냥
PDF 생성 방식에 따른 비교 분석 본문
Web에서 PDF 생성 방법
Native CSS print rule 활용
- 방법: media query의 print 를 활용해서 pdf 생성
- 장점: 외부 라이브러리 필요없이 간단하게 pdf를 생성
- 단점: 인쇄 버튼을 눌러 저장한 후 pdf를 생성하는 방식으로, 업로드에 필요한 url을 생성할 수 없음. 프린트 화면에서 그 다음 단계로 넘어가야 하는 것이 단점.
DOM Screenshot
- 방법: 현재 보이는 화면에 대한 스크린샷을 찍어서 이를 pdf로 변환 (html ⇒ html2canvas ⇒ canvas 에 html의 스냅샷 추가 ⇒ jspdf ⇒ pdf)
- 장점: 화면에 보이는 그대로 pdf를 찍어낼 수 있음. 사이즈가 고정되어 있고 어느 정도의 양식이 정해져 있는 문서를 생성하기에 유용
- 단점: 화면 사이즈에 의존하고 있어 동적으로 사이즈를 선택하기 어려움. 이미지를 삽입하여 pdf를 만들어내는 것이기 때문에 텍스트를 선택할 수는 없음.
라이브러리 태그 사용
- 방법: 라이브러리에서 제공하는 태그 규칙에 맞춰 PDF 양식 작성.(PDFMake, React-pdf 등) 템플릿에다가 데이터를 주입하여 이를 바탕으로 pdf를 추출하는데 특화됨
- 장점: 빠른 pdf 변환 속도, 고해상도 가능
- 단점
- 리포트 양식이 바뀔 때마다 html 뿐만 아니라 pdf 문서 양식도 개발해야 함. 동적인 형태의 문서를 변환하기 어려움
- html의 각 태그들을 파싱하여 라이브러리에 맞는 태그들로 매칭해주어 변환하는 함수 작성 가능 => 태그 매칭 과정에서 스타일을 주입하기 힘듦.
- 제공하는 인터페이스는 다양하지만(용지 사이즈, 간격, 페이지네이션 등) 동적인 형태의 문서를 다루기는 어려움
서버 API 요청
- 방법: 자사 또는 타사의 pdf 생성 API에 html을 전송해 pdf 파일을 제공받는 방식 (convertAPI, DocRaptor 등)
- 장점: 빠른 pdf 변환 속도, 고해상도 가능, 라이브러리 사용 불필요
- 단점
- 타사 서버 API 사용시 환자 정보 유출에 관한 고민 필요. DocRaptor의 경우 SOC2, HIPAA, GDPR 인증을 획득하였으나, 의료 정보를 해외 서버 저장하는 것이 금지된 병원(NCP 서버 사용 병원)에 대한 대안 필요
- 자사 서버에서 pdf 생성 API를 개발하는 것이 더 나은 방법으로 판단됨
PDF 생성 라이브러리 적용 결과
1. PDFMake
방법1
📚사용한 라이브러리
- html-to-pdfmake
- pdfmake
✨ 장점
- 생성된 pdf파일의 크기가 작음
- pdf생성에 적은 소요 시간
- pdfmake 코드를 빠르고 쉽게 만들어줌
- pdf 파일 text를 선택할 수 있음
- 컴포넌트 구조에 상관없이 일정한 PDF를 생성 가능함
❗️문제점
- html-to-pdfmake에 적합하지 않은 요소가 한개라도 포함되는 경우 라이브러리가 동작하지 않음
- html-to-pdfmake에 css를 수동적으로 적어줘야하는 문제가 있음.
📝 예시 코드
async htmlToPdfMake() {
if (this.element.nativeElelement) {
// html의 string화
// <div>test</div> => '<div>test</div>'
const htmlString: string = this.element.nativeElelement.outerHTML;
// html => pdfmake 내용으로 변환
const convertedPDFMake = htmlToPdfmake(htmlString);
// pdfmake 요소 생성
const contents = {
content: convertedPDFMake,
style: {
'page': {
width: '210mm',
hight: '297mm',
'background': 'white',
}
}
};
// pdfmake로 pdf 다운로드
pdfmake.createPdf(contents).download();
}
}
방법2
📚사용한 라이브러리
- pdfmake
✨ 장점
- 생성된 pdf파일의 크기가 작음
- pdf생성에 적은 소요 시간
- pdf 파일 text를 선택할 수 있음
- 컴포넌트 구조에 상관없이 일정한 PDF를 생성 가능함
❗️문제점
- pdfMake를 사용하면 Print, PDF 생성 시 실제 View와 값이 다른 경우가 있음
(수정 누락, 다른 pipe 사용, 삼항 연산자 또는 Null 병합 연산자 등 코드 차) - pdfmake의 디자인 한계
(폰트, border-radius 등 테이블 관련 디자인, 프린트 후 선이 나옴 등) - report가 변경될 때마다 html요소와 pdfmake를 각각 개발하여 작업시간이 오래걸림
* pdfmake 작업이 적응되기 위힌 러닝 커브가 크다.
📝 예시 코드
async htmlToPdfMake() {
const contents = {
pageSize: 'A4',
content: [
{
table: {
{
text: 'test',
style: 'headerFont',
bold: false,
margin: [0, 0, 0, 0],
border: [false, false, false, false],
}
}
}
],
footer: [],
style: {
headerFont: {
fontSize: 10,
bold: true,
}
}
}
// pdfmake로 pdf 다운로드
pdfmake.createPdf(contents).download();
}
2. native css print rules - window.print()
✨ 장점
- pdf생성에 적은 소요 시간 - 압도적으로 적은 시간 소요
- 외부 라이브러리를 사용할 필요가 없음
❗️문제점
- 인쇄 버튼을 통해 PDF를 저장하는 방식임
- 업로드에 필요한 PDF 관련 데이터를 생성할 수 없음.
(서버에 저장을 위해서는 사용자가 직접적으로 서버에 저장하도록 해야함 - 치명적인 단점)
📝 예시 코드
Ts 파일
elementPrint() {
window.focus();
window.print();
}
css 파일 - 프린트에서 불필요한 요소를 직접적으로 제어
@media print {
@page {
margin: 0;
page-break-after: always;
}
.print-invisible {
display: none;
}
}
3. jsPDF
📚사용한 라이브러리
- jsPDF
✨ 장점
- HTML 요소를 바로 PDF로 만들 수 있음
❗️문제점
- 한글 깨짐 - 치명적 단점
📝 예시 코드
jsPDF() {
const doc = new jspdf.jsPDF({ unit: 'mm', format: 'a4', orientation: 'portrait', compress: true });
const containerArray: any = document.getElementsByClassName('page');
doc.html(containerArray[0], {
callback: (doc: any) => {
doc.save('test.pdf');
}
});
// 사용방법 2
// doc.html(containerArray[0]);
// doc.addFileToVFS();
}
4. HTML2PDF
📚사용한 라이브러리
- html2pdf
✨ 장점
- 크기가 큰 html 요소를 자체적으로 여러 페이지로 나누어 PDF를 생성해줌
(최대 크기 제한은 있음) - 화질이 좋음
- PDF 생성을 위한 별도의 파일이 필요하지 않음
- 컴포넌트를 A4 사이즈 규격으로 만들어야 생성이 용이함
- class 기준으로 각 페이지를 나눌 수 있음
- 컴포넌트가 이미지로 생성되어 원본과 동일한 PDF를 생성할 수 있음
❗️문제점
- 대량 페이지 처리에 있어 소요 시간이 많이 걸림
- PDF 용량이 큼
- 만들어낸 pdf의 text를 선택할 수 없음
- PDF생성 전 이미지에 중간 처리 과정을 할 수 없음
- svg등 이미지가 많을 수록 느려짐
📝 예시 코드
async htmlToPDF() {
return new Promise<void>(async resolve => {
// container는 A4 사이즈의 component가 10개 들어있는 div 요소임
const containerArray: any = document.getElementsByClassName('container');
if (containerArray?.length) {
const startTime = new Date();
const imgWidth = 210; // mm (A4 width)cd cd
const pageHeight = 297;
const scale = 3;
const dpi = 300;
const opt = {
margin: [0, 0],
filename: `myfile_dpi_${dpi}_scale_${scale}.pdf`,
image: { type: 'jpeg', quality: 0.98 },
pagebreak: { after: '.sheet' },
html2canvas: {
dpi,
scale,
scrollX: 0,
scrollY: 0,
useCORS: true,
ignoreElements: (e: any, i: number) => {
return e.classList.contains('page-margin');
},
},
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait', compress: true }
};
if (containerArray.length > 1) {
let worker = html2pdf().set(opt).from(containerArray[0]).toPdf();
for (let i = 1; i < containerArray.length; i++) {
worker = worker.from(containerArray[i]).toContainer().toCanvas().toPdf().get('pdf').then((pdf: any) => {
if (i === containerArray.length - 1) {
// Bump cursor ahead to new page until on last page
const pageCount = pdf.getNumberOfPages();
pdf.deletePage(pageCount);
worker = worker.save();
const endTime = new Date();
console.log('html2pdf-duration: ' + (endTime.getTime() - startTime.getTime()));
resolve();
}
});
}
} else {
let worker = html2pdf().set(opt).from(containerArray[0]).toPdf().get('pdf').then((pdf: any) => {
const pageCount = pdf.getNumberOfPages();
pdf.deletePage(pageCount);
worker = worker.save();
const endTime = new Date();
console.log('html2pdf-duration: ' + (endTime.getTime() - startTime.getTime()));
resolve();
});
}
}
});
}
5. HTML2Canvas
📚사용한 라이브러리
- html2canvas
- jsPdf
✨ 장점
- html2canvas로 생성된 이미지를 jpeg로 변경할 수 있음
- html2canvas로 생성된 이미지를 pdf에 넣기 전 중간 처리 과정을 넣을 수 있음
- 컴포넌트가 이미지로 생성되어 원본과 동일한 PDF를 생성할 수 있음
❗️문제점
- 대량 페이지 처리에 있어 소요 시간이 많이 걸림
- PDF 용량이 큼
- 만들어낸 pdf의 text를 선택할 수 없음
- svg등 이미지가 많을 수록 느려짐
📝 예시 코드
async htmlToCanvas() {
return new Promise<void>(async resolve => {
// container는 A4 사이즈의 component가 10개 들어있는 div 요소임
const containerArray: any = document.getElementsByClassName('container');
if (containerArray?.length) {
const startTime = new Date();
const scale = 3;
const dpi = 300;
const doc = new jspdf.jsPDF({ unit: 'mm', format: 'a4', orientation: 'portrait', compress: true });
const filename: string = `myfile_dpi_${dpi}_scale_${scale}.pdf`;
const opt = {
scale,
scrollX: 0,
scrollY: 0,
useCORS: true,
logging: true,
allowTaint: true,
type: 'view',
ignoreElements: (e: any) => {
return e.classList.contains('page-margin');
},
};
if (containerArray.length > 1) {
for (let i = 0; i < containerArray.length; i++) {
if (i === containerArray.length - 1) {
await this.savePDFtoCanvas(doc, containerArray[i], opt, filename, startTime, i).then(() => resolve());
} else {
await this.savePDFtoCanvas(doc, containerArray[i], opt, undefined, startTime, i);
}
}
} else {
await this.savePDFtoCanvas(doc, containerArray[0], opt, filename, startTime, 0).then(() => resolve());
}
}
});
}
async savePDFtoCanvas(worker: any, element: any, opt: any, filename: string, startTime: Date, containerIndex: number) {
return new Promise<void>(async resolve => {
await html2canvas(element, opt).then((canvas) => {
const canvasToJpeg = canvas.toDataURL('image/jpeg', 1.0);
const imgHeight = (canvas.height * this.imgWidth) / canvas.width;
const pageCount = Math.round(imgHeight / this.pageHeight);
for (let i = 0; i < pageCount; i++) {
if (!(containerIndex === 0 && i === 0)) {
worker = worker.addPage();
}
worker = worker.addImage(canvasToJpeg, 'JPEG', 0, (-this.pageHeight * (i)), this.imgWidth, imgHeight, undefined, 'FAST');
}
if (filename) {
worker = worker.save(filename + '.pdf');
// window.open(worker.output('bloburl')); // 다운로드 전 미리보기 페이지
const endTime = new Date();
console.log('html2canvas-duration: ' + (endTime.getTime() - startTime.getTime()));
}
resolve();
});
});
}
6. HTML-to-Image
📚사용한 라이브러리
- html-to-image
- jsPdf
✨ 장점
- html2canvas로 생성된 이미지를 jpeg로 변경할 수 있음
- html2canvas로 생성된 이미지를 pdf에 넣기 전 중간 처리 과정을 넣을 수 있음
- 컴포넌트가 이미지로 생성되어 원본과 동일한 PDF를 생성할 수 있음
❗️문제점
- 대량 페이지 처리에 있어 소요 시간이 많이 걸림
- PDF 용량이 큼
- 만들어낸 pdf의 text를 선택할 수 없음
- 한 페이지씩 넣을 때는 화질이 좋으나 10페이지 단위로 이미지 구성시 화질이 떨어짐
📝 예시 코드
async exportAsToJpeg() {
return new Promise<void>(async resolve => {
let worker = new jspdf.jsPDF({ unit: 'mm', format: 'a4', orientation: 'portrait', compress: true });
const pageArray: any = document.getElementsByClassName('page');
const containerArray: any = document.getElementsByClassName('container');
const startTime = new Date();
const filename: string = `myfile_html-to-image.pdf`;
const pageSize = pageArray?.length;
if (containerArray.length > 1) {
for (let i = 0; i < containerArray.length; i++) {
if (i === containerArray.length - 1) {
await this.getJPeg(worker, containerArray[i], (pageSize >= this.containerSize && pageSize % this.containerSize === 0 ? this.containerSize : pageSize % this.containerSize), filename, startTime, i);
} else {
await this.getJPeg(worker, containerArray[i], this.containerSize, undefined, startTime, i);
}
}
} else {
await this.getJPeg(worker, containerArray[0], (pageSize >= this.containerSize && pageSize % this.containerSize === 0 ? this.containerSize : pageSize % this.containerSize), filename, startTime, 0);
}
resolve();
});
}
async getJPeg(worker: any, element: any, pageSize: number, filename: string, startTime: Date, containerIndex: number) {
return new Promise<void>(async resolve => {
const scale = 3;
const pageMargin = 5;
const imgHeight = (this.pageHeight + pageMargin) * pageSize;
const filterClass = (node: HTMLElement) => {
const exclusionClasses = ['page-margin'];
return !exclusionClasses.some((classname) => node.classList?.contains(classname));
}
await htmlToImage.toJpeg(element, { quality: 0.95, backgroundColor: 'white', pixelRatio: 100, canvasHeight: imgHeight, canvasWidth: this.imgWidth, filter: filterClass })
.then((canvasToJpeg) => {
for (let i = 0; i < pageSize; i++) {
if (!(containerIndex === 0 && i === 0)) {
worker = worker.addPage();
}
worker = worker.addImage(canvasToJpeg, 'JPEG', 0, (-this.pageHeight * (i)), this.imgWidth, imgHeight, undefined, 'FAST');
}
if (filename) {
worker = worker.save(filename + '.pdf');
// window.open(worker.output('bloburl')); // 다운로드 전 미리보기 페이지
const element = document.getElementById('myIframe');
if (element instanceof HTMLIFrameElement) {
// element.attr('src', doc.output('datauristring'));
element.src = worker.output('datauristring')
}
const endTime = new Date();
console.log('html-to-image-duration: ' + (endTime.getTime() - startTime.getTime()));
}
resolve();
});
});
}
이미지 기반 라이브러리 비교
✨ 이미지 기반 라이브러리 공통점
- 이미지 생성하는 시간동안 CPU를 100% 사용하기 때문에 페이지가 많을수록 오래 걸림
- 1장씩 PDF로 변환하면 걸리는 시간이 상당히 오래 걸림
- 변환할 수 있는 이미지 최대 크기로 묶을수록 빨라짐 (scale 등 고려 필요) - A4크기의 이미지 생성 방식이기 때문에 PDF 용량이 큰 편임
- 컴퓨터 상태에 따라 편차가 심함
⚡ 속도 비교
- 현재 Livestudio 기준
HTML2PDF | HTML2Canvas | HTML-to-Image | |
속도 | 19s | 17s | 430s |
용량 | 10MB | 17.5MB | 9.5MB |
- 가상 Report 기준
HTML2PDF | HTML2Canvas | HTML-to-Image | |
5 Page | 9s | 2s | 2s |
10 Page | 19s | 5s | 3s |
20 Page | 30s | 10s | 10s |
30 Page | 43s | 17s | 12s |
40 Page | 41s | 29s | 19s |
50 Page | 75s | 41s | 21s |
60 Page | 73s | 55s | 24s |
- ECG 그래프 4개 페이지 기준
HTML2PDF | HTML2Canvas | HTML-to-Image | |
10 Page | 3.1 | 4 | 2.4 |
20 Page | 6.2 | 8.3 | 4.7 |
30 Page | 11.8 | 13.2 | 6.9 |
40 Page | 14.2 | 18.8 | 9 |
50 Page | 19.1 (73MB) | 24.3 (91MB) | 11.5 (14MB) |
- ECG 그래프 8개 페이지 기준
HTML2PDF | HTML2Canvas | HTML-to-Image | |
10 Page | 4.4 | 6.3 | 3.8 |
20 Page | 11 | 13.5 | 7.1 |
30 Page | 16.2 | 22.5 | 10.5 |
40 Page | 23.2 | 32.4 | 14.7 |
50 Page | 31.9 (141MB) | 42 (174MB) | 18 (27MB) |
종합 평가 (⭐ : 1 🟡 : 0.5)
속도화질용량안정성 (완성도)개발 난이도
PDFMake | ⭐ ⭐ 🟡 | ⭐ ⭐ ⭐ | ⭐ ⭐ ⭐ | ⭐ ⭐ 🟡 | ⭐ ⭐ ⭐ |
window.print | ⭐ ⭐ ⭐ | ⭐ ⭐ ⭐ | (평가불가) | ⭐ ⭐ | ⭐ |
jsPDF | ⭐ ⭐ ⭐ | ⭐ | (평가불가) | ⭐ | ⭐ |
HTML2Pdf | ⭐ 🟡 | ⭐ ⭐ ⭐ | ⭐ | ⭐ ⭐ | ⭐ ⭐ |
HTML2Canvas | ⭐ 🟡 | ⭐ ⭐ ⭐ | ⭐ | ⭐ ⭐ | ⭐ ⭐ |
HTML-to-Image | ⭐ ⭐ | ⭐ ⭐ | ⭐ 🟡 | ⭐ ⭐ ⭐ | ⭐ ⭐ |
LIST
'FE > 기타' 카테고리의 다른 글
[FE] 실무 예제형 디자인 패턴 (0) | 2024.10.11 |
---|---|
[QA/UX] QA 테스터가 알아야 할 기본 UI 요소 정리 (0) | 2024.06.21 |
[FE] 프론트엔트 웹개발 성능 향상 (0) | 2023.12.03 |
[FE 시각화] Angular 공부 ( D3.js ) (3) | 2023.01.11 |
프론트엔드 면접준비 [ 49 - 60 ] (0) | 2022.11.20 |