첫 글로 Java PDF 생성시 많이 사용하고 있는 itext를 이용하여 HTML페이지를 CSS와 폰트도 함께 적용하여,
PDF를 만들고 또한 라이브 서버 jar 배포시 대응 하기 위한 내용도 같이 포함하여 진행해 보겠습니다.
우선 시작하기에 앞서 환경은 아래와 같습니다.
IDE : intelliJ
자바 : Java 11
스프링 부트 : Spring Boot 2.7.7
템플릿 엔진 : Thymeleaf
빌드 도구 : Gradle
그외 Lombok , spring boot web
작성자의 OS는 Mac입니다. ( Window의 경우 파일 디렉토리 확인 바랍니다. )
사용한 itext 라이브러리 입니다.
// ====================================================================================================
// HTML TO PDF LIBRARY
implementation 'com.itextpdf:itextpdf:5.5.13.3'
implementation 'com.itextpdf.tool:xmlworker:5.5.13.3'
implementation 'com.itextpdf:pdfa:7.2.3'
// ====================================================================================================
대략적인 프로젝트 구조입니다.
itext 패키지에 컨트롤러와 DTO ,
실제 기능이 구현될 Util을 만들어 두었습니다.
그리고 호출될 CSS와 호출해볼 이미지를 미리 준비해 놓았습니다.
templates 하위 index.html의 소스입니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Hello itext pdf</title>
</head>
<body>
<h1>Hello itext pdf</h1>
<p>Click <a href="/attachment/pdf">here</a> to download the pdf</p>
</body>
</html>
itextPdf.css에서 적용할 간단한 스타일 입니다.
body{font-family: AppleSDGothicNeo, sans-serif; font-size:12px; color:#222; background:#fff; height: 100%;}
p{background-color: #e73a3a;}
img{border:0; vertical-align:middle;}
사용할 DTO 입니다
@Getter
@Setter
public class ItextPdfDto {
private String pdfCode; // pdf 종류가 여러개일 경우 html 을 구분하기 위한 코드
private String pdfFilePath; // pdf 파일이 저장될 경로
private String pdfFileName; // pdf 파일명
}
이어서
ItextPdfUtil 파일에 하나씩 기술 하겠습니다.
임의로 정의된 html을 담아둘 메소드입니다
* 현재 글에선 hyeok만 사용 할 예정입니다*
// 사용할 html 코드를 가져오는 메소드
public String getHtml(String code) {
String return_html = "";
switch (code) {
case "jeon" :
return_html = "<html>" +
"<body>" +
"<h1>jeon</h1>" +
"</body>" +
"</html>";
break;
case "nam" :
return_html = "<html>" +
"<body>" +
"<h1>nam</h1>" +
"<p>CSS 테스트 입니다.</p>" +
"</body>" +
"</html>";
break;
case "hyeok" :
return_html = "<html>" +
"<body>" +
"<h1>hyeok</h1>" +
"<p>이미지 테스트 합니다.</p>" +
"<img src='http://localhost:8080/images/test.png' />" +
"</body>" +
"</html>";
break;
}
return return_html;
}
실제 itext가 사용되는 메소드 입니다 ( ItextPdfUtil 안에 있습니다 )
/*
* iText 라이브러리를 사용한 PDF 파일 생성
* CSS , Font 설정 기능 포함
* */
public void createPDF(ItextPdfDto itextPdfDto) {
// 최초 문서 사이즈 설정
Document document = new Document(PageSize.B4, 30, 30, 30, 30);
try {
// PDF 파일 생성
PdfWriter pdfWriter = PdfWriter.getInstance(document, new FileOutputStream(itextPdfDto.getPdfFilePath()+itextPdfDto.getPdfFileName()));
// PDF 파일에 사용할 폰트 크기 설정
pdfWriter.setInitialLeading(12.5f);
// PDF 파일 열기
document.open();
// XMLWorkerHelper xmlWorkerHelper = XMLWorkerHelper.getInstance();
// CSS 설정 변수 세팅
CSSResolver cssResolver = new StyleAttrCSSResolver();
CssFile cssFile = null;
try {
/*
* CSS 파일 설정
* 기존 방식은 FileInputStream을 사용했으나, jar 파일로 빌드 시 파일을 찾을 수 없는 문제가 발생
* 따라서, ClassLoader를 사용하여 파일을 읽어오는 방식으로 변경
*/
InputStream cssStream = getClass().getClassLoader().getResourceAsStream("static/css/ItextPdf.css");
// CSS 파일 담기
cssFile = XMLWorkerHelper.getCSS(cssStream);
// cssFile = XMLWorkerHelper.getCSS(new FileInputStream("src/main/resources/static/css/test.css"));
} catch (Exception e) {
throw new IllegalArgumentException("PDF CSS 파일을 찾을 수 없습니다.");
}
if(cssFile == null) {
throw new IllegalArgumentException("PDF CSS 파일을 찾을 수 없습니다.");
}
// CSS 파일 적용
cssResolver.addCss(cssFile);
// PDF 파일에 HTML 내용 삽입
XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS);
/*
* 폰트 설정
* CSS 와 다르게, fontProvider.register() 메소드를 사용하여 폰트를 등록해야 함
* 해당 메소드 내부에서 경로처리를 하여 개발, 배포 시 폰트 파일을 찾을 수 있도록 함
* */
try {
fontProvider.register("static/font/AppleSDGothicNeoR.ttf", "AppleSDGothicNeo");
} catch (Exception e) {
throw new IllegalArgumentException("PDF 폰트 파일을 찾을 수 없습니다.");
}
if(fontProvider.getRegisteredFonts() == null) {
throw new IllegalArgumentException("PDF 폰트 파일을 찾을 수 없습니다.");
}
// 사용할 폰트를 담아두었던 내용을
// CSSAppliersImpl에 담아 적용
CssAppliers cssAppliers = new CssAppliersImpl(fontProvider);
// HTML Pipeline 생성
HtmlPipelineContext htmlPipelineContext = new HtmlPipelineContext(cssAppliers);
htmlPipelineContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
// ========================================================================================
// Pipelines
PdfWriterPipeline pdfWriterPipeline = new PdfWriterPipeline(document, pdfWriter);
HtmlPipeline htmlPipeline = new HtmlPipeline(htmlPipelineContext, pdfWriterPipeline);
CssResolverPipeline cssResolverPipeline = new CssResolverPipeline(cssResolver, htmlPipeline);
// ========================================================================================
// ========================================================================================
// XMLWorker
XMLWorker xmlWorker = new XMLWorker(cssResolverPipeline, true);
XMLParser xmlParser = new XMLParser(true, xmlWorker, StandardCharsets.UTF_8);
// ========================================================================================
/* HTML 내용을 담은 String 변수
주의점
1. HTML 태그는 반드시 닫아야 함
2. xml 기준 html 태그 확인( ex : <p> </p> , <img/> , <col/> )
위 조건을 지키지 않을 경우 DocumentException 발생
*/
String htmlStr = getHtml(itextPdfDto.getPdfCode());
// HTML 내용을 PDF 파일에 삽입
StringReader stringReader = new StringReader(htmlStr);
// XML 파싱
xmlParser.parse(stringReader);
// PDF 문서 닫기
document.close();
// PDF Writer 닫기
pdfWriter.close();
} catch (DocumentException e1) {
throw new IllegalArgumentException("PDF 라이브러리 설정 에러");
} catch (FileNotFoundException e2) {
e2.printStackTrace();
throw new IllegalArgumentException("PDF 파일 생성중 에러");
} catch (IOException e3) {
e3.printStackTrace();
throw new IllegalArgumentException("PDF 파일 생성중 에러2");
} catch (Exception e4) {
e4.printStackTrace();
throw new IllegalArgumentException("PDF 파일 생성중 에러3");
}
finally {
try {
document.close();
} catch (Exception e) {
System.out.println("PDF 파일 닫기 에러");
e.printStackTrace();
}
}
}
추가적인 메소드로 PDF 파일이 매번 생성되지 않게 하기 위한 메소드 입니다.
/*
* PDF 유무를 체크한 후
* PDF 파일이 없을 경우 PDF 파일 생성 메소드 실행
*/
public File checkPDF (ItextPdfDto pdfDto) {
File file = new File(pdfDto.getPdfFilePath(),pdfDto.getPdfFileName());
int fileSize = (int) file.length();
if (fileSize == 0) {
createPDF(pdfDto);
file = new File(pdfDto.getPdfFilePath(),pdfDto.getPdfFileName());
}
return file;
}
생성된 파일을 다운로드 하기위한 컨트롤러 입니다.
@Controller
@RequiredArgsConstructor
public class ItextPdfController {
@Autowired
private final ItextPdfUtil itextPdfUtil;
@RequestMapping("/attachment/pdf")
public void pdfDownload(HttpServletResponse response) {
// 미리 준비한 DTO 선언
ItextPdfDto itextPdfDto = new ItextPdfDto();
// pdf 파일이 저장될 경로 ( Mac 기준 )
itextPdfDto.setPdfFilePath("/Users/jeon/Documents/JavaProject/Blog/pdf/");
// pdf 파일이 저장될 경로 ( Windows 기준 )
// itextPdfDto.setPdfFilePath("C:\\Users\\hyeok\\Desktop\\pdf");
// pdf 파일명 ( 테스트를 위해 랜덤으로 생성 )
itextPdfDto.setPdfFileName(new Random().nextInt() + ".pdf");
// itextPdfDto.setPdfFileName("test.pdf");
// getHtml 에서 호출될 코드명
itextPdfDto.setPdfCode("hyeok");
// ======================= PDF 존재 유무 체크 =======================
// 없다면 PDF 파일 만들기
File file = itextPdfUtil.checkPDF(itextPdfDto);
int fileSize = (int) file.length();
// ===============================================================
// ===============================================================
// 파일 다운로드를 위한 header 설정
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename="+itextPdfDto.getPdfFileName()+";");
response.setContentLengthLong(fileSize);
response.setStatus(HttpServletResponse.SC_OK);
// ===============================================================
// 파일 다운로드
BufferedInputStream in = null;
BufferedOutputStream out = null;
// PDF 파일을 버퍼에 담은 후 다운로드
try{
in = new BufferedInputStream(new FileInputStream(file));
out = new BufferedOutputStream(response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
try {
byte[] buffer = new byte[4096];
int read = 0;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
Objects.requireNonNull(out).flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
이상으로 Java itext를 사용한 PDF만들기 ( HTML을 PDF로 만들기 ) 입니다.
아래 링크에서 원본 소스 확인 가능합니다.
GitHub : https://github.com/wjsskagur/itextPdf
감사합니다.
'Java > Java' 카테고리의 다른 글
[ Java ] 구글 Firebase 메시지 (앱 푸쉬 알림) 전송하기 (Admin SDK) (0) | 2023.05.25 |
---|---|
[ Java ] 파일 여러개를 zip으로 압축하여 다운로드 (0) | 2023.02.15 |