개발자

react에서 A4 규격으로 pdf 저장 (html2pdf 사용)

2023년 12월 13일조회 1,160

리액트에서 html2pdf 라이브러리를 사용하여 내용물을 A4 규격에 맞추어서 다운로드 하고 싶습니다. printToPDF 함수를 구현하였으나, pdf 파일의 가로, 세로 크기만 A4 규격이고, 아래 이미지 처럼 내용 크기는 축소 되지 않았습니다. (가로 우측 잘림) 내용물을 A4 크기에 맞추어 적용하는 방법 없을까요?

이 질문이 도움이 되었나요?
'추천해요' 버튼을 누르면 좋은 질문이 더 많은 사람에게 노출될 수 있어요. '보충이 필요해요' 버튼을 누르면 질문자에게 질문 내용 보충을 요청하는 알림이 가요.

답변 3

김인후님의 프로필 사진

안녕하세요 전에 비슷한 이슈를 css에서 @media print를 이용하거나 a4에 사이즈를 맞춘 모달을 만들어 그 모달을 프린트 하는 등으로 해결했었습니다.

그린티라떼님의 프로필 사진

html2pdf 라이브러리 대신 html2canvas + jsPDF 조합을 사용했습니다. => 가로,세로 마진 설정이 잘 적용되었습니다. 하지만 페이지 분할 기능은 추가 구현이 필요했습니다. html2canvas는 현재 출력되는 페이지를 윈도우의 "캡처 도구" 처럼 현재 출력되는 화면을 캡처해서 이미지화 시키며, 별도의 DOM(html)을 분할하는 기능은 지원하지 않는것 같습니다. 따라서, View에서 부터 페이지를 분할해 html2canvas로 전달하는 방법을 생각해 보았습니다. marginBottom을 설정한 이유는 해당 페이지가 마지막일 때(다음 페이지가 존재하지 않을때) (a4 세로길이 - 테이블 세로 길이)를 주기 위해서 입니다. 만약, marginBottom을 적용하지 않으면, html2canvas 설정사항에 따라 세로 길이에 맞춰 마지막 페이지의 테이블이 늘어져서 출력되는 현상이 발생합니다. (marginBottom 설정시 useRef.current.scrollHeight 값도 적용해 보았지만, 적용되지 않았습니다...)

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
(이전 코드)

const tableRows = Object.keys(scoresByCombination).map((combinationKey) => {
        const { questionStr, 0: score1 = 0, 1: score2 = 0, 2: score3 = 0, 3: score4 = 0, 4: score5 = 0 } = scoresByCombination[combinationKey];
      
        return (
          <Tr key={combinationKey}>
            <Td style={{ width: "390px" }}>{t(questionStr!)}</Td>
            <Td>{score1}</Td>
            <Td>{score2}</Td>
            <Td>{score3}</Td>
            <Td>{score4}</Td>
            <Td>{score5}</Td>
          </Tr>
        );


  
// 페이지에 맞게 테이블 갯수 나누기
  const tableRowsFirst = tableRows.slice(0, 5);
  const tableRowsSecond = tableRows.slice(5, 28);
  const tableRowsThird = tableRows.slice(28, 51);
  const tableRowsFourth = tableRows.slice(51);


 /* 결과 리포트 pdf 파일로 저장 */

      const printToPDF = async () => {
        const pdf = new jsPDF("p", "mm", "a4");

        const addPageContent = async (element : any) => {
          const canvas = await html2canvas(element);
          const imgData = canvas.toDataURL("image/jpeg");
          pdf.addImage(imgData, "JPEG", 10, 10, 190, 277);
          pdf.addPage();
        };

        const reportContents = [
          document.getElementById("report-contents-1"),
          document.getElementById("report-contents-2"),
          document.getElementById("report-contents-3"),
          document.getElementById("report-contents-4"),
        ];

        for (const reportContent of reportContents) {
          if (reportContent) {
            await addPageContent(reportContent);
          }
        }
        pdf.save("test_report.pdf");
};



 return(
// 1 페이지 
<TestReportContents id ="report-contents-1">

...
 <DetailTable style={{ marginBottom: !tableRowsSecond.length  ? `${1200 - 36 * tableRowsFirst.length}px` :"" }}>
                        <Tr>           
                            <Th style={{width : "390px"}}>{t('문항')}</Th>
                            <Th>{t('확인불가')}</Th>
                            <Th>{t('발생하지 않음')}</Th>
                            <Th>{t('드물게 발생')}</Th>
                            <Th>{t('빈번하게 발생')}</Th>
                            <Th>{t('100% 발생')}</Th>
                        </Tr>
                        {tableRowsFirst}
                    </DetailTable>
                </TestReportContents>
               
// 2페이지
 {tableRowsSecond.length > 0 && ( 
                <TestReportContents id ="report-contents-2">
                    <DetailTable style={{ marginBottom: !tableRowsThird.length ? `${1200 - 36 * tableRowsSecond.length}px` :"" }}>
                        {tableRowsSecond}             
                    </DetailTable>     
                 </TestReportContents>
) }
// 3페이지 
               {tableRowsThird.length > 0 && (
                  <TestReportContents id="report-contents-3">
                    <DetailTable style={{ marginBottom:  !tableRowsFourth.length ? `${1200 - 36 * tableRowsThird.length}px` :"" }}>
                      {tableRowsThird}
                    </DetailTable>
                  </TestReportContents>
 )}
// 4페이지  
              {tableRowsFourth.length > 0 && (
                  <TestReportContents id="report-contents-4">
                    <DetailTable style={{ marginBottom: `${1200 - 36 * tableRowsFourth.length}px` }}>
                      {tableRowsFourth}
                    </DetailTable>
                  </TestReportContents>
 )}
)




/* 

//useRef를 활용해서 높이를 구했으나 실패 

    const detailTable3Ref = useRef<HTMLDivElement | null>(null)
   
return(
...
    {tableRowsThird.length > 0 && detailTable3Ref.current && (
                  <TestReportContents id="report-contents-3">
                    <DetailTable ref={detailTable3Ref} style={{ marginBottom:  !tableRowsFourth.length ? `${1200 - detailTable3Ref.current?.scrollHeight}px` :"" }}>
                      {tableRowsThird}
                    </DetailTable>
                  </TestReportContents>
              )}
}
...
)
*/
비공개님의 프로필 사진

비공개

개발자4월 28일

안녕하세요. 혹시 marginBottom 계산 방법에 대해 조금 더 자세하게 설명해주실 수 있을까요?

그린티라떼님의 프로필 사진

@비공개 ${1200 - 36 * tableRowsSecond.length}은 테이블 높이 입니다. 테이블 높이를 제외한 나머지는 marginBottom을 주어 테이블이 늘어지는 현상을 방지하기 위해서 입니다.

지금 가입하면 모든 질문의 답변을 볼 수 있어요!

현직자들의 명쾌한 답변을 얻을 수 있어요.

또는

이미 회원이신가요?

목록으로
키워드로 질문 모아보기

실무, 커리어 고민이 있다면

새로운 질문 올리기

지금 가입하면 모든 질문의 답변을 볼 수 있어요!