개발자

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

2023년 12월 13일조회 1,364

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

1import { useRecoilValue } from "recoil"
2import { appTestEnrollState } from "./store/stores"
3import { H1, H2, SelectBox } from "./common/Style"
4import styled from "styled-components"
5import { reportTypeOption } from "./common/selectBoxOptions"
6import { t } from "i18next"
7import { useRef, useState } from "react"
8import { ReactComponent as CloseSvg } from "../images/Close.svg"
9import { ReactComponent as DownloadSvg } from "../images/Download.svg"
10import DonutChart from "./DonutChart"
11import { style } from "d3"
12import DetailDonutChart from "./DetailDonutChart"
13import { CompatibilityList } from "./common/TCListArray"
14import html2pdf from "html2pdf.js";
15
16const ModalBackground = styled.div`
17  (생략)
18`
19
20const ModalComponent = styled.div`
21      (생략)
22`
23
24const TestReportWrapper = styled.div`
25
26    width : 270px;
27    height : 48px;
28    gap : 25px;
29    display : flex;
30    align-items : center;
31    justify-content : center;
32`
33
34const TestReportContents = styled.div`
35    width: 942px;
36    margin: 20px auto auto auto;  
37    overflow-y: auto;
38    overflow-x: hidden;
39
40    border: 1px solid #494A50;
41
42    display : flex;
43    flex-direction : column;
44    align-items : center;
45    justify-content : center;
46
47
48`;
49
50const TestSummaryWrapper = styled.div`
51
52    width : 864px;
53    height : 92px;
54    background-color : #FFF4E4;
55
56    display : flex;
57    justify-content : center;
58    align-items : center;
59    margin-top : 35px;
60
61`
62
63const TestSummaryItem = styled.div`
64    width : 214px;
65    height : 61px;
66
67    display : flex;
68    flex-direction : column;
69    justify-content : center;
70    align-items : center;
71    gap : 5px;
72
73    font-weight : 700;
74    border-right : 1px solid #D4D6DD;
75
76    &:last-child{
77        border-right : none;
78    }
79`
80...
81
82
83export default function CompatibilityTestReport({setModalOpen}: {[key : string] : Function}) {
84
85    const closeModal = () => {
86        setModalOpen(false)
87    }
88
89...
90      const printToPDF = () => {
91        const modalComponent = document.getElementById("modal-component");
92    
93        if (modalComponent) {
94          const pdfOptions = {
95            margin: 10,
96            filename: "compatibility_test_report.pdf",
97            image: { type: "jpeg", quality: 0.98 },
98            html2canvas: { scale: 2 },
99            jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
100          };
101    
102          html2pdf().from(modalComponent).set(pdfOptions).save();
103        }
104      }
105
106
107    return(
108    <ModalBackground>
109            <ModalComponent>
110                <div style={{display : "flex", marginLeft : "57px", marginTop : "40px", flexDirection : "row", alignItems : "center", gap : "540px"}}>
111                <H1>{t('테스트')} {t('리포트')}</H1>
112                <TestReportWrapper>
113                    <SelectBox onChange=
114                               {(e : React.ChangeEvent<HTMLSelectElement>) => 
115                                setReportOption(Number(e.target.value))}
116                               placeholder = "선택하세요" 
117                               value={reportOption}
118                               options={reportTypeOption} 
119                               borderRadius={12} 
120                               width={172}/>
121                        <Download
122                          width={32}
123                          height={32}
124                          onClick={printToPDF}
125                        />
126                    <Close onClick={closeModal} width={24} height={24}/>
127                </TestReportWrapper>
128                </div>
129                <TestReportContents id ="modal-component">
130                    <TestSummaryWrapper>
131                        <TestSummaryItem>
132                            <p>Test App</p>
133                            <p>게임명</p>
134                        </TestSummaryItem>
135                     ...
136            </ModalComponent>
137        </ModalBackground>      
138    )
139}
이 질문이 도움이 되었나요?
'추천해요' 버튼을 누르면 좋은 질문이 더 많은 사람에게 노출될 수 있어요. '보충이 필요해요' 버튼을 누르면 질문자에게 질문 내용 보충을 요청하는 알림이 가요.

답변 3

김인후님의 프로필 사진

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

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

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

1(이전 코드)
2
3const tableRows = Object.keys(scoresByCombination).map((combinationKey) => {
4        const { questionStr, 0: score1 = 0, 1: score2 = 0, 2: score3 = 0, 3: score4 = 0, 4: score5 = 0 } = scoresByCombination[combinationKey];
5      
6        return (
7          <Tr key={combinationKey}>
8            <Td style={{ width: "390px" }}>{t(questionStr!)}</Td>
9            <Td>{score1}</Td>
10            <Td>{score2}</Td>
11            <Td>{score3}</Td>
12            <Td>{score4}</Td>
13            <Td>{score5}</Td>
14          </Tr>
15        );
16
17
18  
19// 페이지에 맞게 테이블 갯수 나누기
20  const tableRowsFirst = tableRows.slice(0, 5);
21  const tableRowsSecond = tableRows.slice(5, 28);
22  const tableRowsThird = tableRows.slice(28, 51);
23  const tableRowsFourth = tableRows.slice(51);
24
25
26 /* 결과 리포트 pdf 파일로 저장 */
27
28      const printToPDF = async () => {
29        const pdf = new jsPDF("p", "mm", "a4");
30
31        const addPageContent = async (element : any) => {
32          const canvas = await html2canvas(element);
33          const imgData = canvas.toDataURL("image/jpeg");
34          pdf.addImage(imgData, "JPEG", 10, 10, 190, 277);
35          pdf.addPage();
36        };
37
38        const reportContents = [
39          document.getElementById("report-contents-1"),
40          document.getElementById("report-contents-2"),
41          document.getElementById("report-contents-3"),
42          document.getElementById("report-contents-4"),
43        ];
44
45        for (const reportContent of reportContents) {
46          if (reportContent) {
47            await addPageContent(reportContent);
48          }
49        }
50        pdf.save("test_report.pdf");
51};
52
53
54
55 return(
56// 1 페이지 
57<TestReportContents id ="report-contents-1">
58
59...
60 <DetailTable style={{ marginBottom: !tableRowsSecond.length  ? `${1200 - 36 * tableRowsFirst.length}px` :"" }}>
61                        <Tr>           
62                            <Th style={{width : "390px"}}>{t('문항')}</Th>
63                            <Th>{t('확인불가')}</Th>
64                            <Th>{t('발생하지 않음')}</Th>
65                            <Th>{t('드물게 발생')}</Th>
66                            <Th>{t('빈번하게 발생')}</Th>
67                            <Th>{t('100% 발생')}</Th>
68                        </Tr>
69                        {tableRowsFirst}
70                    </DetailTable>
71                </TestReportContents>
72               
73// 2페이지
74 {tableRowsSecond.length > 0 && ( 
75                <TestReportContents id ="report-contents-2">
76                    <DetailTable style={{ marginBottom: !tableRowsThird.length ? `${1200 - 36 * tableRowsSecond.length}px` :"" }}>
77                        {tableRowsSecond}             
78                    </DetailTable>     
79                 </TestReportContents>
80) }
81// 3페이지 
82               {tableRowsThird.length > 0 && (
83                  <TestReportContents id="report-contents-3">
84                    <DetailTable style={{ marginBottom:  !tableRowsFourth.length ? `${1200 - 36 * tableRowsThird.length}px` :"" }}>
85                      {tableRowsThird}
86                    </DetailTable>
87                  </TestReportContents>
88 )}
89// 4페이지  
90              {tableRowsFourth.length > 0 && (
91                  <TestReportContents id="report-contents-4">
92                    <DetailTable style={{ marginBottom: `${1200 - 36 * tableRowsFourth.length}px` }}>
93                      {tableRowsFourth}
94                    </DetailTable>
95                  </TestReportContents>
96 )}
97)
98
99
100
101
102/* 
103
104//useRef를 활용해서 높이를 구했으나 실패 
105
106    const detailTable3Ref = useRef<HTMLDivElement | null>(null)
107   
108return(
109...
110    {tableRowsThird.length > 0 && detailTable3Ref.current && (
111                  <TestReportContents id="report-contents-3">
112                    <DetailTable ref={detailTable3Ref} style={{ marginBottom:  !tableRowsFourth.length ? `${1200 - detailTable3Ref.current?.scrollHeight}px` :"" }}>
113                      {tableRowsThird}
114                    </DetailTable>
115                  </TestReportContents>
116              )}
117}
118...
119)
120*/
비공개님의 프로필 사진

비공개

개발자2024년 04월 28일

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

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

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

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

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

또는

이미 회원이신가요?

AI 답변
커리어리 AI 봇 프로필 이미지

커리어리 AI 봇

GPT가 달아주는 답변입니다.

2023년 12월 13일

리액트에서가 제공하는 `html2pdf.js`을 사용하여 내용물을 A4 규격으로 변환하려면, CSS를 사용해 특정 컨텐츠 또는 전체 페이지의 크기를 A4에 딱 맞게 조정해야 합니다. 이 과정에서 가장 중요한 것은 가로 세로 비율이 A4 비율인 1.414 (210mm / 297mm) 를 유지하는 것입니다. 그런 다음 html2canvas 옵션 값을 조절하여 pdf 문서의 확대/축소 비율을 조절할 수 있습니다. 아래는 코드 예시입니다: ```javascript const printToPDF = () => { const modalComponent = document.getElementById("modal-component"); if (modalComponent) { let opt = { margin: [10,10,10,10], filename: 'myfile.pdf', image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: window.devicePixelRatio * 1.5 }, // Adjust this value to control the PDF size. jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' } }; html2pdf().from(modalComponent).set(opt).toPdf().get('pdf').then(function (pdf) { var totalPages = pdf.internal.getNumberOfPages(); for (let i = 1; i <= totalPages; i++) { pdf.setPage(i); pdf.setFontSize(10); } }).save(); } } ``` 위 코드 첫줄에 보면 `html2canvas: { scale: window.devicePixelRatio * 1.5 }` 부분이 있는데, 이 값을 통해 PDF 문서의 크기를 조절할 수 있습니다. 값을 높일수록 PDF가 작아지고, 낮출수록 PDF가 커집니다. 이렇게 하더라도 여전히 문제가 해결되지 않는다면, CSS에서 페이지의 크기를 A4 용지에 맞추어 설정하거나 내용을 줄여야 할 수도 있습니다. 이 부분은 디자인과 내용에 따라 달라질 수 있으므로 상황에 따라 적절히 조정해 주세요.

목록으로

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